4. 缺失值处理策略与实战
在多源实验数据中,缺失值几乎不可避免:传感器掉线、样本污染、人工录入遗漏、不同设备采样周期不一致,都会在数据表中留下空洞。但“有缺失”并不等于“可以随便填”。处理缺失值的第一步不是打开代码写 fillna,而是判断缺失产生的机制,因为机制决定了哪些方法在统计上站得住脚、哪些方法会引入系统性偏差。
一、先识别缺失机制:MCAR、MAR、MNAR
统计学把缺失机制分为三类。理解它们的关键是问一个问题:缺失的发生是否与数据本身(尤其是未观测到的值)有关?
- 完全随机缺失(MCAR):缺失与任何变量都无关,纯属随机事件。例如某台温度传感器因偶发通信抖动随机丢包。此时删除含缺失的样本不会使估计产生系统偏差,只是损失样本量。
- 随机缺失(MAR):缺失与已观测变量有关,但与缺失值本身无关。例如高湿度环境下湿度传感器更易失效,而湿度是可观测的;或者低年级学生更倾向漏填某项问卷。此时基于已观测变量做插值或模型预测填充是合理的。
- 非随机缺失(MNAR):缺失与缺失值本身有关。例如浓度超过量程上限时仪器直接报空,或受试者因症状严重而退出导致指标缺失。此时任何仅依赖观测数据的填充都会低估或高估真实分布,必须结合领域知识、敏感性分析或专门模型处理。
实操中可用两条线索辅助判断:一是做缺失指示变量与其它观测变量的相关性检验(若显著相关,倾向 MAR 而非 MCAR);二是结合数据采集流程的物理/业务逻辑推断是否存在 MNAR。需要强调,MCAR/MAR/MNAR 是假设而非可直接验证的事实,MNAR 尤其无法仅凭数据本身证伪,因此结论应写明假设依据。
二、四类方法的适用场景与实现
删除法是最直接的手段,分按行删除(丢弃含缺失的整条记录)和按列删除(缺失率过高的变量整列剔除)。它只在 MCAR 且缺失比例很低时近似无偏;若缺失比例超过约 5%~10%,或缺失与变量相关,删除会扭曲分布。工程上常设阈值:某列缺失率高于 40%~50% 且无关键信息时考虑整列删除,否则优先填充。
统计量填充用均值、中位数或众数补洞。连续且近似对称的变量用均值,存在离群值或偏态时用中位数更稳健,类别变量用众数。它的优点是快,缺点是人为压缩方差、削弱变量间相关性,且对 MAR 场景只是粗略近似。适合作为基线或对精度要求不高的预处理。
插值法利用数据顺序结构,适合时间序列和连续信号。线性插值在相邻两点间连线取值,时间插值按时间戳加权,样条插值适合平滑曲线。它假设缺失点前后趋势连续,因此对传感器短时掉线非常有效,但遇到长时间连续缺失或突变段会失真。
模型预测填充把缺失列当作目标、其它列当作特征训练回归或 KNN,用预测值填补。它能利用变量间相关性,适合 MAR 且变量关系较强的场景;多重插补则通过多次抽样生成多个完整数据集,把插补不确定性纳入后续分析,是统计上更严谨的做法,但实现和解释成本更高。
三、代码实现示例
下面用 pandas 演示同一份实验数据上四种策略的写法(示例数据为教学构造,非实测结果):
import pandas as pd
import numpy as np
from sklearn.linear_model import LinearRegression
df = pd.DataFrame({
"temp": [21.0, 21.5, np.nan, 22.4, 22.8, np.nan, 23.5],
"press": [101.2, 101.3, 101.5, np.nan, 101.8, 102.0, 102.1],
"level": ["低", "低", "中", np.nan, "高", "高", "高"]
})
# 1) 删除法:按行删除
df_drop = df.dropna()
# 2) 统计量填充:中位数 + 众数
df_stat = df.copy()
df_stat["temp"] = df_stat["temp"].fillna(df_stat["temp"].median())
df_stat["level"] = df_stat["level"].fillna(df_stat["level"].mode()[0])
# 3) 插值法:按行序线性插值
df_interp = df.copy()
df_interp["temp"] = df_interp["temp"].interpolate(method="linear")
# 4) 模型预测填充:用 press 预测 temp
train = df.dropna(subset=["temp", "press"])
model = LinearRegression().fit(train[["press"]], train["temp"])
df_model = df.copy()
mask = df_model["temp"].isna() & df_model["press"].notna()
df_model.loc[mask, "temp"] = model.predict(df_model.loc[mask, ["press"]])
运行后应重点观察两点:一是填充前后该列的均值、标准差与缺失比例,判断方差是否被明显压缩;二是填充值与相邻观测值的偏离程度,若出现明显不合理的跳变,说明方法或假设不匹配。对 MNAR 场景,建议额外构造“缺失指示变量”并做敏感性对比,而不是直接采用单一填充结果。