利用Python進行數據分析-Pandas(第六部分-數據聚合與分組運算)


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

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

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

1、GroupBy機制

  Hadlley Wickham(許多熱門R語言包的作者)創造了一個用於表示分組運算的術語"split-apply-combine"(拆分-應用-合並)。(在這里說一句題外話;對於Hadlley Wickham我在知乎上有做過詳細的關於他對R語言的可視化包做了詳盡的介紹,感興趣的可以在這里查看:https://zhuanlan.zhihu.com/p/31929985)。第一個階段,pandas對象(無論是Series、DataFrame還是其他的)中的數據會根據你所提供的一個或多個鍵被拆分(split)為多組。拆分操作是在對象的特定軸上執行的。例如,DataFrame可以在其行(axis=0)或列(axis=1)上進行分組。然后,將一個函數應用(apply)到各個分組並產生一個新值。最后,所有這些函數的執行結果會被合並(combine)到最終結果對象中。結果對象的形式一般取決於數據上所執行的操作。如下圖大致說明了一個簡單的分組聚合過程。

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

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

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

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)})
print(df)
  key1 key2     data1     data2
0    a  one -1.749765  0.514219
1    a  two  0.342680  0.221180
2    b  one  1.153036 -1.070043
3    b  two -0.252436 -0.189496
4    a  one  0.981321  0.255001

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

grouped = df['data1'].groupby(df['key1'])
print(grouped)
<pandas.core.groupby.generic.SeriesGroupBy object at 0x000001D258A794A8>

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

print(grouped.mean())
key1
a   -0.141921
b    0.450300
Name: data1, dtype: float64

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

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

means = df['data1'].groupby([df['key1'], df['key2']]).mean()
print(means)
key1  key2
a     one    -0.384222
      two     0.342680
b     one     1.153036
      two    -0.252436
Name: data1, dtype: float64

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

print(means.unstack())
key2       one       two
key1                    
a    -0.384222  0.342680
b     1.153036 -0.252436

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

states = np.array(['Ohio', 'California', 'California', 'Ohio', 'Ohio'])
years = np.array([2005, 2005, 2006, 2005, 2006])
print(df['data1'].groupby([states, years]).mean())
California  2005    0.342680
            2006    1.153036
Ohio        2005   -1.001101
            2006    0.981321
Name: data1, dtype: float64

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

print(df.groupby('key1').mean())
         data1     data2
key1                    
a    -0.141921  0.330133
b     0.450300 -0.629770
print(df.groupby(['key1', 'key2']).mean())
              data1     data2
key1 key2                    
a    one  -0.384222  0.384610
     two   0.342680  0.221180
b    one   1.153036 -1.070043
     two  -0.252436 -0.189496

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

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

print(df.groupby(['key1', 'key2']).size())
key1  key2
a     one     2
      two     1
b     one     1
      two     1
dtype: int64

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

2、對分組進行迭代

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

for name, group in df.groupby('key1'):
    print(name)
    print(group)
a
  key1 key2     data1     data2
0    a  one -1.749765  0.514219
1    a  two  0.342680  0.221180
4    a  one  0.981321  0.255001
b
  key1 key2     data1     data2
2    b  one  1.153036 -1.070043
3    b  two -0.252436 -0.189496

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

for (k1, k2), group in df.groupby(['key1', 'key2']):
    print((k1, k2))
    print(group)
('a', 'one')
  key1 key2     data1     data2
0    a  one -1.749765  0.514219
4    a  one  0.981321  0.255001
('a', 'two')
  key1 key2    data1    data2
1    a  two  0.34268  0.22118
('b', 'one')
  key1 key2     data1     data2
2    b  one  1.153036 -1.070043
('b', 'two')
  key1 key2     data1     data2
3    b  two -0.252436 -0.189496

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

pieces =dict(list(df.groupby('key1')))
print(pieces['b'])
  key1 key2     data1     data2
