Pandas数据分析-分组和聚合Aggregation&Groupby, since 2022-05-12

  • 注:本文演示基于Python 3.9.7和Pandas 1.3.4

(2022.05.12 Thur)
Python的Pandas工具包提供了类似于SQL中group的操作指令groupby,但功能更为强大。本文介绍基于groupby的数据分组和聚合操作。此外,还介绍了pandas.transform的使用。

GroupBy机制

GroupBy机制可以简单的描述为split-appy-combine过程。pandas对象中的数据,e.g., Series/DataFrame or others,根据用户提供的一个或多个key,被切分(split)成多个group。切分操作按照特定轴进行,即行(axis=0)或列(axis=1)。之后应用(apply)某个函数在切分的group上,生成新值诸如此类。最后将处理后的数据组合(combine)成新的数据对象。

用于分组的键(key)可以有多种形式:

  • 与分组的轴有相同数据长度的list/array
  • DataFrame中的列名
  • 提供了被分组的值和分组名的字典/Series
  • a function to be invoked on the axis index or the individual labels in the index

案例如下

import pandas as pd
import numpy as np
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)})

查看df

>> df
key1    key2    data1   data2
0   a   one 0.162224    -0.497908
1   a   two 0.497138    0.195447
2   b   one 1.158331    -0.580097
3   b   two 1.639932    -0.997953
4   a   one -0.303192   0.525740

下面根据key1列的值对data1列做分组,注意groupby接收的参数是df['key1'],而非'key1',事实上如果被分组的对象是一个Series,则groupby的参数是一个Series,如果被分组对象是一个DataFrame,则groupby的参数可以是一个Series,也可以是该Series的名字,参考下面示例。返回结果grouped的类型是SeriesGroupBy对象。

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

该分组结果计算sum

>> grouped.sum()
key1
a    0.356170
b    2.798262
Name: data1, dtype: float64

同时也可以对整个DataFrame df做分组,再计算其中列data1的聚合值

>> df.groupby('key1')['data1'].sum() # or df.groupby('key1')[['data1']].sum()
key1
a    0.356170
b    2.798262
Name: data1, dtype: float64

可以根据多个列做分组,比如下面用key1key2对DataFrame做分组。

>> g2 = df.groupby(['key1', 'key2']) 
>> g2.sum()
              data1     data2
key1 key2       
a    one     -0.140968  0.027832
     two     0.497138   0.195447
b    one     1.158331   -0.580097
     two     1.639932   -0.997953

上面的g2结果也可以通过unstack方法表示为包含独一无二键值对的分级索引(hierarchical index)

>> g2.sum.unstack()
             data1               data2
key2    one       two        one         two
key1                
a   -0.140968   0.497138    0.027832    0.195447
b   1.158331    1.639932    -0.580097   -0.997953

分组的依据也可以是相同长度的其他数组,比如

>> states = np.array(['Ohio', 'California', 'California', 'Ohio', 'Ohio'])
>> years = np.array([2005, 2005, 2006, 2005, 2006])
>> df['data1'].groupby([states, years]).sum()
California  2005    0.497138
            2006    1.158331
Ohio        2005    1.802156
            2006   -0.303192
Name: data1, dtype: float64

我们再来看有哪些列参与了分组。下面的这个例子,用key1列对DataFrame做分组,结果中只显示了data1data2而没有key2。因为key2列是非数值列,被排除在分组聚合的结果中

>> df.groupby('key1').sum()
    data1   data2
key1        
a   0.356170    0.223279
b   2.798262    -1.578050

size方法返回分组的数字

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

(2022.05.13 Fri)
迭代groupby对象

>> for k,v in df.groupby('key1'):
  ...   print(k,'\n')
  ...   print(v)
a 

  key1 key2     data1     data2
0    a  one  0.162224 -0.497908
1    a  two  0.497138  0.195447
4    a  one -0.303192  0.525740
b 

  key1 key2     data1     data2
