分组方式:拆分-应用-合并#

我们所说的“分组依据”是指涉及以下一个或多个步骤的过程:

  • 拆分 根据某些标准将数据分组。

  • 施药 对每一组独立的一个功能。

  • 组合 将结果转换为数据结构。

其中,分步走是最直截了当的。事实上,在许多情况下,我们可能希望将数据集分成几组,并对这些组做一些事情。在应用步骤中,我们可能希望执行以下操作之一:

  • 聚合 :计算每个组的汇总统计数据(或统计数据)。下面是一些示例:

    • 计算群体总和或平均数。

    • 计算组大小/计数。

  • 转型 :执行一些特定于组的计算并返回一个类似索引的对象。下面是一些示例:

    • 标准化一个组内的数据(Zcore)。

    • 用从每个组导出的值填充组内的NAS。

  • 滤过 :根据评估为True或False的逐组计算,丢弃某些组。下面是一些示例:

    • 丢弃属于只有几个成员的组的数据。

    • 根据组总和或平均值筛选出数据。

  • GroupBy将检查应用步骤的结果,如果不符合上述两个类别中的任何一个,则尝试返回合理组合的结果。

由于PANDA数据结构上的对象实例方法集通常丰富且具有表现力,因此我们通常只想在每个组上调用一个DataFrame函数。GroupBy这个名称对于使用过基于SQL的工具(或 itertools ),您可以在其中编写如下代码:

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

我们的目标是让这样的操作变得自然,并易于使用Pandas来表达。我们将介绍GroupBy功能的每个方面,然后提供一些重要的示例/用例。

请参阅 cookbook 一些先进的策略。

将对象拆分为组#

Pandas物体可以在它们的任意轴上分割。分组的抽象定义是提供标签到组名的映射。要创建GroupBy对象(稍后将详细介绍GroupBy对象的内容),可以执行以下操作:

In [1]: df = pd.DataFrame(
   ...:     [
   ...:         ("bird", "Falconiformes", 389.0),
   ...:         ("bird", "Psittaciformes", 24.0),
   ...:         ("mammal", "Carnivora", 80.2),
   ...:         ("mammal", "Primates", np.nan),
   ...:         ("mammal", "Carnivora", 58),
   ...:     ],
   ...:     index=["falcon", "parrot", "lion", "monkey", "leopard"],
   ...:     columns=("class", "order", "max_speed"),
   ...: )
   ...: 

In [2]: df
Out[2]: 
          class           order  max_speed
falcon     bird   Falconiformes      389.0
parrot     bird  Psittaciformes       24.0
lion     mammal       Carnivora       80.2
monkey   mammal        Primates        NaN
leopard  mammal       Carnivora       58.0

# default is axis=0
In [3]: grouped = df.groupby("class")

In [4]: grouped = df.groupby("order", axis="columns")

In [5]: grouped = df.groupby(["class", "order"])

可以通过多种不同的方式指定映射:

  • 要在每个轴标签上调用的Python函数。

  • 与所选轴长度相同的列表或NumPy数组。

  • 判决书或 Series ,提供了一个 label -> group name 映射。

  • DataFrame 对象,该字符串指示要用于分组的列名或索引级名称。

  • df.groupby('A') 只是句法上的糖, df.groupby(df['A'])

  • 上面任何一样东西的清单。

我们将分组对象统称为 keys 。例如,请考虑以下内容 DataFrame

备注

传递给的字符串 groupby 可以引用列或索引级。如果字符串同时与列名和索引级名称匹配,则 ValueError 都会被举起。

In [6]: 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 [7]: df
Out[7]: 
     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

在DataFrame上,我们通过调用 groupby() 。我们自然而然地可以按照 AB 列或两者兼而有之:

In [8]: grouped = df.groupby("A")

In [9]: grouped = df.groupby(["A", "B"])

如果我们对列也有多重索引 AB ,我们可以按除指定列之外的所有列进行分组

In [10]: df2 = df.set_index(["A", "B"])

In [11]: grouped = df2.groupby(level=df2.index.names.difference(["B"]))

In [12]: grouped.sum()
Out[12]: 
            C         D
A                      
bar -1.591710 -1.739537
foo -0.752861 -1.402938

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

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

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

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

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

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

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

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

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

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

请注意, 未发生拆分 直到需要的时候。创建GroupBy对象只是验证您是否传递了有效的映射。

备注

许多复杂的数据操作都可以用GroupBy操作来表示(尽管不能保证是最有效的)。您可以使用标签映射功能变得非常有创意。

按排序分组#

默认情况下,组密钥在 groupby 手术。不过,你可以通过 sort=False 对于潜在的加速:

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

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

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

请注意, groupby 将保持以下顺序 观察 是已排序的 每组。例如,由创建的组 groupby() 以下是它们在原件中出现的顺序 DataFrame

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

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

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

1.1.0 新版功能.

Groupby DropNA#

默认情况下, NA 期间,从组密钥中排除值 groupby 手术。但是,如果您想要包括 NA 值,则可以将 dropna=False 来实现这一目标。

In [27]: df_list = [[1, 2, 3], [1, None, 4], [2, 1, 3], [1, 2, 2]]

In [28]: df_dropna = pd.DataFrame(df_list, columns=["a", "b", "c"])

In [29]: df_dropna
Out[29]: 
   a    b  c
0  1  2.0  3
1  1  NaN  4
2  2  1.0  3
3  1  2.0  2
# Default ``dropna`` is set to True, which will exclude NaNs in keys
In [30]: df_dropna.groupby(by=["b"], dropna=True).sum()
Out[30]: 
     a  c
b        
1.0  2  3
2.0  2  5

# In order to allow NaN in keys, set ``dropna`` to False
In [31]: df_dropna.groupby(by=["b"], dropna=False).sum()
Out[31]: 
     a  c
b        
1.0  2  3
2.0  2  5
NaN  1  4

默认设置为 dropna 论据是 True 这意味着 NA 不包括在组密钥中。

Groupby对象属性#

这个 groups 属性是一个字典,其关键字是计算出的唯一组,相应的值是属于每个组的轴标签。在上面的示例中,我们有:

In [32]: df.groupby("A").groups
Out[32]: {'bar': [1, 3, 5], 'foo': [0, 2, 4, 6, 7]}

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

