Python数据分析实战:Pandas处理缺失值的5个高级技巧
真实业务数据从来不会干净。今天把我在项目中踩过的坑,一次性整理给你。
做数据分析的都知道,数据清洗占整个分析工作量的60-80%。而缺失值处理,又是数据清洗中最常见的问题。
很多人遇到缺失值第一反应就是df.dropna(),一行代码删完。然后呢?数据从10万行变成3万行,分析结论偏差巨大。
今天分享5个在真实项目中验证过的高级技巧,每个都有代码示例。
技巧1:按业务逻辑分组填充
场景:用户收入数据有缺失,但你不能简单地用全局均值填充。一线城市和三线城市的收入差距很大,全局均值会把三线城市的数据拉高。
方案:按城市分组,用组内中位数填充。
import pandas as pd import numpy as np # 模拟数据 df = pd.DataFrame({ 'city': ['北京', '上海', '北京', '成都', '上海', '成都', '北京', '成都'], 'income': [25000, 22000, None, 8000, None, 7500, 28000, None] }) # 按城市分组,用组内中位数填充 df['income'] = df.groupby('city')['income'].transform( lambda x: x.fillna(x.median()) ) print(df) # city income # 0 北京 25000.0 # 1 上海 22000.0 # 2 北京 26500.0 <- 用北京的中位数填充 # 3 成都 8000.0 # 4 上海 22000.0 <- 用上海的中位数填充 # 5 成都 7500.0 # 6 北京 28000.0 # 7 成都 7750.0 <- 用成都的中位数填充为什么用中位数不用均值?
收入数据通常右偏分布(少数高收入拉高均值),中位数更稳健。这是数据分析的基本功,但很多人会忽略。
技巧2:时间序列插值法
场景:每日活跃用户数据有几天缺失,不能直接删除(会影响趋势分析),也不能用均值填充(会抹平波动)。
方案:用时间插值法,基于前后数据点推算缺失值。
# 模拟日活数据(4月10日和12日缺失) dates = pd.date_range('2026-04-08', periods=7) dau = pd.Series([12000, 13500, None, 14200, None, 15100, 14800], index=dates) # 线性插值 dau_filled = dau.interpolate(method='time') print(dau_filled) # 2026-04-08 12000.0 # 2026-04-09 13500.0 # 2026-04-10 13850.0 <- 基于前后值线性推算 # 2026-04-11 14200.0 # 2026-04-12 14650.0 <- 基于前后值线性推算 # 2026-04-13 15100.0 # 2026-04-14 14800.0Pandas的interpolate()方法支持多种插值策略:linear(线性)、time(考虑时间间隔)、quadratic(二次曲线)等。
技巧3:多重插值法处理高缺失率
场景:某个字段的缺失率超过30%,简单填充会引入大量偏差。
方案:用其他特征做回归预测来填充缺失值。
from sklearn.linear_model import LinearRegression import numpy as np # 模拟数据:age和income,income有缺失 df = pd.DataFrame({ 'age': [25, 30, 35, 28, 40, 32, 45, 27, 38, 33], 'income': [8000, 12000, None, 9500, 22000, None, 25000, 8500, None, 15000] }) # 分离有值和缺失的数据 known = df[df['income'].notna()] unknown = df[df['income'].isna()] # 用已知数据训练回归模型 model = LinearRegression() model.fit(known[['age']], known['income']) # 预测缺失值 df.loc[df['income'].isna(), 'income'] = model.predict(unknown[['age']]) print(df.round(0)) # age income # 0 25 8000.0 # 1 30 12000.0 # 2 35 16500.0 <- 模型预测值 # 3 28 9500.0 # 4 40 22000.0 # 5 32 13200.0 <- 模型预测值 # 6 45 25000.0 # 7 27 8500.0 # 8 38 19500.0 <- 模型预测值 # 9 33 15000.0实际项目中建议用IterativeImputer(sklearn提供),它会对每个缺失特征迭代使用其他特征做预测,精度更高。
技巧4:标记缺失值本身也是一种信息
场景:用户注册时"年收入"字段为空,这个"空"本身可能意味着"不愿意填"或"收入较低"。
方案:新增一列标记是否缺失,再填充原始列。
# 新增缺失标记列 df['income_missing'] = df['income'].isna().astype(int) # 再用中位数填充原始列 df['income'] = df['income'].fillna(df['income'].median()) print(df) # age income income_missing # 0 25 8000.0 0 # 1 30 12000.0 0 # 2 35 15000.0 1 <- 标记为缺失 # 3 28 9500.0 0 # 4 40 22000.0 0 # 5 32 15000.0 1 <- 标记为缺失 # 后续建模时,income_missing可以作为特征使用 # 模型能学到"缺失"这个模式船长经验:在金融风控和用户分析项目中,缺失标记列往往比填充值本身更有预测力。一个不愿意填收入的用户,违约概率可能更高。
技巧5:缺失值可视化诊断
场景:数据集有50多个字段,你需要快速了解缺失值的分布情况。
方案:用缺失值热力图和统计表做快速诊断。
import pandas as pd # 模拟多字段数据集 np.random.seed(42) df = pd.DataFrame({ 'user_id': range(1000), 'age': np.random.choice([np.nan, *range(18, 65)], 1000), 'income': np.random.choice([np.nan, *range(3000, 50000, 1000)], 1000), 'city': np.random.choice([np.nan, '北京', '上海', '广州', '深圳'], 1000), 'login_days': np.random.choice([np.nan, *range(1, 365)], 1000), 'order_count': np.random.choice([np.nan, *range(0, 50)], 1000), }) # 1. 缺失值统计表 missing_stats = pd.DataFrame({ '缺失数量': df.isnull().sum(), '缺失比例': (df.isnull().sum() / len(df) * 100).round(1), '数据类型': df.dtypes }) missing_stats = missing_stats[missing_stats['缺失数量'] > 0].sort_values( '缺失比例', ascending=False ) print(missing_stats) # 缺失数量 缺失比例 数据类型 # income 520 52.0 float64 # city 498 49.8 object # age 503 50.3 float64 # login_days 495 49.5 float64 # order_count 502 50.2 float64 # 2. 快速判断:缺失是否随机 # 按某个字段分组看缺失率差异 print(df.groupby('city')['income'].apply(lambda x: x.isna().mean())) # city # 上海 0.50 # 北京 0.51 # 广州 0.50 # 深圳 0.49 # 如果某组缺失率显著偏高,说明缺失不是随机的总结:缺失值处理决策流程
第一步:先看缺失率——低于5%可以直接删或简单填充,高于30%需要特殊处理
第二步:判断缺失模式——是随机缺失还是系统性缺失(系统性缺失要标记)
第三步:选择填充策略——业务分组 > 时间插值 > 回归预测 > 全局统计量
第四步:始终保留缺失标记列——"缺失"本身就是信息
第五步:验证填充效果——对比填充前后的分布,确保没有引入偏差
船长的话:数据不说谎,但会误导人。缺失值处理不当,你的分析结论就是建立在沙子上的城堡。这5个技巧覆盖了90%的真实业务场景,建议收藏备用。别再用dropna一键删完了。
你平时怎么处理缺失值?有没有遇到过什么奇葩场景?评论区聊聊。