《利用Python進(jìn)行數(shù)據(jù)分析》第9章 groupby技術(shù)和數(shù)據(jù)聚合筆記

數(shù)據(jù)聚合與分組運(yùn)算

對(duì)數(shù)據(jù)集進(jìn)行分組并對(duì)各組應(yīng)用一個(gè)函數(shù)(無論是聚合還是轉(zhuǎn)換)寻仗,這是數(shù)據(jù)分析工作中的重要環(huán)節(jié)仲墨。在將數(shù)據(jù)集準(zhǔn)備好之后,通常的任務(wù)就是計(jì)算分組統(tǒng)計(jì)或生成透視表

在本章中蹬碧,你將會(huì)學(xué)到:

  • 根據(jù)一個(gè)或多個(gè)鍵(可以是函數(shù)色鸳、數(shù)組或DataFrame列名)拆分pandas對(duì)象。
  • 計(jì)算分組摘要統(tǒng)計(jì)坠七,如計(jì)數(shù)水醋、平均值、標(biāo)準(zhǔn)差彪置,或用戶自定義函數(shù)拄踪。
  • 對(duì)DataFrame的列應(yīng)用各種各樣的函數(shù)。
  • 應(yīng)用組內(nèi)轉(zhuǎn)換或其他運(yùn)算拳魁,如規(guī)格化惶桐、線性回歸、排名或選取子集等潘懊。
  • 計(jì)算透視表或交叉表姚糊。
  • 執(zhí)行分位數(shù)分析以及其他分組分析。

分組運(yùn)算的第一個(gè)階段授舟,pandas對(duì)象(無論是Series救恨、DataFrame還是其他的)中的數(shù)據(jù)會(huì)根據(jù)你所提供的一個(gè)或多個(gè)鍵被拆分(split)為多組。拆分操作是在對(duì)象的特定軸上執(zhí)行的释树。例如肠槽,DataFrame可以在其行(axis=0)或列(axis=1)上進(jìn)行分組。然后躏哩,將一個(gè)函數(shù)應(yīng)用(apply)到各個(gè)分組并產(chǎn)生一個(gè)新值署浩。最后揉燃,所有這些函數(shù)的執(zhí)行結(jié)果會(huì)被合并(combine)到最終的結(jié)果對(duì)象中扫尺。結(jié)果對(duì)象的形式一般取決于數(shù)據(jù)上所執(zhí)行的操作。圖9-1大致說明了一個(gè)簡單的分組聚合過程炊汤。

分組聚合演示

GroupBy技術(shù)

分組鍵可以有多種形式正驻,且類型不必相同:

  • 列表或數(shù)組,其長度與待分組的軸一樣抢腐。

  • 表示DataFrame某個(gè)列名的值姑曙。

  • 字典或Series,給出待分組軸上的值與分組名之間的對(duì)應(yīng)關(guān)系迈倍。

  • 函數(shù)伤靠,用于處理軸索引或索引中的各個(gè)標(biāo)簽。

from pandas import Series,DataFrame
import pandas as pd
import numpy as np
df=DataFrame({'key1':['a','a','b','b','a'],'key2':['one','two','one','two','one'],
              'data1':np.random.randn(5),'data2':np.random.randn(5)})
df

按key1進(jìn)行分組啼染,并計(jì)算data1列的平均值宴合』烂罚可以通過訪問data1,并根據(jù)key1調(diào)用groupby

grouped=df['data1'].groupby(df['key1'])
grouped
<pandas.core.groupby.SeriesGroupBy object at 0x000000001052FDA0>

變量grouped是一個(gè)GroupBy對(duì)象,接下來調(diào)用GroupBy的mean方法來計(jì)算分組平均值

grouped.mean()
key1
a    0.597727
b    0.318738
Name: data1, dtype: float64

如果一次傳入多個(gè)數(shù)組卦洽,會(huì)得到不同的結(jié)果

means=df['data1'].groupby([df['key1'],df['key2']]).mean()
means
key1  key2
a     one     0.595071
      two     0.603039
b     one    -0.077999
      two     0.715474
Name: data1, dtype: float64

上面通過兩個(gè)鍵對(duì)數(shù)據(jù)進(jìn)行了分組贞言,得到的Series具有一個(gè)層次化索引(由唯一的鍵對(duì)組成),下面進(jìn)行調(diào)換

means.unstack()

以上的例子中分組鍵都是Series阀蒂。其實(shí)该窗,分組鍵可以是任何長度適當(dāng)?shù)臄?shù)組