调用标准的Python len GroupBy对象上的函数只返回 groups Dict,所以这在很大程度上只是为了方便:

In [34]: grouped = df.groupby(["A", "B"])

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

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

GroupBy 将制表符完成列名(和其他属性):

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

In [38]: gb = df.groupby("gender")
In [39]: gb.<TAB>  # noqa: E225, E999
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#

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

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

In [40]: arrays = [
   ....:     ["bar", "bar", "baz", "baz", "foo", "foo", "qux", "qux"],
   ....:     ["one", "two", "one", "two", "one", "two", "one", "two"],
   ....: ]
   ....: 

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

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

In [43]: s
Out[43]: 
first  second
bar    one      -0.919854
       two      -0.042379
baz    one       1.247642
       two      -0.009920
foo    one       0.290213
       two       0.495767
qux    one       0.362949
       two       1.548106
dtype: float64

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

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

In [45]: grouped.sum()
Out[45]: 
first
bar   -0.962232
baz    1.237723
foo    0.785980
qux    1.911055
dtype: float64

如果多重索引具有指定的名称,则可以传递这些名称,而不是级别编号:

In [46]: s.groupby(level="second").sum()
Out[46]: 
second
one    0.980950
two    1.991575
dtype: float64

支持多级分组。

In [47]: s
Out[47]: 
first  second  third
bar    doo     one     -1.131345
               two     -0.089329
baz    bee     one      0.337863
               two     -0.945867
foo    bop     one     -0.932132
               two      1.956030
qux    bop     one      0.017587
               two     -0.016692
dtype: float64

In [48]: s.groupby(level=["first", "second"]).sum()
Out[48]: 
first  second
bar    doo      -1.220674
baz    bee      -0.608004
foo    bop       1.023898
qux    bop       0.000895
dtype: float64

索引级名称可以作为键提供。

In [49]: s.groupby(["first", "second"]).sum()
Out[49]: 
first  second
bar    doo      -1.220674
baz    bee      -0.608004
foo    bop       1.023898
qux    bop       0.000895
dtype: float64

更多信息请访问 sum 稍后的函数和聚合。

使用索引级别和列对DataFrame进行分组#

通过将列名称指定为字符串并将索引级别指定为,可以按列和索引级别的组合对DataFrame进行分组 pd.Grouper 对象。

In [50]: arrays = [
   ....:     ["bar", "bar", "baz", "baz", "foo", "foo", "qux", "qux"],
   ....:     ["one", "two", "one", "two", "one", "two", "one", "two"],
   ....: ]
   ....: 

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

In [52]: df = pd.DataFrame({"A": [1, 1, 1, 1, 2, 2, 3, 3], "B": np.arange(8)}, index=index)

In [53]: df
Out[53]: 
              A  B
first second      
bar   one     1  0
      two     1  1
baz   one     1  2
      two     1  3
foo   one     2  4
      two     2  5
qux   one     3  6
      two     3  7

以下示例分组 dfsecond 指标级别和 A 列。

In [54]: df.groupby([pd.Grouper(level=1), "A"]).sum()
Out[54]: 
          B
second A   
one    1  2
       2  4
       3  6
two    1  4
       2  5
       3  7

索引级别也可以按名称指定。

In [55]: df.groupby([pd.Grouper(level="second"), "A"]).sum()
Out[55]: 
          B
second A   
one    1  2
       2  4
       3  6
two    1  4
       2  5
       3  7

索引级名称可以直接指定为键 groupby

In [56]: df.groupby(["second", "A"]).sum()
Out[56]: 
          B
second A   
one    1  2
       2  4
       3  6
two    1  4
       2  5
       3  7

GroupBy中的数据框列选择#

从DataFrame创建GroupBy对象后,您可能希望对每个列执行不同的操作。因此,使用 [] 与从DataFrame获取列类似,您可以执行以下操作:

In [57]: 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 [58]: df
Out[58]: 
     A      B         C         D
0  foo    one -0.575247  1.346061
1  bar    one  0.254161  1.511763
2  foo    two -1.143704  1.627081
3  bar  three  0.215897 -0.990582
4  foo    two  1.193555 -0.441652
5  bar    two -0.077118  1.211526
6  foo    one -0.408530  0.268520
7  foo  three -0.862495  0.024580

In [59]: grouped = df.groupby(["A"])

In [60]: grouped_C = grouped["C"]

In [61]: grouped_D = grouped["D"]

这主要是另一种选择的句法糖,而且要冗长得多:

In [62]: df["C"].groupby(df["A"])
Out[62]: <pandas.core.groupby.generic.SeriesGroupBy object at 0x7f0de8239de0>

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

遍历组#

有了GroupBy对象,遍历分组的数据非常自然,其功能类似于 itertools.groupby()

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

In [64]: for name, group in grouped:
   ....:     print(name)
   ....:     print(group)
   ....: 
bar
     A      B         C         D
1  bar    one  0.254161  1.511763
3  bar  three  0.215897 -0.990582
5  bar    two -0.077118  1.211526
foo
     A      B         C         D
0  foo    one -0.575247  1.346061
2  foo    two -1.143704  1.627081
4  foo    two  1.193555 -0.441652
6  foo    one -0.408530  0.268520
7  foo  three -0.862495  0.024580

在按多个键分组的情况下,组名将是一个元组:

In [65]: for name, group in df.groupby(['A', 'B']):
   ....:     print(name)
   ....:     print(group)
   ....: 
('bar', 'one')
     A    B         C         D
1  bar  one  0.254161  1.511763
('bar', 'three')
     A      B         C         D
3  bar  three  0.215897 -0.990582
('bar', 'two')
     A    B         C         D
5  bar  two -0.077118  1.211526
('foo', 'one')
     A    B         C         D
0  foo  one -0.575247  1.346061
6  foo  one -0.408530  0.268520
('foo', 'three')
     A      B         C        D
7  foo  three -0.862495  0.02458
('foo', 'two')
     A    B         C         D
2  foo  two -1.143704  1.627081
4  foo  two  1.193555 -0.441652

看见 遍历组

选择一个组#

可以使用以下命令选择单个组 get_group()

In [66]: grouped.get_group("bar")
Out[66]: 
     A      B         C         D
1  bar    one  0.254161  1.511763
3  bar  three  0.215897 -0.990582
5  bar    two -0.077118  1.211526

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

