对多个groupby列应用多个函数

文档函数展示了如何使用以输出列名为键的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对象需要很长时间)。我将不得不改变它,以便我在一次运行中遍历整个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调用,键必须是一个列)。

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

407367 次浏览

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

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[],后者只选择符合条件的行。

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

目前接受的答案的后半部分已经过时,有两个不赞成的地方。首先也是最重要的是,你不能再将字典的字典传递给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())

我希望这能有所帮助。

作为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管道和data.table链接命令。不是说他们更好,只是对我来说更熟悉。(对于许多人来说,我当然认识到对这些类型的操作使用更形式化的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

Pandas >= 0.25.0,命名聚合

自从熊猫版本0.25.0或更高版本以来,我们正在远离基于字典的聚合和重命名,并转向接受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

0.25.0新版功能

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

  • 关键字是输出列名
  • 这些值是元组,其第一个元素是要选择的列,第二个元素是要应用到该列的聚合。熊猫提供熊猫。NamedAgg用字段['column', 'aggfunc']命名元组,使其更清楚参数是什么。通常,聚合可以是可调用的或字符串别名。
>>> 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]
... })


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


>>> print(
...     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),
...     )
... )
min_height  max_height  average_weight
kind
cat          9.1         9.5            8.90
dog          6.0        34.0          102.75

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

>>> print(
...     animals
...     .groupby('kind')
...     .agg(
...         min_height=('height', 'min'),
...         max_height=('height', 'max'),
...         average_weight=('weight', np.mean),
...     )
... )
min_height  max_height  average_weight
kind
cat          9.1         9.5            8.90
dog          6.0        34.0          102.75

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

命名聚合也适用于系列groupby聚合。在这种情况下,没有列选择,所以值只是函数。

>>> print(
...     animals
...     .groupby('kind')
...     .height
...     .agg(
...         min_height='min',
...         max_height='max',
...     )
... )
min_height  max_height
kind
cat          9.1         9.5
dog          6.0        34.0

这是对使用命名聚合的“exans”答案的扭曲。它是一样的,但是有参数解包,它允许你仍然将一个字典传递给agg函数。

命名的aggs是一个很好的特性,但是乍一看可能很难用编程方式编写,因为它们使用关键字,但实际上通过参数/关键字解包很简单。

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]})
 

agg_dict = {
"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)
}


animals.groupby("kind").agg(**agg_dict)

结果

      min_height  max_height  average_weight
kind
cat          9.1         9.5            8.90
dog          6.0        34.0          102.75