- Aggregation:分析大数据时,常常需要进行有效的数据累计(summarization),得到相应的累计指标(aggregation),例如
sum()
,mean()
,min()
等;
- Grouping:基于
groupby
函数实现对数据集的按需分组。
注意,虽然累计也有单独使用的场景(前几节介绍的内容),但更重要的用法是配合分组进行各组内部的累计——可以说aggregation和grouping是一对孪生兄弟。
使用Seaborn库中的一份行星数据:
# 导入seaborn库,获取该数据集,打印形状
import seaborn as sns
planets = sns.load_dataset('planets')
planets.shape
(1035, 6)
# 查看前5行数据
planets.head()
|
method |
number |
orbital_period |
mass |
distance |
year |
0 |
Radial Velocity |
1 |
269.300 |
7.10 |
77.40 |
2006 |
1 |
Radial Velocity |
1 |
874.774 |
2.21 |
56.95 |
2008 |
2 |
Radial Velocity |
1 |
763.000 |
2.60 |
19.84 |
2011 |
3 |
Radial Velocity |
1 |
326.030 |
19.40 |
110.62 |
2007 |
4 |
Radial Velocity |
1 |
516.220 |
10.50 |
119.47 |
2009 |
Table of contents
1. 回顾:pandas的简单Aggregate功能
1.1 Series累计
ser = pd.Series(np.arange(10,16))
ser
0 10
1 11
2 12
3 13
4 14
5 15
dtype: int64
# 求和
ser.sum()
75
# 求均值
ser.mean()
12.5
1.2 Dataframe累计
Dataframe默认对每列进行累计,即默认axis=0
;可以修改axis=1
使累计在每行进行。
df = pd.DataFrame({'A':np.arange(5,10),
'B':np.arange(10,15)})
df
|
A |
B |
0 |
5 |
10 |
1 |
6 |
11 |
2 |
7 |
12 |
3 |
8 |
13 |
4 |
9 |
14 |
# 默认对列
df.sum()
A 35
B 60
dtype: int64
# 修改为行
df.sum(axis=1)
0 15
1 17
2 19
3 21
4 23
dtype: int64
1.3 describe常用统计值
describe() 函数可以计算每一列的若干常用统计值。
planets.describe()
|
number |
orbital_period |
mass |
distance |
year |
count |
1035.000000 |
992.000000 |
513.000000 |
808.000000 |
1035.000000 |
mean |
1.785507 |
2002.917596 |
2.638161 |
264.069282 |
2009.070531 |
std |
1.240976 |
26014.728304 |
3.818617 |
733.116493 |
3.972567 |
min |
1.000000 |
0.090706 |
0.003600 |
1.350000 |
1989.000000 |
25% |
1.000000 |
5.442540 |
0.229000 |
32.560000 |
2007.000000 |
50% |
1.000000 |
39.979500 |
1.260000 |
55.250000 |
2010.000000 |
75% |
2.000000 |
526.005000 |
3.040000 |
178.500000 |
2012.000000 |
max |
7.000000 |
730000.000000 |
25.000000 |
8500.000000 |
2014.000000 |
2. Groupby:分割, 应用, 组合
有时我们需要对某些标签或索引的局部进行累计分析。groupby
函数可以实现lazy分割,得到groupby对象;再对groupby对象进行函数操作时,就等价于将函数操作分别作用于分割出的每一个组,并输出合并的结果。此过程称为split-apply-combine:
- split:将df按照指定的key分割成若干组;
- apply:对每个组应用函数(通常是累计、转换或过滤函数);
- combine:将每一组的结果合并成一个输出数组。
虽然可以使用此前掩码、累计、合并操作实现上述过程,但
groupby
常常只需要一行代码就可以完成,完美隐藏了中间的分割过程。
2.1 Split-Apply-Combine
df = pd.DataFrame({'key': ['A', 'B', 'C', 'A', 'B', 'C'],
'data': range(6)}, columns=['key', 'data'])
df
|
key |
data |
0 |
A |
0 |
1 |
B |
1 |
2 |
C |
2 |
3 |
A |
3 |
4 |
B |
4 |
5 |
C |
5 |
df.groupby('key')
<pandas.core.groupby.generic.DataFrameGroupBy object at 0x7fae291ef400>
groupby 返回一个DataFrameGroupBy对象,其中“隐藏”着若干组数据,但在没有应用累计函数之前是不会计算的——延迟计算(lazy evalution):
df.groupby('key').sum()
2.2 Groupby对象的基本操作方法
Groupby 对象可以被看做是分割得到的Dataframe的集合。
1)按列取值
依据method列进行分组,只输出orbital_period的中位数:
planets.groupby('method')['orbital_period'].median()
method
Astrometry 631.180000
Eclipse Timing Variations 4343.500000
Imaging 27500.000000
Microlensing 3300.000000
Orbital Brightness Modulation 0.342887
Pulsar Timing 66.541900
Pulsation Timing Variations 1170.000000
Radial Velocity 360.200000
Transit 5.714932
Transit Timing Variations 57.011000
Name: orbital_period, dtype: float64
2)按组迭代
Groupby 对象支持直接按组进行迭代:
for (method, group) in planets.groupby('method'):
print("{0:30s} shape={1}".format(method, group.shape))
Astrometry shape=(2, 6)
Eclipse Timing Variations shape=(9, 6)
Imaging shape=(38, 6)
Microlensing shape=(23, 6)
Orbital Brightness Modulation shape=(3, 6)
Pulsar Timing shape=(5, 6)
Pulsation Timing Variations shape=(1, 6)
Radial Velocity shape=(553, 6)
Transit shape=(397, 6)
Transit Timing Variations shape=(4, 6)
3)调用方法
planets.groupby('method')['year'].describe()
|
count |
mean |
std |
min |
25% |
50% |
75% |
max |
method |
|
|
|
|
|
|
|
|
Astrometry |
2.0 |
2011.500000 |
2.121320 |
2010.0 |
2010.75 |
2011.5 |
2012.25 |
2013.0 |
Eclipse Timing Variations |
9.0 |
2010.000000 |
1.414214 |
2008.0 |
2009.00 |
2010.0 |
2011.00 |
2012.0 |
Imaging |
38.0 |
2009.131579 |
2.781901 |
2004.0 |
2008.00 |
2009.0 |
2011.00 |
2013.0 |
Microlensing |
23.0 |
2009.782609 |
2.859697 |
2004.0 |
2008.00 |
2010.0 |
2012.00 |
2013.0 |
Orbital Brightness Modulation |
3.0 |
2011.666667 |
1.154701 |
2011.0 |
2011.00 |
2011.0 |
2012.00 |
2013.0 |
Pulsar Timing |
5.0 |
1998.400000 |
8.384510 |
1992.0 |
1992.00 |
1994.0 |
2003.00 |
2011.0 |
Pulsation Timing Variations |
1.0 |
2007.000000 |
NaN |
2007.0 |
2007.00 |
2007.0 |
2007.00 |
2007.0 |
Radial Velocity |
553.0 |
2007.518987 |
4.249052 |
1989.0 |
2005.00 |
2009.0 |
2011.00 |
2014.0 |
Transit |
397.0 |
2011.236776 |
2.077867 |
2002.0 |
2010.00 |
2012.0 |
2013.00 |
2014.0 |
Transit Timing Variations |
4.0 |
2012.500000 |
1.290994 |
2011.0 |
2011.75 |
2012.5 |
2013.25 |
2014.0 |
Groupby 对象的一些重要方法:
aggregate
filter
transform
apply
# 数据准备
rng = np.random.RandomState(0)
df = pd.DataFrame({'key': ['A', 'B', 'C', 'A', 'B', 'C'],
'data1': range(6),
'data2': rng.randint(0, 10, 6)},
columns = ['key', 'data1', 'data2'])
df
|
key |
data1 |
data2 |
0 |
A |
0 |
5 |
1 |
B |
1 |
0 |
2 |
C |
2 |
3 |
3 |
A |
3 |
3 |
4 |
B |
4 |
7 |
5 |
C |
5 |
9 |
1)累计
aggregate()
支持更复杂的操作,如字符串、函数等,并且能一次性计算所有累计值;
df.groupby('key').aggregate(['min',max,np.median])
|
data1 |
data2 |
|
min |
max |
median |
min |
max |
median |
key |
|
|
|
|
|
|
A |
0 |
3 |
1.5 |
3 |
5 |
4.0 |
B |
1 |
4 |
2.5 |
0 |
7 |
3.5 |
C |
2 |
5 |
3.5 |
3 |
9 |
6.0 |
df.groupby('key').aggregate({'data1':['min', np.median],
'data2':'max'})
|
data1 |
data2 |
|
min |
median |
max |
key |
|
|
|
A |
0 |
1.5 |
5 |
B |
1 |
2.5 |
7 |
C |
2 |
3.5 |
9 |
2)过滤
# 判断函数
# 参数x是一个组,即dataframe
def filter_func(x):
return x['data2'].std() > 4
df.groupby('key').std()
|
data1 |
data2 |
key |
|
|
A |
2.12132 |
1.414214 |
B |
2.12132 |
4.949747 |
C |
2.12132 |
4.242641 |
# 将过滤函数应用于每个lazy组中
# 不符合条件(False)的lazy组被筛掉。
df.groupby('key').filter(filter_func)
|
key |
data1 |
data2 |
1 |
B |
1 |
0 |
2 |
C |
2 |
3 |
4 |
B |
4 |
7 |
5 |
C |
5 |
9 |
3)转换
转换,形状与原来的输入是一样的。
df.groupby('key').transform(lambda x:x-x.mean())
|
data1 |
data2 |
0 |
-1.5 |
1.0 |
1 |
-1.5 |
-3.5 |
2 |
-1.5 |
-3.0 |
3 |
1.5 |
-1.0 |
4 |
1.5 |
3.5 |
5 |
1.5 |
3.0 |
4)apply方法
apply()
方法让你可以在每个组上应用任意方法:
# 定义函数
# 参数x是一个组,即dataframe
def norm_by_data2(x):
# x是一个分组数据的Dataframe
x['data1'] /= x['data2'].sum()
return x
df.groupby('key').apply(norm_by_data2)
|
key |
data1 |
data2 |
0 |
A |
0.000000 |
5 |
1 |
B |
0.142857 |
0 |
2 |
C |
0.166667 |
3 |
3 |
A |
0.375000 |
3 |
4 |
B |
0.571429 |
7 |
5 |
C |
0.416667 |
9 |
2.4 设置分割的键(不太会用得到)
df
|
key |
data1 |
data2 |
0 |
A |
0 |
5 |
1 |
B |
1 |
0 |
2 |
C |
2 |
3 |
3 |
A |
3 |
3 |
4 |
B |
4 |
7 |
5 |
C |
5 |
9 |
1)将列表、数组、Series或索引作为分组键
分组键的长度需要与df匹配。
# L作为新的一列,成为key列
L = [0,1,0,1,2,0]
df.groupby(L).sum()
|
data1 |
data2 |
0 |
7 |
17 |
1 |
4 |
3 |
2 |
4 |
7 |
2)用字典或Series将索引映射到分组名称
df2 = df.set_index('key')
df2
|
data1 |
data2 |
key |
|
|
A |
0 |
5 |
B |
1 |
0 |
C |
2 |
3 |
A |
3 |
3 |
B |
4 |
7 |
C |
5 |
9 |
mapping = {'A':'vowel', 'B':'consonant', 'C':'consonant'}
df2.groupby(mapping).sum()
|
data1 |
data2 |
key |
|
|
consonant |
12 |
19 |
vowel |
3 |
8 |
3)任意Python函数
df2.groupby(str.lower).mean()
|
data1 |
data2 |
key |
|
|
a |
1.5 |
4.0 |
b |
2.5 |
3.5 |
c |
3.5 |
6.0 |
4)多个有效键组成的列表
df2.groupby([str.lower, mapping]).mean()
|
|
data1 |
data2 |
key |
key |
|
|
a |
vowel |
1.5 |
4.0 |
b |
consonant |
2.5 |
3.5 |
c |
consonant |
3.5 |
6.0 |
df2.groupby([mapping, str.lower]).mean()
|
|
data1 |
data2 |
key |
key |
|
|
consonant |
b |
2.5 |
3.5 |
c |
3.5 |
6.0 |
vowel |
a |
1.5 |
4.0 |