本章節(jié)以及后續(xù)章節(jié)的源碼,當(dāng)然也可以從我的github下載,在源碼中我自己加了一些中文注釋。
對數(shù)據(jù)集進行分組并對各組應(yīng)用一個函數(shù)(無論是聚合還是轉(zhuǎn)換),通常是數(shù)據(jù)分析工作中的重要環(huán)節(jié)。在將數(shù)據(jù)集加載、融合、準(zhǔn)備好之后,通常就是計算分組統(tǒng)計或生成透視表。pandas提供了一個靈活高效的gruopby功能,它使你能以一種自然的方式對數(shù)據(jù)集進行切片、切塊、摘要等操作。
一、GroupBy技術(shù)
Hadley Wickham(許多熱門R語言包的作者)創(chuàng)造了一個用于表示分組運算的術(shù)語"split-apply-combine"(拆分-應(yīng)用-合并)。第一個階段,pandas對象(無論是Series、DataFrame還是其他的)中的數(shù)據(jù)會根據(jù)你所提供的一個或多個鍵被拆分(split)為多組。拆分操作是在對象的特定軸上執(zhí)行的。例如,DataFrame可以在其行(axis=0)或列(axis=1)上進行分組。然后,將一個函數(shù)應(yīng)用(apply)到各個分組并產(chǎn)生一個新值。最后,所有這些函數(shù)的執(zhí)行結(jié)果會被合并(combine)到最終的結(jié)果對象中。結(jié)果對象的形式一般取決于數(shù)據(jù)上所執(zhí)行的操作。如下圖所示:

1、假設(shè)你想要按key1進行分組,并計算data1列的平均值。
訪問data1,并根據(jù)key1調(diào)用groupby。變量grouped是一個GroupBy對象。它實際上還沒有進行任何計算,只是含有一些有關(guān)分組鍵df['key1']的中間數(shù)據(jù)而已。換句話說,該對象已經(jīng)有了接下來對各分組執(zhí)行運算所需的一切信息。例如,我們可以調(diào)用GroupBy的mean方法來計算分組平均值:

如果我們一次傳入多個數(shù)組的列表,就會得到不同的結(jié)果。通過兩個鍵對數(shù)據(jù)進行了分組,得到的Series具有一個層次化索引(由唯一的鍵對組成):

還可以將列名(可以是字符串、數(shù)字或其他Python對象)用作分組鍵:

在執(zhí)行df.groupby('key1').mean()時,結(jié)果中沒有key2列。這是因為df['key2']不是數(shù)值數(shù)據(jù)(俗稱“麻煩列”),所以被從結(jié)果中排除了。默認(rèn)情況下,所有數(shù)值列都會被聚合。
GroupBy的size方法,它可以返回一個含有分組大小的Series:

上面的例子分組鍵均為Series。實際上,分組鍵可以是任何長度適當(dāng)?shù)臄?shù)組:

注意,任何分組關(guān)鍵詞中的缺失值,都會被從結(jié)果中除去。
2、對分組進行迭代
GroupBy對象支持迭代,可以產(chǎn)生一組二元元組(由分組名和數(shù)據(jù)塊組成)??聪旅娴睦樱?/p>

對于多重鍵的情況,元組的第一個元素將會是由鍵值組成的元組:

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

groupby默認(rèn)是在axis=0上進行分組的,通過設(shè)置也可以在其他任何軸上進行分組。拿上面例子中的df來說,我們可以根據(jù)dtype對列進行分組:

3、選取一列或列的子集
對于由DataFrame產(chǎn)生的GroupBy對象,如果用一個(單個字符串)或一組(字符串?dāng)?shù)組)列名對其進行索引,就能實現(xiàn)選取部分列進行聚合的目的。等價:



4、通過字典或Series進行分組

已知列的分組關(guān)系:

將這個字典傳給groupby,來構(gòu)造數(shù)組,但我們可以直接傳遞字典(我包含了鍵“f”來強調(diào),存在未使用的分組鍵是可以的):

