第四章 分組

分組模式及其對象

1. 分組的一般模式

模式 分類 例子 備注
df.groupby(分組依據(jù))[數(shù)據(jù)來源].使用操作 單一維度 df.groupby('Gender')['Longevity'].mean() 明確三個(gè)要素:\color{#FF0000}{分組依據(jù)}\color{#00FF00}{數(shù)據(jù)來源}\color{#0000FF}{操作及其返回結(jié)果}
多維度 df.groupby(['School', 'Gender'])['Height'].mean()
復(fù)雜邏輯 condition = df.Weight > df.Weight.mean()<br />df.groupby(condition)['Height'].mean()
分組依據(jù)的本質(zhì):只需在 groupby 中傳入相應(yīng)列名構(gòu)成的列表即可 自定義分組依據(jù) item = np.random.choice(list('abc'), df.shape[0])<br />df.groupby(item)['Height'].mean() df里并沒有item列
df.groupby([condition, item])['Height'].mean() 傳入兩個(gè)分組依據(jù),最后分組的一句就是這兩個(gè)序列對應(yīng)的唯一組合

【練一練】

請根據(jù)上下四分位數(shù)分割,將體重分為high、normal、low三組,統(tǒng)計(jì)身高的均值。

df['Wight2'] =  df['Weight'].mask(df.Weight < df.Weight.quantile(0.25), 'low').mask((df.Weight >= df.Weight.quantile(0.25))&(df.Weight <= df.Weight.quantile(0.75)), 'normal').mask(df.Weight > df.Weight.quantile(0.75), 'high')
df.groupby('Wight2')['Height'].mean()
'''

Wight2
high      174.935714
low       153.753659
normal    161.883516
Name: Height, dtype: float64
'''

2. Groupby對象

gb = df.groupby(['School', 'Grade'])
屬性,方法 例子 備注
ngroups gb.ngroups 分組個(gè)數(shù)
groups res = gb.groups<br />res.keys() 可以返回從 組名 映射到 組索引列表 的字典
size gb.size() groupby 對象上表示統(tǒng)計(jì)每個(gè)組的元素個(gè)數(shù)
get_group gb.get_group(('Fudan University', 'Freshman')).iloc[:3, :3] 可以直接獲取所在組對應(yīng)的行,此時(shí)必須知道組的具體名字

【練一練】

上一小節(jié)介紹了可以通過drop_duplicates得到具體的組類別,現(xiàn)請用groups屬性完成類似的功能。

res = gb.groups
reslist= []
for element in res.keys():
    reslist.append(list(element))
df2 = pd.DataFrame(data = reslist,
                   columns = ['school', 'grade'])
df2
'''

                school              grade
0   Fudan University                Freshman
1   Fudan University                Junior
2   Fudan University                Senior
3   Fudan University                Sophomore
4   Peking University               Freshman
5   Peking University               Junior
6   Peking University               Senior
7   Peking University               Sophomore
8   Shanghai Jiao Tong University   Freshman
9   Shanghai Jiao Tong University   Junior
10  Shanghai Jiao Tong University   Senior
11  Shanghai Jiao Tong University   Sophomore
12  Tsinghua University             Freshman
13  Tsinghua University             Junior
14  Tsinghua University             Senior
15  Tsinghua University             Sophomore
'''

分組的三大操作:聚合agg

1. 內(nèi)置聚合函數(shù)

因?yàn)樗乃俣然径紩?jīng)過內(nèi)部的優(yōu)化,使用功能時(shí)應(yīng)當(dāng)優(yōu)先考慮。

max/min/mean/median/count/all/any/idxmax/idxmin/mad/nunique/skew/quantile/sum/std/var/sem/size/prod

使用:df.groupby('Wight2')['Height'].mean()

練一練

請查閱文檔,明確 all/any/mad/skew/sem/prod 函數(shù)的含義。