In [67]: df.groupby(["A", "B"]).get_group(("bar", "one"))
Out[67]: 
     A    B         C         D
1  bar  one  0.254161  1.511763

聚合#

一旦创建了GroupBy对象,就可以使用几种方法对分组的数据执行计算。这些操作类似于 aggregating APIwindow API ,以及 resample API

一个明显的例子是通过 aggregate() 或等同于 agg() 方法:

In [68]: grouped = df.groupby("A")

In [69]: grouped.aggregate(np.sum)
Out[69]: 
            C         D
A                      
bar  0.392940  1.732707
foo -1.796421  2.824590

In [70]: grouped = df.groupby(["A", "B"])

In [71]: grouped.aggregate(np.sum)
Out[71]: 
                  C         D
A   B                        
bar one    0.254161  1.511763
    three  0.215897 -0.990582
    two   -0.077118  1.211526
foo one   -0.983776  1.614581
    three -0.862495  0.024580
    two    0.049851  1.185429

如您所见,聚合的结果将使用组名作为分组轴上的新索引。在多个密钥的情况下,结果是一个 MultiIndex 默认情况下,虽然可以使用 as_index 选项:

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

In [73]: grouped.aggregate(np.sum)
Out[73]: 
     A      B         C         D
0  bar    one  0.254161  1.511763
1  bar  three  0.215897 -0.990582
2  bar    two -0.077118  1.211526
3  foo    one -0.983776  1.614581
4  foo  three -0.862495  0.024580
5  foo    two  0.049851  1.185429

In [74]: df.groupby("A", as_index=False).sum()
Out[74]: 
     A         C         D
0  bar  0.392940  1.732707
1  foo -1.796421  2.824590

请注意,您可以使用 reset_index DataFrame函数以达到相同的结果,将列名存储在 MultiIndex

In [75]: df.groupby(["A", "B"]).sum().reset_index()
Out[75]: 
     A      B         C         D
0  bar    one  0.254161  1.511763
1  bar  three  0.215897 -0.990582
2  bar    two -0.077118  1.211526
3  foo    one -0.983776  1.614581
4  foo  three -0.862495  0.024580
5  foo    two  0.049851  1.185429

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

In [76]: grouped.size()
Out[76]: 
     A      B  size
0  bar    one     1
1  bar  three     1
2  bar    two     1
3  foo    one     2
4  foo  three     1
5  foo    two     2
In [77]: grouped.describe()
Out[77]: 
      C                                                                           D                                                                      
  count      mean       std       min       25%       50%       75%       max count      mean       std       min       25%       50%       75%       max
0   1.0  0.254161       NaN  0.254161  0.254161  0.254161  0.254161  0.254161   1.0  1.511763       NaN  1.511763  1.511763  1.511763  1.511763  1.511763
1   1.0  0.215897       NaN  0.215897  0.215897  0.215897  0.215897  0.215897   1.0 -0.990582       NaN -0.990582 -0.990582 -0.990582 -0.990582 -0.990582
2   1.0 -0.077118       NaN -0.077118 -0.077118 -0.077118 -0.077118 -0.077118   1.0  1.211526       NaN  1.211526  1.211526  1.211526  1.211526  1.211526
3   2.0 -0.491888  0.117887 -0.575247 -0.533567 -0.491888 -0.450209 -0.408530   2.0  0.807291  0.761937  0.268520  0.537905  0.807291  1.076676  1.346061
4   1.0 -0.862495       NaN -0.862495 -0.862495 -0.862495 -0.862495 -0.862495   1.0  0.024580       NaN  0.024580  0.024580  0.024580  0.024580  0.024580
5   2.0  0.024925  1.652692 -1.143704 -0.559389  0.024925  0.609240  1.193555   2.0  0.592714  1.462816 -0.441652  0.075531  0.592714  1.109898  1.627081

另一个聚合示例是计算每个组的唯一值的数量。这类似于 value_counts 函数,只是它只计算唯一的值。

In [78]: ll = [['foo', 1], ['foo', 2], ['foo', 2], ['bar', 1], ['bar', 1]]

In [79]: df4 = pd.DataFrame(ll, columns=["A", "B"])

In [80]: df4
Out[80]: 
     A  B
0  foo  1
1  foo  2
2  foo  2
3  bar  1
4  bar  1

In [81]: df4.groupby("A")["B"].nunique()
Out[81]: 
A
bar    1
foo    2
Name: B, dtype: int64

备注