2    b  one  1.153036 -1.070043
3    b  two -0.252436 -0.189496

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

print(df.dtypes)
grouped = df.groupby(df.dtypes, axis=1)
key1      object
key2      object
data1    float64
data2    float64
dtype: object

可以如下打印分組:

for dtype, group in grouped:
    print(dtype)
    print(group)
float64
      data1     data2
0 -1.749765  0.514219
1  0.342680  0.221180
2  1.153036 -1.070043
3 -0.252436 -0.189496
4  0.981321  0.255001
object
  key1 key2
0    a  one
1    a  two
2    b  one
3    b  two
4    a  one

3、選取一列或列的子集

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

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

是以下代碼的語法糖:

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

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

print(df.groupby(['key1', 'key2'])[['data2']].mean())
              data2
key1 key2          
a    one   0.384610
     two   0.221180
b    one  -1.070043
     two  -0.189496

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

s_grouped = df.groupby(['key1', 'key2'])['data2']
print(s_grouped)
<pandas.core.groupby.generic.SeriesGroupBy object at 0x000001B28E96FBE0>
print(s_grouped.mean())
key1  key2
a     one     0.384610
      two     0.221180
b     one    -1.070043
      two    -0.189496
Name: data2, dtype: float64

4、通過字典或Series進行分組

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

np.random.seed(10)
people = pd.DataFrame(np.random.rand(5, 5), columns=['a', 'b', 'c', 'd', 'e'], index=['Joe', 'Steve', 'Wes', 'Jim', 'Travis'] )
people.iloc[2:3, [1, 2]] = np.nan
print(people)
               a         b         c         d         e
Joe     0.771321  0.020752  0.633648  0.748804  0.498507
Steve   0.224797  0.198063  0.760531  0.169111  0.088340
Wes     0.685360       NaN       NaN  0.512192  0.812621
Jim     0.612526  0.721755  0.291876  0.917774  0.714576
Travis  0.542544  0.142170  0.373341  0.674134  0.441833

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

mapping = {'a': 'red', 'b': 'red', 'c': 'blue', 'd': 'blue', 'e': 'red', 'f': 'orange'}

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

by_column = people.groupby(mapping, axis=1)
print(by_column.sum())
            blue       red
Joe     1.382452  1.290580
Steve   0.929642  0.511199
Wes     0.512192  1.497981
Jim     1.209650  2.048857
Travis  1.047474  1.126548

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

map_series = pd.Series(mapping)
print(map_series)
a       red
b       red
c      blue
d      blue
e       red
f    orange
dtype: object
print(people.groupby(map_series, axis=1).count())
        blue  red
Joe        2    3
Steve      2    3
Wes        1    2
Jim        2    3
Travis     2    3

5、通過函數進行分組

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

print(people.groupby(len).sum())
          a         b         c         d         e
3  2.069207  0.742507  0.925524  2.178770  2.025704
5  0.224797  0.198063  0.760531  0.169111  0.088340
6  0.542544  0.142170  0.373341  0.674134  0.441833

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

key_list = ['one', 'one', 'one', 'two', 'two']
print(people.groupby([len, key_list]).min())
              a         b         c         d         e
3 one  0.685360  0.020752  0.633648  0.512192  0.498507
  two  0.612526  0.721755  0.291876  0.917774  0.714576
5 one  0.224797  0.198063  0.760531  0.169111  0.088340
6 two  0.542544  0.142170  0.373341  0.674134  0.441833

6、根據索引級別分組

層次化索引數據集最方便的地方在於它能夠根據軸索引的一個級別進行聚合:

columns = pd.MultiIndex.from_arrays([['US', 'US', 'US', 'JP', 'JP'], [1, 3, 5, 1, 3]], names=['cty', 'tenor'])
np.random.seed(10)
hier_df = pd.DataFrame(np.random.rand(4, 5), columns=columns)
print(hier_df)
cty          US                            JP          
tenor         1         3         5         1         3
0      0.771321  0.020752  0.633648  0.748804  0.498507
1      0.224797  0.198063  0.760531  0.169111  0.088340
2      0.685360  0.953393  0.003948  0.512192  0.812621
3      0.612526  0.721755  0.291876  0.917774  0.714576

