從需求出發(fā)清钥,循序漸進(jìn)琼锋,由淺入深掌握pandas好用的Groupby(分組)方法

1. 分組的概念和功能

我們都知道設(shè)計(jì)和需求相關(guān)。一個(gè)好的設(shè)計(jì)一定是源于一些合理的需求祟昭,能夠?qū)⑦@些合理的需求抽象化缕坎、泛化、再標(biāo)準(zhǔn)化就可以形成一些具體的設(shè)計(jì)范式篡悟。我們常說(shuō)的函數(shù)谜叹,就是這個(gè)理念下的產(chǎn)物。
一個(gè)好用的函數(shù)不僅能輕松應(yīng)對(duì)當(dāng)前的業(yè)務(wù)需求搬葬,還需要有一定的擴(kuò)展性荷腊, 把暫時(shí)沒(méi)有出現(xiàn),但是將來(lái)可能會(huì)出現(xiàn)的情況考慮進(jìn)去急凰。這也是為什么一個(gè)函數(shù)通常包含很多參數(shù)女仰,但是我們常用的僅僅是其中很少的部分。Pandas里面的分組Groupby函數(shù)就是這么一個(gè)非常好用的函數(shù)抡锈。那么它是來(lái)應(yīng)對(duì)哪些數(shù)據(jù)統(tǒng)計(jì)需求呢疾忍?

要理解groupby的功能,首先要明白分組的含義:對(duì)原始數(shù)據(jù)的行按照某一個(gè)或者多個(gè)字段的取值或者要求重新組合床三,將滿(mǎn)足同一個(gè)條件的行聚合到一起在計(jì)算其他數(shù)值一罩。該過(guò)程即一個(gè)標(biāo)準(zhǔn)分組操作。其操作思路遵循split-apply-combine(拆分 - 應(yīng)用 - 合并)的思想勿璃,示意如下:

image.png

上面的操作中擒抛,通過(guò)一個(gè)簡(jiǎn)單的按照門(mén)派來(lái)分組在求平均值聚合我們就可以得到逍遙派顏值出眾推汽,武當(dāng)血厚善于防守而丐幫攻擊厲害這一結(jié)論补疑。喬幫主的降龍十八掌果然天下第一,哈哈跑題了歹撒。

在日常的數(shù)據(jù)統(tǒng)計(jì)分析中莲组,以下的典型場(chǎng)景都可以用分組來(lái)完成:

  • 依據(jù) 性別 分組,統(tǒng)計(jì)全國(guó)人口 壽命 的 平均值
  • 依據(jù) 季節(jié) 分組暖夭,統(tǒng)計(jì)每個(gè)季節(jié)內(nèi)的降雨天數(shù)和降水總量
  • 依據(jù) 班級(jí) 分組锹杈,篩選出組內(nèi) 數(shù)學(xué)分?jǐn)?shù)的平均值超過(guò)80分的班級(jí)

將上面的問(wèn)題抽象化撵孤,標(biāo)準(zhǔn)化可以發(fā)現(xiàn)他們的共性——都涉及到3個(gè)方面的內(nèi)容:

  1. 分組依據(jù)(性別/季節(jié)/班級(jí))
  2. 需要聚合/計(jì)算等操作的字段,或者是數(shù)據(jù)來(lái)源(人口壽命/溫度/數(shù)學(xué)分?jǐn)?shù))
  3. 需要返回的結(jié)果竭望,即需要的聚合操作
    這3項(xiàng)是完成一個(gè)分組操作的3個(gè)必須要素邪码。反之,如果有了這三個(gè)要素咬清,則一定可以完成一個(gè)分組操作闭专。
    將上面這些要素抽象化,我們就可以寫(xiě)出分組操作的基本語(yǔ)法格式:
    df.groupby(m)[n].f
    其中:
  • m: 分組依據(jù)旧烧,但需要按照多個(gè)條件分組時(shí)影钉,需要把條件放到一個(gè)列表中
  • n: 數(shù)據(jù)來(lái)源,即需要計(jì)算的字段掘剪,同樣的平委,需要多個(gè)字段也是放入一個(gè)列表
  • f(function): 聚合函數(shù),常用的有min/max/mean/count等夺谁,也可以傳入自定義參數(shù)
    好了廉赔,以上就是我們從需求出發(fā),將需求的各個(gè)要素抽象化標(biāo)準(zhǔn)化在逐步演化得到groupby函數(shù)的過(guò)程匾鸥,目的是讓大家了解上面的m/n/f三個(gè)關(guān)鍵字的含義昂勉。下面我們就套用上面的公式就行演練!