聚合函数 不会 返回要聚合的组(如果它们已命名 ,何时 as_index=True ,默认设置。分组的列将是 指数 返回的对象的。

Passing as_index=False will return the groups that you are aggregating over, if they are named columns.

聚合函数是那些降低返回对象的维度的函数。下表列出了一些常见的聚合函数:

功能

描述

mean()

群的计算平均值

sum()

计算组值总和

size()

计算组大小

count()

计算组的计数

std()

组的标准差

var()

计算群方差

sem()

分组平均数的标准差

describe()

生成描述性统计信息

first()

计算组值中的第一个

last()

计算组值的最后一个

nth()

取第n个值,如果n是列表,则取一个子集

min()

计算组值的最小值

max()

计算组值最大值

上面的聚合函数将排除NA值。任何函数都可以减少 Series TO标量值是一个聚合函数,并且可以工作,下面是一个简单的例子 df.groupby('A').agg(lambda ser: 1) 。请注意, nth() 可以充当减速剂 or 过滤器,请参见 here

同时应用多个功能#

分组的 Series 您还可以传递用于聚合的函数列表或DICT,输出DataFrame:

In [82]: grouped = df.groupby("A")

In [83]: grouped["C"].agg([np.sum, np.mean, np.std])
Out[83]: 
          sum      mean       std
A                                
bar  0.392940  0.130980  0.181231
foo -1.796421 -0.359284  0.912265

关于分组的 DataFrame 中,您可以传递要应用于每一列的函数列表,这将生成具有分层索引的聚合结果:

In [84]: grouped[["C", "D"]].agg([np.sum, np.mean, np.std])
Out[84]: 
            C                             D                    
          sum      mean       std       sum      mean       std
A                                                              
bar  0.392940  0.130980  0.181231  1.732707  0.577569  1.366330
foo -1.796421 -0.359284  0.912265  2.824590  0.564918  0.884785

产生的聚合是以函数本身命名的。如果需要重命名,则可以为 Series 如下所示:

In [85]: (
   ....:     grouped["C"]
   ....:     .agg([np.sum, np.mean, np.std])
   ....:     .rename(columns={"sum": "foo", "mean": "bar", "std": "baz"})
   ....: )
   ....: 
Out[85]: 
          foo       bar       baz
A                                
bar  0.392940  0.130980  0.181231
foo -1.796421 -0.359284  0.912265

对于分组的 DataFrame ,您可以使用类似的方式重命名:

In [86]: (
   ....:     grouped[["C", "D"]].agg([np.sum, np.mean, np.std]).rename(
   ....:         columns={"sum": "foo", "mean": "bar", "std": "baz"}
   ....:     )
   ....: )
   ....: 
Out[86]: 
            C                             D                    
          foo       bar       baz       foo       bar       baz
A                                                              
bar  0.392940  0.130980  0.181231  1.732707  0.577569  1.366330
foo -1.796421 -0.359284  0.912265  2.824590  0.564918  0.884785

备注

通常,输出列名应该是唯一的。不能对同一列应用相同的函数(或两个同名的函数)。

In [87]: grouped["C"].agg(["sum", "sum"])
Out[87]: 
          sum       sum
A                      
bar  0.392940  0.392940
foo -1.796421 -1.796421

Pandas does 允许您提供多个lambdas。在这种情况下,Pandas将破坏(无名的)lambda函数的名称,将 _<i> 每个后续的Lambda。

In [88]: grouped["C"].agg([lambda x: x.max() - x.min(), lambda x: x.median() - x.mean()])
Out[88]: 
     <lambda_0>  <lambda_1>
A                          
bar    0.331279    0.084917
foo    2.337259   -0.215962

命名聚合#

0.25.0 新版功能.

支持特定于列的聚合 控制输出列名 ,Pandas接受特殊的语法 GroupBy.agg() ,称为“命名聚合”,其中

  • 关键字是 输出 列名

  • 这些值是元组,其第一个元素是要选择的列,第二个元素是要应用于该列的聚合。Pandas提供了 pandas.NamedAgg 带字段的命名元组 ['column', 'aggfunc'] 为了更清楚地说明论点是什么。通常,聚合可以是可调用的别名或字符串别名。

In [89]: animals = pd.DataFrame(
   ....:     {
   ....:         "kind": ["cat", "dog", "cat", "dog"],
   ....:         "height": [9.1, 6.0, 9.5, 34.0],
   ....:         "weight": [7.9, 7.5, 9.9, 198.0],
   ....:     }
   ....: )
   ....: 

In [90]: animals
Out[90]: 
  kind  height  weight
0  cat     9.1     7.9
1  dog     6.0     7.5
2  cat     9.5     9.9
3  dog    34.0   198.0

In [91]: animals.groupby("kind").agg(
   ....:     min_height=pd.NamedAgg(column="height", aggfunc="min"),
   ....:     max_height=pd.NamedAgg(column="height", aggfunc="max"),
   ....:     average_weight=pd.NamedAgg(column="weight", aggfunc=np.mean),
   ....: )
   ....: 
Out[91]: 
      min_height  max_height  average_weight
kind                                        
cat          9.1         9.5            8.90
dog          6.0        34.0          102.75

pandas.NamedAgg 只是一个 namedtuple 。也允许使用普通元组。

In [92]: animals.groupby("kind").agg(
   ....:     min_height=("height", "min"),
   ....:     max_height=("height", "max"),
   ....:     average_weight=("weight", np.mean),
   ....: )
   ....: 
Out[92]: 
      min_height  max_height  average_weight
kind                                        
cat          9.1         9.5            8.90
dog          6.0        34.0          102.75

如果所需的输出列名不是有效的Python关键字,则构建字典并解压缩关键字参数

In [93]: animals.groupby("kind").agg(
   ....:     **{
   ....:         "total weight": pd.NamedAgg(column="weight", aggfunc=sum)
   ....:     }
   ....: )
   ....: 
Out[93]: 
      total weight
kind              
cat           17.8
dog          205.5

其他关键字参数不会传递给聚合函数。只有成对的 (column, aggfunc) 应作为 **kwargs 。如果聚合函数需要其他参数,请将其部分应用于 functools.partial()

备注

对于Python3.5和更早版本,顺序为 **kwargs 在a中,函数未被保留。这意味着输出列的顺序将不一致。为了确保一致的排序,对于Python3.5,将始终对键(以及输出列)进行排序。

命名聚合也适用于按系列分组的聚合。在本例中,没有列选择,因此值只是函数。

In [94]: animals.groupby("kind").height.agg(
   ....:     min_height="min",
   ....:     max_height="max",
   ....: )
   ....: 
Out[94]: 
      min_height  max_height
kind                        
cat          9.1         9.5
dog          6.0        34.0

对DataFrame列应用不同的函数#

通过将判决书传递给 aggregate 您可以对DataFrame的列应用不同的聚合:

In [95]: grouped.agg({"C": np.sum, "D": lambda x: np.std(x, ddof=1)})
Out[95]: 
            C         D
A                      
bar  0.392940  1.366330
foo -1.796421  0.884785

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

In [96]: grouped.agg({"C": "sum", "D": "std"})
Out[96]: 
            C         D
A                      
bar  0.392940  1.366330
foo -1.796421  0.884785

Cython优化的聚合函数#

一些常见的聚合,目前仅 summeanstd ,以及 sem ,已经优化了Cython实现:

In [97]: df.groupby("A").sum()
Out[97]: 
            C         D
A                      
bar  0.392940  1.732707
foo -1.796421  2.824590

In [98]: df.groupby(["A", "B"]).mean()
Out[98]: 
                  C         D
A   B                        
bar one    0.254161  1.511763
    three  0.215897 -0.990582
    two   -0.077118  1.211526
foo one   -0.491888  0.807291
    three -0.862495  0.024580
    two    0.024925  0.592714

当然了 summean 都是在Pandas对象上实现的,所以上面的代码即使没有特殊的版本也可以通过调度(见下文)工作。

使用用户定义函数的聚合#

用户还可以为定制聚合提供他们自己的函数。使用用户定义函数(UDF)聚合时,UDF不应更改提供的 Series ,请参见 使用用户定义函数(UDF)方法进行变异 了解更多信息。

In [99]: animals.groupby("kind")[["height"]].agg(lambda x: set(x))
Out[99]: 
           height
kind             
cat    {9.1, 9.5}
dog   {34.0, 6.0}

结果数据类型将反映聚合函数的数据类型。如果来自不同组的结果具有不同的数据类型,则将以相同的方式确定公共数据类型 DataFrame 建筑。

In [100]: animals.groupby("kind")[["height"]].agg(lambda x: x.astype(int).sum())
Out[100]: 
      height
kind        
cat       18
dog       40

转型#

这个 transform 方法返回的对象的索引与要分组的对象相同(大小相同)。转换函数必须:

  • 返回与群组块大小相同或可广播到群组块大小的结果(例如,标量, grouped.transform(lambda x: x.iloc[-1]) )。

  • 在组块上逐列操作。使用chunk.Apply将转换应用于第一个组块。

  • Not perform in-place operations on the group chunk. Group chunks should be treated as immutable, and changes to a group chunk may produce unexpected results. For example, when using fillna, inplace must be False (grouped.transform(lambda x: x.fillna(inplace=False))).

  • (可选)对整个组块进行操作。如果支持此操作,则使用从 第二 大块头。

类似于 使用用户定义函数的聚合 生成的数据类型将反映转换函数的数据类型。如果来自不同组的结果具有不同的数据类型,则将以相同的方式确定公共数据类型 DataFrame 建筑。

假设我们希望标准化每个组中的数据:

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

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

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

In [104]: ts.head()
Out[104]: 
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 [105]: ts.tail()
Out[105]: 
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 [106]: transformed = ts.groupby(lambda x: x.year).transform(
   .....:     lambda x: (x - x.mean()) / x.std()
   .....: )
   .....: 

我们希望结果现在在每组内具有平均值0和标准差1,我们可以很容易地进行检查:

# Original Data
In [107]: grouped = ts.groupby(lambda x: x.year)

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

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

# Transformed Data
In [110]: grouped_trans = transformed.groupby(lambda x: x.year)

In [111]: grouped_trans.mean()
Out[111]: 
2000   -4.890857e-16
2001   -1.545187e-16
2002    4.136282e-16
dtype: float64

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

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

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

In [114]: compare.plot()
Out[114]: <AxesSubplot:>
../_images/groupby_transform_plot.png

具有低维输出的变换函数被广播以匹配输入阵列的形状。

In [115]: ts.groupby(lambda x: x.year).transform(lambda x: x.max() - x.min())
Out[115]: 
2000-01-08    0.623893
2000-01-09    0.623893
2000-01-10    0.623893
2000-01-11    0.623893
2000-01-12    0.623893
                ...   
2002-09-30    0.558275
2002-10-01    0.558275
2002-10-02    0.558275
2002-10-03    0.558275
2002-10-04    0.558275
Freq: D, Length: 1001, dtype: float64

或者,可以使用内置方法来产生相同的输出。

In [116]: max = ts.groupby(lambda x: x.year).transform("max")

In [117]: min = ts.groupby(lambda x: x.year).transform("min")

In [118]: max - min
Out[118]: 
2000-01-08    0.623893
2000-01-09    0.623893
2000-01-10    0.623893
2000-01-11    0.623893
2000-01-12    0.623893
                ...   
2002-09-30    0.558275
2002-10-01    0.558275
2002-10-02    0.558275
2002-10-03    0.558275
2002-10-04    0.558275
Freq: D, Length: 1001, dtype: float64

另一种常见的数据转换是用分组平均值替换缺失的数据。

In [119]: data_df
Out[119]: 
            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
..        ...       ...       ...
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 [120]: countries = np.array(["US", "UK", "GR", "JP"])

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

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

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

In [124]: transformed = grouped.transform(lambda x: x.fillna(x.mean()))

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

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

In [126]: grouped.mean()  # original group means
Out[126]: 
           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 [127]: grouped_trans.mean()  # transformation did not change group means
Out[127]: 
           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 [128]: grouped.count()  # original has some missing data points
Out[128]: 
      A    B    C
GR  209  217  189
JP  240  255  217
UK  216  231  193
US  239  250  217

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

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

备注

某些函数在应用于GroupBy对象时会自动转换输入,但会返回与原始形状相同的对象。传球 as_index=False 不会影响这些转换方法。

例如: fillna, ffill, bfill, shift.

In [131]: grouped.ffill()
Out[131]: 
            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
..        ...       ...       ...
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]

