Pandas中结合groupby与cumsum实现按类别、年、月维度的累计值计算
问题核心偏差
你之前的cumsum计算逻辑存在两个关键误区,导致结果不符合预期:
- 分组维度完全错位:对已经按年-月-类聚合好的单条计数记录,重复使用同三个字段做groupby,每个分组内仅1条数据,cumsum没有累加空间
- 缺少全量时间框架:默认分组逻辑只会保留有原始数据的年-月-类组合,缺失的月份不会自动补全,也无法实现“无数据月份沿用前序累计值”的要求
正确实现步骤
1. 完成基础计数聚合(原有逻辑正确)
先拿到每个存在数据的年-月-类别的原始记录数:
import pandas as pd sample = [ {'name':11,'category':'A','year':2017,'month':1}, {'name':22,'category':'A','year':2017,'month':2}, {'name':33,'category':'B','year':2015,'month':1}, {'name':33,'category':'C','year':2017,'month':3}, {'name':33,'category':'B','year':2017,'month':8}, {'name':44,'category':'B','year':2016,'month':4}, {'name':44,'category':'A','year':2017,'month':6}, {'name':55,'category':'C','year':2016,'month':9}, {'name':55,'category':'C','year':2017,'month':5}, {'name':55,'category':'B','year':2017,'month':11} ] sample_df = pd.DataFrame(sample) # 基础聚合:统计每个年-月-类别的原始计数 sample_counts = sample_df.groupby(['year','month','category']).agg( category_count=('category', 'count') ).reset_index()
2. 生成全量年-月-类别网格
为了覆盖每个年度下1-12月的全量范围,先通过笛卡尔积生成所有需要的维度组合,避免月份缺失:
# 提取所有需要覆盖的维度 all_years = sample_counts['year'].unique() all_categories = sample_counts['category'].unique() all_months = range(1, 13) # 生成全量框架:年份 × 月份(1-12) × 类别 full_grid = pd.MultiIndex.from_product( iterables=[all_years, all_months, all_categories], names=['year', 'month', 'category'] ).to_frame(index=False)
3. 匹配原始计数到全量框架
把有数据的计数匹配到全量框架上,无数据的位置填充0:
merged = full_grid.merge( sample_counts, on=['year', 'month', 'category'], how='left' ).fillna({'category_count': 0})
4. 计算累计值
按照时间推进的顺序排序后,以「年+类别」为分组单位做累计求和(完全匹配你给出的示例输出逻辑):
# 按年份、类别、月份升序排序,保证时间顺序正确 merged = merged.sort_values(['year', 'category', 'month']) # 核心:同一年、同一类别下,按月份累加计数 merged['Cumulative_By_Category'] = merged.groupby( ['year', 'category'] )['category_count'].cumsum()
适配不同需求的调整:如果需要做跨年度全局累计(也就是2017年1月的累计值需要包含2015、2016年该类别的所有计数),只需要把cumsum的分组键改为仅
['category']即可,无需其他调整。
5. 整理为目标输出格式
调整列名和顺序,匹配你要求的输出结构:
result = merged.rename( columns={'category': 'Category', 'month': 'Month', 'year': 'Year'} )[['Category', 'Month', 'Year', 'Cumulative_By_Category']] result = result.sort_values(['Year', 'Category', 'Month']).reset_index(drop=True)
以上代码运行后,2017年的输出结果和你给出的示例完全一致,会自动补全所有缺失月份、保持无数据月份的累计值连续不跳变。
内容的提问来源于stack exchange,提问作者hope288
相关产品推荐
相关产品推荐

