3. 计划与实际采剥量时间序列对齐
为什么“对齐”是偏差计算的前置条件
基准计划通常按日历日给出(月度计划再按台阶、采区拆分),而实际完成量来自卡车计数、地磅与调度系统,天然是班次级、非等间隔的:夜班跨零点、检修停产、暴雨停工、设备故障都会造成时间轴上的“空洞”或“重叠”。若直接把两条曲线按记录条数或原始时间戳相减,偏差会被时间错位污染——例如把 8 小时停产误判为“欠产”,或把跨零点的夜班量记到错误日期。
本节的任务是:把计划曲线与实际曲线统一到同一时间栅格上,使每个栅格单元(日/班次 × 台阶)都有可比的“计划量”和“实际量”,并显式标记中断区间,为后续累计偏差曲线提供干净输入。
对齐的三条规则
- 栅格优先:以“日历日 + 班次(白/夜)”为最小单元建立时间栅格,夜班归属其开始日期(约定:00:00–08:00 与 16:00–24:00 归当日,08:00–16:00 归当日白班;跨零点夜班按开始日归属)。参照约定一旦确定,计划、实际、SVG 三处必须一致。
- 计划侧降采样:月计划按“日历天数比例”摊到日,再按班次产能权重(如白班 55%、夜班 45%)拆到班次;检修/计划停产日计划量置 0,而不是平均摊入。
- 实际侧聚合 + 中断标记:把班次内多条卡车/地磅记录求和到栅格单元;对无记录且调度日志标记为停产/检修/天气的单元,实际量记 0 并打上中断标签;对无记录但无中断标记的单元,标记为“缺测”,不直接当 0 处理。
关键区别:停产 = 计划为 0 且实际为 0(不产生偏差);缺测 = 计划 > 0 但实际无数据(需补测或标注不确定,不能静默填 0)。二者在对齐表中必须用不同状态区分。
对齐实现(Python,可直接运行)
下面用教学示例数据演示:某台阶 3 天、每天 2 个班次,计划按日给出,实际为班次记录,其中第 2 天夜班因暴雨停产、第 3 天白班缺测。
import pandas as pd
# 1) 计划:按日给出(示例数据)
plan_daily = pd.DataFrame({
"date": pd.to_datetime(["2024-06-01", "2024-06-02", "2024-06-03"]),
"bench": ["1080"] * 3,
"plan_day_t": [12000, 12000, 12000], # 吨/日
})
# 班次产能权重:白班 0.55,夜班 0.45
SHIFT_W = {"day": 0.55, "night": 0.45}
# 2) 实际:班次记录(示例数据,含停产与缺测)
actual = pd.DataFrame({
"date": pd.to_datetime(["2024-06-01","2024-06-01","2024-06-02","2024-06-03"]),
"shift": ["day","night","day","night"],
"bench": ["1080"]*4,
"actual_t": [6800, 5200, 6100, 5400],
})
# 调度日志:中断事件(示例)
outage = pd.DataFrame({
"date": pd.to_datetime(["2024-06-02"]),
"shift": ["night"],
"bench": ["1080"],
"reason": ["暴雨停产"],
})
# 3) 构建时间栅格:日 × 班次 × 台阶
grid = plan_daily.merge(
pd.DataFrame({"shift": ["day", "night"]}), how="cross"
)
grid["plan_t"] = grid.apply(
lambda r: r["plan_day_t"] * SHIFT_W[r["shift"]], axis=1
)
# 4) 实际聚合到栅格
agg = (actual.groupby(["date","shift","bench"], as_index=False)["actual_t"].sum())
grid = grid.merge(agg, on=["date","shift","bench"], how="left")
# 5) 中断标记与状态判定
grid = grid.merge(outage, on=["date","shift","bench"], how="left")
grid["status"] = "正常"
grid.loc[grid["reason"].notna(), "status"] = "停产"
grid.loc[grid["reason"].isna() & grid["actual_t"].isna(), "status"] = "缺测"
grid["actual_t"] = grid["actual_t"].fillna(0)
# 6) 可比偏差(仅对正常/停产单元计算;缺测单独标注)
grid["dev_t"] = grid["actual_t"] - grid["plan_t"]
print(grid[["date","shift","bench","plan_t","actual_t","status","dev_t"]])
运行后得到对齐表(示例输出):
| date | shift | bench | plan_t | actual_t | status | dev_t |
|---|---|---|---|---|---|---|
| 06-01 | day | 1080 | 6600 | 6800 | 正常 | +200 |
| 06-01 | night | 1080 | 5400 | 5200 | 正常 | −200 |
| 06-02 | day | 1080 | 6600 | 6100 | 正常 | −500 |
| 06-02 | night | 1080 | 5400 | 0 | 停产 | −5400 |
| 06-03 | day | 1080 | 6600 | 0 | 缺测 | — |
| 06-03 | night | 1080 | 5400 | 5400 | 正常 | 0 |
注意:06-02 夜班的 −5400 是停产造成的计划落空,不是设备欠产;06-03 白班是缺测,其偏差不可直接参与累计偏差曲线,需先补测或按规则标注为“不确定”。若把缺测当 0,会凭空制造 −6600 的假偏差。
对齐结果的可视化判读
下图把“计划栅格—实际聚合—中断/缺测状态”三者的关系画在同一时间轴上,重点看两处:停产单元计划量仍在、实际量为 0;缺测单元实际量缺失、状态被单独标出,不进入偏差计算。
对齐质量检查清单
- 栅格完整性:日 × 班次 × 台阶的组合数应等于计划覆盖的单元数,无重复、无遗漏。
- 量守恒:各台阶实际量之和 = 地磅/卡车计数总量(允许在容差内,如 ±0.5%)。
- 状态互斥:每个单元只能是“正常 / 停产 / 缺测”之一,停产与缺测不得同时出现。
- 偏差可算性:只有“正常”和“停产”单元进入偏差计算;“缺测”单元单独列出,待补测后回填。
- 参照一致:夜班归属规则在计划拆分、实际聚合、SVG 标注三处完全一致。