《利用Python進行數據分析·第2版》第10章(上) 數據聚合與分組運算

《利用Python進行數據分析·第2版》第10章(上) 數據聚合與分組運算

來自專欄 Python程序員17 人贊了文章

作者:SeanCheney Python愛好者社區專欄作者

簡書專欄:jianshu.com/u/130f76596

前文傳送門:

【翻譯】《利用Python進行數據分析·第2版》第1章 準備工作

【翻譯】《利用Python進行數據分析·第2版》第2章(上)Python語法基礎,IPython和Jupyter

【翻譯】《利用Python進行數據分析·第2版》第2章(中)Python語法基礎,IPython和Jupyter

【翻譯】《利用Python進行數據分析·第2版》第2章(下)Python語法基礎,IPython和Jupyter

【翻譯】《利用Python進行數據分析·第2版》第3章(上)Python的數據結構、函數和文件

【翻譯】《利用Python進行數據分析·第2版》第3章(中)Python的數據結構、函數和文件

【翻譯】《利用Python進行數據分析·第2版》第3章(下)Python的數據結構、函數和文件

【翻譯】《利用Python進行數據分析·第2版》第4章(上)NumPy基礎:數組和矢量計算

【翻譯】《利用Python進行數據分析·第2版》第4章(中)NumPy基礎:數組和矢量計算

【翻譯】《利用Python進行數據分析·第2版》第4章(下)NumPy基礎:數組和矢量計算

【翻譯】《利用Python進行數據分析·第2版》第5章(上)pandas入門

【翻譯】《利用Python進行數據分析·第2版》第5章(中)pandas入門

【翻譯】《利用Python進行數據分析·第2版》第5章(下)pandas入門

【翻譯】《利用Python進行數據分析·第2版》第6章(上) 數據載入、存儲與文件格式

【翻譯】《利用Python進行數據分析·第2版》第6章(中) 數據載入、存儲與文件格式

【翻譯】《利用Python進行數據分析·第2版》第6章(下) 數據載入、存儲與文件格式

【翻譯】《利用Python進行數據分析·第2版》第7章(上)數據清洗和準備

【翻譯】《利用Python進行數據分析·第2版》第7章(中) 數據清洗和準備

【翻譯】《利用Python進行數據分析·第2版》第7章(下) 數據清洗和準備

【翻譯】《利用Python進行數據分析·第2版》第8章(上) 數據規整:聚合、合併和重塑

【翻譯】《利用Python進行數據分析·第2版》第8章(中) 數據規整:聚合、合併和重塑

【翻譯】《利用Python進行數據分析·第2版》第8章(下) 數據規整:聚合、合併和重塑

【翻譯】《利用Python進行數據分析·第2版》第9章(上) 繪圖和可視化

【翻譯】《利用Python進行數據分析·第2版》第9章(中) 繪圖和可視化

【翻譯】《利用Python進行數據分析·第2版》第9章(下) 繪圖和可視化

對數據集進行分組並對各組應用一個函數(無論是聚合還是轉換),通常是數據分析工作中的重要環節。在將數據集載入、融合、準備好之後,通常就是計算分組統計或生成透視表。pandas提供了一個靈活高效的gruopby功能,它使你能以一種自然的方式對數據集進行切片、切塊、摘要等操作。

關係型資料庫和SQL(Structured Query Language,結構化查詢語言)能夠如此流行的原因之一就是其能夠方便地對數據進行連接、過濾、轉換和聚合。但是,像SQL這樣的查詢語言所能執行的分組運算的種類很有限。在本章中你將會看到,由於Python和pandas強大的表達能力,我們可以執行複雜得多的分組運算(利用任何可以接受pandas對象或NumPy數組的函數)。在本章中,你將會學到:

  • 計算分組摘要統計,如計數、平均值、標準差,或用戶自定義函數。
  • 計算分組的概述統計,比如數量、平均值或標準差,或是用戶定義的函數。
  • 應用組內轉換或其他運算,如規格化、線性回歸、排名或選取子集等。
  • 計算透視表或交叉表。
  • 執行分位數分析以及其它統計分組分析。

