将多个功能应用于多个groupby列


221

文档展示了如何使用输出列名称作为键的字典一次在groupby对象上应用多个功能:

In [563]: grouped['D'].agg({'result1' : np.sum,
   .....:                   'result2' : np.mean})
   .....:
Out[563]: 
      result2   result1
A                      
bar -0.579846 -1.739537
foo -0.280588 -1.402938

但是,这仅适用于Series groupby对象。同样,当将字典类似地传递到groupby DataFrame时,它期望键是将应用该函数的列名。

我想做的是对多个列应用多个功能(但是某些列将被多次操作)。同样,某些函数将依赖于groupby对象中的其他列(如sumif函数)。我当前的解决方案是逐列进行操作,并使用类似于上面代码的代码,对依赖其他行的函数使用lambda。但这要花很长时间,(我认为花很长时间才能遍历groupby对象)。我必须对其进行更改,以便一次运行即可遍历整个groupby对象,但是我想知道熊猫中是否有内置的方法可以使此操作更加简洁。

例如,我尝试过类似

grouped.agg({'C_sum' : lambda x: x['C'].sum(),
             'C_std': lambda x: x['C'].std(),
             'D_sum' : lambda x: x['D'].sum()},
             'D_sumifC3': lambda x: x['D'][x['C'] == 3].sum(), ...)

但正如我所料,我收到一个KeyError(因为如果agg从DataFrame调用,则键必须是一列)。

是否有任何内置方法可以执行我想做的事情,或者可能添加了此功能,或者我只需要手动遍历groupby?

谢谢


2
如果您在2017年以后要解决此问题,请参阅下面答案,以了解将多个列汇总在一起的惯用方式。当前选择的答案中有多个不赞成使用的内容,即您不能再使用字典词典重命名groupby结果中的列。
泰德·彼得鲁

Answers:


282

当前接受的答案的后半部分已过时,并且有两个过时的建议。首先也是最重要的是,您无法再将字典词典传递给agg groupby方法。第二,永远不要使用.ix

如果您希望同时使用两个单独的列,则建议使用 apply隐式将DataFrame传递给应用函数的方法。让我们使用与上面类似的数据框

df = pd.DataFrame(np.random.rand(4,4), columns=list('abcd'))
df['group'] = [0, 0, 1, 1]
df

          a         b         c         d  group
0  0.418500  0.030955  0.874869  0.145641      0
1  0.446069  0.901153  0.095052  0.487040      0
2  0.843026  0.936169  0.926090  0.041722      1
3  0.635846  0.439175  0.828787  0.714123      1

从列名映射到聚合函数的字典仍然是执行聚合的理想方法。

df.groupby('group').agg({'a':['sum', 'max'], 
                         'b':'mean', 
                         'c':'sum', 
                         'd': lambda x: x.max() - x.min()})

              a                   b         c         d
            sum       max      mean       sum  <lambda>
group                                                  
0      0.864569  0.446069  0.466054  0.969921  0.341399
1      1.478872  0.843026  0.687672  1.754877  0.672401

如果您不喜欢该丑陋的lambda列名称,则可以使用常规函数,并为特殊__name__属性提供自定义名称,如下所示:

def max_min(x):
    return x.max() - x.min()

max_min.__name__ = 'Max minus Min'

df.groupby('group').agg({'a':['sum', 'max'], 
                         'b':'mean', 
                         'c':'sum', 
                         'd': max_min})

              a                   b         c             d
            sum       max      mean       sum Max minus Min
group                                                      
0      0.864569  0.446069  0.466054  0.969921      0.341399
1      1.478872  0.843026  0.687672  1.754877      0.672401

使用 apply和返回系列

现在,如果您有多个需要一起交互的列,则无法使用 agg,这会将Series隐式传递给聚合函数。当使用apply整个集团作为一个数据帧被传递给函数。

我建议制作一个自定义函数,以返回一系列所有聚合。使用系列索引作为新列的标签:

def f(x):
    d = {}
    d['a_sum'] = x['a'].sum()
    d['a_max'] = x['a'].max()
    d['b_mean'] = x['b'].mean()
    d['c_d_prodsum'] = (x['c'] * x['d']).sum()
    return pd.Series(d, index=['a_sum', 'a_max', 'b_mean', 'c_d_prodsum'])

df.groupby('group').apply(f)

         a_sum     a_max    b_mean  c_d_prodsum
group                                           
0      0.864569  0.446069  0.466054     0.173711
1      1.478872  0.843026  0.687672     0.630494

如果您爱上了MultiIndexes,仍然可以返回带有以下内容的Series:

    def f_mi(x):
        d = []
        d.append(x['a'].sum())
        d.append(x['a'].max())
        d.append(x['b'].mean())
        d.append((x['c'] * x['d']).sum())
        return pd.Series(d, index=[['a', 'a', 'b', 'c_d'], 
                                   ['sum', 'max', 'mean', 'prodsum']])

