Chapter 10

生态集成与性能实战

打通与 pandas/NumPy/Arrow/DuckDB 的互操作,掌握 Parquet 最佳实践、性能调优清单,以及从 pandas 迁移时的常见坑。

10.1 与 pandas / NumPy / Arrow 互转

to_pandas()
Polars DataFrame → pandas DataFrame。默认会拷贝;若列都是 Arrow 兼容类型,可用 use_pyarrow_extension_array=True 走零拷贝。
pl.from_pandas()
pandas → Polars。把 pandas 数据吸进 Arrow 内存。
to_numpy()
Polars → NumPy 数组。含 null 或多类型时会拷贝并可能升为 object,纯数值单列最高效。
to_arrow() / pl.from_arrow()
与 Apache Arrow Table 互转,通常零拷贝——因为 Polars 内存本就是 Arrow 格式。
零拷贝(Zero-copy)
不复制底层内存、只共享缓冲区的转换。Arrow 生态内互转常能零拷贝,是 Polars 与 DuckDB/pandas(pyarrow backend) 高效协作的基础。
PyArrow backend
pandas 2.x 起可用 Arrow 作为后端存储,此时与 Polars 互转更省,且字符串/缺失值语义更一致。
import polars as pl

pdf = df.to_pandas()                 # → pandas
df2 = pl.from_pandas(pdf)             # pandas → Polars
arr = df.to_numpy()                  # → NumPy
tbl = df.to_arrow()                  # → Arrow Table(零拷贝)

10.2 与 DuckDB 协作

DuckDB 与 Polars 都构建在 Arrow 上,可以零拷贝互喂:用 SQL 表达复杂查询,用 Polars 做表达式管道,各取所长。

import duckdb, polars as pl

df = pl.read_parquet("data.parquet")

# DuckDB 直接查询 Polars DataFrame(变量名即表名)
out = duckdb.sql("SELECT city, sum(sales) FROM df GROUP BY city").pl()
# .pl() 把结果转回 Polars DataFrame

10.3 Parquet 最佳实践

df.write_parquet("out.parquet", compression="zstd")

# 分区写
df.write_parquet("dataset/", partition_by=["year", "region"])

# 惰性读一批分区文件并只取需要的
(pl.scan_parquet("dataset/**/*.parquet")
   .filter(pl.col("year") == 2026)
   .collect())

10.4 性能调优清单

提速十条

1) 大数据用 Lazy + scan_*,让下推生效。
2) 中间存储用 Parquet,不用 CSV。
3) 用向量化表达式,禁用 map_elements 逐行回调。
4) 低基数字符串列转 Categorical,分组/连接更快、更省。
5) 一次 with_columns 塞多个表达式,让它们并行。
6) 别中途 collect,把链一路写到底。
7) 用 select/scan 的 columns 只取需要的列。
8) 超内存用 collect(engine="streaming")sink_*
9) 用 explain 确认下推生效。
10) 连接前检查键重复度,避免结果扇出爆炸。

10.5 从 pandas 迁移对照

pandasPolars
df[df.a > 1]df.filter(pl.col("a") > 1)
df["c"] = df.a + df.bdf.with_columns((pl.col("a")+pl.col("b")).alias("c"))
df.groupby("k").sum()df.group_by("k").agg(pl.all().sum())
df.sort_values("a")df.sort("a")
pd.merge(a,b,on="k")a.join(b, on="k")
df.apply(f, axis=1)用表达式/when-then 重写(不要逐行)
df.resample("1D").sum()df.group_by_dynamic("t", every="1d").agg(...)

10.6 常见坑

迁移三大坑

没有索引:Polars 无 pandas 那样的 Index,用普通列 + filter/join 代替 set_index/loc
null ≠ NaN:缺失是 null,浮点非数才是 NaN,二者需分别处理。
group_by 无序:默认不保证行顺序,需要就 maintain_order=True 或后接 sort

什么时候不必换 Polars

小数据(几万行)、一次性脚本、重度依赖 pandas 专有生态(如某些 sklearn/statsmodels 直接吃 pandas)时,pandas 依然够用。Polars 的收益在中大数据、生产管道、需要多核与惰性优化的场景最明显。

10.7 全教程总结