在日常的數據分析中,經常需要將數據根據某個(多個)字段划分為不同的群體(group)進行分析,如電商領域將全國的總銷售額根據省份進行划分,分析各省銷售額的變化情況,社交領域將用戶根據畫像(性別、年齡)進行細分,研究用戶的使用情況和偏好等。在Pandas中,上述的數據處理操作主要運用groupby
完成,這篇文章就介紹一下groupby
的基本原理及對應的agg
、transform
和apply
操作。
為了后續圖解的方便,采用模擬生成的10個樣本數據,代碼和數據如下:
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中,實現分組操作的代碼很簡單,僅需一行代碼,在這里,將上面的數據集按照company
字段進行划分:
In [5]: group = data.groupby("company")
將上述代碼輸入ipython
后,會得到一個DataFrameGroupBy
對象
In [6]: group Out[6]: <pandas.core.groupby.generic.DataFrameGroupBy object at 0x000002B7E2650240>
那這個生成的DataFrameGroupBy
是啥呢?對data
進行了groupby
后發生了什么?ipython
所返回的結果是其內存地址,並不利於直觀地理解,為了看看group
內部究竟是什么,這里把group
轉換成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)]
轉換成列表的形式后,可以看到,列表由三個元組組成,每個元組中,第一個元素是組別(這里是按照company
進行分組,所以最后分為了A
,B
,C
),第二個元素的是對應組別下的DataFrame
,整個過程可以圖解如下:
總結來說,groupby
的過程就是將原有的DataFrame
按照groupby
的字段(這里是company
),划分為若干個分組DataFrame
,被分為多少個組就有多少個分組DataFrame
。所以說,在groupby
之后的一系列操作(如agg
、apply
等),均是基於子DataFrame
的操作。理解了這點,也就基本摸清了Pandas中groupby
操作的主要原理。下面來講講groupby
之后的常見操作。
二、agg 聚合操作
聚合操作是groupby
后非常常見的操作,會寫SQL
的朋友對此應該是非常熟悉了。聚合操作可以用來求和、均值、最大值、最小值等,下面的表格列出了Pandas中常見的聚合操作。
針對樣例數據集,如果我想求不同公司員工的平均年齡和平均薪水,可以按照下方的代碼進行:
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
如果想對針對不同的列求不同的值,比如要計算不同公司員工的平均年齡以及薪水的中位數,可以利用字典進行聚合操作的指定:
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
是一種什么數據操作?和agg
有什么區別呢?為了更好地理解transform
和agg
的不同,下面從實際的應用場景出發進行對比。
在上面的agg
中,我們學會了如何求不同公司員工的平均薪水,如果現在需要在原數據集中新增一列avg_salary
,代表員工所在的公司的平均薪水(相同公司的員工具有一樣的平均薪水),該怎么實現呢?如果按照正常的步驟來計算,需要先求得不同公司的平均薪水,然后按照員工和公司的對應關系填充到對應的位置,不用transform
的話,實現代碼如下:
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
的實現過程(為了更直觀展示,圖中加入了company
列,實際按照上面的代碼只有salary
列):
圖中的大方框是transform
和agg
所不一樣的地方,對agg
而言,會計算得到A
,B
,C
公司對應的均值並直接返回,但對transform
而言,則會對每一條數據求得相應的結果,同一組內的樣本會有相同的值,組內求完均值后會按照原索引的順序返回結果,如果有不理解的可以拿這張圖和agg
那張對比一下。
四、apply
apply
應該是大家的老朋友了,它相比agg
和transform
而言更加靈活,能夠傳入任意自定義的函數,實現復雜的數據操作。在Pandas數據處理三板斧——map、apply、applymap詳解中,介紹了apply
的使用,那在groupby
后使用apply
和之前所介紹的有什么區別呢?
區別是有的,但是整個實現原理是基本一致的。兩者的區別在於,對於groupby
后的apply
,以分組后的子DataFrame
作為參數傳入指定函數的,基本操作單位是DataFrame
,而之前介紹的apply
的基本操作單位是Series
。還是以一個案例來介紹groupby
后的apply
用法。
假設我現在需要獲取各個公司年齡最大的員工的數據,該怎么實現呢?可以用以下代碼實現:
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
這樣便得到了每個公司年齡最大的員工的數據,整個流程圖解如下:
可以看到,此處的apply
和上篇文章中所介紹的作用原理基本一致,只是傳入函數的參數由Series
變為了此處的分組DataFrame
。
最后,關於apply
的使用,這里有個小建議,雖然說apply
擁有更大的靈活性,但apply
的運行效率會比agg
和transform
更慢。所以,groupby
之后能用agg
和transform
解決的問題還是優先使用這兩個方法,實在解決不了了才考慮使用apply
進行操作。
相關文章:
- Pandas數據處理三板斧——map、apply、applymap詳解
- Pandas數據分析——Merge數據拼接圖文詳解
- Pandas數據處理——玩轉時間序列數據
- Pandas數據處理——盤點那些常用的函數(上)
- Pandas數據處理——盤點那些常用的函數(下)
- 天秀!Pandas還能用來寫爬蟲?
- 提高數據的顏值!一起看看Pandas中的那些Style
- 提速百倍的Pandas性能優化方法,讓你的Pandas飛起來!
轉自https://zhuanlan.zhihu.com/p/101284491?utm_source=wechat_session