文档展示了如何在一个groupby对象上同时应用多个函数,使用输出列名作为键的dict:

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传递给一个组时,它期望键是函数将应用到的列名。

What I want to do is apply multiple functions to several columns (but certain columns will be operated on multiple times). Also, some functions will depend on other columns in the groupby object (like sumif functions). My current solution is to go column by column, and doing something like the code above, using lambdas for functions that depend on other rows. But this is taking a long time, (I think it takes a long time to iterate through a groupby object). I'll have to change it so that I iterate through the whole groupby object in a single run, but I'm wondering if there's a built in way in pandas to do this somewhat cleanly.

例如,我曾经尝试过

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调用)。

是否有任何内置的方式来做我想做的事情,或者这种功能可能会被添加,或者我只需要手动遍历组?


当前回答

目前接受的答案的后半部分已经过时,有两个不赞成的地方。首先也是最重要的是,您不能再将字典的字典传递给agg组by方法。其次,永远不要使用。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并返回一个Series

现在,如果您有多个需要交互的列,那么您不能使用agg,它会隐式地将一个Series传递给聚合函数。当使用apply时,整个组作为数据帧被传递到函数中。

我建议创建一个自定义函数,该函数返回所有聚合的Series。使用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.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

其他回答

泰德的回答很惊人。我最后用了一个更小的版本,以防有人感兴趣。当您正在寻找一个依赖于多个列的值的聚合时非常有用:

创建一个数据框架

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

print(df)
   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进行分组和聚合(使用多个列)

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

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

我喜欢这种方法,因为我仍然可以使用聚合。也许人们会告诉我,在对组进行聚合时,为什么需要apply来获取多个列。

现在看起来很明显,但只要不直接在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())

我希望这能有所帮助。

Pandas >= 0.25.0,命名为聚合

从pandas 0.25.0或更高版本开始,我们将不再使用基于字典的聚合和重命名,而是使用接受元组的命名聚合。现在我们可以同时聚合+重命名为一个更有信息的列名:

例子:

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

目前接受的答案的后半部分已经过时,有两个不赞成的地方。首先也是最重要的是,您不能再将字典的字典传递给agg组by方法。其次,永远不要使用。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并返回一个Series

现在,如果您有多个需要交互的列,那么您不能使用agg,它会隐式地将一个Series传递给聚合函数。当使用apply时,整个组作为数据帧被传递到函数中。

我建议创建一个自定义函数,该函数返回所有聚合的Series。使用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.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

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

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.index传递给df。Ix[]从df中选择当前组。然后测试列C是否小于0.5。返回的布尔序列被传递给g[], 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

作为Ted Petrou的答案的替代(主要是在美学方面),我发现我更喜欢一个更紧凑的列表。请不要考虑接受它,它只是一个更详细的评论Ted的答案,加上代码/数据。Python/熊猫不是我的第一个/最好的,但我发现这个读起来很好:

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管道和数据。表链接命令。不是说他们更好,只是对我来说更熟悉。(对于许多人来说,我当然认识到对这些类型的操作使用更形式化的def函数的力量和偏好。这只是一种选择,不一定更好。)


我用和泰德一样的方式生成数据,我将添加一个种子以提高再现性。

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