要根據級別分組,使用level關鍵字傳遞級別序號或名字:

print(hier_df.groupby(level='cty', axis=1).count())
cty  JP  US
0     2   3
1     2   3
2     2   3
3     2   3

7、數據聚合

  聚合指的是任何能夠從數組產生標量值的數據轉換過程。之前的例子已經用過一些,比如mean、count、min以及sum等。你可能想知道在GroupBy對象上調用mean()時究竟發生了什么。許多常見的聚合函數都有進行優化。然而,除了這些方法,你還可以使用其他的:

函數名 說明
count 分組中非NA值的數量
sum 非NA值的和
mean 非NA值的平均值
median 非NA值的算術中位數
std、var 無偏(分母為n-1)標准差和方法
min、max 非NA值的最小值和最大值
prod 非NA值的積
first、last 第一個和最后一個非NA值

你可以使用自己發明的聚合函數,還可以調用分組對象上已經定義好的任何方法。例如,quantile可以計算Series或DataFrame列的樣本分位數。

 雖然quantile並沒有明確地實現於groupBy,但它是一個Series方法,所以這里是能用的。實際上,GroupBy會高效地對Series進行切片,然后對各片調用piece.quantile(0.9),最后將這些結果組裝成最終結果:

grouped = df.groupby('key1')
print(grouped['data1'].quantile(0.9))
key1
a    0.853593
b    1.012489
Name: data1, dtype: float64

如果要使用自己的聚合函數,只需將其傳入aggregate或agg方法即可:

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


print(grouped.agg(peak_to_peak))
         data1     data2
key1                    
a     2.731086  0.293039
b     1.405472  0.880547

你可能注意到,有些方法(如describe)也是可以用在這里的,即使嚴格來講,它們並非聚合運算:

print(grouped.describe())
     data1                      ...     data2                    
     count      mean       std  ...       50%       75%       max
key1                            ...                              
a      3.0 -0.141921  1.428579  ...  0.255001  0.384610  0.514219
b      2.0  0.450300  0.993819  ... -0.629770 -0.409633 -0.189496

[2 rows x 16 columns]

8、面向列的多函數應用

  回到前面小費的例子。使用read_csv導入數據之后,我們添加了一個小費百分比的列tip_pct:

tips = pd.read_csv('tips.csv', encoding='utf-8')
tips['tip_pct'] = tips['tip']/tips['total_bill']
print(tips[:6])
   total_bill   tip     sex smoker  day    time  size   tip_pct
0       16.99  1.01  Female     No  Sun  Dinner     2  0.059447
1       10.34  1.66    Male     No  Sun  Dinner     3  0.160542
2       21.01  3.50    Male     No  Sun  Dinner     3  0.166587
3       23.68  3.31    Male     No  Sun  Dinner     2  0.139780
4       24.59  3.61  Female     No  Sun  Dinner     4  0.146808
5       25.29  4.71    Male     No  Sun  Dinner     4  0.186240

你已經看到,對Series或DataFrame列的聚合運算其實就是使用aggregate(使用自定義函數)或調用諸如mean、std之類的方法。然而,你可能希望對不同的列使用不同的聚合函數,或一次應用多個函數。其實這也好辦,將通過一些示例來進行講解。首先,根據天和smoker對tips進行分組:

grouped = tips.groupby(['day', 'smoker'])
grouped_pct = grouped['tip_pct']
print(grouped_pct.agg('mean'))
day   smoker
Fri   No        0.151650
      Yes       0.174783
Sat   No        0.158048
      Yes       0.147906
Sun   No        0.160113
      Yes       0.187250
