在销量预测中,通过时序分解与残差分析识别异常值是一种经典且可解释性强的技术路径。其核心思想是将原始销量序列分解为趋势、季节性和残差等成分,并认为残差中蕴含了模型无法解释的“意外”波动,其中超出正常范围的波动即为潜在异常。

1. 时序分解与残差分析的核心步骤

该方法的完整流程可以概括为以下步骤,其核心逻辑与关键技术要点如下表所示:

步骤 目标 关键操作/选择 说明
1. 数据准备与预处理 确保数据为规整的时间序列 按时间排序、处理缺失值、确定频率 这是分析的基础,数据不规整会导致分解失效。
2. 时序分解 (Decomposition) 将序列拆解为趋势(T)、季节(S)、残差(R)成分 选择加法模型 (Y = T + S + R) 或乘法模型 (Y = T * S * R) 加法模型适用于季节性波动幅度相对稳定的序列;乘法模型适用于波动幅度随趋势水平变化的序列。
3. 残差提取与统计分析 从分解结果中分离出残差序列 计算残差的描述性统计量(均值、标准差) 残差理论上应是一个均值为0、方差恒定的平稳序列(白噪声)。
4. 异常阈值设定 定义判断异常值的量化标准 常用 均值 ± N倍标准差,N通常取2.5, 3或根据IQR计算 例如,将超过 3倍标准差 的残差点视为强异常点。
5. 异常点识别与标记 定位具体的时间点 筛选出残差绝对值超过阈值的日期 得到初步的异常点列表。
6. 业务解释与复核 区分“真异常”与“可解释的波动” 结合促销日历、节假日、库存信息进行人工或规则判断 例如,残差异常高但当天有促销活动,则可能不是数据问题,而是正常业务波动。

2. 代码实现与实战示例

以下是一个使用Python的statsmodels库进行时序分解与异常检测的完整示例。代码结合了中的核心思想,并进行了详细注释。

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from statsmodels.tsa.seasonal import seasonal_decompose

def detect_anomalies_by_residual(series, model='additive', period=7, threshold_std=3):
    """
    使用时序分解与残差分析检测销量序列中的异常值。
    参考自销量预测异常检测方案 。

    参数:
    series: pd.Series, 索引为日期时间的销量序列。
    model: str, 分解模型,'additive'(加法)或 'multiplicative'(乘法)。
    period: int, 季节性周期。对于周度明显的销量数据,通常设为7。
    threshold_std: float, 判定异常的标准差倍数。

    返回:
    anomalies: pd.DatetimeIndex, 被识别为异常点的日期索引。
    result: DecomposeResult, 分解结果对象,用于绘图分析。
    """
    # 步骤1 & 2: 执行时序分解
    # statsmodels的seasonal_decompose函数默认使用移动平均进行分解 
    result = seasonal_decompose(series.dropna(), model=model, period=period)

    # 步骤3: 提取残差并计算统计量
    resid = result.resid.dropna()  # 残差序列
    resid_mean = resid.mean()
    resid_std = resid.std()
    print(f"残差均值: {resid_mean:.4f}, 残差标准差: {resid_std:.4f}")

    # 步骤4 & 5: 设定阈值并识别异常点
    upper_bound = resid_mean + threshold_std * resid_std
    lower_bound = resid_mean - threshold_std * resid_std
    # 找出残差超过阈值的点
    anomaly_mask = (resid > upper_bound) | (resid < lower_bound)
    anomalies = resid[anomaly_mask].index

    print(f"使用{threshold_std}倍标准差阈值,共检测到{len(anomalies)}个异常点。")
    if len(anomalies) > 0:
        print("异常日期及残差值:")
        for date, val in resid[anomaly_mask].items():
            print(f"  {date.date()}: {val:.2f}")

    # 可视化:原始序列与残差序列对比
    fig, axes = plt.subplots(2, 1, figsize=(12, 8))
    # 绘制原始序列和趋势
    axes[0].plot(series, label='原始销量', color='blue', alpha=0.6)
    axes[0].plot(result.trend, label='趋势成分', color='red', linewidth=2)
    axes[0].set_title('销量序列与趋势分解')
    axes[0].legend()
    axes[0].grid(True)
    # 绘制残差序列与异常点
    axes[1].plot(resid, label='残差', color='grey')
    axes[1].axhline(y=upper_bound, color='r', linestyle='--', label=f'上界 (+{threshold_std}σ)')
    axes[1].axhline(y=lower_bound, color='r', linestyle='--', label=f'下界 (-{threshold_std}σ)')
    axes[1].scatter(anomalies, resid.loc[anomalies], color='red', s=50, zorder=5, label='异常点')
    axes[1].set_title('残差序列与异常点检测')
    axes[1].legend()
    axes[1].grid(True)
    plt.tight_layout()
    plt.show()

    return anomalies, result

