數(shù)據(jù)聚合與分組運(yùn)算
對(duì)數(shù)據(jù)集進(jìn)行分組并對(duì)各組應(yīng)用一個(gè)函數(shù)(無論是聚合還是轉(zhuǎn)換)寻仗,這是數(shù)據(jù)分析工作中的重要環(huán)節(jié)仲墨。在將數(shù)據(jù)集準(zhǔn)備好之后,通常的任務(wù)就是計(jì)算分組統(tǒng)計(jì)或生成透視表
在本章中蹬碧,你將會(huì)學(xué)到:
- 根據(jù)一個(gè)或多個(gè)鍵(可以是函數(shù)色鸳、數(shù)組或DataFrame列名)拆分pandas對(duì)象。
- 計(jì)算分組摘要統(tǒng)計(jì)坠七,如計(jì)數(shù)水醋、平均值、標(biāo)準(zhǔn)差彪置,或用戶自定義函數(shù)拄踪。
- 對(duì)DataFrame的列應(yīng)用各種各樣的函數(shù)。
- 應(yīng)用組內(nèi)轉(zhuǎn)換或其他運(yùn)算拳魁,如規(guī)格化惶桐、線性回歸、排名或選取子集等潘懊。
- 計(jì)算透視表或交叉表姚糊。
- 執(zhí)行分位數(shù)分析以及其他分組分析。
分組運(yùn)算的第一個(gè)階段授舟,pandas對(duì)象(無論是Series救恨、DataFrame還是其他的)中的數(shù)據(jù)會(huì)根據(jù)你所提供的一個(gè)或多個(gè)鍵被拆分(split)為多組。拆分操作是在對(duì)象的特定軸上執(zhí)行的释树。例如肠槽,DataFrame可以在其行(axis=0)或列(axis=1)上進(jìn)行分組。然后躏哩,將一個(gè)函數(shù)應(yīng)用(apply)到各個(gè)分組并產(chǎn)生一個(gè)新值署浩。最后揉燃,所有這些函數(shù)的執(zhí)行結(jié)果會(huì)被合并(combine)到最終的結(jié)果對(duì)象中扫尺。結(jié)果對(duì)象的形式一般取決于數(shù)據(jù)上所執(zhí)行的操作。圖9-1大致說明了一個(gè)簡單的分組聚合過程炊汤。
GroupBy技術(shù)
分組鍵可以有多種形式正驻,且類型不必相同:
列表或數(shù)組,其長度與待分組的軸一樣抢腐。
表示DataFrame某個(gè)列名的值姑曙。
字典或Series,給出待分組軸上的值與分組名之間的對(duì)應(yīng)關(guān)系迈倍。
函數(shù)伤靠,用于處理軸索引或索引中的各個(gè)標(biāo)簽。
from pandas import Series,DataFrame
import pandas as pd
import numpy as np
df=DataFrame({'key1':['a','a','b','b','a'],'key2':['one','two','one','two','one'],
'data1':np.random.randn(5),'data2':np.random.randn(5)})
df
按key1進(jìn)行分組啼染,并計(jì)算data1列的平均值宴合』烂罚可以通過訪問data1,并根據(jù)key1調(diào)用groupby
grouped=df['data1'].groupby(df['key1'])
grouped
<pandas.core.groupby.SeriesGroupBy object at 0x000000001052FDA0>
變量grouped是一個(gè)GroupBy對(duì)象,接下來調(diào)用GroupBy的mean方法來計(jì)算分組平均值
grouped.mean()
key1
a 0.597727
b 0.318738
Name: data1, dtype: float64
如果一次傳入多個(gè)數(shù)組卦洽,會(huì)得到不同的結(jié)果
means=df['data1'].groupby([df['key1'],df['key2']]).mean()
means
key1 key2
a one 0.595071
two 0.603039
b one -0.077999
two 0.715474
Name: data1, dtype: float64
上面通過兩個(gè)鍵對(duì)數(shù)據(jù)進(jìn)行了分組贞言,得到的Series具有一個(gè)層次化索引(由唯一的鍵對(duì)組成),下面進(jìn)行調(diào)換
means.unstack()
以上的例子中分組鍵都是Series阀蒂。其實(shí)该窗,分組鍵可以是任何長度適當(dāng)?shù)臄?shù)組
states=np.array(['Ohio','California','California','Ohio','Ohio'])
years=np.array([2005,2005,2006,2005,2006])
states
array(['Ohio', 'California', 'California', 'Ohio', 'Ohio'],
dtype='<U10')
years
array([2005, 2005, 2006, 2005, 2006])
df['data1'].groupby([states,years]).mean()
California 2005 0.603039
2006 -0.077999
Ohio 2005 0.107602
2006 1.690412
Name: data1, dtype: float64
可以使用列名的字符串、數(shù)字或其他Python對(duì)象用作分組鍵
df.groupby('key1').mean()
df.groupby(['key1','key2']).mean()
groupby的size方法,可以返回一個(gè)含有分組大小的Series
df.groupby(['key1','key2']).size()
key1 key2
a one 2
two 1
b one 1
two 1
dtype: int64
對(duì)分組進(jìn)行迭代
groupby對(duì)象可以產(chǎn)生一組二元元組(由分組名和數(shù)據(jù)塊組成)
for name,group in df.groupby('key1'):
print(name)
print(group)
a
data1 data2 key1 key2
0 -0.500271 1.820004 a one
1 0.603039 -0.783806 a two
4 1.690412 -2.138761 a one
b
data1 data2 key1 key2
2 -0.077999 -1.464172 b one
3 0.715474 1.048138 b two
對(duì)于多重鍵的情況蚤霞,元組的第一個(gè)元素將會(huì)是由鍵值組成的元組
for (k1,k2),group in df.groupby(['key1','key2']):
print(k1,k2)
print(group)
a one
data1 data2 key1 key2
0 -0.500271 1.820004 a one
4 1.690412 -2.138761 a one
a two
data1 data2 key1 key2
1 0.603039 -0.783806 a two
b one
data1 data2 key1 key2
2 -0.077999 -1.464172 b one
b two
data1 data2 key1 key2
3 0.715474 1.048138 b two
將這些數(shù)據(jù)片段做成一個(gè)字典
pieces=dict(list(df.groupby('key1')))
pieces
{'a': data1 data2 key1 key2
0 -0.500271 1.820004 a one
1 0.603039 -0.783806 a two
4 1.690412 -2.138761 a one, 'b': data1 data2 key1 key2
2 -0.077999 -1.464172 b one
3 0.715474 1.048138 b two}
pieces['b']
groupby默認(rèn)是在axis=0上進(jìn)行分組的,下面根據(jù)dtype對(duì)列進(jìn)行分組
df.dtypes
data1 float64
data2 float64
key1 object
key2 object
dtype: object
grouped=df.groupby(df.dtypes,axis=1)
dict(list(grouped))
{dtype('float64'): data1 data2
0 -0.500271 1.820004
1 0.603039 -0.783806
2 -0.077999 -1.464172
3 0.715474 1.048138
4 1.690412 -2.138761, dtype('O'): key1 key2
0 a one
1 a two
2 b one
3 b two
4 a one}
選取一個(gè)或一組列
對(duì)于由DataFrame產(chǎn)生的GroupBy對(duì)象酗失,如果用一個(gè)(單個(gè)字符串)或一組(字符串?dāng)?shù)組)列名對(duì)其進(jìn)行索引,就能實(shí)現(xiàn)選取部分列進(jìn)行聚合的目的
df.groupby('key1')['data1']
<pandas.core.groupby.SeriesGroupBy object at 0x00000000105DC400>
df['data1'].groupby(df['key1'])
<pandas.core.groupby.SeriesGroupBy object at 0x00000000105DC7F0>
對(duì)于大數(shù)據(jù)集昧绣,很可能只需要對(duì)部分列進(jìn)行聚合级零。例如,在前面那個(gè)數(shù)據(jù)集中滞乙,如果只需計(jì)算data2列的平均值并以DataFrame形式得到結(jié)果奏纪,我們可以編寫
df.groupby(['key1','key2'])[['data2']].mean()
這種索引操作所返回的對(duì)象是一個(gè)已分組的DataFrame(如果傳入的是列表或數(shù)組)或已分組的Series(如果傳入的是標(biāo)量形式的單個(gè)列名)
s_grouped=df.groupby(['key1','key2'])['data2']
s_grouped
<pandas.core.groupby.SeriesGroupBy object at 0x00000000105C7470>
s_grouped.mean()
key1 key2
a one -0.159378
two -0.783806
b one -1.464172
two 1.048138
Name: data2, dtype: float64
通過字典或Series進(jìn)行分組
除數(shù)組以外,分組信息還可以其他形式存在斩启,實(shí)例:DataFrame
people=DataFrame(np.random.randn(5,5),columns=['a','b','c','d','e'],
index=['Joe','Steve','Wes','Jim','Travis'])
people.loc[2:3,['b','c']]=np.nan #添加NA值
people
假設(shè)已知列的分組關(guān)系序调,并希望根據(jù)分組計(jì)算列的總計(jì),首先把這個(gè)字典傳給groupby
mapping={'a':'red','b':'red','c':'blue','d':'blue','e':'red','f':'orange'}
by_column=people.groupby(mapping,axis=1)
by_column.sum()
Series也有同樣的功能,它可以被看做一個(gè)固定大小的映射兔簇。這個(gè)例子发绢,如果用Series作為分組鍵,則pandas會(huì)檢查Series以確保其索引跟分組軸是對(duì)齊的
map_series=Series(mapping)
map_series
a red
b red
c blue
d blue
e red
f orange
dtype: object
people.groupby(map_series,axis=1).count()
blue | red | |
---|---|---|
Joe | 2 | 3 |
Steve | 2 | 3 |
Wes | 1 | 2 |
Jim | 2 | 3 |
Travis | 2 | 3 |
通過函數(shù)進(jìn)行分組
任何被當(dāng)做分組鍵的函數(shù)都會(huì)在各個(gè)索引值上被調(diào)用一次垄琐,其返回值就會(huì)被用作分組名稱边酒。
具體點(diǎn)說,以上的示例DataFrame為例狸窘,其索引值為人的名字墩朦。假設(shè)你希望根據(jù)人名的長度進(jìn)行分組,雖然可以求取一個(gè)字符串長度數(shù)組翻擒,但其實(shí)僅僅傳入len函數(shù)
people.groupby(len).sum()
將函數(shù)跟數(shù)組氓涣、列表、字典陋气、Series混合使用可以劳吠,因?yàn)槿魏螙|西最終都會(huì)被轉(zhuǎn)換為數(shù)組
key_list=['one','one','one','two','two']
people.groupby([len,key_list]).min()
根據(jù)索引級(jí)別分組
層次化索引數(shù)據(jù)集最方便的地方就在于它能夠根據(jù)索引級(jí)別進(jìn)行聚合。通過level關(guān)鍵字傳入級(jí)別編號(hào)或名稱
columns=pd.MultiIndex.from_arrays([['US','US','US','JP','JP'],
[1,3,5,1,3]],names=['cty','tenor'])
hier_df=DataFrame(np.random.randn(4,5),columns=columns)
hier_df
hier_df.groupby(level='cty',axis=1).count()
數(shù)據(jù)聚合
對(duì)于聚合巩趁,指的是任何能夠從數(shù)組產(chǎn)生標(biāo)量值的數(shù)據(jù)轉(zhuǎn)換過程痒玩。
使用自己發(fā)明的聚合運(yùn)算,還可以調(diào)用分組對(duì)象上已經(jīng)定義好的任何方法。
例如蠢古,quantile可以計(jì)算Series或DataFrame列的樣本分位數(shù)
df=DataFrame({'key1':['a','a','b','b','a'],'key2':['one','two','one','two','one'],
'data1':np.random.randn(5),'data2':np.random.randn(5)})
df
grouped=df.groupby('key1')
grouped['data1'].quantile(0.9)
key1
a -0.932423
b -0.006997
Name: data1, dtype: float64
如果要使用你自己的聚合函數(shù)燃观,只需將其傳入aggregate或agg方法
def peak_to_peak(arr):
return arr.max()-arr.min()
grouped.agg(peak_to_peak)
describe也可以使用,即使嚴(yán)格來講便瑟,它們并非聚合運(yùn)算
grouped.describe()
data1 data2
key1
a count 3.000000 3.000000
mean 0.746672 0.910916
std 1.109736 0.712217
min -0.204708 0.092908
25% 0.137118 0.669671
50% 0.478943 1.246435
75% 1.222362 1.319920
max 1.965781 1.393406
b count 2.000000 2.000000
mean -0.537585 0.525384
std 0.025662 0.344556
min -0.555730 0.281746
25% -0.546657 0.403565
50% -0.537585 0.525384
75% -0.528512 0.647203
max -0.519439 0.769023
注意:自定義聚合函數(shù)要比表9-1中那些經(jīng)過優(yōu)化的函數(shù)慢得多缆毁。這是因?yàn)樵跇?gòu)造中間分組數(shù)據(jù)塊時(shí)存在非常大的開銷(函數(shù)調(diào)用、數(shù)據(jù)重排等)
為了說明一些更高級(jí)的聚合功能到涂,將使用一個(gè)有關(guān)餐館小費(fèi)的數(shù)據(jù)集(在本書的GitHub庫中)https://github.com/wesm/pydata-book/tree/1st-edition
通過read_csv加載之后脊框,添加一個(gè)表示小費(fèi)比例的列tip_pct
tips=pd.read_csv('pydata_book/ch08/tips.csv') #加載數(shù)據(jù)
tips[:8] #選取前8個(gè)數(shù)據(jù)集
total_bill tip sex smoker day time size
0 16.99 1.01 Female No Sun Dinner 2
1 10.34 1.66 Male No Sun Dinner 3
2 21.01 3.50 Male No Sun Dinner 3
3 23.68 3.31 Male No Sun Dinner 2
4 24.59 3.61 Female No Sun Dinner 4
5 25.29 4.71 Male No Sun Dinner 4
6 8.77 2.00 Male No Sun Dinner 2
7 26.88 3.12 Male No Sun Dinner 4
tips['tip_pct']=tips['tip']/tips['total_bill'] #添加“小費(fèi)占總額百分比”的列
tips[:8]
total_bill tip sex smoker day time size tip_pct
0 16.99 1.01 Female No Sun Dinner 2 0.059447
1 10.34 1.66 Male No Sun Dinner 3 0.160542
2 21.01 3.50 Male No Sun Dinner 3 0.166587
3 23.68 3.31 Male No Sun Dinner 2 0.139780
4 24.59 3.61 Female No Sun Dinner 4 0.146808
5 25.29 4.71 Male No Sun Dinner 4 0.186240
6 8.77 2.00 Male No Sun Dinner 2 0.228050
7 26.88 3.12 Male No Sun Dinner 4 0.116071
面向列的多函數(shù)應(yīng)用
我們已經(jīng)看到,對(duì)Series或DataFrame列的聚合運(yùn)算其實(shí)就是使用aggregate(使用自定義函數(shù))或調(diào)用諸如mean践啄、std之類的方法浇雹。然而,你可能希望對(duì)不同的列使用不同的聚合函數(shù)屿讽,或一次應(yīng)用多個(gè)函數(shù)昭灵。
下面將練習(xí)各種實(shí)例
grouped=tips.groupby(['sex','smoker'])#根據(jù)sex和smoker對(duì)tips進(jìn)行分組
grouped_pct=grouped['tip_pct'] #將函數(shù)名以字符串的形式傳入
grouped_pct.agg('mean')
sex smoker
Female No 0.156921
Yes 0.182150
Male No 0.160669
Yes 0.152771
Name: tip_pct, dtype: float64
傳入一組函數(shù)或函數(shù)名,得到的DataFrame的列就會(huì)以相應(yīng)的函數(shù)命名
grouped_pct.agg(['mean','std',peak_to_peak])
mean std peak_to_peak
sex smoker
Female No 0.156921 0.036421 0.195876
Yes 0.182150 0.071595 0.360233
Male No 0.160669 0.041849 0.220186
Yes 0.152771 0.090588 0.674707
如果傳入的是一個(gè)由(name,function)元組組成的列表伐谈,則各元組的第一個(gè)元素就會(huì)被用作DataFrame的列名(可以將這種二元元組列表看做一個(gè)有序映射)
grouped_pct.agg([('foo','mean'),('bar',np.std)])
foo bar
sex smoker
Female No 0.156921 0.036421
Yes 0.182150 0.071595
Male No 0.160669 0.041849
Yes 0.152771 0.090588
對(duì)于DataFram,定義一組應(yīng)用于全部列的函數(shù)烂完,或不同的列應(yīng)用不同的函數(shù)。假設(shè)我們想要對(duì)tip_pct和total_bill列計(jì)算三個(gè)統(tǒng)計(jì)信息
functions=['count','mean','max']
result=grouped['tip_pct','total_bill'].agg(functions)
result
結(jié)果DataFrame擁有層次化的列诵棵,這相當(dāng)于分別對(duì)各列進(jìn)行聚合抠蚣,然后用concat將結(jié)果組裝到一起(列名用作keys參數(shù))
result['tip_pct']
傳入帶有自定義名稱的元組列表
ftuples=[('Durchschnitt','mean'),('Abweichung',np.var)]
grouped['tip_pct','total_bill'].agg(ftuples)
如果想要對(duì)不同的列應(yīng)用不同的函數(shù)。具體的辦法是向agg傳入一個(gè)從列名映射到函數(shù)的字典
grouped.agg({'tip':np.max,'size':'sum'})
grouped.agg({'tip_pct':['min','max','mean','std'],'size':'sum'})
只有將多個(gè)函數(shù)應(yīng)用到至少一列時(shí)履澳,DataFrame才會(huì)擁有層次化的列嘶窄。
以“無索引”的形式返回聚合數(shù)據(jù)
示例中的聚合數(shù)據(jù)都有由唯一的分組鍵組成的索引(可能還是層次化的)。由于并不總是需要如此距贷,可以向groupby傳入as_index=False以禁用該功能
對(duì)結(jié)果調(diào)用reset_index也能得到這種形式的結(jié)果
tips.groupby(['sex','smoker'],as_index=False).mean()
下一節(jié)學(xué)習(xí)分組級(jí)運(yùn)算和轉(zhuǎn)換柄冲。