states=np.array(['Ohio','California','California','Ohio','Ohio'])
years=np.array([2005,2005,2006,2005,2006])
states
array(['Ohio', 'California', 'California', 'Ohio', 'Ohio'],
      dtype='<U10')
years
array([2005, 2005, 2006, 2005, 2006])
df['data1'].groupby([states,years]).mean()
California  2005    0.603039
            2006   -0.077999
Ohio        2005    0.107602
            2006    1.690412
Name: data1, dtype: float64

可以使用列名的字符串、數(shù)字或其他Python對(duì)象用作分組鍵

df.groupby('key1').mean()
df.groupby(['key1','key2']).mean()

groupby的size方法,可以返回一個(gè)含有分組大小的Series

df.groupby(['key1','key2']).size()
key1  key2
a     one     2
      two     1
b     one     1
      two     1
dtype: int64

對(duì)分組進(jìn)行迭代

groupby對(duì)象可以產(chǎn)生一組二元元組(由分組名和數(shù)據(jù)塊組成)

for name,group in df.groupby('key1'):
    print(name)
    print(group)
a
      data1     data2 key1 key2
0 -0.500271  1.820004    a  one
1  0.603039 -0.783806    a  two
4  1.690412 -2.138761    a  one
b
      data1     data2 key1 key2
2 -0.077999 -1.464172    b  one
3  0.715474  1.048138    b  two

對(duì)于多重鍵的情況蚤霞,元組的第一個(gè)元素將會(huì)是由鍵值組成的元組

for (k1,k2),group in df.groupby(['key1','key2']):
    print(k1,k2)
    print(group)
a one
      data1     data2 key1 key2
0 -0.500271  1.820004    a  one
4  1.690412 -2.138761    a  one
a two
      data1     data2 key1 key2
1  0.603039 -0.783806    a  two
b one
      data1     data2 key1 key2
2 -0.077999 -1.464172    b  one
b two
      data1     data2 key1 key2
3  0.715474  1.048138    b  two

將這些數(shù)據(jù)片段做成一個(gè)字典

pieces=dict(list(df.groupby('key1')))
pieces
{'a':       data1     data2 key1 key2
 0 -0.500271  1.820004    a  one
 1  0.603039 -0.783806    a  two
 4  1.690412 -2.138761    a  one, 'b':       data1     data2 key1 key2
 2 -0.077999 -1.464172    b  one
 3  0.715474  1.048138    b  two}
pieces['b']

groupby默認(rèn)是在axis=0上進(jìn)行分組的,下面根據(jù)dtype對(duì)列進(jìn)行分組

df.dtypes
data1    float64
data2    float64
key1      object
key2      object
dtype: object
grouped=df.groupby(df.dtypes,axis=1)
dict(list(grouped))
{dtype('float64'):       data1     data2
 0 -0.500271  1.820004
 1  0.603039 -0.783806
 2 -0.077999 -1.464172
 3  0.715474  1.048138
 4  1.690412 -2.138761, dtype('O'):   key1 key2
 0    a  one
 1    a  two
 2    b  one
 3    b  two
 4    a  one}

選取一個(gè)或一組列

對(duì)于由DataFrame產(chǎn)生的GroupBy對(duì)象酗失,如果用一個(gè)(單個(gè)字符串)或一組(字符串?dāng)?shù)組)列名對(duì)其進(jìn)行索引,就能實(shí)現(xiàn)選取部分列進(jìn)行聚合的目的

df.groupby('key1')['data1']
<pandas.core.groupby.SeriesGroupBy object at 0x00000000105DC400>
df['data1'].groupby(df['key1'])
<pandas.core.groupby.SeriesGroupBy object at 0x00000000105DC7F0>

對(duì)于大數(shù)據(jù)集昧绣,很可能只需要對(duì)部分列進(jìn)行聚合级零。例如,在前面那個(gè)數(shù)據(jù)集中滞乙,如果只需計(jì)算data2列的平均值并以DataFrame形式得到結(jié)果奏纪,我們可以編寫

df.groupby(['key1','key2'])[['data2']].mean()

這種索引操作所返回的對(duì)象是一個(gè)已分組的DataFrame(如果傳入的是列表或數(shù)組)或已分組的Series(如果傳入的是標(biāo)量形式的單個(gè)列名)

