Pandas數(shù)據(jù)分析——超好用的Groupby詳解

? ? ?作者:易執(zhí)
? ? ?來源:Python讀財
在日常的數(shù)據(jù)分析中,經(jīng)常需要將數(shù)據(jù)根據(jù)某個(多個)字段劃分為不同的群體(group)進行分析,如電商領(lǐng)域?qū)⑷珖目備N售額根據(jù)省份進行劃分,分析各省銷售額的變化情況,社交領(lǐng)域?qū)⒂脩舾鶕?jù)畫像(性別、年齡)進行細分,研究用戶的使用情況和偏好等。在Pandas中,上述的數(shù)據(jù)處理操作主要運用groupby完成,這篇文章就介紹一下groupby的基本原理及對應(yīng)的agg、transform和apply操作。
為了后續(xù)圖解的方便,采用模擬生成的10個樣本數(shù)據(jù),代碼和數(shù)據(jù)如下:
company=["A","B","C"]
data=pd.DataFrame({
????"company":[company[x]?for?x?in?np.random.randint(0,len(company),10)],
????"salary":np.random.randint(5,50,10),
????"age":np.random.randint(15,50,10)
}
)

groupby的基本原理
在Pandas中,實現(xiàn)分組操作的代碼很簡單,僅需一行代碼,在這里,將上面的數(shù)據(jù)集按照company字段進行劃分:
In?[5]:?group?=?data.groupby("company")
將上述代碼輸入ipython后,會得到一個DataFrameGroupBy對象
In?[6]:?group
Out[6]:?0x000002B7E2650240>
那這個生成的DataFrameGroupBy是啥呢?對data進行了groupby后發(fā)生了什么?ipython所返回的結(jié)果是其內(nèi)存地址,并不利于直觀地理解,為了看看group內(nèi)部究竟是什么,這里把group轉(zhuǎn)換成list的形式來看一看:
In?[8]:?list(group)
Out[8]:
[('A',???company??salary??age
??3???????A??????20???22
??6???????A??????23???33),?
?('B',???company??salary??age
??4???????B??????10???17
??5???????B??????21???40
??8???????B???????8???30),?
?('C',???company??salary??age
??0???????C??????43???35
??1???????C??????17???25
??2???????C???????8???30
??7???????C??????49???19)]
轉(zhuǎn)換成列表的形式后,可以看到,列表由三個元組組成,每個元組中,第一個元素是組別(這里是按照company進行分組,所以最后分為了A,B,C),第二個元素的是對應(yīng)組別下的DataFrame,整個過程可以圖解如下:

總結(jié)來說,groupby的過程就是將原有的DataFrame按照groupby的字段(這里是company),劃分為若干個分組DataFrame,被分為多少個組就有多少個分組DataFrame。所以說,在groupby之后的一系列操作(如agg、apply等),均是基于分組DataFrame的操作。理解了這點,也就基本摸清了Pandas中groupby操作的主要原理。下面來講講groupby之后的常見操作。
agg分組聚合
聚合操作是groupby后常見的操作,會寫SQL的朋友對此應(yīng)該是非常熟悉了。聚合操作可以用來求和、均值、最大值、最小值等,下面的表格列出了Pandas中常見的聚合操作。

針對樣例數(shù)據(jù)集,如果我想計算不同公司員工的平均年齡和平均薪水,可以按照下方的代碼進行:
In?[12]:?data.groupby("company").agg('mean')Out[12]:
?????????salary????age
company
A?????????21.50??27.50
B?????????13.00??29.00
C?????????29.25??27.25
如果想對針對不同的列求不同的值,比如要計算不同公司員工的平均年齡以及薪水的中位數(shù),可以利用字典指定進行聚合操作:
In?[17]:?data.groupby('company').agg({'salary':'median','age':'mean'})
Out[17]:
?????????salary????age
company
A??????????21.5??27.50
B??????????10.0??29.00
C??????????30.0??27.25
agg聚合過程可以圖解如下(第二個例子為例):

