pandas 清洗 Excel 空值,判断顺序比代码本身更重要:先弄清空值在表里以什么形式存在(真空单元格、空字符串、纯空格、还是「无」「NA」「-」这类占位符),再决定哪些列必须删行、哪些列适合填补。如果一上来就 df.dropna(),很可能把含占位符的行全部留下,或者因为某列混入空字符串导致数值列变成 object,后面统计全部失效。
处理 Excel 空值的稳妥路径是:读入阶段用read_excel的na_values把占位符归一到 NaN,用isna().sum()按列统计缺口;关键列(主键、金额、日期)缺值优先删行,非关键列按业务语义填补;处理前复制一份 DataFrame,输出到新文件而不覆盖原表。验证看行数变化、各列空值计数和 dtype 是否符合预期。合并单元格、公式未重算、多 sheet 是常见盲区,需要单独确认。
先确认当前场景和目标
空值处理没有通用答案,先确认下面几件事,再动手写代码。
- 空值比例和分布:是零星缺失,还是整列大面积空白。整列缺失通常说明列本身不该保留,而不是逐行填补。
- 列的角色:主键、金额、日期这类列缺值会破坏后续聚合和关联,一般不做猜测性填补;备注、二级分类这类列可以用固定值或前值填充。
- 数据的最终形态:结果要给人在 Excel 里看,还是导入数据库或参与计算。给计算用就要保证 dtype 正确;给人看则要避免把空值填成 0 造成误读。
- 空值的真实形式:Excel 里的空白单元格和含空格的字符串在 pandas 眼里完全不同。可以先抽几行看
repr,确认是NaN、''还是' '。 - 是否存在合并单元格:pandas 读取合并区域时只有左上角保留值,其余读成 NaN,这类「空值」是表格结构造成的,不能靠填补解决。
如果这几个条件还没确认,建议先只做统计、不修改数据,把 df.isna().sum() 和各列 dtype 打印出来,再决定策略。
按这个顺序处理
推荐顺序是「读入归一 → 统计 → 按列决策 → 类型修正 → 写出」。每一步都可以单独跑、单独看结果。

import pandas as pd
# 1. 读入时把常见占位符统一识别为 NaN,按实际表里的写法增删
na_tokens = ['', ' ', 'NA', 'N/A', '无', '-', '--', 'null']
df = pd.read_excel('src.xlsx', sheet_name='明细', na_values=na_tokens, keep_default_na=True)
# 2. 按列统计缺口,先看再改
print(df.shape)
print(df.isna().sum())
print(df.dtypes)
统计结果出来后,按列分配策略。关键列缺值直接删行,并指定 subset,避免因无关列空值误删:
key_cols = ['订单号', '金额', '下单日期']
df_clean = df.dropna(subset=key_cols).copy()
非关键列按语义填补,不要统一填 0 或统一填「未知」。分类列填固定值,数值列如果要填,用中位数而不是 0,前提是这一列确实参与计算:
df_clean['备注'] = df_clean['备注'].fillna('未填写')
df_clean['城市'] = df_clean['城市'].fillna(df_clean['城市'].mode().iloc[0])
如果表格是时间序列或按顺序记录的状态列,可以谨慎使用 ffill(),但要加 limit 限制连续填补长度,避免把长时间空白一次性填满:

df_clean['状态'] = df_clean['状态'].ffill(limit=2)
最后处理类型问题。数值列里混入过空字符串会导致整列是 object,先用 to_numeric 强制转换,errors='coerce' 会把无法解析的值变成 NaN,转换完要再统计一次空值:
df_clean['金额'] = pd.to_numeric(df_clean['金额'], errors='coerce')
print(df_clean['金额'].isna().sum())
写出时另存新文件,不要覆盖原表,同时关闭索引列:

df_clean.to_excel('src_clean.xlsx', index=False)
改完后怎么验证
验证不需要复杂工具,对比几个可观察的信号就够。
- 行数对比:记录处理前后的
df.shape,删除行数与dropna前后的差值应能对上;如果删掉的行数明显超出关键列空值数量,说明subset写错或读入时把正常值误判成了 NaN。 - 空值计数:处理后再跑一次
isna().sum(),目标列应为 0,被保留的列允许有非零值,但要在预期范围内。 - dtype 检查:金额、日期、ID 列处理完后应为数值或日期类型;如果仍是 object,多半还有残留的空白字符串或占位符。
- 抽样对照:用
df_clean.sample(5)或按主键定位几行,回到原表核对,确认填补值没有覆盖掉原本有效的数据。 - 打开输出文件确认:
to_excel写出的 NaN 在 Excel 里显示为空单元格,填成''也同样是空白,肉眼很难区分。需要区分时,可以在输出前把空值临时替换成明确标记再导出。
容易忽略的边界
以下几点在真实表格里经常导致结果和预期不一致,建议先在小样本上确认。
- 多 sheet 和隐藏行:默认只读一个 sheet,其他 sheet 的空值不会被处理;隐藏行会被正常读入,判断数据量时不要按可见行估算。
- 公式单元格:pandas 通过 openpyxl 读取时拿到的是缓存值,如果文件保存前未重算,可能读到空值或旧值。建议在 Excel 里另存为只含数值的副本再处理。
- 合并单元格:合并区域只保留左上角的值,其余为 NaN。这类结构性问题应在原表拆分填充,而不是在 pandas 里
ffill猜测。 - 填补的副作用:用 0 填补金额会把缺失计入合计,用均值填补会改变分布。涉及统计口径的列,建议保留一列原始数据或记录填补标记,方便回退。
- 大面积整列缺失:如果一列空值比例很高,直接填补等于编造数据,更稳妥的做法是整列删除或单独输出缺失清单,人工确认。
- 回退准备:处理前保留原始 Excel 文件,代码里对
df使用.copy()后再修改,输出写新文件名。这样一旦发现填补策略不合适,重跑即可,不必回溯。
如果表格规模较大或需要反复清洗,建议把读入参数、关键列名、填补规则抽成配置,而不是散落在脚本各处;这样换一张表时只需要改配置,判断逻辑本身不变。