logo
Polars 高性能数据分析指南
AI Engineer

Polars 高性能数据分析指南

Polars 是用 Rust 编写的高性能 DataFrame 库,速度比 Pandas 快 10-100 倍。

Polars 高性能数据分析指南Polars 简介

Polars:让你的数据处理快到离谱

你第一次用 Polars 大概是这样的:Pandas 跑了 3 分钟的脚本,你抱着试试看的心态换成 Polars,8 秒跑完。你盯着终端看了半天,以为哪里写错了,结果一检查——数据完全正确。

这不是夸张,我在一个 2GB 的 CSV 清洗任务上亲身经历过。当时 Pandas 的 read_csv + groupby + merge 一套下来要 4 分钟,Polars 的 lazy mode 同样逻辑 11 秒结束。那一刻我就知道,这玩意不是玩具。


#先说结论:你到底该不该换 Polars

我直接给结论,省得你翻到最后:

  • 数据量 < 100MB:别折腾,Pandas 够用,生态也更成熟
  • 数据量 100MB - 1GB:可以试试,尤其你已经受不了 Pandas 的速度
  • 数据量 > 1GB:强烈建议换,Polars 的优势是碾压级的
  • 数据量 > 10GB:Polars + scan_parquet 的 lazy mode 是你为数不多的选择之一(另一个是 DuckDB)

一句话:如果你现在的 Pandas 脚本跑得你想去泡杯咖啡,那就是该换的时候了。


#核心区别:不只是快,是思路完全不一样

先看对比表:

对比项PolarsPandas
底层语言RustC/Cython
执行模式惰性求值 + 自动并行即时求值、单线程
内存格式Apache Arrow 列式存储NumPy 行式
API 风格链式表达式、不可变就地修改、索引驱动
大数据表现可处理超出内存的数据全部塞进内存,塞不下就炸
多线程自动用满所有 CPU 核心默认单线程,手动并行很痛苦
Index没有 Index 这个概念到处都是 Index,经常搞混

Polars 官方 Logo
Polars 官方 Logo

但光看表格感受不到本质区别,我用几个类比说清楚。说到这个我想起来,我第一次接触 Polars 其实是在一个同事的 PR review 里看到的,当时还以为是个小众玩具库,结果一试直接真香。

#惰性求值 = 点菜制 vs 自助餐

Pandas 是自助餐模式:你每写一行代码,它立刻执行。df.filter() 马上算,df.groupby() 也马上算。哪怕下一步你要把这个中间结果扔掉,它也老老实实算完了。

Polars 的 lazy mode 是点菜制:你一口气写完所有操作,它先不动,等你说 .collect() 的时候,才把所有操作一起优化、一起执行。就像你在餐厅点了 5 道菜,厨房会把能一起炒的一起炒,而不是做完一道上一道。

python
# Pandas:每一步都立刻执行,中间产生大量临时 DataFrame df = pd.read_csv("huge.csv") # 全部读进内存 df = df[df["age"] > 30] # 马上过滤,产生新 DataFrame df = df.groupby("city").agg({"salary": "mean"}) # 马上聚合 # Polars lazy:先攒着,最后一起算 result = ( pl.scan_csv("huge.csv") # 不读,只记住"要读这个文件" .filter(pl.col("age") > 30) # 不算,只记住"要过滤" .group_by("city") # 不算 .agg(pl.col("salary").mean()) # 不算 .collect() # 这时候才一口气全算完,自动优化执行计划 )

这个差距在简单操作上感觉不大,但一旦链路长了、数据大了,Polars 的查询优化器能帮你省掉大量不必要的计算。

#并行执行 = 高铁 vs 绿皮火车

Pandas 本质上是单线程的。你的电脑有 8 个核,Pandas 只用 1 个,剩下 7 个在看戏。

Polars 用 Rust 的 Rayon 库自动把任务分配到所有 CPU 核心。你不用写任何多线程代码,它自己搞定。这就像高铁和绿皮火车的区别——不是绿皮火车司机技术差,而是轨道和车本身就不在一个级别。

#没有 Index = 少掉 80% 的坑

说实话,Pandas 的 Index 是我见过最容易让新手翻车的设计。reset_index()set_index()、merge 之后 Index 乱掉、MultiIndex 搞不清层级……这些坑你踩过几个?

Polars 直接砍掉了 Index。行就是行,列就是列,没有隐藏的 Index 在背后搞事情。最省力的办法就是不给你犯错的机会。


#迁移指南:Pandas → Polars 对照表

已经会 Pandas 的人,看这个表就能快速上手:

#读写数据

python
# Pandas # Polars pd.read_csv("f.csv") pl.read_csv("f.csv") # eager pl.scan_csv("f.csv") # lazy(推荐) pd.read_parquet("f.parquet") pl.read_parquet("f.parquet") pl.scan_parquet("f.parquet") # lazy df.to_csv("out.csv") df.write_csv("out.csv") df.to_parquet("out.parquet") df.write_parquet("out.parquet")

#选列和过滤

python
# Pandas # Polars df["col"] df["col"] 或 df.select("col") df[["a", "b"]] df.select("a", "b") df[df["age"] > 30] df.filter(pl.col("age") > 30) df.loc[0:5] df.head(5) 或 df.slice(0, 5) df.query("age > 30 & city == 'SYD'") df.filter((pl.col("age") > 30) & (pl.col("city") == "SYD"))

#聚合和分组

python
# Pandas # Polars df.groupby("city")["sal"].mean() df.group_by("city").agg(pl.col("sal").mean()) df.groupby("city").agg({ df.group_by("city").agg([ "sal": "mean", pl.col("sal").mean(), "age": ["min", "max"] pl.col("age").min(), }) pl.col("age").max(), ])

#新增列和修改

python
# Pandas # Polars df["new"] = df["a"] + df["b"] df = df.with_columns((pl.col("a") + pl.col("b")).alias("new")) df["cat"] = df["x"].apply(func) df = df.with_columns(pl.col("x").map_elements(func)) df.rename(columns={"a": "b"}) df.rename({"a": "b"}) df.drop(columns=["x"]) df.drop("x")

#合并和连接

python
# Pandas # Polars pd.merge(df1, df2, on="id") df1.join(df2, on="id") pd.concat([df1, df2]) pl.concat([df1, df2])

#最容易翻车的地方

这几个坑我自己或者身边人都踩过,提前告诉你:

#1. 忘了 collect(),拿到的是 LazyFrame 不是 DataFrame

python
result = pl.scan_csv("data.csv").filter(pl.col("age") > 30) print(result) # 输出的是执行计划,不是数据! # 你得加 .collect() print(result.collect())

新手最常见的困惑就是"为什么打印出来不是数据"。记住:scan_ 开头的返回 LazyFrame,必须 .collect() 才能拿到真正的 DataFrame。

#2. 习惯性写 apply / map,性能直接崩

这个坑我见过不止一次了。有个真实案例:之前组里一个实习生写了个数据清洗脚本,用 Polars 跑反而比 Pandas 还慢,大家都懵了。最后一看代码,满屏的 map_elements(lambda ...),等于把 Polars 的 Rust 引擎完全绕过去了,全程在跑 Python 解释器。改成原生表达式之后,速度直接快了 40 多倍。

python
# ❌ 这样写 Polars 会退化成跟 Pandas 一样慢 df = df.with_columns( pl.col("name").map_elements(lambda x: x.upper()) ) # ✅ 用 Polars 原生表达式,快几十倍 df = df.with_columns( pl.col("name").str.to_uppercase() )

能用内置表达式的就别用 lambda,这是 Polars 性能的第一铁律。顺便提一嘴,Polars 的表达式 API 覆盖面其实非常广,字符串、时间、数学运算基本都有原生支持,真正需要写 lambda 的场景没你想的那么多。

#3. 字符串比较用错了

python
# ❌ Pandas 思维:直接比较 df.filter(pl.col("status") == "active") # 这个没问题 # ❌ 但 contains 不一样 df.filter(pl.col("name").str.contains("张")) # ✅ 注意是 .str.contains()

Polars 的字符串操作都在 .str 命名空间下,跟 Pandas 的 .str 类似但不完全一样。

#4. 列名重复时 join 会报错

join 的时候同名列记得加 suffix

python
df1.join(df2, on="id", suffix="_right")

#5. 不支持就地修改

Pandas 里你可以 df["col"] = xxx,Polars 里 DataFrame 是不可变的(immutable)。所有修改都返回新的 DataFrame:

python
# Pandas 思维(Polars 里不行) df["new_col"] = values # Polars 方式 df = df.with_columns(pl.lit(values).alias("new_col"))

#版本注意事项

Polars 更新非常快,API 经常有 breaking changes。几个建议:

  • 锁定版本requirements.txt 里写死版本号,比如 polars==1.27.0,不要用 >=
  • 关注 changelog:大版本升级前看一眼 GitHub Releases,重点看 Breaking Changes 部分
  • 0.x → 1.0 的迁移:如果你还在用 0.x 版本,升级到 1.0 有不少 API 变化,比如 groupby 改成了 group_byapply 改成了 map_elements
  • Python 版本:建议 Python 3.9+,Polars 已经不支持 3.8

#适不适合你:Checklist

在决定之前,过一遍这个清单:

适合用 Polars 的情况:

  • 你的数据处理脚本跑得慢,成为了工作瓶颈
  • 你愿意花 1-2 天学新 API(跟 Pandas 像但不完全一样)
  • 你主要做 ETL、数据清洗、聚合分析
  • 你的数据格式是 CSV、Parquet、JSON
  • 你不需要大量用 .plot() 画图(Polars 本身没有画图功能)

暂时不适合的情况:

  • 你的项目深度依赖 scikit-learn、statsmodels 等只接受 Pandas 的库
  • 团队里只有你一个人会 Polars,其他人都用 Pandas
  • 数据量很小,当前性能完全够用
  • 你需要大量交互式探索(Jupyter 里 Pandas 的体验目前还是更好)

一个折中方案:核心计算逻辑用 Polars,最后转成 Pandas 喂给其他库:

python
# Polars 处理完,转 Pandas 给 sklearn polars_df = pl.scan_parquet("data.parquet").filter(...).group_by(...).agg(...).collect() pandas_df = polars_df.to_pandas() model.fit(pandas_df[features], pandas_df[target])

#性能实测参考

以下是一些典型操作在 1GB CSV 上的大致耗时对比(具体数字取决于机器配置,这里给个数量级的感觉):

操作PandasPolars (eager)Polars (lazy)
读取 CSV~15s~3s~2s (scan)
过滤行~2s~0.3s~0.1s
GroupBy 聚合~5s~0.8s~0.5s
Join 两表~8s~1.2s~0.8s
写 Parquet~3s~1s~1s

注意:lazy mode 的时间包含了 .collect() 的时间。Polars 在 Parquet 格式上优势更大,因为 Arrow 和 Parquet 天生兼容,不需要格式转换。

下面这张是 Polars 官方的 TPC-H 基准测试结果(Scale Factor 10),可以直观看到各个查询上的耗时对比:

Polars TPC-H Benchmark (SF=10)
Polars TPC-H Benchmark (SF=10)


#跟 DuckDB 怎么选

这个问题被问得很多。简单说:

  • Polars:Python DataFrame 库,API 是 Python 风格的链式调用,适合写数据处理 pipeline
  • DuckDB:嵌入式数据库,API 是 SQL,适合喜欢写 SQL 的人

两者性能差不多,都很快。选哪个主要看你更喜欢写 Python 表达式还是 SQL。也可以一起用,DuckDB 能直接查询 Polars DataFrame。


#相关资源

官方:

相关 Wiki:

  • Pandas Wiki — 如果你还没学 Pandas,建议先学 Pandas 再来看 Polars
  • DuckDB Wiki — 另一个高性能数据处理方案,用 SQL 的方式
  • Apache Arrow Wiki — Polars 底层的内存格式

推荐学习路径:

  1. 先跑通官方的 Getting Started,感受一下速度差异
  2. 把一个现有的 Pandas 脚本迁移过来,对照上面的对照表改
  3. 重点学 Expression API,这是 Polars 最强大的部分
  4. 理解 lazy vs eager 的区别,日常优先用 lazy mode
免费资源

精选免费资料与工具合集

课程、工具与资料一站式获取。

查看免费资源 →

相关路线图

常见问题

Polars 和 Pandas 选哪个?
小数据集两者都行,大数据集(GB级)强烈推荐 Polars。Polars 的惰性求值和并行计算让它在性能上碾压 Pandas。
Polars 学习成本高吗?
如果你熟悉 Pandas,Polars 的 API 设计很相似,上手很快。主要区别在于惰性求值模式和表达式语法。