Pandas 資料分析——超好用的 Groupby 詳解

Harry_03發表於2020-01-15

微信公眾號:「Python讀財」

如有問題或建議,請公眾號留言

在日常的資料分析中,經常需要將資料根據某個(多個)欄位劃分為不同的群體(group)進行分析,如電商領域將全國的總銷售額根據省份進行劃分,分析各省銷售額的變化情況,社交領域將使用者根據畫像(性別、年齡)進行細分,研究使用者的使用情況和偏好等。在Pandas中,上述的資料處理操作主要運用groupby完成,這篇文章就介紹一下groupby的基本原理及對應的aggtransformapply操作。

為了後續圖解的方便,採用模擬生成的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原理.png

總結來說,groupby的過程就是將原有的DataFrame按照groupby的欄位(這裡是company),劃分為若干個分組DataFrame,被分為多少個組就有多少個分組DataFrame所以說,在groupby之後的一系列操作(如aggapply等),均是基於子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聚合過程可以圖解如下(第二個例子為例):

agg圖解.png

transform是一種什麼資料操作?和agg有什麼區別呢?為了更好地理解transformagg的不同,下面從實際的應用場景出發進行對比。

在上面的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

還是以圖解的方式來看看進行groupbytransform的實現過程(為了更直觀展示,圖中加入了company列,實際按照上面的程式碼只有salary列):

transform圖解.png

圖中的大方框是transformagg所不一樣的地方,對agg而言,會計算得到ABC公司對應的均值並直接返回,但對transform而言,則會對每一條資料求得相應的結果,同一組內的樣本會有相同的值,組內求完均值後會按照原索引的順序返回結果,如果有不理解的可以拿這張圖和agg那張對比一下。

apply應該是大家的老朋友了,它相比aggtransform而言更加靈活,能夠傳入任意自定義的函式,實現複雜的資料操作。在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過程.png

可以看到,此處的apply和上篇文章中所介紹的作用原理基本一致,只是傳入函式的引數由Series變為了此處的分組DataFrame

最後,關於apply的使用,這裡有個小建議,雖然說apply擁有更大的靈活性,但apply的執行效率會比aggtransform更慢。所以,groupby之後能用aggtransform解決的問題還是優先使用這兩個方法,實在解決不了了才考慮使用apply進行操作。

掃碼關注公眾號「Python讀財」,第一時間獲取乾貨,還可以加Python學習交流群!!
底部二維碼.png

本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章