Group By: split-apply-combine

“分组”是指涉及一个或多个以下步骤的过程

  • 根据某些条件将数据拆分成组
  • 对每个组独立应用功能
  • 结果合并到一个数据结构中

其中,分离步骤是最直接的。事实上,在许多情况下,您可能希望将数据集拆分成组,并自己对这些组执行某些操作。在应用步骤中,我们可能希望以下之一:

  • 汇总:计算有关每个组的汇总统计量(或统计量)。一些例子:

    • 计算组和或平均值
    • 计算组大小/计数
  • 转换:执行一些特定于组的计算并返回类似索引。一些例子:

    • 标准化组内的数据(zscore)
    • 在组内填充具有从每个组派生的值的NA
  • 过滤:根据评估True或False的按组计算,丢弃一些组。一些例子:

    • 丢弃属于只有少数成员的组的数据
    • 基于组总和或平均值过滤数据
  • 上述的一些组合:GroupBy将检查应用步骤的结果,并且如果不适合上述两个类别中的任一个,则尝试返回明智的组合结果

由于在pandas数据结构上的对象实例方法的集合通常是丰富和表达的,所以我们通常只是想调用每个组上的DataFrame函数。对于使用基于SQL的工具(或itertools)的人来说,GroupBy的名称应该相当熟悉,您可以在其中编写代码:

SELECT Column1, Column2, mean(Column3), sum(Column4)
FROM SomeTable
GROUP BY Column1, Column2

我们的目标是使这样的操作自然,容易使用Panda表达。我们将讨论GroupBy功能的每个领域,然后提供一些非平凡的例子/用例。

有关某些高级策略,请参阅cookbook

Splitting an object into groups

熊猫对象可以在任何轴上分割。分组的抽象定义是提供标签到组名称的映射。要创建GroupBy对象(更多关于GroupBy对象的更多信息),请执行以下操作:

>>> grouped = obj.groupby(key)
>>> grouped = obj.groupby(key, axis=1)
>>> grouped = obj.groupby([key1, key2])

可以以许多不同的方式指定映射:

  • 一个Python函数,要在每个轴标签上调用
  • 与所选轴长度相同的列表或NumPy数组
  • 提供标签 - > 名称
  • 对于DataFrame对象,指示要用于分组的列的字符串。当然df.groupby('A')只是df.groupby(df['A'])的语法糖,但它使生活更简单
  • 任何上述事情的列表

我们将分组对象称为例如,考虑以下DataFrame:

In [1]: df = pd.DataFrame({'A' : ['foo', 'bar', 'foo', 'bar',
   ...:                           'foo', 'bar', 'foo', 'foo'],
   ...:                    'B' : ['one', 'one', 'two', 'three',
   ...:                           'two', 'two', 'one', 'three'],
   ...:                    'C' : np.random.randn(8),
   ...:                    'D' : np.random.randn(8)})
   ...: 

In [2]: df
Out[2]: 
     A      B         C         D
0  foo    one  0.469112 -0.861849
1  bar    one -0.282863 -2.104569
2  foo    two -1.509059 -0.494929
3  bar  three -1.135632  1.071804
4  foo    two  1.212112  0.721555
5  bar    two -0.173215 -0.706771
6  foo    one  0.119209 -1.039575
7  foo  three -1.044236  0.271860

我们可以通过AB列或两者自然分组:

In [3]: grouped = df.groupby('A')

In [4]: grouped = df.groupby(['A', 'B'])

这些将在其索引(行)上拆分DataFrame。我们还可以按列分割:

In [5]: def get_letter_type(letter):
   ...:     if letter.lower() in 'aeiou':
   ...:         return 'vowel'
   ...:     else:
   ...:         return 'consonant'
   ...: 

In [6]: grouped = df.groupby(get_letter_type, axis=1)

从0.8开始,pandas Index对象现在支持重复值。如果在groupby操作中将非唯一索引用作组键,则同一索引值的所有值将被视为在一个组中,因此聚合函数的输出将仅包含唯一索引值:

In [7]: lst = [1, 2, 3, 1, 2, 3]

In [8]: s = pd.Series([1, 2, 3, 10, 20, 30], lst)

In [9]: grouped = s.groupby(level=0)

In [10]: grouped.first()
Out[10]: 
1    1
2    2
3    3
dtype: int64

In [11]: grouped.last()
Out[11]: 
1    10
2    20
3    30
dtype: int64

In [12]: grouped.sum()
Out[12]: 
1    11
2    22
3    33
dtype: int64

请注意,不发生分裂,直到需要。创建GroupBy对象只会验证您是否通过了有效的映射。

注意

许多种类的复杂数据操作可以用GroupBy操作来表示(尽管不能保证是最有效的)。您可以使用标签映射函数获得相当的创意。

GroupBy sorting

默认情况下,组密钥在groupby操作期间排序。但是,您可以通过sort=False来获得潜在的加速:

In [13]: df2 = pd.DataFrame({'X' : ['B', 'B', 'A', 'A'], 'Y' : [1, 2, 3, 4]})

In [14]: df2.groupby(['X']).sum()
Out[14]: 
   Y
X   
A  7
B  3

In [15]: df2.groupby(['X'], sort=False).sum()
Out[15]: 
   Y
X   
B  3
A  7

Note that groupby will preserve the order in which observations are sorted within each group. 例如,以下由groupby()创建的组按照它们在原始DataFrame中显示的顺序:

In [16]: df3 = pd.DataFrame({'X' : ['A', 'B', 'A', 'B'], 'Y' : [1, 4, 3, 2]})

In [17]: df3.groupby(['X']).get_group('A')
Out[17]: 
   X  Y
0  A  1
2  A  3

In [18]: df3.groupby(['X']).get_group('B')
Out[18]: 
   X  Y
1  B  4
3  B  2

GroupBy object attributes

groups属性是dict,其键是计算的唯一组,对应的值是属于每个组的轴标签。在上面的例子中,我们有:

In [19]: df.groupby('A').groups
Out[19]: 
{'bar': Int64Index([1, 3, 5], dtype='int64'),
 'foo': Int64Index([0, 2, 4, 6, 7], dtype='int64')}

