数据聚合

  • 逐列以及多函数应用
  • 返回不含行索引的聚合数据

聚合是指根据数组产生标量值的数据转换过程,如mean、count、min和sum等。在之前介绍groupby机制的时候我们知道对GroupBy对象可以使用count等方法进行聚合,得到每个分组的聚合结果。常见的GroupBy对象的聚合方法见下表:

方法

描述

count

分组中的非NA数值

sum

非NA值的和

mean

非NA值的均值

median

非NA值的算数中位数

std、var

无偏(n-1为分母)标准差和方差

min、max

非NA值的最小值和最大值

prod

非NA值的乘积

frist,last

非NA值的第一个和最后一个值

考虑以下这个DataFrame:

df = pd.DataFrame({'key1':list('aabba'),
                   'key2':['one','two','one','two','one'],
                   'data1':np.random.randn(5),
                   'data2':np.random.randn(5)})

grouped = df.groupby('key1')

quantile可以计算DataFrame或者Series的分位数,尽管GroupBy对象没有此方法,但是我们仍可以对GroupBy对象使用此方法,内部GroupBy对每一个Series对象或者DataFrame对象使用该方法,例如我们可以计算data1中每个分组的分位数:

print(grouped['data1'].quantile(0.9))
# key1
# a    1.001195
# b    2.274486
# Name: data1, dtype: float64

如果想自定义一个聚合函数,可以使用GroupBy对象的agg方法:

def peak_to_peak(arr):
    """
    计算每组的极差
    :param arr: Series对象
    :return: 极差
    """
    return arr.max()-arr.min()

print(grouped.agg(peak_to_peak))
#          data1     data2
# key1                    
# a     1.605113  0.866069
# b     1.117966  1.030901

逐列以及多函数应用

我们可以使用agg方法对分组后的结果同时使用多个方法进行聚合,先考虑下面这个DataFrame,数据来自于文章开头中的资源,我们为它加上tip_pct这一项,并使用groupby方法按day和smoker来进行分组:

tips = pd.read_csv('../pydata-book-2nd-edition/examples/tips.csv')
tips['tip_pct'] = tips['tip']/tips['total_bill']
print(tips.head())
#    total_bill   tip smoker  day    time  size   tip_pct
# 0       16.99  1.01     No  Sun  Dinner     2  0.059447
# 1       10.34  1.66     No  Sun  Dinner     3  0.160542
# 2       21.01  3.50     No  Sun  Dinner     3  0.166587
# 3       23.68  3.31     No  Sun  Dinner     2  0.139780
# 4       24.59  3.61     No  Sun  Dinner     4  0.146808

grouped = tips.groupby(['day','smoker'])
grouped_pct = grouped['tip_pct']

之后如果给agg方法传入一个包含函数或者函数名的列表,则可以得到列名为函数名的聚合结果:

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

如果你传入的是一个(名字,函数或函数名)的元组,则列名则会是你传入的名字:

print(grouped_pct.agg([('平均直','mean'),('极差',peak_to_peak)]))
#                   平均直        极差
# day  smoker
# Fri  No      0.151650  0.067349
#      Yes     0.174783  0.159925
# Sat  No      0.158048  0.235193
#      Yes     0.147906  0.290095
# Sun  No      0.160113  0.193226
#      Yes     0.187250  0.644685
# Thur No      0.160298  0.193350
#      Yes     0.163863  0.151240

我们可以对两个列同时进行相同的三种数据操作,结果如下:

grouped = tips.groupby(['day','smoker'])
result = grouped['tip_pct','total_bill'].agg(['count','mean',peak_to_peak])
print(result)
#             tip_pct                        total_bill
#               count      mean peak_to_peak      count       mean peak_to_peak
# day  smoker
# Fri  No           4  0.151650     0.067349          4  18.420000        10.29
#      Yes         15  0.174783     0.159925         15  16.813333        34.42
# Sat  No          45  0.158048     0.235193         45  19.661778        41.08
#      Yes         42  0.147906     0.290095         42  21.276667        47.74
# Sun  No          57  0.160113     0.193226         57  20.506667        39.40
#      Yes         19  0.187250     0.644685         19  24.120000        38.10
# Thur No          45  0.160298     0.193350         45  17.113111        33.68
#      Yes         17  0.163863     0.151240         17  19.190588        32.77

print(result['tip_pct'])
#              count      mean  peak_to_peak
# day  smoker                               
# Fri  No          4  0.151650      0.067349
#      Yes        15  0.174783      0.159925
# Sat  No         45  0.158048      0.235193
#      Yes        42  0.147906      0.290095
# Sun  No         57  0.160113      0.193226
#      Yes        19  0.187250      0.644685
# Thur No         45  0.160298      0.193350
#      Yes        17  0.163863      0.151240

当然,我们可以以(名字,函数)的形式传入agg也是可以的,具体使用方法与之前的介绍相同,在此不再赘述。
如果想在不同的列上应用不同的函数,可以给agg方法传入字典,并且此时不再需要提前将需要进行聚合的列分隔出来:

grouped = tips.groupby(['day','smoker'])
print(grouped.agg({'tip_pct':['min','max',peak_to_peak],'total_bill':'std'}))
#               tip_pct                        total_bill
#                   min       max peak_to_peak        std
# day  smoker
# Fri  No      0.120385  0.187735     0.067349   5.059282
#      Yes     0.103555  0.263480     0.159925   9.086388
# Sat  No      0.056797  0.291990     0.235193   8.939181
#      Yes     0.035638  0.325733     0.290095  10.069138
# Sun  No      0.059447  0.252672     0.193226   8.130189
#      Yes     0.065660  0.710345     0.644685  10.442511
# Thur No      0.072961  0.266312     0.193350   7.721728
#      Yes     0.090014  0.241255     0.151240   8.355149

返回不含行索引的聚合数据

在默认情况下使用groupby函数进行分组的时候会将分组键作为行索引,当然我们可以在使用groupby方法时指定as_index = False来禁用此行为,而是使用默认的数字索引:

grouped = tips.groupby(['day','smoker'],as_index=False)
print(grouped.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