微信公眾號:「Python讀財」
如有問題或建議,請公眾號留言
在日常的資料分析中,經常需要將資料根據某個(多個)欄位劃分為不同的群體(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)
}
)
company | salary | age | |
---|---|---|---|
0 | C | 43 | 35 |
1 | C | 17 | 25 |
2 | C | 8 | 30 |
3 | A | 20 | 22 |
4 | B | 10 | 17 |
5 | B | 21 | 40 |
6 | A | 23 | 33 |
7 | C | 49 | 19 |
8 | B | 8 | 30 |
在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
之後的常見操作。
聚合操作是groupby
後非常常見的操作,會寫SQL
的朋友對此應該是非常熟悉了。聚合操作可以用來求和、均值、最大值、最小值等,下面的表格列出了Pandas中常見的聚合操作。
函式 | 用途 |
---|---|
min | 最小值 |
max | 最大值 |
sum | 求和 |
mean | 均值 |
median | 中位數 |
std | 標準差 |
var | 方差 |
count | 計數 |
針對樣例資料集,如果我想求不同公司員工的平均年齡和平均薪水,可以按照下方的程式碼進行:
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
是一種什麼資料操作?和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
應該是大家的老朋友了,它相比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
進行操作。
掃碼關注公眾號「Python讀財」,第一時間獲取乾貨,還可以加Python學習交流群!!
本作品採用《CC 協議》,轉載必須註明作者和本文連結