使用 pandas GroupBy.agg() 对同一列进行多次聚合
- 2024-12-05 08:37:00
- admin 原创
- 151
问题描述:
是否有一种内置的 pandas 方法可以将两个不同的聚合函数应用于f1, f2
同一列df["returns"]
,而无需agg()
多次调用?
示例数据框:
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()
似乎它只接受字典。
除了定义一个仅应用其中两个函数的辅助函数之外,还有其他解决方法吗?(无论如何,这与聚合如何一起工作?)
解决方案 1:
截至 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
解决方案 2:
TLDR;Pandasgroupby.agg
有一个新的、更简单的语法,用于指定 (1) 多列上的聚合,以及 (2) 单列上的多个聚合。因此,要对pandas >= 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
Pandas >= 0.25:命名聚合
Pandas 已改变其行为,GroupBy.agg
转而采用更直观的语法来指定命名聚合。请参阅0.25 文档中的增强功能部分以及相关的 GitHub 问题GH18366和GH26512。
根据文档,
为了支持特定于列的聚合并控制输出列名称,pandas 接受 中的特殊语法
GroupBy.agg()
,称为“命名聚合”,其中
关键字是输出列名称
这些值是元组,其第一个元素是要选择的列,第二个元素是要应用于该列的聚合。Pandas 提供了带有字段 ['column', 'aggfunc'] 的 pandas.NamedAgg 命名元组,以使参数更清晰。与往常一样,聚合可以是可调用的或字符串别名。
现在,您可以通过关键字参数传递元组。元组遵循以下格式(<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', ...})
熊猫 < 0.25
在较新版本的 pandas 0.24 中,如果使用字典来指定聚合输出的列名,则会得到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 中,可以通过传递元组列表更简单地指定此操作。如果以这种方式指定函数,则该列的所有函数都需要指定为 (name, function) 对的元组。
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
解决方案 3:
类似这样的工作会成功吗:
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
解决方案 4:
如果您有多个列需要应用相同的多个聚合函数,最简单的方法(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 列的数据框。如果需要平面自定义列名称,则可以使用命名聚合(如此处其他答案所建议的那样)。
如文档中所述,键应为输出列名,值应(column, aggregation function)
为命名聚合的元组。由于存在多个列和多个函数,因此会产生嵌套结构。要将其展平为单个字典,您可以使用collections.ChainMap()
或嵌套循环。
此外,如果您更喜欢将石斑鱼列(dummy
)作为列(而不是索引),请as_index=False
在中指定groupby()
。
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)
解决方案 5:
您还可以在 NamedAggregation 中使用 lambda
df.groupby('dummy').returns.agg({
'summed' : pd.NamedAgg(column='date', aggfunc=lambda series: sum(series.values()),
'joined' : pd.NamedAgg(column='returns', aggfunc=lambda series: ','.join(series.values())),
})