第 20 课 - 缺失值处理:检测、填充与删除策略
学习目标
完成本课学习后,你将能够:
- 识别数据集中的缺失值,并理解其产生的主要原因。
- 掌握三种处理缺失值的基本策略:删除、填充和标记。
- 根据数据特征和业务场景,为不同类型的缺失数据选择合适的处理方法。
- 使用 Pandas 库编写完整的代码来处理实际数据集中的缺失值。
核心概念
1. 什么是缺失值?
想象一个记录学生成绩的Excel表格,某个同学的数学成绩那一格是空的。这个“空”就是缺失值。在计算机中,它通常用特殊符号表示,如 Python 中的 None,Pandas 中的 NaN (Not a Number),或数据库中的 NULL。
缺失值产生的原因:
- 数据收集遗漏:用户没有填写某个字段。
- 数据存储问题:设备故障、数据传输错误。
- 数据整合错误:合并不同来源的数据时,某些记录无法匹配。
- 数据本身特性:例如,对于“离婚日期”字段,未婚人士的数据必然是缺失的。
2. 缺失值的类型
理解缺失类型有助于选择正确的处理方法:
- 完全随机缺失 (MCAR):缺失的发生与任何数据都无关。例如,问卷因为打印机墨水用完而没打出来几题。
- 随机缺失 (MAR):缺失的发生与数据集中其他观测变量有关,但与缺失值本身无关。例如,年龄大的受访者更倾向于不填写“游戏时长”,但缺失与否与游戏时长本身无关。
- 非随机缺失 (MNAR):缺失的发生与缺失值本身直接相关。例如,低收入者可能不愿意填写“收入”字段。这是最难处理的情况。
3. 三大处理策略
处理缺失值就像修补一件有破洞的衣服,主要有三种思路:
| 策略 | 比喻 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|---|
| 删除 | 把有破洞的布料直接剪掉。 | 简单、快捷。 | 可能丢失大量有价值的信息。 | 缺失比例非常低,且数据量很大。 |
| 填充 | 用相似的布料(如旁边的颜色)把破洞补上。 | 保留了所有数据样本。 | 引入了可能不准确的数据。 | 缺失比例适中,且缺失值有较强的可预测性。 |
| 标记/保留 | 把破洞原样保留,并做上记号,交给专门的工匠处理。 | 真实,不引入额外偏差。 | 不是所有机器学习模型都能处理带缺失值的数据。 | 缺失本身就是一种重要信息时。 |
填充策略详解:
- 统计值填充:用该列的均值(数值型)、中位数(数值型,抗异常值)、众数(分类型)来填充。
- 特定值填充:用一个特定值(如 0, -1, “未知”)填充,以表示“此处缺失”。
- 前向/后向填充:在时间序列数据中,用前一个或后一个有效值填充 (
ffill,bfill)。 - 插值法:基于现有数据点估算缺失点的值(线性插值等)。
- 模型预测填充:使用其他特征作为输入,通过机器学习模型(如KNN、回归)来预测缺失值(本课暂不涉及)。
代码示例
下面,我们创建一个模拟的学生成绩数据集,并演示完整的缺失值处理流程。
# 导入必要的库
import pandas as pd
import numpy as np
import warnings
warnings.filterwarnings(‘ignore’) # 忽略一些警告信息,让输出更简洁
# 1. 创建一个包含缺失值的数据集
data = {
'姓名': ['张三', '李四', '王五', '赵六', '钱七', '孙八', '周九', '吴十'],
'年龄': [20, 21, 19, 22, 20, 23, 21, 24],
'数学成绩': [85, 92, np.nan, 78, np.nan, 88, 95, 72], # np.nan 表示缺失值
'英语成绩': [90, 88, 82, 95, 89, np.nan, 78, 84],
'城市': ['北京', '上海', '广州', np.nan, '深圳', '北京', np.nan, '上海']
}
df = pd.DataFrame(data)
print("原始数据集:")
print(df)
print("-" * 50)
# 2. 检测缺失值
print("1. 各列缺失值计数:")
print(df.isnull().sum()) # isnull() 判断缺失,sum() 计数
print(f"\n总缺失值数量:{df.isnull().sum().sum()}")
print("\n2. 缺失值位置(布尔矩阵):")
print(df.isnull()) # 显示一个布尔矩阵,True代表缺失
print("\n3. 查看缺失值比例:")
print((df.isnull().sum() / len(df) * 100).round(2)) # 计算每列缺失比例
print("-" * 50)
# 3. 处理缺失值 - 策略一:删除
print("策略一:删除含缺失值的行")
df_drop_rows = df.dropna() # 删除任何包含缺失值的行
print("删除行后的数据:")
print(df_drop_rows)
print(f"删除后剩余行数:{len(df_drop_rows)}")
print("\n策略一(补充):删除含缺失值的列")
# 只有当该列缺失值非常多时才考虑删除列
# 例如,我们计算每列缺失率,假设阈值为50%
threshold = 0.5
cols_to_drop = [col for col in df.columns if df[col].isnull().mean() > threshold]
print(f"缺失率超过{threshold*100}%的列:{cols_to_drop}")
# 如果有的话,可以这样删除:df = df.drop(columns=cols_to_drop)
print("-" * 50)
# 4. 处理缺失值 - 策略二:填充
print("策略二:填充缺失值")
# 4.1 用统计值填充(适用于数值型)
df_filled = df.copy()
# 用数学成绩的均值填充数学成绩的缺失值
mean_math = df_filled['数学成绩'].mean()
df_filled['数学成绩'].fillna(mean_math, inplace=True) # inplace=True 直接修改原DataFrame
# 用英语成绩的中位数填充(中位数比均值更抗异常值)
median_english = df_filled['英语成绩'].median()
df_filled['英语成绩'].fillna(median_english, inplace=True)
print("用均值/中位数填充数值列后:")
print(df_filled[['姓名', '数学成绩', '英语成绩']])
print()
# 4.2 用众数或特定值填充(适用于分类型)
mode_city = df_filled['城市'].mode()[0] # 获取众数(出现次数最多的值)
print(f"城市的众数是:{mode_city}")
df_filled['城市'].fillna(mode_city, inplace=True)
# 或者,用特定标记如‘未知’填充
# df_filled['城市'].fillna(‘未知’, inplace=True)
print("用众数填充分类列后:")
print(df_filled)
print("-" * 50)
# 5. 处理缺失值 - 策略三:标记(以新增一个标记列为例)
print("策略三:标记缺失值(示例)")
df_marked = df.copy()
# 创建一个新的布尔列,标记数学成绩是否缺失
df_marked['数学成绩缺失'] = df_marked['数学成绩'].isnull().astype(int)
print("新增标记列后:")
print(df_marked[['姓名', '数学成绩', '数学成绩缺失']])
实践练习
练习1:基础检测 给定以下数据,请使用 Pandas 代码回答:
- 整个 DataFrame 中共有多少个缺失值?
- 哪些列存在缺失值?各缺失几个?
import pandas as pd
import numpy as np
data = {
‘A’: [1, 2, np.nan, 4, 5],
‘B’: [np.nan, 2, 3, 4, 5],
‘C’: [1, 2, 3, 4, 5],
‘D’: [‘x’, np.nan, ‘z’, ‘w’, np.nan]
}
ex_df = pd.DataFrame(data)
练习2:策略选择与填充
针对练习1中的 ex_df:
- 选择合适的方法填充列
A和B的缺失值(说明你选择的理由)。 - 将列
D的缺失值用字符串"缺失"填充。 - 展示填充后的 DataFrame。
练习3:综合处理
你正在处理一个包含 姓名、年龄、薪资、职位 的员工数据集。
姓名列有1个缺失值,你决定删除这一行。请写出代码。年龄列有5%的缺失值,薪资列有40%的缺失值。请分别提出一种合理的填充策略,并说明理由。- 如果
职位列有20%的缺失值,且你认为“缺失”这个状态本身(例如可能代表实习生或临时工)对分析有用,你会如何处理?
常见错误
-
不加思考地直接删除 (
dropna()):- 错误:只要看到缺失值就整行删除。
- 后果:当缺失比例较高时,会丢失大量宝贵数据,导致样本量不足或引入偏差。
- 正确做法:先分析缺失比例和模式,再决定是删除行、删除列还是填充。
-
用均值填充所有数值列:
- 错误:不分青红皂白,对所有数值型缺失值都用均值填充。
- 后果:如果数据中有异常值(如个别极高收入),均值会被拉高,填充结果就不具代表性。此外,均值会忽略数据分布(如偏态分布)。
- 正确做法:先观察数据分布,可考虑使用中位数(对异常值更稳健)或众数(如果数据是离散的)。
-
填充分类型变量时使用均值:
- 错误:试图计算“城市”或“性别”列的均值。
- 后果:代码报错,逻辑错误。
- 正确做法:对分类型变量,使用众数(最常见类别)或一个特定类别(如“未知”、“其他”)进行填充。
-
忽略缺失类型盲目处理:
- 错误:不分析缺失是随机的还是有规律的,直接使用最简单的方法处理。
- 后果:对于非随机缺失 (MNAR),直接填充或删除会引入严重偏差。例如,高收入者不填收入,你用低收入者的均值去填充,会严重低估整体收入水平。
- 正确做法:尝试理解缺失原因。如果怀疑是MNAR,可能需要将“是否缺失”作为一个特征,或在建模时采用专门处理MNAR的方法。
-
处理顺序错误:
- 错误:先做其他预处理(如标准化),再处理缺失值。
- 后果:缺失值会影响标准化等操作的结果。
- 正确做法:缺失值处理通常是数据预处理的早期步骤,在数据类型转换、标准化/归一化之前进行。
小结
在本课中,我们深入学习了数据处理中的关键环节——缺失值处理。回顾一下核心要点:
- 检测先行:使用
isnull().sum()、info()等方法全面了解缺失情况。 - 策略多样:
- 删除:简单粗暴,适用于缺失极少的情况。包括删除行 (
dropna) 和删除列。 - 填充:最常用的方法。数值型可用均值、中位数;分类型可用众数、特定值;时序数据可用前后值或插值。
- 标记:保留缺失状态,有时能揭示重要信息。
- 删除:简单粗暴,适用于缺失极少的情况。包括删除行 (
- 决策依据:选择策略需综合考虑缺失比例、数据类型、缺失机制(是否随机)以及业务含义。
- 实践第一:没有放之四海而皆准的最佳方法,必须结合具体数据和目标进行实验和选择。
处理好缺失值,是为后续的数据分析与机器学习建模打下坚实基础的至关重要的一步。在下一课中,我们将学习另一种常见的数据质量问题——异常值的检测与处理方法。
练习编辑器
rust
Loading...