1. 课程导入
金融数据清洗是量化研究、风险建模与交易系统开发中最耗时、最容易出错的环节之一。行情快照、逐笔成交、财务报表、宏观指标往往来自不同供应商,字段命名混乱、时间戳精度不一、缺失值与异常值交织,单日数据量动辄数千万行。传统方案中,Pandas 凭借丰富 API 成为默认选择,但当数据规模超过内存、需要多表关联与滚动窗口计算时,其单线程执行模型和即时求值(Eager)方式会迅速成为瓶颈。Polars 正是为这类场景设计的现代列式 DataFrame 引擎:它用 Rust 实现、基于 Apache Arrow 内存格式、支持多核并行与查询优化,在金融数据清洗任务中通常能带来数倍到数十倍的性能提升。
核心原理:为什么 Polars 适合金融数据清洗
理解 Polars 的价值,需要抓住三个关键机制。第一是列式存储与向量化执行:金融清洗中大量操作是“按列筛选、按列变换”,列式布局让 CPU 缓存命中率更高,配合 SIMD 指令可批量处理数值。第二是多线程并行:分组聚合、连接、排序等操作会自动拆分到多个核心,无需用户手动编写并行代码。第三是懒执行与查询优化:这是本课程的主线。懒执行(Lazy)不会立即计算结果,而是先构建逻辑计划,再由优化器进行谓词下推、投影下推、公共子表达式消除、连接重排序等改写,最后才生成物理计划执行。
一句话概括:Eager 模式是“写一步算一步”,Lazy 模式是“先描述全部意图,再让优化器决定怎么算最省”。在金融清洗这种多步骤、多表关联的场景中,优化器往往能消除大量中间数据。
下面这张图展示了 Polars 懒执行的核心数据流:从数据源到最终结果,中间经过逻辑计划、优化器与物理计划三个层次,优化器是性能收益的关键闸门。
方法流程:从 Eager 到 Lazy 的清洗路径
在金融数据清洗中,推荐的工作流是“先 Lazy 描述,后 collect 执行”。典型步骤包括:
- 扫描数据源:使用
pl.scan_csv、pl.scan_parquet或pl.scan_ipc创建 LazyFrame,此时不读取数据。 - 声明清洗逻辑:链式调用
filter、with_columns、join、group_by、sort等,构建逻辑计划。 - 触发优化:调用
explain()查看优化后的计划,确认谓词下推与投影下推是否生效。 - 执行并物化:调用
collect()或sink_parquet()得到结果,后者可直接流式写出,避免全量驻留内存。
import polars as pl
lf = (
pl.scan_parquet("trades_2024.parquet")
.filter(pl.col("symbol").is_in(["AAPL", "MSFT", "NVDA"]))
.filter(pl.col("price") > 0)
.with_columns(
pl.col("ts").str.to_datetime("%Y-%m-%d %H:%M:%S%.f"),
(pl.col("price") * pl.col("qty")).alias("notional")
)
.group_by("symbol")
.agg(
pl.col("notional").sum().alias("total_notional"),
pl.col("price").mean().alias("avg_price"),
pl.len().alias("trade_count")
)
)
print(lf.explain())
result = lf.collect()
这段代码在 Eager 模式下会逐步生成多个中间表;在 Lazy 模式下,优化器会把两个 filter 合并并下推到扫描阶段,只读取需要的列,从而显著降低 I/O 与内存压力。
案例场景:金融清洗中的典型收益点
| 清洗任务 | Pandas 常见痛点 | Polars 懒执行收益 |
|---|---|---|
| 逐笔成交去重与排序 | 全量加载后排序,内存峰值高 | 谓词下推 + 并行排序,内存更可控 |
| 多表关联(行情 + 财务) | 连接顺序固定,中间表膨胀 | 连接重排序,减少中间结果 |
| 滚动窗口特征计算 | 单线程 rolling,速度慢 | 多核并行 + 表达式融合 |
| 缺失值与异常值处理 | 逐列操作,代码冗长 | 表达式统一声明,优化器合并 |
| 大规模数据写出 | 需先物化再写盘 | sink_parquet 流式写出 |
误区与复盘:懒执行不是“自动变快”
常见误区一:认为只要用了 scan_* 就一定快。实际上,如果在中途调用 collect() 再继续操作,优化会被打断,退化为多次物化。误区二:忽视 explain() 的作用,盲目堆叠表达式,导致优化器无法有效改写。误区三:在 Lazy 链中混用 Python 自定义函数(UDF),这会阻断向量化与并行,性能可能反而不如原生表达式。
复盘建议:每次写完 Lazy 管道后,先执行 lf.explain(),检查是否出现 FILTER 下推到 SCAN、是否只选择了必要列、是否有重复计算被合并。把 explain() 当作清洗代码的“体检报告”,是掌握 Polars 懒执行优化最有效的习惯。
本课程的整体学习路径将围绕三条主线展开:第一条是数据清洗能力,覆盖类型转换、时间处理、缺失值、异常值、去重与对齐;第二条是懒执行与查询优化,深入谓词下推、投影下推、连接优化与流式写出;第三条是金融实战场景,包括行情清洗、财务表关联、特征工程与回测数据准备。实战目标是让你能够独立构建一条可解释、可优化、可扩展的 Polars 清洗管道,并在真实数据规模下验证性能收益。