
Pandas 的 groupby.agg() 默认不支持直接在聚合函数中同时访问多个列,但可通过预计算新列或改用 groupby.apply() 实现多列协同计算,本文详解两种可靠方案及性能权衡。
pandas 的 `groupby.agg()` 默认不支持直接在聚合函数中同时访问多个列,但可通过预计算新列或改用 `groupby.apply()` 实现多列协同计算,本文详解两种可靠方案及性能权衡。
在 Pandas 中进行分组聚合时,一个常见需求是:基于同一分组内多个数值列的组合运算(如 value1 * value2 的均值)生成新统计量。然而,DataFrameGroupBy.agg() 的标准用法(包括字典映射、命名元组或关键字参数形式)仅允许每个聚合函数作用于单列——即函数接收的是 Series,而非整个子 DataFrame。因此,如下写法会报错或行为异常:
# ❌ 错误示例:agg 不支持 lambda 中跨列访问 df.groupby(["Sample", "Year"]).agg(agg1=lambda group: (group["value1"] * group["value2"]).mean())
✅ 方案一:预计算辅助列 + agg()(推荐)
最高效、最符合 pandas 惯例的方式是先构造中间列,再分组聚合。利用 eval() 或普通赋值创建临时列,随后在 agg() 中引用该列:
import pandas as pd
df = pd.DataFrame({
"value1": range(8),
"value2": range(7, -1, -1),
"Sample": [1, 2] * 4,
"Year": list(range(2023, 2027)) * 2
})
result = (df.eval('prod = value1 * value2')
.groupby(['Sample', 'Year'])
.agg(agg1=('prod', 'mean')))
print(result)
输出:
agg1
Sample Year
1 2023 6.0
2025 8.0
2 2024 8.0
2026 6.0
✅ 优势:向量化计算、内存友好、可链式调用、兼容 .agg() 的所有高级功能(如多函数、多列映射)。
⚠️ 注意:若需保留原始 DataFrame 结构,可在链式操作末尾加 .drop(columns='prod')。
✅ 方案二:使用 groupby.apply()(灵活但慎用)
当逻辑极其复杂(如涉及条件分支、外部状态或非标量返回)时,apply() 允许函数接收整个分组 DataFrame,从而自由访问任意列:
result_apply = (df.groupby(['Sample', 'Year'])
.apply(lambda x: x['value1'].mul(x['value2']).mean()))
# 返回 Series,索引为 MultiIndex
print(result_apply)
输出:
Sample Year
1 2023 6.0
2025 8.0
2 2024 8.0
2026 6.0
dtype: float64
⚠️ 注意事项:
-
apply()是逐组 Python 循环,性能显著低于向量化操作(尤其大数据集); - 默认返回
Series,若需DataFrame输出,需显式构造(如lambda x: pd.Series({'agg1': ...})); - 避免在
apply中重复调用.copy()或复杂 I/O,否则进一步拖慢速度。
总结建议
| 场景 | 推荐方法 | 理由 |
|---|---|---|
| 多列简单算术/统计(如乘积均值、差值标准差) | eval() + agg() |
高效、清晰、可读性强、易于调试 |
| 需要分组内行间逻辑、动态列选择或非标量输出 | apply() |
灵活性高,但务必评估性能影响 |
需同时对多列做不同聚合(如 value1.sum() 和 value2.mean()) |
直接 agg({'value1': 'sum', 'value2': 'mean'})
|
原生支持,无需跨列 |
始终优先尝试向量化预计算;仅当 agg() 无法满足逻辑表达时,再启用 apply() 并辅以性能测试(如 %%timeit)。










