聚合(Aggregation
)指的是一些数据转化(data transformation
),这些数据转化能从数组中产生标量(scalar values
)。下面的例子就是一些聚合方法,包括mean, count, min and sum
。我们可能会好奇,在一个GroupBy
对象上调用mean()
的时候,究竟发生了什么。一些常见的聚合,比如下表,实现方法上都已经被优化过了。当然,我们可以使用的聚合方法不止这些:
我们可以使用自己设计的聚合方法,而且可以调用分组后对象上的任意方法。例如,我们可以调用quantile
来计算Series
或DataFrame
中列的样本的百分数。
尽管quantile
并不是专门为GroupBy
对象设计的方法,这是一个Series
方法,但仍可以被GroupBy
对象使用。GroupBy
会对Series
进行切片(slice up
),并对于切片后的每一部分调用piece.quantile(0.9)
,然后把每部分的结果整合到一起:
import numpy as np
import pandas as pd
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
data1 | data2 | key1 | key2 | |
---|---|---|---|---|
0 | 1.707738 | 0.186729 | a | one |
1 | 1.069831 | 1.305796 | a | two |
2 | -2.291339 | -1.609071 | b | one |
3 | 1.348090 | -0.294999 | b | two |
4 | 0.341176 | 0.429461 | a | one |
grouped = df.groupby('key1')
for key, group in grouped:
print(key)
print(group)
a
data1 data2 key1 key2
0 1.707738 0.186729 a one
1 1.069831 1.305796 a two
4 0.341176 0.429461 a one
b
data1 data2 key1 key2
2 -2.291339 -1.609071 b one
3 1.348090 -0.294999 b two
grouped['data1'].quantile(0.9)
key1
a 1.580157
b 0.984147
Name: data1, dtype: float64
如果想用自己设计的聚合函数,把用于聚合数组的函数传入到aggregate
或agg
方法即可:
def peak_to_peak(arr):
return arr.max() - arr.min()
grouped.agg(peak_to_peak)
data1 | data2 | |
---|---|---|
key1 | ||
a | 1.366563 | 1.119067 |
b | 3.639430 | 1.314072 |
我们发现很多方法,比如describe
,也能正常使用,尽管严格的来说,这并不是聚合:
grouped.describe()
data1 | data2 | ||
---|---|---|---|
key1 | |||
a | count | 3.000000 | 3.000000 |
mean | 1.039582 | 0.640662 | |
std | 0.683783 | 0.588670 | |
min | 0.341176 | 0.186729 | |
25% | 0.705503 | 0.308095 | |
50% | 1.069831 | 0.429461 | |
75% | 1.388785 | 0.867629 | |
max | 1.707738 | 1.305796 | |
b | count | 2.000000 | 2.000000 |
mean | -0.471624 | -0.952035 | |
std | 2.573465 | 0.929189 | |
min | -2.291339 | -1.609071 | |
25% | -1.381482 | -1.280553 | |
50% | -0.471624 | -0.952035 | |
75% | 0.438233 | -0.623517 | |
max | 1.348090 | -0.294999 |
细节的部分在10.3会进行更多解释。
注意:自定义的函数会比上面表中的函数慢一些,上面的函数时优化过的,而自定义的函数会有一些额外的计算,所以慢一些。
让我们回到tipping
数据集。加载数据及后,我们添加一列用于描述小费的百分比:
tips = pd.read_csv('../examples/tips.csv')
# Add tip percentage of total bill
tips['tip_pct'] = tips['tip'] / tips['total_bill']
tips[:6]
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 |
5 | 25.29 | 4.71 | No | Sun | Dinner | 4 | 0.186240 |
我们可以看到,对series
或DataFrame
进行聚合,其实就是通过aggregate
使用合适的函数,或者调用一些像mean
或std
这样的方法。然而,我们可能想要在列上使用不同的函数进行聚合,又或者想要一次执行多个函数。幸运的是,这是可能的,下面将通过一些例子来说明。首先,对于tips
数据集,先用day
和smoker
进行分组:
grouped = tips.groupby(['day', 'smoker'])
对于像是上面表格10-1中的一些描述性统计,我们可以直接传入函数的名字,即字符串:
grouped_pct = grouped['tip_pct']
for name, group in grouped_pct:
print(name)
print(group[:2], '\n')
('Fri', 'No')
91 0.155625
94 0.142857
Name: tip_pct, dtype: float64
('Fri', 'Yes')
90 0.103555
92 0.173913
Name: tip_pct, dtype: float64
('Sat', 'No')
19 0.162228
20 0.227679
Name: tip_pct, dtype: float64
('Sat', 'Yes')
56 0.078927
58 0.156584
Name: tip_pct, dtype: float64
('Sun', 'No')
0 0.059447
1 0.160542
Name: tip_pct, dtype: float64
('Sun', 'Yes')
164 0.171331
172 0.710345
Name: tip_pct, dtype: float64
('Thur', 'No')
77 0.147059
78 0.131810
Name: tip_pct, dtype: float64
('Thur', 'Yes')
80 0.154321
83 0.152999
Name: tip_pct, dtype: float64
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
如果我们把函数或函数的名字作为一个list
传入,我们会得到一个DataFrame
,每列的名字就是函数的名字:
# def peak_to_peak(arr):
# return arr.max() - arr.min()
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 |
上面我们把多个聚合函数作为一个list
传入给agg
,这些函数会独立对每一个组进行计算。
上面结果的列名是自动给出的,当然,我们也可以更改这些列名。这种情况下,传入一个由tuple
组成的list,每个tuple
的格式是(name, function)
,每个元组的第一个元素会被用于作为DataFrame
的列名(我们可以认为这个二元元组list
是一个有序的映射):
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
,我们有更多的选择,我们可以用一个含有多个函数的list
应用到所有的列上,也可以在不同的列上应用不同的函数。演示一下,假设我们想要在tip_pct
和total_bill
这两列上,计算三个相同的统计指标:
functions = ['count', 'mean', 'max']
result = grouped['tip_pct', 'total_bill'].agg(functions)
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
有多层级的列(hierarchical columns
)。另外一种做法有相同的效果,即我们对于每一列单独进行聚合(aggregating each column separately
),然后使用concat
把结果都结合在一起,然后用列名作为keys
参数:
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 |
我们之前提到过,可以用元组组成的list来自己定义列名:
ftuples = [('Durchschnitt', 'mean'), ('Abweichung', np.var)]
grouped['tip_pct', 'total_bill'].agg(ftuples)
tip_pct | total_bill | ||||
---|---|---|---|---|---|
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
传递一个dict
,这个dict
需要包含映射关系,用来表示列名和函数之间的对应关系:
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 |
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
才会有多层级列(hierarchical columns
)
目前为止提到的所有例子,最后返回的聚合数据都是有索引的,而且这个索引默认是多层级索引,这个索引是由不同的组键的组合构成的(unique group key combinations
)。因为我们并不是总需要返回这种索引,所以我们可以取消这种模式,在调用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 |
当然,我们也可以在上面的结果上直接调用reset_index
,这样的话就能得到之前那种多层级索引的结果。不过使用as_index=False
方法可以避免一些不必要的计算。