2. Groupby分組依據(jù)

我們先來(lái)拆一拆groupby的第一個(gè)關(guān)鍵字——分組依據(jù)扫腺。下面通過(guò)案例來(lái)講解岗照。
下面的使用一份learn_pandas.csv的原始數(shù)據(jù),這是一份包含學(xué)生學(xué)校性別等信息的表格笆环。

import pandas as pd
import numpy as np

df = pd.read_csv('learn_pandas.csv')
df.head()
image.png

下面需要依據(jù)學(xué)校和性別分組攒至,統(tǒng)計(jì)身高均值,我們套用上面的公式:

  • 分組依據(jù):m——學(xué)校和性別躁劣,多個(gè)參數(shù)可以作為一個(gè)列表:['School', 'Gender']
  • 數(shù)據(jù)來(lái)源:n——身高
  • 聚合方法:求平均值mean
    將這三個(gè)參數(shù)帶入迫吐,代碼如下:
df.groupby(['School', 'Gender'])['Height'].mean()
>>>
School                         Gender
Fudan University               Female    158.776923
                               Male      174.212500
Peking University              Female    158.666667
                               Male      172.030000
Shanghai Jiao Tong University  Female    159.122500
                               Male      176.760000
Tsinghua University            Female    159.753333
                               Male      171.638889
Name: Height, dtype: float64

上面的例子中,分組的依據(jù)(School账忘,Gender)為原始表格中已有的字段志膀,那么能不能不使用原有的字段,而是按照一定的條件來(lái)分組呢鳖擒?答案是可以的溉浙。例如我們需要按照體重是否大于均值分成兩組,分別統(tǒng)計(jì)兩組的身高均值:

df.groupby(df['Weight'] > df['Weight'].mean())['Height'].mean()
>>>
Weight
False    159.034646
True     172.705357
Name: Height, dtype: float64

上面的代碼中df['Weight'] > df['Weight'].mean()即是分組依據(jù)蒋荚,通過(guò)結(jié)果的索引(False/True)可以看出戳稽,其實(shí)最后產(chǎn)生的結(jié)果就是按照條件列表中元素的值(此處是 True 和 False )來(lái)分組。下面用隨機(jī)傳入字母序列來(lái)驗(yàn)證這一想法:

item = np.random.choice(list('abc'), df.shape[0])
df.groupby(item)['Height'].mean()
>>>
a    162.567347
b    164.367606
c    162.428571
Name: Height, dtype: float64

上面的代碼先創(chuàng)建了一個(gè)和原DataFrame等長(zhǎng)的序列期升,并將這個(gè)序列作為分組依據(jù)惊奇。

從上面的例子中我們可以總結(jié)出分組依據(jù)的本質(zhì):
分組的依據(jù)來(lái)自于數(shù)據(jù)來(lái)源組合的unique值互躬。 例如在上面的學(xué)生信息表格中按照學(xué)校School和性別Gender來(lái)分組,如果學(xué)校的個(gè)數(shù)為m, 性別個(gè)數(shù)為2颂郎,并且在原始數(shù)據(jù)吼渡,每個(gè)學(xué)校都存在2中性別的行,則最終分組的個(gè)數(shù)為2m

3. Groupby對(duì)象

最終具體做分組操作時(shí)乓序,所調(diào)用的方法都來(lái)自于 pandas 中的 groupby 對(duì)象诞吱,這個(gè)對(duì)象上定義了許多方法,也具有一些方便的屬性竭缝。