In [20]: df.groupby(get_letter_type, axis=1).groups
Out[20]: 
{'consonant': Index([u'B', u'C', u'D'], dtype='object'),
 'vowel': Index([u'A'], dtype='object')}

调用GroupBy对象上的标准Python len函数只返回groups dict的长度,因此很大程度上只是一个方便:

In [21]: grouped = df.groupby(['A', 'B'])

In [22]: grouped.groups
Out[22]: 
{('bar', 'one'): Int64Index([1], dtype='int64'),
 ('bar', 'three'): Int64Index([3], dtype='int64'),
 ('bar', 'two'): Int64Index([5], dtype='int64'),
 ('foo', 'one'): Int64Index([0, 6], dtype='int64'),
 ('foo', 'three'): Int64Index([7], dtype='int64'),
 ('foo', 'two'): Int64Index([2, 4], dtype='int64')}

In [23]: len(grouped)
Out[23]: 6

GroupBy将标签完成列名称(和其他属性)

In [24]: df
Out[24]: 
            gender     height      weight
2000-01-01    male  42.849980  157.500553
2000-01-02    male  49.607315  177.340407
2000-01-03    male  56.293531  171.524640
2000-01-04  female  48.421077  144.251986
2000-01-05    male  46.556882  152.526206
2000-01-06  female  68.448851  168.272968
2000-01-07    male  70.757698  136.431469
2000-01-08  female  58.909500  176.499753
2000-01-09  female  76.435631  174.094104
2000-01-10    male  45.306120  177.540920

In [25]: gb = df.groupby('gender')
In [26]: gb.<TAB>
gb.agg        gb.boxplot    gb.cummin     gb.describe   gb.filter     gb.get_group  gb.height     gb.last       gb.median     gb.ngroups    gb.plot       gb.rank       gb.std        gb.transform
gb.aggregate  gb.count      gb.cumprod    gb.dtype      gb.first      gb.groups     gb.hist       gb.max        gb.min        gb.nth        gb.prod       gb.resample   gb.sum        gb.var
gb.apply      gb.cummax     gb.cumsum     gb.fillna     gb.gender     gb.head       gb.indices    gb.mean       gb.name       gb.ohlc       gb.quantile   gb.size       gb.tail       gb.weight

GroupBy with MultiIndex

使用hierarchically-indexed data,按层次结构的一个级别分组是很自然的。

让我们创建一个具有两级MultiIndex的系列。

In [27]: arrays = [['bar', 'bar', 'baz', 'baz', 'foo', 'foo', 'qux', 'qux'],
   ....:           ['one', 'two', 'one', 'two', 'one', 'two', 'one', 'two']]
   ....: 

In [28]: index = pd.MultiIndex.from_arrays(arrays, names=['first', 'second'])

In [29]: s = pd.Series(np.random.randn(8), index=index)

In [30]: s
Out[30]: 
first  second
bar    one      -0.575247
       two       0.254161
baz    one      -1.143704
       two       0.215897
foo    one       1.193555
       two      -0.077118
qux    one      -0.408530
       two      -0.862495
dtype: float64

然后,我们可以按s中的一个级别分组。

In [31]: grouped = s.groupby(level=0)

In [32]: grouped.sum()
Out[32]: 
first
bar   -0.321085
baz   -0.927807
foo    1.116437
qux   -1.271025
dtype: float64

如果MultiIndex具有指定的名称,则可以传递这些名称,而不是级别号码:

In [33]: s.groupby(level='second').sum()
Out[33]: 
second
one   -0.933926
two   -0.469555
dtype: float64

聚合函数(如sum)将直接采用级别参数。此外,生成的索引将根据所选级别命名:

In [34]: s.sum(level='second')
Out[34]: 
second
one   -0.933926
two   -0.469555
dtype: float64

同样从v0.6,支持与多个级别的分组。

In [35]: s
Out[35]: 
first  second  third
bar    doo     one      1.346061
               two      1.511763
baz    bee     one      1.627081
               two     -0.990582
foo    bop     one     -0.441652
               two      1.211526
qux    bop     one      0.268520
               two      0.024580
dtype: float64

In [36]: s.groupby(level=['first', 'second']).sum()
Out[36]: 
first  second
bar    doo       2.857824
baz    bee       0.636499
foo    bop       0.769873
qux    bop       0.293100
dtype: float64

稍后关于sum函数和聚合的更多信息。

DataFrame column selection in GroupBy

例如,从DataFrame创建GroupBy对象后,您可能需要对每个列执行不同的操作。因此,使用[]类似于从DataFrame获取列,您可以:

In [37]: grouped = df.groupby(['A'])

In [38]: grouped_C = grouped['C']

In [39]: grouped_D = grouped['D']

这主要是语法糖替代和更冗长:

In [40]: df['C'].groupby(df['A'])
Out[40]: <pandas.core.groupby.SeriesGroupBy object at 0x7ff26f58b810>

此外,此方法避免重新计算从传递的密钥导出的内部分组信息。

Iterating through groups

使用GroupBy对象,迭代分组数据是非常自然的,其功能类似于itertools.groupby

In [41]: grouped = df.groupby('A')

In [42]: for name, group in grouped:
   ....:        print(name)
   ....:        print(group)
   ....: 
bar
     A      B         C         D
1  bar    one -0.042379 -0.089329
3  bar  three -0.009920 -0.945867
5  bar    two  0.495767  1.956030
foo
     A      B         C         D
0  foo    one -0.919854 -1.131345
2  foo    two  1.247642  0.337863
4  foo    two  0.290213 -0.932132
6  foo    one  0.362949  0.017587
7  foo  three  1.548106 -0.016692

在通过多个键进行分组的情况下,组名称将是元组:

In [43]: for name, group in df.groupby(['A', 'B']):
   ....:        print(name)
   ....:        print(group)
   ....: 
('bar', 'one')
     A    B         C         D
1  bar  one -0.042379 -0.089329
('bar', 'three')
     A      B        C         D
3  bar  three -0.00992 -0.945867
('bar', 'two')
     A    B         C        D
5  bar  two  0.495767  1.95603
('foo', 'one')
     A    B         C         D
