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年的经济周期)。在短期需求预测中,季节性比周期性更重要。
时间序列成分分解示意图 时间序列成分分解示意图 原始需求时间序列 包含趋势 + 季节性 + 随机波动 趋势成分 长期上升/下降方向 季节成分 固定周期重复模式 残差成分 随机噪声/异常值 加法模型:Y(t) = Trend(t) + Seasonal(t) + Residual(t) 乘法模型:Y(t) = Trend(t) × Seasonal(t) × Residual(t) 💡 应用说明: • 加法模型适用于季节波动幅度不随趋势变化的情况(如月销量波动±200件) • 乘法模型适用于季节波动幅度随趋势变化的情况(如销量增长后波动同步放大) • 供应链需求预测中,乘法模型更常见(高销量通常伴随高波动)

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越大,预测值对真实趋势变化的响应越滞后。在供应链计划中,这种滞后可能导致补货不及时或库存积压。
移动平均法原理与窗口滑动示意图 移动平均法原理与窗口滑动示意图(N=3) t-5 t-4 t-3 t-2 t-1 t t+1 t+2 120 135 110 145 125 130 预测=? 预测=? 窗口1:计算F(t+1) (145+125+130)/3 = 133.3 窗口2:计算F(t+2) (125+130+133.3)/3 = 129.4 📌 移动平均法关键特性: • 窗口滑动:每产生一个新实际值,窗口向前移动一格,丢弃最旧数据 • 滞后效应:预测值总是落后于实际趋势变化,N越大滞后越明显 • 等权假设:SMA对所有历史数据赋予相同权重,WMA可改善但无法消除滞后

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期历史数据才能开始预测,冷启动阶段预测精度低。
在后续章节中,我们将学习指数平滑法和ARIMA模型,这些方法能有效克服上述局限性。

2.6 本章小结

  • 时间序列由趋势、季节性、周期性和随机波动四个成分组成。理解这些成分是选择预测方法的基础。
  • 平稳性是时间序列建模的重要前提,可通过ADF检验判断。非平稳序列需要先进行差分或变换处理。
  • 季节性是供应链需求预测中最常见的可预测模式,可通过时序图、ACF图和季节分解识别。
  • 移动平均法是最简单的平滑预测方法,通过滑动窗口计算均值来预测未来。窗口大小N的选择直接影响预测的平滑度和响应速度。
  • Python实现中,SMA和WMA的代码结构清晰,可直接嵌入供应链计划系统的预测模块。误差评估指标(MAE、RMSE、MAPE)用于量化预测精度。