Python Polars 完整教學:比 Pandas 快 30 倍的 DataFrame 新選擇
上個月我接到一個案子,需要處理一份 800 萬行的交易紀錄 CSV。照老習慣打開 Jupyter Notebook,寫了一行 pd.read_csv(),然後就開始等。等了 47 秒,記憶體飆到 6GB,筆電風扇狂轉。我心想:「這才 800 萬行,如果是一億行呢?」
就在那個週末,我試了 Polars。同一份 CSV,pl.read_csv() 只花了 2.3 秒。沒有打錯,是 2.3 秒。記憶體用量不到 2GB。我當場愣住,然後花了整個週末把它研究透徹。今天這篇文章,就是我這幾個月使用 Polars 的完整心得。
Polars 是什麼?為什麼這麼快
Polars 是一個用 Rust 寫的 DataFrame 函式庫,專門為高效能資料處理而生。它不是 Pandas 的 wrapper,而是從底層完全重新設計的。如果你已經熟悉 Pandas 資料分析入門,那 Polars 會讓你重新定義「快」這個字。
Polars 之所以快,主要有三個原因:
- Rust 引擎:底層用 Rust 實作,沒有 Python GIL 的限制,能充分利用多核心 CPU 平行處理
- Apache Arrow 記憶體格式:採用列式(columnar)儲存,對分析型查詢友善,快取命中率極高
- Lazy 評估與查詢優化器:像 SQL 一樣先規劃執行計畫,自動合併操作、消除冗餘步驟
簡單來說,Pandas 是「你叫它做什麼,它立刻做什麼」,而 Polars 是「你告訴它你要什麼結果,它自己找最快的路」。這個差異在大數據集上會非常明顯。
安裝與環境設定
安裝 Polars 非常簡單。我個人推薦使用 uv 套件管理器,速度比 pip 快非常多:
# 用 uv 安裝(推薦)
uv pip install polars
# 或用 pip
pip install polars
# 如果需要額外功能(如讀取 Excel、連接資料庫)
uv pip install 'polars[all]'
建議在虛擬環境中安裝,避免套件衝突。Polars 支援 Python 3.8 以上,但我建議至少用 3.10 以獲得最佳相容性。
基本操作:讀取、篩選、排序
來看幾個最常用的操作。Polars 的 API 設計非常直覺,如果你寫過 Pandas,大概看一眼就懂:
import polars as pl
# 讀取 CSV
df = pl.read_csv("transactions.csv")
# 查看前幾行
print(df.head())
# 查看 schema(欄位名稱和型別)
print(df.schema)
# 篩選:金額大於 1000 的交易
high_value = df.filter(pl.col("amount") > 1000)
# 選取特定欄位
subset = df.select(["user_id", "amount", "created_at"])
# 新增計算欄位
df = df.with_columns(
(pl.col("amount") * 1.05).alias("amount_with_tax"),
pl.col("created_at").str.to_datetime().alias("datetime")
)
# 排序
df_sorted = df.sort("amount", descending=True)
注意到了嗎?Polars 用 pl.col() 來引用欄位,而不是像 Pandas 那樣用 df["column"]。這個設計讓 Polars 能在 Lazy 模式下先建立查詢計畫,而不是立刻執行。這是效能差異的關鍵之一。
GroupBy 聚合與 Join 合併
資料分析最常做的就是分組聚合和表格合併。Polars 在這兩個操作上都比 Pandas 快很多:
# GroupBy 聚合
summary = df.group_by("category").agg(
pl.col("amount").sum().alias("total_amount"),
pl.col("amount").mean().alias("avg_amount"),
pl.col("user_id").n_unique().alias("unique_users"),
pl.len().alias("transaction_count")
)
# Join 合併(類似 SQL JOIN)
users = pl.read_csv("users.csv")
result = df.join(
users,
on="user_id",
how="left" # 支援 inner, left, right, outer, cross, semi, anti
)
# 多欄位 Join
result = orders.join(
products,
left_on=["product_id", "region"],
right_on=["id", "sales_region"],
how="inner"
)
我實測過一個 500 萬行的 GroupBy 操作,Pandas 花了 8.2 秒,Polars 只花了 0.4 秒。在 Join 操作上差距更大,因為 Polars 會自動選擇最佳的 Join 演算法。
Lazy 評估:Polars 的殺手級功能
如果只能學 Polars 的一個功能,我會毫不猶豫選 Lazy 評估。這是 Polars 和 Pandas 最根本的差異,也是效能差距的最大來源。
# Eager 模式(立刻執行,類似 Pandas)
result = df.filter(pl.col("amount") > 100).select(["user_id", "amount"]).sort("amount")
# Lazy 模式(先建立查詢計畫,最後一次執行)
result = (
df.lazy()
.filter(pl.col("amount") > 100)
.select(["user_id", "amount"])
.sort("amount")
.collect() # 這裡才真正執行
)
# 直接用 scan_csv 進入 Lazy 模式(推薦!)
result = (
pl.scan_csv("huge_file.csv") # 不會立刻讀整個檔案
.filter(pl.col("region") == "TW")
.group_by("category")
.agg(pl.col("revenue").sum())
.sort("revenue", descending=True)
.head(10)
.collect()
)
用 scan_csv 的好處是,Polars 的查詢優化器會分析你的整個查詢鏈,然後決定「其實我只需要讀取 region 和 category 和 revenue 這三個欄位就好」。如果你的 CSV 有 200 個欄位,它只會讀這三個。這叫做 projection pushdown,是 Lazy 模式自動幫你做的。
另外還有 predicate pushdown:篩選條件會被「推」到資料讀取階段,所以 region 不是 TW 的資料根本不會載入記憶體。這對處理超大檔案來說是革命性的改進。
Pandas vs Polars 語法對照表
為了幫助從 Pandas 轉換過來的人,我整理了一份對照表:
| 操作 | Pandas | Polars |
|---|---|---|
| 讀取 CSV | pd.read_csv(f) | pl.read_csv(f) |
| Lazy 讀取 | 無 | pl.scan_csv(f) |
| 篩選 | df[df["col"] > 5] | df.filter(pl.col("col") > 5) |
| 選取欄位 | df[["a","b"]] | df.select(["a","b"]) |
| 新增欄位 | df["new"] = df["a"]*2 | df.with_columns((pl.col("a")*2).alias("new")) |
| GroupBy | df.groupby("a").agg({"b":"sum"}) | df.group_by("a").agg(pl.col("b").sum()) |
| 排序 | df.sort_values("a") | df.sort("a") |
| 合併 | pd.merge(a,b,on="id") | a.join(b,on="id") |
| 空值處理 | df.fillna(0) | df.fill_null(0) |
| 型別轉換 | df["a"].astype(int) | df.with_columns(pl.col("a").cast(pl.Int64)) |
什麼時候該用 Polars
經過幾個月的實戰,我的建議是:
適合用 Polars 的場景:
- 資料集超過 100 萬行
- 需要處理多個大型 CSV/Parquet 檔案
- ETL 管線需要最佳效能
- 記憶體有限但資料很大
- 需要平行處理多核心 CPU
暫時還是用 Pandas 的場景:
- 小型資料集(幾萬行以下)——兩者差異不大
- 需要和大量第三方套件整合(很多套件只支援 Pandas DataFrame)
- 團隊都熟 Pandas,沒有效能瓶頸時
- 需要就地修改(Polars DataFrame 是 immutable 的)
想把結果做成圖表?不管你用 Polars 或 Pandas 處理完資料,都可以用 Matplotlib 視覺化教學裡的方法來繪圖。Polars DataFrame 可以輕鬆轉成 Pandas(.to_pandas()),所以和 Matplotlib、Seaborn 的相容性完全沒問題。
從 Pandas 遷移的實用建議
如果你打算在現有專案中引入 Polars,這裡有幾個我踩過坑後學到的建議:
1. 漸進式遷移:不需要一口氣全換。先在效能瓶頸的地方引入 Polars,其他地方繼續用 Pandas。兩者可以並存。
2. 善用 .to_pandas() 和 pl.from_pandas():Polars 和 Pandas 之間的轉換幾乎零成本(因為都用 Arrow 格式),所以你可以在需要的地方轉換。
# Pandas -> Polars
polars_df = pl.from_pandas(pandas_df)
# Polars -> Pandas
pandas_df = polars_df.to_pandas()
3. 優先用 Lazy 模式:養成用 scan_csv 而不是 read_csv 的習慣。讓查詢優化器幫你省記憶體和時間。
4. 注意 immutability:Polars 的 DataFrame 是不可變的。with_columns 不是修改原來的 DataFrame,而是回傳新的。這和 Pandas 的 inplace=True 觀念不同,但其實更安全。
5. 用 Expressions 思考:Polars 的核心設計哲學是 Expression。把每個操作都想成一個 Expression(pl.col("a").sum()),組合起來就是你的查詢邏輯。這比 Pandas 的「先取 Series 再操作」更有結構。
說實話,我現在已經很少在新專案裡用 Pandas 了。Polars 的 API 更一致、效能更好、記憶體更省。唯一的缺點是生態系還沒有 Pandas 那麼成熟,但社群成長非常快。我建議你今天就開一個新 notebook,把上面的程式碼跑一遍,感受一下那個速度差異。相信我,回不去了。
繼續閱讀
Python Matplotlib 資料視覺化入門教學:用圖表說故事的完整指南
資料分析做完了,但老闆看不懂你的數字?Matplotlib 是 Python 最經典的繪圖套件,這篇教你從零畫出各種圖表,讓數據自己說話。
相關文章
你可能也喜歡
探索其他領域的精選好文