本章節(jié)以及后續(xù)章節(jié)的源碼帖旨,當(dāng)然也可以從我的github下載扇商,在源碼中我自己加了一些中文注釋毒姨。
對(duì)數(shù)據(jù)集進(jìn)行分組并對(duì)各組應(yīng)用一個(gè)函數(shù)(無(wú)論是聚合還是轉(zhuǎn)換),通常是數(shù)據(jù)分析工作中的重要環(huán)節(jié)溶浴。在將數(shù)據(jù)集加載翘鸭、融合、準(zhǔn)備好之后戳葵,通常就是計(jì)算分組統(tǒng)計(jì)或生成透視表。pandas提供了一個(gè)靈活高效的gruopby功能汉匙,它使你能以一種自然的方式對(duì)數(shù)據(jù)集進(jìn)行切片拱烁、切塊生蚁、摘要等操作。
一戏自、GroupBy技術(shù)
Hadley Wickham(許多熱門(mén)R語(yǔ)言包的作者)創(chuàng)造了一個(gè)用于表示分組運(yùn)算的術(shù)語(yǔ)"split-apply-combine"(拆分-應(yīng)用-合并)邦投。第一個(gè)階段,pandas對(duì)象(無(wú)論是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í)行的操作庐橙。如下圖所示:
1假勿、假設(shè)你想要按key1進(jìn)行分組,并計(jì)算data1列的平均值态鳖。
訪問(wèn)data1转培,并根據(jù)key1調(diào)用groupby。變量grouped是一個(gè)GroupBy對(duì)象浆竭。它實(shí)際上還沒(méi)有進(jìn)行任何計(jì)算浸须,只是含有一些有關(guān)分組鍵df['key1']的中間數(shù)據(jù)而已。換句話(huà)說(shuō)兆蕉,該對(duì)象已經(jīng)有了接下來(lái)對(duì)各分組執(zhí)行運(yùn)算所需的一切信息羽戒。例如,我們可以調(diào)用GroupBy的mean方法來(lái)計(jì)算分組平均值:
如果我們一次傳入多個(gè)數(shù)組的列表虎韵,就會(huì)得到不同的結(jié)果易稠。通過(guò)兩個(gè)鍵對(duì)數(shù)據(jù)進(jìn)行了分組,得到的Series具有一個(gè)層次化索引(由唯一的鍵對(duì)組成):
還可以將列名(可以是字符串包蓝、數(shù)字或其他Python對(duì)象)用作分組鍵:
在執(zhí)行df.groupby('key1').mean()時(shí)驶社,結(jié)果中沒(méi)有key2列。這是因?yàn)閐f['key2']不是數(shù)值數(shù)據(jù)(俗稱(chēng)“麻煩列”)测萎,所以被從結(jié)果中排除了亡电。默認(rèn)情況下,所有數(shù)值列都會(huì)被聚合硅瞧。
GroupBy的size方法份乒,它可以返回一個(gè)含有分組大小的Series:
上面的例子分組鍵均為Series。實(shí)際上,分組鍵可以是任何長(zhǎng)度適當(dāng)?shù)臄?shù)組:
注意或辖,任何分組關(guān)鍵詞中的缺失值瘾英,都會(huì)被從結(jié)果中除去。
2颂暇、對(duì)分組進(jìn)行迭代
GroupBy對(duì)象支持迭代缺谴,可以產(chǎn)生一組二元元組(由分組名和數(shù)據(jù)塊組成)《欤看下面的例子:
對(duì)于多重鍵的情況湿蛔,元組的第一個(gè)元素將會(huì)是由鍵值組成的元組:
將這些數(shù)據(jù)片段做成一個(gè)字典:
groupby默認(rèn)是在axis=0上進(jìn)行分組的,通過(guò)設(shè)置也可以在其他任何軸上進(jìn)行分組县爬。拿上面例子中的df來(lái)說(shuō)阳啥,我們可以根據(jù)dtype對(duì)列進(jìn)行分組:
3、選取一列或列的子集
對(duì)于由DataFrame產(chǎn)生的GroupBy對(duì)象捌省,如果用一個(gè)(單個(gè)字符串)或一組(字符串?dāng)?shù)組)列名對(duì)其進(jìn)行索引苫纤,就能實(shí)現(xiàn)選取部分列進(jìn)行聚合的目的。等價(jià):
4纲缓、通過(guò)字典或Series進(jìn)行分組
已知列的分組關(guān)系:
將這個(gè)字典傳給groupby卷拘,來(lái)構(gòu)造數(shù)組,但我們可以直接傳遞字典(我包含了鍵“f”來(lái)強(qiáng)調(diào)祝高,存在未使用的分組鍵是可以的):
Series也有同樣的功能栗弟,它可以被看做一個(gè)固定大小的映射:
5、通過(guò)函數(shù)進(jìn)行分組
比起使用字典或Series工闺,使用Python函數(shù)是一種更原生的方法定義分組映射乍赫。任何被當(dāng)做分組鍵的函數(shù)都會(huì)在各個(gè)索引值上被調(diào)用一次,其返回值就會(huì)被用作分組名稱(chēng)陆蟆。具體點(diǎn)說(shuō)雷厂,以people為例,其索引值為人的名字叠殷。你可以計(jì)算一個(gè)字符串長(zhǎng)度的數(shù)組改鲫,更簡(jiǎn)單的方法是傳入len函數(shù):
將函數(shù)跟數(shù)組、列表林束、字典像棘、Series混合使用也不是問(wèn)題,因?yàn)槿魏螙|西在內(nèi)部都會(huì)被轉(zhuǎn)換為數(shù)組:
6壶冒、根據(jù)索引級(jí)別分組
層次化索引數(shù)據(jù)集最方便的地方就在于它能夠根據(jù)軸索引的一個(gè)級(jí)別進(jìn)行聚合:
要根據(jù)級(jí)別分組缕题,使用level關(guān)鍵字傳遞級(jí)別序號(hào)或名字:
二、數(shù)據(jù)聚合
聚合指的是任何能夠從數(shù)組產(chǎn)生標(biāo)量值的數(shù)據(jù)轉(zhuǎn)換過(guò)程胖腾。之前的例子已經(jīng)用過(guò)一些烟零,比如mean瘪松、count、min以及sum等瓶摆。你可能想知道在GroupBy對(duì)象上調(diào)用mean()時(shí)究竟發(fā)生了什么凉逛。許多常見(jiàn)的聚合運(yùn)算都有進(jìn)行優(yōu)化。
你可以使用自己發(fā)明的聚合運(yùn)算群井,還可以調(diào)用分組對(duì)象上已經(jīng)定義好的任何方法。例如毫胜,quantile可以計(jì)算Series或DataFrame列的樣本分位數(shù):
如果要使用你自己的聚合函數(shù)书斜,只需將其傳入aggregate或agg方法即可。自定義聚合函數(shù)要比上表中那些經(jīng)過(guò)優(yōu)化的函數(shù)慢得多酵使。這是因?yàn)樵跇?gòu)造中間分組數(shù)據(jù)塊時(shí)存在非常大的開(kāi)銷(xiāo)(函數(shù)調(diào)用荐吉、數(shù)據(jù)重排等)。
有些方法(如describe)也是可以用在這里的:
1口渔、面向列的多函數(shù)應(yīng)用
回到前面小費(fèi)的例子样屠。使用read_csv導(dǎo)入數(shù)據(jù)之后,我們添加了一個(gè)小費(fèi)百分比的列tip_pct:
對(duì)Series或DataFrame列的聚合運(yùn)算其實(shí)就是使用aggregate(使用自定義函數(shù))或調(diào)用諸如mean缺脉、std之類(lèi)的方法痪欲。然而,你可能希望對(duì)不同的列使用不同的聚合函數(shù)攻礼,或一次應(yīng)用多個(gè)函數(shù)业踢。
首先,我根據(jù)天和smoker對(duì)tips進(jìn)行分組:
對(duì)于上表中的那些描述統(tǒng)計(jì)礁扮,可以將函數(shù)名以字符串的形式傳入:
如果傳入一組函數(shù)或函數(shù)名知举,得到的DataFrame的列就會(huì)以相應(yīng)的函數(shù)命名:
并非一定要接受GroupBy自動(dòng)給出的那些列名,特別是lambda函數(shù)太伊,它們的名稱(chēng)是''雇锡,這樣的辨識(shí)度就很低了(通過(guò)函數(shù)的name屬性看看就知道了)。因此僚焦,如果傳入的是一個(gè)由(name,function)元組組成的列表锰提,則各元組的第一個(gè)元素就會(huì)被用作DataFrame的列名(可以將這種二元元組列表看做一個(gè)有序映射):
對(duì)于DataFrame,你還有更多選擇叠赐,你可以定義一組應(yīng)用于全部列的一組函數(shù)欲账,或不同的列應(yīng)用不同的函數(shù)。假設(shè)我們想要對(duì)tip_pct和total_bill列計(jì)算三個(gè)統(tǒng)計(jì)信息:
這相當(dāng)于分別對(duì)各列進(jìn)行聚合芭概,然后用concat將結(jié)果組裝到一起赛不,使用列名用作keys參數(shù):
跟前面一樣,這里也可以傳入帶有自定義名稱(chēng)的一組元組:
想要對(duì)一個(gè)列或不同的列應(yīng)用不同的函數(shù)罢洲。具體的辦法是向agg傳入一個(gè)從列名映射到函數(shù)的字典:
只有將多個(gè)函數(shù)應(yīng)用到至少一列時(shí)踢故,DataFrame才會(huì)擁有層次化的列文黎。
2、以“沒(méi)有行索引”的形式返回聚合數(shù)據(jù)
到目前為止殿较,所有示例中的聚合數(shù)據(jù)都有由唯一的分組鍵組成的索引(可能還是層次化的)耸峭。由于并不總是需要如此,所以你可以向groupby傳入as_index=False以禁用該功能:
三淋纲、apply:一般性的“拆分-應(yīng)用-合并”
1劳闹、最通用的GroupBy方法是apply。
如圖所示洽瞬,apply會(huì)將待處理的對(duì)象拆分成多個(gè)片段本涕,然后對(duì)各片段調(diào)用傳入的函數(shù),最后嘗試將各片段組合到一起伙窃。
回到之前那個(gè)小費(fèi)數(shù)據(jù)集菩颖,假設(shè)你想要根據(jù)分組選出最高的5個(gè)tip_pct值。首先为障,編寫(xiě)一個(gè)選取指定列具有最大值的行的函數(shù):
如果對(duì)smoker分組并用該函數(shù)調(diào)用apply晦闰,就會(huì)得到:
如果傳給apply的函數(shù)能夠接受其他參數(shù)或關(guān)鍵字,則可以將這些內(nèi)容放在函數(shù)名后面一并傳入:
除這些基本用法之外鳍怨,能否充分發(fā)揮apply的威力很大程度上取決于你的創(chuàng)造力呻右。傳入的那個(gè)函數(shù)能做什么全由你說(shuō)了算,它只需返回一個(gè)pandas對(duì)象或標(biāo)量值即可京景。
在GroupBy中窿冯,當(dāng)你調(diào)用諸如describe之類(lèi)的方法時(shí),實(shí)際上只是應(yīng)用了下面兩條代碼的快捷方式而已:
2确徙、禁止分組鍵
分組鍵會(huì)跟原始對(duì)象的索引共同構(gòu)成結(jié)果對(duì)象中的層次化索引醒串。將group_keys=False傳入groupby即可禁止該效果:
3、分位數(shù)和桶分析
pandas有一些能根據(jù)指定面元或樣本分位數(shù)將數(shù)據(jù)拆分成多塊的工具(比如cut和qcut)鄙皇。將這些函數(shù)跟groupby結(jié)合起來(lái)芜赌,就能非常輕松地實(shí)現(xiàn)對(duì)數(shù)據(jù)集的桶(bucket)或分位數(shù)(quantile)分析了。以下面這個(gè)簡(jiǎn)單的隨機(jī)數(shù)據(jù)集為例伴逸,我們利用cut將其裝入長(zhǎng)度相等的桶中:
由cut返回的Categorical對(duì)象可直接傳遞到groupby缠沈。因此,我們可以像下面這樣對(duì)data2列做一些統(tǒng)計(jì)計(jì)算:
這些都是長(zhǎng)度相等的桶错蝴。要根據(jù)樣本分位數(shù)得到大小相等的桶洲愤,使用qcut即可。傳入labels=False即可只獲取分位數(shù)的編號(hào):
示例:用特定于分組的值填充缺失值
對(duì)于缺失數(shù)據(jù)的清理工作顷锰,有時(shí)你會(huì)用dropna將其替換掉柬赐,而有時(shí)則可能會(huì)希望用一個(gè)固定值或由數(shù)據(jù)集本身所衍生出來(lái)的值去填充N(xiāo)A值。這時(shí)就得使用fillna這個(gè)工具了官紫。在下面這個(gè)例子中肛宋,我用平均值去填充N(xiāo)A值:
假設(shè)你需要對(duì)不同的分組填充不同的值州藕。一種方法是將數(shù)據(jù)分組,并使用apply和一個(gè)能夠?qū)Ω鲾?shù)據(jù)塊調(diào)用fillna的函數(shù)即可酝陈。下面是一些有關(guān)美國(guó)幾個(gè)州的示例數(shù)據(jù)床玻,這些州又被分為東部和西部:
用分組平均值去填充N(xiāo)A值:
也可以在代碼中預(yù)定義各組的填充值。由于分組具有一個(gè)name屬性沉帮,所以我們可以拿來(lái)用一下:
示例:隨機(jī)采樣和排列
假設(shè)你想要從一個(gè)大數(shù)據(jù)集中隨機(jī)抽刃馑馈(進(jìn)行替換或不替換)樣本以進(jìn)行蒙特卡羅模擬(Monte Carlo simulation)或其他分析工作∮鑫鳎“抽取”的方式有很多馅精,這里使用的方法是對(duì)Series使用sample方法:
從整副牌中抽出5張,代碼如下:
假設(shè)你想要從每種花色中隨機(jī)抽取兩張牌粱檀。由于花色是牌名的最后一個(gè)字符,所以我們可以據(jù)此進(jìn)行分組漫玄,并使用apply:
也可以這樣寫(xiě):
示例:分組加權(quán)平均數(shù)和相關(guān)系數(shù)
根據(jù)groupby的“拆分-應(yīng)用-合并”范式茄蚯,可以進(jìn)行DataFrame的列與列之間或兩個(gè)Series之間的運(yùn)算(比如分組加權(quán)平均)。以下面這個(gè)數(shù)據(jù)集為例睦优,它含有分組鍵渗常、值以及一些權(quán)重值:
然后可以利用category計(jì)算分組加權(quán)平均數(shù):
另一個(gè)例子,考慮一個(gè)來(lái)自Yahoo!Finance的數(shù)據(jù)集汗盘,其中含有幾只股票和標(biāo)準(zhǔn)普爾500指數(shù)(符號(hào)SPX)的收盤(pán)價(jià):
計(jì)算一個(gè)由日收益率(通過(guò)百分?jǐn)?shù)變化計(jì)算)與SPX之間的年度相關(guān)系數(shù)組成的DataFrame皱碘。下面是一個(gè)實(shí)現(xiàn)辦法,我們先創(chuàng)建一個(gè)函數(shù)隐孽,用它計(jì)算每列和SPX列的成對(duì)相關(guān)系數(shù)癌椿,接下來(lái),我們使用pct_change計(jì)算close_px的百分比變化:
(利用DataFrame的corrwith方法菱阵,可以計(jì)算其列或行跟另一個(gè)Series或DataFrame之間的相關(guān)系數(shù)踢俄,傳入一個(gè)Series將會(huì)返回一個(gè)相關(guān)系數(shù)值Series(針對(duì)各列進(jìn)行計(jì)算))
最后,我們用年對(duì)百分比變化進(jìn)行分組晴及,可以用一個(gè)一行的函數(shù)都办,從每行的標(biāo)簽返回每個(gè)datetime標(biāo)簽的year屬性:
還可以計(jì)算列與列之間的相關(guān)系數(shù)。這里虑稼,我們計(jì)算Apple和Microsoft的年相關(guān)系數(shù):
示例:組級(jí)別的線性回歸
可以用groupby執(zhí)行更為復(fù)雜的分組統(tǒng)計(jì)分析琳钉,只要函數(shù)返回的是pandas對(duì)象或標(biāo)量值即可。例如蛛倦,我可以定義下面這個(gè)regress函數(shù)(利用statsmodels計(jì)量經(jīng)濟(jì)學(xué)庫(kù))對(duì)各數(shù)據(jù)塊執(zhí)行普通最小二乘法(Ordinary Least Squares歌懒,OLS)回歸:
為了按年計(jì)算AAPL對(duì)SPX收益率的線性回歸,執(zhí)行:
四胰蝠、透視表和交叉表
1歼培、透視表
透視表(pivot table)是各種電子表格程序和其他數(shù)據(jù)分析軟件中一種常見(jiàn)的數(shù)據(jù)匯總工具震蒋。它根據(jù)一個(gè)或多個(gè)鍵對(duì)數(shù)據(jù)進(jìn)行聚合,并根據(jù)行和列上的分組鍵將數(shù)據(jù)分配到各個(gè)矩形區(qū)域中躲庄。在Python和pandas中查剖,可以通過(guò)本章所介紹的groupby功能以及(能夠利用層次化索引的)重塑運(yùn)算制作透視表。DataFrame有一個(gè)pivot_table方法噪窘,此外還有一個(gè)頂級(jí)的pandas.pivot_table函數(shù)笋庄。除能為groupby提供便利之外,pivot_table還可以添加分項(xiàng)小計(jì)倔监,也叫做margins直砂。
回到小費(fèi)數(shù)據(jù)集,假設(shè)我想要根據(jù)day和smoker計(jì)算分組平均數(shù)(pivot_table的默認(rèn)聚合類(lèi)型)浩习,并將day和smoker放到行上:
假設(shè)我們只想聚合tip_pct和size静暂,而且想根據(jù)time進(jìn)行分組。我將smoker放到列上谱秽,把day放到行上:
傳入margins=True添加分項(xiàng)小計(jì)洽蛀。這將會(huì)添加標(biāo)簽為All的行和列,其值對(duì)應(yīng)于單個(gè)等級(jí)中所有數(shù)據(jù)的分組統(tǒng)計(jì)疟赊。這里郊供,All值為平均數(shù):不單獨(dú)考慮煙民與非煙民(All列),不單獨(dú)考慮行分組兩個(gè)級(jí)別中的任何單項(xiàng)(All行)近哟。
要使用其他的聚合函數(shù)驮审,將其傳給aggfunc即可。例如吉执,使用count或len可以得到有關(guān)分組大小的交叉表(計(jì)數(shù)或頻率):
如果存在空的組合(也就是NA)疯淫,你可能會(huì)希望設(shè)置一個(gè)fill_value:
2、交叉表:crosstab
交叉表(cross-tabulation鼠证,簡(jiǎn)稱(chēng)crosstab)是一種用于計(jì)算分組頻率的特殊透視表峡竣。
作為調(diào)查分析的一部分,我們可能想要根據(jù)國(guó)籍和用手習(xí)慣對(duì)這段數(shù)據(jù)進(jìn)行統(tǒng)計(jì)匯總量九。雖然可以用pivot_table實(shí)現(xiàn)該功能适掰,但是pandas.crosstab函數(shù)會(huì)更方便:
crosstab的前兩個(gè)參數(shù)可以是數(shù)組或Series,或是數(shù)組列表荠列。就像小費(fèi)數(shù)據(jù):
快速學(xué)習(xí):
第四節(jié) 數(shù)據(jù)加載类浪、存儲(chǔ)
第七節(jié) 數(shù)據(jù)聚合與分組運(yùn)算
數(shù)據(jù)分析案例--1880-2010年間全美嬰兒姓名的處理
數(shù)據(jù)分析案例--MovieLens 1M數(shù)據(jù)集
數(shù)據(jù)分析案例--USA.gov數(shù)據(jù)