Pandas缺失值处理全攻略:从诊断到修复的完整工作流

📅 2026/7/29 17:10:47
Pandas缺失值处理全攻略:从诊断到修复的完整工作流
1. 项目概述从数据“体检”到“治疗”的完整闭环做数据分析的朋友尤其是刚入门用Python和Pandas的估计都遇到过这种情况从数据库、Excel或者爬虫抓来的数据打开一看好家伙一堆NaN、None或者干脆就是空单元格。这些就是数据里的“缺失值”它们就像机器里的沙子不处理干净后续的分析、建模基本就是“垃圾进垃圾出”结果根本没法看。今天要聊的就是处理这些缺失值最核心、最常用的一套“组合拳”info()、isnull()、dropna()和fillna()。你别看就四个函数它们构成了一个从“诊断”到“治疗”的完整工作流。很多新手一上来就急着用fillna()填数或者用dropna()删行这其实很危险。就像医生看病不先做检查info()和isnull()就直接开刀很容易误诊。这套方法适用于任何需要处理表格数据的场景无论是金融风控、电商用户分析、科研实验数据清洗还是简单的个人记账整理。只要你用Pandas读入了DataFrame这套流程就是你的标准操作程序。接下来我会以一个模拟的电商用户数据集为例带你走一遍完整的流程把每个函数的“为什么用”、“怎么用”以及“用的时候要注意什么”都掰开揉碎了讲清楚。2. 核心思路诊断先行治疗在后处理缺失值最忌讳的就是拍脑袋决策。一个成熟的流程一定是先搞清楚“病情”再决定“治疗方案”。我们的核心思路可以概括为“四步走”全局扫描 (info()): 快速了解数据的“体检报告”看看有多少列每列有多少非空值对数据整体健康状况有个数。精准定位 (isnull()): 拿着“显微镜”和“CT机”精确找出每一个缺失值的位置量化缺失的严重程度。评估与决策: 这是最关键的一步基于前两步的诊断结果结合业务知识决定是“切除”删除还是“修补”填充。决策依据包括缺失比例、列的重要性、数据量大小等。执行治疗 (dropna()或fillna()): 根据决策执行删除或填充操作并验证效果。这个流程的核心思想是“数据驱动决策”。我们不是凭感觉去删或填而是基于数据本身呈现出的客观事实来做选择。比如一列数据缺失了90%那它很可能已经失去了分析价值直接删除这列比绞尽脑汁去填充要明智得多。反之如果一列至关重要比如用户ID且只缺失了0.1%那我们就需要非常谨慎地采用合适的策略进行填充。3. 数据准备与初步诊断info()函数详解在开始治疗前我们得先知道病人长什么样。假设我们手头有一个user_data.csv文件记录了电商平台的部分用户信息。import pandas as pd import numpy as np # 模拟创建一份有缺失值的数据 data { ‘user_id‘: [1001, 1002, 1003, 1004, 1005, 1006], ‘age‘: [25, np.nan, 30, 35, np.nan, 40], ‘gender‘: [‘M‘, ‘F‘, np.nan, ‘M‘, ‘F‘, ‘M‘], ‘city‘: [‘Beijing‘, ‘Shanghai‘, ‘Guangzhou‘, np.nan, ‘Shenzhen‘, ‘Beijing‘], ‘last_purchase_amount‘: [150.0, 89.5, np.nan, 200.0, 120.0, np.nan], ‘vip_level‘: [1, np.nan, 2, 1, 3, 2] } df pd.DataFrame(data) print(“原始数据“) print(df)运行后你会看到一个典型的包含缺失值显示为NaN的DataFrame。现在我们请出第一位“医生”——info()。df.info()这行简单的命令会输出一份“数据体检报告”class ‘pandas.core.frame.DataFrame‘ RangeIndex: 6 entries, 0 to 5 Data columns (total 6 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 user_id 6 non-null int64 1 age 4 non-null float64 2 gender 5 non-null object 3 city 5 non-null object 4 last_purchase_amount 4 non-null float64 5 vip_level 5 non-null float64 dtypes: float64(3), int64(1), object(2) memory usage: 420.0 bytes报告解读与实操要点数据概览首先告诉你这是一个DataFrame有6行索引0到56列。核心信息——Non-Null Count这是info()在缺失值诊断中最重要的输出。它直接显示了每一列非空值的数量。user_id: 6 non-null。完美没有缺失。age: 4 non-null。总共有6行说明有2个缺失值6-42。gender: 5 non-null。缺失1个。city: 5 non-null。缺失1个。last_purchase_amount: 4 non-null。缺失2个。vip_level: 5 non-null。缺失1个。数据类型Dtypeage、last_purchase_amount、vip_level显示为float64这是因为Pandas用np.nan一个浮点数来表示缺失导致整数列被向上转型为浮点型。这是一个非常重要的细节如果你期望vip_level是整数在填充后可能需要用astype(int)转换回来。内存占用约420字节数据量很小。注意info()默认只显示数据概览。如果你想看到更详细的信息比如每列的唯一值数量、最大值、最小值等应该使用df.describe(include‘all‘)。info()的核心优势在于快速定位缺失列而不是做全面的统计分析。为什么先看info()因为它快。一秒钟之内你就能对数据集的完整性和结构有一个宏观把握知道该把注意力重点放在哪几列上。在我们的例子里age和last_purchase_amount缺失最多2处是需要重点关注的“重症”列。4. 深度检测与量化分析isnull()与相关函数info()告诉了我们哪些列“病了”但没告诉我们具体是哪些“细胞”坏了。isnull()或它的别名isna()就是我们的“显微镜”。4.1 基本使用定位每一个缺失值df.isnull()会对整个DataFrame进行扫描返回一个形状相同的布尔型DataFrame缺失值位置为True非缺失为False。missing_mask df.isnull() print(“缺失值布尔掩码“) print(missing_mask)输出user_id age gender city last_purchase_amount vip_level 0 False False False False False False 1 False True False False False True 2 False False True False True False 3 False False False True False False 4 False True False False False False 5 False False False False True False现在一目了然了。例如第1行索引1的age和vip_level是缺失的第2行的gender和last_purchase_amount是缺失的。4.2 进阶应用统计与可视化缺失情况单纯看布尔表不够直观我们通常需要一些统计量。1. 按列统计缺失数量/比例这是最常用的操作它能量化问题的严重性。# 计算每列的缺失数量 missing_count df.isnull().sum() print(“每列缺失数量“) print(missing_count) # 计算每列的缺失比例更直观 missing_ratio df.isnull().sum() / len(df) print(“\n每列缺失比例“) print(missing_ratio)输出每列缺失数量 user_id 0 age 2 gender 1 city 1 last_purchase_amount 2 vip_level 1 dtype: int64 每列缺失比例 user_id 0.000000 age 0.333333 gender 0.166667 city 0.166667 last_purchase_amount 0.333333 vip_level 0.166667 dtype: float64决策参考通常我们会设定一个阈值。比如如果一列缺失比例超过30%0.3我们就倾向于直接删除该列因为填充的可靠性太低且可能引入巨大偏差。在我们的数据中age和last_purchase_amount缺失33%处于需要谨慎评估的临界点。2. 按行统计缺失数量有时我们需要知道哪些行的数据质量特别差。# 计算每行的缺失数量 missing_per_row df.isnull().sum(axis1) print(“每行缺失数量“) print(missing_per_row) # 找出缺失严重的行例如缺失2列及以上 severe_missing_rows df[df.isnull().sum(axis1) 2] print(“\n缺失超过2列的行“) print(severe_missing_rows)输出每行缺失数量 0 0 1 2 2 2 3 1 4 1 5 1 dtype: int64 缺失超过2列的行 user_id age gender city last_purchase_amount vip_level 1 1002.0 NaN F Shanghai 89.5 NaN 2 1003.0 30.0 NaN Guangzhou NaN 2.0我们发现第1行和第2行都缺失了2个字段。在后续处理时这些行可能需要特别关注。3. 可视化缺失矩阵高级技巧对于大型数据集用seaborn或missingno库进行可视化可以瞬间掌握全局缺失模式。# 需要安装 pip install matplotlib missingno import missingno as msno import matplotlib.pyplot as plt msno.matrix(df) plt.show()这张图能清晰显示数据缺失的分布情况是否存在某些区块整体缺失可能源于数据源问题还是随机缺失。实操心得df.isnull().sum()是我日常使用频率最高的缺失值分析命令没有之一。它给出的比例是决策的黄金依据。记住不要一上来就处理先量化。5. 治疗方案一直接删除 - dropna()函数详解诊断完毕如果决定“切除”那就轮到dropna()上场了。它的逻辑很简单丢掉含有缺失值的行或列。5.1 基本用法与参数解析# 默认参数删除所有包含任何缺失值的行 df_dropped_rows df.dropna() print(“删除任何缺失行后的数据默认“) print(df_dropped_rows) print(f“原始形状{df.shape} 删除后形状{df_dropped_rows.shape}“)输出删除任何缺失行后的数据默认 user_id age gender city last_purchase_amount vip_level 0 1001.0 25.0 M Beijing 150.0 1.0 原始形状(6, 6) 删除后形状(1, 6)结果触目惊心因为默认参数how‘any‘只要某行有一个NaN整行就被删除。6行数据只剩下了第0行这1行“完人”。在数据量本就不大的情况下这种操作是毁灭性的会损失大量信息。所以dropna()的关键在于参数调节how: 删除条件。‘any‘(默认): 行或列中有任何缺失值就删除。‘all‘: 行或列中所有值都缺失才删除。这个参数实用得多。axis: 删除方向。0或‘index‘(默认): 删除行。1或‘columns‘: 删除列。这是清理无效列的利器。thresh: 阈值。这是一个非常强大但常被忽略的参数。它要求行或列中至少有多少个非缺失值才得以保留。subset: 子集。指定只根据某些列来判断是否删除行其他列的缺失值忽略。这是最常用、最精细的控制方式。5.2 实战场景与参数选择场景1删除全为空的列有时候数据里会有一些完全没用的空列。# 假设我们数据里多了一列全为空 df[‘useless_col‘] np.nan print(“添加全空列后“) print(df.columns.tolist()) df_cleaned_cols df.dropna(axis1, how‘all‘) # 只删除整列为空的列 print(“\n删除全空列后“) print(df_cleaned_cols.columns.tolist())场景2基于关键列删除行最常用业务上有些列是分析的核心绝对不能缺失。比如在用户分析中user_id缺失的行毫无意义。又或者我们只关心age和last_purchase_amount都存在的记录。# 只删除在‘age‘和‘last_purchase_amount‘这两列上同时缺失的行 # 注意其他列如gender有缺失不影响 df_selective_drop df.dropna(subset[‘age‘, ‘last_purchase_amount‘]) print(“仅删除‘age‘和‘last_purchase_amount‘同时缺失的行“) print(df_selective_drop)输出显示第2行和第5行因为在这两列上都有缺失一个缺失last_purchase_amount一个缺失age所以被删除了。第1行虽然age缺失但last_purchase_amount存在所以被保留。这比一刀切合理得多。场景3使用thresh保留数据相对完整的行我们希望保留那些至少填充了N个字段的行。# 保留至少有4个非缺失值的行 (总共6列允许缺失2列) df_thresh df.dropna(thresh4) print(“保留至少4个非空值的行“) print(df_thresh)这样只缺失了1列的第3、4、5行都被保留了缺失2列的第1、2行被删除。避坑指南使用dropna()前务必先用info()和isnull().sum()评估删除会损失多少数据。如果删除行会导致样本量锐减比如超过20%就要慎重考虑填充方案。永远记住删除是最简单但也最粗暴的方案可能带来样本偏差。6. 治疗方案二智能填充 - fillna()函数详解当删除数据代价太大时“修补”就成了首选。fillna()是填充缺失值的瑞士军刀功能强大但用法不当也会“补”出问题。6.1 基本填充方法# 1. 用单一值填充所有缺失值慎用 df_filled_0 df.fillna(0) print(“用0填充所有缺失“) print(df_filled_0) # 问题gender列填了0 city列填了0这从业务上解释不通。 # 2. 前向填充 (ffill) / 后向填充 (bfill) # 假设数据是按时间或逻辑顺序排列的可以用前面或后面的值来填 df_filled_ffill df.fillna(method‘ffill‘) # 用上一个有效值填充 print(“\n前向填充“) print(df_filled_ffill) # 注意第1行的age用第0行的25填充了但第0行之前没有数据所以第0行如果是NaN则无法填充。6.2 按列差异化填充核心技巧这才是实战中最常用的方式。不同的列业务含义不同应该采用不同的填充策略。# 定义一个填充字典key是列名value是填充值或策略 fill_values { ‘age‘: df[‘age‘].mean(), # 数值型用均值填充假设年龄分布均匀 ‘gender‘: ‘Unknown‘, # 分类型用‘Unknown‘这个新类别填充 ‘city‘: df[‘city‘].mode()[0], # 分类型用众数填充出现最多的城市 ‘last_purchase_amount‘: df[‘last_purchase_amount‘].median(), # 数值型用中位数填充抗干扰 ‘vip_level‘: df[‘vip_level‘].median() # 有序分类用中位数填充 } # 注意我们没有填充‘user_id‘因为它没有缺失。 df_filled_smart df.fillna(fill_values) print(“按列智能填充后的数据“) print(df_filled_smart) print(“\n检查是否还有缺失“) print(df_filled_smart.isnull().sum())填充策略选择的“为什么”age(连续数值): 使用mean()均值。前提是年龄分布没有严重偏态且缺失是随机的。如果数据有异常值比如有个200岁的用户均值会被拉偏此时用median()中位数更稳健。gender(名义分类): 使用‘Unknown‘。绝对不要用均值或众数给性别填个“1.5”或强行指定为“男”都会扭曲分析。增加一个“未知”类别是更诚实的做法。city(名义分类): 使用mode()[0]众数。填充为最常见的城市这是一个合理的猜测。last_purchase_amount(连续数值可能有偏): 使用median()中位数。消费金额通常存在少数极高值长尾分布中位数比均值更能代表普通水平。vip_level(有序分类如1,2,3): 使用median()中位数。它保留了等级的序数意义。6.3 高级填充技巧插值法对于时间序列数据如股价、传感器读数简单的均值填充可能不合适因为数据点之间存在时间相关性。这时可以使用插值法。# 假设df按时间排序且‘price‘列有缺失 # df[‘price‘].interpolate(method‘time‘) # 按时间索引插值 # df[‘price‘].interpolate(method‘linear‘) # 线性插值默认 # df[‘price‘].interpolate(method‘polynomial‘, order2) # 多项式插值核心经验没有“最好”的填充方法只有“最合适”的。填充的本质是用估计值代替真实值必然会引入不确定性或偏差。你的填充策略必须建立在对业务的理解之上。在报告或模型中必须明确指出哪些数据是经过填充的填充的方法是什么这关系到结果的可靠性。7. 综合实战与决策流程现在我们把所有步骤串联起来形成一个处理一份新数据集的标准化决策流程。假设新数据集sales_data有1000行20列。第一步快速体检 (df.info())发现‘customer_income‘缺失40%‘last_login_days‘缺失5%‘product_category‘缺失1%。第二步深度诊断 (df.isnull().sum(),df.isnull().sum(axis1))确认缺失比例。发现‘customer_income‘缺失太高。检查是否有行大量缺失。发现10行数据缺失超过10个字段这些可能是无效记录。第三步制定决策对于列‘customer_income‘(缺失40%)比例过高填充不可靠。决策删除该列。df df.drop(columns[‘customer_income‘])‘last_login_days‘(缺失5%)重要指标需要填充。决策用中位数填充因为登录天数可能受极端值影响。df[‘last_login_days‘].fillna(df[‘last_login_days‘].median(), inplaceTrue)‘product_category‘(缺失1%)分类变量缺失很少。决策用**“Unknown”** 填充。df[‘product_category‘].fillna(‘Unknown‘, inplaceTrue)对于行缺失超过10个字段的10行数据质量极差保留价值低。决策删除这些行。df df[df.isnull().sum(axis1) 10]对于剩下的行可能还有零星缺失在其他非关键列可以用fillna(method‘ffill‘)或特定值填充。第四步执行与验证执行上述drop和fillna操作。最终验证再次运行df.info()和df.isnull().sum()确认所有缺失值已被妥善处理。这个流程的核心是权衡在数据量损失、引入的偏差和分析的便利性之间找到平衡点。对于关键特征的小比例缺失精心选择的填充优于简单删除。对于高比例缺失的非关键特征删除则是更干净利落的选择。8. 常见陷阱、疑难杂症与排查技巧在实际操作中你会遇到比教程更复杂的情况。下面是一些踩坑实录和解决方案。问题1为什么填充了缺失值但统计结果如mean()还是不对可能原因Pandas的统计函数默认skipnaTrue会自动跳过缺失值。但如果填充的值不合理比如用0填充了年龄计算出的均值自然会被拉低。排查填充后用df.describe()或df[‘col‘].hist()快速查看数据分布检查是否存在不合理的值域。问题2inplaceTrue参数用不用df.fillna(0, inplaceTrue)会直接修改原df。df_filled df.fillna(0)会返回一个新DataFrame原df不变。建议在调试和探索阶段不要用inplaceTrue保留原始数据。只有当清洗流程完全确定后才考虑使用inplaceTrue来节省内存。新手最容易犯的错就是一路inplace下去想回头看看原始数据都没了。问题3缺失值不止有NaN怎么办真实数据中缺失可能以各种形式存在空字符串““、“NULL“、“N/A“、-1、999等。处理在读取数据时就用pd.read_csv(…, na_values[““, “NULL“, “N/A“, -1])参数指定哪些值应被识别为缺失值。或者读取后用df.replace(‘NULL‘, np.nan)进行替换。问题4填充后数据类型变了怎么办如前所述用浮点数np.nan填充整数列该列会变成float64。解决填充完成后使用df[‘col‘] df[‘col‘].astype(‘int‘)转换回来。但要注意如果填充的是小数如均值转换会截断小数部分。问题5如何处理时间序列中的缺失简单的前后填充(ffill/bfill)或插值(interpolate)是常用方法但要根据序列特性选择。对于有周期性的数据如每日销售额可以考虑用“去年同期”或“上周同天”的数据来填充这需要更复杂的逻辑通常需要自己写函数。问题6多重填充与不确定性高级对于严谨的建模任务单一的确定性填充如均值会低估模型的不确定性。更先进的方法是多重插补即创建多个填充后的数据集分别建模后再合并结果。这可以通过scikit-learn的IterativeImputer或专门的statsmodels等库来实现但这已超出基础数据预处理的范围。记住数据清洗没有银弹。info()、isnull()、dropna()、fillna()这四个函数是你工具箱里的核心工具但如何运用它们取决于你对数据的理解和要解决的问题。每一次处理都是一次业务逻辑与数据事实之间的对话。