2    b  one  1.158331 -0.580097
3    b  two  1.639932 -0.997953

也可以转化成list对象迭代

>> list(df.groupby('key1'))
[('a',
    key1 key2     data1     data2
  0    a  one  0.162224 -0.497908
  1    a  two  0.497138  0.195447
  4    a  one -0.303192  0.525740),
 ('b',
    key1 key2     data1     data2
  2    b  one  1.158331 -0.580097
  3    b  two  1.639932 -0.997953)]

注意,当使用多个key做分组时,key的不同元素的组合保存为tuple

>> for k, g in df.groupby(['key1', 'key2']):
  ...   print(k, '\n')
  ...   print(g)
('a', 'one') 

  key1 key2     data1     data2
0    a  one  0.162224 -0.497908
4    a  one -0.303192  0.525740
('a', 'two') 

  key1 key2     data1     data2
1    a  two  0.497138  0.195447
('b', 'one') 

  key1 key2     data1     data2
2    b  one  1.158331 -0.580097
('b', 'two') 

  key1 key2     data1     data2
3    b  two  1.639932 -0.997953

按字典和Series分组
除了数组之外,DataFrame还可按字典和Series分组。
考虑如下DataFrame

people = pd.DataFrame(np.random.randn(5, 5),
columns=['a', 'b', 'c', 'd', 'e'],
index=['Joe', 'Steve', 'Wes', 'Jim', 'Travis'])
people.iloc[2:3, [1, 2]] = np.nan

打印

>> people
         a           b          c           d           e
Joe 1.317179    -0.209542   0.943849    -2.226055   -0.430271
Steve   -1.757563   -1.531038   -0.519030   -0.533200   0.447961
Wes 0.407967    NaN NaN 0.658021    0.440425
Jim -0.320255   -1.165006   0.275818    1.276247    -1.346920
Travis  -1.678433   -1.272230   1.391566    -0.006185   -0.109339

定义一个字典

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

可以字典为key,对people进行分组

>> by_column = people.groupby(mapping, axis=1)
>> by_column.sum()
        blue     red
Joe -1.282207   0.677367
Steve   -1.052230   -2.840639
Wes 0.658021    0.848391
Jim 1.552065    -2.832181
Travis  1.385381    -3.060002

也可以将字典转化为Series并以此为依据对数据做分组

>> ms = pd.Series(mapping)
>> ms
a       red
b       red
c      blue
d      blue
e       red
f    orange
dtype: object
>> people.groupby(ms, axis=1).sum()
        blue       red
Joe -1.282207   0.677367
Steve   -1.052230   -2.840639
Wes 0.658021    0.848391
Jim 1.552065    -2.832181
Travis  1.385381    -3.060002

根据函数分组
函数也可作为group key。一旦函数做为group key,将index值作为函数的输入,而返回值作为组名。比如上面的people数组,调用len作为group key,则返回的index值的长度将作为组名。

>> people.groupby(len).sum()
        a           b           c           d         e
3   1.404891    -1.374547   1.219667    -0.291787   -1.336766
5   -1.757563   -1.531038   -0.519030   -0.533200   0.447961
6   -1.678433   -1.272230   1.391566    -0.006185   -0.109339

另一个例子,一个函数检测string的结尾字母,如果是e或s则分别返回,其他情况返回others。将这个函数作为group key,查看结果。

def endswith(k):
    if k.endswith('e'):
        return 'e'
    elif k.endswith('s'):
        return 's'
    else:
        return 'others'
>> people.groupby(endswith).sum()
          a         b           c           d           e
e   -0.440383   -1.740579   0.424819    -2.759255   0.017690
others  -0.320255   -1.165006   0.275818    1.276247    -1.346920
s   -1.270466   -1.272230   1.391566    0.651836    0.331085

亦可混合使用

>> key_list = ['one', 'one', 'one', 'two', 'two']
>> people.groupby([len, key_list]).min()
            a          b            c           d           e
