15 写入Excel文件
15.1 引言数据导出的必要性与Excel在金融领域的地位
在金融数据分析和商业智能项目中,数据导出是工作流程的关键环节。分析人员经常需要将处理好的数据导出为Excel格式,原因包括:
- 报告生成: 向管理层或客户呈现分析结果
- 跨部门协作: 与非技术人员(如业务部门、财务部门)共享数据
- 审计合规: 保存分析过程的中间结果和最终输出
- 进一步分析: 利用Excel的透视表、图表等功能进行探索性分析
15.2 本章学习目标
通过本章学习,你将能够:
- 使用
df.to_excel的sheet_name、startrow/startcol、index、header参数控制导出位置与内容 - 使用
na_rep、inf_rep自定义缺失值与无穷大在 Excel 中的呈现 - 使用
pd.ExcelWriter上下文管理器向同一文件、同一工作表多次写入,并创建多工作表报表 - 根据数据量、受众与用途,在 CSV 与 Excel 之间选择导出格式
先修内容:第 章节 14 章的 Excel 读取方法。
补充说明:Excel文件格式的技术演进
Excel文件经历了多次格式演变,每种格式都有其特点:
| 格式 | 扩展名 | 特点 | 适用场景 |
|---|---|---|---|
| XLS | .xls | Excel 97-2003格式,专有二进制格式 | 兼容老版本Excel |
| XLSX | .xlsx | Excel 2007+格式,基于Office Open XML标准 | 现代Excel工作的标准格式 |
| XLSB | .xlsb | 二进制格式,文件更小,加载更快 | 大数据量场景 |
| CSV | .csv | 纯文本,逗号分隔值 | 跨平台数据交换 |
Pandas主要通过openpyxl引擎写入XLSX文件,通过xlsxwriter引擎实现高级格式化功能。
15.3 to_excel函数基础导出方法
数学背景:数据序列化与持久化
数据持久化(Data Persistence)是将程序中的数据保存到非易失性存储设备(如硬盘)的过程。在写入Excel时,需要处理以下技术挑战:
- 数据类型映射: Python类型 → Excel类型
int64→ Excel数值float64→ Excel数值datetime64→ Excel日期时间bool→ Excel逻辑值str→ Excel文本
- 特殊值处理:
NaN(Not a Number) → 缺失值标记inf(无穷大) → 默认写为字符串inf(inf_rep参数默认值),可用inf_rep='<INF>'自定义-inf(负无穷大) → 同上
任务要求:构造含日期时间、缺失值(NaN)、无穷大(inf)、整数与布尔列的 3 行数据框 df 并命名其索引;先用 to_excel 导出为 written_with_pandas1.xlsx(从第 2 行第 2 列写起,na_rep='<NA>'、inf_rep='<INF>');再用 ExcelWriter 上下文管理器创建 written_with_pandas2.xlsx,向 Sheet1 两处不同位置各写一次、再向 Sheet2 写一次;最后打印 df。请将代码原样输入教学平台(注释除外),判定以平台为准。
# ⚠️ 平台原始代码 - 请原样输入至教学平台(注释除外),平台才会判定答案正确
import numpy as np # 导入NumPy数值计算库
import pandas as pd # 导入Pandas数据分析库
import datetime as dt # 导入日期时间处理模块
data=[[dt.datetime(2020,1,1, 10, 13), 2.222, 1, True], # 定义列表data
[dt.datetime(2020,1,2), np.nan, 2, False], # 第二行数据(含缺失值NaN)
[dt.datetime(2020,1,2), np.inf, 3, True]] # 第三行数据(含无穷大inf)
df = pd.DataFrame(data=data,columns=["Dates", "Floats", "Integers", "Booleans"]) # 创建数据框df
df.index.name="index" # 设置数据框索引列的名称
# 将数据框导出至Excel文件,指定工作表名和写入参数
df.to_excel("written_with_pandas1.xlsx", sheet_name="Output",
startrow=1, startcol=1, index=True, header=True, # 设置写入Excel时的起始行列位置和索引/表头选项
na_rep="<NA>", inf_rep="<INF>") #
with pd.ExcelWriter("written_with_pandas2.xlsx") as writer: # 使用上下文管理器
# 将数据框写入Sheet1工作表的第2行第2列位置
df.to_excel(writer, sheet_name="Sheet1", startrow=1, startcol=1)
# 将数据框再次写入Sheet1工作表的第11行位置
df.to_excel(writer, sheet_name="Sheet1", startrow=10, startcol=1)
df.to_excel(writer, sheet_name="Sheet2") # 将数据框写入Excel文件
print(df) # 输出数据框数据预期输出(本机 Python 实际运行结果,具体以平台运行结果为准):
Dates Floats Integers Booleans
index
0 2020-01-01 10:13:00 2.222 1 True
1 2020-01-02 00:00:00 NaN 2 False
2 2020-01-02 00:00:00 inf 3 True
运行后工作目录会生成 written_with_pandas1.xlsx 与 written_with_pandas2.xlsx 两个文件(本机已验证可成功写出):第一个文件的数据从 B2 单元格开始,NaN 所在单元格写入字符串 <NA>、inf 所在单元格写入 <INF>;第二个文件的 Sheet1 中有两段相同数据(分别自第 2 行与第 11 行开始),Sheet2 中有一段。
代码深度解析:
15.3.1 第一部分to_excel函数参数详解
startrow和startcol参数的作用:startrow=1: 数据从Excel的第2行开始(第1行留给其他内容,如报告标题)startcol=1: 数据从Excel的第2列开始(第1列留给行号或其他标识)- 这种灵活性允许在一个Excel文件中创建复杂的布局,如多表格报告
特殊值处理参数:
na_rep: 控制缺失值的显示方式- 默认:空单元格
- 本例中:
"<NA>"明确标记缺失值 - 其他常用值:
"NA","-","NULL"
inf_rep: 控制无穷大的显示方式- 默认:写为字符串
inf(inf_rep参数默认值为'inf') - 本例中:
"<INF>"自定义表示 - 其他常用值:
"Infinity","∞"
- 默认:写为字符串
数据类型转换:
# Pandas内部执行的数据类型映射 - datetime64[ns] → Excel日期时间(序列号) - float64 (NaN) → Excel空单元格(或na_rep指定的值) - float64 (inf) → 字符串 `inf`(或inf_rep指定的值) - bool → Excel的TRUE/FALSE
15.3.2 第二部分ExcelWriter类的优势
理论背景:上下文管理器与资源管理
ExcelWriter使用了Python的上下文管理器(Context Manager)模式,通过with语句实现:
with pd.ExcelWriter("file.xlsx") as writer:
# 执行操作
# 自动关闭文件,释放资源这种设计的优势:
- 自动资源管理: 无论是否发生异常,文件都会正确关闭
- 异常安全: 即使写入过程中出错,也能保证文件完整性
- 代码简洁: 不需要显式调用
close()方法
同一工作表多次写入的原理:
ExcelWriter不会覆盖已有内容,而是从指定位置开始写入
这允许创建复杂的报表布局,如:
[标题区] [数据表1] [空行] [数据表2]
多工作表管理的优势:
- 数据分区: 将不同类型的数据放在不同工作表
- Sheet1: 原始数据
- Sheet2: 计算指标
- Sheet3: 图表数据
- 权限控制: 可以对不同工作表设置不同的访问权限
- 性能优化: 大数据集可以分成多个工作表,提高加载速度
to_excel vs ExcelWriter对比:
| 特性 | to_excel | ExcelWriter |
|---|---|---|
| 单工作表 | ✓ | ✓ |
| 多工作表 | ✗ | ✓ |
| 同表多次写入 | ✗ | ✓ |
| 代码复杂度 | 简单 | 稍复杂 |
| 资源管理 | 自动 | 需用with语句 |
15.4 实际应用场景
- 财务报表导出: 将计算好的财务指标导出为Excel,供审计使用
- 交易报告生成: 每日交易结束后,生成交易汇总报告
- 数据存档: 将处理后的历史数据保存为Excel,便于离线分析
补充说明:大数据量处理策略
当处理大规模金融数据时(如百万级交易记录),直接导出到Excel可能遇到性能瓶颈:
- Excel的行数限制:
- XLS格式(旧版): 65,536行
- XLSX格式(新版): 1,048,576行
- 性能优化策略:
- 分批写入:将大数据集分成多个小文件
- 数据抽样:导出代表性样本
- 聚合后导出:先汇总再导出
- 文件大小优化:
- 使用
XLSB二进制格式(文件更小) - 压缩数据(删除不必要的列、降低精度)
- 分割文件(按时间、类别等)
- 使用
易混淆概念辨析:to_csv vs to_excel
| 特性 | CSV | Excel |
|---|---|---|
| 文件大小 | 小(纯文本) | 大(包含格式) |
| 读取速度 | 快 | 慢 |
| 支持多工作表 | ✗ | ✓ |
| 支持格式化 | ✗ | ✓ |
| 跨平台兼容性 | 极好 | 需Excel |
| 适用场景 | 数据交换、大数据集 | 报告、可视化 |
选择建议:
- 数据备份/迁移 → CSV
- 向非技术人员展示 → Excel
- 大数据量(>100万行) → CSV或数据库
- 需要多表格报告 → Excel
15.5 本章小结
要点:
to_excel一次写一个工作表;ExcelWriter配合with支持同文件多工作表、同工作表多处写入startrow/startcol从 0 计数,startrow=1, startcol=1表示数据从 B2 单元格开始na_rep/inf_rep控制缺失值与无穷大落盘时的呈现,便于人工审阅- 大数据量导出优先 CSV,正式报告与人机交互优先 Excel
易错点:
- 不用
with而直接创建ExcelWriter时,文件要等close()或进程结束才完整落盘 - 同一工作表多次写入必须错开
startrow/startcol,写在同一位置会相互覆盖 - XLSX 单表上限约 1048576 行,超出会报错,此时应改用 CSV 或分批导出
15.6 动手与思考
以下练习每题附参考答案(默认折叠)。请先独立完成并写下你的判断,再点开对照,最后上机验证。
输出预测:不运行代码,先写出下面代码的输出结果,再上机检验你的判断。
import pandas as pd df = pd.DataFrame({'a': [1, 2]}) df.to_excel('t.xlsx', index=False) back = pd.read_excel('t.xlsx') print(back) print(back.columns.tolist())参考答案(先写下你的预测再点开)
解题思路:
to_excel('t.xlsx', index=False)把数据写入文件且不写行索引——A1 单元格是列名a,A2、A3 是 1 和 2,仅此三格。read_excel默认header=0(第 1 行作表头),于是 A1 的a成为列名,A2、A3 是两行数据;行索引 0、1 是读入时 pandas 重新生成的 RangeIndex,因为原索引根本没有落盘。back是 2 行 1 列的数据框;back.columns.tolist()把列名取成 Python 列表['a']。# 验证脚本:导出后读回,观察index=False对落盘内容的影响 import pandas as pd # 导入Pandas库 df = pd.DataFrame({'a': [1, 2]}) # 2行1列的小表,行索引为0、1 df.to_excel('t.xlsx', index=False) # 导出时不写行索引 back = pd.read_excel('t.xlsx') # 读回:首行作表头,行索引重新生成 print(back) # 输出读回的数据框 print(back.columns.tolist()) # 输出列名列表预期输出(本机 peter 环境实际运行结果,具体以平台运行结果为准):
a 0 1 1 2 ['a']回扣本章:对应本章小结“要点”第 1 条——
index参数控制是否把行索引写入文件;index=False写出的文件读回后行索引是重新生成的。概念辨析:
to_csv与to_excel在文件大小、读写速度、多工作表与格式化能力上如何取舍?to_excel与ExcelWriter的分工是什么?参考答案(点开前请先独立完成)
解题思路:逐点作答。第一,
to_csv与to_excel的取舍:CSV 是纯文本,文件小、读写快、跨平台兼容性极好,但只有一张“表”、没有任何格式信息;Excel 文件体积大、读写慢,但支持多工作表、单元格格式与人工审阅批注。选择口径:数据备份、迁移、程序间交换,或数据量超过 XLSX 单表约 1048576 行的上限时,选 CSV;面向管理层或业务部门的正式报告、需要多表格布局与人机交互的场合,选 Excel。第二,to_excel与ExcelWriter的分工:to_excel是“一次调用写一个工作表”的便捷方法,直接给文件名即可;ExcelWriter是上下文管理器,必须配合with使用,退出with时自动保存并关闭——它支持向同一文件的多个工作表写入,也支持向同一工作表的多个位置反复写入(用startrow/startcol错开),这是裸to_excel做不到的。回扣本章:对应本章小结“要点”第 1、4 条——大数据量导出优先 CSV,正式报告与人机交互优先 Excel;多工作表、多处写入必须用
ExcelWriter。变式任务(平台任务同型改造):把平台任务中的
df写入同一文件的 Sheet1 两处(错开位置)与 Sheet2 一处,再用read_excel配合skiprows/nrows把 Sheet1 的两段数据分别读回来。参考答案(点开前请先独立完成)
解题思路:
df已由 列表 15.1 定义(3 行数据、Dates/Floats/Integers/Booleans 四列、索引名 index)。写入端用ExcelWriter:Sheet1 第一段startrow=1, startcol=1(数据自 B2 起),第二段错开到startrow=6, startcol=1(自 B7 起),每段各占“1 行表头 + 3 行数据”;再向 Sheet2 默认位置写一段。读回端要算清行号:第 1 段表头在 Excel 第 2 行,skiprows=1跳过上方空行后表头即被正确识别,nrows=3取回 3 行数据,usecols='B:F'只取 5 列数据区;第 2 段表头在 Excel 第 7 行,故skiprows=6。两段读回后与原df数值一致。# 变式程序:先按 @lst-ch15-platform-task 重建同表 df,再同表两处写入并分别读回 import numpy as np # 导入NumPy库 import pandas as pd # 导入Pandas库 import datetime as dt # 导入日期时间模块 data = [[dt.datetime(2020, 1, 1, 10, 13), 2.222, 1, True], # 重建3行样例数据(含NaN与inf) [dt.datetime(2020, 1, 2), np.nan, 2, False], [dt.datetime(2020, 1, 2), np.inf, 3, True]] df = pd.DataFrame(data=data, columns=['Dates', 'Floats', 'Integers', 'Booleans']) # 创建数据框df df.index.name = 'index' # 设置索引列名称 with pd.ExcelWriter('variant_report.xlsx') as writer: # 打开上下文管理器,可向同一文件多处写入 df.to_excel(writer, sheet_name='Sheet1', startrow=1, startcol=1) # 第1段:数据自B2单元格起 df.to_excel(writer, sheet_name='Sheet1', startrow=6, startcol=1) # 第2段:错开位置,自B7单元格起 df.to_excel(writer, sheet_name='Sheet2') # Sheet2:默认位置写一段 segment_1 = pd.read_excel('variant_report.xlsx', sheet_name='Sheet1', skiprows=1, nrows=3, usecols='B:F') # 第1段表头在Excel第2行,跳过上方1行 segment_2 = pd.read_excel('variant_report.xlsx', sheet_name='Sheet1', skiprows=6, nrows=3, usecols='B:F') # 第2段表头在Excel第7行,故跳过6行 print(segment_1) # 读回第1段 print(segment_2) # 读回第2段预期输出(本机 peter 环境实际运行结果,具体以平台运行结果为准):
index Dates Floats Integers Booleans 0 0 2020-01-01 10:13:00 2.222 1 True 1 1 2020-01-02 00:00:00 NaN 2 False 2 2 2020-01-02 00:00:00 inf 3 True index Dates Floats Integers Booleans 0 0 2020-01-01 10:13:00 2.222 1 True 1 1 2020-01-02 00:00:00 NaN 2 False 2 2 2020-01-02 00:00:00 inf 3 True注意:以上为本题变式的独立代码;列表 15.1 对应的平台原始代码仍须原样输入教学平台,不要用本变式替换。
回扣本章:对应本章小结“要点”第 1 条与“易错点”第 2 条——
ExcelWriter支持同文件多工作表、同工作表多处写入;多处写入必须错开startrow/startcol,否则相互覆盖。动手验证:分别用
na_rep='<NA>'与默认方式导出含NaN的表,再用pd.read_excel(..., keep_default_na=False)读回,比较两种写法下单元格内容的差异。参考答案(点开前请先独立完成)
解题思路:两种写法的落盘内容不同:
na_rep='<NA>'把缺失值写成可见的字符串<NA>;默认写法把缺失值留成空单元格。keep_default_na=False关闭读回时“把空单元格与默认 NA 字符串自动识别为缺失”的规则,于是差异被原样暴露:前者读回的'<NA>'保持字符串形态;后者读回为空字符串(显示为空白),两者都不再是 NaN。这正说明na_rep的定位是“人工可读的缺失标记”——落盘可见,但是否被还原为缺失值取决于读回端的 NA 解析设置;若不加keep_default_na=False,空单元格与<NA>都会被自动还原成 NaN。# 验证脚本:对比na_rep与默认写法在读回端的差异 import numpy as np # 导入NumPy库,用于生成NaN import pandas as pd # 导入Pandas库 price_table = pd.DataFrame({'收盘价': [10.5, np.nan, 11.2]}) # 含一个NaN的3行小表 price_table.to_excel('na_tagged.xlsx', index=False, na_rep='<NA>') # 用'<NA>'显式标记缺失值 price_table.to_excel('na_default.xlsx', index=False) # 默认写法:缺失值写成空单元格 print(pd.read_excel('na_tagged.xlsx', keep_default_na=False)) # 关闭默认NA识别:'<NA>'按普通字符串读回 print(pd.read_excel('na_default.xlsx', keep_default_na=False)) # 空单元格读回为空字符串,而非NaN预期输出(本机 peter 环境实际运行结果,具体以平台运行结果为准):
收盘价 0 10.5 1 <NA> 2 11.2 收盘价 0 10.5 1 2 11.2回扣本章:对应本章小结“要点”第 3 条——
na_rep/inf_rep控制缺失值与无穷大落盘时的呈现,便于人工审阅。