df.groupby('group').apply(f_mi)

              a                   b       c_d
            sum       max      mean   prodsum
group                                        
0      0.864569  0.446069  0.466054  0.173711
1      1.478872  0.843026  0.687672  0.630494

3
我喜欢使用返回序列的函数的模式。井井有条。
Stephen McAteer

2
这是我发现同时模拟多个列输入来聚合数据帧的唯一方法(上述c_d示例)
Blake

2
我对结果感到困惑,以a组内的总和0不应该0.418500 + 0.446069 = 0.864569吗?对于其他单元格也是如此,数字似乎没有加起来。在后面的示例中使用的底层数据帧可能会略有不同吗?
slackline

我经常将.size()与groupby一起使用以查看记录数。有没有办法使用agg:dict方法来做到这一点?我知道我可以对一个特定的字段进行计数,但我更希望计数与字段无关。
克里斯·德克

1
@slackline是的。我刚刚测试了它,它工作正常。Ted必须刚创建框架几次,并且由于它是通过随机数生成创建的,因此实际生成数据的df数据与计算中最终使用的数据不同
Lucas H

166

对于第一部分,您可以传递键的列名字典和值的函数列表:

In [28]: df
Out[28]:
          A         B         C         D         E  GRP
0  0.395670  0.219560  0.600644  0.613445  0.242893    0
1  0.323911  0.464584  0.107215  0.204072  0.927325    0
2  0.321358  0.076037  0.166946  0.439661  0.914612    1
3  0.133466  0.447946  0.014815  0.130781  0.268290    1

In [26]: f = {'A':['sum','mean'], 'B':['prod']}

In [27]: df.groupby('GRP').agg(f)
Out[27]:
            A                   B
          sum      mean      prod
GRP
0    0.719580  0.359790  0.102004
1    0.454824  0.227412  0.034060

更新1:

由于聚合函数适用于Series,因此对其他列名称的引用会丢失。为了解决这个问题,您可以引用整个数据框并使用lambda函数中的组索引对其进行索引。

这是一个骇人的解决方法:

In [67]: f = {'A':['sum','mean'], 'B':['prod'], 'D': lambda g: df.loc[g.index].E.sum()}

In [69]: df.groupby('GRP').agg(f)
Out[69]:
            A                   B         D
          sum      mean      prod  <lambda>
GRP
0    0.719580  0.359790  0.102004  1.170219
1    0.454824  0.227412  0.034060  1.182901

在此,结果“ D”列由总和“ E”值组成。

更新2:

我认为这是一种可以满足您要求的方法。首先创建一个自定义lambda函数。下面,g引用该组。汇总时,g将是一个系列。传递g.indexdf.ix[]从df中选择当前组。然后,我测试C列是否小于0.5。返回的布尔系列传递给g[]该布尔系列,该布尔系列仅选择那些符合条件的行。

In [95]: cust = lambda g: g[df.loc[g.index]['C'] < 0.5].sum()

In [96]: f = {'A':['sum','mean'], 'B':['prod'], 'D': {'my name': cust}}

In [97]: df.groupby('GRP').agg(f)
Out[97]:
            A                   B         D
          sum      mean      prod   my name
GRP
0    0.719580  0.359790  0.102004  0.204072
1    0.454824  0.227412  0.034060  0.570441

有趣的是,我还可以传递一个{funcname: func}as值而不是列表的字典来保留我的自定义名称。但是,无论哪种情况,我都无法传递lambda使用其他列的(例如lambda x: x['D'][x['C'] < 3].sum():“ KeyError:'D'”)。知道这是否可能吗?
beardc 2013年

我一直试图做到这一点,并且得到了错误KeyError: 'D'
Zelazny7 2013年

太酷了,我可以使用它df['A'].ix[g.index][df['C'] < 0].sum()。不过,这开始变得非常混乱。我认为,出于可读性考虑,手动循环可能更可取,而且我不确定是否有办法在agg参数中为其指定我的首选名称(而不是<lambda>)。我坚持希望有人会知道更简单的方法……
beardc 2013年

3
您可以为列值传递一个dict {'D': {'my name':lambda function}},它将使内部dict键成为列名。
Zelazny7

1
我认为熊猫现在支持应用于按分组数据框的
IanS

22

作为泰德·彼得鲁(Ted Petrou)回答的替代方案(主要是美学方面),我发现我更喜欢紧凑的清单。请不要考虑接受它,它只是对Ted的答案以及代码/数据的更详细的评论。Python / pandas不是我的第一个/最好的,但是我发现它读起来不错:

df.groupby('group') \
  .apply(lambda x: pd.Series({
      'a_sum'       : x['a'].sum(),
      'a_max'       : x['a'].max(),
      'b_mean'      : x['b'].mean(),
      'c_d_prodsum' : (x['c'] * x['d']).sum()
  })
)

          a_sum     a_max    b_mean  c_d_prodsum