3   one 0.407967    -0.209542   0.943849    -2.226055   -0.430271
    two -0.320255   -1.165006   0.275818    1.276247    -1.346920
5   one -1.757563   -1.531038   -0.519030   -0.533200   0.447961
6   two -1.678433   -1.272230   1.391566    -0.006185   -0.109339

根据分级索引(hierarchical index)分组

>> columns = pd.MultiIndex.from_arrays([['US', 'US', 'US', 'JP', 'JP'],
                                      [1, 3, 5, 1, 3]],
                                       names=['cty', 'tenor'])
>> hier_df = pd.DataFrame(np.random.randn(4, 5), columns=columns)
>> hier_df
cty   US                         JP
tenor 1        3         5        1        3
0 0.560145 -1.265934 0.119827 -1.063512 0.332883
1 -2.359419 -0.199543 -1.541996 -0.970736 -1.307030
2 0.286350 0.377984 -0.753887 0.331286 1.349742
3 0.069877 0.246674 -0.011862 1.004812 1.327195
>> hier_df.groupby(level='cty', axis=1).count()
cty JP  US
0   2   3
1   2   3
2   2   3
3   2   3

Data Aggregation数据聚合

数据聚合指的是从数组生成标量(scalar)值的数据变换过程。常见的数据聚合包括前面出现的几种,比如meansumcountmin/maxmedianstd, varprodfirstlast等。还包括quantile用于计算分位点。比如前面的根据key1分组的data1数据,计算其0.9分位点

>> grouped.quantile(0.9)
key1
a    0.430156
b    1.591772
Name: data1, dtype: float64

也可以自定义聚合函数,并用agg方法引入。比如定义一个80分位点与20分位点的差值函数q82,并用agg方法调用该聚合函数。

def q82(arr):
    return np.percentile(arr, 80) - np.percentile(arr, 20)
>> grouped.agg(q82)
key1
a    0.480198
b    0.288961
Name: data1, dtype: float64

注意,如果是自定义的函数,在被agg方法调用时直接写函数名即可,如果是内置聚合函数如上面提到的,则需要在名字上加引号,且与不用agg方法的直接调用返回相同的结果。

>> grouped.agg('mean')
key1
a    0.118723
b    1.399131
Name: data1, dtype: float64
>> grouped.mean()
key1
a    0.118723
b    1.399131
Name: data1, dtype: float64

可通过describe方法验证上面的结果

>> grouped.describe()
   count    mean      std          min          25%         50%         75%  
max
key1                                
a   3.0   0.118723  0.401935    -0.303192   -0.070484   0.162224    0.329681    0.497138
b   2.0   1.399131  0.340543    1.158331    1.278731    1.399131    1.519531    1.639932

当然也可以用实现其他功能的函数,比如对分组结果排序

def sf(arr):
    arr = list(map(lambda x: round(x, 2), arr))
    return sorted(arr)
>> grouped.agg(sf)
key1
a    [-0.3, 0.16, 0.5]
b         [1.16, 1.64]
Name: data1, dtype: object

列级(colume-wise)应用和多函数应用

对单独一个列做多个函数操作,参考如下案例

>> tips = pd.read_csv('/Users/.../pydata-book/examples/tips.csv')
>> tips.shape
(244, 6)
>> tips.columns
Index(['total_bill', 'tip', 'smoker', 'day', 'time', 'size'], dtype='object')
>> tips.head(5)
    total_bill tip smoker day time size
0   16.99   1.01    No  Sun Dinner  2
1   10.34   1.66    No  Sun Dinner  3
2   21.01   3.50    No  Sun Dinner  3
3   23.68   3.31    No  Sun Dinner  2
4   24.59   3.61    No  Sun Dinner  4
>> tips['tip_pct'] = tips['tip']/tips['total_bill'] # 生成小费比例字段
>> grouped = tips.groupby(['day', 'smoker']) #根据day和smoker两个字段对数据分组
>> grouped_pct = grouped['tip_pct'] # 取得tip_pct字段