Thur  No        0.160298
      Yes       0.163863
Name: tip_pct, dtype: float64

如果傳入一組函數或函數名,得到的DataFrame的列就會以相應的函數命名:

print(grouped_pct.agg(['mean', 'std', peak_to_peak]))
                 mean       std  peak_to_peak
day  smoker                                  
Fri  No      0.151650  0.028123      0.067349
     Yes     0.174783  0.051293      0.159925
Sat  No      0.158048  0.039767      0.235193
     Yes     0.147906  0.061375      0.290095
Sun  No      0.160113  0.042347      0.193226
     Yes     0.187250  0.154134      0.644685
Thur No      0.160298  0.038774      0.193350
     Yes     0.163863  0.039389      0.151240

這里,我們傳遞了一組聚合函數進行聚合,獨立對數據分組進行評估。

你並非一定要接受GroupBy自動給出的那些列名,特別是lambda函數,它們的名稱是'<lambda>',這樣的辨識度就很低了(通過函數的name屬性看看就知道了)。因此,如果傳入的是一個由(name, function)元組組成的列表,則各元組的第一個元素就會被用作DataFrame的列名(可以將這種二元元組列表看做一個有序映射):

print(grouped_pct.agg([('foo', 'mean'), ('bar', np.std)]))
                  foo       bar
day  smoker                    
Fri  No      0.151650  0.028123
     Yes     0.174783  0.051293
Sat  No      0.158048  0.039767
     Yes     0.147906  0.061375
Sun  No      0.160113  0.042347
     Yes     0.187250  0.154134
Thur No      0.160298  0.038774
     Yes     0.163863  0.039389

對於DataFrame,你還有更多選擇,你可以定義一組應用於全部列的一組函數,或不同的列應用不同的函數。假設我們想要對tip_pct和total_bill列計算三個統計信息:

functions = ['count', 'mean', 'max']
result = grouped['tip_pct', 'total_bill'].agg(functions)
print(result)
            tip_pct                     total_bill                  
              count      mean       max      count       mean    max
day  smoker                                                         
Fri  No           4  0.151650  0.187735          4  18.420000  22.75
     Yes         15  0.174783  0.263480         15  16.813333  40.17
Sat  No          45  0.158048  0.291990         45  19.661778  48.33
     Yes         42  0.147906  0.325733         42  21.276667  50.81
Sun  No          57  0.160113  0.252672         57  20.506667  48.17
     Yes         19  0.187250  0.710345         19  24.120000  45.35
Thur No          45  0.160298  0.266312         45  17.113111  41.19
     Yes         17  0.163863  0.241255         17  19.190588  43.11

如你所見,結果DataFrame擁有層次化的列,這相當於分別對各列進行聚合,然后用concat將結果組裝到一起,使用列名用做keys參數:

print(result['tip_pct'])
             count      mean       max
day  smoker                           
Fri  No          4  0.151650  0.187735
     Yes        15  0.174783  0.263480
Sat  No         45  0.158048  0.291990
     Yes        42  0.147906  0.325733
Sun  No         57  0.160113  0.252672
     Yes        19  0.187250  0.710345
Thur No         45  0.160298  0.266312
     Yes        17  0.163863  0.241255

跟前面一樣,這里也可以傳入帶有自定義名稱的一組元組:

print(grouped['tip_pct', 'total_bill'].agg(ftuples))
            Durchschnitt Abweichung Durchschnitt  Abweichung
day  smoker                                                 
Fri  No         0.151650   0.000791    18.420000   25.596333
     Yes        0.174783   0.002631    16.813333   82.562438
Sat  No         0.158048   0.001581    19.661778   79.908965
     Yes        0.147906   0.003767    21.276667  101.387535
Sun  No         0.160113   0.001793    20.506667   66.099980
     Yes        0.187250   0.023757    24.120000  109.046044
