查看: 166|回复: 3

Python批量统计Excel工作簿最大值和最小值(含行级定位与全局汇总)

[复制链接]
发表于 3 小时前 | 显示全部楼层 |阅读模式
在日常办公中,经常需要从包含多个工作表的Excel工作簿中提取某个数值列的最大值和最小值。如果手工操作,不仅效率低下,还容易漏表、选错区域,更关键的是难以追溯极值来源于哪张表哪条记录。本文提供一套基于Python的自动化方案,利用pandas和xlwings实现批量统计,并输出每张表的极值、行索引、关键字段以及全局极值。

## 目标效果:可追溯的极值统计
单纯的数字意义有限,更实用的输出应包含三层:
- 单表极值:每张工作表的最大值和最小值
- 行级定位:极值所在的行索引和对应的关键字段(如产品名称)
- 全局汇总:一个独立的“极值汇总”工作表,展示全局最大值/最小值及其来源工作表

这样打开结果文件即可直接看到结论,不需要再回头翻查原始数据。

## 实现思路
整个流程分为五步:
1. 打开Excel工作簿并遍历所有工作表
2. 跳过汇总表、空表及缺少目标列的工作表
3. 读取目标列并进行数据清洗(去除货币符号、逗号、空格等),转换为数值类型
4. 计算最大值、最小值及其行索引,同时提取关键字段
5. 将每张表的统计结果写入原表指定位置,并在工作簿最前面创建汇总表

## 完整代码
以下代码使用pandas进行数据处理,xlwings与Excel交互。运行前请确保已安装依赖:

pip install pandas xlwings