计算平均

>> 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

计算一列的多个统计量/聚合值,用agg方法,统计量用list保存

>> grouped_pct.agg(['mean', 'max', q82])
          mean          max       q82
day smoker          
Fri No  0.151650    0.187735    0.034600
    Yes 0.174783    0.263480    0.097666
Sat No  0.158048    0.291990    0.061659
    Yes 0.147906    0.325733    0.112116
Sun No  0.160113    0.252672    0.072194
    Yes 0.187250    0.710345    0.144385
Thur No 0.160298    0.266312    0.060163
    Yes 0.163863    0.241255    0.061858

此外,你可以为作用于列的统计量/聚合函数命名,并以新命名显示,以tuple in list为表示。

>> rn = [('func_mean', 'mean'), ('func_q82', q82)]
>> grouped_pct.agg(rn)
       func_mean    func_q82
day smoker      
Fri No  0.151650    0.034600
    Yes 0.174783    0.097666
Sat No  0.158048    0.061659
    Yes 0.147906    0.112116
Sun No  0.160113    0.072194
    Yes 0.187250    0.144385
Thur No 0.160298    0.060163
    Yes 0.163863    0.061858

此方法也可用于多个列使用相同的聚合方法,注意多列的名字表示成一个list,作为grouped的index。注意到对多个列使用不同的聚合方法,DataFrame将返回分级列(hierarchical column)。

>> tmp = grouped[['tip_pct', 'total_bill']].agg(['mean', 'max', q82])
>> tmp
                     tip_pct                         total_bill
          mean         max         q82         mean      max    q82
day smoker                      
Fri No  0.151650    0.187735    0.034600    18.420000   22.75   8.022
    Yes 0.174783    0.263480    0.097666    16.813333   40.17   11.166
Sat No  0.158048    0.291990    0.061659    19.661778   48.33   7.724
    Yes 0.147906    0.325733    0.112116    21.276667   50.81   15.168
Sun No  0.160113    0.252672    0.072194    20.506667   48.17   11.844
    Yes 0.187250    0.710345    0.144385    24.120000   45.35   17.224
Thur No 0.160298    0.266312    0.060163    17.113111   41.19   11.706
    Yes 0.163863    0.241255    0.061858    19.190588   43.11   7.284

上面结果可按列读取

>> tmp['tip_pct']
           mean      max        q82
day smoker          
Fri No  0.151650    0.187735    0.034600
    Yes 0.174783    0.263480    0.097666
Sat No  0.158048    0.291990    0.061659
    Yes 0.147906    0.325733    0.112116
Sun No  0.160113    0.252672    0.072194
    Yes 0.187250    0.710345    0.144385
Thur No 0.160298    0.266312    0.060163
    Yes 0.163863    0.241255    0.061858

而对不同的列采用不同的聚合函数,需要使用字典。比如对tip列要计算最大值,对size列计算求和,可用列名做key,聚合函数名或聚合函数形成的列做value

>> ac = {'tip': ['max', 'mean'], 'size': 'sum'}
>> grouped.agg(ac)
            tip            size
        max    mean        sum
day smoker          
Fri No  3.50    2.812500    9
    Yes 4.73    2.714000    31
Sat No  9.00    3.102889    115
    Yes 10.00   2.875476    104
Sun No  6.00    3.167895    167
    Yes 6.50    3.516842    49
Thur No 6.70    2.673778    112
    Yes 5.00    3.030000    40

聚合结果取消index

对分组后的聚合结果取消index,只需要在做groupby操作时加入as_index = False选项

>> 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

使用聚合函数的agg方法与apply的差别

agg方法应用于列、Series
apply方法应用于这个DataFrame
(待验证)

Apply-一般性的Split-Apply-Combine

