3. 位移数据异常值识别与清洗规则配置

为什么清洗规则要“先分类、再处置”

雷达位移序列中的异常并非同一种性质:跳变是相邻采样点之间的突变,负值是物理上不可能的累计位移方向,超量程是超出设备标称测量范围的读数。三类异常的成因不同(多路径反射、遮挡失锁、解算发散、设备重启等),处置方式也不同——跳变适合“标记并插值”,负值适合“置空并告警”,超量程适合“整段剔除”。因此规则配置的核心是把判定条件与处置动作解耦,并让每一次处置都留下可追溯的日志。

原始位移序列 t / 测点ID / d(mm) t1 P07 12.4 t2 P07 12.6 t3 P07 41.9 ←跳变 t4 P07 -3.2 ←负值 t5 P07 999.0 ←超量程 清洗规则引擎(按序判定) R1 跳变检测 |d(t)-d(t-1)| > 20 mm 动作:标记 JUMP,线性插值 R2 负值检测 d(t) < 0 mm 动作:置空 NULL,触发告警 R3 超量程检测 d(t) > 500 mm 或 NaN 动作:整段剔除,标记 RANGE 清洗后序列 t1 12.4 t2 12.6 t3 12.8 (插值) t4 NULL t5 剔除 清洗日志 t3 P07 JUMP 41.9→12.8 t4 P07 NEG -3.2→NULL t5 P07 RANGE 999.0→剔除 告警/复核 负值→现场核查 超量程→设备自检 判定顺序:R1→R2→R3,命中即执行对应动作并写日志;未命中则原值透传。

规则参数与处置动作的配置表

下面这份配置表可直接作为规则引擎的输入。阈值需结合设备标称量程与测点历史波动水平设定,示例中的数值仅作教学演示,实际项目应按监测设计文件调整。

规则判定条件处置动作日志字段是否告警
R1 跳变|d(t) − d(t−1)| > 20 mm标记 JUMP,用前后有效值线性插值时间、测点、原值、插值否(累计频次超限再告警)
R2 负值d(t) < 0 mm置为 NULL,不参与后续速率计算时间、测点、原值是
R3 超量程d(t) > 500 mm 或 NaN整段剔除,标记 RANGE时间、测点、原值、剔除原因是

可运行的清洗实现

以下 Python 代码实现上述三条规则,输入为按时间排序的位移记录列表,输出清洗后序列与清洗日志。代码自包含,可直接运行验证。

import math

# 规则参数(示例值,实际按监测设计调整)
JUMP_THRESHOLD = 20.0   # mm
RANGE_MAX = 500.0       # mm

def clean_displacement(records):
    """
    records: [{'t': str, 'point': str, 'd': float or None}, ...] 按时间升序
    返回: (cleaned, logs)
    """
    cleaned, logs = [], []
    prev_valid = None

    for i, rec in enumerate(records):
        t, pid, d = rec['t'], rec['point'], rec['d']
        action = None

        # R3 超量程 / 缺失
        if d is None or (isinstance(d, float) and math.isnan(d)) or d > RANGE_MAX:
            action = 'RANGE'
            logs.append({'t': t, 'point': pid, 'rule': 'R3',
                         'raw': d, 'action': '剔除'})
            cleaned.append({'t': t, 'point': pid, 'd': None, 'flag': 'RANGE'})
            continue

        # R2 负值
        if d < 0:
            action = 'NEG'
            logs.append({'t': t, 'point': pid, 'rule': 'R2',
                         'raw': d, 'action': '置空'})
            cleaned.append({'t': t, 'point': pid, 'd': None, 'flag': 'NEG'})
            continue

        # R1 跳变
        if prev_valid is not None and abs(d - prev_valid) > JUMP_THRESHOLD:
            # 用前一个有效值暂代,后续可做线性插值
            logs.append({'t': t, 'point': pid, 'rule': 'R1',
                         'raw': d, 'action': '插值'})
            cleaned.append({'t': t, 'point': pid, 'd': prev_valid, 'flag': 'JUMP'})
            continue

        # 正常值
        prev_valid = d
        cleaned.append({'t': t, 'point': pid, 'd': d, 'flag': 'OK'})

    return cleaned, logs

if __name__ == '__main__':
    raw = [
        {'t': 't1', 'point': 'P07', 'd': 12.4},
        {'t': 't2', 'point': 'P07', 'd': 12.6},
        {'t': 't3', 'point': 'P07', 'd': 41.9},
        {'t': 't4', 'point': 'P07', 'd': -3.2},
        {'t': 't5', 'point': 'P07', 'd': 999.0},
    ]
    cleaned, logs = clean_displacement(raw)
    for c in cleaned:
        print(c)
    print('--- 清洗日志 ---')
    for lg in logs:
        print(lg)

结果判读与验证要点

  • t3 跳变:41.9 与前一有效值 12.6 相差 29.3 mm,超过 20 mm 阈值,被标记 JUMP 并以 12.6 暂代;若需更平滑,可在前后有效值之间做线性插值。
  • t4 负值:−3.2 mm 被置为 NULL 并写入 R2 日志,后续速率计算应跳过该点,避免出现虚假的“回弹”速率。
  • t5 超量程:999.0 mm 超过 500 mm 上限,整条记录剔除并标记 RANGE,同时应触发设备自检流程。
  • 日志完整性:每条被处置的记录都必须包含时间、测点、规则编号、原值与动作,便于事后追溯与规则调优。
规则顺序会影响结果:若先判负值再判跳变,则负值不会被误判为跳变;建议按“超量程→负值→跳变”的顺序执行,命中即终止后续判定。
阈值不是越小越好。跳变阈值过小会把真实加速变形误判为异常而插值抹平,反而掩盖预警信号;应结合测点历史波动统计(如 3σ 或分位数)设定,并保留人工复核通道。