代码中的关键参数包括:
- input_xlsx:原始Excel文件路径
- value_col:需要统计极值的列名
- key_col:可选关键字段列名(如产品名称),用于追溯记录
- summary_sheet:汇总表名称
- write_cell:每张原始表中写入统计结果的起始单元格
- start_cell:数据区域起始单元格
- save_as:另存为路径,建议使用新文件避免覆盖原数据
  1. import pandas as pd
  2. import xlwings as xw
  3. def clean_to_number(s: pd.Series) -> pd.Series:
  4.     """
  5.     将Excel中可能带有逗号、货币符号、空格的文本数字转换为真正的数值。
  6.     例如:¥12,345.67 -> 12345.67,9,876.5 -> 9876.5,空值或异常字符 -> NaN
  7.     """
  8.     s = s.astype(str).str.strip()
  9.     s = s.str.replace(",", "", regex=False)
  10.     s = s.str.replace(r"[¥¥$ ]", "", regex=True)
  11.     s = s.str.replace(r"[^0-9\.\-]", "", regex=True)
  12.     return pd.to_numeric(s, errors="coerce")
  13. def batch_min_max(
  14.     input_xlsx: str,
  15.     value_col: str = "销售利润",
  16.     key_col: str | None = None,
  17.     summary_sheet: str = "极值汇总",
  18.     write_cell: str = "J1",
  19.     start_cell: str = "A1",
  20.     save_as: str | None = None,
  21. ) -> None:
  22.     """
  23.     批量统计一个工作簿中所有工作表指定列的最大值和最小值。
  24.     """
  25.     app = xw.App(visible=False, add_book=False)
  26.     app.display_alerts = False
  27.     app.screen_updating = False
  28.     try:
  29.         wb = app.books.open(input_xlsx)
  30.         rows = []
  31.         for sht in wb.sheets:
  32.             if sht.name == summary_sheet:
  33.                 continue
  34.             rng = sht.range(start_cell).expand("table")
  35.             if rng.value is None:
  36.                 print(f"[SKIP] {sht.name}: 空表")
  37.                 continue
  38.             df = rng.options(pd.DataFrame).value
  39.             if df is None or df.empty:
  40.                 print(f"[SKIP] {sht.name}: 无有效数据")
  41.                 continue
  42.             if value_col not in df.columns:
  43.                 print(f"[SKIP] {sht.name}: 缺少列:{value_col}")
  44.                 continue
  45.             num = clean_to_number(df[value_col])
  46.             if num.isna().all():
  47.                 print(f"[SKIP] {sht.name}: {value_col} 全部无法转换为数值")
  48.                 continue
  49.             max_val = float(num.max(skipna=True))
  50.             min_val = float(num.min(skipna=True))
  51.             max_idx = int(num.idxmax(skipna=True))
  52.             min_idx = int(num.idxmin(skipna=True))
  53.             max_key = df.loc[max_idx, key_col] if (key_col and key_col in df.columns) else ""
  54.             min_key = df.loc[min_idx, key_col] if (key_col and key_col in df.columns) else ""
  55.             stat_df = pd.DataFrame({
  56.                 "指标": [
  57.                     "最大值",
  58.                     "最小值",
  59.                     "最大值所在行索引",
  60.                     "最小值所在行索引",
  61.                     "最大值关键字段",
  62.                     "最小值关键字段"
  63.                 ],
  64.                 "数值": [
  65.                     max_val,
  66.                     min_val,
  67.                     max_idx,
  68.                     min_idx,
  69.                     max_key,
  70.                     min_key
  71.                 ],
  72.             })
  73.             sht.range(write_cell).options(index=False).value = stat_df
  74.             sht.autofit()
  75.             rows.append({
  76.                 "工作表": sht.name,
  77.                 f"{value_col}最大值": max_val,
  78.                 f"{value_col}最小值": min_val,
  79.                 "最大值行索引": max_idx,
  80.                 "最小值行索引": min_idx,
  81.                 "最大值关键字段": max_key,
  82.                 "最小值关键字段": min_key,
  83.             })
  84.             print(f"[OK] {sht.name}: max={max_val}, min={min_val}")
  85.         if not rows:
  86.             raise RuntimeError("未得到任何极值结果,请检查字段名或数据区域。")
  87.         summary_df = pd.DataFrame(rows)
  88.         global_max_row = summary_df.loc[summary_df[f"{value_col}最大值"].idxmax()]
  89.         global_min_row = summary_df.loc[summary_df[f"{value_col}最小值"].idxmin()]
  90.         try:
  91.             sum_sht = wb.sheets[summary_sheet]
  92.             sum_sht.clear()
  93.         except Exception:
  94.             sum_sht = wb.sheets.add(summary_sheet, before=wb.sheets[0])
  95.         sum_sht.range("A1").value = "全局最大值"
  96.         sum_sht.range("B1").value = float(global_max_row[f"{value_col}最大值"])
  97.         sum_sht.range("C1").value = "来自工作表"
  98.         sum_sht.range("D1").value = global_max_row["工作表"]
  99.         sum_sht.range("A2").value = "全局最小值"
  100.         sum_sht.range("B2").value = float(global_min_row[f"{value_col}最小值"])
  101.         sum_sht.range("C2").value = "来自工作表"
  102.         sum_sht.range("D2").value = global_min_row["工作表"]
  103.         summary_df_sorted = summary_df.sort_values(
  104.             by=f"{value_col}最大值",
  105.             ascending=False
  106.         )
  107.         sum_sht.range("A4").options(index=False).value = summary_df_sorted
  108.         try:
  109.             sum_sht.range("A1:D2").api.Font.Bold = True
  110.             sum_sht.autofit()
  111.         except Exception:
  112.             pass
  113.         if save_as:
  114.             wb.save(save_as)
  115.             print(f"[DONE] 已另存为:{save_as}")
  116.         else:
  117.             wb.save()
  118.             print(f"[DONE] 已覆盖保存:{input_xlsx}")
  119.         wb.close()
  120.     finally:
  121.         app.quit()
  122. if __name__ == "__main__":
  123.     batch_min_max(
  124.         input_xlsx="产品销售统计表.xlsx",
  125.         value_col="销售利润",
  126.         key_col="产品名称",
  127.         summary_sheet="极值汇总",
  128.         write_cell="J1",
  129.         start_cell="A1",
  130.         save_as="产品销售统计表_极值统计.xlsx"
  131.     )
复制代码

如果表中没有关键字段列,请将 key_col 设为 None,否则会取不到数据。字段名必须与Excel表头完全一致(包括空格和括号)。

## 关键点解析
代码中同时使用了 max()、min()、idxmax() 和 idxmin()。后两个函数返回的是DataFrame内部的索引,不是Excel行号。如果数据从第2行开始(表头在第1行),则Excel行号 = DataFrame索引 + 2。若存在合并单元格或复杂表头,需要自行调整映射关系。

建议始终输出统计结论和数据来源,即使是最简单的极值统计,也应附带上工作表名和行索引,方便后续快速定位。