0  foo  one -0.919854 -1.131345
6  foo  one  0.362949  0.017587
('foo', 'three')
     A      B         C         D
7  foo  three  1.548106 -0.016692
('foo', 'two')
     A    B         C         D
2  foo  two  1.247642  0.337863
4  foo  two  0.290213 -0.932132

它是标准的Python-fu但是记住你可以在for循环语句中解压缩元组: (k1, k2) t3> group in 分组:

Selecting a group

可以使用GroupBy.get_group()选择单个组:

In [44]: grouped.get_group('bar')
Out[44]: 
     A      B         C         D
1  bar    one -0.042379 -0.089329
3  bar  three -0.009920 -0.945867
5  bar    two  0.495767  1.956030

或者对于在多个列上分组的对象:

In [45]: df.groupby(['A', 'B']).get_group(('bar', 'one'))
Out[45]: 
     A    B         C         D
1  bar  one -0.042379 -0.089329

Aggregation

一旦GroupBy对象被创建,几种方法可用于对分组的数据执行计算。

一个明显的是通过aggregate或等效地agg方法的聚合:

In [46]: grouped = df.groupby('A')

In [47]: grouped.aggregate(np.sum)
Out[47]: 
            C         D
A                      
bar  0.443469  0.920834
foo  2.529056 -1.724719

In [48]: grouped = df.groupby(['A', 'B'])

In [49]: grouped.aggregate(np.sum)
Out[49]: 
                  C         D
A   B                        
bar one   -0.042379 -0.089329
    three -0.009920 -0.945867
    two    0.495767  1.956030
foo one   -0.556905 -1.113758
    three  1.548106 -0.016692
    two    1.537855 -0.594269

如您所见,聚合的结果将组名称作为分组轴上的新索引。在多个键的情况下,默认情况下,结果为MultiIndex,但可以使用as_index选项更改:

In [50]: grouped = df.groupby(['A', 'B'], as_index=False)

In [51]: grouped.aggregate(np.sum)
Out[51]: 
     A      B         C         D
0  bar    one -0.042379 -0.089329
1  bar  three -0.009920 -0.945867
2  bar    two  0.495767  1.956030
3  foo    one -0.556905 -1.113758
4  foo  three  1.548106 -0.016692
5  foo    two  1.537855 -0.594269

In [52]: df.groupby('A', as_index=False).sum()
Out[52]: 
     A         C         D
0  bar  0.443469  0.920834
1  foo  2.529056 -1.724719

请注意,您可以使用reset_index DataFrame函数来实现与列名存储在结果MultiIndex中相同的结果:

In [53]: df.groupby(['A', 'B']).sum().reset_index()
Out[53]: 
     A      B         C         D
0  bar    one -0.042379 -0.089329
1  bar  three -0.009920 -0.945867
2  bar    two  0.495767  1.956030
3  foo    one -0.556905 -1.113758
4  foo  three  1.548106 -0.016692
5  foo    two  1.537855 -0.594269

另一个简单的聚合示例是计算每个组的大小。这作为size方法包含在GroupBy中。它返回一个系列,其索引是组名称,其值是每个组的大小。

In [54]: grouped.size()
Out[54]: 
A    B    
bar  one      1
     three    1
     two      1
foo  one      2
     three    1
     two      2
dtype: int64
In [55]: grouped.describe()
Out[55]: 
                C         D
0 count  1.000000  1.000000
  mean  -0.042379 -0.089329
  std         NaN       NaN
  min   -0.042379 -0.089329
  25%   -0.042379 -0.089329
  50%   -0.042379 -0.089329
  75%   -0.042379 -0.089329
...           ...       ...
5 mean   0.768928 -0.297134
  std    0.677005  0.898022
  min    0.290213 -0.932132
  25%    0.529570 -0.614633
  50%    0.768928 -0.297134
  75%    1.008285  0.020364
  max    1.247642  0.337863

[48 rows x 2 columns]

注意

如果as_index=True(默认值),聚合函数不会返回您聚合的组,如果他们被命名为分组的列将是返回对象的indices

传递as_index=False 返回您要聚合的组(如果它们命名为)。

聚合函数是减少返回对象的维度的函数,例如:mean, sum, size, count , std, var, sem, describe, last, nth, min, max这是当你做例如DataFrame.sum()并得到一个Series时会发生什么。

nth可以用作减速器过滤器,请参阅here

Applying multiple functions at once

使用分组系列,您还可以传递函数的列表或字典以进行聚合,输出DataFrame:

In [56]: grouped = df.groupby('A')

In [57]: grouped['C'].agg([np.sum, np.mean, np.std])
Out[57]: 
          sum      mean       std
A                                
bar  0.443469  0.147823  0.301765
foo  2.529056  0.505811  0.966450

如果传递了dict,则键将用于命名列。否则将使用函数的名称(存储在函数对象中)。

In [58]: grouped['D'].agg({'result1' : np.sum,
   ....:                   'result2' : np.mean})
   ....: 
Out[58]: 
      result2   result1
A                      
bar  0.306945  0.920834
foo -0.344944 -1.724719

在分组的DataFrame上,您可以传递要应用于每个列的函数列表,这会生成具有层次索引的聚合结果:

In [59]: grouped.agg([np.sum, np.mean, np.std])
Out[59]: 
            C                             D                    
          sum      mean       std       sum      mean       std
A                                                              
bar  0.443469  0.147823  0.301765  0.920834  0.306945  1.490982
foo  2.529056  0.505811  0.966450 -1.724719 -0.344944  0.645875

默认情况下,传递函数的dict具有不同的行为,请参见下一节。

Applying different functions to DataFrame columns

通过将dict传递到aggregate,您可以对DataFrame的列应用不同的聚合:

In [60]: grouped.agg({'C' : np.sum,
   ....:              'D' : lambda x: np.std(x, ddof=1)})
   ....: 
Out[60]: 
            C         D
A                      
bar  0.443469  1.490982
foo  2.529056  0.645875

函数名也可以是字符串。为了使字符串有效,它必须在GroupBy上实现或通过dispatching可用:

In [61]: grouped.agg({'C' : 'sum', 'D' : 'std'})
Out[61]: 
            C         D
