如何将Excel多个工作表按学号合并为宽表结构

1次阅读

如何将Excel多个工作表按学号合并为宽表结构

本文介绍使用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数据的标准实践。

text=ZqhQzanResources