s_grouped=df.groupby(['key1','key2'])['data2']
s_grouped
<pandas.core.groupby.SeriesGroupBy object at 0x00000000105C7470>
s_grouped.mean()
key1  key2
a     one    -0.159378
      two    -0.783806
b     one    -1.464172
      two     1.048138
Name: data2, dtype: float64

通過字典或Series進(jìn)行分組

除數(shù)組以外,分組信息還可以其他形式存在斩启,實(shí)例:DataFrame

people=DataFrame(np.random.randn(5,5),columns=['a','b','c','d','e'],
                index=['Joe','Steve','Wes','Jim','Travis'])
people.loc[2:3,['b','c']]=np.nan #添加NA值
people

假設(shè)已知列的分組關(guān)系序调,并希望根據(jù)分組計(jì)算列的總計(jì),首先把這個(gè)字典傳給groupby

mapping={'a':'red','b':'red','c':'blue','d':'blue','e':'red','f':'orange'}
by_column=people.groupby(mapping,axis=1)
by_column.sum()

Series也有同樣的功能,它可以被看做一個(gè)固定大小的映射兔簇。這個(gè)例子发绢,如果用Series作為分組鍵,則pandas會(huì)檢查Series以確保其索引跟分組軸是對(duì)齊的

map_series=Series(mapping)
map_series
a       red
b       red
c      blue
d      blue
e       red
f    orange
dtype: object
people.groupby(map_series,axis=1).count()
blue red
Joe 2 3
Steve 2 3
Wes 1 2
Jim 2 3
Travis 2 3

通過函數(shù)進(jìn)行分組

任何被當(dāng)做分組鍵的函數(shù)都會(huì)在各個(gè)索引值上被調(diào)用一次垄琐,其返回值就會(huì)被用作分組名稱边酒。

具體點(diǎn)說,以上的示例DataFrame為例狸窘,其索引值為人的名字墩朦。假設(shè)你希望根據(jù)人名的長度進(jìn)行分組,雖然可以求取一個(gè)字符串長度數(shù)組翻擒,但其實(shí)僅僅傳入len函數(shù)

people.groupby(len).sum()

將函數(shù)跟數(shù)組氓涣、列表、字典陋气、Series混合使用可以劳吠,因?yàn)槿魏螙|西最終都會(huì)被轉(zhuǎn)換為數(shù)組

key_list=['one','one','one','two','two']
people.groupby([len,key_list]).min()

根據(jù)索引級(jí)別分組

層次化索引數(shù)據(jù)集最方便的地方就在于它能夠根據(jù)索引級(jí)別進(jìn)行聚合。通過level關(guān)鍵字傳入級(jí)別編號(hào)或名稱

columns=pd.MultiIndex.from_arrays([['US','US','US','JP','JP'],
                                     [1,3,5,1,3]],names=['cty','tenor'])
hier_df=DataFrame(np.random.randn(4,5),columns=columns)
hier_df
hier_df.groupby(level='cty',axis=1).count()

數(shù)據(jù)聚合

對(duì)于聚合巩趁,指的是任何能夠從數(shù)組產(chǎn)生標(biāo)量值的數(shù)據(jù)轉(zhuǎn)換過程痒玩。
使用自己發(fā)明的聚合運(yùn)算,還可以調(diào)用分組對(duì)象上已經(jīng)定義好的任何方法。

例如蠢古,quantile可以計(jì)算Series或DataFrame列的樣本分位數(shù)

df=DataFrame({'key1':['a','a','b','b','a'],'key2':['one','two','one','two','one'],
              'data1':np.random.randn(5),'data2':np.random.randn(5)})
df
grouped=df.groupby('key1')
grouped['data1'].quantile(0.9)
key1
a   -0.932423
b   -0.006997
Name: data1, dtype: float64

如果要使用你自己的聚合函數(shù)燃观,只需將其傳入aggregate或agg方法

def peak_to_peak(arr):
    return arr.max()-arr.min()
grouped.agg(peak_to_peak)

describe也可以使用,即使嚴(yán)格來講便瑟,它們并非聚合運(yùn)算

grouped.describe()

data1     data2
key1
a    count  3.000000  3.000000
     mean   0.746672  0.910916
     std    1.109736  0.712217
     min   -0.204708  0.092908
     25%     0.137118  0.669671
     50%     0.478943  1.246435
     75%     1.222362  1.319920
     max     1.965781  1.393406
b    count   2.000000  2.000000
     mean   -0.537585  0.525384
     std     0.025662  0.344556
     min    -0.555730  0.281746
     25%    -0.546657  0.403565
     50%    -0.537585  0.525384
     75%    -0.528512  0.647203
     max    -0.519439  0.769023