# groupby返回一個(gè)groupby對(duì)象
df1 = df.groupby(['School', 'Grade'])
type(df1)
>>>
pandas.core.groupby.generic.DataFrameGroupBy

可以看到房维,groupby后返回一個(gè)groupby對(duì)象,且是一個(gè)生成器抬纸。既然是生成器我們就可以用for循環(huán)遍歷里面的元素:

for i in df1:
  print(i)

結(jié)果太長(zhǎng)咙俩,下面是部分截圖。通過(guò)截圖可以看到每個(gè)元素是一個(gè)tuple, tuple的第一個(gè)元素是分組的依據(jù)湿故,第二個(gè)是具體的值阿趁,是一個(gè)DataFrame


image.png
for i in df1:
    print(type(i), i[0])
>>>
<class 'tuple'> ('Fudan University', 'Freshman')
<class 'tuple'> ('Fudan University', 'Junior')
<class 'tuple'> ('Fudan University', 'Senior')
<class 'tuple'> ('Fudan University', 'Sophomore')
<class 'tuple'> ('Peking University', 'Freshman')
<class 'tuple'> ('Peking University', 'Junior')
<class 'tuple'> ('Peking University', 'Senior')
<class 'tuple'> ('Peking University', 'Sophomore')
<class 'tuple'> ('Shanghai Jiao Tong University', 'Freshman')
<class 'tuple'> ('Shanghai Jiao Tong University', 'Junior')
<class 'tuple'> ('Shanghai Jiao Tong University', 'Senior')
<class 'tuple'> ('Shanghai Jiao Tong University', 'Sophomore')
<class 'tuple'> ('Tsinghua University', 'Freshman')
<class 'tuple'> ('Tsinghua University', 'Junior')
<class 'tuple'> ('Tsinghua University', 'Senior')
<class 'tuple'> ('Tsinghua University', 'Sophomore')

其他常用屬性還有

  • ngroups: 返回分組的個(gè)數(shù)
  • groups(a):返回該組所有行在原表中的行號(hào)(行索引)
  • size:每個(gè)組的大小
print(df1.ngroups)  # ngroups:分組個(gè)數(shù)
print("-" * 10)
print(df1.groups[('Fudan University', 'Freshman')]) # 返回改組的索引
print("-" * 10)
print(df1.size())  # 每個(gè)組別的個(gè)數(shù)

>>>
16
----------
Int64Index([15, 28, 63, 70, 73, 105, 108, 157, 186], dtype='int64')
----------
School                         Grade    
Fudan University               Freshman      9
                               Junior       12
                               Senior       11
                               Sophomore     8
Peking University              Freshman     13
                               Junior        8
                               Senior        8
                               Sophomore     5
Shanghai Jiao Tong University  Freshman     13
                               Junior       17
                               Senior       22
                               Sophomore     5
Tsinghua University            Freshman     17
                               Junior       22
                               Senior       14
                               Sophomore    16
dtype: int64

4. 本次的重點(diǎn)內(nèi)容:分組后3大基本操作

熟悉了一些分組的基本知識(shí)后,重新回到開(kāi)頭舉的三個(gè)例子坛猪,可能會(huì)發(fā)現(xiàn)一些端倪脖阵,即這三種類(lèi)型分組返回的數(shù)據(jù)型態(tài)并不一樣:
第一個(gè)例子中,每一個(gè)組返回一個(gè)標(biāo)量值墅茉,可以是平均值命黔、中位數(shù)、組容量 size 等
第二個(gè)例子中就斤,做了原序列的標(biāo)準(zhǔn)化處理悍募,也就是說(shuō)每組返回的是一個(gè) Series 類(lèi)型
第三個(gè)例子中,既不是標(biāo)量也不是序列洋机,而是通過(guò)篩選返回的整個(gè)組所在行的本身坠宴,即返回了 DataFrame 類(lèi)型
由此,引申出分組的三大操作:

  1. 聚合- agg绷旗、
  2. 變換 - transform
  3. 過(guò)濾 - filter

