4、缺失值偏差:缺失值类型(MCAR、MAR、MNAR)、Spark中检测缺失值、缺失率统计
缺失值是特征工程中最常见的数据质量问题之一。缺失值并非随机出现,其缺失机制直接影响后续填充策略与模型偏差。本章系统梳理三种缺失值类型,并给出在 Spark 环境下检测缺失值与统计缺失率的完整方法。
4.1 缺失值三种机制:MCAR、MAR、MNAR
根据缺失数据与观测数据、未观测数据之间的依赖关系,缺失值可分为三类。理解这三类机制是选择填充方法的前提。
| 类型 | 全称 | 定义 | 典型场景 | 对模型影响 |
|---|---|---|---|---|
| MCAR | 完全随机缺失 | 缺失与否完全独立于所有变量(观测值与非观测值) | 传感器随机断电、问卷随机漏填 | 无偏差,可直接删除或简单填充 |
| MAR | 随机缺失 | 缺失与否依赖于其他已观测变量,但与自身真实值无关 | 高收入人群更倾向不填收入字段(但收入值本身可被其他变量预测) | 条件无偏,可用观测变量建模填充 |
| MNAR | 非随机缺失 | 缺失与否与缺失变量自身的真实值直接相关 | 病情严重者拒绝填写健康评分(评分越低越不填) | 存在系统性偏差,需领域知识或敏感度分析 |
关键判断原则: 如果缺失概率可以用数据中其他字段解释,则倾向于 MAR;如果缺失概率与缺失字段本身的值有关且无法被其他字段解释,则为 MNAR。MCAR 在真实生产数据中极少见。
4.2 缺失机制判别流程
实际项目中无法 100% 确定缺失类型,但可通过以下步骤辅助判断:
- 统计缺失率:计算每列缺失比例,识别高缺失字段。
- 分组对比:按其他特征分组,观察缺失率是否随分组变化(如按年龄段分组看收入缺失率)。
- 相关性检验:计算缺失指示变量与其他特征的相关性(点二列相关系数)。
- 领域知识介入:结合业务逻辑判断缺失是否与未观测因素有关。
实践建议: 在 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 缺失机制判别与缺失率统计流程
下图展示了从原始数据到缺失类型判别的完整流程:
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 列 + 业务规则填充 |
核心原则: 缺失值处理不是简单的“填上就行”,而是先判断缺失机制,再选择与机制匹配的填充策略。错误的填充方法会引入系统性偏差,比缺失本身更危险。