A                      
bar  0.443469  1.490982
foo  2.529056  0.645875

注意

如果将dict传递到aggregate,则输出列的顺序是非确定性的。如果您想确保输出列按特定顺序排列,您可以使用OrderedDict比较以下两个命令的输出:

In [62]: grouped.agg({'D': 'std', 'C': 'mean'})
Out[62]: 
            C         D
A                      
bar  0.147823  1.490982
foo  0.505811  0.645875

In [63]: grouped.agg(OrderedDict([('D', 'std'), ('C', 'mean')]))
Out[63]: 
            D         C
A                      
bar  1.490982  0.147823
foo  0.645875  0.505811

Cython-optimized aggregation functions

一些常见的聚合,目前只有summeanstdsem

In [64]: df.groupby('A').sum()
Out[64]: 
            C         D
A                      
bar  0.443469  0.920834
foo  2.529056 -1.724719

In [65]: df.groupby(['A', 'B']).mean()
Out[65]: 
                  C         D
A   B                        
bar one   -0.042379 -0.089329
    three -0.009920 -0.945867
    two    0.495767  1.956030
foo one   -0.278452 -0.556879
    three  1.548106 -0.016692
    two    0.768928 -0.297134

当然,在pandas对象上实现summean,所以上面的代码即使没有特殊的版本也可以通过dispatching(见下文)。

Transformation

transform方法返回一个对象,其索引与被分组的对象相同(大小相同)。因此,传递的变换函数应返回与组块大小相同的结果。例如,假设我们希望标准化每个组中的数据:

In [66]: index = pd.date_range('10/1/1999', periods=1100)

In [67]: ts = pd.Series(np.random.normal(0.5, 2, 1100), index)

In [68]: ts = ts.rolling(window=100,min_periods=100).mean().dropna()

In [69]: ts.head()
Out[69]: 
2000-01-08    0.779333
2000-01-09    0.778852
2000-01-10    0.786476
2000-01-11    0.782797
2000-01-12    0.798110
Freq: D, dtype: float64

In [70]: ts.tail()
Out[70]: 
2002-09-30    0.660294
2002-10-01    0.631095
2002-10-02    0.673601
2002-10-03    0.709213
2002-10-04    0.719369
Freq: D, dtype: float64

In [71]: key = lambda x: x.year

In [72]: zscore = lambda x: (x - x.mean()) / x.std()

In [73]: transformed = ts.groupby(key).transform(zscore)

我们期望结果现在在每个组内具有平均值0和标准偏差1,这可以容易地检查:

# Original Data
In [74]: grouped = ts.groupby(key)

In [75]: grouped.mean()
Out[75]: 
2000    0.442441
2001    0.526246
2002    0.459365
dtype: float64

In [76]: grouped.std()
Out[76]: 
2000    0.131752
2001    0.210945
2002    0.128753
dtype: float64

# Transformed Data
In [77]: grouped_trans = transformed.groupby(key)

In [78]: grouped_trans.mean()
Out[78]: 
2000    1.168208e-15
2001    1.454544e-15
2002    1.726657e-15
dtype: float64

In [79]: grouped_trans.std()
Out[79]: 
2000    1.0
2001    1.0
2002    1.0
dtype: float64

我们还可以直观地比较原始数据集和转换后的数据集。

In [80]: compare = pd.DataFrame({'Original': ts, 'Transformed': transformed})

In [81]: compare.plot()
Out[81]: <matplotlib.axes._subplots.AxesSubplot at 0x7ff26ffe62d0>
http://pandas.pydata.org/pandas-docs/version/0.19.2/_images/groupby_transform_plot.png

另一个常见的数据转换是用群平均替换丢失的数据。

In [82]: data_df
Out[82]: 
            A         B         C
0    1.539708 -1.166480  0.533026
1    1.302092 -0.505754       NaN
2   -0.371983  1.104803 -0.651520
3   -1.309622  1.118697 -1.161657
4   -1.924296  0.396437  0.812436
5    0.815643  0.367816 -0.469478
6   -0.030651  1.376106 -0.645129
..        ...       ...       ...
993  0.012359  0.554602 -1.976159
994  0.042312 -1.628835  1.013822
995 -0.093110  0.683847 -0.774753
996 -0.185043  1.438572       NaN
997 -0.394469 -0.642343  0.011374
998 -1.174126  1.857148       NaN
999  0.234564  0.517098  0.393534

[1000 rows x 3 columns]

In [83]: countries = np.array(['US', 'UK', 'GR', 'JP'])

In [84]: key = countries[np.random.randint(0, 4, 1000)]

In [85]: grouped = data_df.groupby(key)

# Non-NA count in each group
In [86]: grouped.count()
Out[86]: 
      A    B    C
GR  209  217  189
JP  240  255  217
UK  216  231  193
US  239  250  217

In [87]: f = lambda x: x.fillna(x.mean())

In [88]: transformed = grouped.transform(f)

我们可以验证组平均值在变换的数据中没有变化,并且变换的数据不包含NA。

In [89]: grouped_trans = transformed.groupby(key)

In [90]: grouped.mean() # original group means
Out[90]: 
           A         B         C
GR -0.098371 -0.015420  0.068053
JP  0.069025  0.023100 -0.077324
UK  0.034069 -0.052580 -0.116525
US  0.058664 -0.020399  0.028603

In [91]: grouped_trans.mean() # transformation did not change group means
Out[91]: 
           A         B         C
GR -0.098371 -0.015420  0.068053
JP  0.069025  0.023100 -0.077324
UK  0.034069 -0.052580 -0.116525
US  0.058664 -0.020399  0.028603

In [92]: grouped.count() # original has some missing data points
Out[92]: 
      A    B    C
GR  209  217  189
JP  240  255  217
UK  216  231  193
US  239  250  217

In [93]: grouped_trans.count() # counts after transformation
Out[93]: 
      A    B    C
GR  228  228  228
JP  267  267  267
UK  247  247  247
US  258  258  258

In [94]: grouped_trans.size() # Verify non-NA count equals group size
Out[94]: 
GR    228
JP    267
UK    247
US    258
dtype: int64

注意

一些函数应用于groupby对象时将自动变换输入,返回与原始形状相同的对象。传递as_index=False不会影响这些转换方法。

