2. 缺失值类型:完全随机缺失(MCAR)、随机缺失(MAR)、非随机缺失(MNAR)的概念与区别
在数据挖掘与特征工程中,缺失值的产生机制直接影响插补策略的选择。错误的假设会导致有偏估计或模型失效。缺失值机制分为三种类型:完全随机缺失(MCAR)、随机缺失(MAR)和非随机缺失(MNAR)。理解三者的区别是选择插补方法的前提。
2.1 三种缺失机制的核心定义
设数据集包含完整变量 X 和缺失变量 Y,缺失指示变量为 R(R=1 表示缺失,R=0 表示观测)。三种机制的定义如下:
| 缺失机制 | 数学表达 | 缺失概率依赖 | 典型场景 |
|---|---|---|---|
| MCAR | P(R | Y, X) = P(R) | 完全独立于数据本身 | 传感器随机故障、问卷随机漏填 |
| MAR | P(R | Y, X) = P(R | X) | 仅依赖已观测变量 X | 高收入人群更倾向不填收入 |
| MNAR | P(R | Y, X) = P(R | Y, X) | 依赖缺失变量 Y 本身 | 病情严重者更可能缺失检查结果 |
关键判断准则:如果缺失概率与缺失值本身无关,则属于 MCAR 或 MAR;如果缺失概率与缺失值的大小或状态直接相关,则属于 MNAR。MNAR 是最难处理的情况,通常需要领域知识或敏感性分析。
2.2 三种机制的详细对比与影响
- MCAR(完全随机缺失):缺失是完全随机的,观测数据是完整数据的一个随机子集。此时直接删除缺失样本(listwise deletion)不会引入偏差,但会损失样本量。均值插补、中位数插补等简单方法在 MCAR 下是有效的。
- MAR(随机缺失):缺失概率依赖于其他已观测变量,但不依赖于缺失变量本身。例如,在收入调查中,年龄越大的人越可能不填收入(年龄已观测)。MAR 是实际数据中最常见的缺失机制。此时需要使用基于模型的插补方法(如多重插补、回归插补)来利用观测变量信息。
- MNAR(非随机缺失):缺失概率与缺失变量本身的值有关。例如,高收入人群故意不填收入,或病情严重的患者拒绝接受检查。MNAR 无法通过观测数据完全验证,需要引入外部信息或进行敏感性分析。简单删除或插补都会导致严重偏差。
实践警告:大多数插补方法(如均值插补、KNN 插补、MICE)都默认数据至少满足 MAR 假设。如果数据实际上是 MNAR,这些方法会产生有偏估计。建议在插补前进行缺失机制检验(如 Little's MCAR 检验),并记录假设条件。
2.3 缺失机制判断流程
在实际项目中,判断缺失机制通常遵循以下步骤:
- 统计检验:使用 Little's MCAR 检验。如果 p 值 > 0.05,则不能拒绝 MCAR 假设。
- 逻辑回归分析:以缺失指示变量 R 为因变量,以其他完整变量 X 为自变量建立逻辑回归模型。如果模型显著,则数据可能为 MAR 或 MNAR。
- 领域知识评估:结合业务逻辑判断缺失是否可能与缺失变量本身有关。例如,在医疗数据中,重症患者更可能缺失某些检查结果,这通常是 MNAR 的信号。
- 敏感性分析:在 MAR 假设下进行插补,然后改变假设(如假设 MNAR 并调整插补值),观察分析结果是否稳定。
2.4 三种机制对插补策略的影响
| 缺失机制 | 推荐插补方法 | 不推荐的方法 | 偏差风险 |
|---|---|---|---|
| MCAR | 均值/中位数插补、直接删除、KNN 插补 | 无特别限制 | 低(仅损失样本量) |
| MAR | 多重插补(MICE)、回归插补、EM 算法 | 简单均值插补(会低估方差) | 中等(需正确建模依赖关系) |
| MNAR | 选择模型、模式混合模型、敏感性分析 | 任何忽略缺失机制的插补 | 高(需外部信息或强假设) |
实战建议:在大多数数据挖掘项目中,数据通常满足 MAR 假设。建议优先使用多重插补(MICE)作为默认策略,因为它对 MAR 数据表现稳健,且能提供不确定性估计。如果怀疑 MNAR,务必在报告中说明假设并展示敏感性分析结果。
2.5 代码示例:使用 Python 检验 MCAR 假设
import pandas as pd
import numpy as np
from scipy.stats import chi2
def little_mcar_test(df):
"""
Little's MCAR 检验:检验缺失是否完全随机
返回:卡方统计量、自由度、p值
"""
# 计算每个变量的缺失模式
missing_pattern = df.isnull().astype(int)
pattern_counts = missing_pattern.value_counts()
# 计算总体均值与协方差
n = len(df)
means = df.mean()
cov = df.cov()
# 计算每个缺失模式的期望与观测差异
chi2_stat = 0
df_total = 0
for pattern, count in pattern_counts.items():
if count < 2:
continue
# 该模式下的观测变量
obs_vars = [i for i, v in enumerate(pattern) if v == 0]
if len(obs_vars) == 0:
continue
# 提取该模式下的数据
pattern_data = df.iloc[missing_pattern.eq(pattern).all(axis=1).values]
pattern_means = pattern_data.mean()
# 计算卡方贡献
diff = pattern_means - means.iloc[obs_vars]
# 简化计算:使用对角协方差
var_diag = np.diag(cov.iloc[obs_vars, obs_vars])
var_diag = np.where(var_diag > 0, var_diag, 1e-6)
chi2_contrib = np.sum((diff ** 2) / var_diag) * count
chi2_stat += chi2_contrib
df_total += len(obs_vars)
p_value = 1 - chi2.cdf(chi2_stat, df=df_total - 1)
return chi2_stat, df_total - 1, p_value
# 示例使用
np.random.seed(42)
data = pd.DataFrame({
'age': np.random.normal(40, 10, 200),
'income': np.random.normal(50000, 15000, 200),
'score': np.random.normal(75, 15, 200)
})
# 随机引入 MCAR 缺失
data.loc[np.random.choice(200, 20, replace=False), 'income'] = np.nan
data.loc[np.random.choice(200, 15, replace=False), 'score'] = np.nan
stat, dof, pval = little_mcar_test(data)
print(f"Little's MCAR 检验结果:")
print(f"卡方统计量 = {stat:.2f}")
print(f"自由度 = {dof}")
print(f"p值 = {pval:.4f}")
if pval > 0.05:
print("结论:不能拒绝 MCAR 假设(数据可能为 MCAR)")
else:
print("结论:拒绝 MCAR 假设(数据可能为 MAR 或 MNAR)")
代码说明:上述代码实现了简化版的 Little's MCAR 检验。实际应用中建议使用
missingno 库的 mcar_test() 函数或 PyMCAR 包进行更稳健的检验。注意:Little's 检验对样本量敏感,大样本下即使微小偏离也会显著。