python數據分組運算


摘要: pandas 的 GroupBy 功能可以方便地對數據進行分組、應用函數、轉換和聚合等操作。   # 原作者:lionets

GroupBy


分組運算有時也被稱為 “split-apply-combine” 操作。其中的 “split” 便是借由 obj.groupby() 方法來實現的。

.groupby(by=None, axis=0, level=None, as_index=True, sort=True, group_keys=True, squeeze=False) 方法作用於一條軸向上,並接受一個分組鍵(by)參數來給調用者分組。分組鍵可以是Series 或列表,要求其長度與待分組的軸一致;也可以是映射函數、字典甚至數組的某條列名(字符串),但這些參數類型都只是快捷方式,其最終仍要用於生成一組用於拆分對象的值。

lang:python >>> 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 data1 data2 key1 key2 0 0.922269 0.110285 a one 1 -0.181773 1.022435 a two 2 0.635899 0.279316 b one 3 0.527926 0.482807 b two 4 -1.586040 -1.312042 a one [5 rows x 4 columns] >>> grouped = df.groupby(df['key1']) >>> grouped <pandas.core.groupby.DataFrameGroupBy object at 0x0000000005BC25F8> 

這里使用 df['key1'] 做了分組鍵,即按 a 和 b 進行分組。但實際分組鍵並不需要與數組對象之間存在聯系,只要長度相同即可,使用數組的列只是圖方便。上例中如果使用 [1,1,2,2,3] 這樣的列表做分組鍵的話,結果與df['key1'] 是相同的。

groupby 方法返回的 DataFrameGroupBy 對象實際並不包含數據內容,它記錄的是有關分組鍵——df['key1'] 的中間數據。當你對分組數據應用函數或其他聚合運算時,pandas 再依據 groupby 對象內記錄的信息對 df 進行快速分塊運算,並返回結果。

上面這段話其實想說是: groupby 方法的調用本身並不涉及運算,因此速度很快。而在操作這個 grouped 對象的時候,還是將其看成一個保存了實際數據的對象比較方便。比如我們可以直接對其應用很多方法,或索引切片:

lang:python >>> grouped.mean() data1 data2 key1 a -0.281848 -0.059774 b 0.581912 0.381061 [2 rows x 2 columns] 

上例中沒有顯示 key2 列,是因為其值不是數字類型,被 mean() 方法自動忽視了。當想要只看某一(些)列的時候,可以通過索引來實現,在 groupby 方法調用前后均可(這是一種語法糖):

lang:python >>> df['data1'].groupby(df['key1']).mean() key1 a -0.281848 b 0.581912 dtype: float64 >>> df.groupby(df['key2'])['data2'].mean() key2 one -0.307481 two 0.752621 Name: data2, dtype: float64 

如果分組鍵使用的是多個數組,就會得到一個層次化索引的結果:

lang:python >>> df.groupby([df['key1'],df['key2']]).mean() data1 data2 key1 key2 a one -0.331885 -0.600879 two -0.181773 1.022435 b one 0.635899 0.279316 two 0.527926 0.482807 [4 rows x 2 columns] 

最后,可以使用 GroupBy 對象(不論是 DataFrameGroupBy 還是 SeriesGroupBy)的 .size() 方法查看分組大小:

lang:python >>> grouped.size() key1 a 3 b 2 dtype: int64 

<br />

對分組進行迭代

GroupBy 對象是可以通過 for 循環迭代的,可以產生一組二元組,分別為分組名和組內數據。下面是一個多重分組鍵的情況:

lang:python
>>> for i,j in df.groupby([df['key1'],df['key2']]): print(i) print('-----------') print(j) ('a', 'one') ----------- data1 data2 key1 key2 0 0.922269 0.110285 a one 4 -1.586040 -1.312042 a one [2 rows x 4 columns] ('a', 'two') ----------- data1 data2 key1 key2 1 -0.181773 1.022435 a two [1 rows x 4 columns] ('b', 'one') ----------- data1 data2 key1 key2 2 0.635899 0.279316 b one [1 rows x 4 columns] ('b', 'two') ----------- data1 data2 key1 key2 3 0.527926 0.482807 b two [1 rows x 4 columns] 

<br />

使用字符串列名作分組鍵

前面曾提到過可以使用字符串形式的列名作為分組鍵,但上面例子中都沒有用。是因為這種方法雖然方便,卻存在隱患——使用這種方法時,調用者必須是 DataFrame 對象自身而不可以是 DataFrame 的索引形式。即df.groupby('key1')['data1'] 是 ok 的,但 df['data1'].groupby('key1') 會報錯。使用時當注意區分。 <br />

