文章目录
- 12.2 Advanced GroupBy Use(高级GroupBy用法)
- 1 Group Transforms and “Unwrapped” GroupBys(组变换和无包装的GroupBy)
- 2 Grouped Time Resampling(分组时间重采样)
12.2 Advanced GroupBy Use(高级GroupBy用法)
我们已经在第十章讨论了groupby
的一些用法,这里还有一些技巧可能会用得到。
1 Group Transforms and “Unwrapped” GroupBys(组变换和无包装的GroupBy)
在第十章里,使用apply
方法在组上进行转换操作的。还有一个内建的方法叫transform
,和apply
相同,但是在一些函数的用法上有一些限制:
- 可以产生一个标量,将数据广播(
broadcast
)到与组一样的形状(这里的broadcast
可以理解为改变数据形状的方法,感兴趣的可以直接搜索numpy
broadcast
) - 可以产生一个和输入的组一样形状的对象
- 不能对输入进行改变
举个例子:
import numpy as np
import pandas as pd
df = pd.DataFrame({'key': ['a', 'b', 'c'] * 4,'value': np.arange(12.)})
df
key | value | |
---|---|---|
0 | a | 0.0 |
1 | b | 1.0 |
2 | c | 2.0 |
3 | a | 3.0 |
4 | b | 4.0 |
5 | c | 5.0 |
6 | a | 6.0 |
7 | b | 7.0 |
8 | c | 8.0 |
9 | a | 9.0 |
10 | b | 10.0 |
11 | c | 11.0 |
通过key
来计算组的平均值:
g = df.groupby('key').value
g.mean()
key
a 4.5
b 5.5
c 6.5
Name: value, dtype: float64
假设我们想要产生一个和df['value']
一样大小的Series
,不过要用key
分组后的平均值来替换。我们可以把函数lambda x: x.mean()
给transform
:
g.transform(lambda x: x.mean())
0 4.5
1 5.5
2 6.5
3 4.5
4 5.5
5 6.5
6 4.5
7 5.5
8 6.5
9 4.5
10 5.5
11 6.5
Name: value, dtype: float64
对于内建的聚合函数,我们可以传入一个字符串别名,就像使用groupby agg
方法的时候一样:
g.transform('mean')
0 4.5
1 5.5
2 6.5
3 4.5
4 5.5
5 6.5
6 4.5
7 5.5
8 6.5
9 4.5
10 5.5
11 6.5
Name: value, dtype: float64
就像apply
,transform
能用那些返回Series
的函数,但是结果的大小和输入的必须一样。例如,我们通过一个lambda
函数令每个小组都乘2:
g.transform(lambda x: x * 2)
0 0.0
1 2.0
2 4.0
3 6.0
4 8.0
5 10.0
6 12.0
7 14.0
8 16.0
9 18.0
10 20.0
11 22.0
Name: value, dtype: float64
一个更复杂的例子,我们可以按降序来计算每一个组:
g.transform(lambda x: x.rank(ascending=False))
0 4.0
1 4.0
2 4.0
3 3.0
4 3.0
5 3.0
6 2.0
7 2.0
8 2.0
9 1.0
10 1.0
11 1.0
Name: value, dtype: float64
考虑一个包含简单聚合的分组转换函数:
def normalize(x):return (x - x.mean()) / x.std()
使用transform
或apply
,都能得到一样的结果:
g.transform(normalize)
0 -1.161895
1 -1.161895
2 -1.161895
3 -0.387298
4 -0.387298
5 -0.387298
6 0.387298
7 0.387298
8 0.387298
9 1.161895
10 1.161895
11 1.161895
Name: value, dtype: float64
g.apply(normalize)
0 -1.161895
1 -1.161895
2 -1.161895
3 -0.387298
4 -0.387298
5 -0.387298
6 0.387298
7 0.387298
8 0.387298
9 1.161895
10 1.161895
11 1.161895
Name: value, dtype: float64
内建的聚合函数,比如mean, sum
经常比一般的apply
函数要快。而是用transform
的话,会更快一些。这就需要我们使用无包装的组操作(upwrapped group operation
):
g.transform('mean')
0 4.5
1 5.5
2 6.5
3 4.5
4 5.5
5 6.5
6 4.5
7 5.5
8 6.5
9 4.5
10 5.5
11 6.5
Name: value, dtype: float64
normalized = (df['value'] - g.transform('mean')) / g.transform('std')
normalized
0 -1.161895
1 -1.161895
2 -1.161895
3 -0.387298
4 -0.387298
5 -0.387298
6 0.387298
7 0.387298
8 0.387298
9 1.161895
10 1.161895
11 1.161895
Name: value, dtype: float64
一个无包装的组操作可能会涉及多个组聚合操作,不过向量化操作会胜过这种操作。
2 Grouped Time Resampling(分组时间重采样)
对于时间序列数据,resample
方法是一个基于时间的组操作。这里有一个样本表格:
N = 15
times = pd.date_range('2017-05-20 00:00', freq='1min', periods=N)
df = pd.DataFrame({'time': times, 'value': np.arange(N)})
df
time | value | |
---|---|---|
0 | 2017-05-20 00:00:00 | 0 |
1 | 2017-05-20 00:01:00 | 1 |
2 | 2017-05-20 00:02:00 | 2 |
3 | 2017-05-20 00:03:00 | 3 |
4 | 2017-05-20 00:04:00 | 4 |
5 | 2017-05-20 00:05:00 | 5 |
6 | 2017-05-20 00:06:00 | 6 |
7 | 2017-05-20 00:07:00 | 7 |
8 | 2017-05-20 00:08:00 | 8 |
9 | 2017-05-20 00:09:00 | 9 |
10 | 2017-05-20 00:10:00 | 10 |
11 | 2017-05-20 00:11:00 | 11 |
12 | 2017-05-20 00:12:00 | 12 |
13 | 2017-05-20 00:13:00 | 13 |
14 | 2017-05-20 00:14:00 | 14 |
我们用time
索引,然后重采样:
df.set_index('time').resample('5min').count()
value | |
---|---|
time | |
2017-05-20 00:00:00 | 5 |
2017-05-20 00:05:00 | 5 |
2017-05-20 00:10:00 | 5 |
假设一个DataFrame
包含多个时间序列,用多一个key
列来表示:
df2 = pd.DataFrame({'time': times.repeat(3),'key': np.tile(['a', 'b', 'c'], N), 'value': np.arange(N * 3.)})
df2[:7]
key | time | value | |
---|---|---|---|
0 | a | 2017-05-20 00:00:00 | 0.0 |
1 | b | 2017-05-20 00:00:00 | 1.0 |
2 | c | 2017-05-20 00:00:00 | 2.0 |
3 | a | 2017-05-20 00:01:00 | 3.0 |
4 | b | 2017-05-20 00:01:00 | 4.0 |
5 | c | 2017-05-20 00:01:00 | 5.0 |
6 | a | 2017-05-20 00:02:00 | 6.0 |
想要对key
列的值做重采样,我们引入pandas.TimeGrouper
对象:
time_key = pd.TimeGrouper('5min')
然后设置time为索引,对key和time_key做分组,然后聚合:
resampled = (df2.set_index('time').groupby(['key', time_key]).sum())
resampled
value | ||
---|---|---|
key | time | |
a | 2017-05-20 00:00:00 | 30.0 |
2017-05-20 00:05:00 | 105.0 | |
2017-05-20 00:10:00 | 180.0 | |
b | 2017-05-20 00:00:00 | 35.0 |
2017-05-20 00:05:00 | 110.0 | |
2017-05-20 00:10:00 | 185.0 | |
c | 2017-05-20 00:00:00 | 40.0 |
2017-05-20 00:05:00 | 115.0 | |
2017-05-20 00:10:00 | 190.0 |
resampled.reset_index()
key | time | value | |
---|---|---|---|
0 | a | 2017-05-20 00:00:00 | 30.0 |
1 | a | 2017-05-20 00:05:00 | 105.0 |
2 | a | 2017-05-20 00:10:00 | 180.0 |
3 | b | 2017-05-20 00:00:00 | 35.0 |
4 | b | 2017-05-20 00:05:00 | 110.0 |
5 | b | 2017-05-20 00:10:00 | 185.0 |
6 | c | 2017-05-20 00:00:00 | 40.0 |
7 | c | 2017-05-20 00:05:00 | 115.0 |
8 | c | 2017-05-20 00:10:00 | 190.0 |
使用TimeGrouper
的一个限制是时间必须是Series
或DataFrame
的索引才行。