4. 缺失值处理(上):识别缺失值、可视化与删除

各位同学,今天咱们来聊聊数据清洗里最让人头疼,但也最基础的问题——缺失值。说实话,我做了这么多年量化投资,见过太多因为缺失值没处理好,导致整个模型崩掉的案例。你想想看,数据里缺了一块,就像盖房子少了几块砖,后面再怎么折腾也白搭。

这一节,咱们先搞定缺失值的识别、可视化,以及最粗暴但有时候也最有效的处理方式——直接删除。嗯,别急着跳过,这里面的门道可不少。

4.1 识别缺失值:isnull() 与 isnull().sum()

拿到一份数据,第一件事是什么?我个人习惯,先看看数据长什么样,尤其是哪些地方是空的。Pandas 里最常用的就是 isnull() 这个方法。

isnull() 会返回一个和原 DataFrame 形状一样的布尔型 DataFrame。每个单元格如果是缺失值,就标为 True,否则是 False。说白了,就是给你画一张「缺失地图」。

import pandas as pd
import numpy as np

# 模拟一份景气度数据
df = pd.DataFrame({
    '日期': ['2024-01', '2024-02', '2024-03', '2024-04'],
    'PMI': [50.2, np.nan, 51.0, np.nan],
    '工业增加值': [6.1, 5.8, np.nan, 6.3],
    '社融规模': [4.5, np.nan, np.nan, 5.1]
})

# 识别缺失值
print(df.isnull())

输出结果会告诉你,哪些位置是空的。但光看这个还不够直观,我们更想知道每列有多少缺失值。这时候 isnull().sum() 就派上用场了。

# 统计每列缺失值数量
print(df.isnull().sum())

输出类似这样:

日期          0
PMI          2
工业增加值     1
社融规模      2
dtype: int64

你看,一目了然。PMI 和社融规模各缺了 2 个,工业增加值缺了 1 个。日期列是完整的。我在项目中遇到过,有些新手一上来就 dropna() 全删了,结果发现删掉了 80% 的数据,那还分析个啥?所以,先摸清缺失值的家底,是第一步。

小技巧: 如果你想知道缺失值占总数据的比例,可以这样写:df.isnull().sum() / len(df) * 100。这样能更直观地判断缺失是否严重。

4.2 缺失值可视化:missingno 库

光看数字有时候不够直观。你想想看,如果数据有几百列,你一个个看 sum() 的结果,眼睛不花才怪。这时候,可视化就派上用场了。我强烈推荐 missingno 这个库,专门用来画缺失值。

安装很简单:

pip install missingno

然后咱们来看看怎么用:

import missingno as msno
import matplotlib.pyplot as plt

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

这个矩阵图里,白色的地方就是缺失值,黑色的地方是正常值。一眼就能看出哪些列缺失严重,以及缺失值之间有没有关联。比如,如果 PMI 和社融规模同时缺失,那可能这两个数据来源是同一个渠道出了问题。

还有一个更实用的图叫 条形图

msno.bar(df)
plt.show()

这个图直接显示每列的非缺失值数量,相当于把 isnull().sum() 画成了柱状图。我个人习惯,拿到数据先跑一个 msno.matrix(),再跑一个 msno.bar(),基本就能对数据质量有个全局把握了。

避坑指南: 我曾经在做一个宏观景气度项目时,发现某个月的 PMI 和工业增加值同时缺失。一开始以为是数据源问题,后来用 missingno 的 heatmap 功能一看,发现这两个变量的缺失模式高度相关。原来是因为那个月统计局调整了统计口径,导致部分数据延迟发布。如果不做可视化,光看数字根本发现不了这个规律。

4.3 删除缺失值:dropna()

好了,识别完了,也看清楚了。接下来怎么办?最直接的方法就是——删掉。Pandas 提供了 dropna() 方法,可以删除包含缺失值的行或列。

基本用法:

# 删除所有包含缺失值的行
df_clean = df.dropna()
print(df_clean)

输出结果:

        日期   PMI  工业增加值  社融规模
0  2024-01  50.2      6.1    4.5

你看,只有第一行是完整的,其他行都被删掉了。这在实际项目中往往太激进。你想想看,如果数据本来就少,这么一删可能就剩个零头了。

所以,dropna() 有几个参数可以精细控制:

  • axis=0:默认按行删除(axis=0),也可以按列删除(axis=1)。
  • how='any':默认只要有一个缺失值就删除。可以改成 how='all',只有整行全为空时才删除。
  • thresh:指定非缺失值的最小数量。比如 thresh=3 表示该行至少有 3 个非缺失值才保留。
  • subset:只检查某些列是否有缺失值。

举个例子:

# 只删除 PMI 和 社融规模 这两列都缺失的行
df_clean2 = df.dropna(subset=['PMI', '社融规模'], how='all')
print(df_clean2)

输出:

        日期   PMI  工业增加值  社融规模
0  2024-01  50.2      6.1    4.5
1  2024-02   NaN      5.8    NaN
3  2024-04   NaN      6.3    5.1

你看,第 2 行(索引 2)因为 PMI 和社融规模都缺失,被删掉了。但第 1 行和第 3 行虽然也有缺失,但至少有一列是完整的,所以保留了下来。这在处理多变量数据时非常实用。

注意: 删除缺失值一定要谨慎。我见过有人一上来就 df.dropna(inplace=True),结果把整个数据集删得面目全非。建议先备份原始数据,或者用 df.copy() 创建一个副本再操作。记住,数据删了就回不来了。

4.4 知识体系总览

为了让你更直观地理解这一节的内容,我画了一张流程图,把缺失值处理的逻辑串起来:

缺失值处理流程(上) 1. 识别缺失值 缺失严重? 严重 不严重 2. 可视化(missingno) 2. 统计(isnull().sum()) 是否删除? 3. 删除(dropna) 3. 下一节:填充

这张图把咱们这一节的内容串起来了。先识别,再判断缺失严重程度,然后决定是可视化深入分析,还是直接统计。最后,如果决定删除,就用 dropna() 精细操作。如果不删除,那就留到下一节讲填充方法。

4.5 实战小贴士

最后,分享几个我在实际项目中总结的经验:

  • 先备份,再操作:任何时候,对原始数据做修改前,先 df_backup = df.copy()。这是血的教训。
  • 不要盲目删除:如果缺失值占比超过 30%,删除可能不是好主意。这时候要考虑填充或者用模型预测。
  • 注意缺失模式:用 missingno 的 heatmap 看看缺失值之间有没有相关性。如果有,说明缺失不是随机的,背后可能有系统性原因。
  • 记录你的操作:我习惯在代码里加注释,记录每一步删除了多少行、为什么删。这样复盘时能快速定位问题。

好了,这一节的内容就到这里。缺失值识别和删除是数据清洗的基本功,但也是最容易出错的地方。你想想看,如果连数据里缺了什么都不知道,后面再高级的分析也是空中楼阁。下一节咱们会讲缺失值的填充方法,那才是真正考验功力的地方。


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