窗口和重采样操作#

它可以使用 resample()expanding()rolling() 就像团购中的方法一样。

下面的示例将应用 rolling() 方法以A柱为基团,对B柱的样品进行分析。

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

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

[20 rows x 2 columns]

In [134]: df_re.groupby("A").rolling(4).B.mean()
Out[134]: 
A    
1  0      NaN
   1      NaN
   2      NaN
   3      1.5
   4      2.5
         ... 
5  15    13.5
   16    14.5
   17    15.5
   18    16.5
   19    17.5
Name: B, Length: 20, dtype: float64

这个 expanding() 方法将累加给定的操作。 (sum() 在该示例中)用于每个特定组的所有成员。

In [135]: df_re.groupby("A").expanding().sum()
Out[135]: 
          B
A          
1 0     0.0
  1     1.0
  2     3.0
  3     6.0
  4    10.0
...     ...
5 15   75.0
  16   91.0
  17  108.0
  18  126.0
  19  145.0

[20 rows x 1 columns]

假设您想要使用 resample() 方法来获取每组数据帧中的每日频率,并希望使用 ffill() 方法。

In [136]: 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 [137]: df_re
Out[137]: 
            group  val
date                  
2016-01-03      1    5
2016-01-10      1    6
2016-01-17      2    7
2016-01-24      2    8

In [138]: df_re.groupby("group").resample("1D").ffill()
Out[138]: 
                  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
...                 ...  ...
2     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]

滤过#

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

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

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

的论据 filter 必须是一个函数,该函数作为一个整体应用于组,返回 TrueFalse

另一个有用的操作是筛选出属于只有几个成员的组的元素。

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

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

或者,我们可以返回一个类似索引的对象,其中未通过筛选器的组由NAN填充,而不是删除违规的组。

In [143]: dff.groupby("B").filter(lambda x: len(x) > 2, dropna=False)
Out[143]: 
     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

