简介
pandas中一类非常重要的操作是数据聚合与分组运算。通过groupby方法能够实现对数据集的拆分、统计、转换等操作,这个过程一气呵成。
在本文中,你将学到:
- 选取特定列分组;
- 对分组进行迭代;
- 通过字典或Series分组;
- 通过函数分组;
- 通过索引级别分组。
Groupby技术
一个完整的分组运算过程是:“split-apply-combine”(拆分-应用-合并)。首先将数据分组拆分,按照组别划分。这种分组可以针对行(axis=0)或列(axis=1);然后针对分好的组别应用操作函数(聚合、统计等);最后这些结果会合并到最终对象中。这个过程如下:
接下来我们一一介绍。
选取特定列分组
直接来看一个例子:
1 2 3 4 5 6 7 8 9 10 |
>>>import numpy as np >>>import pandas as pd >>>df = pd.DataFrame({'x': list('aabba'), 'y': ['one', 'two', 'one', 'two', 'one'], 'd1': np.random.randn(5), 'd2': np.random.randn(5)}) x y d1 d2 0 a one 0.053183 0.447796 1 a two -1.117457 -1.476354 2 b one -1.360028 -2.527450 3 b two 1.856344 0.432823 4 a one -0.134772 1.628732 |
我们有这样一个DataFrame,假设我们想通过 x 进行分组,并计算 d1 列的平均值,有这样两种方式:
1 2 3 4 5 6 7 8 |
# 先选中d1列,然后再按照x分组 >>>a = df['d1'].groupby(df['x']) >>>a <pandas.core.groupby.groupby.SeriesGroupBy object at 0x000001D724D14828> # 先分组,在选中d1列 >>>a = df.groupby('x')['d1'] <pandas.core.groupby.groupby.SeriesGroupBy object at 0x000001D724D14D30> |
可以看出这里的变量 a 是一个 GroupBy 对象,可以认为它是做好了分组的中间数据。接下来我们就可以通过 mean() 方法计算平均值:
1 2 3 4 5 |
>>>a.mean() x a -0.399682 b 0.248158 Name: d1, dtype: float64 |
结果表明,通过 x 分组后有 'a', 'b' 两组,最后得到两组的平均值。以上是通过一列进行分组,我们还可以指定多列分组:
1 2 3 4 5 6 7 8 |
>>>b = df.groupby(['x', 'y']).mean() >>>b d1 d2 x y a one -0.040794 1.038264 two -1.117457 -1.476354 b one -1.360028 -2.527450 two 1.856344 0.432823 |
在上面的例子中,我们使用的分组都是基于已经有的列名。事实上,我们也可以根据自定义的数组来分组,假如我有如下两个数组:
1 2 |
>>>name = np.array(['joe', 'lucy', 'lucy', 'joe', 'joe']) >>>year = np.array([2018, 2018, 2019, 2018, 2019]) |
根据数组分组:
1 2 3 4 5 6 |
>>>df['d1'].groupby([name, year]).mean() joe 2018 0.954764 2019 -0.134772 lucy 2018 -1.117457 2019 -1.360028 Name: d1, dtype: float64 |
GroupBy对象迭代
GroupBy对象也支持迭代,可以产生一个二元元组(由分组索引和数据集组成)。比如:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
>>>for k, j in a: print(k) print(j) a 0 0.053183 1 -1.117457 4 -0.134772 Name: d1, dtype: float64 b 2 -1.360028 3 1.856344 Name: d1, dtype: float64 |
如果是多个分组索引,二元元组的第一个元素就是分组索引组成的元组:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
>>>for (k1, k2), j in df.groupby(['x', 'y']): print(k1, k2) print(j) a one x y d1 d2 0 a one 0.053183 0.447796 4 a one -0.134772 1.628732 a two x y d1 d2 1 a two -1.117457 -1.476354 b one x y d1 d2 2 b one -1.360028 -2.52745 b two x y d1 d2 3 b two 1.856344 0.432823 |
既然知道groupby对象是一个二元元组,一个可能有用的操作技巧是将它转变成字典,然后就可以通过键访问对应的数据:
1 2 3 4 5 |
>>>c = dict(list(df.groupby(['x', 'y']))) >>>c[('a', 'one')] x y d1 d2 0 a one 0.053183 0.447796 4 a one -0.134772 1.628732 |
除了在行上进行分组,我们也可以在列上进行分组:
1 2 3 4 5 6 |
>>>df.dtypes x object y object d1 float64 d2 float64 dtype: object |
通过 dtypes 能够获取每列的数据类型,接着我们根据数据类型对列进行分组:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
>>>d = df.groupby(df.dtypes, axis=1) >>>for k, j in d: print(k) print(j) float64 d1 d2 0 0.053183 0.447796 1 -1.117457 -1.476354 2 -1.360028 -2.527450 3 1.856344 0.432823 4 -0.134772 1.628732 object x y 0 a one 1 a two 2 b one 3 b two 4 a one |
通过字典或Series分组
在前面就讲到可以根据自定义的数组进行分组,那么我们也可以传入一个字典或者Series映射现有的行或列,再通过自定义的数组
1 2 3 4 5 6 7 |
>>>df1 = pd.DataFrame(np.random.randn(4, 4), columns=['aa', 'bb', 'cc', 'dd'], index=list('abcd')) >>>df1 aa bb cc dd a -0.684296 -1.282066 0.582461 0.010453 b 1.160331 1.089198 -0.525495 0.613127 c 0.605130 1.214561 1.843957 -1.464937 d 0.997813 0.049371 -0.319833 -0.396488 |
建立一个字典映射:
1 2 3 4 5 |
>>>dict1 = {'a': 'joe', 'b': 'joe', 'c': 'lucy', 'd': 'joe', 'e': 'lucy'} >>>df1.groupby(dict1).mean() aa bb cc dd joe 0.491283 -0.047832 -0.087622 0.075697 lucy 0.605130 1.214561 1.843957 -1.464937 |
将现有的行索引按照 ['joe', 'lucy'] 来分组。
或者使用Series:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
>>>dict1_series = pd.Series(dict1) >>>dict1_series a joe b joe c lucy d joe e lucy dtype: object >>>df1.groupby(dict1_series).mean() aa bb cc dd joe 0.491283 -0.047832 -0.087622 0.075697 lucy 0.605130 1.214561 1.843957 -1.464937 |
通过函数进行分组
假设有这样地数据:
1 2 3 4 5 6 7 |
>>>df2 = df1.rename(index={'b':'bb', 'd':'dd'}) >>>df2 aa bb cc dd a -0.684296 -1.282066 0.582461 0.010453 bb 1.160331 1.089198 -0.525495 0.613127 c 0.605130 1.214561 1.843957 -1.464937 dd 0.997813 0.049371 -0.319833 -0.396488 |
我们想根据行索引字符串的长度进行分组,就可以直接调用 len 函数:
1 2 3 4 |
>>>df2.groupby(len).sum() aa bb cc dd 1 -0.079166 -0.067505 2.426418 -1.454484 2 2.158144 1.138569 -0.845328 0.216639 |
行索引1、2就表示行索引字符串长度为1和2的两个组。
我们还可以将函数和数组、列表、字典、Series混合使用:
1 2 3 4 5 6 7 8 |
>>>list1 = [2018, 2018, 2019, 2019] >>>df2.groupby([len, list1]).max() aa bb cc dd 1 2018 -0.684296 -1.282066 0.582461 0.010453 2019 0.605130 1.214561 1.843957 -1.464937 2 2018 1.160331 1.089198 -0.525495 0.613127 2019 0.997813 0.049371 -0.319833 -0.396488 |
通过索引级别分组
对于层次化索引分组,可以根据 level 参数选择索引级别进行分组。如果有下面这个层次化数据集:
1 2 3 4 5 6 7 8 |
>>>columns = pd.MultiIndex.from_arrays([['one', 'one', 'two', 'two'], ['a', 'c', 'a', 'b']], names=['key1', 'key2']) >>>df3 = pd.DataFrame(np.random.randn(3, 4), columns=columns) >>>df3 key1 one two key2 a c a b 0 0.379105 -0.467082 -0.196458 0.913196 1 -0.773172 0.058906 -0.733571 0.834142 2 -0.609070 0.916934 0.346457 -0.620837 |
level 参数可以传入级别名称或者编号:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
# 传入级别名称 >>>df3.groupby(level='key1', axis=1).mean() key1 one two 0 -0.043988 0.358369 1 -0.357133 0.050286 2 0.153932 -0.137190 # 传入级别编号 >>>df3.groupby(level=0, axis=1).mean() key1 one two 0 -0.043988 0.358369 1 -0.357133 0.050286 2 0.153932 -0.137190 |
设置了 axis=1 ,表示在列上进行分组从外到内的级别编号依次0、1…
总结
再本文中初步介绍了groupby方法的使用,可以根据不同的方式(字典、Series、数组、函数、索引级别)选择分组索引进行分组。在后面的文章中我们还会详细介绍传入函数的应用,因为这在实际工作中使用较多。