Groupby的sum与cumsum差异:返回DataFrame/Series的原因及解决方法
Pandas GroupBy:sum与cumsum行为差异及多聚合操作解决方案
问题背景
测试数据:
import pandas as pd import numpy as np dict1 = {'A': {0: 'A0', 1: 'A0', 2: 'A0', 3: 'A0', 4: 'A1', 5: 'A1', 6: 'A1', 7: 'A1', 8: 'A1', 9: 'A1'}, 'B': {0: 'B0', 1: 'B1', 2: 'B2', 3: 'B3', 4: 'B4', 5: 'B5', 6: 'B6', 7: 'B7', 8: 'B8', 9: 'B9'}, 'C': {0: 0, 1: 1, 2: 2, 3: 3, 4: 4, 5: 5, 6: 6, 7: 7, 8: 8, 9: 9}, 'D': {0: 10, 1: 11, 2: 12, 3: 13, 4: 14, 5: 15, 6: 16, 7: 17, 8: 18, 9: 19}, 'E': {0: 'E0', 1: 'E1', 2: 'E0', 3: 'E1', 4: 'E0', 5: 'E1', 6: 'E0', 7: 'E1', 8: 'E0', 9: 'E1'}} df2 = pd.DataFrame(dict1)
1. sum与cumsum的行为差异
执行分组求和:
df2.groupby(['A','E'])['D'].sum()
返回带分组索引的聚合结果:
A E A0 E0 22 E1 24 A1 E0 48 E1 51 Name: D, dtype: int64
执行分组累计求和:
df2.groupby(['A','E'])['D'].cumsum()
仅返回保留原索引的行级结果:
0 10 1 11 2 22 3 24 4 14 5 15 6 30 7 32 8 48 9 51 Name: D, dtype: int64
2. 多列混合聚合问题
尝试一次聚合中同时执行累计求和与自定义聚合:
df2.groupby(['A','E']).agg({'D':'cumsum','C':lambda x: 4*np.sum(x)})
结果出现大量NaN:
D C 0 10.0 NaN 1 11.0 NaN 2 22.0 NaN 3 24.0 NaN 4 14.0 NaN 5 15.0 NaN 6 30.0 NaN 7 32.0 NaN 8 48.0 NaN 9 51.0 NaN (A0, E0) NaN 8.0 (A0, E1) NaN 16.0 (A1, E0) NaN 72.0 (A1, E1) NaN 84.0
解答
一、sum与cumsum行为差异的原因
- sum是聚合(Aggregation)操作:将每个分组的所有数据汇总为单个值,输出行数等于分组数量,索引为分组键组合。
- cumsum是转换(Transformation)操作:为原数据的每一行返回对应分组内的累计值,保留原索引,输出行数与原数据一致,核心是在不改变数据结构行数的前提下完成分组内逐行计算。
二、让cumsum返回包含分组信息的DataFrame
无需手动赋值的简洁方式:
# 通过apply返回带分组键的DataFrame result_df = df2.groupby(['A','E'])['D'].apply(lambda x: x.cumsum()).reset_index()
输出结果:
A E D 0 A0 E0 10 1 A0 E0 22 2 A0 E1 11 3 A0 E1 24 4 A1 E0 14 5 A1 E0 30 6 A1 E0 48 7 A1 E1 15 8 A1 E1 32 9 A1 E1 51
三、多列混合聚合的解决方案
问题根源:聚合操作返回分组级结果,转换操作返回行级结果,两者索引不匹配导致NaN。以下是两种无需单独处理的实现方式:
方案1:先转换后聚合再合并
# 计算行级累计求和 df2['D_cumsum'] = df2.groupby(['A','E'])['D'].cumsum() # 计算分组级聚合结果 agg_df = df2.groupby(['A','E'])['C'].agg(lambda x: 4*np.sum(x)).reset_index(name='C_agg') # 合并结果 final_df = pd.merge(df2[['A','E','D_cumsum']], agg_df, on=['A','E'], how='left')
最终输出:
A E D_cumsum C_agg 0 A0 E0 10 8 1 A0 E1 11 16 2 A0 E0 22 8 3 A0 E1 24 16 4 A1 E0 14 72 5 A1 E1 15 84 6 A1 E0 30 72 7 A1 E1 32 84 8 A1 E0 48 72 9 A1 E1 51 84
方案2:自定义分组处理函数
通过groupby.apply在每个分组内同时完成转换与聚合:
def process_group(group): group['D_cumsum'] = group['D'].cumsum() group['C_agg'] = 4 * group['C'].sum() return group final_df = df2.groupby(['A','E'], group_keys=False).apply(process_group)
此方法直接返回包含所有计算列的完整DataFrame,无需额外合并步骤。
内容的提问来源于stack exchange,提问作者Rajib Lochan Sarkar
相关产品推荐
相关产品推荐

