4、缺失值偏差:缺失值类型(MCAR、MAR、MNAR)、Spark中检测缺失值、缺失率统计

缺失值是特征工程中最常见的数据质量问题之一。缺失值并非随机出现,其缺失机制直接影响后续填充策略与模型偏差。本章系统梳理三种缺失值类型,并给出在 Spark 环境下检测缺失值与统计缺失率的完整方法。

4.1 缺失值三种机制:MCAR、MAR、MNAR

根据缺失数据与观测数据、未观测数据之间的依赖关系,缺失值可分为三类。理解这三类机制是选择填充方法的前提。

类型 全称 定义 典型场景 对模型影响
MCAR 完全随机缺失 缺失与否完全独立于所有变量(观测值与非观测值) 传感器随机断电、问卷随机漏填 无偏差,可直接删除或简单填充
MAR 随机缺失 缺失与否依赖于其他已观测变量,但与自身真实值无关 高收入人群更倾向不填收入字段(但收入值本身可被其他变量预测) 条件无偏,可用观测变量建模填充
MNAR 非随机缺失 缺失与否与缺失变量自身的真实值直接相关 病情严重者拒绝填写健康评分(评分越低越不填) 存在系统性偏差,需领域知识或敏感度分析
关键判断原则: 如果缺失概率可以用数据中其他字段解释,则倾向于 MAR;如果缺失概率与缺失字段本身的值有关且无法被其他字段解释,则为 MNAR。MCAR 在真实生产数据中极少见。

4.2 缺失机制判别流程

实际项目中无法 100% 确定缺失类型,但可通过以下步骤辅助判断:

  1. 统计缺失率:计算每列缺失比例,识别高缺失字段。
  2. 分组对比:按其他特征分组,观察缺失率是否随分组变化(如按年龄段分组看收入缺失率)。
  3. 相关性检验:计算缺失指示变量与其他特征的相关性(点二列相关系数)。
  4. 领域知识介入:结合业务逻辑判断缺失是否与未观测因素有关。
实践建议: 在 Spark 中先做缺失率统计与分组对比,若发现缺失率与某些特征强相关,则倾向于 MAR;若缺失率在各分组中均匀分布,可近似视为 MCAR;若无法解释,按 MNAR 处理并做敏感性分析。

4.3 Spark 中检测缺失值

Spark DataFrame 提供多种方式检测缺失值。以下为最常用的三种方法:

4.3.1 逐列统计缺失数量

from pyspark.sql import functions as F

# 统计每列缺失数量(null 或 NaN)
def missing_count(df):
    return df.select([
        F.count(F.when(F.col(c).isNull() | F.isnan(F.col(c)), c)).alias(c)
        for c in df.columns
    ])

missing_count(df).show()

4.3.2 缺失率统计(推荐)

# 统计每列缺失率
def missing_rate(df):
    total = df.count()
    return df.select([
        (F.count(F.when(F.col(c).isNull() | F.isnan(F.col(c)), c)) / total).alias(c)
        for c in df.columns
    ])

missing_rate(df).show()

4.3.3 缺失模式汇总

# 查看缺失模式:哪些列同时缺失
df.select([
    F.when(F.col(c).isNull(), 1).otherwise(0).alias(c)
    for c in df.columns
]).groupby(*df.columns).count().show(10)
注意: isnan() 仅对数值类型有效,字符串类型请使用 isNull()。对于空字符串,建议先转换为 null 再统计。

4.4 缺失机制判别与缺失率统计流程

下图展示了从原始数据到缺失类型判别的完整流程:

缺失值检测与缺失机制判别流程 原始 DataFrame 步骤1:缺失率统计 步骤2:分组对比缺失率 步骤3:缺失指示变量相关性 步骤4:结合领域知识判别 MCAR/MAR/MNAR 关键指标 缺失率 > 20%:高缺失 缺失率 5%-20%:中等缺失 缺失率 < 5%:低缺失 判别提示 分组间缺失率差异大 → MAR 分组间缺失率均匀 → 近似 MCAR 无法解释 → 按 MNAR 处理

4.5 缺失率统计最佳实践

在实际项目中,建议将缺失率统计封装为可复用的工具函数,并输出结构化的结果表:

def missing_summary(df, threshold=0.05):
    """
    返回缺失率统计表,并标记高缺失列
    threshold: 缺失率阈值,默认 5%
    """
    total = df.count()
    summary = []
    for col_name in df.columns:
        null_count = df.filter(
            F.col(col_name).isNull() | F.isnan(F.col(col_name))
        ).count()
        rate = null_count / total
        summary.append((col_name, null_count, round(rate, 4), "高缺失" if rate > threshold else "正常"))
    return spark.createDataFrame(summary, schema=["列名", "缺失数量", "缺失率", "标记"])

missing_summary(df, threshold=0.1).show(truncate=False)

4.6 缺失类型对填充策略的影响

缺失类型 推荐填充方法 不推荐的方法 Spark 实现示例
MCAR 均值/中位数填充、直接删除 复杂建模填充(过度拟合) df.fillna(df.select(F.mean("col")).collect()[0][0])
MAR 回归填充、KNN 填充、模型预测 简单均值填充(引入偏差) 使用 VectorAssembler + LinearRegression 预测
MNAR 领域知识填充、缺失指示变量、敏感性分析 自动填充(掩盖偏差) 新增 is_missing 列 + 业务规则填充
核心原则: 缺失值处理不是简单的“填上就行”,而是先判断缺失机制,再选择与机制匹配的填充策略。错误的填充方法会引入系统性偏差,比缺失本身更危险。