transform
transform是一種什么數(shù)據(jù)操作?和agg有什么區(qū)別呢?為了更好地理解transform和agg的不同,下面從實際的應(yīng)用場景出發(fā)進行對比。
在上面的agg中,我們學(xué)會了如何求不同公司員工的平均薪水,如果現(xiàn)在需要在原數(shù)據(jù)集中新增一列avg_salary,代表員工所在的公司的平均薪水(相同公司的員工具有一樣的平均薪水),該怎么實現(xiàn)呢?如果按照正常的步驟來計算,需要先求得不同公司的平均薪水,然后按照員工和公司的對應(yīng)關(guān)系填充到對應(yīng)的位置,不用transform的話,實現(xiàn)代碼如下:
In?[21]:?avg_salary_dict?=?data.groupby('company')['salary'].mean().to_dict()
In?[22]:?data['avg_salary']?=?data['company'].map(avg_salary_dict)
In?[23]:?data
Out[23]:
??company??salary??age??avg_salary
0???????C??????43???35???????29.25
1???????C??????17???25???????29.25
2???????C???????8???30???????29.25
3???????A??????20???22???????21.50
4???????B??????10???17???????13.00
5???????B??????21???40???????13.00
6???????A??????23???33???????21.50
7???????C??????49???19???????29.25
8???????B???????8???30???????13.00
如果使用transform的話,僅需要一行代碼:
In?[24]:?data['avg_salary']?=?data.groupby('company')['salary'].transform('mean')
In?[25]:?data
Out[25]:
??company??salary??age??avg_salary
0???????C??????43???35???????29.25
1???????C??????17???25???????29.25
2???????C???????8???30???????29.25
3???????A??????20???22???????21.50
4???????B??????10???17???????13.00
5???????B??????21???40???????13.00
6???????A??????23???33???????21.50
7???????C??????49???19???????29.25
8???????B???????8???30???????13.00
還是以圖解的方式來看看進行groupby后transform的實現(xiàn)過程(為了更直觀展示,圖中加入了company列,實際按照上面的代碼只有salary列):

圖中的大方框是transform和agg所不一樣的地方,對agg而言,會計算得到A,B,C公司對應(yīng)的均值并直接返回,但對transform而言,則會對每一條數(shù)據(jù)求得相應(yīng)的結(jié)果,同一組內(nèi)的樣本會有相同的值,組內(nèi)求完均值后會按照原索引的順序返回結(jié)果,如果有不理解的可以拿這張圖和agg那張對比一下。
apply
apply應(yīng)該是大家的老朋友了,它相比agg和transform而言更加靈活,能夠傳入任意自定義的函數(shù),實現(xiàn)復(fù)雜的數(shù)據(jù)操作。在Pandas數(shù)據(jù)處理三板斧,你會幾板?中,介紹了apply的使用,那在groupby后使用apply和之前所介紹的有什么區(qū)別呢?
區(qū)別是有的,但是整個實現(xiàn)原理是基本一致的。兩者的區(qū)別在于,對于groupby后的apply,以分組后的分組DataFrame作為參數(shù)傳入指定函數(shù)的,基本操作單位是DataFrame,而之前介紹的apply的基本操作單位是Series。還是以一個案例來介紹groupby后的apply用法。
假設(shè)我現(xiàn)在需要獲取各個公司年齡最大的員工的數(shù)據(jù),該怎么實現(xiàn)呢?可以用以下代碼實現(xiàn):
In?[38]:?def?get_oldest_staff(x):
????...:?????df?=?x.sort_values(by?=?'age',ascending=True)
????...:?????return?df.iloc[-1,:]
????...:
In?[39]:?oldest_staff?=?data.groupby('company',as_index=False).apply(get_oldest_staff)
In?[40]:?oldest_staff
Out[40]:
??company??salary??age??
0???????A??????23???33???????
1???????B??????21???40???????
2???????C??????43???35??????
這樣便得到了每個公司年齡最大的員工的數(shù)據(jù),整個流程圖解如下:
可以看到,此處的apply和上篇文章中所介紹的作用原理基本一致,只是傳入函數(shù)的參數(shù)由Series變?yōu)榱舜颂幍?/span>分組DataFrame。最后,關(guān)于apply的使用,這里有個小建議,雖然說apply擁有更大的靈活性,但apply的運行效率會比agg和transform更慢。所以,groupby之后能用agg和transform解決的問題還是優(yōu)先使用這兩個方法,實在解決不了了才考慮使用apply進行操作。
◆?◆?◆ ?◆?◆
長按二維碼關(guān)注我們
數(shù)據(jù)森麟公眾號的交流群已經(jīng)建立,許多小伙伴已經(jīng)加入其中,感謝大家的支持。大家可以在群里交流關(guān)于數(shù)據(jù)分析&數(shù)據(jù)挖掘的相關(guān)內(nèi)容,還沒有加入的小伙伴可以掃描下方管理員二維碼,進群前一定要關(guān)注公眾號奧,關(guān)注后讓管理員幫忙拉進群,期待大家的加入。
管理員二維碼:
