2. 需求预测基础:时间序列概念、平稳性与季节性、移动平均法原理与Python实现
需求预测是供应链计划系统的核心输入。本章从时间序列的基本概念出发,逐步深入平稳性与季节性的判断方法,并重点讲解移动平均法的原理与Python实现。掌握这些基础,是后续构建指数平滑、ARIMA等高级预测模型的必要前提。
2.1 时间序列的基本概念
时间序列(Time Series)是按时间顺序排列的一组观测值。在供应链场景中,常见的需求时间序列包括:每日订单量、每周出货量、每月销售额等。时间序列分析的核心目标是:从历史数据中提取模式,并用于预测未来。
一个完整的需求时间序列通常包含以下四个成分:
| 成分 | 说明 | 供应链示例 |
|---|---|---|
| 趋势(Trend) | 长期上升或下降的总体方向 | 某品类年销量逐年增长15% |
| 季节性(Seasonality) | 固定周期内的重复波动 | 每年12月礼品需求高峰 |
| 周期性(Cyclical) | 非固定周期的长期波动 | 经济周期导致的3-5年需求起伏 |
| 随机波动(Irregular) | 不可预测的偶然因素 | 突发促销、自然灾害导致的异常订单 |
实践提示:在供应链计划中,我们通常将趋势和季节性作为可预测成分,将随机波动视为噪声。移动平均法的主要作用就是平滑噪声,突出趋势和季节性模式。
2.2 平稳性与季节性
平稳性(Stationarity)是时间序列分析的重要前提。一个平稳时间序列的统计性质(均值、方差、自协方差)不随时间变化。对于非平稳序列,直接建模往往会产生虚假回归,导致预测失效。
2.2.1 平稳性的判断标准
- 均值恒定:序列围绕一个固定水平波动,无明显趋势。
- 方差恒定:波动幅度不随时间变化。
- 自协方差仅与滞后阶数有关:序列的相关结构不随时间改变。
常用的统计检验方法是ADF检验(Augmented Dickey-Fuller Test)。当p值小于0.05时,拒绝非平稳的原假设,认为序列平稳。
2.2.2 季节性的识别
季节性(Seasonality)是指时间序列在固定周期长度(如12个月、7天、4个季度)内呈现的重复模式。识别季节性的常用方法:
- 时序图观察:直接观察数据是否存在周期性波峰和波谷。
- 自相关函数(ACF)图:在季节性滞后阶数处出现显著峰值。
- 季节分解:使用STL或X13-ARIMA将序列分解为趋势、季节和残差成分。
常见误区:不要将周期性(Cyclical)与季节性(Seasonality)混淆。季节性周期固定且已知(如一年12个月),而周期性周期不固定且通常更长(如3-5年的经济周期)。在短期需求预测中,季节性比周期性更重要。
2.3 移动平均法原理
移动平均法(Moving Average, MA)是最基础的时间序列平滑方法。其核心思想是:用最近N期的平均值作为下一期的预测值。随着新数据加入,窗口向前滑动,因此称为"移动"平均。
2.3.1 简单移动平均(SMA)
公式:
F(t+1) = (Y(t) + Y(t-1) + ... + Y(t-N+1)) / N
其中:
- F(t+1):第t+1期的预测值
- Y(t):第t期的实际观测值
- N:移动平均窗口大小(阶数)
2.3.2 加权移动平均(WMA)
公式:
F(t+1) = w₁·Y(t) + w₂·Y(t-1) + ... + wₙ·Y(t-N+1)
其中权重满足:w₁ + w₂ + ... + wₙ = 1,且通常近期数据赋予更高权重。
2.3.3 窗口大小N的选择原则
| 窗口N | 平滑效果 | 对趋势的响应 | 适用场景 |
|---|---|---|---|
| 小(N=3~5) | 弱平滑,保留较多波动 | 快速响应趋势变化 | 需求波动大、趋势变化快的场景 |
| 中(N=7~12) | 中等平滑 | 适度响应 | 月度数据、季节性周期匹配 |
| 大(N=20~30) | 强平滑,噪声几乎消除 | 滞后严重,响应慢 | 稳定需求、长期趋势分析 |
关键洞察:移动平均法本质上是一个低通滤波器——它保留低频趋势成分,滤除高频随机噪声。但代价是:N越大,预测值对真实趋势变化的响应越滞后。在供应链计划中,这种滞后可能导致补货不及时或库存积压。
2.4 Python实现:移动平均法
下面给出简单移动平均(SMA)和加权移动平均(WMA)的完整Python实现。代码包含数据生成、预测计算和结果可视化。
2.4.1 简单移动平均实现
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
def simple_moving_average(series, window):
"""
简单移动平均预测
参数:
series: 历史需求数据(list或numpy数组)
window: 移动窗口大小N
返回:
predictions: 预测值列表(前window-1个为NaN)
"""
series = np.array(series)
n = len(series)
predictions = np.full(n, np.nan)
for i in range(window, n + 1):
# 取最近window个实际值的均值作为下一期预测
predictions[i - 1] = np.mean(series[i - window:i])
return predictions
# 示例数据:12个月的需求量
demand = [120, 135, 110, 145, 125, 130, 150, 140, 155, 160, 145, 170]
window_size = 3
pred_sma = simple_moving_average(demand, window_size)
# 输出结果
print("月份 | 实际需求 | SMA预测(N=3)")
print("-" * 35)
for i in range(len(demand)):
pred_str = f"{pred_sma[i]:.1f}" if not np.isnan(pred_sma[i]) else "N/A"
print(f" {i+1:2d} | {demand[i]:3d} | {pred_str}")
2.4.2 加权移动平均实现
def weighted_moving_average(series, weights):
"""
加权移动平均预测
参数:
series: 历史需求数据
weights: 权重列表(长度=窗口大小,从最近到最远)
返回:
predictions: 预测值列表
"""
series = np.array(series)
window = len(weights)
weights = np.array(weights) / np.sum(weights) # 归一化
n = len(series)
predictions = np.full(n, np.nan)
for i in range(window, n + 1):
predictions[i - 1] = np.sum(series[i - window:i] * weights)
return predictions
# 示例:最近3期权重分别为0.5, 0.3, 0.2
weights = [0.5, 0.3, 0.2]
pred_wma = weighted_moving_average(demand, weights)
# 对比SMA和WMA
print("月份 | 实际需求 | SMA(N=3) | WMA(0.5,0.3,0.2)")
print("-" * 50)
for i in range(len(demand)):
sma_str = f"{pred_sma[i]:.1f}" if not np.isnan(pred_sma[i]) else "N/A"
wma_str = f"{pred_wma[i]:.1f}" if not np.isnan(pred_wma[i]) else "N/A"
print(f" {i+1:2d} | {demand[i]:3d} | {sma_str:>6s} | {wma_str:>6s}")
2.4.3 预测误差评估
def forecast_errors(actual, predicted):
"""
计算预测误差指标
返回: MAE, RMSE, MAPE
"""
actual = np.array(actual)
predicted = np.array(predicted)
# 去除NaN值
mask = ~np.isnan(predicted)
actual = actual[mask]
predicted = predicted[mask]
mae = np.mean(np.abs(actual - predicted))
rmse = np.sqrt(np.mean((actual - predicted) ** 2))
mape = np.mean(np.abs((actual - predicted) / actual)) * 100
return mae, rmse, mape
# 计算SMA和WMA的误差
mae_sma, rmse_sma, mape_sma = forecast_errors(demand, pred_sma)
mae_wma, rmse_wma, mape_wma = forecast_errors(demand, pred_wma)
print(f"\nSMA(N=3) - MAE: {mae_sma:.2f}, RMSE: {rmse_sma:.2f}, MAPE: {mape_sma:.2f}%")
print(f"WMA(0.5,0.3,0.2) - MAE: {mae_wma:.2f}, RMSE: {rmse_wma:.2f}, MAPE: {mape_wma:.2f}%")
代码运行结果解读:在上述示例中,WMA由于给予最近数据更高权重,通常比SMA更快响应趋势变化,因此MAE和RMSE往往更低。但WMA的权重选择需要根据实际数据特征调整,没有通用最优解。建议在项目中通过交叉验证选择最佳权重组合。
2.5 移动平均法的供应链应用场景
| 应用场景 | 推荐窗口N | 注意事项 |
|---|---|---|
| 周度补货计划 | 4~8周 | 匹配供应商交货周期,避免过度平滑导致缺货 |
| 月度销售预测 | 3~6个月 | 考虑季节性因素,可结合季节指数调整 |
| 安全库存计算 | 12~24个月 | 使用较长窗口估计需求波动标准差 |
| 新品需求预测 | 2~4周 | 数据量少,小窗口更灵敏;可参考同类产品 |
移动平均法的局限性:
- 对所有历史数据等权处理(SMA),无法区分新旧数据的重要性差异。
- 预测值总是滞后于实际趋势变化,N越大滞后越严重。
- 无法处理季节性模式——如果数据存在明显季节性,移动平均会模糊季节特征。
- 需要至少N期历史数据才能开始预测,冷启动阶段预测精度低。
2.6 本章小结
- 时间序列由趋势、季节性、周期性和随机波动四个成分组成。理解这些成分是选择预测方法的基础。
- 平稳性是时间序列建模的重要前提,可通过ADF检验判断。非平稳序列需要先进行差分或变换处理。
- 季节性是供应链需求预测中最常见的可预测模式,可通过时序图、ACF图和季节分解识别。
- 移动平均法是最简单的平滑预测方法,通过滑动窗口计算均值来预测未来。窗口大小N的选择直接影响预测的平滑度和响应速度。
- Python实现中,SMA和WMA的代码结构清晰,可直接嵌入供应链计划系统的预测模块。误差评估指标(MAE、RMSE、MAPE)用于量化预测精度。