Groupby方法里面最常用的部分是Apply,即对数据分组之后,再对每个分组piece进行的操作。Apply操作之后即是对数据做concatenate。注意Apply作用的对象是group piece,也是一个DataFrame。

仍然考虑上面的数据框tips,找出每天的中晚饭小费收入占比最高的前五位。首先写个函数返回一个数据框按某一列排序最高的前5位所有数据。

def top(df, n=5, column='tip_pct'):
    return df.sort_value(name=column)[-n:]

下面按daytime分组,查看每组有多少笔小费,并对分组执行top函数。可以看出周六周日两天人们普遍支付更多比例的小费。

>> tips.groupby(['day', 'time']).size()
day   time  
Fri   Dinner    12
      Lunch      7
Sat   Dinner    87
Sun   Dinner    76
Thur  Dinner     1
      Lunch     61
dtype: int64
>> tips.groupby(['day', 'time']).apply(top)
           total_bill  tip  smoker  day  time  size  tip_pct
day time                                
Fri Dinner  91  22.49   3.50    No  Fri Dinner  2   0.155625
            92  5.75    1.00    Yes Fri Dinner  2   0.173913
            101 15.38   3.00    Yes Fri Dinner  2   0.195059
            100 11.35   2.50    Yes Fri Dinner  2   0.220264
            93  16.32   4.30    Yes Fri Dinner  2   0.263480
     Lunch  220 12.16   2.20    Yes Fri Lunch   2   0.180921
            223 15.98   3.00    No  Fri Lunch   3   0.187735
            226 10.09   2.00    Yes Fri Lunch   2   0.198216
            222 8.58    1.92    Yes Fri Lunch   1   0.223776
            221 13.42   3.48    Yes Fri Lunch   2   0.259314
Sat Dinner  20  17.92   4.08    No  Sat Dinner  2   0.227679
            214 28.17   6.50    Yes Sat Dinner  3   0.230742
            109 14.31   4.00    Yes Sat Dinner  2   0.279525
            232 11.61   3.39    No  Sat Dinner  2   0.291990
            67  3.07    1.00    Yes Sat Dinner  1   0.325733
Sun Dinner  181 23.33   5.65    Yes Sun Dinner  2   0.242177
            51  10.29   2.60    No  Sun Dinner  2   0.252672
            183 23.17   6.50    Yes Sun Dinner  4   0.280535
            178 9.60    4.00    Yes Sun Dinner  2   0.416667
            172 7.25    5.15    Yes Sun Dinner  2   0.710345
Thur Dinner 243 18.78   3.00    No  Thur    Dinner  2   0.159744
     Lunch  200 18.71   4.00    Yes Thur    Lunch   3   0.213789
            87  18.28   4.00    No  Thur    Lunch   2   0.218818
            88  24.71   5.85    No  Thur    Lunch   2   0.236746
            194 16.58   4.00    Yes Thur    Lunch   2   0.241255
            149 7.51    2.00    No  Thur    Lunch   2   0.266312
...

apply中调用的函数可以通过如下方式传递参数。比如查看每天每个时段最高价的bill,可指定n=1columns=total_bill

>> tips.groupby(['day', 'time']).apply(top, n=1, columns='total_bill')
            total_bill  tip   smoker day  time size  tip_pct
day time                                
Fri Dinner  95  40.17   4.73    Yes Fri Dinner  4   0.117750
    Lunch   225 16.27   2.50    Yes Fri Lunch   2   0.153657
Sat Dinner  170 50.81   10.00   Yes Sat Dinner  3   0.196812
Sun Dinner  156 48.17   5.00    No  Sun Dinner  6   0.103799
Thur Dinner 243 18.78   3.00    No  Thur    Dinner  2   0.159744
    Lunch   197 43.11   5.00    Yes Thur    Lunch   4   0.115982

再比如,查看分组后某个数据的describe信息,除了对分组结果调用内置的describe方法,也可将describe写成lambda函数通过apply调用

>> tips.groupby('smoker')['tip_pct'].describe()
     count    mean        std          min         25%        50%        75%        max
