pd.read_excel读取合并单元格excel时,默认将合并单元格视为左上角存值、其余为空,导致nan、表头错位、列名变为unnamed;需用header指定多级表头、ffill填充纵向合并、openpyxl处理导出样式。

读取含合并单元格的Excel时,pd.read_excel 默认行为会出什么问题
直接用 pd.read_excel 读取带合并表头(比如前两行是层级标题)的Excel,结果里会出现大量 NaN,第一列变成索引、表头错位、列名变成 Unnamed: 0 这类占位符——这不是bug,是pandas把合并单元格“还原”成原始网格后的自然表现。
根本原因是:Excel里的合并单元格在底层只是「视觉对齐」,实际只在左上角存值,其余单元格为空。pandas按行列逐格读取,不主动推断合并逻辑。
- 若合并发生在首行(如列名跨2列),需用
header参数指定多层表头行号,例如header=[0, 1] - 若合并发生在数据区(如某几行的「部门」列纵向合并),pandas无法自动填充,必须手动用
ffill或bfill -
skiprows和usecols配合能快速跳过无用标题区,比靠header硬调更稳
用 header 和 index_col 处理多级表头
当表头本身是树状结构(如「销售部」下分「Q1」「Q2」,每季度又分「销售额」「退货率」),必须让pandas知道哪些行构成层级。
示例:Excel第0行是大类(销售部、技术部),第1行是子项(Q1销售额、Q1退货率……),数据从第2行开始:
pd.read_excel("report.xlsx", header=[0, 1], skiprows=2)
这样生成的DataFrame列名是 MultiIndex,支持用元组索引,比如 df[("销售部", "Q1销售额")]。
- 若合并表头中间夹杂空行,先用
skiprows跳过,再设header,别指望pandas自动识别“空行即分隔” -
index_col设为0或列表(如[0, 1])可把左侧合并的行标签转为索引,避免它被当普通数据列读入 - 读入后检查
df.columns类型,是MultiIndex才说明多级头生效;如果是普通Index,大概率header行号算错了
修复数据区纵向合并单元格(如「省份」列跨多行)
这是最常踩坑的地方:Excel里「江苏」合并了5行,但pandas只在第1行存“江苏”,后面4行是 NaN。直接分析会丢维度。
正确做法是定位该列,用 fillna(method="ffill") 向下填充:
df["省份"] = df["省份"].fillna(method="ffill")
但要注意边界——如果「省份」列开头就是空的,ffill 会把 NaN 一直传下去。稳妥方式是先用 df["省份"].first_valid_index() 找到首个非空位置,再切片处理。
- 不要对整行用
ffill(axis=1),容易污染横向本应为空的字段(如「备注」列) - 若合并逻辑复杂(如按「城市」列是否为空来决定是否继承上一行「省份」),得写显式循环或
apply+ 自定义函数 - 填充后立刻用
df.groupby("省份").size()快速验证各组行数是否合理,防止过度填充
导出时保留合并表头?别折腾,pandas不支持写合并单元格
df.to_excel 只能输出规整网格,无法还原原始Excel的合并样式。想让下游用户看到合并效果,有两个务实选择:
- 用
openpyxl加载已生成的Excel文件,在保存前手动合并单元格,例如:wb = openpyxl.load_workbook("output.xlsx")ws = wb.activews.merge_cells("A1:B1") - 彻底放弃样式,改用清晰的列命名替代合并,比如把「销售部/Q1/销售额」直接命名为
sales_dept_q1_revenue,更适合程序处理 - 若必须交付带样式的报表,建议用模板+
openpyxl填充,而不是靠pandas生成再修样式
真正难的从来不是读,而是确认「哪些合并是语义性的(必须保留)」和「哪些只是排版(可以扁平化)」——这个判断得人来定,pandas只负责执行。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