使用 字典或Series作分組鍵

這兩種參數需要提供一種從行(列)名到組名的映射關系。(還記得 Series 就是一種定長有序字典 這種說法嘛)

lang:python
>>> df.groupby({0:'a',1:'a',2:'b',3:'b',4:'a'}).mean() data1 data2 a -0.281848 -0.059774 b 0.581912 0.381061 [2 rows x 2 columns] 

<br />

通過函數進行分組

函數的作用有些類似於字典,或者說這些奇怪的分組鍵都類似於字典——利用某種映射關系將待分組的軸轉化為一個等長的由分組名組成的序列。

如果說行列名是作為索引傳遞給字典以獲取組名的話,那么在函數分組鍵中,行列名就會作為參數傳遞給函數。這便是你需要提供的函數類型:

lang:python
>>> df.groupby(lambda x:'even' if x%2==0 else 'odd').mean() data1 data2 even -0.009290 -0.307481 odd 0.173076 0.752621 [2 rows x 2 columns] 

<br />

根據索引級別分組

當根據高級別索引來分組的時候,參數就不再是 by=None 了,而要換成 level=None,值可以是索引級別的編號或名稱:

lang:python >>> index = pd.MultiIndex.from_arrays([['even','odd','even','odd','even'], [0,1,2,3,4]],names=['a','b']) >>> df.index = index >>> df.groupby(level='a').mean() data1 data2 a even -0.009290 -0.307481 odd 0.173076 0.752621 [2 rows x 2 columns] >>> df.groupby(level=0).mean() data1 data2 a even -0.009290 -0.307481 odd 0.173076 0.752621 [2 rows x 2 columns] 

<br />

數據聚合(Aggregation)


數據聚合,指的是任何能夠從數組產生標量值的數據轉換過程。你也可以簡單地將其理解為統計計算,如 mean(), sum(), max() 等。

數據聚合本身與分組並沒有直接關系,在任何一列(行)或全部列(行)上都可以進行。不過當這種運算被應用在分組數據上的時候,結果可能會變得更有意義。

對於 GroupBy 對象可以應用的聚合運算包括:

  • 已經內置的方法,如 sum(), mean() 等
  • Series 的方法,如 quantile() 等
  • 自定義的聚合函數,通過傳入 GroupBy.aggregate() 或 GroupBy.agg() 來實現

其中自定義函數的參數應當為一個數組類型,即 GroupBy 對象迭代出的元組的第二個元素。如

lang:python
>>> df.groupby('key1')['data1','data2'].agg(lambda arr:arr.max()-arr.min()) data1 data2 key1 a 2.508309 2.334477 b 0.107973 0.203492 [2 rows x 2 columns] 

但其實自定義函數的效率很慢,遠不如 GroupBy 對象已經優化過的內建方法,這些方法包括: <br />

<table style="font-size:14px"> <tr> <td>############</td> <td>**</td> </tr> <tr> <td>count</td> <td>分組中非 NA 值得數量</td> </tr> <tr> <td>sum</td> <td>非 NA 值的和</td> </tr> <tr> <td>mean</td> <td>非 NA 值的平均值</td> </tr> <tr> <td>median</td> <td>非 NA 值的算數中位數</td> </tr> <tr> <td>std, var</td> <td>無偏(分母為 n-1)標准差和方差</td> </tr> <tr> <td>min, max</td> <td>非 NA 值的最小值和最大值</td> </tr> <tr> <td>prod</td> <td>非 NA 值的積</td> </tr> <tr> <td>first, last</td> <td>第一個和最后一個非 NA 值</td> </tr> </table> <br />

面向列的多函數應用

前面的例子中,我們每次都只調用一個聚合方法。對於多函數應用,我們可以分兩種情況討論:

第一種是相同列應用多個函數從而得到多個結果的情況,這時只需給 agg() 傳入一個函數列表即可:

lang:python
>>> df.groupby('key1')['data1','data2'].agg(['min','max']) data1 data2 min max min max key1 a -1.586040 0.922269 -1.312042 1.022435 b 0.527926 0.635899 0.279316 0.482807 [2 rows x 4 columns] 

這里一個技巧是,對上節中那些統計方法,可以將方法名以字符串的形式傳入 agg()。另外,如果你不喜歡列的命名方式,或你使用的干脆是 lambda 匿名函數,你可以把函數參數替換成(name,function)的元組格式,這樣結果集中的列就不再以函數名命名而是以你給出的 name 為准。

第二種是對不同列應用不同函數的情況,這時需要傳給 agg() 一個從列名映射到函數名的字典:

lang:python
>>> df.groupby('key1').agg({'data1':'min','data2':'max'}) data1 data2 key1 a -1.586040 1.022435 b 0.527926 0.482807 [2 rows x 2 columns] 

這里要注意的是,就不要再在 GroupBy 對象上進行索引操作啦,你的字典參數已經做了響應的列選取工作。 <br />

分組級運算和轉換


聚合只是分組運算的一種,更多種類的分組運算可以通過 .transform() 和 apply() 方法實現。 <br />

transform

前面進行聚合運算的時候,得到的結果是一個以分組名為 index 的結果對象。如果我們想使用原數組的 index 的話,就需要進行 merge 轉換。transform(func, *args, **kwargs) 方法簡化了這個過程,它會把 func 參數應用到所有分組,然后把結果放置到原數組的 index 上(如果結果是一個標量,就進行廣播):

lang:python
>>> df
           data1 data2 key1 key2 a b even 0 0.922269 0.110285 a one odd 1 -0.181773 1.022435 a two even 2 0.635899 0.279316 b one odd 3 0.527926 0.482807 b two even 4 -1.586040 -1.312042 a one [5 rows x 4 columns] >>> df.groupby('key1').transform('mean') data1 data2 a b even 0 -0.281848 -0.059774 odd 1 -0.281848 -0.059774 even 2 0.581912 0.381061 odd 3 0.581912 0.381061 even 4 -0.281848 -0.059774 [5 rows x 2 columns] 

<br />

apply

apply(func, *args, **kwargs) 會將待處理的對象拆分成多個片段,然后對各片段調用傳入的函數,最后嘗試用pd.concat() 把結果組合起來。func 的返回值可以是 pandas 對象或標量,並且數組對象的大小不限。

lang:python
>>> df data1 data2 key1 key2 0 0.721150 -0.359337 a one 1 -1.727197 1.539508 a two 2 -0.339751 0.171379 b one 3 -0.291888 -1.000769 b two 4 -0.127029 0.506162 a one [5 rows x 4 columns] >>> def foo(df,n=12): return pd.DataFrame(np.arange(n).reshape(3,4)) >>> df.groupby('key1').apply(foo) 0 1 2 3 key1 a 0 0 1 2 3 1 4 5 6 7 2 8 9 10 11 b 0 0 1 2 3 1 4 5 6 7 2 8 9 10 11 [6 rows x 4 columns] 

這是一個毫無意義的例子,因為傳給 apply 的 func 參數沒有對 df 做任何處理,直接返回了一個(3,4)的數組。而實際上,這樣一個毫無意義的例子恰好說明了 apply 方法的通用性——你可以返回任意的結果。多數時候,限制 apply 發揮的其實是用戶的腦洞。 <br />

透視表和交叉表

DataFrame 對象有一個 .pivot_table(data, values=None, rows=None, cols=None, aggfunc='mean', fill_value=None, margins=False, dropna=True) 方法可以用來制作透視表,同時 pd.pivot_table() 也是一個頂層函數。

  • data 參數相當於 self,這里將其命名為 data 也許是為了與頂級函數版本的 pivot_table 保持一致。
  • values 參數可以是一個以列名為元素的列表,用於指定想要聚合的數據,不給出的話默認使用全部數據。
  • rows 參數用於指定行分組鍵
  • cols 參數用於指定列分組鍵
  • aggfunc 參數用於指定聚合函數,默認為均值(mean)
  • margins 參數是小計(Total)功能的開關,設為 True 后結果集中會出現名為 “ALL” 的行和列

例:

lang:python >>> df A B C D 0 foo one small 1 1 foo one large 2 2 foo one large 2 3 foo two small 3 4 foo two small 3 5 bar one large 4 6 bar one small 5 7 bar two small 6 8 bar two large 7 >>> table = pivot_table(df, values='D', rows=['A', 'B'], ... cols=['C'], aggfunc=np.sum) >>> table small large foo one 1 4 two 6 NaN bar one 5 4 two 6 7 

<br /> 交叉表(cross-tabulation,crosstab)是一種用於計算分組頻數的特殊透視表。

crosstab(rows, cols, values=None, rownames=None, colnames=None, aggfunc=None, margins=False, dropna=True)

lang:python >>> a array([foo, foo, foo, foo, bar, bar, bar, bar, foo, foo, foo], dtype=object) >>> b array([one, one, one, two, one, one, one, two, two, two, one], dtype=object) >>> c array([dull, dull, shiny, dull, dull, shiny, shiny, dull, shiny, shiny, shiny], dtype=object) >>> crosstab(a, [b, c], rownames=['a'], colnames=['b', 'c']) b one two c dull shiny dull shiny a bar 1 2 1 0 foo 2 2 1 2


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM