从 Pandas 转向 Polars:新手常见的10 个问题与优化建议

张开发
2026/5/31 10:08:49 15 分钟阅读
从 Pandas 转向 Polars:新手常见的10 个问题与优化建议
Polars 速度快、语法现代、表达力强但很多人刚上手就把它当 Pandas 用结果性能优势全都浪费了。下面是新手最容易犯的 10 个错误以及对应的解决思路。1、直接 read_csv而不用 scan_*新手拿到一个大 CSV上来就这么写dfpl.read_csv(events.csv)这会把整个文件一口气塞进内存。文件一旦上了 GB 级别内存直接爆掉性能也跟着完蛋。正确做法是用惰性扫描lfpl.scan_csv(events.csv)所有操作保持惰性状态直到最后调用.collect()。这样做的好处是优化器可以把过滤和投影操作下推到扫描阶段I/O 和内存占用都会大幅下降。2、还在用 Python 循环或 .apply()想给数据加个新列很多人会写成这样dfdf.with_columns( pl.col(price).apply(lambdax: x*1.19) )这种写法强迫 Python 逐行处理完全没有向量化可言慢得离谱。换成原生表达式dfdf.with_columns( (pl.col(price) *1.19).alias(price_with_vat) )这样操作会跑在 Rust 层面有 SIMD 加速还能融合进查询计划里。性能差距就变得很大了3、collect() 调用太早、太频繁新手经常写出这种流水线df1lf.filter(...).collect() df2df1.with_columns(...).collect()每调一次.collect()整个数据集就要完整物化一遍。应该把所有操作串起来最后只 collect 一次result ( lf.filter(...) .with_columns(...) .groupby(...) .agg(...) ) dfresult.collect()单次.collect()让优化器有机会做全局优化计算量能省下一大截。4、不做列裁剪投影下推比如加载了一张 200 多列的宽表实际只用到 4 列——但整张表还是全读进来了。正确做法是是尽早筛选列lflf.select([user_id, country, revenue, event_time])Polars 会把投影下推到扫描层从磁盘上读取时只读这几列。配合 Parquet 格式效果更明显速度提升非常可观。5、太早转成 Pandas有人习惯这么干pd_dflf.collect().to_pandas()还没过滤、没分组、没聚合就先转成 Pandas 了结果几千万行数据全在 Pandas 里慢慢磨。合理的做法是先在 Polars 里把重活干完cleanedlf.filter(...).groupby(...).agg(...) pdfcleaned.collect().to_pandas()Polars 是计算引擎Pandas 只是展示层搞反了性能优势就没有了。6、搞混 DataFrame、LazyFrame 和 Expr新手容易写出这种代码lf.groupby(user_id).sum()或者df.with_columns(lf.col(price))原因是没搞清楚三种核心类型的区别。要记住DataFrame 是已经物化的数据LazyFrame 是查询计划Expr 是列表达式。lfpl.scan_csv(file.csv) # LazyFrame dflf.collect() # DataFrame exprpl.col(amount) # Expr模型清晰了才能避开各种隐蔽 bug也才能让优化器真正发挥作用。7、以为 .unique()和 Pandas 一样有些人期望.unique()返回排序后的结果但 Polars 默认保留原始顺序lf.select(pl.col(country).unique())这跟 Pandas 的行为是不一样所以很容易出逻辑错误。如果需要排序就显式加上lf.select(pl.col(country).unique().sort())显式排序能避免跨框架时的隐性差异。8、不管数据类型CSV 里的数据经常乱七八糟19.99, 20, error, Pandas 碰到这种情况会默默建个 object 列而Polars 会尝试推断类型但新手往往不验证。这时在扫描时直接指定类型更靠谱lfpl.scan_csv( orders.csv, dtypes{price: pl.Float64} )或者读完再转dfdf.with_columns(pl.col(price).cast(pl.Float64))类型明确的管道更稳定、更可预测跑起来也更快。9、大数据聚合不开流式模式几十亿行数据做 groupbylf.groupby(user_id).agg(...)内存肯定撑不住程序就直接崩掉了。这时要开启流式模式result ( lf.groupby(user_id) .agg(pl.col(amount).sum()) .collect(streamingTrue) )流式处理会分块执行特别适合 ETL 场景和日志分析管道。10、多次 with_columns而不是合并表达式新手容易这么写dfdf.with_columns(pl.col(a) pl.col(b)) dfdf.with_columns(pl.col(c) -pl.col(d)) dfdf.with_columns(pl.col(e) *1.19)三次调用三个独立步骤没法融合优化。可以将他们合并到一个表达式块里dfdf.with_columns([ (pl.col(a) pl.col(b)).alias(ab), (pl.col(c) -pl.col(d)).alias(cd), (pl.col(e) *1.19).alias(e_vat) ])Polars 会把这些表达式融合成一个优化后的操作。步骤少了自然就快了。总结从 Pandas 转过来的人很容易带着旧习惯写 Polars 代码结果性能优势全没了。上面这些点总结下来就是惰性优先、表达式为主、最后才 collect、别用 Python 循环、列要有明确类型、多用 LazyFrame、善用投影下推和谓词下推、大数据开流式处理。养成这些习惯Polars 的性能才能真正释放出来。

更多文章