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 缺失机制判断流程

在实际项目中,判断缺失机制通常遵循以下步骤:

  1. 统计检验:使用 Little's MCAR 检验。如果 p 值 > 0.05,则不能拒绝 MCAR 假设。
  2. 逻辑回归分析:以缺失指示变量 R 为因变量,以其他完整变量 X 为自变量建立逻辑回归模型。如果模型显著,则数据可能为 MAR 或 MNAR。
  3. 领域知识评估:结合业务逻辑判断缺失是否可能与缺失变量本身有关。例如,在医疗数据中,重症患者更可能缺失某些检查结果,这通常是 MNAR 的信号。
  4. 敏感性分析:在 MAR 假设下进行插补,然后改变假设(如假设 MNAR 并调整插补值),观察分析结果是否稳定。
缺失机制判断流程图 原始数据含缺失值 步骤1:Little's MCAR 检验 p > 0.05 → MCAR p ≤ 0.05 → 非MCAR MCAR:可直接删除或简单插补 步骤2:逻辑回归分析缺失指示变量 模型显著 → 可能 MAR 或 MNAR 步骤3:领域知识 + 敏感性分析 → 区分 MAR/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 检验对样本量敏感,大样本下即使微小偏离也会显著。