这一章的内容:
pandas
对象(series
或DataFrame
)按key
分解为多个group summary statistics
),比如计数,平均值,标准差,或用户自己定义的函数Hadley Wickham
,是很多R语言有名库的作者,他描述group operation
(组操作)为split-apply-combine
(分割-应用-结合)。第一个阶段,存储于series
或DataFrame
中的数据,根据不同的keys
会被split
(分割)为多个组。而且分割的操作是在一个特定的axis
(轴)上。例如,DataFrame
能按行(axis=0
)或列(axis=1
)来分组。之后,我们可以把函数apply
(应用)在每一个组上,产生一个新的值。最后,所以函数产生的结果被combine
(结合)为一个结果对象(result object
)。下面是一个图示:
每一个用于分组的key
能有很多形式,而且keys
也不必都是一种类型:
list
或array
的长度,与按axis
分组后的长度是一样的DataFrame
中的列名dict
或Series
,给出一个对应关系,用于对应按轴分组后的值与组的名字axis index
(轴索引)上被调用的函数,或index
上的labels
(标签)注意后面三种方法都是用于产生一个数组的快捷方式,而这个数组责备用来分割对象(split up the object
)。不用担心这些很抽象,这一章会有很多例子来帮助我们理解这些方法。先从一个例子来开始吧,这里有一个用DataFrame
表示的表格型数据集:
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.364533 | 0.633262 | a | one |
1 | 1.353368 | 0.361008 | a | two |
2 | 0.253311 | -1.107940 | b | one |
3 | -1.513444 | -1.038035 | b | two |
4 | -0.920317 | 2.037712 | a | one |
假设我们想要,通过使用key1
作为labels
,来计算data1
列的平均值。有很多方法可以做到这点,一种是访问data1
,并且使用列(a series
)在key1
上,调用groupby
。(译者:其实就是按key1
来进行分组,但只保留data1
这一列):
grouped = df['data1'].groupby(df['key1'])
grouped
这个grouped
变量是一个GroupBy object
(分组对象)。实际上现在还没有进行任何计算,除了调用group key
(分组键)df['key1']
时产生的一些中间数据。整个方法是这样的,这个GroupBy object
(分组对象)已经有了我们想要的信息,现在需要的是对于每一个group
(组)进行一些操作。例如,通过调用GroupBy
的mean
方法,我们可以计算每个组的平均值:
grouped.mean()
key1
a 0.599194
b -0.630067
Name: data1, dtype: float64
之后我们会对于调用.mean()
后究竟发生了什么进行更详细的解释。重要的是,我们通过group key
(分组键)对数据(a series
)进行了聚合,这产生了一个新的Series
,而且这个series
的索引是key1
列中不同的值。
得到的结果中,index
(索引)也有’key1
’,因为我们使用了df['key1']
。
如果我们传入多个数组作为一个list,那么我们会得到不同的东西:
means = df['data1'].groupby([df['key1'], df['key2']]).mean()
means
key1 key2
a one 0.222108
two 1.353368
b one 0.253311
two -1.513444
Name: data1, dtype: float64
这里我们用了两个key
来分组,得到的结果series
现在有一个多层级索引,这个多层索引是根据key1
和key2
不同的值来构建的:
means.unstack()
key2 | one | two |
---|---|---|
key1 | ||
a | 0.222108 | 1.353368 |
b | 0.253311 | -1.513444 |
在上面的例子里,group key
全都是series
,即DataFrame
中的一列,当然,group key
只要长度正确,可以是任意的数组:
states = np.array(['Ohio', 'California', 'California', 'Ohio', 'Ohio'])
years = np.array([2005, 2005, 2006, 2005, 2006])
df['data1'].groupby([states, years]).mean()
California 2005 1.353368
2006 0.253311
Ohio 2005 -0.074456
2006 -0.920317
Name: data1, dtype: float64
df['data1'].groupby([states, years])
df['data1']
0 1.364533
1 1.353368
2 0.253311
3 -1.513444
4 -0.920317
Name: data1, dtype: float64
df
data1 | data2 | key1 | key2 | |
---|---|---|---|---|
0 | 1.364533 | 0.633262 | a | one |
1 | 1.353368 | 0.361008 | a | two |
2 | 0.253311 | -1.107940 | b | one |
3 | -1.513444 | -1.038035 | b | two |
4 | -0.920317 | 2.037712 | a | one |
其中分组信息经常就在我们处理的DataFrame
中,在这种情况下,我们可以传入列名(可以是字符串,数字,或其他python
对象)作为group keys
:
df.groupby('key1').mean()
data1 | data2 | |
---|---|---|
key1 | ||
a | 0.599194 | 1.010661 |
b | -0.630067 | -1.072987 |
df.groupby(['key1', 'key2']).mean()
data1 | data2 | ||
---|---|---|---|
key1 | key2 | ||
a | one | 0.222108 | 1.335487 |
two | 1.353368 | 0.361008 | |
b | one | 0.253311 | -1.107940 |
two | -1.513444 | -1.038035 |
我们注意到第一个例子里,df.groupby('key1').mean()
的结果里并没有key2
这一列。因为df['key2']
这一列不是数值型数据,我们称这种列为nuisance column
(有碍列),这种列不会出现在结果中。默认,所有的数值型列都会被汇总计算,但是出现有碍列的情况的话,就会过滤掉这种列。
一个很有用的GroupBy
方法是size
,会返回一个包含group size
(组大小)的series
:
df.groupby(['key1', 'key2']).size()
key1 key2
a one 2
two 1
b one 1
two 1
dtype: int64
另外一点需要注意的是,如果作为group key
的列中有缺失值的话,也不会出现在结果中。
GroupBy
对象支持迭代,能产生一个2-tuple
(二元元组),包含组名和对应的数据块。考虑下面的情况:
for name, group in df.groupby('key1'):
print(name)
print(group)
a
data1 data2 key1 key2
0 1.364533 0.633262 a one
1 1.353368 0.361008 a two
4 -0.920317 2.037712 a one
b
data1 data2 key1 key2
2 0.253311 -1.107940 b one
3 -1.513444 -1.038035 b two
对于有多个key
的情况,元组中的第一个元素会被作为另一个元组的key
值
for (k1, k2), group in df.groupby(['key1', 'key2']):
print((k1, k2))
print(group)
('a', 'one')
data1 data2 key1 key2
0 1.364533 0.633262 a one
4 -0.920317 2.037712 a one
('a', 'two')
data1 data2 key1 key2
1 1.353368 0.361008 a two
('b', 'one')
data1 data2 key1 key2
2 0.253311 -1.10794 b one
('b', 'two')
data1 data2 key1 key2
3 -1.513444 -1.038035 b two
当然,也可以对数据的一部分进行各种操作。一个便利的用法是,用一个含有数据片段(data pieces
)的dict
来作为单行指令(one-liner)
:
pieces = dict(list(df.groupby('key1')))
pieces
{'a': data1 data2 key1 key2
0 1.364533 0.633262 a one
1 1.353368 0.361008 a two
4 -0.920317 2.037712 a one, 'b': data1 data2 key1 key2
2 0.253311 -1.107940 b one
3 -1.513444 -1.038035 b two}
pieces['b']
data1 | data2 | key1 | key2 | |
---|---|---|---|---|
2 | 0.253311 | -1.107940 | b | one |
3 | -1.513444 | -1.038035 | b | two |
groupby
默认作用于axis=0
,但是我们可以指定任意的轴。例如,我们可以按dtype
来对列进行分组:
df.dtypes
data1 float64
data2 float64
key1 object
key2 object
dtype: object
grouped = df.groupby(df.dtypes, axis=1)
for dtype, group in grouped:
print(dtype)
print(group)
float64
data1 data2
0 1.364533 0.633262
1 1.353368 0.361008
2 0.253311 -1.107940
3 -1.513444 -1.038035
4 -0.920317 2.037712
object
key1 key2
0 a one
1 a two
2 b one
3 b two
4 a one
如果一个GroupBy
对象是由DataFrame
创建来的,那么通过列名或一个包含列名的数组来对GroupBy
对象进行索引的话,就相当于对列取子集做聚合(column subsetting for aggregation
)。这句话的意思是:
df.groupby('key1')['data1']
df.groupby('key1')[['data2']]
上面的代码其实就是下面的语法糖(Syntactic sugar):
df['data1'].groupby(df['key1'])
df[['data2']].groupby(df['key1'])
语法糖(Syntactic sugar),是由Peter J. Landin(和图灵一样的天才人物,是他最先发现了Lambda演算,由此而创立了函数式编程)创造的一个词语,它意指那些没有给计算机语言添加新功能,而只是对人类来说更“甜蜜”的语法。语法糖往往给程序员提供了更实用的编码方式,有益于更好的编码风格,更易读。不过其并没有给语言添加什么新东西。
尤其是对于一些很大的数据集,这种用法可以聚集一部分列。例如,在处理一个数据集的时候,想要只计算data2
列的平均值,并将结果返还为一个DataFrame
,我们可以这样写:
df
data1 | data2 | key1 | key2 | |
---|---|---|---|---|
0 | 1.364533 | 0.633262 | a | one |
1 | 1.353368 | 0.361008 | a | two |
2 | 0.253311 | -1.107940 | b | one |
3 | -1.513444 | -1.038035 | b | two |
4 | -0.920317 | 2.037712 | a | one |
df.groupby(['key1', 'key2'])[['data2']].mean()
data2 | ||
---|---|---|
key1 | key2 | |
a | one | 1.335487 |
two | 0.361008 | |
b | one | -1.107940 |
two | -1.038035 |
如果一个list
或一个数组被传入,返回的对象是一个分组后的DataFrame
,如果传入的只是单独一个列名,那么返回的是一个分组后的grouped
:
s_grouped = df.groupby(['key1', 'key2'])['data2']
s_grouped
s_grouped.mean()
key1 key2
a one 1.335487
two 0.361008
b one -1.107940
two -1.038035
Name: data2, dtype: float64
分组信息可以不是数组的形式。考虑下面的例子:
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 # Add a few NA values
people
a | b | c | d | e | |
---|---|---|---|---|---|
Joe | 1.358054 | -0.124378 | 0.159913 | -0.006129 | -1.116065 |
Steve | 0.926572 | -0.281652 | -0.586583 | -0.266538 | -0.216959 |
Wes | 0.277803 | NaN | NaN | 0.820144 | -0.002076 |
Jim | 1.623214 | 0.109414 | 2.967603 | 0.075661 | 1.085864 |
Travis | -0.578750 | 1.252605 | 0.757412 | 0.352343 | -1.342396 |
假设我们有一个组,对应多个列,而且我们想要按组把这些列的和计算出来:
mapping = {'a': 'red', 'b': 'red', 'c': 'blue',
'd': 'blue', 'e': 'red', 'f': 'orange'}
现在,我们可以通过这个dict
构建一个数组,然后传递给groupby
,但其实我们可以直接传入dict
(可以注意到key
里有一个'f'
,这说明即使有,没有被用到的group key
,也是ok
的):
by_column = people.groupby(mapping, axis=1)
by_column.sum()
blue | red | |
---|---|---|
Joe | 0.153784 | 0.117611 |
Steve | -0.853121 | 0.427961 |
Wes | 0.820144 | 0.275727 |
Jim | 3.043264 | 2.818492 |
Travis | 1.109754 | -0.668541 |
这种用法同样适用于series
,这种情况可以看作是固定大小的映射(fixed-size mapping
):
map_series = pd.Series(mapping)
map_series
a red
b red
c blue
d blue
e red
f orange
dtype: object
people.groupby(map_series, axis=1).count()
blue | red | |
---|---|---|
Joe | 2 | 3 |
Steve | 2 | 3 |
Wes | 1 | 2 |
Jim | 2 | 3 |
Travis | 2 | 3 |
比起用dict
或series
定义映射关系,使用python
的函数是更通用的方法。任何一个作为group key
的函数,在每一个index value
(索引值)上都会被调用一次,函数计算的结果在返回的结果中会被用做group name
。更具体一点,考虑前一个部分的DataFrame
,用人的名字作为索引值。假设我们想要按照名字的长度来分组;同时我们要计算字符串的长度,使用len
函数会变得非常简单:
people.groupby(len).sum() # len函数在每一个index(即名字)上被调用了
a | b | c | d | e | |
---|---|---|---|---|---|
3 | 3.259071 | -0.014964 | 3.127516 | 0.889676 | -0.032277 |
5 | 0.926572 | -0.281652 | -0.586583 | -0.266538 | -0.216959 |
6 | -0.578750 | 1.252605 | 0.757412 | 0.352343 | -1.342396 |
混合不同的函数、数组,字典或series
都不成问题,因为所有对象都会被转换为数组:
key_list = ['one', 'one', 'one', 'two', 'two']
people.groupby([len, key_list]).min()
a | b | c | d | e | ||
---|---|---|---|---|---|---|
3 | one | 0.277803 | -0.124378 | 0.159913 | -0.006129 | -1.116065 |
two | 1.623214 | 0.109414 | 2.967603 | 0.075661 | 1.085864 | |
5 | one | 0.926572 | -0.281652 | -0.586583 | -0.266538 | -0.216959 |
6 | two | -0.578750 | 1.252605 | 0.757412 | 0.352343 | -1.342396 |
最后关于多层级索引数据集(hierarchically indexed dataset
),一个很方便的用时是在聚集(aggregate
)的时候,使用轴索引的层级(One of the levels of an axis index
)。看下面的例子:
columns = pd.MultiIndex.from_arrays([['US', 'US', 'US', 'JP', 'JP'],
[1, 3, 5, 1, 3]],
names=['cty', 'tenor'])
columns
MultiIndex(levels=[['JP', 'US'], [1, 3, 5]],
labels=[[1, 1, 1, 0, 0], [0, 1, 2, 0, 1]],
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.898073 | 0.156686 | -0.151011 | 0.423881 | 0.336215 |
1 | 0.736301 | 0.901515 | 0.081655 | 0.450248 | -0.031245 |
2 | -1.619125 | -1.041775 | 0.129422 | 1.222881 | -0.717410 |
3 | 0.998536 | -1.373455 | 1.724266 | -2.084529 | 0.535651 |
要想按层级分组,传入层级的数字或者名字,通过使用level
关键字:
hier_df.groupby(level='cty', axis=1).count()
cty | JP | US |
---|---|---|
0 | 2 | 3 |
1 | 2 | 3 |
2 | 2 | 3 |
3 | 2 | 3 |