Thur No         0.160298   0.001503    17.113111   59.625081
     Yes        0.163863   0.001551    19.190588   69.808518

現在,假設你想要對一個列或不同的列應用不同的函數。具體的辦法是向agg傳入一個從列名映射到函數的字典:

print(grouped.agg({'tip': np.max, 'size': 'sum'}))
               tip  size
day  smoker             
Fri  No       3.50     9
     Yes      4.73    31
Sat  No       9.00   115
     Yes     10.00   104
Sun  No       6.00   167
     Yes      6.50    49
Thur No       6.70   112
     Yes      5.00    40
print(grouped.agg({'tip_pct': ['min', 'max', 'mean', 'std'], 'size': 'sum'}))
              tip_pct                               size
                  min       max      mean       std  sum
day  smoker                                             
Fri  No      0.120385  0.187735  0.151650  0.028123    9
     Yes     0.103555  0.263480  0.174783  0.051293   31
Sat  No      0.056797  0.291990  0.158048  0.039767  115
     Yes     0.035638  0.325733  0.147906  0.061375  104
Sun  No      0.059447  0.252672  0.160113  0.042347  167
     Yes     0.065660  0.710345  0.187250  0.154134   49
Thur No      0.072961  0.266312  0.160298  0.038774  112
     Yes     0.090014  0.241255  0.163863  0.039389   40

只有將多個函數應用到至少一列時,DataFrame才會擁有層次化的列。

9、以“沒有行索引”的形式返回聚合數據

  到目前為止,所有示例中的聚合數據都有唯一的分組鍵組成的索引(可能還是層次化的)。由於並不總是需要如此,所以你可以向groupby傳入as_index=False以禁用該功能:

print(tips.groupby(['day', 'smoker'], as_index=False).mean())
    day smoker  total_bill       tip      size   tip_pct
0   Fri     No   18.420000  2.812500  2.250000  0.151650
1   Fri    Yes   16.813333  2.714000  2.066667  0.174783
2   Sat     No   19.661778  3.102889  2.555556  0.158048
3   Sat    Yes   21.276667  2.875476  2.476190  0.147906
4   Sun     No   20.506667  3.167895  2.929825  0.160113
5   Sun    Yes   24.120000  3.516842  2.578947  0.187250
6  Thur     No   17.113111  2.673778  2.488889  0.160298
7  Thur    Yes   19.190588  3.030000  2.352941  0.163863

當然,對結果調用reset_index也能得到這種形式的結果。使用as_index=False方法可以避免一些不必要的計算。

10、apply:一般性的“拆分-應用-合並”

  最通用的GroupBy方法是apply,本節剩余部分將重點講解它。如圖所示,apply會將待處理的對象拆分成多個片段,然后對各片段調用傳入的函數,最后嘗試將各片段組合到一起:

 

   回到之前那個小費數據集,假設你想要根據分組選出最高的5個tip_pct值。首先,編寫一個選取指定列具有最大值的行的函數:

def top(df, n=5, column='tip_pct'):
    return df.sort_values(by=column)[-n:]
print(top(tips, n=6))
     total_bill   tip     sex smoker  day    time  size   tip_pct
109       14.31  4.00  Female    Yes  Sat  Dinner     2  0.279525
183       23.17  6.50    Male    Yes  Sun  Dinner     4  0.280535
232       11.61  3.39    Male     No  Sat  Dinner     2  0.291990
67         3.07  1.00  Female    Yes  Sat  Dinner     1  0.325733
178        9.60  4.00  Female    Yes  Sun  Dinner     2  0.416667
172        7.25  5.15    Male    Yes  Sun  Dinner     2  0.710345

現在,如果對smoker分組並用該函數調用apply,就會得到:

print(tips.groupby('smoker').apply(top))
            total_bill   tip     sex smoker   day    time  size   tip_pct