下面分別介紹

4.1 聚合 aggregation (agg)

  1. 內(nèi)置聚合函數(shù)
    在介紹agg之前喜鼓,首先要了解一些直接定義在groupby對(duì)象的聚合函數(shù),因?yàn)樗乃俣然径紩?huì)經(jīng)過(guò)內(nèi)部的優(yōu)化衔肢,使用功能時(shí)應(yīng)當(dāng)優(yōu)先考慮庄岖。
    包括如下函數(shù):
  • max/min/mean/median/count/
  • all/any/idxmax/idxmin/
  • mad/nunique/skew/quantile/
  • sum/std/var/sem/size/prod
    其中有些不常用的函數(shù)如下:
  • any(): 如果組內(nèi)有truthful的值就返回True。
  • all(): 組內(nèi)所有元素都是truthful膀懈,返回True顿锰。
  • mad():返回組內(nèi)元素的絕對(duì)中位差。先計(jì)算出數(shù)據(jù)與它們的中位數(shù)之間的殘差启搂,MAD就是這些偏差的絕對(duì)值的中位數(shù)硼控。MAD比方差魯棒性更好。
  • skew():組內(nèi)數(shù)據(jù)的偏度胳赌。
  • sem():組內(nèi)數(shù)據(jù)的均值標(biāo)準(zhǔn)誤差牢撼。
  • prod() :組內(nèi)所有元素的乘積。
df.groupby('Gender')['Height'].idxmin()
>>>
Gender
Female    143
Male      199
Name: Height, dtype: int64

df.groupby('Gender')[['Height', 'Weight']].max()
>>>
Height  Weight
Gender      
Female  170.2   63.0
Male    193.9   89.0
  1. agg方法

雖然在 groupby 對(duì)象上定義了許多方便的函數(shù)疑苫,但仍然有以下不便之處:

  • 無(wú)法同時(shí)使用多個(gè)函數(shù)
  • 無(wú)法對(duì)特定的列使用特定的聚合函數(shù)
  • 無(wú)法使用自定義的聚合函數(shù)
  • 無(wú)法直接對(duì)結(jié)果的列名在聚合前進(jìn)行自定義命名

下面說(shuō)明如何通過(guò) agg 函數(shù)解決這四類(lèi)問(wèn)題:

【a】使用多個(gè)函數(shù)
當(dāng)使用多個(gè)聚合函數(shù)時(shí)熏版,需要用列表的形式把內(nèi)置聚合函數(shù)對(duì)應(yīng)的字符串傳入,先前提到的所有字符串都是合法的捍掺。例如我們需要同時(shí)獲得每個(gè)學(xué)校的學(xué)生的身高/體重的最大/最小值/學(xué)生總數(shù)以及該學(xué)生的行索引:
同樣的撼短,對(duì)應(yīng)上面的公式:

  • 分組依據(jù)m = 'School'
  • 數(shù)據(jù)n = ['Height', 'Weight']
  • 聚合函數(shù),這里需要多個(gè)函數(shù)挺勿,使用agg,參數(shù)為['max', 'min', 'count', 'idxmax', 'idxmin']
    代入即可:
df.groupby('School')['Height', 'Weight'].agg(['max', 'min', 'count', 'idxmax', 'idxmin'])

結(jié)果如下:

image.png

從結(jié)果看曲横,此時(shí)的列索引為多級(jí)索引,第一層為數(shù)據(jù)源不瓶,第二層為使用的聚合方法禾嫉,分別逐一對(duì)列使用聚合,因此結(jié)果為10列蚊丐。

【b】對(duì)特定的列使用特定的聚合函數(shù)
對(duì)于方法和列的特殊對(duì)應(yīng)熙参,可以通過(guò)構(gòu)造字典傳入 agg 中實(shí)現(xiàn),其中字典以列名為鍵麦备,以聚合字符串或字符串列表為值孽椰。例如現(xiàn)在的需求為按照性別分組,對(duì)身高/體重聚合凛篙,其中身高需要最大值以及該值的行索引弄屡,體重需要平均值和該性別的總數(shù)

