跳到内容

2.2 异常、重复与实体一致性:识别可疑记录而不是自动删除

阿花在战报中发现温度 999°C 和两笔相似订单,但管理员不允许把“看着可疑”直接变成删除动作。

温度 999°C 可能超过设备量程,也可能是缺失占位符;支付金额突然增长十倍可能是攻击,也可能是促销日。异常检测首先产生调查候选,不直接产生删除命令。

本课目标

  • 区分无效值、统计离群点和真正异常事件;
  • 正确使用 IQR、稳健分数和分组基线;
  • 区分重复投递、业务重复和实体重复;
  • 用确定性 survivorship 规则合并记录并保留审计轨迹。

1. 三类“异常”不要混用

类别判断依据示例常见处理
无效值违反契约或物理边界设备量程上限 150,却记录 999隔离、置缺失、修源头
统计离群点相对分布罕见收入位于顶部 0.1%标记、稳健建模、核查
业务异常与风险或事件定义相关同账户一分钟百次转账调查、告警、保留证据

离群不等于错误,正常范围内也可能有欺诈。删除罕见值会让系统失去最值得研究的事件。

2. 领域约束优先

python
invalid_temperature = ~frame["temperature_c"].between(-50, 150)
invalid_duration = frame["ended_at"] < frame["started_at"]
invalid_status = ~frame["status"].isin({"ok", "warning", "failed"})

这些规则需要版本和适用条件:不同传感器型号量程不同,某些时钟校正可能产生暂时负 duration。把条件写进规则配置并记录命中原因。

跨字段规则往往比单列范围更有价值:结束时间不得早于开始时间,completed 状态必须有完成时间,货币与金额精度要匹配。

3. IQR 规则只标记候选

$$ IQR=Q_3-Q_1, $$

常见围栏:

$$ [Q_1-1.5IQR,\ Q_3+1.5IQR]. $$

python
series = frame["measurement"].dropna()
q1, q3 = series.quantile([0.25, 0.75])
iqr = q3 - q1
lower = q1 - 1.5 * iqr
upper = q3 + 1.5 * iqr
frame["measurement_iqr_flag"] = ~frame["measurement"].between(lower, upper)

1.5 是惯例,不是异常真值。强偏态、多峰和小样本会产生大量误标。若 $IQR=0$,规则尤其需要单独处理。

全局基线还可能掩盖群体差异。数据来自多个设备时,应先按型号、地点或运行模式分组,在样本量足够且不会碎片化过度的前提下建立基线。

4. 时间序列异常要看上下文

时序数据需要考虑:

  • 趋势与季节性;
  • 采样间隔变化;
  • 设备重启和校准;
  • 突变、持续偏移和孤立尖峰;
  • 迟到或乱序记录。

滚动中位数和 MAD 可提供稳健局部基线,但窗口不能包含未来数据用于在线告警。历史回看与实时检测应分别实现,避免前视偏差。

5. 截尾与变换会改变问题

python
lower, upper = training["amount"].quantile([0.01, 0.99])
transformed = frame.assign(
    amount_clipped=frame["amount"].clip(lower=lower, upper=upper),
    amount_was_clipped=~frame["amount"].between(lower, upper),
)

Winsorization 会把不同极端值压成边界值。它可能稳定某些模型,却不适合审计、风险检测或尾部估计。阈值必须只从训练数据拟合,并保留原值与标记。

对数变换可压缩右偏分布,但零、负值和解释尺度需要处理。变换不是“清洗掉异常”,而是改变模型表示。

6. 完全重复只是最简单情况

python
exact_duplicates = frame.duplicated(keep=False)

完全相同行可能来自文件拼接、重复导出,也可能是合法的两次同值测量。没有 event ID 或业务键,无法只凭整行相同判断。

重复类型:

  • delivery duplicate:同一 event ID 被重投;
  • version duplicate:同一实体多个更新时间版本;
  • business duplicate:用户重复提交同一业务动作;
  • entity duplicate:不同 ID 实际指向同一人或对象。

每一类需要不同规则和证据。

7. 确定性去重需要总排序

python
ordered = frame.sort_values(
    ["mission_id", "sensor_id", "event_time", "ingested_at", "event_id"]
)
latest = ordered.drop_duplicates(
    subset=["mission_id", "sensor_id"],
    keep="last",
)

只按 timestamp 排序可能在时间相同时得到不稳定结果。加入唯一 event ID 形成确定性 tie-breaker。选择“最新”也必须符合语义:事件事实可能应全部保留,当前状态表才选最新版本。

去重输出应带:

  • canonical record ID;
  • 被合并记录列表;
  • 选择规则版本;
  • 合并时间与 run ID;
  • 字段级来源。

这样规则变更后才能解释为何某条记录消失。

8. 实体解析不是 drop_duplicates

姓名、地址相似并不证明是同一实体。实体解析可能使用规范化、阻塞、相似度和人工复核,并评估 false merge 与 false split。

错误合并两个真实用户通常比漏合并风险更高,尤其涉及财务、医疗或访问权限。阈值应按业务成本决定,不能只最大化总体准确率。

规范化也要谨慎:统一大小写、空白和 Unicode 形式可能有助匹配,却不能无条件改写需要原样展示或具有区域语义的字段。保留原值,另建 normalized key。

常见误区

  • IQR 外的值就是错误:它只是相对分布的候选标记。
  • 截尾不会影响结论:尾部风险、均值和回归关系都会变化。
  • 整行相同一定是重复:没有事件身份就无法区分重复投递与两次相同观测。
  • 保留最新记录总是正确:事件表与当前态表的语义不同。

练习

  1. 为温度数据分别写无效值、离群值和业务告警规则。
  2. 构造一个全局 IQR 误伤某设备型号正常数据的例子。
  3. 为时间相同的两条版本记录设计确定性 tie-breaker。
  4. 给实体解析定义 false merge 与 false split 的业务成本。

小结

异常规则回答“值得调查什么”,契约规则回答“什么不可能合法”,去重规则回答“哪些记录代表同一事实”。三者不能用一个 drop 操作替代。所有变换都应保留原值、原因和规则版本。

下一课把这些单点规则组装成可重跑的清洗管道,并用不变量、隔离区和差异报告验证清洗没有悄悄改坏数据。

Built with VitePress | Software Systems Atlas