筆記:對時間序列數據的聚合(groupby的特殊用法之一)也稱作重採樣(resampling),本書將在第11章中單獨對其進行講解。

10.1 GroupBy機制

Hadley Wickham(許多熱門R語言包的作者)創造了一個用於表示分組運算的術語"split-apply-combine"(拆分-應用-合併)。第一個階段,pandas對象(無論是Series、DataFrame還是其他的)中的數據會根據你所提供的一個或多個鍵被拆分(split)為多組。拆分操作是在對象的特定軸上執行的。例如,DataFrame可以在其行(axis=0)或列(axis=1)上進行分組。然後,將一個函數應用(apply)到各個分組併產生一個新值。最後,所有這些函數的執行結果會被合併(combine)到最終的結果對象中。結果對象的形式一般取決於數據上所執行的操作。圖10-1大致說明了一個簡單的分組聚合過程。

圖10-1 分組聚合演示

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

  • 列表或數組,其長度與待分組的軸一樣。
  • 表示DataFrame某個列名的值。
  • 字典或Series,給出待分組軸上的值與分組名之間的對應關係。
  • 函數,用於處理軸索引或索引中的各個標籤。

注意,後三種都只是快捷方式而已,其最終目的仍然是產生一組用於拆分對象的值。如果覺得這些東西看起來很抽象,不用擔心,我將在本章中給出大量有關於此的示例。首先來看看下面這個非常簡單的表格型數據集(以DataFrame的形式):

In [10]: df = pd.DataFrame({key1 : [a, a, b, b, a], ....: key2 : [one, two, one, two, one], ....: data1 : np.random.randn(5), ....: data2 : np.random.randn(5)})In [11]: dfOut[11]: data1 data2 key1 key20 -0.204708 1.393406 a one1 0.478943 0.092908 a two2 -0.519439 0.281746 b one3 -0.555730 0.769023 b two4 1.965781 1.246435 a one

假設你想要按key1進行分組,並計算data1列的平均值。實現該功能的方式有很多,而我們這裡要用的是:訪問data1,並根據key1調用groupby:

In [12]: grouped = df[data1].groupby(df[key1])In [13]: groupedOut[13]: <pandas.core.groupby.SeriesGroupBy object at 0x7faa31537390>

變數grouped是一個GroupBy對象。它實際上還沒有進行任何計算,只是含有一些有關分組鍵df[key1]的中間數據而已。換句話說,該對象已經有了接下來對各分組執行運算所需的一切信息。例如,我們可以調用GroupBy的mean方法來計算分組平均值:

In [14]: grouped.mean()Out[14]: key1a 0.746672b -0.537585Name: data1, dtype: float64

稍後我將詳細講解.mean()的調用過程。這裡最重要的是,數據(Series)根據分組鍵進行了聚合,產生了一個新的Series,其索引為key1列中的唯一值。之所以結果中索引的名稱為key1,是因為原始DataFrame的列df[key1]就叫這個名字。

如果我們一次傳入多個數組的列表,就會得到不同的結果:

In [15]: means = df[data1].groupby([df[key1], df[key2]]).mean()In [16]: meansOut[16]: key1 key2a one 0.880536 two 0.478943b one -0.519439 two -0.555730Name: data1, dtype: float64

這裡,我通過兩個鍵對數據進行了分組,得到的Series具有一個層次化索引(由唯一的鍵對組成):

In [17]: means.unstack()Out[17]: key2 one twokey1 a 0.880536 0.478943b -0.519439 -0.555730

在這個例子中,分組鍵均為Series。實際上,分組鍵可以是任何長度適當的數組:

