4. 异常值突跳的滑动窗口检测与标记

突跳检测的核心:用滑动窗口建立“局部正常基线”

在线监测序列中的突跳(spike)表现为单点或极短时段内数值突然偏离邻近水平,随后又快速回落。它与零点漂移(缓慢基线偏移)、量程超限(持续饱和)不同,关键特征是局部性:异常只影响很短的一段,前后数据仍处于正常波动范围。因此检测思路不是拿全序列均值做比较,而是用滑动窗口在每一时刻构造一个“局部参考值”,再判断当前点偏离该参考值是否超过阈值。

本例采用滑动中位数 + MAD(中位数绝对偏差)作为窗口统计量。相比均值与标准差,中位数和 MAD 对窗口内混入的异常点不敏感,不会被突跳本身抬高基线或撑大阈值,从而避免“异常点掩盖自己”。判定规则为:

对时刻 i,取窗口 W = [i-k, i+k](不含 i 本身,或含 i 但用稳健统计)
ref_i  = median(W)
mad_i  = median(|x - ref_i| for x in W)
score_i = |x_i - ref_i| / (1.4826 * mad_i + eps)
若 score_i > T(如 T=3.5),标记 x_i 为突跳

其中 1.4826 是使 MAD 在正态分布下与标准差一致的比例因子,eps 防止零方差时除零。窗口长度 2k+1 直接决定“局部”的范围:窗口太短,正常波动容易被误判;窗口太长,突跳会稀释在大量正常点中,score 被压低而漏检。

可运行的检测与标记实现

下面代码用纯 Python 实现窗口检测,并对比三种窗口长度。数据为教学示例:在平稳基线上叠加小幅噪声,再人为注入 4 个突跳点(含 1 个负向突跳),用于观察检出效果。

import statistics as st

def sliding_spike_detect(series, k, threshold=3.5, eps=1e-9):
    n = len(series)
    flags = [False] * n
    scores = [0.0] * n
    for i in range(n):
        lo, hi = max(0, i - k), min(n, i + k + 1)
        window = [series[j] for j in range(lo, hi) if j != i]
        if len(window) < 3:
            continue
        ref = st.median(window)
        mad = st.median([abs(x - ref) for x in window])
        scale = 1.4826 * mad + eps
        scores[i] = abs(series[i] - ref) / scale
        flags[i] = scores[i] > threshold
    return flags, scores

# 教学示例序列:基线 50,噪声 ±0.6,注入突跳
base = [50.0, 50.4, 49.7, 50.2, 50.1, 49.9, 50.3, 50.0,
        50.2, 49.8, 50.1, 50.3, 49.9, 50.0, 50.2, 49.8,
        50.1, 50.0, 49.9, 50.3, 50.1, 49.7, 50.2, 50.0]
base[6]  = 58.5   # 正向突跳
base[13] = 41.0   # 负向突跳
base[18] = 56.2   # 正向突跳
base[22] = 44.8   # 负向突跳

for k in (2, 4, 7):
    flags, scores = sliding_spike_detect(base, k)
    hit = [i for i, f in enumerate(flags) if f]
    print(f"k={k}, 窗口={2*k+1}, 检出点={hit}, 数量={len(hit)}")

预期输出(示例数据可直接推出):k=2 时窗口仅 5 点,噪声波动被放大,可能出现误报;k=4 时窗口 9 点,4 个注入突跳应全部检出且误报少;k=7 时窗口 15 点,突跳在窗口中占比降低,score 被稀释,部分幅度较小的突跳可能漏检。实际运行结果以本地执行为准,这里给出的是基于规则的分析预期。

窗口长度对检出效果的影响

窗口长度不是越大越好,也不是越小越灵敏,它需要在灵敏度与稳健性之间取平衡。判断依据应同时看两个指标:

  • 召回率:注入的突跳中被标记的比例,反映漏检程度。
  • 误报数:正常点被标记的数量,反映对噪声的敏感程度。

实践中可先用已知注入点的历史片段做一次“回放标定”:固定阈值 T,扫描 k 的取值,选召回率高且误报可接受的最小窗口。若序列采样间隔为 1 min,突跳通常持续 1~2 个点,窗口取 7~11 点(k=3~5)较常见;若采样间隔为 1 h,突跳可能持续数小时,窗口应相应加长。

滑动窗口突跳检测:局部基线 → 稳健评分 → 标记 在线监测序列(示例:基线 50,注入 4 个突跳) 基线 50 58.5 41.0 56.2 44.8 窗口 k=4 逐点稳健评分 score = |x − median(W)| / (1.4826·MAD(W)),阈值 T = 3.5 k=2(窗口 5 点) 局部基线过窄,噪声被放大 召回:高 误报:偏多 适用:突跳极短、噪声低 k=4(窗口 9 点) 局部基线与突跳尺度匹配 召回:4/4 误报:少 推荐:本例首选 k=7(窗口 15 点) 突跳被大量正常点稀释 召回:可能漏检小幅突跳 误报:很少 适用:缓变、长周期异常

标记结果与人工复核要点

检测输出的是“候选突跳点”,不应直接删除或替换。正确做法是先标记、后复核:在数据表中增加 spike_flag 与 score 两列,保留原始值,供后续插补或剔除时追溯。复核时重点看三类情况:

  1. 连续多点被标记:若相邻 3 个以上点同时超阈值,更可能是真实事件(如短时排放峰值)或传感器故障,而非单点突跳,应转人工判断。
  2. 正负交替突跳:同一时段正负方向反复跳变,常见于接触不良或电磁干扰,需结合设备状态排查,而不是简单标记了事。
  3. 阈值边界点:score 在 3.0~3.5 之间的点建议保留观察,不强行标记,避免把正常波动误判为异常。
窗口统计量建议与业务采样频率绑定:先确认数据的时间间隔,再按“突跳持续点数 × 3~5 倍”估算窗口长度,最后用注入回放标定阈值 T。不要直接套用固定 k 值。
MAD 为零或极小时(序列长期恒定),score 会被放大,导致正常微小波动被误报。此时应设置最小尺度下限(如 scale = max(1.4826·MAD, 0.5)),或改用固定绝对阈值兜底。