
本文详解如何在pandas中实现按组分段、以首个有效日期为动态起点、严格遵循工作日(跳过周末)规则的逆向累计日期推算,适用于供应链排程、交付倒排等业务场景。
本文详解如何在pandas中实现按组分段、以首个有效日期为动态起点、严格遵循工作日(跳过周末)规则的逆向累计日期推算,适用于供应链排程、交付倒排等业务场景。
在实际业务分析中(如订单交付计划、生产排程),常需基于各组内首个确定的截止日期(如“承诺交付日”),向上游逆向推算各前置环节的启动时间,并确保所有间隔仅计工作日(Monday–Friday)。与固定取组末日期不同,本需求要求每个组内首次出现非空 Start 的行作为动态基准点,其后的所有 NaN 行需按 LT(Lead Time,单位:工作日)向上逆推;而该基准点之后已存在的日期则保持不变——即“只填补上游空白,不覆盖下游已有计划”。
以下方案通过巧妙构造二级分组(Group + grp),精准隔离各组内首个有效日期之后的连续空白段,再结合 BusinessDay 偏移与逆序累积求和,实现高鲁棒性计算。
✅ 核心步骤解析
- 统一日期格式化:将 Start 列强制转为 datetime64[ns],NaT 自动保留;
- 识别动态起点:利用 df['LT'].eq(0) 标记所有 LT == 0 的行(通常对应锚定日期行),并构造分组标识 grp,使同一组内首个非空 Start 及其之前的所有 NaN 行归属同一子组;
- 逆序工作日累积偏移:对 LT 列逆序后,按 ['Group', grp] 二级分组进行 cumsum(),再逐项映射为 BusinessDay(n) 偏移对象;
- 基准日期减偏移:对每组子组取 Start 的首个有效值(即动态起点),减去对应偏移量,最后格式化输出。
? 完整可运行代码
import pandas as pd
# 构造示例数据
data = {
'Group': ['A', 'A', 'A', 'B', 'B', 'C', 'C', 'C', 'C', 'C'],
'LT': [5, 10, 0, 3, 0, 2, 4, 0, 0, 0],
'Start': [None, None, '20-03-2024', None, '04-03-2024', None, None, '04-04-2024', '10-04-2024', '24-04-2024']
}
df = pd.DataFrame(data)
# 步骤1:安全转换为 datetime,自动处理 NaT
df['Start'] = pd.to_datetime(df['Start'], errors='coerce', format='%d-%m-%Y')
# 步骤2:构造二级分组标识 —— 关键!解决“首个非空 Start 后续空白段”的识别
s = df['LT'].eq(0) # 标记 LT==0 的锚点行(通常对应 Start 有值)
grp = s.groupby(df['Group']).cumsum() - s # 确保同组内首个 0 之前 NaN 归入同一子组
# 步骤3:逆序计算工作日累计偏移(BusinessDay 对象序列)
s1 = (df.loc[::-1, 'LT'] # 逆序遍历
.groupby([df['Group'], grp]) # 按组+子组双重分组
.cumsum() # 累加 LT(工作日数)
.apply(pd.offsets.BusinessDay) # 转为 BusinessDay 偏移对象
)
# 步骤4:取每子组首个 Start 值,减去对应偏移,并格式化
out = (df
.groupby(['Group', grp])['Start'].transform('first') # 动态起点
.sub(s1) # 逆向减去工作日偏移
.dt.strftime('%d-%m-%Y') # 统一输出格式
)
# 更新原列
df['Start'] = out
print(df)⚠️ 注意事项与最佳实践
- LT == 0 是关键锚点假设:本方案默认 LT 为 0 的行对应 Start 有值(业务语义上即“终点”)。若实际数据中存在 LT != 0 但 Start 非空的情况,请先预处理:df.loc[df['Start'].notna(), 'LT'] = 0。
- BusinessDay 默认不含节假日:如需排除法定节假日,应使用 pd.offsets.CustomBusinessDay(holidays=...) 并传入自定义节日列表。
- 性能提示:对超大表(>1M 行),apply(pd.offsets.BusinessDay) 可能较慢;可改用 np.busday_offset 向量化加速(需额外处理 NaT 和分组逻辑)。
- 空组防护:若某组全为 NaT,transform('first') 将返回 NaT,减法结果仍为 NaT,符合预期;无需额外异常处理。
✅ 总结
该方法突破了传统 bfill() 或 last_valid_index 的局限,通过二级分组机制精准捕获“组内首个有效日期”,再结合 Pandas 原生 BusinessDay 偏移与逆序累积,实现了语义清晰、逻辑严密、工业级可用的动态逆向工作日排程。掌握此模式,可灵活适配 MRP 计划、服务 SLA 倒排、多级供应商协同等复杂时序推演场景。