In [18]: states = np.array([Ohio, California, California, Ohio, Ohio])In [19]: years = np.array([2005, 2005, 2006, 2005, 2006])In [20]: df[data1].groupby([states, years]).mean()Out[20]: California 2005 0.478943 2006 -0.519439Ohio 2005 -0.380219 2006 1.965781Name: data1, dtype: float64

通常,分組信息就位於相同的要處理DataFrame中。這裡,你還可以將列名(可以是字元串、數字或其他Python對象)用作分組鍵:

In [21]: df.groupby(key1).mean()Out[21]: data1 data2key1a 0.746672 0.910916b -0.537585 0.525384In [22]: df.groupby([key1, key2]).mean()Out[22]: data1 data2key1 key2 a one 0.880536 1.319920 two 0.478943 0.092908b one -0.519439 0.281746 two -0.555730 0.769023

你可能已經注意到了,第一個例子在執行df.groupby(key1).mean()時,結果中沒有key2列。這是因為df[key2]不是數值數據(俗稱「麻煩列」),所以被從結果中排除了。默認情況下,所有數值列都會被聚合,雖然有時可能會被過濾為一個子集,稍後就會碰到。

無論你準備拿groupby做什麼,都有可能會用到GroupBy的size方法,它可以返回一個含有分組大小的Series:

In [23]: df.groupby([key1, key2]).size()Out[23]: key1 key2a one 2 two 1b one 1 two 1dtype: int64

注意,任何分組關鍵詞中的缺失值,都會被從結果中除去。

對分組進行迭代

GroupBy對象支持迭代,可以產生一組二元元組(由分組名和數據塊組成)。看下面的例子:

In [24]: for name, group in df.groupby(key1): ....: print(name) ....: print(group) ....:a data1 data2 key1 key20 -0.204708 1.393406 a one1 0.478943 0.092908 a two4 1.965781 1.246435 a oneb data1 data2 key1 key22 -0.519439 0.281746 b one3 -0.555730 0.769023 b two

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

In [25]: for (k1, k2), group in df.groupby([key1, key2]): ....: print((k1, k2)) ....: print(group) ....:(a, one) data1 data2 key1 key20 -0.204708 1.393406 a one4 1.965781 1.246435 a one(a, two) data1 data2 key1 key21 0.478943 0.092908 a two(b, one) data1 data2 key1 key22 -0.519439 0.281746 b one(b, two) data1 data2 key1 key23 -0.55573 0.769023 b two

當然,你可以對這些數據片段做任何操作。有一個你可能會覺得有用的運算:將這些數據片段做成一個字典:

In [26]: pieces = dict(list(df.groupby(key1)))In [27]: pieces[b]Out[27]: data1 data2 key1 key22 -0.519439 0.281746 b one3 -0.555730 0.769023 b two

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

In [28]: df.dtypesOut[28]: data1 float64data2 float64key1 objectkey2 objectdtype: objectIn [29]: grouped = df.groupby(df.dtypes, axis=1)

可以如下列印分組:

In [30]: for dtype, group in grouped: ....: print(dtype) ....: print(group) ....:float64 data1 data20 -0.204708 1.3934061 0.478943 0.0929082 -0.519439 0.2817463 -0.555730 0.7690234 1.965781 1.246435object key1 key20 a one1 a two2 b one3 b two4 a one

選取一列或列的子集

對於由DataFrame產生的GroupBy對象,如果用一個(單個字元串)或一組(字元串數組)列名對其進行索引,就能實現選取部分列進行聚合的目的。也就是說:

df.groupby(key1)[data1]df.groupby(key1)[[data2]]

是以下代碼的語法糖:

df[data1].groupby(df[key1])df[[data2]].groupby(df[key1])

尤其對於大數據集,很可能只需要對部分列進行聚合。例如,在前面那個數據集中,如果只需計算data2列的平均值並以DataFrame形式得到結果,可以這樣寫:

In [31]: df.groupby([key1, key2])[[data2]].mean()Out[31]: data2key1 key2 a one 1.319920 two 0.092908b one 0.281746 two 0.769023

這種索引操作所返回的對象是一個已分組的DataFrame(如果傳入的是列表或數組)或已分組的Series(如果傳入的是標量形式的單個列名):

In [32]: s_grouped = df.groupby([key1, key2])[data2]In [33]: s_groupedOut[33]: <pandas.core.groupby.SeriesGroupBy object at 0x7faa30c78da0>In [34]: s_grouped.mean()Out[34]: key1 key2a one 1.319920 two 0.092908b one 0.281746 two 0.769023Name: data2, dtype: float64

通過字典或Series進行分組

除數組以外,分組信息還可以其他形式存在。來看另一個示例DataFrame:

In [35]: people = pd.DataFrame(np.random.randn(5, 5), ....: columns=[a, b, c, d, e], ....: index=[Joe, Steve, Wes, Jim, Travis])In [36]: people.iloc[2:3, [1, 2]] = np.nan # Add a few NA valuesIn [37]: peopleOut[37]: a b c d eJoe 1.007189 -1.296221 0.274992 0.228913 1.352917Steve 0.886429 -2.001637 -0.371843 1.669025 -0.438570Wes -0.539741 NaN NaN -1.021228 -0.577087Jim 0.124121 0.302614 0.523772 0.000940 1.343810Travis -0.713544 -0.831154 -2.370232 -1.860761 -0.860757

現在,假設已知列的分組關係,並希望根據分組計算列的和:

In [38]: mapping = {a: red, b: red, c: blue, ....: d: blue, e: red, f : orange}

現在,你可以將這個字典傳給groupby,來構造數組,但我們可以直接傳遞字典(我包含了鍵「f」來強調,存在未使用的分組鍵是可以的):

In [39]: by_column = people.groupby(mapping, axis=1)In [40]: by_column.sum()Out[40]: blue redJoe 0.503905 1.063885Steve 1.297183 -1.553778Wes -1.021228 -1.116829Jim 0.524712 1.770545Travis -4.230992 -2.405455

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

In [41]: map_series = pd.Series(mapping)In [42]: map_seriesOut[42]: a redb redc blued bluee redf orangedtype: objectIn [43]: people.groupby(map_series, axis=1).count()Out[43]: blue redJoe 2 3Steve 2 3Wes 1 2Jim 2 3Travis 2 3

通過函數進行分組

比起使用字典或Series,使用Python函數是一種更原生的方法定義分組映射。任何被當做分組鍵的函數都會在各個索引值上被調用一次,其返回值就會被用作分組名稱。具體點說,以上一小節的示例DataFrame為例,其索引值為人的名字。你可以計算一個字元串長度的數組,更簡單的方法是傳入len函數:

In [44]: people.groupby(len).sum()Out[44]: a b c d e3 0.591569 -0.993608 0.798764 -0.791374 2.1196395 0.886429 -2.001637 -0.371843 1.669025 -0.4385706 -0.713544 -0.831154 -2.370232 -1.860761 -0.860757

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

In [45]: key_list = [one, one, one, two, two]In [46]: people.groupby([len, key_list]).min()Out[46]: a b c d e3 one -0.539741 -1.296221 0.274992 -1.021228 -0.577087 two 0.124121 0.302614 0.523772 0.000940 1.3438105 one 0.886429 -2.001637 -0.371843 1.669025 -0.4385706 two -0.713544 -0.831154 -2.370232 -1.860761 -0.860757

推薦閱讀:

數據挖掘實戰1:利用LM神經網路演算法進行電力竊漏電用戶自動識別
演算法工程師的能力素質模型
新人入門乾貨,淺談數據分析報告
口腔醫學的大數據時代——柏德口腔
Python數據分析與數據化運營:會員數據化運營1-概述與關鍵指標

TAG:數據分析 | 數據挖掘 | Python |