5.4 识别并处理异常值
规范日期与单位后,仍会有一些观测远离其他数值。异常值(outlier)是指在某个选定参考分布或规则下不寻常的观测,它并不等同于错误。五小时配送可能来自重复时间戳、道路封闭、单位问题,也可能是真实极端事件;区分原因比标记本身更重要。
区分不可能值与统计异常值
先使用业务约束:
minutes = deliveries["minutes"]
domain_invalid = (minutes.lt(0) | minutes.gt(24 * 60)).fillna(False)负的经过时间可能表示时间戳顺序颠倒或时钟问题。超过一天的用时可能违反当前表格的流程契约。这些不是统计判断,而是来自变量定义。纠正或排除之前,应先确认契约。
然后使用均值之外的指标总结分布:
summary = minutes.describe(
percentiles=[0.01, 0.25, 0.5, 0.75, 0.95, 0.99]
)比较均值和中位数,检查分位数,并查看两端的原始记录。均值与中位数相差很大,可能表示偏斜或极端值,却无法指出原因。
把检测规则当作调查透镜
四分位距(Interquartile Range, IQR)定义为
一种常见筛查规则会标记低于
或高于
的数值。在 pandas 中:
valid = minutes.mask(domain_invalid)
q1, q3 = valid.quantile([0.25, 0.75])
iqr = q3 - q1
iqr_flag = (
valid.lt(q1 - 1.5 * iqr)
| valid.gt(q3 + 1.5 * iqr)
)系数 1.5 是惯例,不是定律。在强烈偏斜分布中,它可能标记许多合法尾部值;小样本中的四分位数也可能不稳定。
经典标准分数(z-score)使用均值 和标准差 :
但被检测的观测本身会拉动 和 ,从而削弱规则。一种稳健替代方法使用中位数与中位数绝对偏差(Median Absolute Deviation, MAD):
常用缩放后的修正分数为
当 MAD 为零时,除法没有定义。当至少一半观测共享同一数值时,就可能发生这种情况。此时应使用业务规则、检查唯一值,或选择另一项有依据的尺度估计,不能强行计算分数。
背景会改变“异常”的含义
90 分钟配送在紧凑城区可能很极端,在郊区则可能很普通。只有当群体拥有足够观测且流程确实不同,才应计算分组参考:
zone_stats = deliveries.groupby("zone")["minutes"].agg(
n="count",
median="median",
q1=lambda values: values.quantile(0.25),
q3=lambda values: values.quantile(0.75),
)很小的群体会产生不稳定边界,还可能隐藏跨组异常。应同时比较全局与分组条件视图。
单变量规则也会漏掉异常组合。一笔配送的距离和用时都可能单独看很普通,两者组合出的速度却不合理。派生检查可以编码关系:
hours = deliveries["minutes"].div(60)
speed_kmh = deliveries["distance_km"].div(hours)
relationship_invalid = speed_kmh.gt(160) | speed_kmh.lt(0)标记仍只是调查起点。极高的计算速度可能暴露单位不匹配或时间戳问题,而不是车辆真的以该速度行驶。
选择处理方式之前调查来源
对于每条被标记记录,应复核:
- 不可变源行与源系统事件历史。
- 单位、解析结果、时区和转换血缘。
- 影响该记录的重复或实体解析决策。
- 天气、促销、系统中断或道路封闭等运营背景。
- 该事件是不可能、错误、罕见但有效,还是仍未解决。
应保存独立标志与复核状态。一组实用状态是 confirmed_error、verified_extreme 和 unresolved。不要把一个统计布尔值直接变成删除命令。
不同处理回答不同问题
保留:经过核实的极端值属于观测总体。除了基于均值的统计量,还要报告中位数与 IQR 等稳健摘要。
纠正:如果来源证明是可恢复的录入或单位错误,应创建修正后的分析字段,同时保留原值、纠正原因与规则版本。
排除:从某项特定分析中移除确认无效记录,而不是从历史证据中删除。必须说明分析总体如何变化。
截尾(winsorize):在一个分析场景中,用声明的边界替换尾部数值:
lower, upper = verified.quantile([0.01, 0.99])
winsorized = verified.clip(lower=lower, upper=upper)这样会限制影响力,却也改变总量与尾部行为。除非边界有业务依据,否则它不属于修复。
转换:对数可以让正值右偏变量更易建模:
import numpy as np
log_minutes = np.log1p(deliveries["minutes"])转换改变尺度,不改变事实。它不能让无效负值变得有效,结果也必须在转换尺度或反变换尺度上解释。
展示敏感性,不要隐藏判断
在不覆盖原始列的情况下建立并行场景:
raw_source = deliveries["minutes"].copy()
raw_analysis = raw_source.astype("Float64")
confirmed_error = deliveries["outlier_status"].eq("confirmed_error")
verified = raw_analysis.mask(confirmed_error)
lower, upper = verified.quantile([0.01, 0.99])
scenarios = {
"keep_all": raw_analysis,
"remove_confirmed_errors": verified,
"winsorize_verified_1_99": verified.clip(lower, upper),
}每个场景都应报告样本量、均值、中位数、上部分位数和总量,然后重复主要业务比较或模型。如果某个结论在合理策略下发生反转,这种敏感性就是结果的一部分。
第 5 章清洗契约
一份可辩护的清洗数据集应保留:
- 不可变原始字段或带版本的原始来源。
- 缺失、解析、重复、规范化和异常值标志。
- 与原始表示并存的纠正或标准化分析字段。
- 映射表、阈值、转换因子、幸存规则及其版本。
- 操作前后数量与验证结果。
- 复核决策、未解决案例与敏感性报告。
清洗不是让数据看起来整齐划一,而是在保留足够证据解释每项重大变化的前提下,受控地产生更可用的表示。第 6 章将使用这份清洗表示进行转换、排名、编码与重塑。