注意:自定義聚合函數(shù)要比表9-1中那些經(jīng)過優(yōu)化的函數(shù)慢得多缆毁。這是因?yàn)樵跇?gòu)造中間分組數(shù)據(jù)塊時(shí)存在非常大的開銷(函數(shù)調(diào)用、數(shù)據(jù)重排等)

為了說明一些更高級(jí)的聚合功能到涂,將使用一個(gè)有關(guān)餐館小費(fèi)的數(shù)據(jù)集(在本書的GitHub庫中)https://github.com/wesm/pydata-book/tree/1st-edition

通過read_csv加載之后脊框,添加一個(gè)表示小費(fèi)比例的列tip_pct

tips=pd.read_csv('pydata_book/ch08/tips.csv')  #加載數(shù)據(jù)
tips[:8]  #選取前8個(gè)數(shù)據(jù)集

    total_bill  tip sex smoker  day time    size
0   16.99   1.01    Female  No  Sun Dinner  2
1   10.34   1.66    Male    No  Sun Dinner  3
2   21.01   3.50    Male    No  Sun Dinner  3
3   23.68   3.31    Male    No  Sun Dinner  2
4   24.59   3.61    Female  No  Sun Dinner  4
5   25.29   4.71    Male    No  Sun Dinner  4
6   8.77    2.00    Male    No  Sun Dinner  2
7   26.88   3.12    Male    No  Sun Dinner  4
tips['tip_pct']=tips['tip']/tips['total_bill']  #添加“小費(fèi)占總額百分比”的列
tips[:8]

    total_bill  tip sex smoker  day time    size    tip_pct
0   16.99   1.01    Female  No  Sun Dinner  2   0.059447
1   10.34   1.66    Male    No  Sun Dinner  3   0.160542
2   21.01   3.50    Male    No  Sun Dinner  3   0.166587
3   23.68   3.31    Male    No  Sun Dinner  2   0.139780
4   24.59   3.61    Female  No  Sun Dinner  4   0.146808
5   25.29   4.71    Male    No  Sun Dinner  4   0.186240
6   8.77    2.00    Male    No  Sun Dinner  2   0.228050
7   26.88   3.12    Male    No  Sun Dinner  4   0.116071

面向列的多函數(shù)應(yīng)用

我們已經(jīng)看到,對(duì)Series或DataFrame列的聚合運(yùn)算其實(shí)就是使用aggregate(使用自定義函數(shù))或調(diào)用諸如mean践啄、std之類的方法浇雹。然而,你可能希望對(duì)不同的列使用不同的聚合函數(shù)屿讽,或一次應(yīng)用多個(gè)函數(shù)昭灵。

下面將練習(xí)各種實(shí)例

grouped=tips.groupby(['sex','smoker'])#根據(jù)sex和smoker對(duì)tips進(jìn)行分組
grouped_pct=grouped['tip_pct']   #將函數(shù)名以字符串的形式傳入
grouped_pct.agg('mean')
sex     smoker
Female  No        0.156921
        Yes       0.182150
Male    No        0.160669
        Yes       0.152771
Name: tip_pct, dtype: float64

傳入一組函數(shù)或函數(shù)名,得到的DataFrame的列就會(huì)以相應(yīng)的函數(shù)命名

grouped_pct.agg(['mean','std',peak_to_peak])


mean    std peak_to_peak
sex smoker          
Female  No  0.156921    0.036421    0.195876
Yes 0.182150    0.071595    0.360233
Male    No  0.160669    0.041849    0.220186
Yes 0.152771    0.090588    0.674707

如果傳入的是一個(gè)由(name,function)元組組成的列表伐谈,則各元組的第一個(gè)元素就會(huì)被用作DataFrame的列名(可以將這種二元元組列表看做一個(gè)有序映射)

grouped_pct.agg([('foo','mean'),('bar',np.std)])

        foo bar
sex smoker      
Female  No  0.156921    0.036421
Yes 0.182150    0.071595
Male    No  0.160669    0.041849
Yes 0.152771    0.090588

對(duì)于DataFram,定義一組應(yīng)用于全部列的函數(shù)烂完,或不同的列應(yīng)用不同的函數(shù)。假設(shè)我們想要對(duì)tip_pct和total_bill列計(jì)算三個(gè)統(tǒng)計(jì)信息

