pandas教程:GroupBy Mechanics 分组机制

文章目录

  • Chapter 10 Data Aggregation and Group Operations(数据汇总和组操作)
  • 10.1 GroupBy Mechanics(分组机制)
  • 1 Iterating Over Groups(对组进行迭代)
  • 2 Selecting a Column or Subset of Columns (选中一列,或列的子集)
  • 3 Grouping with Dicts and Series(用Dicts与Series进行分组)
  • 4 Grouping with Functions(用函数进行分组)
  • 5 Grouping by Index Levels (按索引层级来分组)

Chapter 10 Data Aggregation and Group Operations(数据汇总和组操作)

这一章的内容:

  • 把一个pandas对象(seriesDataFrame)按key分解为多个
  • 计算组的汇总统计值(group summary statistics),比如计数,平均值,标准差,或用户自己定义的函数
  • 应用组内的转换或其他一些操作,比如标准化,线性回归,排序,子集选择
  • 计算透视表和交叉列表
  • 进行分位数分析和其他一些统计组分析

10.1 GroupBy Mechanics(分组机制)

Hadley Wickham,是很多R语言有名库的作者,他描述group operation(组操作)为split-apply-combine(分割-应用-结合)。第一个阶段,存储于seriesDataFrame中的数据,根据不同的keys会被split(分割)为多个组。而且分割的操作是在一个特定的axis(轴)上。例如,DataFrame能按行(axis=0)或列(axis=1)来分组。之后,我们可以把函数apply(应用)在每一个组上,产生一个新的值。最后,所以函数产生的结果被combine(结合)为一个结果对象(result object)。下面是一个图示:

每一个用于分组的key能有很多形式,而且keys也不必都是一种类型:

  • 含有值的listarray的长度,与按axis分组后的长度是一样的
  • 值的名字指明的是DataFrame中的列名
  • 一个dictSeries,给出一个对应关系,用于对应按轴分组后的值与组的名字
  • 能在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(组)进行一些操作。例如,通过调用GroupBymean方法,我们可以计算每个组的平均值:

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现在有一个多层级索引,这个多层索引是根据key1key2不同的值来构建的:

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的列中有缺失值的话,也不会出现在结果中。

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']
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

2 Selecting a Column or Subset of Columns (选中一列,或列的子集)

如果一个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

3 Grouping with Dicts and Series(用Dicts与Series进行分组)

分组信息可以不是数组的形式。考虑下面的例子:

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

4 Grouping with Functions(用函数进行分组)

比起用dictseries定义映射关系,使用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

5 Grouping by Index Levels (按索引层级来分组)

最后关于多层级索引数据集(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

你可能感兴趣的:(pandas使用教程,pandas,机器学习,人工智能,numpy,nlp,lda,python)