
本文介绍使用pandas高效合并excel中多个工作表的方法:以“roll number”为键,将各表的“brief”列分别作为独立列展开,避免传统循环merge导致的笛卡尔积重复问题。
在处理多班级、多学期或分批次采集的学生成绩/课程数据时,常遇到一个Excel文件包含多个工作表(如ClassA、ClassB),每张表结构不完全一致,但均包含关键字段Roll Number和Brief。目标是将所有工作表中的Brief信息,按Roll Number对齐、横向展开为宽表——即每个工作表对应一列,同一学号的多条记录按出现顺序逐行对齐(而非交叉匹配)。
你最初尝试的pd.merge循环方式失败的根本原因在于:merge默认基于键做全连接(outer join),当某学号在一张表中出现多次(如Roll Number=11在ClassA中有两条记录),而另一张表中仅出现一次时,merge会生成所有组合(即11×11 → 2行×1行 = 2行结果,但ClassB列被重复填充),造成逻辑错误与数据冗余。
✅ 正确解法的核心思路是:为每个学号在各表内的多条记录添加唯一序号(row number within group),再以(Roll Number, 序号)为复合索引进行拼接。这样可确保同组内第1条、第2条……严格对齐,彻底规避错位与爆炸式连接。
以下是完整、健壮的实现步骤:
✅ 步骤 1:一次性读取全部工作表为字典
import pandas as pd
# 用 sheet_name=None 直接读取所有sheet到字典 {sheet_name: DataFrame}
df_dict = pd.read_excel("input.xlsx", sheet_name=None)✅ 步骤 2:为每张表提取并标记行序号
对每张表:
- 仅保留 Roll Number 和 Brief 列;
- 使用 groupby('Roll Number').cumcount() 为每个学号内部的记录编号(从0开始);
- 将 Roll Number 和该序号设为双重索引;
- 将 Brief 列重命名为对应工作表名(如 'ClassA')。
dfs = []
for sheet_name, df in df_dict.items():
# 提取必要列 + 添加组内序号
temp = df[['Roll Number', 'Brief']].copy()
temp['seq'] = temp.groupby('Roll Number').cumcount()
# 设置复合索引并重命名列
temp = temp.set_index(['Roll Number', 'seq'])[['Brief']].rename(columns={'Brief': sheet_name})
dfs.append(temp)✅ 步骤 3:沿列方向拼接并整理索引
# 按列(axis=1)拼接所有带复合索引的DataFrame result = pd.concat(dfs, axis=1).reset_index(level='seq', drop=True).reset_index()
? 关键说明:pd.concat(..., axis=1) 在索引对齐时天然支持“相同 (Roll Number, seq) 对应同一行”,因此无需手动merge;.droplevel(1) 或 .reset_index(level='seq', drop=True) 用于丢弃无意义的seq索引层级,最终得到干净的三列结构:Roll Number, ClassA, ClassB……
✅ 完整可运行示例(含测试数据)
import pandas as pd
# 模拟原始Excel多Sheet数据
data_classa = {'Roll Number': [11, 11, 12], 'Brief': ['Maths 11', 'Science 12', 'History']}
data_classb = {'Roll Number': [11, 13, 12], 'Brief': ['Art 71', 'Science 12', 'Maths']}
df_dict = {'ClassA': pd.DataFrame(data_classa), 'ClassB': pd.DataFrame(data_classb)}
# 执行核心逻辑
dfs = []
for name, df in df_dict.items():
temp = df[['Roll Number', 'Brief']].copy()
temp['seq'] = temp.groupby('Roll Number').cumcount()
temp = temp.set_index(['Roll Number', 'seq'])[['Brief']].rename(columns={'Brief': name})
dfs.append(temp)
out = pd.concat(dfs, axis=1).reset_index(level='seq', drop=True).reset_index()
print(out)
# 输出:
# Roll Number ClassA ClassB
# 0 11 Maths 11 Art 71
# 1 11 Science 12 NaN
# 2 12 History Maths
# 3 13 NaN Science 12
# 保存结果
out.to_excel("combined_output.xlsx", index=False)⚠️ 注意事项
- 若某学号在某表中无记录,对应单元格自动为 NaN,符合预期;
- cumcount() 默认从 0 开始编号,确保各表第0条、第1条……严格对齐;
- 若原始数据含空值(如缺失Roll Number),建议提前清洗:df.dropna(subset=['Roll Number']);
- 工作表名若含空格或特殊字符,rename() 后列名会自动保留,导出Excel时无影响;
- 内存优化:对于超大文件,可改用 chunksize 分批处理,但本场景通常无需。
此方法简洁、高效、可扩展,轻松应对数十个工作表、百万级行数的合并需求,是处理教育类、问卷类多源Excel数据的标准实践。