例如:fillna, ffill, bfill, shift

In [95]: grouped.ffill()
Out[95]: 
            A         B         C
0    1.539708 -1.166480  0.533026
1    1.302092 -0.505754  0.533026
2   -0.371983  1.104803 -0.651520
3   -1.309622  1.118697 -1.161657
4   -1.924296  0.396437  0.812436
5    0.815643  0.367816 -0.469478
6   -0.030651  1.376106 -0.645129
..        ...       ...       ...
993  0.012359  0.554602 -1.976159
994  0.042312 -1.628835  1.013822
995 -0.093110  0.683847 -0.774753
996 -0.185043  1.438572 -0.774753
997 -0.394469 -0.642343  0.011374
998 -1.174126  1.857148 -0.774753
999  0.234564  0.517098  0.393534

[1000 rows x 3 columns]

New syntax to window and resample operations

版本0.18.1中的新功能。

使用对groupby级别的重采样,扩展或滚动操作,需要应用辅助函数。然而,现在可以使用resample()expanding()rolling()作为groupbys上的方法。

下面的示例将基于列A的组对列B的样本应用rolling()方法。

In [96]: df_re = pd.DataFrame({'A': [1] * 10 + [5] * 10,
   ....:                       'B': np.arange(20)})
   ....: 

In [97]: df_re
Out[97]: 
    A   B
0   1   0
1   1   1
2   1   2
3   1   3
4   1   4
5   1   5
6   1   6
.. ..  ..
13  5  13
14  5  14
15  5  15
16  5  16
17  5  17
18  5  18
19  5  19

[20 rows x 2 columns]

In [98]: df_re.groupby('A').rolling(4).B.mean()
Out[98]: 
A    
1  0      NaN
   1      NaN
   2      NaN
   3      1.5
   4      2.5
   5      3.5
   6      4.5
         ... 
5  13    11.5
   14    12.5
   15    13.5
   16    14.5
   17    15.5
   18    16.5
   19    17.5
Name: B, dtype: float64

expanding()方法将为每个特定组的所有成员累积给定操作(在示例中为sum())。

In [99]: df_re.groupby('A').expanding().sum()
Out[99]: 
         A      B
A                
1 0    1.0    0.0
  1    2.0    1.0
  2    3.0    3.0
  3    4.0    6.0
  4    5.0   10.0
  5    6.0   15.0
  6    7.0   21.0
...    ...    ...
5 13  20.0   46.0
  14  25.0   60.0
  15  30.0   75.0
  16  35.0   91.0
  17  40.0  108.0
  18  45.0  126.0
  19  50.0  145.0

[20 rows x 2 columns]

假设您要使用resample()方法来获取每个数据帧的每日频率,并希望使用ffill()方法完成缺少的值。

In [100]: df_re = pd.DataFrame({'date': pd.date_range(start='2016-01-01',
   .....:                               periods=4,
   .....:                       freq='W'),
   .....:                      'group': [1, 1, 2, 2],
   .....:                      'val': [5, 6, 7, 8]}).set_index('date')
   .....: 

In [101]: df_re
Out[101]: 
            group  val
date                  
2016-01-03      1    5
2016-01-10      1    6
2016-01-17      2    7
2016-01-24      2    8

In [102]: df_re.groupby('group').resample('1D').ffill()
Out[102]: 
                  group  val
group date                  
1     2016-01-03      1    5
      2016-01-04      1    5
      2016-01-05      1    5
      2016-01-06      1    5
      2016-01-07      1    5
      2016-01-08      1    5
      2016-01-09      1    5
...                 ...  ...
2     2016-01-18      2    7
      2016-01-19      2    7
      2016-01-20      2    7
      2016-01-21      2    7
      2016-01-22      2    7
      2016-01-23      2    7
      2016-01-24      2    8

[16 rows x 2 columns]

Filtration

版本0.12中的新功能。

filter方法返回原始对象的子集。假设我们只想取得属于群组总和大于2的群组的元素。

In [103]: sf = pd.Series([1, 1, 2, 3, 3, 3])

In [104]: sf.groupby(sf).filter(lambda x: x.sum() > 2)
Out[104]: 
3    3
4    3
5    3
dtype: int64

filter的参数必须是应用于整个组的函数,返回TrueFalse

另一个有用的操作是过滤掉属于只有几个成员的组的元素。

In [105]: dff = pd.DataFrame({'A': np.arange(8), 'B': list('aabbbbcc')})

In [106]: dff.groupby('B').filter(lambda x: len(x) > 2)
Out[106]: 
   A  B
2  2  b
3  3  b
4  4  b
5  5  b

或者,代替丢弃有问题的组,我们可以返回类似索引的对象,其中未通过过滤器的组用NaN填充。

In [107]: dff.groupby('B').filter(lambda x: len(x) > 2, dropna=False)
Out[107]: 
     A    B
0  NaN  NaN
1  NaN  NaN
2  2.0    b
3  3.0    b
4  4.0    b
5  5.0    b
6  NaN  NaN
7  NaN  NaN

对于具有多个列的DataFrames,过滤器应显式指定一个列作为过滤条件。

In [108]: dff['C'] = np.arange(8)

In [109]: dff.groupby('B').filter(lambda x: len(x['C']) > 2)
Out[109]: 
   A  B  C
2  2  b  2
3  3  b  3
4  4  b  4
5  5  b  5

注意

应用于groupby对象时,某些函数将作为输入上的过滤器,返回原始缩减的形状(并可能消除组),但索引不变。传递as_index=False不会影响这些转换方法。

例如:head, tail

In [110]: dff.groupby('B').head(2)
Out[110]: 
   A  B  C
0  0  a  0
1  1  a  1
2  2  b  2
3  3  b  3
6  6  c  6
7  7  c  7

Dispatching to instance methods

当执行聚合或转换时,您可能只想对每个数据组调用实例方法。这通过传递lambda函数很容易做到:

In [111]: grouped = df.groupby('A')

In [112]: grouped.agg(lambda x: x.std())
Out[112]: 
            C         D
A                      
bar  0.301765  1.490982
foo  0.966450  0.645875