对于具有多列的DataFrame,筛选器应显式指定一列作为筛选条件。

In [144]: dff["C"] = np.arange(8)

In [145]: dff.groupby("B").filter(lambda x: len(x["C"]) > 2)
Out[145]: 
   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 [146]: dff.groupby("B").head(2)
Out[146]: 
   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

调度到实例方法#

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

In [147]: grouped = df.groupby("A")

In [148]: grouped.agg(lambda x: x.std())
Out[148]: 
            C         D
A                      
bar  0.181231  1.366330
foo  0.912265  0.884785

但是,它相当冗长,如果您需要传递额外的参数,它可能会很凌乱。利用一点元编程的智慧,GroupBy现在能够将方法调用“分派”给组:

In [149]: grouped.std()
Out[149]: 
            C         D
A                      
bar  0.181231  1.366330
foo  0.912265  0.884785

这里实际发生的情况是正在生成一个函数包装器。调用时,它接受任何传递的参数,并使用每个组上的任何参数调用函数(在上面的示例中, std 功能)。然后将结果组合在一起,很大程度上类似于 aggtransform (它实际上使用 apply 要推断粘合,请参阅下面的文档)。这使得一些操作可以相当简洁地执行:

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

In [151]: tsdf.iloc[::2] = np.nan

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

In [153]: grouped.fillna(method="pad")
Out[153]: 
                   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
...              ...       ...       ...
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 在小组里。

这个 nlargestnsmallest 方法适用于 Series Style Group By:

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

In [155]: g = pd.Series(list("abababab"))

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

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

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

软性 apply#

对分组数据的某些操作可能既不适合聚合类别,也不适合转换类别。或者,您可能只是想让GroupBy推断如何组合结果。对于这些,请使用 apply 函数,该函数可以替换这两个 aggregatetransform 在许多标准用例中。然而, apply 可以处理一些特殊的用例。

备注

apply 可以充当减速器、变压器、 or 筛选器函数,具体取决于传递给它的内容。它可能取决于传递的函数以及您要分组的确切内容。因此,可以将分组的列包括在输出中并设置索引。

In [159]: df
Out[159]: 
     A      B         C         D
0  foo    one -0.575247  1.346061
1  bar    one  0.254161  1.511763
2  foo    two -1.143704  1.627081
3  bar  three  0.215897 -0.990582
4  foo    two  1.193555 -0.441652
5  bar    two -0.077118  1.211526
6  foo    one -0.408530  0.268520
7  foo  three -0.862495  0.024580

In [160]: grouped = df.groupby("A")

# could also just call .describe()
In [161]: grouped["C"].apply(lambda x: x.describe())
Out[161]: 
A         
bar  count    3.000000
     mean     0.130980
     std      0.181231
     min     -0.077118
     25%      0.069390
                ...   
foo  min     -1.143704
     25%     -0.862495
     50%     -0.575247
     75%     -0.408530
     max      1.193555
Name: C, Length: 16, dtype: float64

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

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

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

apply 可以对应用函数的返回值进行运算,该返回值本身就是一个序列,并可能将结果向上强制转换为DataFrame:

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

In [165]: s = pd.Series(np.random.rand(5))

In [166]: s
Out[166]: 
0    0.321438
1    0.493496
2    0.139505
3    0.910103
4    0.194158
dtype: float64

In [167]: s.apply(f)
Out[167]: 
          x       x^2
0  0.321438  0.103323
1  0.493496  0.243538
2  0.139505  0.019462
3  0.910103  0.828287
4  0.194158  0.037697

使用控制已分组的列的放置 group_keys#

备注

如果 group_keys=True 调用时指定 groupby() ,传递给的函数 apply 返回类似索引的输出将把组键添加到结果索引中。只有当应用函数的结果与输入的索引不同时,以前版本的PANDA才会添加组键。如果 group_keys 如果未指定,则不会为类似索引的输出添加组键。在未来,这种行为将改变为始终尊重 group_keys ,它缺省为 True

在 1.5.0 版更改.

要控制已分组的列是否包括在索引中,可以使用参数 group_keys 。比较

In [168]: df.groupby("A", group_keys=True).apply(lambda x: x)
Out[168]: 
         A      B         C         D
A                                    
bar 1  bar    one  0.254161  1.511763
    3  bar  three  0.215897 -0.990582
    5  bar    two -0.077118  1.211526
foo 0  foo    one -0.575247  1.346061
    2  foo    two -1.143704  1.627081
    4  foo    two  1.193555 -0.441652
    6  foo    one -0.408530  0.268520
    7  foo  three -0.862495  0.024580

使用

In [169]: df.groupby("A", group_keys=False).apply(lambda x: x)
Out[169]: 
     A      B         C         D
0  foo    one -0.575247  1.346061
1  bar    one  0.254161  1.511763
2  foo    two -1.143704  1.627081
3  bar  three  0.215897 -0.990582
4  foo    two  1.193555 -0.441652
5  bar    two -0.077118  1.211526
6  foo    one -0.408530  0.268520
7  foo  three -0.862495  0.024580

类似于 使用用户定义函数的聚合 生成的数据类型将反映Apply函数的数据类型。如果来自不同组的结果具有不同的数据类型,则将以相同的方式确定公共数据类型 DataFrame 建筑。

Numba加速例程#

1.1 新版功能.

如果 Numba 作为可选依赖项安装,则 transformaggregate 方法支持 engine='numba'engine_kwargs 争论。看见 enhancing performance with Numba 以了解参数的一般用法和性能注意事项。

The function signature must start with values, index exactly as the data belonging to each group will be passed into values, and the group index will be passed into index.

警告

在使用时 engine='numba' ,在内部不会有“后退”的行为。组数据和组索引将作为NumPy数组传递给JITed用户定义函数,不会尝试其他执行尝试。

其他有用的功能#

自动排除“滋扰”栏#

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

In [170]: df
Out[170]: 
     A      B         C         D
0  foo    one -0.575247  1.346061
1  bar    one  0.254161  1.511763
2  foo    two -1.143704  1.627081
3  bar  three  0.215897 -0.990582
4  foo    two  1.193555 -0.441652
5  bar    two -0.077118  1.211526
6  foo    one -0.408530  0.268520
7  foo  three -0.862495  0.024580

