資料聚合和分組運算
GroupBy技術
主要流程為split-apply-combine(拆分-應用-合併),具體為:
分組運算的第一個階段,pandas物件中的資料會根據你所提供的一個或多個鍵被拆分為多組,拆分操作實在特定的軸上執行的。然後,將一個函式應用到各個分組併產生一個新值。最後,所有這些函式的執行結果會被合併到最終的結果物件中。
- 根據一個或多個鍵拆分pandas物件
分組鍵可以有多種形式,且型別不必相同:
- 列表或陣列,其長度與帶分組的軸一樣 - 表示DataFrame某個列名的值 - 字典或Series,給出待分組軸上的值與分組名之間的對應關係 - 函式,用於處理軸索引或索引中的各個標籤
來看示例:
df
Out[5]:
data1 data2 key1 key2
0 0.242283 1.316144 a one
1 0.535346 -1.398294 a two
2 0.516628 2.207073 b one
3 -1.262803 -0.352256 b two
4 0.653567 -0.407961 a one
按key1進行分組,並計算data1列的平均值
grouped = df['data1'].groupby(df['key1'])
grouped
Out[7]: <pandas.core.groupby.SeriesGroupBy object at 0x000001B734984160>
變數grouped是一個GroupBy物件,實際上他還沒有進行任何運算。
grouped.mean()
Out[8]:
key1
a 0.477065
b -0.373088
Name: data1, dtype: float64
如果一次傳入過個數組,就會得到一個層次化索引的Series
means=df['data1'].groupby([df['key1'],df['key2']]).mean()
means
Out[10]:
key1 key2
a one 0.447925
two 0.535346
b one 0.516628
two -1.262803
Name: data1, dtype: float64
同樣可以將列名作為分組鍵:
df.groupby('key1').mean()
Out[11]:
data1 data2
key1
a 0.477065 -0.163370
b -0.373088 0.927409
對分組進行迭代
for name, group in df.groupby('key1'):
print (name)
print (group)
a
data1 data2 key1 key2
0 0.242283 1.316144 a one
1 0.535346 -1.398294 a two
4 0.653567 -0.407961 a one
b
data1 data2 key1 key2
2 0.516628 2.207073 b one
3 -1.262803 -0.352256 b two
groupy預設是axis=0上進行分組的,可以設定axis在其他任何軸上進行分組。
選取一個或一組列
對於有DataFrame產生的GroupBy物件,如果用一個或一組列名對其索引,就能實現選取部分列進行聚合的目的。
df.groupby('key1')['data1']
Out[14]: <pandas.core.groupby.SeriesGroupBy object at 0x000001B7343A6550>
#它是df['data1'].groupby(df['key1'])的語法糖
- 應用組內轉換或其他運算
資料聚合分組運算的一種,它接受能夠將一維陣列簡化為標量值的函式。
如下面例子中,計算DataFrame列的樣本分位數
df
Out[5]:
data1 data2 key1 key2
0 -0.039369 0.080425 a one
1 -0.629809 0.198063 a two
2 1.179436 0.510897 b one
3 0.478062 0.304403 b two
4 0.772346 -0.315774 a one
grouped=df.groupby('key1')
grouped['data1'].quantile(0.9)
Out[7]:
key1
a 0.610003
b 1.109298
Name: data1, dtype: float64
如果你想使用你自己的聚合函式,只需將其傳入aggregate或agg即可
def peak_to_peak(arr):
return arr.max() - arr.min()
grouped.agg(peak_to_peak)
Out[9]:
data1 data2
key1
a 1.402154 0.513837
b 0.701374 0.206494
transform方法
people
Out[11]:
a b c d e
Joe -1.827769 -0.355652 0.552273 -1.898069 -0.543428
Steve -1.601029 0.562845 1.050669 -0.215205 -1.099391
Wes 1.866622 -1.534953 0.692820 -0.043697 0.240079
Jim -0.765684 1.837627 0.281838 0.218192 1.704804
Travis -0.716688 -1.390355 -0.725222 1.312762 0.374653
key = ['one','two','one','two','one']
people.groupby(key).mean()
Out[13]:
a b c d e
one -0.225945 -1.093653 0.173290 -0.209668 0.023768
two -1.183356 1.200236 0.666253 0.001493 0.302707
people.groupby(key).transform(np.mean)
Out[14]:
a b c d e
Joe -0.225945 -1.093653 0.173290 -0.209668 0.023768
Steve -1.183356 1.200236 0.666253 0.001493 0.302707
Wes -0.225945 -1.093653 0.173290 -0.209668 0.023768
Jim -1.183356 1.200236 0.666253 0.001493 0.302707
Travis -0.225945 -1.093653 0.173290 -0.209668 0.023768
可以看出,transform會將一個函式應用到各個分組,然後將結果放置到適當的位置上。如果個分組產生的是一個標量值,則該值就會被廣播出去。
假設你希望從各組中減去平均值,我們此案建立一個距平化函式,病傳給transform。
def demean(arr):
return arr - arr.mean()
demeaned = people.groupby(key).transform(demean)
demeaned
Out[17]:
a b c d e
Joe -1.601824 0.738002 0.378982 -1.688400 -0.567196
Steve -0.417672 -0.637391 0.384416 -0.216699 -1.402097
Wes 2.092567 -0.441300 0.519529 0.165971 0.216311
Jim 0.417672 0.637391 -0.384416 0.216699 1.402097
Travis -0.490743 -0.296701 -0.898512 1.522430 0.350885
apply方法:apply會將待處理的物件分成多個片段,然後對各片段呼叫傳入的函式,最後嘗試將各片段組合到一起。
假設你想要根據分組選出最高的5個tip_pct值,首先,編寫一個選取指定列具有最大值的行的函式
def top(df,n=5,column='tic_pct'):
return df.sort_index(by=column)[-n:]
top(tips,n=6)
D:\Anaconda3\lib\site-packages\ipykernel_launcher.py:2: FutureWarning: by argument to sort_index is deprecated, please use .sort_values(by=...)
Out[35]:
total_bill tip smoker day time size tip_pct tic_pct
109 14.31 4.00 Yes Sat Dinner 2 0.279525 0.279525
183 23.17 6.50 Yes Sun Dinner 4 0.280535 0.280535
232 11.61 3.39 No Sat Dinner 2 0.291990 0.291990
67 3.07 1.00 Yes Sat Dinner 1 0.325733 0.325733
178 9.60 4.00 Yes Sun Dinner 2 0.416667 0.416667
172 7.25 5.15 Yes Sun Dinner 2 0.710345 0.710345
現在,如果對smoker分組應用用該函式
tips.groupby('smoker').apply(top)
D:\Anaconda3\lib\site-packages\ipykernel_launcher.py:2: FutureWarning: by argument to sort_index is deprecated, please use .sort_values(by=...)
Out[36]:
total_bill tip smoker day time size tip_pct tic_pct
smoker
No 88 24.71 5.85 No Thur Lunch 2 0.236746 0.236746
185 20.69 5.00 No Sun Dinner 5 0.241663 0.241663
51 10.29 2.60 No Sun Dinner 2 0.252672 0.252672
149 7.51 2.00 No Thur Lunch 2 0.266312 0.266312
232 11.61 3.39 No Sat Dinner 2 0.291990 0.291990
Yes 109 14.31 4.00 Yes Sat Dinner 2 0.279525 0.279525
183 23.17 6.50 Yes Sun Dinner 4 0.280535 0.280535
67 3.07 1.00 Yes Sat Dinner 1 0.325733 0.325733
178 9.60 4.00 Yes Sun Dinner 2 0.416667 0.416667
172 7.25 5.15 Yes Sun Dinner 2 0.710345 0.710345
可以看出,top函式在DataFrame的各個片段上呼叫,然後結果由pandas.concat組裝到一起,並以分組名稱進行了標記。所以,最終結果就有了一個層次化索引,其內層索引值來自原DataFrame。
分位數和桶分析
pandas有一些能根據指定面元或樣本分位數將資料拆分分成的工具。將這些函式跟GroupBy結合起來,就能非常輕鬆地實現對資料集的桶或分位數分析了。
frame = pd.DataFrame({'data1': np.random.randn(1000),
'data2': np.random.randn(1000)})
factor = pd.cut(frame.data1, 4)
factor[:10]
Out[39]:
0 (1.002, 2.789]
1 (-2.579, -0.785]
2 (1.002, 2.789]
3 (-2.579, -0.785]
4 (-0.785, 1.002]
5 (-2.579, -0.785]
6 (-0.785, 1.002]
7 (-0.785, 1.002]
8 (1.002, 2.789]
9 (-0.785, 1.002]
Name: data1, dtype: category
Categories (4, interval[float64]): [(-2.579, -0.785] < (-0.785, 1.002] < (1.002, 2.789] < (2.789, 4.576]]
由cut返回的Factor物件可直接用於GroupBy。因此,我們可以像下面這樣對data2做一些統計計算:
def get_stats(group):
return {'min': group.min(), 'max': group.max(),
'count':group.count(), 'mean': group.mean()}
grouped = frame.data2.groupby(factor)
grouped.apply(get_stats).unstack()
Out[42]:
count max mean min
data1
(-2.579, -0.785] 200.0 2.614581 0.058889 -2.904774
(-0.785, 1.002] 636.0 3.180617 -0.078317 -3.641501
(1.002, 2.789] 162.0 2.740718 0.064263 -2.914572
(2.789, 4.576] 2.0 -0.027067 -0.200847 -0.374627
- 計算透視表或交叉表
透視表是各種電子表格程式和其他資料分析軟體中一種常見的資料彙總工具。待續。