
本文介绍如何基于原始提交月份对分组数据进行智能前向填充,生成覆盖目标时间范围(如截至当前月)的完整月度序列,适用于销售预测、库存计划等需时间连续性的场景。
本文介绍如何基于原始提交月份对分组数据进行智能前向填充,生成覆盖目标时间范围(如截至当前月)的完整月度序列,适用于销售预测、库存计划等需时间连续性的场景。
在实际业务分析中,常遇到“稀疏提交 + 时间连续性要求”的矛盾:例如某 SKU-区域组合仅在 1 月和 4 月提交过预测数据,但下游系统需要 1–12 月每月均有对应记录。此时不能简单使用 df.fillna(method='ffill'),而需按逻辑分组、按时间轴扩展、再逐组前向填充——即对每个 (sku, location) 组合,将其历史提交视为“快照”,并将该快照向前延展至下一个快照出现前,或直至截止月份(如 2023 年 12 月)。
核心思路是:将数据重塑为以 submissionmonth 为行索引、forecastmonth 为列索引的宽表结构,利用 reindex 补全缺失的提交月份,再通过 ffill() 实现跨行前向填充,最后 stack() 恢复长格式。该方法天然支持多维度分组(无需显式 groupby),且严格保持原始数据的时间语义。
以下为完整实现代码(含关键注释与健壮性增强):
import pandas as pd
# 构建原始数据
input_data = pd.DataFrame([
{"submissionmonth": "Jan-2023", "sku": "A1", "location": "sup1", "forecastmonth": "Jan-2023", "cost": 100},
{"submissionmonth": "Jan-2023", "sku": "A1", "location": "sup1", "forecastmonth": "Feb-2023", "cost": 105},
{"submissionmonth": "Jan-2023", "sku": "A1", "location": "sup1", "forecastmonth": "Mar-2023", "cost": 108},
{"submissionmonth": "Jan-2023", "sku": "A1", "location": "sup1", "forecastmonth": "Apr-2023", "cost": 106},
{"submissionmonth": "Apr-2023", "sku": "A1", "location": "sup1", "forecastmonth": "Apr-2023", "cost": 101},
{"submissionmonth": "Apr-2023", "sku": "A1", "location": "sup1", "forecastmonth": "May-2023", "cost": 102},
{"submissionmonth": "Apr-2023", "sku": "A1", "location": "sup1", "forecastmonth": "Jun-2023", "cost": 109},
{"submissionmonth": "Apr-2023", "sku": "A1", "location": "sup1", "forecastmonth": "Jul-2023", "cost": 104},
{"submissionmonth": "Oct-2023", "sku": "A2", "location": "sup2", "forecastmonth": "Oct-2023", "cost": 101},
{"submissionmonth": "Oct-2023", "sku": "A2", "location": "sup2", "forecastmonth": "Nov-2023", "cost": 102},
{"submissionmonth": "Oct-2023", "sku": "A2", "location": "sup2", "forecastmonth": "Dec-2023", "cost": 109},
{"submissionmonth": "Oct-2023", "sku": "A2", "location": "sup2", "forecastmonth": "Jan-2024", "cost": 104},
])
# ✅ 步骤 1:解析并确定时间范围
submission_dt = pd.to_datetime(input_data["submissionmonth"], format="%b-%Y")
forecast_dt = pd.to_datetime(input_data["forecastmonth"], format="%b-%Y")
# 设定截止月份(示例为 2023-12)
cutoff_month = pd.Timestamp("2023-12-01")
# 构建完整提交月份序列:从首个提交月到截止月(含)
full_submission_months = pd.date_range(
start=submission_dt.min(),
end=cutoff_month,
freq="MS" # Month Start,确保 Jan-2023 → 2023-01-01
).strftime("%b-%Y")
# ✅ 步骤 2:重塑 → 填充 → 恢复
output_data = (
input_data
.set_index(["submissionmonth", "forecastmonth"]) # 多级索引便于 pivot
.unstack("forecastmonth") # forecastmonth 变为列
.reindex(full_submission_months, axis=0) # 补全所有提交月(含中间空缺)
.ffill() # 按行前向填充(继承最近有效快照)
.stack("forecastmonth") # 列转回行,恢复 forecastmonth 列
.reset_index(name="cost") # 重置索引,cost 作为值列
)
# ✅ 步骤 3:还原原始列顺序(可选但推荐)
output_data = output_data[input_data.columns]
print(f"Original shape: {input_data.shape}")
print(f"Output shape: {output_data.shape}")
output_data.head(10)
注意事项与最佳实践:
- 时间格式一致性:务必统一用 pd.to_datetime(..., format="%b-%Y") 解析,避免 Jan-2023 被误判为 2023-01-01 或 2023-01-00;
- 截止月设定:示例中硬编码 cutoff_month,生产环境建议动态获取:pd.Timestamp.today().replace(day=1);
- 多维分组保障:本方案隐式满足 (sku, location) 级别独立填充——因 set_index 包含全部标识字段,unstack 后每个组合独占一行;
- 性能提示:若数据量极大(>100 万行),可先 groupby(['sku','location']) 分块处理,避免宽表内存膨胀;
- 边界验证:输出中 submissionmonth 应覆盖 Jan-2023 至 Dec-2023,且每个 submissionmonth 对应的 forecastmonth 集合应与该快照原始覆盖范围一致(如 Apr-2023 提交始终带 Apr–Jul-2023 四个月预测)。
此方法兼顾简洁性与鲁棒性,是构建时序连续数据管道的标准范式。