df.groupby('Gender')['Height', 'Weight'].agg({'Height': ['max', 'idxmax'], 'Weight': ['mean', 'count']})
image.png

【c】使用自定義函數(shù)

在 agg 中可以使用具體的自定義函數(shù), 需要注意傳入函數(shù)的參數(shù)是之前數(shù)據(jù)源中的列鞋诗,逐列進(jìn)行計(jì)算 膀捷。下面分組計(jì)算身高和體重的極差:

df.groupby('School')['Weight', 'Height'].agg([lambda x: x.max() - x.min()])
image.png

【d】聚合結(jié)果重命名

如果想要對(duì)聚合結(jié)果的列名進(jìn)行重命名,只需要將上述函數(shù)的位置改寫(xiě)成元組削彬,元組的第一個(gè)元素為新的名字全庸,第二個(gè)位置為原來(lái)的函數(shù),包括聚合字符串和自定義函數(shù)融痛,現(xiàn)舉例子說(shuō)明:

df.groupby('School')['Weight', 'Height'].agg([('range', lambda x: x.max() - x.min())])
image.png

好了壶笼,本次重點(diǎn)介紹groupby后的agg方法,除了agg聚合之外雁刷,groupby之后還有transform覆劈、apply和filter方法,我們下在來(lái)介紹。

另外责语,更多精彩內(nèi)容也可以微信搜索炮障,并關(guān)注公眾號(hào):‘Python數(shù)據(jù)科學(xué)家之路“ ,期待您的到來(lái)和我交流坤候!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末胁赢,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子白筹,更是在濱河造成了極大的恐慌智末,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,204評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件徒河,死亡現(xiàn)場(chǎng)離奇詭異系馆,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)顽照,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,091評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)由蘑,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)柳譬,“玉大人班眯,你說(shuō)我怎么就攤上這事∈蘸粒” “怎么了奢人?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,548評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵谓媒,是天一觀(guān)的道長(zhǎng)。 經(jīng)常有香客問(wèn)我何乎,道長(zhǎng)句惯,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,657評(píng)論 1 293
  • 正文 為了忘掉前任支救,我火速辦了婚禮抢野,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘各墨。我一直安慰自己指孤,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,689評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布贬堵。 她就那樣靜靜地躺著恃轩,像睡著了一般。 火紅的嫁衣襯著肌膚如雪黎做。 梳的紋絲不亂的頭發(fā)上叉跛,一...
    開(kāi)封第一講書(shū)人閱讀 51,554評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音蒸殿,去河邊找鬼筷厘。 笑死鸣峭,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的酥艳。 我是一名探鬼主播摊溶,決...
    沈念sama閱讀 40,302評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼玖雁!你這毒婦竟也來(lái)了更扁?” 一聲冷哼從身側(cè)響起盖腕,我...
    開(kāi)封第一講書(shū)人閱讀 39,216評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤赫冬,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后溃列,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體劲厌,經(jīng)...
    沈念sama閱讀 45,661評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,851評(píng)論 3 336
  • 正文 我和宋清朗相戀三年听隐,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了补鼻。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,977評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡雅任,死狀恐怖风范,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情沪么,我是刑警寧澤硼婿,帶...
    沈念sama閱讀 35,697評(píng)論 5 347
  • 正文 年R本政府宣布,位于F島的核電站禽车,受9級(jí)特大地震影響寇漫,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜殉摔,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,306評(píng)論 3 330
  • 文/蒙蒙 一州胳、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧逸月,春花似錦栓撞、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,898評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至肛响,卻和暖如春岭粤,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背特笋。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,019評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工剃浇, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留巾兆,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,138評(píng)論 3 370
  • 正文 我出身青樓虎囚,卻偏偏與公主長(zhǎng)得像角塑,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子淘讥,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,927評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容