1. 程式人生 > >資料聚合和分組運算

資料聚合和分組運算

GroupBy技術
主要流程為split-apply-combine(拆分-應用-合併),具體為:
分組運算的第一個階段,pandas物件中的資料會根據你所提供的一個或多個鍵被拆分為多組,拆分操作實在特定的軸上執行的。然後,將一個函式應用到各個分組併產生一個新值。最後,所有這些函式的執行結果會被合併到最終的結果物件中。
- 根據一個或多個鍵拆分pandas物件
分組鍵可以有多種形式,且型別不必相同:

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

來看示例:

df
Out[5]: 
      data1     data2 key1 key2
0  0.242283  1.316144    a  one
1  0.535346 -1.398294    a  two
2  0.516628  2.207073    b  one
3 -1.262803 -0.352256    b  two
4  0.653567 -0.407961    a  one

按key1進行分組,並計算data1列的平均值

grouped = df['data1'].groupby(df['key1'])

grouped
Out[7]: <pandas.core.groupby.SeriesGroupBy object
at 0x000001B734984160>

變數grouped是一個GroupBy物件,實際上他還沒有進行任何運算。

grouped.mean()
Out[8]: 
key1
a    0.477065
b   -0.373088
Name: data1, dtype: float64

如果一次傳入過個數組,就會得到一個層次化索引的Series

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

means
Out[10]: 
key1  key2
a     one     0.447925
      two     0.535346
b one 0.516628 two -1.262803 Name: data1, dtype: float64

同樣可以將列名作為分組鍵:

df.groupby('key1').mean()
Out[11]: 
         data1     data2
key1                    
a     0.477065 -0.163370
b    -0.373088  0.927409

對分組進行迭代

for name, group in df.groupby('key1'):
    print (name)
    print (group)

a
      data1     data2 key1 key2
0  0.242283  1.316144    a  one
1  0.535346 -1.398294    a  two
4  0.653567 -0.407961    a  one
b
      data1     data2 key1 key2
2  0.516628  2.207073    b  one
3 -1.262803 -0.352256    b  two

groupy預設是axis=0上進行分組的,可以設定axis在其他任何軸上進行分組。

選取一個或一組列
對於有DataFrame產生的GroupBy物件,如果用一個或一組列名對其索引,就能實現選取部分列進行聚合的目的。

df.groupby('key1')['data1']
Out[14]: <pandas.core.groupby.SeriesGroupBy object at 0x000001B7343A6550>
#它是df['data1'].groupby(df['key1'])的語法糖
  • 應用組內轉換或其他運算
    資料聚合分組運算的一種,它接受能夠將一維陣列簡化為標量值的函式。
    如下面例子中,計算DataFrame列的樣本分位數
df
Out[5]: 
      data1     data2 key1 key2
0 -0.039369  0.080425    a  one
1 -0.629809  0.198063    a  two
2  1.179436  0.510897    b  one
3  0.478062  0.304403    b  two
4  0.772346 -0.315774    a  one

grouped=df.groupby('key1')

grouped['data1'].quantile(0.9)
Out[7]: 
key1
a    0.610003
b    1.109298
Name: data1, dtype: float64

如果你想使用你自己的聚合函式,只需將其傳入aggregate或agg即可

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


grouped.agg(peak_to_peak)
Out[9]: 
         data1     data2
key1                    
a     1.402154  0.513837
b     0.701374  0.206494

transform方法

people
Out[11]: 
               a         b         c         d         e
Joe    -1.827769 -0.355652  0.552273 -1.898069 -0.543428
Steve  -1.601029  0.562845  1.050669 -0.215205 -1.099391
Wes     1.866622 -1.534953  0.692820 -0.043697  0.240079
Jim    -0.765684  1.837627  0.281838  0.218192  1.704804
Travis -0.716688 -1.390355 -0.725222  1.312762  0.374653

key = ['one','two','one','two','one']

people.groupby(key).mean()
Out[13]: 
            a         b         c         d         e
one -0.225945 -1.093653  0.173290 -0.209668  0.023768
two -1.183356  1.200236  0.666253  0.001493  0.302707

people.groupby(key).transform(np.mean)
Out[14]: 
               a         b         c         d         e
Joe    -0.225945 -1.093653  0.173290 -0.209668  0.023768
Steve  -1.183356  1.200236  0.666253  0.001493  0.302707
Wes    -0.225945 -1.093653  0.173290 -0.209668  0.023768
Jim    -1.183356  1.200236  0.666253  0.001493  0.302707
Travis -0.225945 -1.093653  0.173290 -0.209668  0.023768

可以看出,transform會將一個函式應用到各個分組,然後將結果放置到適當的位置上。如果個分組產生的是一個標量值,則該值就會被廣播出去。
假設你希望從各組中減去平均值,我們此案建立一個距平化函式,病傳給transform。

def demean(arr):
    return arr - arr.mean()


demeaned = people.groupby(key).transform(demean)