# ===== 模拟数据生成与实战调用 =====
# 生成一段带有趋势、季节性和几个异常点的模拟销量数据
np.random.seed(42)
dates = pd.date_range(start='2023-01-01', periods=365, freq='D')
trend = np.linspace(100, 200, 365)  # 线性上升趋势
seasonality = 20 * np.sin(2 * np.pi * np.arange(365) / 7)  # 以7天为周期的季节性
noise = np.random.normal(0, 10, 365)  # 随机噪声
# 合成序列
sales = trend + seasonality + noise
# 人工注入几个异常点 (突增和突降)
sales[50] += 80   # 第50天突增
sales[150] -= 70  # 第150天突降
sales[300] += 90  # 第300天突增

# 创建Series
sales_series = pd.Series(sales, index=dates)
sales_series.name = 'Daily_Sales'

# 调用函数进行异常检测
anomaly_dates, decomp_result = detect_anomalies_by_residual(
    series=sales_series,
    model='additive',
    period=7,
    threshold_std=3
)

3. 方法优势与局限性

优势:

  1. 可解释性强:异常判定基于“偏离模型预期”的程度,业务人员容易理解。例如,可以明确指出“某天的销量在剔除趋势和季节性影响后,仍然异常偏高/偏低”。
  2. 能区分异常类型:通过观察异常点在残差序列中的位置(正残差或负残差),可以初步判断是销量暴增还是锐减型异常,为后续修复提供方向。
  3. 与业务逻辑结合方便:如和所述,检测出的异常点需要与促销日历、节假日、库存状态(如是否缺货)进行交叉验证,以区分数据错误和真实的业务事件。

局限性及注意事项:

  1. 模型假设:该方法依赖于时序分解模型的有效性。如果序列不满足模型的基本假设(如季节性不稳定),分解结果可能不准确,导致误判。
  2. 参数选择敏感:季节性周期 period 的选择至关重要。错误的周期会导致季节性成分提取错误,从而污染残差。分解模型(加法/乘法)的选择也影响结果。
  3. 阈值需谨慎threshold_std 的取值需要根据历史数据和业务经验调整。过于严格会漏掉一些温和异常,过于宽松则会引入过多噪声。
  4. 非唯一方法:残差分析是识别“全局”异常的有效方法,但对于局部突变点(如水平移位),专门的突变点检测算法(如PELT、贝叶斯变点)可能更敏感。在实际工业系统中,如盒马的预测框架,常将多种检测方法(如统计阈值、机器学习模型)结合使用,形成多道防线。

4. 进阶:与业务系统集成

在成熟的销量预测系统中,时序分解与残差分析通常作为一个模块嵌入自动化数据清洗流水线。其输出(异常点列表)会与库存系统、促销系统的日志进行关联分析。例如,一个被标记为“负残差异常”的点,若在库存系统中对应“缺货”状态,则可自动触发“缺货零值修复”流程,用插值或需求估计方法进行数据修正。这种“检测-归因-修复”的闭环,是提升预测模型在真实脏数据环境下鲁棒性的关键。


参考来源

 

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