

大家好,我是正在实战各种AI项目的程序员晚枫。
今天学习数据清洗中最重要的话题——缺失值处理。
真实世界的数据往往不完整,如何处理缺失值直接影响分析结果的准确性。我将分享5种常用策略,帮你应对各种场景。
认识缺失值
创建示例数据
1 2 3 4 5 6 7 8 9 10 11 12 13
| import pandas as pd import numpy as np
df = pd.DataFrame({ '姓名': ['张三', '李四', '王五', '赵六', '钱七'], '年龄': [25, np.nan, 30, np.nan, 35], '薪资': [15000, 12000, np.nan, 10000, np.nan], '部门': ['技术', '销售', '技术', np.nan, '销售'], '入职日期': pd.to_datetime(['2020-03-15', '2019-07-20', '2018-11-08', '2021-01-10', np.nan]) })
print(df)
|
检测缺失值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| print(df.isnull())
print(df.isnull().sum())
print(df.isnull().sum(axis=1))
print(df.isnull().mean() * 100)
print(df[df.isnull().any(axis=1)])
print(df.dropna())
|
策略1:删除缺失值
适用场景:缺失值很少(<5%),且随机分布。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| df_clean = df.dropna()
df_clean = df.dropna(how='all')
df_clean = df.dropna(subset=['薪资'])
df_clean = df.dropna(thresh=3)
threshold = len(df) * 0.5 df_clean = df.dropna(axis=1, thresh=threshold)
|
注意:删除会丢失信息,谨慎使用!
策略2:填充固定值
适用场景:知道缺失值代表什么含义。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| df['薪资'].fillna(0, inplace=True)
df['部门'].fillna('未知', inplace=True)
df['年龄'].fillna(18, inplace=True)
values = {'年龄': df['年龄'].median(), '薪资': 0, '部门': '未知'} df.fillna(value=values, inplace=True)
|
策略3:统计值填充(最常用)
适用场景:数值型数据,缺失是随机的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| df['薪资'].fillna(df['薪资'].mean(), inplace=True)
df['年龄'].fillna(df['年龄'].median(), inplace=True)
mode_dept = df['部门'].mode()[0] df['部门'].fillna(mode_dept, inplace=True)
df['薪资'] = df.groupby('部门')['薪资'].transform( lambda x: x.fillna(x.mean()) )
|
策略4:前后值填充
适用场景:时间序列数据。
1 2 3 4 5 6 7 8 9 10 11 12
| df['销量'].fillna(method='ffill', inplace=True)
df['销量'].fillna(method='bfill', inplace=True)
df['销量'].fillna(method='ffill', limit=1, inplace=True)
df['销量'].fillna(method='ffill', inplace=True) df['销量'].fillna(method='bfill', inplace=True)
|
策略5:插值法
适用场景:数值有规律变化的数据。
1 2 3 4 5 6 7 8 9
| df['温度'].interpolate(method='linear', inplace=True)
df['温度'].interpolate(method='polynomial', order=2, inplace=True)
df.set_index('日期', inplace=True) df['销量'].interpolate(method='time', inplace=True)
|
实战:完整清洗流程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| import pandas as pd import numpy as np
df = pd.DataFrame({ '姓名': ['张三', '李四', '王五', '赵六', '钱七', '孙八'], '年龄': [25, np.nan, 30, 28, np.nan, 35], '薪资': [15000, 12000, np.nan, 10000, np.nan, 18000], '部门': ['技术', '销售', '技术', np.nan, '销售', '技术'], '工作年限': [2, np.nan, 5, 3, np.nan, 6] })
print("原始缺失情况:") print(df.isnull().sum())
df = df[df.isnull().sum(axis=1) <= 2]
df['年龄'].fillna(df['年龄'].median(), inplace=True)
df['薪资'] = df.groupby('部门')['薪资'].transform( lambda x: x.fillna(x.mean()) )
df['部门'].fillna(df['部门'].mode()[0], inplace=True)
df['工作年限'].fillna(df['年龄'] - 22, inplace=True)
print("\n清洗后缺失情况:") print(df.isnull().sum()) print("\n清洗后的数据:") print(df)
|
最佳实践建议
✅ 应该做的
- 先分析缺失模式:是随机的还是有规律的?
- 记录清洗过程:保留原始数据,新建清洗后的列
- 可视化检查:用图表看缺失值分布
- 对比验证:填充前后统计指标的变化
❌ 不应该做的
- 无脑删除:可能丢失重要信息
- 全部填0:会扭曲数据分布
- 不记录操作:无法复现和审计
- 忽视业务逻辑:要结合实际情况选择策略
性能对比:不同缺失值处理方式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| import pandas as pd import numpy as np
df = pd.DataFrame({ 'A': [1, np.nan, 3, np.nan, 5, 6, 7, 8, 9, 10] * 10000, 'B': [np.nan, 2, 3, 4, np.nan, 6, 7, 8, np.nan, 10] * 10000, 'C': range(100000) })
%timeit df.dropna()
%timeit df.fillna(0)
%timeit df.fillna(df.mean())
%timeit df.interpolate()
|
进阶用法
分组填充
1 2 3 4 5 6 7
| df['salary'] = df.groupby('dept')['salary'].transform( lambda x: x.fillna(x.mean()) )
df['price'] = df.groupby('stock_id')['price'].fillna(method='ffill')
|
插值法详解
1 2 3 4 5 6 7 8 9 10 11
| df['value'] = df['value'].interpolate(method='linear')
df['price'] = df['price'].interpolate(method='time')
df['value'] = df['value'].interpolate(method='polynomial', order=2)
df['value'] = df['value'].interpolate(limit=3)
|
避坑指南
❌ 坑1:fillna没有inplace
1 2 3 4 5 6 7 8 9
| df.fillna(0) print(df.isnull().sum())
df = df.fillna(0)
df.fillna(0, inplace=True)
|
❌ 坑2:非标准缺失值
1 2 3 4 5 6 7 8 9
| df = pd.DataFrame({'value': ['1', '-', 'N/A', 'null', '5']})
df = pd.read_csv('data.csv', na_values=['-', 'N/A', 'null', 'NA', ''])
df = df.replace(['-', 'N/A', 'null', 'NA'], np.nan)
|
实战案例:清洗房产交易数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| import pandas as pd import numpy as np
np.random.seed(42) n = 5000 df = pd.DataFrame({ 'house_id': range(10001, 10001 + n), 'area': np.where(np.random.rand(n) > 0.1, np.random.uniform(50, 200, n), np.nan), 'price': np.where(np.random.rand(n) > 0.15, np.random.uniform(50, 500, n) * 10000, np.nan), 'rooms': np.where(np.random.rand(n) > 0.05, np.random.choice([1,2,3,4,5], n), np.nan), 'floor': np.where(np.random.rand(n) > 0.08, np.random.randint(1, 35, n), np.nan), 'district': np.where(np.random.rand(n) > 0.03, np.random.choice(['浦东', '静安', '徐汇', '黄浦', '长宁'], n), np.nan), 'year': np.where(np.random.rand(n) > 0.12, np.random.randint(1990, 2025, n), np.nan) })
print("=== 缺失值报告 ===") print(df.isnull().sum()) print(f"\n缺失率:") print((df.isnull().mean() * 100).round(1))
df['area'] = df.groupby('district')['area'].transform(lambda x: x.fillna(x.mean()))
avg_unit_price = (df['price'] / df['area']).median() df['price'] = df['price'].fillna(df['area'] * avg_unit_price)
df['rooms'] = df['rooms'].fillna(df['rooms'].mode()[0])
df['floor'] = df['floor'].fillna(df['floor'].median())
df = df.dropna(subset=['district'])
df['year'] = df['year'].fillna(df['year'].median())
print(f"\n清洗后缺失值: {df.isnull().sum().sum()}") print(f"清洗后数据量: {len(df)} 行")
|
缺失值处理决策树
1 2 3 4 5 6 7 8 9 10 11 12
| 发现缺失值 │ ├── 缺失比例 < 5%? → 删除缺失行 │ ├── 缺失比例 5-30%? │ ├── 数值型 → 填充均值/中位数 │ ├── 分类型 → 填充众数/"未知" │ └── 时间序列 → 插值法 │ ├── 缺失比例 30-70%? → 谨慎处理,考虑是否值得保留 │ └── 缺失比例 > 70%? → 删除该列(信息量太少)
|
缺失值可视化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| import matplotlib.pyplot as plt import seaborn as sns
plt.figure(figsize=(12, 6)) sns.heatmap(df.isnull(), cbar=False, yticklabels=False, cmap='viridis') plt.title('缺失值分布') plt.show()
missing = df.isnull().sum() missing[missing > 0].plot(kind='barh', figsize=(10, 6)) plt.title('各列缺失值数量') plt.show()
import missingno as msno msno.matrix(df) msno.bar(df) msno.heatmap(df)
|
下节预告
下一课我们将学习处理重复值,继续数据清洗的旅程。
👉 继续阅读:Pandas数据清洗-处理重复值
💬 加入学习交流群
扫码加入Python学习交流群,和数千名同学一起进步:
👉 点击加入交流群
群里不定期分享:
- 数据分析实战案例
- Python学习资料
- 求职面试经验
- 行业最新动态
推荐:AI Python数据分析实战营
🎁 限时福利:送《利用Python进行数据分析》实体书
👉 点击了解详情
课程导航
上一篇: Pandas数据筛选与查询
下一篇: Pandas数据清洗-处理重复值
PS:数据清洗占数据分析工作的70%。耐心处理好缺失值,后续分析才能准确。
📚 推荐教材
主教材:《Excel+Python 飞速搞定数据分析与处理(图灵出品)》
💬 联系我
主营业务:AI 编程培训、企业内训、技术咨询
🎓 AI 编程实战课程
想系统学习 AI 编程?程序员晚枫的 AI 编程实战课 帮你从零上手!