smoker                                                                   
No     88        24.71  5.85    Male     No  Thur   Lunch     2  0.236746
       185       20.69  5.00    Male     No   Sun  Dinner     5  0.241663
       51        10.29  2.60  Female     No   Sun  Dinner     2  0.252672
       149        7.51  2.00    Male     No  Thur   Lunch     2  0.266312
       232       11.61  3.39    Male     No   Sat  Dinner     2  0.291990
Yes    109       14.31  4.00  Female    Yes   Sat  Dinner     2  0.279525
       183       23.17  6.50    Male    Yes   Sun  Dinner     4  0.280535
       67         3.07  1.00  Female    Yes   Sat  Dinner     1  0.325733
       178        9.60  4.00  Female    Yes   Sun  Dinner     2  0.416667
       172        7.25  5.15    Male    Yes   Sun  Dinner     2  0.710345

這里發生了什么?top函數在DataFrame的各個片段上調用,然后結果由pandas.concat組裝到一起,並以分組名稱進行了標記。於是,最終結果就有了一個層次化索引,其內層索引值來自原DataFrame。

如果傳給apply的函數能夠接受其他參數或關鍵字,則可以將這些內容放在函數名后面一並傳入:

print(tips.groupby(['smoker', 'day']).apply(top, n=1, column='total_bill'))
                 total_bill    tip     sex smoker   day    time  size   tip_pct
smoker day                                                                     
No     Fri  94        22.75   3.25  Female     No   Fri  Dinner     2  0.142857
       Sat  212       48.33   9.00    Male     No   Sat  Dinner     4  0.186220
       Sun  156       48.17   5.00    Male     No   Sun  Dinner     6  0.103799
       Thur 142       41.19   5.00    Male     No  Thur   Lunch     5  0.121389
Yes    Fri  95        40.17   4.73    Male    Yes   Fri  Dinner     4  0.117750
       Sat  170       50.81  10.00    Male    Yes   Sat  Dinner     3  0.196812
       Sun  182       45.35   3.50    Male    Yes   Sun  Dinner     3  0.077178
       Thur 197       43.11   5.00  Female    Yes  Thur   Lunch     4  0.115982

除了這些基本用法之外,能否充分發揮apply的威力很大程度取決於你的創造力。傳入的那個函數能做什么全由你說了算,它只需返回一個pandas對象或標量值即可。本章后續部分的示例主要用於講解如何利用groupby解決各種各樣的問題。

11、禁止分組鍵

   從上面的例子中可以看出,分組鍵會跟原始對象的索引共同構成結果對象中的層次化索引。將group_keys=False傳入groupby即可禁止該效果。

print(tips.groupby('smoker', group_keys= False).apply(top))
     total_bill   tip     sex smoker   day    time  size   tip_pct
88        24.71  5.85    Male     No  Thur   Lunch     2  0.236746
185       20.69  5.00    Male     No   Sun  Dinner     5  0.241663
51        10.29  2.60  Female     No   Sun  Dinner     2  0.252672
149        7.51  2.00    Male     No  Thur   Lunch     2  0.266312
232       11.61  3.39    Male     No   Sat  Dinner     2  0.291990
109       14.31  4.00  Female    Yes   Sat  Dinner     2  0.279525
183       23.17  6.50    Male    Yes   Sun  Dinner     4  0.280535
67         3.07  1.00  Female    Yes   Sat  Dinner     1  0.325733
178        9.60  4.00  Female    Yes   Sun  Dinner     2  0.416667
172        7.25  5.15    Male    Yes   Sun  Dinner     2  0.710345

12、分位數和桶分析

  曾在第8章中講過,pandas有一些能夠根據指定面元或樣本分位數將數據拆分成多塊的工具(比如cut和qcut)。將這些函數跟groupby結合起來,就能非常輕松的實現對數據集的桶(bucket)或分位數(quantile)分析了。以下面這個簡單的隨機數據集為例,我們利用cut將其裝入長度相等的桶中:

 

持續更新中......


免責聲明!

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



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