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)用 - 合并)的思想勿璃,示意如下:
上面的操作中擒抛,通過(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)容:
- 分組依據(jù)(性別/季節(jié)/班級(jí))
- 需要聚合/計(jì)算等操作的字段,或者是數(shù)據(jù)來(lái)源(人口壽命/溫度/數(shù)學(xué)分?jǐn)?shù))
- 需要返回的結(jié)果竭望,即需要的聚合操作
這3項(xiàng)是完成一個(gè)分組操作的3個(gè)必須要素邪码。反之,如果有了這三個(gè)要素咬清,則一定可以完成一個(gè)分組操作闭专。
將上面這些要素抽象化,我們就可以寫(xiě)出分組操作的基本語(yǔ)法格式:
其中:
- 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()
下面需要依據(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
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)型
由此,引申出分組的三大操作:
- 聚合- agg绷旗、
- 變換 - transform
- 過(guò)濾 - filter
下面分別介紹
4.1 聚合 aggregation (agg)
- 內(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
- 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é)果如下:
從結(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']})
【c】使用自定義函數(shù)
在 agg 中可以使用具體的自定義函數(shù), 需要注意傳入函數(shù)的參數(shù)是之前數(shù)據(jù)源中的列鞋诗,逐列進(jìn)行計(jì)算 膀捷。下面分組計(jì)算身高和體重的極差:
df.groupby('School')['Weight', 'Height'].agg([lambda x: x.max() - x.min()])
【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())])
好了壶笼,本次重點(diǎn)介紹groupby后的agg方法,除了agg聚合之外雁刷,groupby之后還有transform覆劈、apply和filter方法,我們下在來(lái)介紹。
另外责语,更多精彩內(nèi)容也可以微信搜索炮障,并關(guān)注公眾號(hào):‘Python數(shù)據(jù)科學(xué)家之路“ ,期待您的到來(lái)和我交流坤候!