ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Python+pandas按列拆分Excel:groupby实现数据自动分类导出

Python+pandas按列拆分Excel:groupby实现数据自动分类导出 处理Excel表格的朋友十有八九都碰到过这种需求一张总表里混着各个部门、月份或地区的数据领导一句“按某个列拆成几个文件”你就得手动复制粘贴折腾半天。其实这个操作用Python来做也就是十几行代码的事。这篇文章就围绕“按某一列值拆分Excel”这个场景把从环境准备、代码编写到踩坑排查的完整流程拆开讲清楚保证你看完就能照着用。这个需求之所以高频是因为它本质上是数据分组的问题——同一列里有多少个不同的值理论上就要拆出多少个文件。手动操作不仅低效还容易漏行、错行尤其是数据量一大眼睛根本盯不过来。用Python的pandas库处理核心就是“读取-分组-写出”三个动作全过程自动完成数据一条都不会漏。适合谁看日常跟Excel打交道、需要批量处理表格的职场人以及刚开始学Python想找个练手场景的新手都能从这篇文章里拿到可直接运行的方案。1. 按列拆分Excel的核心思路与方案选型1.1 为什么选Python而不是Excel自带功能Excel本身不是没有拆分能力。透视表、筛选后另存、VBA宏都能实现类似效果。但实际用起来各有各的别扭透视表做的是汇总不是拆表筛选后手动另存数据一多就繁琐到怀疑人生VBA当然能做写完还要处理宏安全设置换个电脑可能就没法运行而且Office不同版本兼容性也让人头大。Python方案最大的好处是一次编写永久复用。今天按“部门”拆明天按“月份”拆改一行列名字符串就行。更重要的是它跟办公软件本身无关了——哪怕这台电脑没装Office只要Python环境能跑用openpyxl引擎照样读写Excel文件。再加上Python在大数据处理上的天然优势几千行、几万行的表格拆分也是秒级完成这是手工操作完全比不了的。还有一个容易被忽略的点Python脚本可追溯、可复现。半年前拆的表和今天拆的表只要同一份代码跑出来的结果一定是可对齐的。这对于后面接数据核对、审计这类的流程来说价值远大于临时手动点出来的文件。1.2 技术路线拆解核心就是groupby从数据处理的逻辑来看“按某一列值拆分”其实就对应pandas里的groupby()操作。它的作用简单理解就是“分门别类”——把同一列里值相同的行归到一组然后我们对每一组分别处理比如单独存成一个文件。有人可能问为什么非要用groupby自己遍历行不行当然行但那样代码量上去了还要自己处理DataFrame切片和索引对齐的细节容易出bug。pandas的groupby是经过大量实战检验的底层实现分组效率高而且API设计得简洁一行for group_name, group_df in df.groupby(列名)就把逻辑表达清楚了。方案的执行流程大致是用pandas.read_excel()读取整个Excel文件为DataFrame。用.groupby()对目标列分组拿到每一组的名称和对应数据。循环每组数据用df.to_excel()写成新的Excel文件。文件名上用组合逻辑拼出来比如“原文件名_组名.xlsx”便于辨认。整个流程中没有多余的手工干预这也是这个方案最大的价值所在把重复劳动彻底自动化。2. 环境准备与依赖安装2.1 Python环境检查与安装开始动手之前得先确认电脑里有Python环境。在命令行Windows下是CMD或PowerShellmacOS/Linux下是终端输入下面这个命令看看python --version有版本输出说明环境已经就绪。如果提示“python不是内部或外部命令”那说明还没安装或者没有把Python加进环境变量。去Python官网下载对应系统的安装包安装的时候有一个很关键的勾选项——“Add Python to PATH”一定记得勾上。这一步不做后面命令都执行不了非常坑。macOS用户需要注意一点系统自带的Python版本可能比较老建议直接用官网安装包装新版命令也可能是python3区分一下。装好之后可以顺便检查一下pip是否可用python -m pip --version2.2 安装pandas和openpyxlpandas是数据处理的核心库openpyxl是用来读写Excel的引擎。安装命令很简单pip install pandas openpyxl国内用户如果下载速度慢可以用清华镜像源加速pip install pandas openpyxl -i https://pypi.tuna.tsinghua.edu.cn/simple这里解释一下为什么要装openpyxl——pandas读写Excel本身需要一个底层引擎openpyxl就是处理.xlsx格式文件的库。不装它执行read_excel()的时候会直接报错找不到引擎。pandas还会依赖numpy等底层库不过装pandas的时候会自动装好不用自己操心。安装完成后可以用一行代码验证import pandas as pd print(pd.__version__)能打印出版本号说明环境已经完全可用了。3. 核心代码实操按列拆分Excel全流程3.1 基础版代码十几行搞定拆分直接先看代码这一段就是拆分功能的核心实现import pandas as pd import os # 1. 读取原始Excel文件 source_file 销售数据总表.xlsx df pd.read_excel(source_file) # 2. 指定要拆分的列名 split_column 部门 # 3. 按列值分组并逐个写入新文件 output_dir 拆分结果 os.makedirs(output_dir, exist_okTrue) for group_name, group_df in df.groupby(split_column): output_file os.path.join(output_dir, f{group_name}.xlsx) group_df.to_excel(output_file, indexFalse) print(f已生成: {output_file})代码逐行拆开来看pd.read_excel(source_file)读取整个Excel为DataFrame。默认读取第一个sheet。df.groupby(split_column)按“部门”这一列分组。返回的group_name是这一列里的某个具体值比如“销售部”“市场部”group_df则是对应这个值的所有数据行。os.makedirs(output_dir, exist_okTrue)创建存放结果的文件夹。exist_okTrue的意思是如果文件夹已存在不报错继续执行。这个小参数挺关键的否则第二次运行时就会因为目录已存在而中断。group_df.to_excel(output_file, indexFalse)把分组数据写成新的Excel文件。indexFalse表示不写入DataFrame的索引列。这个一定要写不然生成的文件里会多出一列无意义的行号看起来非常业余。f{group_name}.xlsx直接拿列值作为文件名。实际使用中还可以拼上原文件名前缀避免不同表拆分出的文件重名比如改成f销售数据总表_{group_name}.xlsx。运行这个脚本控制台会依次打印生成的文件名整个过程一气呵成。3.2 进阶版代码处理文件路径与特殊字符基础版能跑通大部分场景但实际项目里总有各种幺蛾子。最常见的问题是组名里带有系统不允许的字符比如/、\、:、*、?、、、、|。Windows文件名里出现这些字符写文件直接报错。比如部门名称如果有“销售/市场”就会翻车。解决思路是在拼接文件名之前做一次替换清洗import re def safe_filename(name): # 将非法字符替换为下划线 return re.sub(r[\\/*?:|], _, str(name))然后把写文件那行改成output_file os.path.join(output_dir, f{safe_filename(group_name)}.xlsx)还有个细节是路径长度问题。Windows系统对单文件的完整路径长度有260字符的限制如果原目录层级深、文件名又长建议优先保持文件名简洁或者直接把输出目录放在磁盘根目录附近。3.3 传递参数化运行从写死到灵活第一次写脚本大家通常都是把文件名和列名写死在代码里这没什么不好意思的。但如果这个脚本需要频繁使用每次改代码就不太优雅了。用input()或者命令行参数把输入量抽出来体验会好很多。用input()的方式最简单import pandas as pd import os source_file input(请输入原始Excel文件路径: ) split_column input(请输入要拆分的列名: )或者用sys.argv实现命令行传参import sys source_file sys.argv[1] split_column sys.argv[2]这个方案适合已经有点Python基础的人。比如跑的时候直接执行python split_excel.py 销售数据总表.xlsx 部门好处是自动化流程里可以反复调用不用每次手动应答。这里我自己的习惯是用input()版本比较多毕竟不是所有人都在命令行工具里工作交互式输完点回车对新手更友好。4. 分组维度与命名规范的细节打磨4.1 多列组合拆分怎么办有时候需求更进一步要按“部门月份”两个维度组合拆分。这个时候groupby的参数传入一个列表就行for group_keys, group_df in df.groupby([部门, 月份]): # group_keys 是一个元组比如 (销售部, 2024-01) dept, month group_keys filename f{dept}_{month}.xlsx group_df.to_excel(os.path.join(output_dir, filename), indexFalse)这里有个细节值得说清楚groupby传单列的时候分组的键是一个标量值传入列表的时候分组的键会变成元组。用for a, b in ...的写法可以解包出来方便后续拼文件名。组合列拆分在实际工作里非常常见比如大型公司要按“大区-省份-城市”层级下发表格一条数据要能最终下发到各个城市就必须用多列组合。4.2 处理列名不固定或包含空格的问题Excel列名有时候并不那么规范可能带着空格、特殊符号或者大小写不统一。比如同事建的表格里写的是“部门 ”末尾带空格你没注意到df.groupby(部门)直接被KeyError打断。解决这类问题通常有两种思路思路一读进来之后先把列名清洗一遍把空格去掉、统一转成小写df.columns [str(col).strip().lower() for col in df.columns] split_column split_column.strip().lower()这里用str(col)是因为Excel里列名偶尔会出现非字符串类型比如数字列名不转换在下一步调用.strip()时会报AttributeError。思路二用正则匹配目标列名找到包含关键字的列再分组match_col [col for col in df.columns if 部门 in str(col)][0]实战中第一招更稳健但第二招在列名特别乱的时候能救急。两招结合使用基本可以应对99%的“脏列名”场景。4.3 分组键值有缺失值怎么办这是另一个高发问题。如果拆分列里有空行groupby会专门分出一个键名为nan的分组生成一个叫“nan.xlsx”的文件。这个文件一般没什么用处而且容易混淆视听。常规做法是拆分前把空值行过滤掉或者单独存放df df.dropna(subset[split_column])万一你想保留这些数据可以单独处理这一部分原因是空值本身没法作为有意义的文件标识。如果你需要“未分组数据”也输出一个文件那可以用fillna()方法把空值替换为指定文本df[split_column] df[split_column].fillna(未分配)这两种方式按实际业务需求选择。我之前遇到过一个场景有人填Excel的时候部门列有将近10%空值直接用dropna丢掉会导致后面汇总对不上数。后来改成两个文件输出一个按正常分组一个专门放未分配组核对起来就方便了。5. 完整流程实测从原始数据到验证结果5.1 构造测试数据并运行为了演示整个效果我构造了一个销售明细表包含编号、部门、销售员、销售额四列一共720行数据部门有“销售部”“市场部”“技术部”“行政部”四类每种接近180条。运行前面写的进阶版代码控制台输出类似这样已生成: 拆分结果/销售部.xlsx 已生成: 拆分结果/市场部.xlsx 已生成: 拆分结果/技术部.xlsx 已生成: 拆分结果/行政部.xlsx整个过程大概一两秒。这里特别说明一下pandas处理大量数据时内部是C级别的运算性能远高于Python层级的循环。实际跑几十万行按列拆分成百个文件瓶颈通常不在分组计算上而在频繁写小文件的IO开销上。遇到这种情况可以分批写入或者用ExcelWriter一次性打开多个sheet再把每个sheet另存为独立文件速度能提升不少。5.2 验证拆分结果是否完整代码跑完不等于万事大吉强烈建议做一步验证原始数据的行数总和应该等于所有拆分文件的行数总和。写一个简单脚本核对import pandas as pd import os source_df pd.read_excel(销售数据总表.xlsx) total_source len(source_df) total_split 0 for file in os.listdir(拆分结果): if file.endswith(.xlsx): total_split len(pd.read_excel(os.path.join(拆分结果, file))) print(f原始数据行数: {total_source}) print(f拆分文件总行数: {total_split}) print(校验通过 if total_source total_split else 校验失败数据有丢失)这种核对看起来简单但关键时刻能救命。尤其是数据量一多、分组值特别多的情况下文件名对应错乱、重复写入之类的问题是防不胜防的做一次行数对账能发现绝大部分问题。5.3 进阶按ExcelWriter批量生成多个Sheet还有一个使用频率很高的变体不拆分成多个文件而是拆分成同一个文件里的多个Sheet。这个需求通常出现在要发出去一个压缩包里面一个Excel文件带多个Sheet传给下游的情况。实现方式用pd.ExcelWriteroutput_file 销售数据分部门.xlsx with pd.ExcelWriter(output_file, engineopenpyxl) as writer: for group_name, group_df in df.groupby(split_column): sheet_name str(group_name)[:31] # Excel的sheet名称最大长度31个字符 group_df.to_excel(writer, sheet_namesheet_name, indexFalse) print(f已写入Sheet: {sheet_name})这里有个非常蹊跷的坑Excel的Sheet名称长度上限是31个字符且不能含有[]:*?/\等字符。组名一旦超长写入直接报错。手动在Excel里建Sheet的时候可能没机会触及这个限制但用代码生成时很容易踩中我见过不止一次因为公司全称太长生成失败的案例。解决方式就一行str(group_name)[:31]暴力截断。6. 常见报错与排查技巧实录6.1 常见报错对照表整理了这些年用过这个脚本的人最容易踩的坑列一张速查表备查报错信息原因分析解决方案ModuleNotFoundError: No module named pandas当前Python环境没装pandaspip install pandasImportError: Missing optional dependency openpyxl缺少Excel读写引擎pip install openpyxlKeyError: 部门列名不匹配可能含空格、大小写不同先打印df.columns核对或清洗列名PermissionError: [Errno 13]目标文件被占用比如开着Excel关掉Excel再运行ValueError: Excel does not supportSheet名称不合法或超长截断或替换非法字符EmptyDataErrorExcel文件内容是空的或格式不标准检查源文件另存为xlsx格式这张表里排第一的“环境没装库”问题最简单也最容易忽视。尤其是有时候电脑上装了多个Python版本pip安装到了一个环境运行时却用另一个环境执行就会踩到“明明装了却报ModuleNotFoundError”的魔幻问题。建议用python -m pip install pandas openpyxl代替pip install ...确保安装到当前执行环境的site-packages。6.2 排查思路从报错信息反推问题点遇到没见过的报错别慌着去搜先看报错信息的最后一行。Python的报错信息是栈式的最底层的那一行才是真正引发问题的源头。举个例子Traceback (most recent call last): File split_excel.py, line 12, in module group_df.to_excel(output_file, indexFalse) File .../pandas/core/generic.py, line 2345, in to_excel ... File .../openpyxl/workbook/workbook.py, line 312, in create_sheet raise ValueError(Excel does not support sheet names longer than 31 characters) ValueError: Excel does not support sheet names longer than 31 characters虽然报错中间看起来很长但最底部一行已经把原因说清楚了Sheet名称超长。所以排查时请重点看最后一行再往上翻两三层定位到具体是哪条调用触发的解决思路就清晰了。6.3 提高代码健壮性的三个习惯前面断断续续提到了不少坑这里梳理三个具体的编码习惯能让脚本的健壮性上一个档次。第一个习惯是打印关键信息来确认流程走到了哪一步。在读取原始文件后打印df.shape能看到表格的维度行数、列数在分组后打印df.groupby(split_column).size()能一眼看到每个分组的行数。这些信息不要嫌多分分钟帮你定位问题。第二个习惯是用try...except包裹主逻辑出错时把错误信息写入日志文件。这个在自动化任务里尤其重要——半夜定时任务跑挂了你得知道它为什么挂try: for group_name, group_df in df.groupby(split_column): output_file os.path.join(output_dir, f{safe_filename(group_name)}.xlsx) group_df.to_excel(output_file, indexFalse) except Exception as e: with open(error.log, a, encodingutf-8) as f: f.write(f{datetime.now()}: {e}\n) raise第三个习惯是输出文件时保持编码一致。写入的内容如果包含中文加上encodingutf-8参数更稳妥。虽然pandas的to_excel默认使用openpyxl引擎写入xlsx时编码问题不明显但写CSV或做日志时utf-8是有必要的。另外Windows下部分工具读取CSV默认用GBK这又是另一个话题了不过写日志文件时直接指定UTF-8在跨平台场景下更安全。7. 大数据量与性能优化思路7.1 数据量变大时的表现与瓶颈当源表数据量达到几十万行甚至百万行拆分列的分类又有成百上千个时你是否注意到脚本变慢了很多这个慢主要是慢在写文件IO上——每次to_excel()都要创建一个工作簿对象、写入行数据、关闭文件句柄循环几百上千次IO开销大是必然的。优化手段有几个方向按组分批处理避免一次性把所有分组都装进内存但pandas的groupby在for循环里本身是生成器式迭代不会一次性把所有分组都加载这个倒不用担心。用ExcelWriter复用连接写同一个目标文件的多组数据时用ExcelWriter可以避免重复创建文件句柄。拆分为多进程如果分组的文件彼此独立理论上可以用多进程并行写。7.2 多进程并行拆分的改进版Python的多线程由于GIL锁限制在CPU密集型任务上帮不上什么忙但写CSV/Excel这种涉及大量IO阻塞的操作用多进程会有实实在在的提升。思路是先把分组结果都准备好再用进程池并行写文件from multiprocessing import Pool import pandas as pd import os def write_group(args): group_name, group_df, output_dir args output_file os.path.join(output_dir, f{safe_filename(group_name)}.xlsx) group_df.to_excel(output_file, indexFalse) return output_file if __name__ __main__: df pd.read_excel(大表.xlsx) split_column 城市 groups [(name, group, 拆分结果) for name, group in df.groupby(split_column)] with Pool(processes4) as pool: results pool.map(write_group, groups) print(f完成共生成{len(results)}个文件)这里的核心是Pool.map()把“数据准备”和“文件写入”分离多个子进程各自独立写文件互不干扰。实测中在Windows下需要注意if __name__ __main__:这一行不能少因为多进程在Windows上是通过重新启动脚本方式创建的不写这行会无限递归创建进程。不过多进程的使用建议是“够用就好”。几千行几万行数据普通单进程脚本一两秒就完成了没必要上并行。真要碰到百万行级别拆几百个文件这个优化才值得做。8. 写在最后的实战体会做这行久了越来越觉得很多数据处理问题没有想象中那么玄乎。就“按某一列拆分Excel”这个需求来说核心知识就三个read_excel读进来、groupby分组、to_excel写出去。把这三板斧练熟工作中一多半的表格拆分场景都能应付。但也有一些小经验是代码本身给不了你的。比如拆分之前先看一眼源表有没有合并单元格、有没有隐藏行、有没有筛选状态——这些Excel层面的改动在读入时会影响结果。用pandas读数据本质上是读“值”任何格式层面的修饰都不会带过来所以你需要在源数据层面先做一次“清理”再交给脚本去处理。另外拆出来的文件最好养成压缩归档的习惯压缩包不仅体积小发出去也不容易乱。如果再往深走一步你可以把这个脚本封装成一个函数加个简单的图形界面或者做成定时任务每天自动跑。到那个阶段它就不只是一个脚本了算得上你自己搭建的“数据小工具”。从一行pandas.read_excel开始慢慢扩展出属于自己的工具链这大概就是编程带来的实打实的好处吧。
返回列表