Polars 速度快、語法現代、表達力強,但很多人剛上手就把它當 Pandas 用,結果性能優勢全都浪費了。
下面是新手最容易犯的 10 個錯誤,以及對應的解決思路。
1、直接 read_csv而不用 scan_*
新手拿到一個大 CSV,上來就這麼寫:
df=pl.read_csv("events.csv")
這會把整個文件一口氣塞進內存。文件一旦上了 GB 級別,內存直接爆掉,性能也跟着完蛋。正確做法是用惰性掃描:
lf=pl.scan_csv("events.csv")
所有操作保持惰性狀態,直到最後調用
.collect()
。
這樣做的好處是優化器可以把過濾和投影操作下推到掃描階段,I/O 和內存佔用都會大幅下降。
2、還在用 Python 循環或 .apply()
想給數據加個新列,很多人會寫成這樣:
df=df.with_columns(
pl.col("price").apply(lambdax: x*1.19)
)
這種寫法強迫 Python 逐行處理,完全沒有向量化可言,慢得離譜。換成原生表達式:
df=df.with_columns(
(pl.col("price") *1.19).alias("price_with_vat")
)
這樣操作會跑在 Rust 層面,有 SIMD 加速,還能融合進查詢計劃裏。性能差距就變得很大了
3、collect() 調用太早、太頻繁
新手經常寫出這種流水線:
df1=lf.filter(...).collect()
df2=df1.with_columns(...).collect()
每調一次
.collect()
,整個數據集就要完整物化一遍。應該把所有操作串起來,最後只 collect 一次:
result= (
lf.filter(...)
.with_columns(...)
.groupby(...)
.agg(...)
)
df=result.collect()
單次
.collect()
讓優化器有機會做全局優化,計算量能省下一大截。
4、不做列裁剪(投影下推)
比如加載了一張 200 多列的寬表,實際只用到 4 列——但整張表還是全讀進來了。正確做法是是儘早篩選列:
lf=lf.select(["user_id", "country", "revenue", "event_time"])
Polars 會把投影下推到掃描層,從磁盤上讀取時只讀這幾列。配合 Parquet 格式效果更明顯,速度提升非常可觀。
5、太早轉成 Pandas
有人習慣這麼幹:
pd_df=lf.collect().to_pandas()
還沒過濾、沒分組、沒聚合,就先轉成 Pandas 了,結果幾千萬行數據全在 Pandas 裏慢慢磨。合理的做法是先在 Polars 裏把重活幹完:
cleaned=lf.filter(...).groupby(...).agg(...)
pdf=cleaned.collect().to_pandas()
Polars 是計算引擎,Pandas 只是展示層,搞反了性能優勢就沒有了。
6、搞混 DataFrame、LazyFrame 和 Expr
新手容易寫出這種代碼:
lf.groupby("user_id").sum()
或者:
df.with_columns(lf.col("price"))
原因是沒搞清楚三種核心類型的區別。
要記住:DataFrame 是已經物化的數據;LazyFrame 是查詢計劃;Expr 是列表達式。
lf=pl.scan_csv("file.csv") # LazyFrame
df=lf.collect() # DataFrame
expr=pl.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 會嘗試推斷類型,但新手往往不驗證。
這時在掃描時直接指定類型更靠譜:
lf=pl.scan_csv(
"orders.csv",
dtypes={"price": pl.Float64}
)
或者讀完再轉:
df=df.with_columns(pl.col("price").cast(pl.Float64))
類型明確的管道更穩定、更可預測,跑起來也更快。
9、大數據聚合不開流式模式
幾十億行數據做 groupby:
lf.groupby("user_id").agg(...)
內存肯定撐不住,程序就直接崩掉了。這時要開啓流式模式:
result= (
lf.groupby("user_id")
.agg(pl.col("amount").sum())
.collect(streaming=True)
)
流式處理會分塊執行特別適合 ETL 場景和日誌分析管道。
10、多次 with_columns而不是合併表達式
新手容易這麼寫:
df=df.with_columns(pl.col("a") +pl.col("b"))
df=df.with_columns(pl.col("c") -pl.col("d"))
df=df.with_columns(pl.col("e") *1.19)
三次調用,三個獨立步驟,沒法融合優化。可以將他們合併到一個表達式塊裏:
df=df.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 的性能才能真正釋放出來。
https://avoid.overfit.cn/post/9936cca71070432e9f47e83aa2575a5b
作者:Brent Fischer