functions=['count','mean','max']
result=grouped['tip_pct','total_bill'].agg(functions)
result

結(jié)果DataFrame擁有層次化的列诵棵,這相當(dāng)于分別對(duì)各列進(jìn)行聚合抠蚣,然后用concat將結(jié)果組裝到一起(列名用作keys參數(shù))

result['tip_pct']

傳入帶有自定義名稱的元組列表

ftuples=[('Durchschnitt','mean'),('Abweichung',np.var)]
grouped['tip_pct','total_bill'].agg(ftuples)

如果想要對(duì)不同的列應(yīng)用不同的函數(shù)。具體的辦法是向agg傳入一個(gè)從列名映射到函數(shù)的字典

grouped.agg({'tip':np.max,'size':'sum'})
grouped.agg({'tip_pct':['min','max','mean','std'],'size':'sum'})

只有將多個(gè)函數(shù)應(yīng)用到至少一列時(shí)履澳,DataFrame才會(huì)擁有層次化的列嘶窄。

以“無索引”的形式返回聚合數(shù)據(jù)

示例中的聚合數(shù)據(jù)都有由唯一的分組鍵組成的索引(可能還是層次化的)。由于并不總是需要如此距贷,可以向groupby傳入as_index=False以禁用該功能

對(duì)結(jié)果調(diào)用reset_index也能得到這種形式的結(jié)果

tips.groupby(['sex','smoker'],as_index=False).mean()

下一節(jié)學(xué)習(xí)分組級(jí)運(yùn)算和轉(zhuǎn)換柄冲。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市忠蝗,隨后出現(xiàn)的幾起案子现横,更是在濱河造成了極大的恐慌,老刑警劉巖什湘,帶你破解...
    沈念sama閱讀 212,383評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件长赞,死亡現(xiàn)場(chǎng)離奇詭異晦攒,居然都是意外死亡闽撤,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,522評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門脯颜,熙熙樓的掌柜王于貴愁眉苦臉地迎上來哟旗,“玉大人,你說我怎么就攤上這事≌⒉停” “怎么了饱亮?”我有些...
    開封第一講書人閱讀 157,852評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長舍沙。 經(jīng)常有香客問我近上,道長,這世上最難降的妖魔是什么拂铡? 我笑而不...
    開封第一講書人閱讀 56,621評(píng)論 1 284
  • 正文 為了忘掉前任壹无,我火速辦了婚禮,結(jié)果婚禮上感帅,老公的妹妹穿的比我還像新娘斗锭。我一直安慰自己,他們只是感情好失球,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,741評(píng)論 6 386
  • 文/花漫 我一把揭開白布岖是。 她就那樣靜靜地躺著,像睡著了一般实苞。 火紅的嫁衣襯著肌膚如雪豺撑。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,929評(píng)論 1 290
  • 那天黔牵,我揣著相機(jī)與錄音前硫,去河邊找鬼。 笑死荧止,一個(gè)胖子當(dāng)著我的面吹牛屹电,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播跃巡,決...
    沈念sama閱讀 39,076評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼危号,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了素邪?” 一聲冷哼從身側(cè)響起外莲,我...
    開封第一講書人閱讀 37,803評(píng)論 0 268
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎兔朦,沒想到半個(gè)月后偷线,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,265評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡沽甥,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,582評(píng)論 2 327
  • 正文 我和宋清朗相戀三年声邦,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片摆舟。...
    茶點(diǎn)故事閱讀 38,716評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡亥曹,死狀恐怖邓了,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情媳瞪,我是刑警寧澤骗炉,帶...
    沈念sama閱讀 34,395評(píng)論 4 333
  • 正文 年R本政府宣布,位于F島的核電站蛇受,受9級(jí)特大地震影響句葵,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜兢仰,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,039評(píng)論 3 316
  • 文/蒙蒙 一笼呆、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧旨别,春花似錦诗赌、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,798評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至递览,卻和暖如春叼屠,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背绞铃。 一陣腳步聲響...
    開封第一講書人閱讀 32,027評(píng)論 1 266
  • 我被黑心中介騙來泰國打工镜雨, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人儿捧。 一個(gè)月前我還...
    沈念sama閱讀 46,488評(píng)論 2 361
  • 正文 我出身青樓荚坞,卻偏偏與公主長得像,于是被迫代替她去往敵國和親菲盾。 傳聞我的和親對(duì)象是個(gè)殘疾皇子颓影,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,612評(píng)論 2 350

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