使用pandas对同一列进行多个聚合

是否有一种pandas内置的方法,可以将两个不同的聚合函数f1, f2应用于同一列df["returns"],而无需多次调用agg() ?

示例dataframe:

import pandas as pd
import datetime as dt
import numpy as np


pd.np.random.seed(0)
df = pd.DataFrame({
"date"    :  [dt.date(2012, x, 1) for x in range(1, 11)],
"returns" :  0.05 * np.random.randn(10),
"dummy"   :  np.repeat(1, 10)
})

语法上错误,但直觉上正确的做法是:

# Assume `f1` and `f2` are defined for aggregating.
df.groupby("dummy").agg({"returns": f1, "returns": f2})

显然,Python不允许重复键。是否有其他方式来表示agg()的输入?也许一个元组[(column, function)]列表会更好,以允许多个函数应用于同一列?但是agg()似乎只接受字典。

除了定义一个辅助函数来应用它里面的两个函数,还有什么解决方法吗?(这在聚合中是如何工作的呢?)

202160 次浏览

像这样的东西会有用吗:

In [7]: df.groupby('dummy').returns.agg({'func1' : lambda x: x.sum(), 'func2' : lambda x: x.prod()})
Out[7]:
func2     func1
dummy
1     -4.263768e-16 -0.188565

截至2022-06-20,以下是可接受的聚合实践:

df.groupby('dummy').agg(
Mean=('returns', np.mean),
Sum=('returns', np.sum))

下面包含了pandas的历史版本。

你可以简单地以列表的形式传递函数:

In [20]: df.groupby("dummy").agg({"returns": [np.mean, np.sum]})
Out[20]:
mean       sum
dummy
1      0.036901  0.369012

或者作为字典:

In [21]: df.groupby('dummy').agg({'returns':
{'Mean': np.mean, 'Sum': np.sum}})
Out[21]:
returns
Mean       Sum
dummy
1      0.036901  0.369012

TLDR;Pandas groupby.agg有一个新的更简单的语法,用于指定(1)多列上的聚合,以及(2)一列上的多个聚合。因此,要为熊猫>= 0.25执行此操作,请使用

df.groupby('dummy').agg(Mean=('returns', 'mean'), Sum=('returns', 'sum'))


Mean       Sum
dummy
1      0.036901  0.369012

df.groupby('dummy')['returns'].agg(Mean='mean', Sum='sum')


Mean       Sum
dummy
1      0.036901  0.369012

熊猫>= 0.25:叫聚合

Pandas改变了GroupBy.agg的行为,以支持更直观的语法来指定命名聚合。参见0.25文档增强部分以及相关的GitHub问题GH18366GH26512

从文档来看,

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

.
  • 关键字是输出列名
  • 这些值是元组,其中第一个元素是要选择的列,第二个元素是要应用到该列的聚合。 熊猫提供熊猫。NamedAgg带有字段的命名元组 ['column', 'aggfunc']使参数更清楚。作为 通常,聚合可以是可调用对象或字符串别名

您现在可以通过关键字参数传递一个元组。元组遵循(<colName>, <aggFunc>)的格式。

import pandas as pd


pd.__version__
# '0.25.0.dev0+840.g989f912ee'


# Setup
df = 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]
})


df.groupby('kind').agg(
max_height=('height', 'max'), min_weight=('weight', 'min'),)


max_height  min_weight
kind
cat          9.5         7.9
dog         34.0         7.5

或者,你可以使用pd.NamedAgg(本质上是一个命名元组),这使事情更显式。

df.groupby('kind').agg(
max_height=pd.NamedAgg(column='height', aggfunc='max'),
min_weight=pd.NamedAgg(column='weight', aggfunc='min')
)


max_height  min_weight
kind
cat          9.5         7.9
dog         34.0         7.5

对于Series来说更简单,只需将aggfunc传递给关键字参数。

df.groupby('kind')['height'].agg(max_height='max', min_height='min')


max_height  min_height
kind
cat          9.5         9.1
dog         34.0         6.0

最后,如果你的列名不是有效的python标识符,请使用带有解包的字典:

df.groupby('kind')['height'].agg(**{'max height': 'max', ...})

熊猫& lt;0.25

在0.24之前的pandas最新版本中,如果使用字典为聚合输出指定列名,则会得到FutureWarning:

df.groupby('dummy').agg({'returns': {'Mean': 'mean', 'Sum': 'sum'}})
# FutureWarning: using a dict with renaming is deprecated and will be removed
# in a future version

在v0.20中不支持使用字典重命名列。在最新版本的pandas中,可以通过传递元组列表来更简单地指定。如果以这种方式指定函数,该列的所有函数需要指定为(名称,函数)对的元组。

df.groupby("dummy").agg({'returns': [('op1', 'sum'), ('op2', 'mean')]})


returns
op1       op2
dummy
1      0.328953  0.032895

或者,

df.groupby("dummy")['returns'].agg([('op1', 'sum'), ('op2', 'mean')])


op1       op2
dummy
1      0.328953  0.032895

如果需要对多个列应用相同的多个聚合函数,最简单的方法(imo)是使用字典理解。

#setup
df = pd.DataFrame({'dummy': [0, 1, 1], 'A': range(3), 'B':range(1, 4), 'C':range(2, 5)})


# aggregation
df.groupby("dummy").agg({k: ['sum', 'mean'] for k in ['A', 'B', 'C']})

multiindex

上面的结果是一个带有MultiIndex列的数据框架。如果希望使用平面自定义列名,则可以使用命名聚合(如本文其他答案所建议的那样)。

作为在文件中说明,键应该是输出列名,值应该是命名聚合的元组(column, aggregation function)。由于有多个列和多个函数,这将导致一个嵌套结构。要将其平铺成单个字典,可以使用collections.ChainMap()或嵌套循环。

同样,如果你更喜欢将grouper列(dummy)作为列(而不是索引),请在groupby()中指定as_index=False

from collections import ChainMap
# convert a list of dictionaries into a dictionary
dct = dict(ChainMap(*reversed([{f'{k}_total': (k, 'sum'), f'{k}_mean': (k, 'mean')} for k in ['A','B','C']])))
# {'A_total': ('A', 'sum'), 'A_avg': ('A', 'mean'), 'B_total': ('B', 'sum'), 'B_avg': ('B', 'mean'), 'C_total': ('C', 'sum'), 'C_avg': ('C', 'mean')}


# the same result obtained by a nested loop
# dct = {k:v for k in ['A','B','C'] for k,v in [(f'{k}_total', (k, 'sum')), (f'{k}_avg', (k, 'mean'))]}


# aggregation
df.groupby('dummy', as_index=False).agg(**dct)

flat