但是,它是相当冗长,如果你需要传递额外的参数,可能不整洁。使用一点元编程聪明,GroupBy现在有能力“调度”方法调用到组:

In [113]: grouped.std()
Out[113]: 
            C         D
A                      
bar  0.301765  1.490982
foo  0.966450  0.645875

这里实际发生的是生成函数包装器。当被调用时,它接受任何传递的参数,并调用每个组上的任何参数的函数(在上面的例子中,std函数)。然后,结果以aggtransform的样式(它实际上使用apply来推断胶合,接下来记录)的样式组合在一起。这使得一些操作可以相当简洁地进行:

In [114]: tsdf = pd.DataFrame(np.random.randn(1000, 3),
   .....:                     index=pd.date_range('1/1/2000', periods=1000),
   .....:                     columns=['A', 'B', 'C'])
   .....: 

In [115]: tsdf.ix[::2] = np.nan

In [116]: grouped = tsdf.groupby(lambda x: x.year)

In [117]: grouped.fillna(method='pad')
Out[117]: 
                   A         B         C
2000-01-01       NaN       NaN       NaN
2000-01-02 -0.353501 -0.080957 -0.876864
2000-01-03 -0.353501 -0.080957 -0.876864
2000-01-04  0.050976  0.044273 -0.559849
2000-01-05  0.050976  0.044273 -0.559849
2000-01-06  0.030091  0.186460 -0.680149
2000-01-07  0.030091  0.186460 -0.680149
...              ...       ...       ...
2002-09-20  2.310215  0.157482 -0.064476
2002-09-21  2.310215  0.157482 -0.064476
2002-09-22  0.005011  0.053897 -1.026922
2002-09-23  0.005011  0.053897 -1.026922
2002-09-24 -0.456542 -1.849051  1.559856
2002-09-25 -0.456542 -1.849051  1.559856
2002-09-26  1.123162  0.354660  1.128135

[1000 rows x 3 columns]

在这个例子中,我们将时间序列的集合切成年份,然后在组上独立地称为fillna

版本0.14.1中的新功能。

nlargestnsmallest方法适用于Series样式groupbys:

In [118]: s = pd.Series([9, 8, 7, 5, 19, 1, 4.2, 3.3])

In [119]: g = pd.Series(list('abababab'))

In [120]: gb = s.groupby(g)

In [121]: gb.nlargest(3)
Out[121]: 
a  4    19.0
   0     9.0
   2     7.0
b  1     8.0
   3     5.0
   7     3.3
dtype: float64

In [122]: gb.nsmallest(3)
Out[122]: 
a  6    4.2
   2    7.0
   0    9.0
b  5    1.0
   7    3.3
   3    5.0
dtype: float64

Flexible apply

对分组数据的某些操作可能不适合聚合或变换类别。或者,您可能只需要GroupBy推断如何组合结果。对于这些,使用apply函数,可以在许多标准用例中替换aggregatetransform但是,apply可以处理一些特殊的用例,例如:

In [123]: df
Out[123]: 
     A      B         C         D
0  foo    one -0.919854 -1.131345
1  bar    one -0.042379 -0.089329
2  foo    two  1.247642  0.337863
3  bar  three -0.009920 -0.945867
4  foo    two  0.290213 -0.932132
5  bar    two  0.495767  1.956030
6  foo    one  0.362949  0.017587
7  foo  three  1.548106 -0.016692

In [124]: grouped = df.groupby('A')

# could also just call .describe()
In [125]: grouped['C'].apply(lambda x: x.describe())
Out[125]: 
A         
bar  count    3.000000
     mean     0.147823
     std      0.301765
     min     -0.042379
     25%     -0.026149
     50%     -0.009920
     75%      0.242924
                ...   
foo  mean     0.505811
     std      0.966450
     min     -0.919854
     25%      0.290213
     50%      0.362949
     75%      1.247642
     max      1.548106
Name: C, dtype: float64

返回结果的维度也可以更改:

In [126]: grouped = df.groupby('A')['C']

In [127]: def f(group):
   .....:     return pd.DataFrame({'original' : group,
   .....:                          'demeaned' : group - group.mean()})
   .....: 

In [128]: grouped.apply(f)
Out[128]: 
   demeaned  original
0 -1.425665 -0.919854
1 -0.190202 -0.042379
2  0.741831  1.247642
3 -0.157743 -0.009920
4 -0.215598  0.290213
5  0.347944  0.495767
6 -0.142862  0.362949
7  1.042295  1.548106

apply可以对应用函数的返回值进行操作,这本身就是一个系列,并且可能将结果上传到DataFrame

In [129]: def f(x):
   .....:   return pd.Series([ x, x**2 ], index = ['x', 'x^2'])
   .....: 

In [130]: s
Out[130]: 
0     9.0
1     8.0
2     7.0
3     5.0
4    19.0
5     1.0
6     4.2
7     3.3
dtype: float64

In [131]: s.apply(f)
Out[131]: 
      x     x^2
0   9.0   81.00
1   8.0   64.00
2   7.0   49.00
3   5.0   25.00
4  19.0  361.00
5   1.0    1.00
6   4.2   17.64
7   3.3   10.89

注意

apply可以作为缩减器,变换器,过滤器函数,具体取决于传递给它的内容。所以,取决于所采取的路径,正是你正在分组。因此,分组的列可以被包括在输出中以及设置索引。

警告

在当前实现中,在第一组上应用调用func两次以决定它是否可以采取快或慢的代码路径。这可能导致意想不到的行为,如果func有副作用,因为它们将对第一组生效两次。

In [132]: d = pd.DataFrame({"a":["x", "y"], "b":[1,2]})

In [133]: def identity(df):
   .....:     print df
   .....:     return df
   .....: 

In [134]: d.groupby("a").apply(identity)
   a  b
0  x  1
   a  b
0  x  1
   a  b
1  y  2
Out[134]: 
   a  b
0  x  1
1  y  2

Other useful features

Automatic exclusion of “nuisance” columns

再次考虑我们一直在看的DataFrame示例:

In [135]: df
Out[135]: 
     A      B         C         D