假设我们想要计算标准偏差除以 A 纵队。有一个小问题,即我们不关心列中的数据 B 。我们将其称为“讨厌”专栏。如果传递的聚合函数不能应用于某些列,麻烦的列将被(静默)删除。因此,这不会造成任何问题:

In [171]: df.groupby("A").std()
Out[171]: 
            C         D
A                      
bar  0.181231  1.366330
foo  0.912265  0.884785

请注意, df.groupby('A').colname.std(). 的效率比 df.groupby('A').std().colname ,因此,如果聚合函数的结果只对一列感兴趣(此处 colname ),则可以对其进行过滤 在此之前 应用聚合函数。

备注

任何对象列,如果它包含数值,如 Decimal 对象,被认为是一个“扰民”栏目。它们在GROUPBY中自动从聚合函数中排除。

如果您确实希望将小数列或对象列包含在具有其他非有害数据类型的聚合中,则必须显式执行此操作。

In [172]: from decimal import Decimal

In [173]: df_dec = pd.DataFrame(
   .....:     {
   .....:         "id": [1, 2, 1, 2],
   .....:         "int_column": [1, 2, 3, 4],
   .....:         "dec_column": [
   .....:             Decimal("0.50"),
   .....:             Decimal("0.15"),
   .....:             Decimal("0.25"),
   .....:             Decimal("0.40"),
   .....:         ],
   .....:     }
   .....: )
   .....: 

# Decimal columns can be sum'd explicitly by themselves...
In [174]: df_dec.groupby(["id"])[["dec_column"]].sum()
Out[174]: 
   dec_column
id           
1        0.75
2        0.55

# ...but cannot be combined with standard data types or they will be excluded
In [175]: df_dec.groupby(["id"])[["int_column", "dec_column"]].sum()
Out[175]: 
    int_column
id            
1            4
2            6

# Use .agg function to aggregate over standard and "nuisance" data types
# at the same time
In [176]: df_dec.groupby(["id"]).agg({"int_column": "sum", "dec_column": "sum"})
Out[176]: 
    int_column dec_column
id                       
1            4       0.75
2            6       0.55

(未观察到的)类别值的处理#

在使用 Categorical 石斑鱼(作为单个石斑鱼,或作为多个石斑鱼的一部分), observed 关键字控制是否返回所有可能的分组器值的笛卡尔乘积 (observed=False )或仅为观察到的石斑鱼 (observed=True )。

显示所有值:

In [177]: pd.Series([1, 1, 1]).groupby(
   .....:     pd.Categorical(["a", "a", "a"], categories=["a", "b"]), observed=False
   .....: ).count()
   .....: 
Out[177]: 
a    3
b    0
dtype: int64

仅显示观察到的值:

In [178]: pd.Series([1, 1, 1]).groupby(
   .....:     pd.Categorical(["a", "a", "a"], categories=["a", "b"]), observed=True
   .....: ).count()
   .....: 
Out[178]: 
a    3
dtype: int64

返回的分组的数据类型将 始终 包括 all 被分组的类别。

In [179]: s = (
   .....:     pd.Series([1, 1, 1])
   .....:     .groupby(pd.Categorical(["a", "a", "a"], categories=["a", "b"]), observed=False)
   .....:     .count()
   .....: )
   .....: 

In [180]: s.index.dtype
Out[180]: CategoricalDtype(categories=['a', 'b'], ordered=False)

NAT和NAT组处理#

如果分组密钥中有任何NAN或NAT值,则会自动排除这些值。也就是说,永远不会有“NA群”或“NA群”。在较老版本的Pandas中情况并非如此,但无论如何,用户通常都会放弃NA组(支持它是一个令人头疼的实现问题)。

用有序因子进行分组#

类别变量表示为Pandas的实例 Categorical 类可以用作组密钥。如果是这样,级别的顺序将保持不变:

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

In [182]: factor = pd.qcut(data, [0, 0.25, 0.5, 0.75, 1.0])

In [183]: data.groupby(factor).mean()
Out[183]: 
(-2.645, -0.523]   -1.362896
(-0.523, 0.0296]   -0.260266
(0.0296, 0.654]     0.361802
(0.654, 2.21]       1.073801
dtype: float64

使用Grouper规范进行分组#

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

In [184]: import datetime

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

按所需频率的特定列分组。这类似于重采样。

In [187]: df.groupby([pd.Grouper(freq="1M", key="Date"), "Buyer"]).sum()
Out[187]: 
                  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 [188]: df = df.set_index("Date")

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

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

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

取每组的第一行#

就像DataFrame或Series一样,您可以在Groupby上调用Head和Tail:

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

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

In [194]: g = df.groupby("A")

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

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

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

取每组的第n排#

若要从DataFrame或Series中选择第n项,请使用 nth() 。这是一种Reduction方法,如果您为n传递一个int,则将为每个组返回一行(或不返回任何行):

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

In [198]: g = df.groupby("A")

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

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

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

如果要选择第n个非空项,请使用 dropna 科瓦格。对于DataFrame,这应该是 'any''all' 就像您会传递给Dropna一样:

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

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

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

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

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

与其他方法一样,传递 as_index=False ,将实现筛选,这将返回分组的行。

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

In [208]: g = df.groupby("A", as_index=False)

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

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

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

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

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

# get the first, 4th, and last date index for each month
In [213]: df.groupby([df.index.year, df.index.month]).nth([0, 3, -1])
Out[213]: 
        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

枚举组项目#

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

In [214]: dfg = pd.DataFrame(list("aaabba"), columns=["A"])

In [215]: dfg
Out[215]: 
   A
0  a
1  a
2  a
3  b
4  b
5  a

In [216]: dfg.groupby("A").cumcount()
Out[216]: 
0    0
1    1
2    2
3    0
4    1
5    3
dtype: int64

In [217]: dfg.groupby("A").cumcount(ascending=False)
Out[217]: 
0    3
1    2
2    1
3    1
4    0
5    0
dtype: int64

枚举组#

查看组的顺序(与由给定的组内行的顺序相反 cumcount )您可以使用 ngroup()

请注意,分配给组的数字与迭代Groupby对象时看到组的顺序匹配,而不是与第一次观察到组的顺序匹配。

In [218]: dfg = pd.DataFrame(list("aaabba"), columns=["A"])

In [219]: dfg
Out[219]: 
   A
