4、缺失值处理(上):缺失值检测(isnull/notnull)、缺失值统计与可视化

数据清洗里,缺失值处理是绕不开的一关。

我做了这么多年投研平台,见过太多因为缺失值没处理好,导致模型跑偏、回测结果虚高的案例。说白了,缺失值就是数据里的「黑洞」——你不把它找出来,它就会在背后悄悄搞事情。

这一节,我们先解决最基础的问题:怎么发现缺失值?怎么统计它?怎么把它画出来?

4.1 缺失值长什么样?

在 pandas 里,缺失值通常用 NaN(Not a Number)表示。但实际数据里,它可能伪装成 None、空字符串 '',甚至是 0-999

嗯,这里要注意:0 不是缺失值,它是真实值。我遇到过有人把 0 当缺失值删了,结果收益率曲线直接崩了。

⚠️ 注意: 业务上的「缺失」和代码里的「缺失」是两码事。比如某只股票当天停牌,成交量为0,这个0不是缺失,是真实发生的。别一刀切。

4.2 缺失值检测:isnull() 和 notnull()

这两个函数是检测缺失值的基本工具。

  • isnull():返回布尔值,缺失为 True,否则 False
  • notnull():正好相反,缺失为 False,否则 True

我个人习惯先用 isnull() 扫一遍,心里有个底。

import pandas as pd
import numpy as np

# 模拟一份投研数据
df = pd.DataFrame({
    '股票代码': ['000001', '000002', '000003', '000004'],
    '收盘价': [10.5, np.nan, 11.2, np.nan],
    '成交量': [100000, 200000, np.nan, 150000],
    '市盈率': [8.5, 9.2, np.nan, np.nan]
})

# 检测缺失值
print(df.isnull())

输出结果长这样:

   股票代码  收盘价   成交量   市盈率
0  False  False  False  False
1  False   True  False  False
2  False  False   True   True
3  False   True  False   True

你看,True 的位置就是缺失值。一眼就能看出哪些地方有问题。

💡 小技巧: 如果数据量很大,用 df.isnull().sum() 直接看每列有多少缺失值,比肉眼扫快得多。

4.3 缺失值统计:到底缺了多少?

光知道哪里缺还不够,你得知道缺了多少、占比多少。这决定了你后续怎么处理——是删、是补、还是留着。

我一般会做三件事:

  1. 按列统计缺失数量
  2. 按列统计缺失比例
  3. 按行统计缺失数量
# 按列统计缺失数量
missing_count = df.isnull().sum()
print("每列缺失数量:")
print(missing_count)

# 按列统计缺失比例
missing_ratio = df.isnull().mean() * 100
print("\n每列缺失比例(%):")
print(missing_ratio)

# 按行统计缺失数量
row_missing = df.isnull().sum(axis=1)
print("\n每行缺失数量:")
print(row_missing)

输出结果:

每列缺失数量:
股票代码    0
收盘价     2
成交量     1
市盈率     2
dtype: int64

每列缺失比例(%):
股票代码     0.0
收盘价     50.0
成交量     25.0
市盈率     50.0
dtype: float64

每行缺失数量:
0    0
1    1
2    2
3    2
dtype: int64

你看,收盘价市盈率 都缺了一半。这种比例,直接删掉就太可惜了。我曾经在回测时删掉了一列缺失率 40% 的因子,结果模型 AUC 掉了 0.15——后来发现那个因子虽然缺得多,但信息量很大。

🔑 核心原则: 缺失比例 < 5%,可以考虑直接删除行;5% ~ 30%,需要填充;> 30%,要慎重,先分析缺失原因再决定。

4.4 缺失值可视化:一张图胜过千言万语

数字统计很精确,但不够直观。我习惯用可视化来快速定位缺失模式。

常用的工具有两个:missingnoseaborn.heatmap

4.4.1 使用 missingno 库

missingno 是专门做缺失值可视化的库,轻量又好用。

import missingno as msno
import matplotlib.pyplot as plt

# 缺失值矩阵图
msno.matrix(df)
plt.show()

# 缺失值条形图
msno.bar(df)
plt.show()

# 缺失值相关性热力图
msno.heatmap(df)
plt.show()
  • matrix 图:白色条带就是缺失位置,一眼看出缺失是否集中。
  • bar 图:每列缺失数量的柱状图,适合快速对比。
  • heatmap 图:显示列与列之间缺失的相关性。如果两列经常同时缺失,说明可能有共同原因。
💡 实战经验: 我在做财务因子清洗时,发现「研发费用」和「无形资产」的缺失高度相关(相关系数 0.92)。后来一查,是因为很多中小公司根本不披露研发费用,导致两列同时缺失。这种模式,直接填充均值是不对的,得按行业分组处理。

4.4.2 使用 seaborn 热力图

如果你不想装额外库,用 seaborn 也能画缺失热力图。

import seaborn as sns

# 先转成布尔矩阵,再画热力图
sns.heatmap(df.isnull(), cbar=False, cmap='viridis')
plt.title('缺失值热力图')
plt.show()

深色代表缺失,浅色代表有值。这种图适合看整体分布,但不如 missingno 精细。

4.5 知识体系总览

下面这张图,把缺失值检测的核心逻辑串起来了。我建议你保存下来,以后做清洗时对照着看。

缺失值检测知识体系 缺失值检测 缺失值检测 isnull() notnull() 缺失值统计 按列统计 按行统计 缺失比例 缺失值可视化 matrix 图 bar 图 热力图 目标:定位缺失模式 → 决定处理策略

4.6 避坑指南

最后,分享几个我踩过的坑:

  • 别只看 NaN:有些数据用 -9999999 或空字符串表示缺失。记得先做值域检查。
  • 时间序列的缺失要小心:股票数据里,非交易日就是缺失。这种缺失是正常的,别乱补。
  • 缺失模式比缺失数量更重要:如果缺失是随机的,填充均值问题不大。如果是系统性的(比如某只股票连续10天缺失),那就要深挖原因了。
📌 一句话总结: 缺失值检测不是走流程,而是为了理解数据背后的故事。先看清「为什么缺」,再决定「怎么补」。

公众号:蓝海资料掘金营,微信deep3321