0  foo    one -0.919854 -1.131345
1  bar    one -0.042379 -0.089329
2  foo    two  1.247642  0.337863
3  bar  three -0.009920 -0.945867
4  foo    two  0.290213 -0.932132
5  bar    two  0.495767  1.956030
6  foo    one  0.362949  0.017587
7  foo  three  1.548106 -0.016692

假设我们希望计算由A列分组的标准偏差。有一个小问题,即我们不关心B列中的数据。我们将其称为“烦扰”列。如果传递的聚合函数不能应用于某些列,那么麻烦的列将被(静默地)丢弃。因此,这不会造成任何问题:

In [136]: df.groupby('A').std()
Out[136]: 
            C         D
A                      
bar  0.301765  1.490982
foo  0.966450  0.645875

NA and NaT group handling

如果分组键中有NaN或NaT值,这些值将被自动排除。因此,永远不会有“NA组”或“NaT组”。这在老版本的熊猫不是这样,但用户通常抛弃NA组反正(和支持它是一个实现头痛)。

Grouping with ordered factors

作为pandas的Categorical类实例表示的分类变量可以用作组键。如果是,级别的顺序将保留:

In [137]: data = pd.Series(np.random.randn(100))

In [138]: factor = pd.qcut(data, [0, .25, .5, .75, 1.])

In [139]: data.groupby(factor).mean()
Out[139]: 
[-2.617, -0.684]    -1.331461
(-0.684, -0.0232]   -0.272816
(-0.0232, 0.541]     0.263607
(0.541, 2.369]       1.166038
dtype: float64

Grouping with a Grouper specification

您可能需要指定更多的数据才能正确分组。您可以使用pd.Grouper提供此本地控制。

In [140]: import datetime

In [141]: df = pd.DataFrame({
   .....:          'Branch' : 'A A A A A A A B'.split(),
   .....:          'Buyer': 'Carl Mark Carl Carl Joe Joe Joe Carl'.split(),
   .....:          'Quantity': [1,3,5,1,8,1,9,3],
   .....:          'Date' : [
   .....:              datetime.datetime(2013,1,1,13,0),
   .....:              datetime.datetime(2013,1,1,13,5),
   .....:              datetime.datetime(2013,10,1,20,0),
   .....:              datetime.datetime(2013,10,2,10,0),
   .....:              datetime.datetime(2013,10,1,20,0),
   .....:              datetime.datetime(2013,10,2,10,0),
   .....:              datetime.datetime(2013,12,2,12,0),
   .....:              datetime.datetime(2013,12,2,14,0),
   .....:              ]
   .....:          })
   .....: 

In [142]: df
Out[142]: 
  Branch Buyer                Date  Quantity
0      A  Carl 2013-01-01 13:00:00         1
1      A  Mark 2013-01-01 13:05:00         3
2      A  Carl 2013-10-01 20:00:00         5
3      A  Carl 2013-10-02 10:00:00         1
4      A   Joe 2013-10-01 20:00:00         8
5      A   Joe 2013-10-02 10:00:00         1
6      A   Joe 2013-12-02 12:00:00         9
7      B  Carl 2013-12-02 14:00:00         3

分组具有所需频率的特定列。这就像重采样。

In [143]: df.groupby([pd.Grouper(freq='1M',key='Date'),'Buyer']).sum()
Out[143]: 
                  Quantity
Date       Buyer          
2013-01-31 Carl          1
           Mark          3
2013-10-31 Carl          6
           Joe           9
2013-12-31 Carl          3
           Joe           9

你有一个不明确的规范,你有一个命名的索引和一个可能是潜在的石斑鱼的列。

In [144]: df = df.set_index('Date')

In [145]: df['Date'] = df.index + pd.offsets.MonthEnd(2)

In [146]: df.groupby([pd.Grouper(freq='6M',key='Date'),'Buyer']).sum()
Out[146]: 
                  Quantity
Date       Buyer          
2013-02-28 Carl          1
           Mark          3
2014-02-28 Carl          9
           Joe          18

In [147]: df.groupby([pd.Grouper(freq='6M',level='Date'),'Buyer']).sum()
Out[147]: 
                  Quantity
Date       Buyer          
2013-01-31 Carl          1
           Mark          3
2014-01-31 Carl          9
           Joe          18

Taking the first rows of each group

就像一个DataFrame或系列,你可以调用head和tail:

In [148]: df = pd.DataFrame([[1, 2], [1, 4], [5, 6]], columns=['A', 'B'])

In [149]: df
Out[149]: 
   A  B
0  1  2
1  1  4
2  5  6

In [150]: g = df.groupby('A')

In [151]: g.head(1)
Out[151]: 
   A  B
0  1  2
2  5  6

In [152]: g.tail(1)
Out[152]: 
   A  B
1  1  4
2  5  6

这显示每个组的第一或最后n行。

警告

在0.14.0之前,这是通过fall-through apply实现的,因此结果将不正确地遵守as_index标志:

>>> g.head(1):  # was equivalent to g.apply(lambda x: x.head(1))
      A  B
 A
 1 0  1  2
 5 2  5  6

Taking the nth row of each group

要从DataFrame或Series中选择第n个项目,请使用第n个方法。这是一种缩减方法,如果为n传递一个int,则每个组将返回一行(或没有行)

In [153]: df = pd.DataFrame([[1, np.nan], [1, 4], [5, 6]], columns=['A', 'B'])

In [154]: g = df.groupby('A')

In [155]: g.nth(0)
Out[155]: 
     B
A     
1  NaN
5  6.0

In [156]: g.nth(-1)
Out[156]: 
     B
A     
1  4.0
5  6.0

In [157]: g.nth(1)
Out[157]: 
     B
A     
1  4.0

如果要选择第n个非空项,请使用dropna kwarg。对于DataFrame,这应该是'any''all',就像你会传递给dropna,对于一个系列,这只是需要是真的。

# nth(0) is the same as g.first()
In [158]: g.nth(0, dropna='any')
Out[158]: 
     B
A     
1  4.0
5  6.0

In [159]: g.first()
Out[159]: 
     B
A     
1  4.0
5  6.0

# nth(-1) is the same as g.last()
In [160]: g.nth(-1, dropna='any')  # NaNs denote group exhausted when using dropna
Out[160]: 
     B