smoker                              
No  151.0   0.159328    0.039910    0.056797    0.136906    0.155625    0.185014    0.291990
Yes 93.0    0.163196    0.085119    0.035638    0.106771    0.153846    0.195059    0.710345
>> f = lambda x: x.describe()
>> tips.groupby('smoker')['tip_pct'].apply(f)
smoker       
No      count    151.000000
        mean       0.159328
        std        0.039910
        min        0.056797
        25%        0.136906
        50%        0.155625
        75%        0.185014
        max        0.291990
Yes     count     93.000000
        mean       0.163196
        std        0.085119
        min        0.035638
        25%        0.106771
        50%        0.153846
        75%        0.195059
        max        0.710345
Name: tip_pct, dtype: float64

(2022.05.16 Mon)

不显示分组键Suppressing the group key:

上面的例子中结果数据在初始对象的索引之上保存了分级索引hierachical index,可使用group_keys=False指令取消分级索引:

>> tips.groupby(['day', 'time'], group_keys=False).apply(top, n=1, columns='total_bill')
    total_bill  tip smoker day time size tip_pct
95  40.17   4.73    Yes Fri Dinner  4   0.117750
225 16.27   2.50    Yes Fri Lunch   2   0.153657
170 50.81   10.00   Yes Sat Dinner  3   0.196812
156 48.17   5.00    No  Sun Dinner  6   0.103799
243 18.78   3.00    No  Thur    Dinner  2   0.159744
197 43.11   5.00    Yes Thur    Lunch   4   0.115982

分位数和桶分析Quantile and Bucket Analysis

可使用pandas.cut指令对一个Series做分位分析,并结合groupby指令对数组做分析。

>> frame = pd.DataFrame({'data1': np.random.randn(1000),
                      'data2': np.random.randn(1000)})
>> frame.head()
       data1   data2
0   -0.754650   -0.085965
1   -0.922789   0.523089
2   -1.001486   0.306347
3   1.169146    -1.424664
4   2.311694    0.280592
>> quartiles = pd.cut(frame.data1, 4)
>> quartiles.head()
0    (-1.563, 0.154]
1    (-1.563, 0.154]
2    (-1.563, 0.154]
3     (0.154, 1.871]
4     (1.871, 3.588]
Name: data1, dtype: category
Categories (4, interval[float64, right]): [(-3.287, -1.563] < (-1.563, 0.154] < (0.154, 1.871] < (1.871, 3.588]]

cut方法返回的Categorial对象可传递给groupby方法。我们可以以此为依据对data2字段做分组。

def get_stats(df):
    return {'mean': df.mean(), 'min': df.min(), 'max': df.max(), 'count': df.count()}
>> q = frame.data2.groupby(quartiles).apply(get_stats).unstack()
        mean      min      max      count
data1               
(-3.287, -1.563]    0.069223    -2.676886   2.390891    57.0
(-1.563, 0.154] -0.089421   -3.099386   2.872714    514.0
(0.154, 1.871]  -0.001327   -3.319630   3.122984    394.0
(1.871, 3.588]  0.093478    -2.111966   2.091880    35.0

注意这里的cut得到的分组是范围的长度尺度上相同/相似的结果。如果要每个分组内值的数量相同/相似,则需要使用qcut指令。

GroupBy应用

Case 1:按组填充NA值

按组填充该组中的空值。有下面数据,保存美国若干州的某项数据。这些州分属东西两部分。我们设置其中一些值为空。

>> states = ['Ohio', 'New York', 'Vermont', 'Florida',\
          'Oregon', 'Nevada', 'California', 'Idaho']