函數(shù) 參數(shù) 含義
all axis : {index (0), columns (1)}
skipna : boolean, default True
排除NA/null值,如果一整行或一整列都是缺失值,則結(jié)果為NA
level : int or level name, default None
If the axis is a MultiIndex (hierarchical), count along a particular level, collapsing into a Series
bool_only : boolean, default None
只包括布爾型的列。如果沒有,會嘗試使用所有(?),然后會只使用布爾類型的數(shù)據(jù)。系列值沒有這個(gè)選項(xiàng)
如果這個(gè)組所有值都是True,則為true,否則為false
any axis : {index (0), columns (1)}
skipna : boolean, default True
排除NA/null值,如果一整行或一整列都是缺失值,則結(jié)果為NA
level : int or level name, default None
If the axis is a MultiIndex (hierarchical), count along a particular level, collapsing into a Series
bool_only : boolean, default None
只包括布爾型的列。如果沒有,會嘗試使用所有(?),然后會只使用布爾類型的數(shù)據(jù)。系列值沒有這個(gè)選項(xiàng)
如果這個(gè)組有任意值是True,則為true,否則為false
mad axis : {index (0), columns (1)}
skipna : boolean, default True
排除NA/null值,如果一整行或一整列都是缺失值,則結(jié)果為NA
level : int or level name, default None
如果軸是多索引的,計(jì)算特定的層級
numeric_only : boolean, default None
Include only float, int, boolean columns. If None, will attempt to use everything, then use only numeric data. Not implemented for Series.只包括浮點(diǎn)型,布爾型,整型的列。如果沒有,會嘗試使用所有(?),然后會只使用數(shù)值類型的數(shù)據(jù)。系列值沒有這個(gè)選項(xiàng)
返回平均絕對離差
skew axis : {index (0), columns (1)}
skipna : boolean, default True
排除NA/null值,如果一整行或一整列都是缺失值,則結(jié)果為NA
level : int or level name, default None
如果軸是多索引的,計(jì)算特定的層級
numeric_only : boolean, default None
只包括浮點(diǎn)型,布爾型,整型的列。如果沒有,會嘗試使用所有(?),然后會只使用數(shù)值類型的數(shù)據(jù)。系列值沒有這個(gè)選項(xiàng)
返回組的樣本偏度
sem ddof : integer,默認(rèn)值為1,表示自由度 返回標(biāo)準(zhǔn)誤差,不包含缺失值
prod 每個(gè)組所有數(shù)的乘積

2. agg方法

可以解決內(nèi)置聚合函數(shù)的4個(gè)問題:

問題 例子 備注
同時(shí)使用多個(gè)函數(shù) gb.\color{#961E87}agg(['sum', 'idxmax', 'skew']) 用列表的形式把內(nèi)置聚合函數(shù)對應(yīng)的字符串傳入
對特定的列使用特定的聚合函數(shù) gb.\color{#961E87}agg({'Height':['mean','max'], 'Weight':'count'}) 通過構(gòu)造字典傳入 agg 中實(shí)現(xiàn),其中字典以列名為鍵,以聚合字符串或字符串列表為值。
使用自定義的聚合函數(shù) gb.\color{#961E87}agg(lambda x: x.mean()-x.min()) 方法1;需要注意傳入函數(shù)的參數(shù)是之前數(shù)據(jù)源中的列,逐列進(jìn)行計(jì)算
def my_func(s): <br /> res = 'High' <br /> if s.mean() <= df[s.name].mean(): <br /> res = 'Low' <br /> return res<br />gb.\color{#961E87}agg(my_func) 方法2
直接對結(jié)果的列名在聚合前進(jìn)行自定義命名 gb.\color{#961E87}agg([('range', lambda x: x.max()-x.min()), ('my_sum', 'sum')])<br />gb.(\color{#961E87}agg{'Height': [('my_func', my_func), 'sum'], 'Weight': lambda x:x.max()}) 將上述函數(shù)的位置改寫成元組,元組的第一個(gè)元素為新的名字,第二個(gè)位置為原來的函數(shù),包括聚合字符串和自定義函數(shù);<br />另外需要注意,使用對一個(gè)或者多個(gè)列使用單個(gè)聚合的時(shí)候,重命名需要加方括號,否則就不知道是新的名字還是手誤輸錯(cuò)的內(nèi)置函數(shù)字符串

練一練

請使用傳入字典的方法完成gb.agg(['sum', 'idxmax', 'skew'])等價(jià)的聚合任務(wù)。

gb.agg({'Height':['sum', 'idxmax', 'skew'], 'Weight':['sum', 'idxmax', 'skew']})

groupby 對象中可以使用 describe 方法進(jìn)行統(tǒng)計(jì)信息匯總,請同時(shí)使用多個(gè)聚合函數(shù),完成與該方法相同的功能。

gb.agg(['count','mean','std','min',('25%', lambda x: x.quantile(0.25)),('50%', lambda x: x.quantile(0.25)),('75%', lambda x: x.quantile(0.25)),'max'])

分組的三大操作:變換

1.內(nèi)置變換函數(shù):

累計(jì)函數(shù)cumcount/cumsum/cumprod/cummax/cummin

練一練

groupby 對象中, rank 方法也是一個(gè)實(shí)用的變換函數(shù),請查閱它的功能并給出一個(gè)使用的例子。

功能:計(jì)算沿軸的數(shù)值數(shù)據(jù)等級(1到n)。相等的值被分配一個(gè)等級,這個(gè)等級是這些值等級的平均值(默認(rèn))

參數(shù):

  • axis : {0 or ‘index’, 1 or ‘columns’}, default 0

  • method : {‘a(chǎn)verage’, ‘min’, ‘max’, ‘first’, ‘dense’}相同值以什么方法取它的等級

  • dense: 類似最小值,但排名在組之間以1為步長增加

  • numeric_only : boolean, default None

    只包括布爾值,浮點(diǎn)型和整型數(shù)據(jù)

  • na_option : {‘keep’, ‘top’, ‘bottom’}

    • keep: 保留缺失值
    • top: 如果升序則缺失值是最小值
    • bottom: 如果降序則缺失值是最小值
  • ascending : boolean, default True是否升序

  • pct : boolean, default False計(jì)算數(shù)據(jù)的百分比排名

gb.rank(method="max") #將Height和Weight分別取它的排名
'''
    Height  Weight
0   59.0    50.0
1   5.0 21.0
2   50.0    54.0
3   NaN 16.0
4   27.0    33.0
... ... ...
195 22.0    50.0
196 83.0    86.0
197 22.0    44.0
198 31.0    24.0
199 1.0 1.0
'''

2.自定義變換:transform 方法

被調(diào)用的自定義函數(shù), 其傳入值為數(shù)據(jù)源的序列 ,與 agg 的傳入類型是一致的,其最后的返回結(jié)果是行列索引與數(shù)據(jù)源一致的 DataFrame

#1. 傳入lambda或函數(shù)
gb.transform(lambda x: (x-x.mean())/x.std()).head()
#2. 傳入標(biāo)量
gb.transform('mean').head() 

練一練

對于 transform 方法無法像 agg 一樣,通過傳入字典來對指定列使用特定的變換,如果需要在一次 transform 的調(diào)用中實(shí)現(xiàn)這種功能,請給出解決方案。

def test(x):
    if x.name == 'Height':
        return np.sum(x)
    if x.name =='Weight':
        return np.std(x)
df.groupby('Gender')['Height','Weight'].transform(test)

分組的三大操作:過濾

gb.filter(lambda x: x.shape[0] > 100).head()

練一練

從概念上說,索引功能是組過濾功能的子集,請使用 filter 函數(shù)完成 loc[.] 的功能,這里假設(shè) ” . “是元素列表。

 def test2(x, listA):
     result = True
     for i in listA:
         if i not in x['Height'].values:
             result = False
     return result
 gb.filter(lambda x:test2(x,[158.9,162.5]))

四、跨列分組apply

傳入:和filter相同

輸出:3種情況:

輸出 例子 索引情況
標(biāo)量 def BMI(x):
Height = x['Height']/100
Weight = x['Weight']
BMI_value = Weight/Height**2
return BMI_value.mean()
gb.apply(BMI)<br />df.groupby(['Gender','Test_Number'])[['Height','Weight']]<br />gb.apply(lambda x: 0)
結(jié)果得到的是 Series ,索引與 agg 的結(jié)果一致
一維Series gb.apply(lambda x: pd.Series([0,0],index=['a','b'])) 行索引與標(biāo)量情況一致,列索引為 Series 的索引
二維 DataFrame

練一練

  1. 請嘗試在 apply 傳入的自定義函數(shù)中,根據(jù)組的某些特征返回相同長度但索引不同的 Series ,會報(bào)錯(cuò)嗎?

會。。不知道是不是這樣:

gb.apply(lambda x: pd.Series([0,0],index=['a',np.random.choice(list('qwertyu'))]))

報(bào)的錯(cuò)是索引名稱需要是可以hash的類型(Series.name must be a hashable type)

  1. 請嘗試在 apply 傳入的自定義函數(shù)中,根據(jù)組的某些特征返回相同大小但列索引不同的 DataFrame ,會報(bào)錯(cuò)嗎?如果只是行索引不同,會報(bào)錯(cuò)嗎?

不報(bào)錯(cuò)

列索引不同:

gb.apply(lambda x: pd.DataFrame(np.ones((2,2)), index = ['a','b'], columns=pd.Index([('w',np.random.choice(list('xabcd'))),('y','z')])))

<img src="typora-user-images/image-20201225223748508.png" alt="image-20201225223748508" style="zoom:50%;" />

行索引不同:

gb.apply(lambda x: pd.DataFrame(np.ones((2,2)), index = [np.random.choice(list('abcd')),'b'], columns=pd.Index([('w','x'),('y','z')])))

<img src="typora-user-images/image-20201225223939672.png" alt="image-20201225223939672" style="zoom:50%;" />

  1. 在 groupby 對象中還定義了 cov 和 corr 函數(shù),從概念上說也屬于跨列的分組處理。請利用之前定義的 gb 對象,使用apply函數(shù)實(shí)現(xiàn)與 gb.cov() 同樣的功能并比較它們的性能。

每次運(yùn)行的時(shí)間不太相同,數(shù)據(jù)量小,時(shí)間差距不太大。

import datetime
beg_time = datetime.datetime.now()
gb.cov()
end_time = datetime.datetime.now()
print ("gb.cov():", end_time - beg_time)
beg_time = datetime.datetime.now()
gb.apply(lambda x:x.cov())
end_time = datetime.datetime.now()
print ("gb.apply(lambda x:x.cov()):", end_time - beg_time)
'''
gb.cov(): 0:00:00.009975
gb.apply(lambda x:x.cov()): 0:00:00.014961
'''

五、練習(xí)

Ex1:汽車數(shù)據(jù)集

現(xiàn)有一份汽車數(shù)據(jù)集,其中 Brand, Disp., HP 分別代表汽車品牌、發(fā)動(dòng)機(jī)蓄量、發(fā)動(dòng)機(jī)輸出。

In [45]: df = pd.read_csv('data/car.csv')

In [46]: df.head(3)
Out[46]: 
             Brand  Price Country  Reliability  Mileage   Type  Weight  Disp.   HP
0   Eagle Summit 4   8895     USA          4.0       33  Small    2560     97  113
1  Ford Escort   4   7402     USA          2.0       33  Small    2345    114   90
2   Ford Festiva 4   6319   Korea          4.0       37  Small    1845     81   63
  1. 先過濾出所屬 Country 數(shù)超過2個(gè)的汽車,即若該汽車的 Country 在總體數(shù)據(jù)集中出現(xiàn)次數(shù)不超過2則剔除,再按 Country 分組計(jì)算價(jià)格均值、價(jià)格變異系數(shù)、該 Country 的汽車數(shù)量,其中變異系數(shù)的計(jì)算方法是標(biāo)準(zhǔn)差除以均值,并在結(jié)果中把變異系數(shù)重命名為 CoV
country = df['Country'].value_counts()
df2 = df.loc[df.Country.isin(country[country>2].index.tolist())] # index并不是列表,需要用tolist來轉(zhuǎn)成list
def Cov_x(x):
    return np.std(x)/np.mean(x)
df2.groupby('Country')['Price','Brand'].agg({'Price':['mean',('CoV',lambda x:Cov_x(x))], 'Brand':'count' })

#答案
df.groupby('Country').filter(lambda x:x.shape[0]>2).groupby('Country')['Price'].agg([('CoV', lambda x: x.std()/x.mean()), 'mean', 'count'])
  1. 按照表中位置的前三分之一、中間三分之一和后三分之一分組,統(tǒng)計(jì) Price 的均值。
df['position'] = df.index+1
df['position'] = df['position'].mask(df.index<= df['position'].count() /3, 'low').mask((df.index<= df['position'].count()/3*2)&(df.index> df['position'].count()/3), 'middle').mask(df.index>= df['position'].count() /3*2, 'high')
df.groupby('position')['Price'].mean()
答案
condition = ['Head']*20+['Mid']*20+['Tail']*20
df.groupby(condition)['Price'].mean()
  1. 對類型 Type 分組,對 PriceHP 分別計(jì)算最大值和最小值,結(jié)果會產(chǎn)生多級索引,請用下劃線把多級列索引合并為單層索引。
res = df.groupby('Type').agg({'Price':['max'], 'HP':'min'})
res.columns = res.columns.map(lambda x:'_'.join(x))
  1. 對類型 Type 分組,對 HP 進(jìn)行組內(nèi)的 min-max 歸一化。
def normalize(s):
     s_min, s_max = s.min(), s.max()
     res = (s - s_min)/(s_max - s_min)
     return res
 

df.groupby('Type')['HP'].transform(normalize).head()
  1. 對類型 Type 分組,計(jì)算 Disp.HP 的相關(guān)系數(shù)。
df.groupby('Type')[['HP', 'Disp.']].apply(lambda x:np.corrcoef(x['HP'].values, x['Disp.'].values)[0,1])
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容