A     
1  4.0
5  6.0

In [161]: g.last()
Out[161]: 
     B
A     
1  4.0
5  6.0

In [162]: g.B.nth(0, dropna=True)
Out[162]: 
A
1    4.0
5    6.0
Name: B, dtype: float64

与其他方法一样,传递as_index=False会实现过滤,返回分组的行。

In [163]: df = pd.DataFrame([[1, np.nan], [1, 4], [5, 6]], columns=['A', 'B'])

In [164]: g = df.groupby('A',as_index=False)

In [165]: g.nth(0)
Out[165]: 
   A    B
0  1  NaN
2  5  6.0

In [166]: g.nth(-1)
Out[166]: 
   A    B
1  1  4.0
2  5  6.0

您还可以通过将多个n个值指定为int列表,从每个组中选择多个行。

In [167]: business_dates = pd.date_range(start='4/1/2014', end='6/30/2014', freq='B')

In [168]: df = pd.DataFrame(1, index=business_dates, columns=['a', 'b'])

# get the first, 4th, and last date index for each month
In [169]: df.groupby((df.index.year, df.index.month)).nth([0, 3, -1])
Out[169]: 
        a  b
2014 4  1  1
     4  1  1
     4  1  1
     5  1  1
     5  1  1
     5  1  1
     6  1  1
     6  1  1
     6  1  1

Enumerate group items

版本0.13.0中的新功能。

要查看每个行在其组中的显示顺序,请使用cumcount方法:

In [170]: df = pd.DataFrame(list('aaabba'), columns=['A'])

In [171]: df
Out[171]: 
   A
0  a
1  a
2  a
3  b
4  b
5  a

In [172]: df.groupby('A').cumcount()
Out[172]: 
0    0
1    1
2    2
3    0
4    1
5    3
dtype: int64

In [173]: df.groupby('A').cumcount(ascending=False)  # kwarg only
Out[173]: 
0    3
1    2
2    1
3    1
4    0
5    0
dtype: int64

Plotting

Groupby也使用一些绘图方法。例如,假设我们怀疑DataFrame中的某些功能可能会按组不同,在这种情况下,第1列中的组为“B”的值平均高出3个。

In [174]: np.random.seed(1234)

In [175]: df = pd.DataFrame(np.random.randn(50, 2))

In [176]: df['g'] = np.random.choice(['A', 'B'], size=50)

In [177]: df.loc[df['g'] == 'B', 1] += 3

我们可以很容易地用boxplot来形容这个:

In [178]: df.groupby('g').boxplot()
Out[178]: 
A         Axes(0.1,0.15;0.363636x0.75)
B    Axes(0.536364,0.15;0.363636x0.75)
dtype: object
http://pandas.pydata.org/pandas-docs/version/0.19.2/_images/groupby_boxplot.png

调用boxplot的结果是一个字典,其键是我们的分组列g(“A”和“B”)的值。结果字典的值可以通过boxplotreturn_type关键字控制。有关更多信息,请参阅visualization documentation

警告

由于历史原因,df.groupby("g").boxplot()不等同于df.boxplot(by="g")有关说明,请参阅here

Examples

Regrouping by factor

根据它们的总和重组数据框架的列,并对聚合的数据求和。

In [179]: df = pd.DataFrame({'a':[1,0,0], 'b':[0,1,0], 'c':[1,0,0], 'd':[2,3,4]})

In [180]: df
Out[180]: 
   a  b  c  d
0  1  0  1  2
1  0  1  0  3
2  0  0  0  4

In [181]: df.groupby(df.sum(), axis=1).sum()
Out[181]: 
   1  9
0  2  2
1  1  3
2  0  4

Groupby by Indexer to ‘resample’ data

重采样从已经存在的观察数据或从生成数据的模型产生新的假设样本(重采样)。这些新样品类似于先前存在的样品。

为了重新采样以对非数据类型的索引工作,可以使用以下过程。

在以下示例中,df.index // 5返回一个二进制数组,用于确定为groupby操作选择的get。

注意

下面的示例显示了我们如何通过将样本合并为更少的样本来进行下采样。这里通过使用df.index // 5,我们在bin中聚合样本。通过应用std()函数,我们将包含在许多样本中的信息聚合为值的一个小子集,这是它们的标准偏差,从而减少了样本数量。

In [182]: df = pd.DataFrame(np.random.randn(10,2))

In [183]: df
Out[183]: 
          0         1
0 -0.832423  0.114059
1  1.218203 -0.890593
2  0.165445 -1.127470
3 -1.192185  0.818644
4  0.237185 -0.336384
5  0.694727  0.750161
6  0.247055  0.645433
7 -1.366120  0.313160
8  0.205207  0.089987
9  0.186062  1.314182

In [184]: df.index // 5
Out[184]: Int64Index([0, 0, 0, 0, 0, 1, 1, 1, 1, 1], dtype='int64')

In [185]: df.groupby(df.index // 5).std()
Out[185]: 
          0         1
0  0.955154  0.783648
1  0.788428  0.467576

Returning a Series to propagate names

组DataFrame列,计算一组度量并返回一个命名的系列。系列名称用作列索引的名称。这尤其适用于重组操作,例如堆栈,其中列索引名称将用作插入列的名称:

In [186]: df = pd.DataFrame({
   .....:          'a':  [0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2],
   .....:          'b':  [0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1],
   .....:          'c':  [1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0],
   .....:          'd':  [0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1],
   .....:          })
   .....: 

In [187]: def compute_metrics(x):
   .....:     result = {'b_sum': x['b'].sum(), 'c_mean': x['c'].mean()}
   .....:     return pd.Series(result, name='metrics')
   .....: 

In [188]: result = df.groupby('a').apply(compute_metrics)

In [189]: result
Out[189]: 
metrics  b_sum  c_mean
a                     
0          2.0     0.5
1          2.0     0.5
2          2.0     0.5

In [190]: result.stack()
Out[190]: 
a  metrics
0  b_sum      2.0
   c_mean     0.5
1  b_sum      2.0
   c_mean     0.5
2  b_sum      2.0
   c_mean     0.5
dtype: float64
Scroll To Top