博客 / 詳情

返回

從 Pandas 轉向 Polars:新手常見的10 個問題與優化建議

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

user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.