demeaned
Out[17]: 
               a         b         c         d         e
Joe    -1.601824  0.738002  0.378982 -1.688400 -0.567196
Steve  -0.417672 -0.637391  0.384416 -0.216699 -1.402097
Wes     2.092567 -0.441300  0.519529  0.165971  0.216311
Jim     0.417672  0.637391 -0.384416  0.216699  1.402097
Travis -0.490743 -0.296701 -0.898512  1.522430  0.350885

apply方法:apply會將待處理的物件分成多個片段,然後對各片段呼叫傳入的函式,最後嘗試將各片段組合到一起。
假設你想要根據分組選出最高的5個tip_pct值,首先,編寫一個選取指定列具有最大值的行的函式

def top(df,n=5,column='tic_pct'):
    return df.sort_index(by=column)[-n:]
top(tips,n=6)
D:\Anaconda3\lib\site-packages\ipykernel_launcher.py:2: FutureWarning: by argument to sort_index is deprecated, please use .sort_values(by=...)

Out[35]: 
     total_bill   tip smoker  day    time  size   tip_pct   tic_pct
109       14.31  4.00    Yes  Sat  Dinner     2  0.279525  0.279525
183       23.17  6.50    Yes  Sun  Dinner     4  0.280535  0.280535
232       11.61  3.39     No  Sat  Dinner     2  0.291990  0.291990
67         3.07  1.00    Yes  Sat  Dinner     1  0.325733  0.325733
178        9.60  4.00    Yes  Sun  Dinner     2  0.416667  0.416667
172        7.25  5.15    Yes  Sun  Dinner     2  0.710345  0.710345

現在,如果對smoker分組應用用該函式

tips.groupby('smoker').apply(top)
D:\Anaconda3\lib\site-packages\ipykernel_launcher.py:2: FutureWarning: by argument to sort_index is deprecated, please use .sort_values(by=...)

Out[36]: 
            total_bill   tip smoker   day    time  size   tip_pct   tic_pct
smoker                                                                     
No     88        24.71  5.85     No  Thur   Lunch     2  0.236746  0.236746
       185       20.69  5.00     No   Sun  Dinner     5  0.241663  0.241663
       51        10.29  2.60     No   Sun  Dinner     2  0.252672  0.252672
       149        7.51  2.00     No  Thur   Lunch     2  0.266312  0.266312
       232       11.61  3.39     No   Sat  Dinner     2  0.291990  0.291990
Yes    109       14.31  4.00    Yes   Sat  Dinner     2  0.279525  0.279525
       183       23.17  6.50    Yes   Sun  Dinner     4  0.280535  0.280535
       67         3.07  1.00    Yes   Sat  Dinner     1  0.325733  0.325733
       178        9.60  4.00    Yes   Sun  Dinner     2  0.416667  0.416667
       172        7.25  5.15    Yes   Sun  Dinner     2  0.710345  0.710345

可以看出,top函式在DataFrame的各個片段上呼叫,然後結果由pandas.concat組裝到一起,並以分組名稱進行了標記。所以,最終結果就有了一個層次化索引,其內層索引值來自原DataFrame。
分位數和桶分析
pandas有一些能根據指定面元或樣本分位數將資料拆分分成的工具。將這些函式跟GroupBy結合起來,就能非常輕鬆地實現對資料集的桶或分位數分析了。

frame = pd.DataFrame({'data1': np.random.randn(1000),
'data2': np.random.randn(1000)})

factor = pd.cut(frame.data1, 4)

factor[:10]
Out[39]: 
0      (1.002, 2.789]
1    (-2.579, -0.785]
2      (1.002, 2.789]
3    (-2.579, -0.785]
4     (-0.785, 1.002]
5    (-2.579, -0.785]
6     (-0.785, 1.002]
7     (-0.785, 1.002]
8      (1.002, 2.789]
9     (-0.785, 1.002]
Name: data1, dtype: category
Categories (4, interval[float64]): [(-2.579, -0.785] < (-0.785, 1.002] < (1.002, 2.789] < (2.789, 4.576]]

由cut返回的Factor物件可直接用於GroupBy。因此,我們可以像下面這樣對data2做一些統計計算:

def get_stats(group):
    return {'min': group.min(), 'max': group.max(),
    'count':group.count(), 'mean': group.mean()}


grouped = frame.data2.groupby(factor)

grouped.apply(get_stats).unstack()
Out[42]: 
                  count       max      mean       min
data1                                                
(-2.579, -0.785]  200.0  2.614581  0.058889 -2.904774
(-0.785, 1.002]   636.0  3.180617 -0.078317 -3.641501
(1.002, 2.789]    162.0  2.740718  0.064263 -2.914572
(2.789, 4.576]      2.0 -0.027067 -0.200847 -0.374627
  • 計算透視表或交叉表
    透視表是各種電子表格程式和其他資料分析軟體中一種常見的資料彙總工具。待續。