这一章的内容:
Hadley Wickham,是很多R语言有名库的作者,他描述group operation(组操作)为split-apply-combine(分割-应用-结合)。第一个阶段,存储于series或DataFrame中的数据,根据不同的keys会被split(分割)为多个组。而且分割的操作是在一个特定的axis(轴)上。例如,DataFrame能按行(axis=0)或列(axis=1)来分组。之后,我们可以把函数apply(应用)在每一个组上,产生一个新的值。最后,所以函数产生的结果被combine(结合)为一个结果对象(result object)。下面是一个图示:
每一个用于分组的key能有很多形式,而且keys也不必都是一种类型:
注意后面三种方法都是用于产生一个数组的快捷方式,而这个数组责备用来分割对象(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
假设我们想要,通过使用key1作为labels,来计算data1列的平均值。有很多方法可以做到这点,一种是访问data1,并且使用列(a series)在key1上,调用groupby。(译者:其实就是按key1来进行分组,但只保留data1这一列):
grouped = df['data1'].groupby(df['key1'])
grouped
<pandas.core.groupby.SeriesGroupBy object at 0x111db3710>
这个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()
在上面的例子里,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])
<pandas.core.groupby.SeriesGroupBy object at 0x112530e48>
df['data1']
0 1.364533
1 1.353368
2 0.253311
3 -1.513444
4 -0.920317
Name: data1, dtype: float64
df
其中分组信息经常就在我们处理的DataFrame中,在这种情况下,我们可以传入列名(可以是字符串,数字,或其他python对象)作为group keys:
df.groupby('key1').mean()
df.groupby(['key1', 'key2']).mean()
我们注意到第一个例子里,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的列中有缺失值的话,也不会出现在结果中。
1 Iterating Over Groups(对组进行迭代)
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']
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
df.groupby(['key1', 'key2'])[['data2']].mean()
data2
如果一个list或一个数组被传入,返回的对象是一个分组后的DataFrame,如果传入的只是单独一个列名,那么返回的是一个分组后的grouped:
s_grouped = df.groupby(['key1', 'key2'])['data2']
s_grouped
<pandas.core.groupby.SeriesGroupBy object at 0x1125309e8>
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
假设我们有一个组,对应多个列,而且我们想要按组把这些列的和计算出来:
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()
这种用法同样适用于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()
比起用dict或series定义映射关系,使用python的函数是更通用的方法。任何一个作为group key的函数,在每一个index value(索引值)上都会被调用一次,函数计算的结果在返回的结果中会被用做group name。更具体一点,考虑前一个部分的DataFrame,用人的名字作为索引值。假设我们想要按照名字的长度来分组;同时我们要计算字符串的长度,使用len函数会变得非常简单:
people.groupby(len).sum() # len函数在每一个index(即名字)上被调用了
混合不同的函数、数组,字典或series都不成问题,因为所有对象都会被转换为数组:
key_list = ['one', 'one', 'one', 'two', 'two']
people.groupby([len, key_list]).min()
最后关于多层级索引数据集(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
要想按层级分组,传入层级的数字或者名字,通过使用level关键字:
hier_df.groupby(level='cty', axis=1).count()
阅读量:1769
点赞量:0
收藏量:0