Python批量处理Excel科研数据:pandas与openpyxl实战避坑指南 简介本资源面向科研人员与数据分析初学者提供一套用Python处理Excel数据的完整实践素材覆盖从读取、清洗、分析到可视化的全流程。包内共159个文件以41个py脚本、38个csv与17个xlsx数据表为主另含10张png图表、8个cfg配置及若干模型与二进制文件压缩包约94.83MB目录结构便于按模块检索。内容涉及pandas的read_excel与to_excel读写、缺失值与重复项处理、描述性统计与分组透视以及matplotlib绘制柱状图、折线图、散点图和热力图等科研常用图表并包含自定义绘图与数据处理脚本可帮助读者快速复现数据清洗与可视化流程。目前已有1134人学习下载适合需要将Excel数据转化为可分析结果、提升科研数据处理效率的读者参考借鉴。1. 科研场景下用 Python 处理 Excel为什么手动点鼠标迟早会翻车如果你在实验室里处理过超过 50 个 Excel 文件大概率经历过这种场景导师临时要一份汇总数据你打开文件夹一看命名规则有实验组_20240301.xlsx、也有实验组-3.1.xlsx还有几个后缀带(1)的副本。手动复制粘贴到凌晨两点第二天发现某个 sheet 名写错了整列数据错位。这不是能力问题是工具选错了。Python 处理 Excel 在科研场景里的核心价值不是自动化三个字而是可复现。你写的脚本就是一份实验记录读了哪些文件、跳过了哪些行、怎么处理缺失值、输出成什么格式全部白纸黑字。下次审稿人问数据怎么清洗的你把脚本甩过去就行。适合谁适合手头有批量 Excel 数据、需要做重复性清洗和汇总、又不想每次重新点鼠标的研究生和工程师。不适合只处理一个文件、格式还天天变的情况——那种直接手动改更快。2. 选 pandas 还是 openpyxl先搞清楚你的 Excel 长什么样2.1 两个库的分工边界很多人上来就import pandas结果遇到合并单元格直接报错。问题出在没搞清楚两个库的定位。pandas是数据处理层它把 Excel 当成一个二维表来读适合规整的、有明确表头的、每列一个字段的数据。openpyxl是文件操作层它关心的是单元格坐标、样式、公式、合并区域这些Excel 本身的结构。科研数据里常见的坑是表头占了三行、中间有合并单元格、末尾有备注行——这种用 pandas 直接读会得到一堆Unnamed: 0和 NaN。我一般的判断逻辑是如果这个文件是给人看的报表用 openpyxl 先定位真实数据区域如果这个文件是给机器读的数据表直接用 pandas。混合情况就先用 openpyxl 做预处理把数据区域提取出来再交给 pandas。2.2 环境准备与版本确认# 建议用虚拟环境避免和系统 Python 冲突 python -m venv venv source venv/bin/activate # Windows 用 venv\Scripts\activate # 安装核心依赖 pip install pandas openpyxl xlrd # 确认版本不同版本 API 有差异 python -c import pandas; print(pandas.__version__) python -c import openpyxl; print(openpyxl.__version__)这里有个血泪经验xlrd从 2.0 版本开始不再支持.xlsx格式只能读.xls。如果你装的是最新版 xlrd 却要读 xlsx会直接报错。解决办法是 xlsx 一律用 openpyxl 引擎xls 才用 xlrd。pandas 的read_excel默认会根据扩展名自动选引擎但遇到文件扩展名和实际格式不一致的情况比如把 xlsx 改名成 xls就会翻车。所以显式指定engine参数是更稳的做法。2.3 读取时的关键参数import pandas as pd # 基础读取显式指定引擎和表头行 df pd.read_excel( 实验数据.xlsx, engineopenpyxl, # xlsx 用 openpyxlxls 用 xlrd sheet_nameSheet1, # 不指定则读第一个 sheet header2, # 表头在第 3 行0-indexed skiprowsNone, # 跳过开头行和 header 配合使用 skipfooter3, # 跳过末尾备注行需要 enginepython usecolsA:F, # 只读 A 到 F 列减少内存占用 dtype{样本编号: str} # 强制某列为字符串防止前导零丢失 ) print(df.shape) print(df.dtypes)header2表示第 3 行是表头前面两行被丢弃。skipfooter需要enginepython但 python 引擎不支持 xlsx所以这个参数在 xlsx 上其实用不了——这是个常见的矛盾点。替代方案是用 openpyxl 先打开文件手动定位数据范围或者读进来之后用df.iloc[:-3]切掉末尾。dtype参数特别重要。样本编号001、002这种pandas 默认会读成整数 1、2前导零直接消失。等你合并多个文件时发现编号对不上回头查半天才发现是这里出的问题。所以凡是编号、批次号、日期字符串这类字段一律强制str。3. 批量合并与清洗把 200 个 Excel 变成一个干净表3.1 批量读取的目录遍历策略科研数据通常按组别、时间、批次分散在多层文件夹里。我一般用pathlib而不是os.path因为路径拼接更直观跨平台也不容易出错。from pathlib import Path import pandas as pd data_dir Path(./实验数据) # rglob 递归匹配所有 xlsx排除临时文件 files sorted([ f for f in data_dir.rglob(*.xlsx) if not f.name.startswith(~$) # 排除 Excel 打开时的临时文件 ]) print(f共找到 {len(files)} 个文件) frames [] for f in files: try: df pd.read_excel(f, engineopenpyxl, dtype{样本编号: str}) # 从文件名提取组别信息写入新列 df[来源文件] f.name df[组别] f.parent.name # 用父文件夹名作为组别 frames.append(df) except Exception as e: print(f跳过 {f.name}: {e}) merged pd.concat(frames, ignore_indexTrue) print(merged.shape)rglob(*.xlsx)会递归所有子目录。~$开头的文件是 Excel 打开时生成的临时文件不排除的话会读到空数据或者报错。f.parent.name取的是文件所在文件夹的名字如果你的数据按组别分文件夹存放这一行就能自动打上组别标签省去手动维护映射表的麻烦。pd.concat的ignore_indexTrue会重新生成行索引。不加这个参数的话每个文件的索引都是 0 到 n合并后索引重复后续用loc取值会出问题。3.2 缺失值与异常值的处理# 查看缺失情况 print(merged.isnull().sum()) # 策略一删除关键字段缺失的行 merged merged.dropna(subset[样本编号, 测量值]) # 策略二对数值列用中位数填充 numeric_cols merged.select_dtypes(includenumber).columns for col in numeric_cols: median_val merged[col].median() merged[col] merged[col].fillna(median_val) # 策略三标记异常值而不是直接删 Q1 merged[测量值].quantile(0.25) Q3 merged[测量值].quantile(0.75) IQR Q3 - Q1 lower, upper Q1 - 1.5 * IQR, Q3 1.5 * IQR merged[是否异常] ~merged[测量值].between(lower, upper)缺失值处理没有万能方案。关键字段缺失直接删辅助字段缺失可以填。用中位数而不是均值是因为科研数据里异常值对均值的影响很大。IQR 方法标记异常值比直接删除更稳妥——你至少保留了原始记录后续可以人工复核。3.3 分组汇总与输出# 按组别和批次汇总 summary merged.groupby([组别, 批次]).agg( 样本数(样本编号, count), 均值(测量值, mean), 标准差(测量值, std), 异常数(是否异常, sum) ).reset_index() # 输出到 Excel多个 sheet 分开存 with pd.ExcelWriter(汇总结果.xlsx, engineopenpyxl) as writer: merged.to_excel(writer, sheet_name清洗后明细, indexFalse) summary.to_excel(writer, sheet_name分组汇总, indexFalse)groupby后的agg可以用字典指定每列不同的聚合函数。reset_index()把分组键从索引变回普通列否则输出到 Excel 时组别和批次会变成行索引格式很别扭。ExcelWriter配合with语句可以一次写入多个 sheet不用反复打开关闭文件。4. 避坑与排查那些让我重跑一整天的问题4.1 日期列变成数字 45000 怎么办现象读进来的日期列显示为 45000 这样的整数而不是 2024-03-01。原因Excel 内部用序列号存储日期1900 年 1 月 1 日是 1。pandas 读取时如果没识别出日期格式就会保留原始序列号。解决读取时加parse_dates参数或者读完后转换。# 方法一读取时指定 df pd.read_excel(data.xlsx, parse_dates[实验日期]) # 方法二读完后转换Excel 序列号起点是 1899-12-30 df[实验日期] pd.to_datetime(df[实验日期], unitD, origin1899-12-30)注意origin是1899-12-30而不是1900-01-01因为 Excel 有个历史遗留的闰年 bug。用错起点会差两天。4.2 合并单元格导致列错位现象读进来的 DataFrame 前面几列全是Unnamed: 0数据整体右移。原因Excel 里表头有合并单元格pandas 按行读取时无法正确解析多级表头。解决用 openpyxl 先取消合并并填充值或者手动指定列名。import openpyxl wb openpyxl.load_workbook(data.xlsx) ws wb.active # 取消所有合并单元格并填充左上角的值 for merge_range in list(ws.merged_cells.ranges): top_left ws.cell(merge_range.min_row, merge_range.min_col).value ws.unmerge_cells(str(merge_range)) for row in ws[merge_range.coord]: for cell in row: cell.value top_left wb.save(data_unmerged.xlsx)处理完再用 pandas 读表头就正常了。这个操作会修改文件建议先备份或者输出到新文件。4.3 大文件读取内存爆掉现象读一个 50MB 的 xlsx 文件内存直接飙到几个 G机器卡死。原因xlsx 本质是 zip 压缩的 XMLopenpyxl 默认会把整个文件解析成对象树内存占用是文件大小的几十倍。解决用read_only模式流式读取或者分批处理。# openpyxl 流式读取 from openpyxl import load_workbook wb load_workbook(big_file.xlsx, read_onlyTrue, data_onlyTrue) ws wb.active for row in ws.iter_rows(values_onlyTrue): # 逐行处理不占内存 pass wb.close()read_onlyTrue不会把整个文件加载到内存data_onlyTrue读取公式的计算结果而不是公式本身。缺点是只能顺序读取不能随机访问单元格。4.4 输出时覆盖了原始文件现象脚本跑完发现原始数据文件被覆盖了欲哭无泪。原因输出路径和输入路径写成同一个了或者循环里用了相同的输出文件名。解决输出目录和输入目录强制分开输出文件名加时间戳。from datetime import datetime timestamp datetime.now().strftime(%Y%m%d_%H%M%S) output_path Path(./输出结果) / f汇总_{timestamp}.xlsx output_path.parent.mkdir(parentsTrue, exist_okTrue)mkdir(parentsTrue, exist_okTrue)确保输出目录存在且不会因为目录已存在而报错。时间戳后缀让你可以追溯每次运行的结果出问题还能回滚。4.5 中文路径导致的编码错误现象在 Windows 上读取中文路径的文件时报UnicodeDecodeError或者文件找不到。原因Windows 默认编码是 GBKPython 在某些环境下处理中文路径会出问题。解决用pathlib统一处理路径避免手动拼接字符串。from pathlib import Path # 推荐pathlib 自动处理编码 data_dir Path(D:/实验数据/第一批) # 不推荐手动拼接容易出编码问题 # data_dir D:/实验数据/第一批/ # 在某些环境下会出问题如果还是报错检查 Python 的文件系统编码python -c import sys; print(sys.getfilesystemencoding())。正常应该是utf-8如果是mbcs说明环境有问题建议重装 Python 或者用 conda 管理环境。5. 进阶技巧用配置驱动让脚本适应不同实验批次写到第 5 章说一个让我少改很多次代码的习惯把变化的部分抽成配置。科研数据的特点是格式会变。这个月用测量值下个月改成测定值这批文件表头在第 2 行下批在第 4 行。如果每次都在代码里改迟早改漏。我一般会写一个 YAML 或 JSON 配置文件把列名映射、表头行号、需要跳过的 sheet 都放进去。import json from pathlib import Path import pandas as pd # config.json # { # header_row: 2, # column_mapping: {测定值: 测量值, 样本号: 样本编号}, # skip_sheets: [说明, 备注], # dtype: {样本编号: str} # } def load_config(config_path): with open(config_path, r, encodingutf-8) as f: return json.load(f) def read_with_config(file_path, config): df pd.read_excel( file_path, engineopenpyxl, headerconfig[header_row], dtypeconfig.get(dtype, {}) ) # 重命名列 df df.rename(columnsconfig.get(column_mapping, {})) return df config load_config(config.json) df read_with_config(实验数据.xlsx, config) print(df.columns.tolist())这样换一批数据时只需要改config.json脚本本身不动。配置文件还可以纳入版本管理每次实验的读取规则都有记录。另一个技巧是输出校验报告。脚本跑完后自动生成一个文本文件记录读了多少文件、跳过了哪些、每个文件多少行、缺失值比例是多少。下次跑之前先看报告心里有数。def generate_report(files, merged, outputreport.txt): with open(output, w, encodingutf-8) as f: f.write(f处理文件数: {len(files)}\n) f.write(f合并后行数: {len(merged)}\n) f.write(f缺失值统计:\n{merged.isnull().sum().to_string()}\n) f.write(f列名: {merged.columns.tolist()}\n)从那以后我每次跑批量处理都强制先跑一遍校验报告确认文件数和行数对得上再往下做分析。这个习惯帮我拦住了至少三次文件没读全的事故。希望帮到你。本文还有配套的精品资源点击获取