0  a
1  a
2  a
3  b
4  b
5  a

In [220]: dfg.groupby("A").ngroup()
Out[220]: 
0    0
1    0
2    0
3    1
4    1
5    0
dtype: int64

In [221]: dfg.groupby("A").ngroup(ascending=False)
Out[221]: 
0    1
1    1
2    1
3    0
4    0
5    1
dtype: int64

标绘#

Groupby还使用一些绘图方法。例如,假设我们怀疑DataFrame中的一些功能可能因组而不同,在本例中,组为“B”的列1中的值平均高出3。

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

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

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

In [225]: df.loc[df["g"] == "B", 1] += 3

我们可以很容易地通过框图将其可视化:

In [226]: df.groupby("g").boxplot()
Out[226]: 
A         AxesSubplot(0.1,0.15;0.363636x0.75)
B    AxesSubplot(0.536364,0.15;0.363636x0.75)
dtype: object
../_images/groupby_boxplot.png

调用的结果 boxplot 是一个字典,其键是我们的分组列的值 g (“A”和“B”)。生成的词典的值可以由 return_type 的关键字 boxplot 。请参阅 visualization documentation 想要更多。

警告

由于历史原因, df.groupby("g").boxplot() 并不等同于 df.boxplot(by="g") 。看见 here 寻求一个解释。

管道函数调用#

类似于提供的功能 DataFrameSeries 、需要执行的函数 GroupBy 对象可以使用 pipe 方法,以实现更清晰、更易读的语法。阅读关于 .pipe 一般而言,请参见 here

组合 .groupby.pipe 在需要重用GroupBy对象时通常很有用。

例如,假设有一个DataFrame,其中包含商店、产品、收入和销售量等列。我们想做一个GroupWise计算 价格 (即收入/数量)每个商店和每个产品。我们可以在一个多步骤的操作中实现这一点,但是用管道的方式来表达它可以使代码更具可读性。首先,我们设置数据:

In [227]: n = 1000

In [228]: df = pd.DataFrame(
   .....:     {
   .....:         "Store": np.random.choice(["Store_1", "Store_2"], n),
   .....:         "Product": np.random.choice(["Product_1", "Product_2"], n),
   .....:         "Revenue": (np.random.random(n) * 50 + 10).round(2),
   .....:         "Quantity": np.random.randint(1, 10, size=n),
   .....:     }
   .....: )
   .....: 

In [229]: df.head(2)
Out[229]: 
     Store    Product  Revenue  Quantity
0  Store_2  Product_1    26.12         1
1  Store_2  Product_1    28.86         1

现在,要查找每个商店/产品的价格,我们只需执行以下操作:

In [230]: (
   .....:     df.groupby(["Store", "Product"])
   .....:     .pipe(lambda grp: grp.Revenue.sum() / grp.Quantity.sum())
   .....:     .unstack()
   .....:     .round(2)
   .....: )
   .....: 
Out[230]: 
Product  Product_1  Product_2
Store                        
Store_1       6.82       7.05
Store_2       6.30       6.64

当您想要将分组的对象传递给某个任意函数时,管道也可以很有表现力,例如:

In [231]: def mean(groupby):
   .....:     return groupby.mean()
   .....: 

In [232]: df.groupby(["Store", "Product"]).pipe(mean)
Out[232]: 
                     Revenue  Quantity
Store   Product                       
Store_1 Product_1  34.622727  5.075758
        Product_2  35.482815  5.029630
Store_2 Product_1  32.972837  5.237589
        Product_2  34.684360  5.224000

哪里 mean 获取GroupBy对象,并分别计算每个Store-Product组合的Revenue和Quantity列的平均值。这个 mean 函数可以是接受GroupBy对象的任何函数; .pipe 将GroupBy对象作为参数传递给您指定的函数。

示例#

按因素重新分组#

根据它们的总和对DataFrame的列进行重新分组,并对聚合的列求和。

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

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

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

多列因式分解#

通过使用 ngroup() ,我们可以用类似于以下方式提取有关组的信息 factorize() (如 reshaping API ),但这自然适用于混合类型和不同来源的多个柱。当组行之间的关系比它们的内容更重要时,这可以用作处理中类似于分类的中间步骤,或者作为只接受整数编码的算法的输入。(有关Pandas对完整分类数据的支持的更多信息,请参阅 Categorical introduction 以及 API documentation 。)

In [236]: dfg = pd.DataFrame({"A": [1, 1, 2, 3, 2], "B": list("aaaba")})

In [237]: dfg
Out[237]: 
   A  B
0  1  a
1  1  a
2  2  a
3  3  b
4  2  a

In [238]: dfg.groupby(["A", "B"]).ngroup()
Out[238]: 
0    0
1    0
2    1
3    2
4    1
dtype: int64

In [239]: dfg.groupby(["A", [0, 0, 0, 1, 1]]).ngroup()
Out[239]: 
0    0
1    0
2    1
3    3
4    2
dtype: int64

按索引器分组以重新采样数据#

重采样从已有的观测数据或从生成数据的模型生成新的假设样本(重采样)。这些新的样本与先前存在的样本相似。

为了对非日期时间的索引进行重采样,可以使用以下过程。

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

备注

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

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

In [241]: df
Out[241]: 
          0         1
0 -0.793893  0.321153
1  0.342250  1.618906
2 -0.975807  1.918201
3 -0.810847 -1.405919
4 -1.977759  0.461659
5  0.730057 -1.316938
6 -0.751328  0.528290
7 -0.257759 -1.081009
8  0.505895 -1.701948
9 -1.006349  0.020208

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

In [243]: df.groupby(df.index // 5).std()
Out[243]: 
          0         1
0  0.823647  1.312912
1  0.760109  0.942941

返回一个序列以传播名称#

对DataFrame列进行分组,计算一组指标并返回命名系列。系列名称用作列索引的名称。这在与重塑操作(如堆叠)配合使用时尤其有用,在这些操作中,列索引名称将用作插入的列的名称:

In [244]: 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 [245]: def compute_metrics(x):
   .....:     result = {"b_sum": x["b"].sum(), "c_mean": x["c"].mean()}
   .....:     return pd.Series(result, name="metrics")
   .....: 

In [246]: result = df.groupby("a").apply(compute_metrics)

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

In [248]: result.stack()
Out[248]: 
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