## 效果验证
脚本运行结束后,打开输出的Excel文件,检查:
1. 是否存在新工作“极值汇总”
2. 汇总表上方显示全局最大值和最小值及其来源工作表
3. 下方列出每张工作表的明细,包含最大值、最小值、行索引和关键字段
4. 原始工作表中在指定位置(如J1)写入了该表的极值统计

如果汇总表为空,请优先检查:目标列名是否正确、数据区域是否从A1开始、目标列是否全为异常文本、工作表是否为空。

## 常见问题与避坑
1. 字段名不一致:打印每张表的列名确认,print(df.columns.tolist())
2. 数字是文本:clean_to_number函数会尝试清洗常见格式,但如果包含特殊字符或中文单位,仍可能被转为NaN,建议预处理数据
3. 行索引与行号混淆:注意DataFrame索引与Excel实际行号的差异
4. 覆盖原文件风险:建议固定使用 save_as 参数另存为新文件,保护原始数据
5. 空表和缺列:代码已做跳过处理,但控制台会输出跳过原因,请留意排查

## 总结
批量统计Excel极值的核心不是MAX/MIN函数本身,而是构建一套可重复、可追溯、可验证的自动化流程。建议将本脚本作为模板,扩展支持多列统计、Top N、条件筛选、多工作簿批处理等场景,形成自己的Excel自动化工具箱。
回复

使用道具 举报

发表于 2 小时前 | 显示全部楼层

Re: Python批量统计Excel工作簿最大值和最小值(含行级定位与全局汇总)

楼主这套方案非常实用,特别是在需要追溯极值来源的工作场景中,行级定位和关键字段提取正好解决了“光看数字不知道从哪来”的痛点。代码结构清晰,分步注释也很友好,对新手很友好。想请教一下:当工作表中数据量较大(比如几万行)时,xlwings的expand(“table”) + Pandas DataFrame 的转换速度表现如何?有没有考虑过直接用 openpyxl 读取以避开 Excel 进程启动的 overhead?另外,clean_to_number 里的正则替换会不会对科学计数法或负数格式产生误伤?整体思路很好,值得收藏。
回复 支持 反对

使用道具 举报

发表于 2 小时前 | 显示全部楼层

Re: Python批量统计Excel工作簿最大值和最小值(含行级定位与全局汇总)

感谢楼主分享,这个方案很实用,尤其是行级定位和全局汇总的设计,解决了“知道最大是100但不知道在哪张表哪个产品”的痛点。代码也写得清晰,clean_to_number 函数对货币符号和逗号的清理很周到,省去了很多手工预处理的工作。 想问一个细节:如果数据量比较大的工作簿(比如几十个sheet每个几万行),xlwings 这种实时交互方式会不会慢?有没有考虑过用 openpyxl 或者直接 pandas 的 ExcelWriter 来替代,还是说 xlwings 在写回原表时优势更明显?另外 key_col 为空时会不会报错,看代码里用了条件判断,应该没问题,但还是想确认一下。 再次感谢分享,收藏了~
回复 支持 反对

使用道具 举报

发表于 2 小时前 | 显示全部楼层

Re: Python批量统计Excel工作簿最大值和最小值(含行级定位与全局汇总)

感谢分享!这个方案很实用,特别是行级定位和全局汇总的设计,解决了手工翻表找极值的痛点。代码结构清晰,参数说明也很到位,直接拿来改一下列名和路径就能用。有个小建议:如果数据量特别大,xlwings 的 `expand('table')` 可能会读入整张空表,可以加个 `len(rng.rows)` 判断来提前检查,避免空表时 pandas 报错。另外货币符号清洗那里用了正则,对于欧元、英镑等符号可能需要扩充一下。整体非常棒,已在项目里试用了,赞一个!
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

指导单位

江苏省公安厅

江苏省通信管理局

浙江省台州刑侦支队

DEFCON GROUP 86025

Hacking Group 021A

旗下站点

态势感知中心

应急响应中心

红盟安全

联系我们

官方QQ群:112851260

官方邮箱:security#ihonker.org(#改成@)

官方核心成员

关注微信公众号

Archiver|手机版|小黑屋| ( 沪ICP备2021026908号 )

GMT+8, 2026-6-29 14:01 , Processed in 0.037810 second(s), 18 queries , Gzip On, Redis On.

Powered by ihonker.com

Copyright © 2015-现在.

  • 返回顶部