尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

Python数据清洗必知:pandas与sklearn缺失值填充全攻略

发布时间:2026/9/29 15:35:27

资讯中心
01
ARTICLE

Python数据清洗必知:pandas与sklearn缺失值填充全攻略

Python数据清洗必知:pandas与sklearn缺失值填充全攻略
搞数据分析、写爬虫、做量化只要是跟表格数据打交道就没有不跟缺失值打照面的。python填充空缺值这事听起来简单做起来门道不少——直接dropna()删掉吧怕丢信息全填均值吧怕偏填0吧业务上又讲不通。这篇文章就是想把填充缺失值这件事从头到尾捋一遍从 pandas 的基础操作到 sklearn 的进阶玩法从手动填到自动补把每个方案的适用场景、实现代码、常见的坑都摊开讲。适合刚接触 pandas 的零基础新手也适合天天跟脏数据打交道的分析岗、算法岗朋友参考。数据里的空缺值就像房子里的裂缝不处理会越拖越大。但处理之前你得先搞清楚裂缝是承重墙上的还是隔断墙上的是天生就漏还是后天才漏的所以我习惯把填充缺失值分成三步摸底细、选策略、做验证。下面一个一个说。1. 先摸透缺失值的脾气再谈填充1.1 缺失值不是一种东西类型与成因很多人一上来就fillna(0)这是最容易翻车的操作。缺失值至少分三种完全随机缺失MCAR某个值是否缺失跟其他任何变量都无关纯粹是意外。比如系统录入时断电、问卷少勾了一题。随机缺失MAR缺失跟当前字段本身无关但跟其他字段有关。比如女性受访者更少填写收入——收入字段缺失但性别字段解释了为什么缺失。非随机缺失MNAR缺失的原因跟这个字段本身的值有关。比如收入极高的人故意不填收入那收入缺失本身就是一个信息。怎么判断没有100%的数学检验但可以从业务角度先琢磨这个数据是怎么来的人工录入、传感器采集、日志解析不同成因决定了你后面选策略的基调。传感器数据用插值很合理人工录入用均值补齐就要小心。另一个容易忽略的点NaN只是 pandas 里缺失值的一种表现形式。None、pd.NaT、甚至字符串NA、null、空字符串肉眼看着都像空但在 pandas 里性质完全不同。我见过太多次因为空字符串不是NaN导致fillna完全没生效的案例。1.2 动手填充前先回答三个问题我每次做数据清洗都会强迫自己过一遍以下三个问题这个字段的业务含义是什么销售额填0和库存填0完全是两码事。销售额缺失填0会拉低均值库存缺失填0可能意味着没货而非数据丢了。别用统一策略糊弄所有列。缺失比例有多大如果某列缺失90%任何填充策略都是在编数据。这时候不如直接当该列不适合做特征处理要么删列要么把是否缺失本身做成一个二值特征这招在机器学习特征工程里很好用。这份数据后续要干嘛如果只是做报表统计均值中位数够用如果要建预测模型填充方式会直接影响模型筛选特征的顺序如果做时间序列分析数据的时间顺序就是最高优先级只能用上下文插值。这三个问题想清楚了再决定用均值、中位数、众数、常数、前后向填充还是模型预测填充。方案没有绝对好坏只有适不适合当下的数据场景。1.3 填充策略怎么选一张对照表看清适用场景填充方式典型代码适用场景注意点常数填充fillna(0)/fillna(未知)业务上缺失无会拉低方差模型特征慎用均值填充fillna(df[col].mean())数值型数据近似正态分布对异常值敏感先看分布中位数填充fillna(df[col].median())数值型有离群点比均值稳健众数填充fillna(df[col].mode()[0])分类变量多众数时按业务挑一个前向/后向填充fillna(methodffill)时间序列、顺序数据需保证索引有序插值interpolate()时间序列、曲线型数据高阶插值易过冲分组统计填充groupby().transform()数据有天然分组结构分组键不能有缺失模型预测填充KNNImputer/IterativeImputer机器学习建模前计算量大注意泄漏这张表是我自己常用的速查清单。下面就把每个方法掰开揉碎用代码演示一遍。2. pandas填充核心操作从识别到落地的全套姿势2.1 识别与统计isnull、isna、notnull 怎么用isnull()和isna()功能完全一样isna是更现代的写法notnull()是取反。它们返回的都是布尔型 DataFrame用sum()可以统计每列缺失数量用mean()可以直接得到缺失比例import pandas as pd import numpy as np df pd.read_excel(orders.xlsx) # 每列缺失数量 print(df.isna().sum()) # 每列缺失比例 print(df.isna().mean()) # 等价写法 print(df.isnull().sum()) # 找出缺失比例超过30%的列方便决定是否删除 high_missing_cols df.columns[df.isna().mean() 0.3] print(f缺失超过30%的列: {list(high_missing_cols)})一个小技巧df.isna().any(axis1)可以找出一行内是否有任何缺失配合df[df.isna().any(axis1)]就能把有问题的行整体拎出来看。想定位到具体哪些行列位置缺失用np.argwhere(df.isna().to_numpy())。2.2 二话不说先删除dropna 的适用边界dropna()是最偷懒但经常最昂贵的做法。我见过有人把一份5万行的表直接删到2万行就是因为懒得想怎么填。删除的适用边界其实很窄# 删除任何含缺失值的行 df_clean df.dropna() # 只有当某行所有核心字段都完整才保留 df_clean df.dropna(subset[order_id, customer_id, amount]) # 删除全都为空的行 df_clean df.dropna(howall) # 删除缺失超过3个的行 df_clean df.dropna(threshlen(df.columns) - 3)用subset指定关键列才是正确姿势。真正需要dropna的场景通常是缺失行占比例极低比如1%、缺失列本身是一次性采集的辅助字段、或者后续算法不支持缺失必须清洗干净。如果缺失比例达到5%~10%以上删行前多想想。2.3 fillna 的几种填法常数、统计量、前后向填充fillna()是填充的主力函数。最简单的用法是常数填充# 数值列填0 df[sales] df[sales].fillna(0) # 字符串列填未知 df[category] df[category].fillna(未知) # 也可以对每列填不同的值 df df.fillna({ sales: 0, age: df[age].median(), category: 未知 })用均值/中位数/众数填充时我强烈建议先算好统计量再填避免代码里到处是df[age].median()这种重复计算# 更推荐的做法先存字典再一次性填充 fill_values { age: df[age].median(), income: df[income].median(), # 有离群点用中位数 gender: df[gender].mode()[0], # 分类列用众数 sales: 0 # 业务含义是当天没卖出 } df_filled df.fillna(fill_values)这里有个老生常谈但必须提醒的fillna默认返回新对象不修改原 DataFrame。很多人忘记赋值回df结果后面用df发现还全是NaN。要么显式赋值df df.fillna(...)要么加inplaceTrue但inplace在新版 pandas 里已经标记为不推荐我习惯显式赋值。前后向填充主要用在顺序数据上# 时间序列用上一个有效值填充 df[close] df[close].ffill() # 用下一个有效值填充 df[close] df[close].bfill() # 上下都取平均需要先ffill再bfill df[close] df[close].ffill().bfill()在老版本里很多教程教你写fillna(methodffill)在 pandas 2.0 里method参数已被移除直接用ffill()和bfill()方法更干净。2.4 插值进阶interpolate() 真的能猜出好值吗interpolate()是前向/后向填充的加强版它不搬邻居的值而是按趋势拟合出一个值。对于时间序列这种前后有连续关系的场景效果比ffill好得多# 数据可视化曲线比如每天的气温 df[temperature] df[temperature].interpolate() # 如果索引是日期最好先排序再插值 df_sorted df.sort_values(date) df_sorted[temperature] df_sorted[temperature].interpolate(methodlinear)method参数有几个值得记的选项选项行为适用场景linear两点连线默认大多数连续变量time按时间间隔加权日期索引且间隔不等polynomial多项式拟合曲线较平滑时注意阶数别太高spline样条插值平滑程度高但易振荡nearest最近邻等价于填最近值分类取值有限时插值不是万能的。我踩过的坑是用methodpolynomial时阶数设成5结果在边界处直接过冲插出一个物理上不可能的温度值。所以插值之后一定要用describe()或画图看分布范围别跟实际业务常识冲突。3. 一个完整案例走通填充缺失值的全流程3.1 造一份带缺失数据的业务表光讲理论没感觉我们造一份零售门店的销售数据来实操。这份表有日期、销售额、客流量、天气温度、门店类别5列其中有一部分故意被改成NaNimport pandas as pd import numpy as np np.random.seed(42) n 200 df pd.DataFrame({ date: pd.date_range(2024-01-01, periodsn, freqD), store_type: np.random.choice([商场店, 社区店, 街边店], sizen), sales: np.random.normal(3000, 800, sizen).round(0), traffic: np.random.poisson(lam200, sizen), temperature: np.random.normal(22, 8, sizen).round(1) }) # 人为制造缺失随机抽位置置为NaN np.random.seed(7) for col in [sales, traffic, temperature, store_type]: idx np.random.choice(df.index, sizeint(n * 0.08), replaceFalse) df.loc[idx, col] np.nan print(df.head(10))这个 DataFrame 里不同列缺失比例都在8%左右比较接近真实业务数据的脏度。3.2 缺失值体检比例、分布与可视化先用info()和isna()看整体print(df.info()) print(df.isna().mean().round(2))再看具体模式。我习惯画一个缺失热力图用seaborn.heatmap能直观看出缺失是不是集中在某几行或某段时间import matplotlib.pyplot as plt import seaborn as sns plt.figure(figsize(10, 4)) sns.heatmap(df.isna(), cbarFalse, cmapplasma, yticklabelsFalse) plt.title(缺失值位置分布图) plt.show()如果看到横向条带说明某些行集中缺失可能是采集设备某天坏了如果看到纵向条纹说明某列整段缺失可能是字段整体没采集到。不同模式决定策略整段缺失就别想用插值补直接考虑删列或构造新特征。3.3 按列类型分批填充数值列与分类列分开处理体检完就可以动手了。核心原则是逐列决策不是一份字典通吃# 日期列不可能插值如果缺失就删除这些极端情况 df df.dropna(subset[date]) # 数值列先看分布决定用中位数还是插值 # sales受门店类型影响先不急着整体填 print(df.groupby(store_type)[sales].describe()) # temperature是连续型且和日期趋势有关用插值 df[temperature] df[temperature].interpolate(methodtime, limit_directionboth) # traffic是计数型有离群点用中位数 df[traffic] df[traffic].fillna(df[traffic].median()) # 分类列store_type用众数 df[store_type] df[store_type].fillna(df[store_type].mode()[0])这里temperature我故意用interpolate(methodtime)因为它是带日期的连续变量线性插值比填均值更能保留趋势。sales留到最后处理因为它跟store_type明显相关全局填一个数不合理。3.4 分组填充与条件填充groupby transform 的精髓不同门店的销售水平差异很大。商场店月销可能是街边店的3倍用全局中位数填充会把商场店的缺失值拉低、把街边店的缺失值拉高。正确做法是分组填充# 按门店类型填各自的中位数 df[sales] df[sales].fillna( df.groupby(store_type)[sales].transform(median) )transform(median)会返回一个和原 DataFrame 等长、且按组填充了中位数的 Series直接作为fillna的填充值。这个写法比我早期用的groupby().apply(lambda x: x.fillna(x.median()))快得多而且语义清楚。还有一个冷门但实用的操作多列联合条件填充。比如规定周末的缺失客流用上周同日客流填充逻辑上也能写成向量化操作# 给 data 加一个星期几字段 df[weekday] df[date].dt.weekday # 用之前同星期的客流均值填充缺失值 df[traffic] df[traffic].fillna( df.groupby(weekday)[traffic].transform(median) )这两招用熟了你会发现自己写不出for循环暴力填的代码了——分组填充不仅更优雅而且性能高一个数量级。3.5 填充效果怎么验证多方案对比才算数填完不是结束必须验证。我至少会做三件事确认没有残留缺失df.isna().sum().sum()应为0。看描述性统计填充前后的mean、std、min、max不能变化太大。比如 temperature 用插值均值几乎不变但如果 fillna(0) 填销售额均值会被明显拉低这本身就是信号。可视化对比分别画填充前后的分布直方图或核密度图看是否有人为的尖峰。before_mean df[sales].mean() # 需要先用原始数据计算 # 填充后再算 after_mean df[sales].mean() print(f填充前均值: {before_mean:.2f}, 填充后均值: {after_mean:.2f})还可以做个占位测试把真实值藏一部分当缺失用各种策略填充再用 RMSE 比较谁最接近原值。这一步在建模前的数据清洗中尤其值得做。4. 进阶用 scikit-learn 把填充做到半自动化4.1 SimpleImputer一行代码换策略pandas 的fillna是手动挡scikit-learn里的SimpleImputer就是自动挡。它可以在一个fit_transform里处理所有需要填充的列并且在后续的机器学习流程中无缝嵌入Pipelinefrom sklearn.impute import SimpleImputer # 中位数填充数值列 imputer SimpleImputer(strategymedian) df[[sales, traffic]] imputer.fit_transform(df[[sales, traffic]])strategy支持的选项包括mean、median、most_frequent众数、constant常数配fill_value使用。用SimpleImputer还有一个好处它会自动保留列名在新版本里fit_transform返回 DataFrame避免重新拼列的麻烦。4.2 KNNImputer用邻居的力量填充KNN 填充的思路很直观找跟当前样本最相似的K个样本用它们该字段的平均值来填。它天然能利用多个维度的信息比单列填均值聪明不少from sklearn.impute import KNNImputer # 注意KNNImputer只能处理数值列 df_num df[[sales, traffic, temperature]] imputer KNNImputer(n_neighbors5, weightsdistance) df_filled imputer.fit_transform(df_num) df[[sales, traffic, temperature]] df_filled用 KNNImputer 要注意三件事所有参与计算的特征必须都是数值型。分类列要么转LabelEncoder要么单独处理。特征要标准化。否则距离会被量纲大的列主导比如销售额几千、客流量几百客流量指标基本不起作用。n_neighbors别设置太大否则会过度平滑相当于把局部信息稀释成全局均值。4.3 IterativeImputer多重插补思路的工程落地说到高级IterativeImputer是目前 sklearn 里最接近统计学多重插补逻辑的实现。它的思路是把每一列当作目标变量用其他列做模型预测缺失值然后反复迭代直到收敛。简单说就是用数据本身预测数据。from sklearn.experimental import enable_iterative_imputer from sklearn.impute import IterativeImputer imputer IterativeImputer( max_iter10, # 迭代轮数 random_state42, # 保证可复现 initial_strategymedian ) df_filled imputer.fit_transform(df[[sales, traffic, temperature]]) df[[sales, traffic, temperature]] df_filled它在数据量小、列之间有较强相关性的场景下效果最好。缺点也很明显迭代多次大数据集上慢得想骂人而且如果某列缺失比例过高模型学不到什么信息填出来的值跟猜差不多。所以它适合在建模前的特征工程阶段使用不适合报表统计场景。需要特别留意的是IterativeImputer目前还在 experimental 阶段使用前需要显式导入enable_iterative_imputer。这也是很多新手报ImportError的原因。4.4 missingno一眼看穿缺失模式处理缺失少不了一款可视化利器missingno。这个库专门用来分析缺失值分布几行代码就能出图import missingno as mso # 缺失矩阵横向是行纵向是列黑色块代表缺失 mso.matrix(df) # 缺失相关性热力图哪些列的缺失经常一起出现 mso.heatmap(df)matrix图能快速识别出某几行集中缺失的情况heatmap能看出不同列的缺失是否相关。比如sales和traffic的缺失总是同时出现那很可能这两个字段是从同一个源系统同步失败的修复数据源比填充更有意义。用pip install missingno就能装。5. 常见问题与排查技巧实录5.1 高频问题速查表症状可能原因解决方案fillna之后数据没变化原列没赋值回去 / 缺失值是空字符串而非NaNdf df.fillna(...)先用df.replace(, np.nan, inplaceTrue)groupby().transform()报错transform返回的元素个数跟组内不一致确认 lambda 里返回的是标量或 Series用x.fillna(x.median())而不是x.median()插值后出现超大值高阶插值过冲降低阶数改用linear或spline加order1填充后数值列变成float整数列里有NaNpandas 自动升级类型填充完用astype(int)显式转回SimpleImputer报错说遇到字符串混入了非数值列先只选数值列分类列单独处理df.interpolate()结果不对索引乱序先sort_values(date)再插值5.2 三个我踩过的坑inplace、索引、dtype第一个坑是inplaceTrue。我好几次写了df.fillna(0, inplaceTrue)后以为完事了结果后续操作用的还是旧df。因为某些 pandas 方法在链式调用时inplace不生效或者压根没落回原对象。现在的建议很明确统一用df df.fillna(...)这种显式赋值可读性更好也避免玄学问题。第二个坑是索引错位。fillna接收的 Series 或 dict如果索引跟原 DataFrame 对不上会填进去一堆NaN。典型场景是先把缺失的行筛出来算均值再拿这个均值去填原表稍不注意索引就对不上。更保险的做法是用df[col].isna()做布尔掩码配合df.loc[mask, col] value全程不依赖索引顺序。第三个坑是 dtype 被偷偷改成float。整数列里有NaNpandas 会把它自动转成float64因为NaN本身就是浮点类型。填充完以后如果想恢复整数要自己astype(int)。但注意如果缺失值不是全部填完astype(int)会直接报错所以要先确保该列没有任何NaN了。5.3 性能与工程化建议别让 fillna 拉垮你的流程最后聊聊性能。几万行的数据用什么方法都秒完但到了几百万行或者做实时特征计算时fillna的写法差距就出来了。第一用向量化操作代替循环。我见过有人这样写# 反例千万别这么写 for idx in df[df[sales].isna()].index: val df.loc[idx, store_type] df.loc[idx, sales] df[df[store_type] val][sales].median()这种写法逻辑没错但循环里做了全表条件筛选复杂度是 O(n²)500万行能跑半小时。换成groupby().transform()是 O(n)秒级完成。第二用 dict 或 Series 做批量填充。对每一列单独调用一次fillna会反复扫描 DataFrame合并成一次df.fillna(fill_values)只扫描一次性能更好代码也更紧凑。第三把填充写进 sklearn Pipeline。如果填充是建模流程的一环强烈建议放在Pipeline里from sklearn.pipeline import Pipeline from sklearn.impute import SimpleImputer from sklearn.ensemble import RandomForestRegressor pipe Pipeline([ (imputer, SimpleImputer(strategymedian)), (model, RandomForestRegressor(n_estimators100)) ]) pipe.fit(X_train, y_train)这样在交叉验证时每次训练都只会用训练集的统计量去填充验证集避免数据泄漏。这一步是很多人容易忽略的直接用全量数据的均值填缺失再切训练验证集会导致验证性能虚高模型上线后表现打折。填充缺失值这件事说到底是在信息不足时做合理决策的练习。填0、填均值、插值、模型预测没有银弹只有对业务的理解加上对数据的反复试探。我自己现在的习惯是先画缺失图再写策略备注最后留一个验证脚本随时复盘。这样一套流程走下来填充过的数据就敢放心交给下游了。最后再分享个小技巧在填充之前先把原数据存一份快照或者加一列is_missing标记把缺失这个事实保留下来。有些机器学习任务里这个值本来缺失本身就是重要信号保留它比抹掉它更有价值。这就是为什么我在做特征工程时经常把df[col].isna().astype(int)作为一个额外特征塞进模型——效果往往比费尽心思选填充策略还明显。
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

◈

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

◐

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

▲

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。