首页 文章

将多个函数应用于多个groupby列

提问于
浏览
118

docs显示如何使用带有输出列名称作为键的dict一次在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对象 . 当dict类似地传递给一个由DataFrame组成的组时,它希望键是该函数将应用于的列名 .

我想要做的是将多个函数应用于多个列(但某些列将被多次操作) . 此外,某些函数将依赖于groupby对象中的其他列(如sumif函数) . 我目前的解决方案是逐列,并执行类似上面的代码,使用lambdas作为依赖于其他行的函数 . 但这需要很长时间,(我认为迭代一个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(因为如果从DataFrame调用 agg ,则键必须是一列) .

有没有内置的方法来做我想做的事情,或者可能添加这个功能的可能性,还是我只需要手动迭代组合?

谢谢

4 回答

  • 0

    目前接受的答案的后半部分已过时,并且有两个弃用 . 首先也是最重要的,您不能再将字典字典传递给 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.560541  0.507058  0.418546  1.707651  0.129667
    1      0.187757  0.157958  0.887315  0.533531  0.652427
    

    如果你不喜欢那个丑陋的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.560541  0.507058  0.418546  1.707651      0.129667
    1      0.187757  0.157958  0.887315  0.533531      0.652427
    

    使用应用并返回系列

    现在,如果您有多个需要进行交互的列,则无法使用 agg ,它会隐式地将Series传递给聚合函数 . 使用 apply 时,整个组作为DataFrame传递给函数 .

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

    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.560541  0.507058  0.418546     0.118106
    1      0.187757  0.157958  0.887315     0.276808
    

    如果你爱上了MultiIndexes,你仍然可以返回一个像这样的系列:

    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.560541  0.507058  0.418546  0.118106
    1      0.187757  0.157958  0.887315  0.276808
    
  • 81

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

    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函数中的组索引对其进行索引 .

    这是一个hacky解决方法:

    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.index 传递给 df.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
    
  • 149

    特德的回答令人惊讶 . 我最终使用了一个较小的版本,万一有人感兴趣 . 在查找依赖于多列值的聚合时很有用:

    创建一个数据帧

    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())
    

    我希望这有帮助 .

  • 1

    作为替代方案(主要是在美学)对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
    

相关问题