>> group_key = ['East'] * 4 + ['West'] * 4
>> data = pd.Series(np.random.randn(8), index=states)
>> data
Ohio          0.869617
New York     -1.113901
Vermont      -0.225091
Florida      -0.010972
Oregon       -0.312229
Nevada       -0.122721
California   -1.171598
Idaho         0.361301
dtype: float64
>> data[::2] = np.nan
>> data
Ohio               NaN
New York     -1.113901
Vermont            NaN
Florida      -0.010972
Oregon             NaN
Nevada       -0.122721
California         NaN
Idaho         0.361301
dtype: float64

如果此时分组,计算得到的是非空值的统计信息,不包含空值部分

>> data.groupby(group_key).mean()
East   -0.562437
West    0.119290
dtype: float64

下面按组填充数据,比如对每组中的空值填充该组其他值的mean

>> f_mean = lambda x: x.fillna(x.mean())
>> data.groupby(group_key).apply(f_mean)
Ohio         -0.562437
New York     -1.113901
Vermont      -0.562437
Florida      -0.010972
Oregon        0.119290
Nevada       -0.122721
California    0.119290
Idaho         0.361301
dtype: float64

也可针对某个组填充特定值

>> fill_values = {'East': 5, 'West': 10}
>> fill_func = lambda g: g.fillna(fill_values[g.name])
>> data.groupby(group_key).apply(fill_func)
Ohio           5.000000
New York      -1.113901
Vermont        5.000000
Florida       -0.010972
Oregon        10.000000
Nevada        -0.122721
California    10.000000
Idaho          0.361301
dtype: float64

Case 2:分组计算均值

df = pd.DataFrame({'category': ['a', 'a', 'a', 'a',\
                                'b', 'b', 'b', 'b'],\
                                 'data': np.random.randn(8),\
                                 'weights': np.random.rand(8)})
>> df
category data weights
0 a 1.561587 0.957515
1 a 1.219984 0.347267
2 a -0.482239 0.581362
3 a 0.315667 0.217091
4 b -0.047852 0.894406
5 b -0.454145 0.918564
6 b -0.556774 0.277825
7 b 0.253321 0.955905

加权平均的函数get_wa

get_wa = lambda x: np.average(x.data, weights=x.weights)

df这个dataframe按分组执行加权平均操作

>> df.groupby('category').apply(get_wa)
category
a    0.111150
b   -1.091834
dtype: float64

基于pandas.groupbypandas.transform指令

前面已经提到,用pandas.groupby指令对DataFrame分组之后,可针对各组做操作。比如对各组求其mean操作

>> dd = pd.DataFrame({'keys': ['a', 'b', 'c', 'c','c','a','b','b','a'], 'values': np.arange(9,)})
>> dd.groupby('keys').values.mean()
keys
a    4.333333
b    4.666667
c    3.000000
Name: values, dtype: float64

假定我们想要得到一个Series和原序列长度相同,但是新Series中的值用原序列分组后的mean值代替。这种情况下pandas提供了transform方法执行这种操作。下面计算对根据key keys做分组的values的值做归一化操作的过程,归一化的结果写入原DataFrame中。

>> f = lambda x: (x - x.mean())/x.std()
>> dd['mean'] = dd.groupby('keys').values.transform('mean')
>> dd['std'] = dd.groupby('keys').values.transform('std')
>> dd['norm'] = dd.groupby('keys').values.transform(f)
>> dd
  keys values  mean       std        norm
0   a   0   4.333333    4.041452    -1.072222
1   b   1   4.666667    3.214550    -1.140647
2   c   2   3.000000    1.000000    -1.000000
3   c   3   3.000000    1.000000    0.000000
4   c   4   3.000000    1.000000    1.000000
5   a   5   4.333333    4.041452    0.164957
6   b   6   4.666667    3.214550    0.414781
7   b   7   4.666667    3.214550    0.725866
8   a   8   4.333333    4.041452    0.907265

Reference

1 Python for Data Analysis, Wes McKinney

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,732评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,496评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,264评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,807评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,806评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,675评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,029评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,683评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,704评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,666评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,773评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,413评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,016评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,204评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,083评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,503评论 2 343

推荐阅读更多精彩内容