Series也有同樣的功能,它可以被看做一個固定大小的映射:

5、通過函數(shù)進行分組
比起使用字典或Series,使用Python函數(shù)是一種更原生的方法定義分組映射。任何被當(dāng)做分組鍵的函數(shù)都會在各個索引值上被調(diào)用一次,其返回值就會被用作分組名稱。具體點說,以people為例,其索引值為人的名字。你可以計算一個字符串長度的數(shù)組,更簡單的方法是傳入len函數(shù):

將函數(shù)跟數(shù)組、列表、字典、Series混合使用也不是問題,因為任何東西在內(nèi)部都會被轉(zhuǎn)換為數(shù)組:

6、根據(jù)索引級別分組
層次化索引數(shù)據(jù)集最方便的地方就在于它能夠根據(jù)軸索引的一個級別進行聚合:

要根據(jù)級別分組,使用level關(guān)鍵字傳遞級別序號或名字:

二、數(shù)據(jù)聚合
聚合指的是任何能夠從數(shù)組產(chǎn)生標(biāo)量值的數(shù)據(jù)轉(zhuǎn)換過程。之前的例子已經(jīng)用過一些,比如mean、count、min以及sum等。你可能想知道在GroupBy對象上調(diào)用mean()時究竟發(fā)生了什么。許多常見的聚合運算都有進行優(yōu)化。

你可以使用自己發(fā)明的聚合運算,還可以調(diào)用分組對象上已經(jīng)定義好的任何方法。例如,quantile可以計算Series或DataFrame列的樣本分位數(shù):

如果要使用你自己的聚合函數(shù),只需將其傳入aggregate或agg方法即可。自定義聚合函數(shù)要比上表中那些經(jīng)過優(yōu)化的函數(shù)慢得多。這是因為在構(gòu)造中間分組數(shù)據(jù)塊時存在非常大的開銷(函數(shù)調(diào)用、數(shù)據(jù)重排等)。

有些方法(如describe)也是可以用在這里的:

1、面向列的多函數(shù)應(yīng)用
回到前面小費的例子。使用read_csv導(dǎo)入數(shù)據(jù)之后,我們添加了一個小費百分比的列tip_pct:

對Series或DataFrame列的聚合運算其實就是使用aggregate(使用自定義函數(shù))或調(diào)用諸如mean、std之類的方法。然而,你可能希望對不同的列使用不同的聚合函數(shù),或一次應(yīng)用多個函數(shù)。
首先,我根據(jù)天和smoker對tips進行分組:

對于上表中的那些描述統(tǒng)計,可以將函數(shù)名以字符串的形式傳入:

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

并非一定要接受GroupBy自動給出的那些列名,特別是lambda函數(shù),它們的名稱是'',這樣的辨識度就很低了(通過函數(shù)的name屬性看看就知道了)。因此,如果傳入的是一個由(name,function)元組組成的列表,則各元組的第一個元素就會被用作DataFrame的列名(可以將這種二元元組列表看做一個有序映射):

對于DataFrame,你還有更多選擇,你可以定義一組應(yīng)用于全部列的一組函數(shù),或不同的列應(yīng)用不同的函數(shù)。假設(shè)我們想要對tip_pct和total_bill列計算三個統(tǒng)計信息:

這相當(dāng)于分別對各列進行聚合,然后用concat將結(jié)果組裝到一起,使用列名用作keys參數(shù):

跟前面一樣,這里也可以傳入帶有自定義名稱的一組元組:

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

只有將多個函數(shù)應(yīng)用到至少一列時,DataFrame才會擁有層次化的列。
2、以“沒有行索引”的形式返回聚合數(shù)據(jù)
到目前為止,所有示例中的聚合數(shù)據(jù)都有由唯一的分組鍵組成的索引(可能還是層次化的)。由于并不總是需要如此,所以你可以向groupby傳入as_index=False以禁用該功能:

三、apply:一般性的“拆分-應(yīng)用-合并”
1、最通用的GroupBy方法是apply。
如圖所示,apply會將待處理的對象拆分成多個片段,然后對各片段調(diào)用傳入的函數(shù),最后嘗試將各片段組合到一起。

回到之前那個小費數(shù)據(jù)集,假設(shè)你想要根據(jù)分組選出最高的5個tip_pct值。首先,編寫一個選取指定列具有最大值的行的函數(shù):

如果對smoker分組并用該函數(shù)調(diào)用apply,就會得到:

如果傳給apply的函數(shù)能夠接受其他參數(shù)或關(guān)鍵字,則可以將這些內(nèi)容放在函數(shù)名后面一并傳入:

除這些基本用法之外,能否充分發(fā)揮apply的威力很大程度上取決于你的創(chuàng)造力。傳入的那個函數(shù)能做什么全由你說了算,它只需返回一個pandas對象或標(biāo)量值即可。

在GroupBy中,當(dāng)你調(diào)用諸如describe之類的方法時,實際上只是應(yīng)用了下面兩條代碼的快捷方式而已:

2、禁止分組鍵
分組鍵會跟原始對象的索引共同構(gòu)成結(jié)果對象中的層次化索引。將group_keys=False傳入groupby即可禁止該效果:

3、分位數(shù)和桶分析
pandas有一些能根據(jù)指定面元或樣本分位數(shù)將數(shù)據(jù)拆分成多塊的工具(比如cut和qcut)。將這些函數(shù)跟groupby結(jié)合起來,就能非常輕松地實現(xiàn)對數(shù)據(jù)集的桶(bucket)或分位數(shù)(quantile)分析了。以下面這個簡單的隨機數(shù)據(jù)集為例,我們利用cut將其裝入長度相等的桶中:

由cut返回的Categorical對象可直接傳遞到groupby。因此,我們可以像下面這樣對data2列做一些統(tǒng)計計算:

這些都是長度相等的桶。要根據(jù)樣本分位數(shù)得到大小相等的桶,使用qcut即可。傳入labels=False即可只獲取分位數(shù)的編號:



示例:用特定于分組的值填充缺失值
對于缺失數(shù)據(jù)的清理工作,有時你會用dropna將其替換掉,而有時則可能會希望用一個固定值或由數(shù)據(jù)集本身所衍生出來的值去填充NA值。這時就得使用fillna這個工具了。在下面這個例子中,我用平均值去填充NA值:

假設(shè)你需要對不同的分組填充不同的值。一種方法是將數(shù)據(jù)分組,并使用apply和一個能夠?qū)Ω鲾?shù)據(jù)塊調(diào)用fillna的函數(shù)即可。下面是一些有關(guān)美國幾個州的示例數(shù)據(jù),這些州又被分為東部和西部:

用分組平均值去填充NA值:

也可以在代碼中預(yù)定義各組的填充值。由于分組具有一個name屬性,所以我們可以拿來用一下:

示例:隨機采樣和排列
假設(shè)你想要從一個大數(shù)據(jù)集中隨機抽?。ㄟM行替換或不替換)樣本以進行蒙特卡羅模擬(Monte Carlo simulation)或其他分析工作?!俺槿 钡姆绞接泻芏啵@里使用的方法是對Series使用sample方法:

從整副牌中抽出5張,代碼如下:

假設(shè)你想要從每種花色中隨機抽取兩張牌。由于花色是牌名的最后一個字符,所以我們可以據(jù)此進行分組,并使用apply:

也可以這樣寫:


示例:分組加權(quán)平均數(shù)和相關(guān)系數(shù)
根據(jù)groupby的“拆分-應(yīng)用-合并”范式,可以進行DataFrame的列與列之間或兩個Series之間的運算(比如分組加權(quán)平均)。以下面這個數(shù)據(jù)集為例,它含有分組鍵、值以及一些權(quán)重值:

然后可以利用category計算分組加權(quán)平均數(shù):

另一個例子,考慮一個來自Yahoo!Finance的數(shù)據(jù)集,其中含有幾只股票和標(biāo)準(zhǔn)普爾500指數(shù)(符號SPX)的收盤價:

