1. 课程导入

金融数据清洗是量化研究、风险建模与交易系统开发中最耗时、最容易出错的环节之一。行情快照、逐笔成交、财务报表、宏观指标往往来自不同供应商,字段命名混乱、时间戳精度不一、缺失值与异常值交织,单日数据量动辄数千万行。传统方案中,Pandas 凭借丰富 API 成为默认选择,但当数据规模超过内存、需要多表关联与滚动窗口计算时,其单线程执行模型和即时求值(Eager)方式会迅速成为瓶颈。Polars 正是为这类场景设计的现代列式 DataFrame 引擎:它用 Rust 实现、基于 Apache Arrow 内存格式、支持多核并行与查询优化,在金融数据清洗任务中通常能带来数倍到数十倍的性能提升。

核心原理:为什么 Polars 适合金融数据清洗

理解 Polars 的价值,需要抓住三个关键机制。第一是列式存储与向量化执行:金融清洗中大量操作是“按列筛选、按列变换”,列式布局让 CPU 缓存命中率更高,配合 SIMD 指令可批量处理数值。第二是多线程并行:分组聚合、连接、排序等操作会自动拆分到多个核心,无需用户手动编写并行代码。第三是懒执行与查询优化:这是本课程的主线。懒执行(Lazy)不会立即计算结果,而是先构建逻辑计划,再由优化器进行谓词下推、投影下推、公共子表达式消除、连接重排序等改写,最后才生成物理计划执行。

一句话概括:Eager 模式是“写一步算一步”,Lazy 模式是“先描述全部意图,再让优化器决定怎么算最省”。在金融清洗这种多步骤、多表关联的场景中,优化器往往能消除大量中间数据。

下面这张图展示了 Polars 懒执行的核心数据流:从数据源到最终结果,中间经过逻辑计划、优化器与物理计划三个层次,优化器是性能收益的关键闸门。

Polars 懒执行数据流:从数据源到结果 数据源 CSV / Parquet / IPC 逻辑计划 LazyFrame scan / filter / with_columns 查询优化器 谓词下推 / 投影下推 物理计划 并行执行引擎 优化器改写示例 filter 下推到 scan → 只读取必要列 → 合并重复表达式 减少 I/O、减少内存占用、减少中间 DataFrame 最终结果:collect() 触发执行 清洗后的 DataFrame / 聚合指标 / 特征表

方法流程:从 Eager 到 Lazy 的清洗路径

在金融数据清洗中,推荐的工作流是“先 Lazy 描述,后 collect 执行”。典型步骤包括:

  1. 扫描数据源:使用 pl.scan_csv、pl.scan_parquet 或 pl.scan_ipc 创建 LazyFrame,此时不读取数据。
  2. 声明清洗逻辑:链式调用 filter、with_columns、join、group_by、sort 等,构建逻辑计划。
  3. 触发优化:调用 explain() 查看优化后的计划,确认谓词下推与投影下推是否生效。
  4. 执行并物化:调用 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 清洗管道,并在真实数据规模下验证性能收益。