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(lambda x: 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
特別聲明:以上內容(如有圖片或視頻亦包括在內)為自媒體平臺“網易號”用戶上傳并發布,本平臺僅提供信息存儲服務。
Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.