3. 缺失值识别与分析:定位数据中的“黑洞”
好,咱们进入实战环节。数据合并前,有一件事必须做——搞清楚数据里到底有多少“坑”。说白了,就是缺失值分析。我见过太多项目,合并前没做这一步,结果合并后数据量莫名其妙少了一半,排查半天才发现是某个字段全是空值。
缺失值识别不是简单数一数空了多少。你得知道:缺失在哪?怎么缺的?缺得严不严重?这三问清楚了,后面怎么补、能不能用,心里才有底。
3.1 第一印象:快速摸清缺失全貌
拿到数据集,我习惯先来一套“全身扫描”。用Pandas的几个函数,几秒钟就能看到缺失的分布情况。
import pandas as pd
import numpy as np
# 模拟一份带缺失值的数据集
df = pd.DataFrame({
'id': range(1, 101),
'age': np.random.choice([20, 25, 30, 35, np.nan], size=100, p=[0.3,0.3,0.2,0.1,0.1]),
'income': np.random.choice([5000, 8000, 12000, np.nan], size=100, p=[0.4,0.3,0.2,0.1]),
'city': np.random.choice(['北京','上海','深圳', np.nan], size=100, p=[0.3,0.3,0.3,0.1]),
'score': np.random.choice([85, 90, 95, np.nan], size=100, p=[0.4,0.3,0.2,0.1])
})
# 快速概览:每列缺失数量与占比
missing_count = df.isnull().sum()
missing_rate = (df.isnull().sum() / len(df)) * 100
missing_df = pd.DataFrame({'缺失数量': missing_count, '缺失率(%)': missing_rate.round(2)})
missing_df
输出示例(假设运行后)
缺失数量 缺失率(%)
id 0 0.00
age 10 10.00
income 10 10.00
city 10 10.00
score 10 10.00
看到这表,你就能快速定位:哪些字段有缺失,缺了多少。但别高兴太早——这只是表面数据。真正的难题在后面:这些缺失是怎么分布的?有没有规律?
3.2 可视化:让缺失模式“显形”
数字太抽象了,我建议你画图。一张热力图,就能把缺失模式看得清清楚楚。用 missingno 库,几分钟搞定。
import missingno as msno
import matplotlib.pyplot as plt
# 缺失值矩阵图:白线是缺失,黑线是完整
msno.matrix(df)
plt.title('缺失值矩阵图')
plt.show()
# 缺失值相关性热图:判断缺失是否彼此关联
msno.heatmap(df)
plt.title('缺失值相关性热图')
plt.show()
# 缺失值条形图:直观对比各列缺失率
msno.bar(df)
plt.title('缺失值条形图')
plt.show()
谈谈我个人的经验。矩阵图里,如果白线集中在某几行,说明这几个样本存在系统性缺失——可能是采集设备坏了,或者问卷没填完。如果白线分散在各列,更可能是随机缺失。
热力图更有意思。它显示不同缺失列之间的相关系数。比如 age 和 income 同时缺失的频率很高,那你就得警惕了——这俩字段的缺失可能源于同一个原因,比如某个收入区间的人群不愿意提供年龄。
3.3 统计方法:量化缺失特征
可视化是定性判断,统计方法能给你定量依据。我常用的几种:
- 逐行缺失计数:看有多少样本同时缺失多个字段
- 分组缺失率对比:按某个完整字段分组,看缺失率是否均匀
- Little's MCAR 检验:专门判断缺失是否完全随机
# 逐行缺失计数
df['missing_count'] = df.isnull().sum(axis=1)
print("每行缺失数量分布:")
print(df['missing_count'].value_counts().sort_index())
# 分组缺失率对比:比如按城市分组,看年龄缺失情况
city_groups = df.groupby('city')['age'].apply(lambda x: x.isnull().mean())
print("\n各城市年龄缺失率:")
print(city_groups)
实用建议:如果分组缺失率差异很大(比如深圳年龄缺失率 25%,北京只有 5%),基本可以排除完全随机缺失。这属于系统性偏差,合并前必须处理。
3.4 三把尺子:理解缺失机制
做缺失值分析,最终要回答一个问题:数据为什么缺失?学术界把缺失机制分为三种类型。搞懂了它们,你才能选对处理策略。
| 类型 | 含义 | 判断依据 | 常见场景 |
|---|---|---|---|
| MCAR 完全随机缺失 |
缺失与否与其他变量完全无关 | Little's MCAR 检验 p > 0.05 | 设备随机故障、问卷丢失一页 |
| MAR 随机缺失 |
缺失与否可由其他完整变量预测 | 分组缺失率差异明显 | 高收入群体更不愿透露具体金额 |
| MNAR 非随机缺失 |
缺失原因与缺失值本身有关 | 无法从现有数据验证,需领域知识 | 极度低分用户故意不填满意度 |
Little's MCAR 检验怎么用?代码也简单:
from scipy.stats import chi2
# 简化的 MCAR 检验逻辑(实际生产可用『missingpy』库)
def little_mcar_test(df):
"""
基于缺失模式的卡方检验,
用于判断数据是否可视为 MCAR。
"""
# 构建缺失模式矩阵
missing_pattern = df.isnull().astype(int)
# 计算期望分布
col_missing_rate = missing_pattern.mean()
n = len(df)
# 如果 p > 0.05,不能拒绝 MCAR 假设
# (完整实现较复杂,此处为示意)
return {'chi2_stat': 15.2, 'p_value': 0.083, 'conclusion': '无法拒绝 MCAR'}
result = little_mcar_test(df)
print(f"Chi2 统计量: {result['chi2_stat']}")
print(f"p 值: {result['p_value']}")
print(f"结论: {result['conclusion']}")
避坑指南:我曾经接手一个医疗项目,一开始以为缺失是随机的,直接用均值填充。结果模型效果极差。回头仔细分析发现——高龄患者更少提供体重数据,这根本就是 MAR。后来改用多重插补,效果才有所改善。千万别盲目假设 MCAR,那是偷懒的借口。
3.5 一张图串起整个流程
讲了这么多,我用一张 SVG 图把缺失值识别与分析的流程串起来。你只需要记住这个结构,以后面对任何数据集,按图索骥就行。
3.6 实战复盘:一个真实例子
最后分享一个我踩过的坑。之前做电商用户画像合并,两个订单表按 user_id 合并后,shipping_address 字段缺失率高达 30%。我第一反应是直接删掉,毕竟地址不重要吧?
但一画缺失矩阵,发现问题了:缺失的地址里,90% 是跨境订单。原来跨境订单的地址格式不同,写入了另一个字段。系统没做映射,导致合并时被识别为空。
这个例子说明了什么?缺失值分析不仅仅是看数字,更是在看数据背后的业务逻辑。你想想看,如果我没做可视化,直接删除那 30% 的行,后续的海外市场分析就全废了。
总结一下本节要点:
- 先粗看:用
isnull().sum()快速摸清缺失总量 - 再细看:用
missingno矩阵图定位缺失模式 - 后深挖:用分组对比和 MCAR 检验推断缺失机制
- 要记住:缺失机制不同,处理策略天差地别
下一套流程走下来,你手里的数据就不再是黑箱了。哪些字段能用,哪些要补,哪些甚至要考虑从源头解决,都一清二楚。这才是合并前质控的正确姿势。