計算一個由日收益率(通過百分?jǐn)?shù)變化計算)與SPX之間的年度相關(guān)系數(shù)組成的DataFrame。下面是一個實現(xiàn)辦法,我們先創(chuàng)建一個函數(shù),用它計算每列和SPX列的成對相關(guān)系數(shù),接下來,我們使用pct_change計算close_px的百分比變化:
(利用DataFrame的corrwith方法,可以計算其列或行跟另一個Series或DataFrame之間的相關(guān)系數(shù),傳入一個Series將會返回一個相關(guān)系數(shù)值Series(針對各列進行計算))


最后,我們用年對百分比變化進行分組,可以用一個一行的函數(shù),從每行的標(biāo)簽返回每個datetime標(biāo)簽的year屬性:

還可以計算列與列之間的相關(guān)系數(shù)。這里,我們計算Apple和Microsoft的年相關(guān)系數(shù):

示例:組級別的線性回歸
可以用groupby執(zhí)行更為復(fù)雜的分組統(tǒng)計分析,只要函數(shù)返回的是pandas對象或標(biāo)量值即可。例如,我可以定義下面這個regress函數(shù)(利用statsmodels計量經(jīng)濟學(xué)庫)對各數(shù)據(jù)塊執(zhí)行普通最小二乘法(Ordinary Least Squares,OLS)回歸:

為了按年計算AAPL對SPX收益率的線性回歸,執(zhí)行:

四、透視表和交叉表
1、透視表
透視表(pivot table)是各種電子表格程序和其他數(shù)據(jù)分析軟件中一種常見的數(shù)據(jù)匯總工具。它根據(jù)一個或多個鍵對數(shù)據(jù)進行聚合,并根據(jù)行和列上的分組鍵將數(shù)據(jù)分配到各個矩形區(qū)域中。在Python和pandas中,可以通過本章所介紹的groupby功能以及(能夠利用層次化索引的)重塑運算制作透視表。DataFrame有一個pivot_table方法,此外還有一個頂級的pandas.pivot_table函數(shù)。除能為groupby提供便利之外,pivot_table還可以添加分項小計,也叫做margins。

回到小費數(shù)據(jù)集,假設(shè)我想要根據(jù)day和smoker計算分組平均數(shù)(pivot_table的默認(rèn)聚合類型),并將day和smoker放到行上:

假設(shè)我們只想聚合tip_pct和size,而且想根據(jù)time進行分組。我將smoker放到列上,把day放到行上:

傳入margins=True添加分項小計。這將會添加標(biāo)簽為All的行和列,其值對應(yīng)于單個等級中所有數(shù)據(jù)的分組統(tǒng)計。這里,All值為平均數(shù):不單獨考慮煙民與非煙民(All列),不單獨考慮行分組兩個級別中的任何單項(All行)。

要使用其他的聚合函數(shù),將其傳給aggfunc即可。例如,使用count或len可以得到有關(guān)分組大小的交叉表(計數(shù)或頻率):

如果存在空的組合(也就是NA),你可能會希望設(shè)置一個fill_value:

2、交叉表:crosstab
交叉表(cross-tabulation,簡稱crosstab)是一種用于計算分組頻率的特殊透視表。

作為調(diào)查分析的一部分,我們可能想要根據(jù)國籍和用手習(xí)慣對這段數(shù)據(jù)進行統(tǒng)計匯總。雖然可以用pivot_table實現(xiàn)該功能,但是pandas.crosstab函數(shù)會更方便:

crosstab的前兩個參數(shù)可以是數(shù)組或Series,或是數(shù)組列表。就像小費數(shù)據(jù):

快速學(xué)習(xí):
數(shù)據(jù)分析案例--1880-2010年間全美嬰兒姓名的處理
數(shù)據(jù)分析案例--MovieLens 1M數(shù)據(jù)集
數(shù)據(jù)分析案例--USA.gov數(shù)據(jù)