1.1 pandas 的时代局限
pandas 是 Python 数据分析的绝对基石,但它诞生于 2008 年,架构决定了它今天的几个硬伤:
- 单线程:绝大多数操作跑在一个 CPU 核心上,受 Python GIL(全局解释器锁)掣肘,你的 16 核机器只用了 1 核。
- 基于 NumPy 的内存布局:数值列还算高效,但字符串、缺失值(
NaN)处理笨重,且混合类型退化为object数组。 - Eager 立即求值:每一步都会物化中间结果,无法跨操作做整体优化——读了 100 列却只用 2 列,它照样把 100 列全读进内存。
- API 冗余:
df[...]、.loc、.iloc、.apply语义各异,还时常撞上SettingWithCopyWarning。
1.2 Polars 是什么
Polars 是用 Rust 从零编写的 DataFrame 库,通过 PyO3 暴露 Python 接口。它不是给 pandas 打补丁,而是重新设计了整个执行引擎:列式内存、多线程、惰性优化。相同的分析任务,Polars 常常比 pandas 快 10 到 100 倍,内存占用也更低。
- DataFrame
- 二维表格数据结构,由若干具名的列(Series)组成,每列类型一致。是 Polars 最常用的容器。
- Series
- 一维、类型统一的具名数组,是 DataFrame 的一列。底层是一个或多个 Arrow
ChunkedArray。 - Apache Arrow
- 一种跨语言的列式内存格式标准。同一列数据在内存中连续排列,缓存友好、便于 SIMD 向量化,且可在 Polars/pandas/DuckDB/Spark 间零拷贝传递。
- 列式存储(Columnar)
- 与"行式"相对。分析型查询通常只碰少数几列,列式布局能只读取需要的列,且同列数据同质、压缩率高、向量化快。
- GIL(全局解释器锁)
- CPython 的机制,保证同一时刻只有一个线程执行 Python 字节码。它让纯 Python 难以真正并行。Polars 的计算在 Rust 层完成、绕开了 GIL,因此能真正多线程并行。
- Eager 执行
- "立即执行"模式:写一行代码就立刻算出结果,交互式探索直观,但每步都物化中间结果,无法整体优化。
- Lazy 执行
- "惰性执行"模式:先把一系列操作记录成查询计划,调用
collect()时才交给优化器整体优化并执行。是 Polars 的性能杀手锏。 - 查询优化器(Query Optimizer)
- 在 Lazy 模式下分析查询计划、自动重写以减少工作量,如谓词下推、投影下推、公共子表达式消除等——类似数据库的优化器。
1.3 为什么这么快
| 维度 | pandas | Polars |
|---|---|---|
| 内核语言 | Python + C(NumPy) | Rust |
| 内存格式 | NumPy(混合/行影响) | Apache Arrow 列式 |
| 并行 | 单线程(受 GIL) | 多线程(无 GIL) |
| 执行模型 | Eager | Eager + Lazy 优化 |
| 字符串 | object 数组,慢 | Arrow Utf8,快 |
| 大于内存 | ❌ 内存不够就崩 | ✅ streaming 流式 |
| 缺失值 | NaN / None 混用 | 统一 null 位图 |
SIMD 是什么
SIMD(Single Instruction Multiple Data,单指令多数据)是 CPU 一次对一组连续数据做同样运算的能力。列式内存把同类型数据连续排列,恰好让编译器能生成 SIMD 指令,一条指令处理 4/8/16 个数,这是列式引擎快的底层原因之一。
1.4 安装
# 推荐用 pip 安装(含常用可选功能)
$ pip install polars
# 想要全部可选功能(Excel、数据库、云存储、绘图等)
$ pip install 'polars[all]'
# 用 uv(更快的包管理器)
$ uv add polars
# 老 CPU(不支持 AVX2)请装兼容版
$ pip install polars-lts-cpu
版本说明
本教程基于 Polars 1.x(2024 年后的稳定 API)。若你查到旧文档里 groupby(无下划线)、pl.count() 等写法,那是 0.x 老 API,本教程一律使用现代写法如 group_by、pl.len()。
1.5 第一个例子:Hello Polars
import polars as pl
# 从字典创建 DataFrame
df = pl.DataFrame({
"name": ["Alice", "Bob", "Carol"],
"age": [30, 25, 35],
"city": ["北京", "上海", "北京"],
})
print(df)
# shape: (3, 3)
# ┌───────┬─────┬──────┐
# │ name ┆ age ┆ city │
# │ --- ┆ --- ┆ --- │
# │ str ┆ i64 ┆ str │
# ╞═══════╪═════╪══════╡
# │ Alice ┆ 30 ┆ 北京 │
# │ Bob ┆ 25 ┆ 上海 │
# │ Carol ┆ 35 ┆ 北京 │
# └───────┴─────┴──────┘
注意打印结果里每列上方的 str/i64——这是列的 dtype(数据类型)。Polars 的表格默认会显示 shape 和类型,比 pandas 更信息密集。
1.6 Eager vs Lazy 初体验
# Eager:每步立即执行
result = (
df.filter(pl.col("age") > 28)
.select(["name", "city"])
)
# Lazy:先构建计划,collect() 才执行 + 优化
result = (
df.lazy()
.filter(pl.col("age") > 28)
.select(["name", "city"])
.collect()
)
心智模型
探索数据、写 Notebook → 用 Eager(直观)。生产管道、大数据、读文件 → 用 Lazy(scan_* + collect),让优化器帮你少读列、少扫行。第 7 章会深入讲 Lazy。
1.7 小结与下一步
- Polars 用 Rust + Apache Arrow 列式内存 + 多线程 + 惰性优化,常比 pandas 快 10-100 倍。
- 核心结构:Series(一列)与 DataFrame(表);核心类型标注在打印结果里。
- 两套模式:Eager 立即执行(探索)、Lazy 惰性优化(生产)。
- 下一章深入 Series 与 DataFrame——数据类型、创建、读取文件与 null 表示。