group                                           
0      0.530559  0.374540  0.553354     0.488525
1      1.433558  0.832443  0.460206     0.053313

我发现它更让人联想到dplyr管道和data.table链接的命令。并不是说它们更好,只是我更熟悉。(我当然认识到def对于这些类型的操作使用更正式的功能的功能,并且对于许多人而言,这是首选。这只是一种选择,不一定更好。)


我以与Ted相同的方式生成数据,我将添加一个可重复性的种子。

import numpy as np
np.random.seed(42)
df = pd.DataFrame(np.random.rand(4,4), columns=list('abcd'))
df['group'] = [0, 0, 1, 1]
df

          a         b         c         d  group
0  0.374540  0.950714  0.731994  0.598658      0
1  0.156019  0.155995  0.058084  0.866176      0
2  0.601115  0.708073  0.020584  0.969910      1
3  0.832443  0.212339  0.181825  0.183405      1

2
我最喜欢这个答案。这类似于R.中的dplyr管道。–
仁怀

18

Pandas >= 0.25.0,称为集合

从pandas版本0.25.0或更高版本开始,我们正在远离基于字典的聚合和重命名,而转向接受a的命名聚合tuple。现在,我们可以同时聚合+重命名为更多信息的列名:

范例

df = pd.DataFrame(np.random.rand(4,4), columns=list('abcd'))
df['group'] = [0, 0, 1, 1]

          a         b         c         d  group
0  0.521279  0.914988  0.054057  0.125668      0
1  0.426058  0.828890  0.784093  0.446211      0
2  0.363136  0.843751  0.184967  0.467351      1
3  0.241012  0.470053  0.358018  0.525032      1

GroupBy.agg以命名聚合应用:

df.groupby('group').agg(
             a_sum=('a', 'sum'),
             a_mean=('a', 'mean'),
             b_mean=('b', 'mean'),
             c_sum=('c', 'sum'),
             d_range=('d', lambda x: x.max() - x.min())
)

          a_sum    a_mean    b_mean     c_sum   d_range
group                                                  
0      0.947337  0.473668  0.871939  0.838150  0.320543
1      0.604149  0.302074  0.656902  0.542985  0.057681

我喜欢这些命名的聚合,但看不到应该如何将它们用于多列?
西蒙·伍德海德

好问题,无法弄清楚,怀疑这是可能的(尚未)。我开了为此。将保持我的问题,你会更新。感谢您指出@SimonWoodhead
尔法恩

4

0.25.0版中的新功能。

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

  • 关键字是输出列名称
  • 值是元组,其第一个元素是要选择的列,第二个元素是要应用于该列的聚合。Pandas为pandas.NamedAgg namedtuple提供了字段['column','aggfunc'],以使参数更清楚。像往常一样,聚合可以是可调用的或字符串别名。
    In [79]: 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 [80]: animals
    Out[80]: 
      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 [81]: 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[81]: 
          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 [82]: animals.groupby("kind").agg(
       ....:     min_height=('height', 'min'),
       ....:     max_height=('height', 'max'),
       ....:     average_weight=('weight', np.mean),
       ....: )
       ....: 
    Out[82]: 
          min_height  max_height  average_weight
    kind                                        
    cat          9.1         9.5            8.90
    dog          6.0        34.0          102.75

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

命名聚合对于Series groupby聚合也有效。在这种情况下,没有列选择,因此值仅是函数。

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

3

特德的答案是惊人的。我最终使用了一个较小的版本,以防有人感兴趣。在寻找一种取决于多个列中的值的聚合时很有用:

创建一个数据框

df=pd.DataFrame({'a': [1,2,3,4,5,6], 'b': [1,1,0,1,1,0], 'c': ['x','x','y','y','z','z']})


   a  b  c
0  1  1  x
1  2  1  x
2  3  0  y
3  4  1  y
4  5  1  z
5  6  0  z

使用apply分组和聚合(使用多列)

df.groupby('c').apply(lambda x: x['a'][(x['a']>1) & (x['b']==1)].mean())

c
x    2.0
y    4.0
z    5.0

使用聚合进行分组和聚合(使用多列)

我喜欢这种方法,因为我仍然可以使用聚合。也许人们会让我知道为什么对组进行汇总时为什么需要套用多列。

现在似乎很明显,但是只要您不选择感兴趣的列 直接在groupby之后的列,就可以从聚合函数中访问数据框的所有列。

仅访问所选列

df.groupby('c')['a'].aggregate(lambda x: x[x>1].mean())

访问所有列,因为选择毕竟是不可思议的

df.groupby('c').aggregate(lambda x: x[(x['a']>1) & (x['b']==1)].mean())['a']

或类似

df.groupby('c').aggregate(lambda x: x['a'][(x['a']>1) & (x['b']==1)].mean())

我希望这有帮助。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.