1. 程式人生 > >pandas 資料分組運算

pandas 資料分組運算

轉載出處:https://my.oschina.net/lionets/blog/280332

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]
>>> deffoo(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