4. 数据清洗基础:缺失值识别与处理
大家好,我是老张。今天咱们聊聊数据清洗里最基础、也最绕不开的一个话题——缺失值。
说实话,我做了这么多年环境数据分析,几乎没遇到过一份“完美”的数据集。要么是传感器故障,要么是人工录入遗漏,要么是传输丢包。缺失值就像空气里的PM2.5,你躲不开,只能学会怎么处理它。
4.1 缺失值识别:isnull 与 isna
在Python里,识别缺失值最常用的两个函数就是 isnull() 和 isna()。其实它们俩是一回事,isna 是 isnull 的别名,用哪个都行。我个人习惯用 isna(),因为拼写短一点,少打几个字母嘛。
核心概念:缺失值在Pandas中通常表示为 NaN(Not a Number),或者 None。这两个函数会返回一个布尔型的DataFrame,缺失的位置是 True,否则是 False。
import pandas as pd
import numpy as np
# 模拟一份环境监测数据
df = pd.DataFrame({
'站点': ['A站', 'B站', 'C站', 'D站'],
'PM2.5': [35, np.nan, 42, 28],
'温度': [22.5, 23.1, np.nan, 21.8],
'湿度': [65, 70, 68, np.nan]
})
print("原始数据:")
print(df)
print("\n使用isna()识别缺失值:")
print(df.isna())
输出结果会告诉你:B站的PM2.5缺失了,C站的温度缺失了,D站的湿度缺失了。嗯,一目了然。
小技巧:如果你只想看哪些列有缺失值,可以用 df.isna().any()。它会返回每列是否有至少一个缺失值。我在项目中经常先用这步快速扫描数据。
4.2 缺失值统计:到底丢了多少?
识别出缺失值之后,下一步就是统计。你想想看,如果整个数据集只有0.1%的缺失,那处理起来很简单;但如果缺失了30%,那就要慎重了。
我常用的统计方法有几种:
# 方法1:统计每列缺失数量
print("每列缺失数量:")
print(df.isna().sum())
# 方法2:统计缺失比例
print("\n每列缺失比例:")
print(df.isna().mean().round(4) * 100, "%")
# 方法3:总缺失情况
total_missing = df.isna().sum().sum()
total_cells = df.size
print(f"\n总缺失率:{total_missing}/{total_cells} = {total_missing/total_cells:.2%}")
我曾经遇到过一个项目,某条河流的pH值数据缺失率高达40%。当时我第一反应是“这数据还能用吗?”后来发现是那个监测站点的pH传感器在雨季经常故障。嗯,这种情况就需要跟业务方沟通了。
| 统计指标 | 代码 | 说明 |
|---|---|---|
| 每列缺失数量 | df.isna().sum() |
返回每列缺失值的个数 |
| 每列缺失比例 | df.isna().mean() |
返回每列缺失值的占比 |
| 总缺失数量 | df.isna().sum().sum() |
整个DataFrame的缺失总数 |
| 总缺失率 | df.isna().sum().sum() / df.size |
整个数据集的缺失比例 |
4.3 缺失值可视化:用missingno一眼看穿
数字统计虽然精确,但不够直观。我特别喜欢用 missingno 这个库来做缺失值可视化。它就像给数据拍了一张X光片,哪里缺了、缺了多少、有没有规律,一眼就能看出来。
注意:missingno不是Python标准库,需要先安装:pip install missingno
import missingno as msno
import matplotlib.pyplot as plt
# 生成一个稍大一点的示例数据
np.random.seed(42)
data = np.random.randn(100, 5)
data[np.random.randint(0, 100, 20), np.random.randint(0, 5, 20)] = np.nan
df_large = pd.DataFrame(data, columns=['温度', '湿度', '风速', '气压', '降水量'])
# 1. 缺失值矩阵图(最常用)
msno.matrix(df_large)
plt.title('缺失值矩阵图 - 白色条带表示缺失')
plt.show()
# 2. 缺失值柱状图
msno.bar(df_large)
plt.title('每列缺失数量柱状图')
plt.show()
# 3. 缺失值热力图(看相关性)
msno.heatmap(df_large)
plt.title('缺失值相关性热力图')
plt.show()
这三种图各有用途:
- 矩阵图:白色横条就是缺失的位置。如果白色条带集中在某几行,说明那几天的数据整体有问题。
- 柱状图:快速比较各列的缺失数量。我一般先看这个,心里有个数。
- 热力图:这个比较高级。它能告诉你——温度缺失的时候,湿度是不是也容易缺失?如果两个变量经常同时缺失,那可能是同一个传感器的问题。
实战经验:有一次我在分析某城市空气质量数据时,发现PM2.5和PM10的缺失模式高度相关(热力图上相关系数0.85)。后来一查,果然是同一个颗粒物监测仪在特定时段重启导致的。这个发现帮我们省了不少排查时间。
4.4 缺失值处理策略:删还是补?
好了,现在你知道缺失值在哪、有多少了。接下来就是关键问题:怎么处理?
说白了,策略就两大类:删除 和 填充。但具体选哪个,要看情况。
4.4.1 删除缺失值
# 删除包含任何缺失值的行
df_drop_any = df.dropna()
print("删除任何有缺失的行:")
print(df_drop_any)
# 删除全部缺失的行(很少用)
df_drop_all = df.dropna(how='all')
# 删除缺失超过一定比例的行
df_drop_thresh = df.dropna(thresh=len(df.columns) - 1) # 至少保留1个非缺失值
删除策略适用于:缺失比例很小(比如<5%),或者缺失完全是随机的。但要注意,删除数据可能会丢失信息。我曾经因为手滑删了太多行,导致时间序列不连续,后面做趋势分析时出了大问题。嗯,从那以后我删数据前都会先备份。
4.4.2 填充缺失值
填充是更常用的方法。常见的有以下几种:
# 1. 用固定值填充(比如0)
df_fill_0 = df.fillna(0)
# 2. 用均值/中位数填充(数值型数据常用)
df_fill_mean = df.fillna(df.mean())
df_fill_median = df.fillna(df.median())
# 3. 用前向/后向填充(时间序列数据常用)
df_fill_ffill = df.fillna(method='ffill') # 用上一个值填充
df_fill_bfill = df.fillna(method='bfill') # 用下一个值填充
# 4. 用插值法填充
df_fill_interp = df.interpolate() # 线性插值
| 填充方法 | 适用场景 | 注意事项 |
|---|---|---|
| 固定值填充 | 明确知道缺失值应该是什么(如缺失记为0) | 会引入偏差,慎用 |
| 均值/中位数填充 | 数值型数据,缺失比例不高 | 会降低数据方差 |
| 前向/后向填充 | 时间序列数据,短期缺失 | 假设数据变化缓慢 |
| 插值法 | 数据有趋势或周期性 | 计算量较大,但效果较好 |
我的建议:没有万能的方法。我一般会先画缺失值矩阵图,看看缺失模式。如果是随机缺失且比例小,用均值填充就够了。如果是连续缺失(比如连续3小时数据丢失),我会用插值法。如果是关键指标缺失太多(比如超过30%),我会跟业务方商量,看是不是要重新采集或者调整模型。
4.5 本章知识体系
下面这张图是我自己整理的缺失值处理流程,你可以把它当作一个决策树来用:
这张图总结了我们今天讲的内容:从识别缺失值开始,到统计、可视化,最后根据缺失比例选择不同的处理策略。你可以把它打印出来贴在工位上,遇到缺失值问题就按这个流程走一遍。
最后说一句:数据清洗没有标准答案。我见过有人把所有缺失值都删掉,也见过有人用机器学习模型预测缺失值。关键是要理解你的数据、理解业务场景。工具和方法都是手段,不是目的。
公众号:蓝海数据掘金营,微信deep3321