本文的Pandas知識點包括:
1、合并數據集
2、重塑和軸向旋轉
3、數據轉換
4、數據聚合
1、合并數據集
Pandas中合并數據集有多種方式,這里我們來逐一介紹
1.1 數據庫風格合并
數據庫風格的合并指根據索引或某一列的值是否相等進行合并的方式,在pandas中,這種合并使用merge以及join函數實現。
先來看下面的例子:
df1 = pd.DataFrame({'key':['b','b','a','c','a','a','b'],'data1':range(7)})
df2 = pd.DataFrame({'key':['a','b','d'],'data2':range(3)})
pd.merge(df1,df2)
如果merge函數只指定了兩個DataFrame,它會自動搜索兩個DataFrame中相同的列索引,即key,當然,這可以進行指定,下面的語句和上面是等價的:
pd.merge(df1,df2,on='key')
當兩個DataFrame沒有相同的列索引時,我們可以指定鏈接的列:
#如果兩個DataFrame的列名不同,可以分別指定
df3 = pd.DataFrame({'lkey':['b','b','a','c','a','a','b'],'data1':range(7)})
df4 = pd.DataFrame({'rkey':['a','b','d'],'data2':range(3)})
pd.merge(df3,df4,left_on='lkey',right_on='rkey')
同時我們可以看到,merge默認情況下采用的是內鏈接方式,當然我們可以通過how參數指定鏈接方式:
pd.merge(df1,df2,how='outer')
要根據多個鍵進行合并,傳入一組由列名組成的列表即可:
left = pd.DataFrame({'key1':['foo','foo','bar'],'key2':['one','two','one'],'lval':[1,2,3]})
right = pd.DataFrame({'key1':['foo','foo','bar','bar'],'key2':['one','one','one','two'],'rval':[4,5,6,7]})
pd.merge(left,right,on=['key1','key2'],how='outer')
上面兩個表有兩列重復的列,如果只根據一列進行合并,則會多出一列重復列,重復列名的處理我們一般使用merge的suffixes屬性,可以幫我們指定重復列合并后的列名:
pd.merge(left,right,on='key1',suffixes=('_left','_right'))
上面的on、left_on、right_on都是根據列值進行合并的,如果我們想用索引進行合并,使用left_index 或者 right_index屬性:
left1 = pd.DataFrame({'key':['a','b','a','a','b','c'],'value':range(6)})
right1 = pd.DataFrame({'group_val':[3.5,7]},index=['a','b'])
pd.merge(left1,right1,left_on='key',right_index=True)
對于層次化索引的數據,我們必須以列表的形式指明用作合并鍵的多個列:
lefth = pd.DataFrame({'key1':['Ohio','Ohio','Ohio','Nevada','Nevada'],
'key2':[2000,2001,2002,2001,2002],
'data':np.arange(5.0)})
righth = pd.DataFrame(np.arange(12).reshape((6,2)),
index=[['Nevada','Nevada','Ohio','Ohio','Ohio','Ohio'],[2001,2000,2000,2000,2001,2002]],
columns=['event1','event2'])
pd.merge(lefth,righth,left_on=['key1','key2'],right_index=True)
如果單純想根據索引進行合并,使用join方法會更加簡單:
left2 = pd.DataFrame([[1.0,2.0],[3.0,4.0],[5.0,6.0]],index = ['a','c','e'],columns=['Ohio','Nevada'])
right2 = pd.DataFrame([[7.0,8.0],[9.0,10.0],[11.0,12.0],[13.0,14.0]],index = ['b','c','d','e'],columns=['Missouri','Alabama'])
left2.join(right2,how='outer')
1.2 軸向鏈接
pandas的軸向鏈接指的是根據某一個軸向來拼接數據,類似于列表的合并。concat函數,默認在軸0上工作,我們先來看一個Series的例子:
s1 = pd.Series([0,1],index=['a','b'])
s2 = pd.Series([2,3,4],index=['c','d','e'])
s3 = pd.Series([5,6],index=['f','g'])
pd.concat([s1,s2,s3])
#輸出
a 0
b 1
c 2
d 3
e 4
f 5
g 6
dtype: int64
pd.concat([s1,s2,s3],axis=1)
#輸出
0 1 2
a 0.0 NaN NaN
b 1.0 NaN NaN
c NaN 2.0 NaN
d NaN 3.0 NaN
e NaN 4.0 NaN
f NaN NaN 5.0
g NaN NaN 6.0
在上面的情況下,參與連接的片段在結果中區分不開,假設你想要在連接軸上創建一個層次化索引,我們可以額使用keys參數:
result = pd.concat([s1,s1,s3],keys=['one','two','three'])
result
#輸出
one a 0
b 1
two a 0
b 1
three f 5
g 6
dtype: int64
如果是沿著axis=1進行軸向合并,keys會變為列索引:
pd.concat([s1,s1,s3],keys=['one','two','three'],axis=1)
上面的邏輯同樣適用于DataFrame的軸向合并:
df1 = pd.DataFrame(np.arange(6).reshape((3,2)),index=['a','b','c'],columns=['one','two'])
df2 = pd.DataFrame(5 + np.arange(4).reshape((2,2)),index=['a','c'],columns=['three','four'])
pd.concat([df1,df2],axis=1,keys=['level1','level2'])
#下面的操作會得到與上面同樣的效果
pd.concat({"level1":df1,'level2':df2},axis=1)
使用ignore_index參數可以不保留軸上的索引,產生一組新的索引:
df1 = pd.DataFrame(np.arange(6).reshape((3,2)),index=[1,2,3],columns=['one','two'])
df2 = pd.DataFrame(5 + np.arange(4).reshape((2,2)),index=[1,2],columns=['three','four'])
pd.concat([df1,df2],ignore_index = True)
2、重塑和軸向旋轉
在重塑和軸向旋轉中,有兩個重要的函數,二者互為逆操作:
stack:將數據的列旋轉為行
unstack:將數據的行旋轉為列
先來看下面的例子:
data = pd.DataFrame(np.arange(6).reshape((2,3)),index=pd.Index(['Ohio','Colorado'],name='state'),
columns=pd.Index(['one','two','three'],name='number'))
result = data.stack()
result
我們使用unstack()將數據的列旋轉為行,默認是最里層的行索引:
result.unstack()
默認unstack是將最里層的行索引旋轉為列索引,不過我們可以指定unstack的層級,unstack之后作為旋轉軸的級別將會成為結果中的最低級別,當然,我們也可以根據名字指定要旋轉的索引,下面兩句代碼是等價的:
result.unstack(0)
result.unstack('state')
如果不是所有的級別都能在分組中找到的話,unstack操作可能會產生缺失數據:
s1 = pd.Series([0,1,2,3],index=['a','b','c','d'])
s2 = pd.Series([4,5,6],index=['c','d','e'])
data2 = pd.concat([s1,s2],keys=['one','two'])
data2.unstack()
stack操作默認會過濾掉缺失值,不過可以使用dropna參數選擇不過濾缺失值:
data2.unstack().stack()
#輸出
one a 0.0
b 1.0
c 2.0
d 3.0
two c 4.0
d 5.0
e 6.0
dtype: float64
data2.unstack().stack(dropna=False)
#輸出
one a 0.0
b 1.0
c 2.0
d 3.0
e NaN
two a NaN
b NaN
c 4.0
d 5.0
e 6.0
dtype: float64
3、數據轉換
3.1 移除重復數據
移除重復數據,使用drop_duplicates方法,該方法默認判斷全部列,不過我們也可以根據指定列進行去重.
data = pd.DataFrame({'k1':['one']*3 + ['two'] * 4,'k2':[1,1,2,3,3,4,4]})
data.drop_duplicates()
#輸出
<bound method DataFrame.drop_duplicates of k1 k2
0 one 1
1 one 1
2 one 2
3 two 3
4 two 3
5 two 4
6 two 4>
data.drop_duplicates(['k2'])
#輸出
k1 k2
0 one 1
2 one 2
3 two 3
5 two 4
默認對于重復數據,系統會保留第一項,即keep參數的默認值為first,不過我們也可以保留最后一項,只需將keep參數設置為last即可:
data.drop_duplicates(['k2'],keep='last')
#輸出
k1 k2
1 one 1
2 one 2
4 two 3
6 two 4
3.2 map函數
在對數據集進行轉換時,你可能希望根據數組、Series或者DataFrame列中的值來實現該轉換工作,我們來看看下面的肉類數據的處理:
data = pd.DataFrame({'food':['bacon','pulled pork','bacon',
'Pastrami','corned beef','Bacon','pastrami','honey ham','nova lox'],
'ounces':[4,3,12,6,7.5,8,3,5,6]})
meat_to_animal = {
'bacon':'pig',
'pulled pork':'pig',
'pastrami':'cow',
'corned beef':'cow',
'honey ham':'pig',
'nova lox':'salmon'
}
#Series的map方法接受一個函數或含有映射關系的字典對象,對元素進行相應的轉換
data['animal']=data['food'].map(str.lower).map(meat_to_animal)
data
使用下面的代碼是等價的:
data['animal'] = data['food'].map(lambda x: meat_to_animal[x.lower()])
3.3 replace值替換
使用replace方法進行值替換,返回一個新的對象。如果希望對不同的值進行不同的替換,傳入一個由替換關系組成的列表或者字典即可:
data = pd.Series([1,-999,2,-999,-1000,3])
data.replace(-999,np.nan)
#輸出
0 1
1 -999
2 2
3 -999
4 -1000
5 3
dtype: int64
data.replace([-999,-1000],[np.nan,0])
#輸出
0 1.0
1 NaN
2 2.0
3 NaN
4 0.0
5 3.0
dtype: float64
3.4 離散化和面元劃分
根據區間對數據進行劃分,使用cut函數,比如我們想根據年齡區間對人群進行劃分,從而得到不同年齡段的人數統計:
ages = [20,22,25,27,21,23,37,31,61,45,41,32]
bins = [18,25,35,60,100]
cats = pd.cut(ages,bins)
cats
#輸出
[(18, 25], (18, 25], (18, 25], (25, 35], (18, 25], ..., (25, 35], (60, 100], (35, 60], (35, 60], (25, 35]]
Length: 12
Categories (4, interval[int64]): [(18, 25] < (25, 35] < (35, 60] < (60, 100]]
cut函數返回一個特殊的Categorical對象,可以通過codes來查看個數據的組別編號:
cats.codes
#輸出
array([0, 0, 0, 1, 0, 0, 2, 1, 3, 2, 2, 1], dtype=int8)
使用value_counts可以實現分組計數:
pd.value_counts(cats)
#輸出
(18, 25] 5
(35, 60] 3
(25, 35] 3
(60, 100] 1
dtype: int64
上面是前開后閉區間,如果想要變為前閉后開區間,只需要設置right=False參數:
cats = pd.cut(ages,bins,right=False)
pd.value_counts(cats)
#輸出
[25, 35) 4
[18, 25) 4
[35, 60) 3
[60, 100) 1
dtype: int64
也可以設置自己的面元名稱,將labels選項設為一個列表或者數組即可:
group_names = ['Youth','YoungAdult','MiddleAged','Senior']
pd.cut(ages,bins,labels=group_names)
#輸出
[Youth, Youth, Youth, YoungAdult, Youth, ..., YoungAdult, Senior, MiddleAged, MiddleAged, YoungAdult]
Length: 12
Categories (4, object): [MiddleAged < Senior < YoungAdult < Youth]
如果向cut傳入的不是面元邊界而是面元的數量,則會根據數據的最大值和最小值自動計算等長面元,比如下面的例子將均勻分布的數據分為四組:
data = np.random.rand(20)
pd.cut(data,4,precision=2)
pandas還提供了一個對數據進行劃分的函數:qcut。qcut基于樣本分位數對數據進行面元劃分,可以自定義分位數,也可以傳入一個數量(會自動計算分位數):
data = np.random.randn(1000)
cats = pd.qcut(data,4)
pd.value_counts(cats)
#輸出
(0.701, 3.451] 250
(0.0727, 0.701] 250
(-0.57, 0.0727] 250
(-3.84, -0.57] 250
dtype: int64
pd.value_counts(pd.qcut(data,[0,0.1,0.5,0.9,1]))
#輸出
(0.0727, 1.338] 400
(-1.247, 0.0727] 400
(1.338, 3.451] 100
(-3.84, -1.247] 100
dtype: int64
3.5 排列和隨機采樣
利用numpy.random.permutation函數可以輕松實現對Series或者DataFrame的列的排列工作,通過需要排列的軸的長度調用permutation,可產生一個表示新順序的整數數組,最后使用pandas的take函數返回指定大小的數據即可實現采樣。
df = pd.DataFrame(np.arange(5*4).reshape(5,4))
sampler = np.random.permutation(5)
df.take(sampler[:3])
4、數據聚合
4.1 數據分組
pandas中的數據分組使用groupby方法,返回的是一個GroupBy對象,對分組之后的數據,我們可以使用一些聚合函數進行聚合,比如求平均值mean:
df = pd.DataFrame({
'key1' :['a','a','b','b','a'],
'key2':['one','two','one','two','one'],
'data1':np.random.randn(5),
'data2':np.random.randn(5)
})
groupd = df['data1'].groupby(df['key1'])
groupd
#<pandas.core.groupby.SeriesGroupBy object at 0x118814dd8>
groupd.mean()
#輸出
key1
a 0.697500
b -0.068161
Name: data1, dtype: float64
上面是進行分組的一個簡單的例子,我們可以根據多種數據格式進行數據分組,下面就一一整理一下:
Series
means = df['data1'].groupby([df['key1'],df['key2']]).mean()
means
#輸出
key1 key2
a one 0.543936
two 1.004630
b one 0.219453
two -0.355776
Name: data1, dtype: float64
合適長度的數組
分組鍵可以是任何適當長度的數組,數組中每一個元素的值代表相應下標的記錄的分組鍵:
states = np.array(['Ohio','Nevada','Nevada','Ohio','Ohio'])
years = np.array([2005,2005,2006,2005,2006])
df['data1'].groupby([states,years]).mean()
列名
df.groupby('key1').mean()
df.groupby(['key1','key2']).mean()
你可能已經注意到了,在執行df.groupby('key1').mean()的結果中,結果并沒有key2這一列,這是因為key2這一列不是數值數據,所以從結果中排除了,默認情況下,所有的數值列都會被聚合,雖然有時可能會被過濾為一個子集。
map
#我們還可以根據map來進行分組
people = pd.DataFrame(np.random.randn(5,5),columns=['a','b','c','d','e'],index=['Joe','Steve','Wes','Jim','Travis'])
mapping = {'a':'red','b':'red','c':'blue','d':'blue','e':'red','f':'orange'}
people.groupby(mapping,axis=1).sum()
Python函數
假如你想根據人名的長度進行分組,雖然可以求取一個字符串長度數組,其實僅僅傳入len函數就可以了:
people.groupby(len).sum()
索引級別
columns = pd.MultiIndex.from_arrays([['US','US','US','JP','JP'],[1,3,5,1,3]],names=['city','tenor'])
hier_df = pd.DataFrame(np.random.randn(4,5),columns=columns)
hier_df.groupby(level='city',axis=1).count()
分組之后產生一個GroupBy對象,這個對象支持迭代,是一個由(分組名,數據塊)組成的二元組:
for name,group in df.groupby('key1'):
print(name)
print(group)
groupby默認是在axis=0上分組的,不過我們也可以在axis=1上分組,比如根據列的數據類型進行分組:
for name,group in df.groupby(df.dtypes,axis=1):
print(name)
print(group)
4.2 數據聚合操作
特定聚合函數
我們可以像之前一樣使用一些特定的聚合函數,比如sum,mean等等,但是同時也可以使用自定義的聚合函數,只需將其傳入agg方法中即可:
df = pd.DataFrame({
'key1' :['a','a','b','b','a'],
'key2':['one','two','one','two','one'],
'data1':np.random.randn(5),
'data2':np.random.randn(5)
})
def peak_to_peak(arr):
return arr.max()-arr.min()
grouped = df.groupby('key1')
grouped.agg(peak_to_peak)
關于agg還有更多的功能,我們使用小費數據(下載地址:http://pan.baidu.com/s/1bpGW3Av 密碼:2p9v),我們讀入數據,并計算小費率一列:
tips = pd.read_csv('data/ch08/tips.csv')
tips['tip_pct'] = tips['tip'] / tips['total_bill']
tips.head(10)
可以同時使用多個聚合函數,此時得到的DataFrame的列就會以相應的函數命名:
grouped = tips.groupby(['sex','smoker'])
grouped_pct = grouped['tip_pct']
grouped_pct.agg(['mean','std',peak_to_peak])
你如果不想接受這些自動給出的列名,你可以用(name,function)的方法指定你的列名:
grouped_pct.agg([('foo','mean'),('bar',np.std)])
假如你想要對不同的列應用不同的函數,具體的辦法是向agg傳入一個從列名映射到函數的字典:
grouped.agg({'tip':[np.max,'min'],'size':'sum'})
如果你想以無索引的方式返回聚合數據,可是設置as_index=False:
tips.groupby(['sex','smoker'],as_index=False).mean()
transform函數
transform會將一個函數運用到各個分組,然后將結果放置到適當的位置上。如果個分組產生的是一個標量值,則該值將會被廣播出去,如果分組產生的是一個相同大小的數組,則會根據下標放置到適當的位置上。
people = pd.DataFrame(np.random.randn(5,5),columns=['a','b','c','d','e'],index=['Joe','Steve','Wes','Jim','Travis'])
key = ['one','two','one','two','one']
people.groupby(key).transform(np.mean)
可以看到,在上面的例子中,分組產生了一個標量,即分組的平均值,然后transform將這個值映射到對應的位置上,現在DataFrame中每個位置上的數據都是對應組別的平均值。假設我們希望從各組中減去平均值,可以用下面的方法實現:
def demean(arr):
return arr - arr.mean()
demeaned = people.groupby(key).transform(demean)
demeaned
apply函數
同agg一樣,transform也是有嚴格條件的函數,傳入的函數只能產生兩種結果:要么產生一個可以廣播的標量值,如np.mean,要么產生一個相同大小的結果數組.最一般化的GroupBy方法是apply,apply將會待處理的對象拆分成多個片段,然后對各片段調用傳入的函數,最后嘗試將各片段組合到一起.
def top(df,n=5,column='tip_pct'):
return df.sort_values(by=column)[-n:]
tips.groupby('smoker').apply(top)
如果傳入apply的方法里有可變參數的話,我們可以自定義這些參數的值:
tips.groupby(['smoker','day']).apply(top,n=1,column='total_bill')
從上面的例子可以看出,分組鍵會跟原始對象的索引共同構成結果對象中的層次化索引。將group_keys=False傳入groupby即可禁止該效果:
tips.groupby(['smoker'],group_keys=False).apply(top)
4.3 數據透視表
透視表是各種電子表格程序和其他數據分析軟件中一種常見的數據匯總工具,它根據一個或多個鍵對數據進行聚合,并根據行和列傷的分組鍵將數據分配到各個矩形區域中。
考慮我們的小費數據集,我們想聚合tip_pct和size,想根據day進行分組,將smoker放到列上,將day放到行上:
tips.pivot_table(['tip_pct','size'],index=['sex','day'],columns='smoker')
如果想增加匯總統計列,可以增加margins=True參數:
tips.pivot_table(['tip_pct','size'],index=['sex','day'],columns='smoker',margins=True)
如果想使用其他聚合函數,將其傳入aggfunc即可,例如使用count或len可以得到有關分組大小的交叉表:
tips.pivot_table('tip_pct',index=['sex','smoker'],columns='day',aggfunc=len,margins=True)
可以使用fill_value填充缺失值:
tips.pivot_table('size',index=['time','sex','smoker'],columns='day',aggfunc=sum,fill_value=0)