Polars 入門 — Pandas より高速な DataFrame ライブラリ

Polars は Rust 製の DataFrame ライブラリで、Pandas と比べてメモリ効率・処理速度の面で大幅な改善が見込めます。この記事では基本操作から Lazy API・DuckDB 連携まで実際のコードで解説します。

Polars とは

Polars は 2020 年に登場した Rust 製の DataFrame ライブラリです。Apache Arrow をメモリフォーマットとして採用し、SIMD 最適化・並列処理により Pandas より高速に動作します。

Pandas との比較

項目PandasPolars
実装言語Python / CRust
メモリフォーマット独自Apache Arrow
並列処理限定的(GIL の制約)ネイティブマルチスレッド
Lazy 評価なしあり(クエリ最適化)
速度(大規模データ)普通2〜10 倍高速(ベンチマーク依存)

インストール

pip install polars

# 追加機能(Excel 読み込み・Parquet 等)
pip install polars[all]

基本操作

DataFrame の作成

import polars as pl

df = pl.DataFrame({
    "name": ["Alice", "Bob", "Charlie", "David"],
    "age": [25, 30, 35, 28],
    "sales": [120.5, 85.0, 200.3, 150.7],
    "region": ["East", "West", "East", "North"],
})

CSV / Parquet の読み込み

# CSV
df = pl.read_csv("data/sales.csv")

# CSV(型を明示)
df = pl.read_csv(
    "data/sales.csv",
    schema_overrides={"date": pl.Date, "amount": pl.Float64},
)

# Parquet(glob も使える)
df = pl.read_parquet("data/*.parquet")

基本的な操作

# 行数・列数
print(df.shape)     # (4, 4)
print(df.schema)    # Schema({'name': String, 'age': Int64, ...})

# 列の選択
df.select("name", "sales")

# 列の追加
df = df.with_columns(
    (pl.col("sales") * 1.1).alias("sales_with_tax")
)

フィルタリング

# 条件フィルタ
df.filter(pl.col("age") > 28)

# 複数条件(AND)
df.filter(
    (pl.col("age") > 25) & (pl.col("region") == "East")
)

# in 条件
df.filter(pl.col("region").is_in(["East", "North"]))

# 文字列の部分一致
df.filter(pl.col("name").str.starts_with("A"))

集計

# グループ集計
df.group_by("region").agg(
    pl.col("sales").sum().alias("total_sales"),
    pl.col("sales").mean().alias("avg_sales"),
    pl.col("sales").count().alias("count"),
)

# 全列の基本統計
df.describe()

Pandas との文法比較

# ---- フィルタリング ----
# Pandas
pd_df[pd_df["age"] > 28]

# Polars
pl_df.filter(pl.col("age") > 28)

# ---- 列の追加 ----
# Pandas(ミュータブル)
pd_df["tax"] = pd_df["sales"] * 0.1

# Polars(イミュータブル)
pl_df = pl_df.with_columns(
    (pl.col("sales") * 0.1).alias("tax")
)

# ---- グループ集計 ----
# Pandas
pd_df.groupby("region")["sales"].sum().reset_index()

# Polars
pl_df.group_by("region").agg(pl.col("sales").sum())

# ---- 欠損値の埋め ----
# Pandas
pd_df["sales"].fillna(0)

# Polars
pl_df.with_columns(pl.col("sales").fill_null(0))

Lazy API — クエリ最適化で大規模データを効率処理

Polars の Lazy API は SQL のクエリプランナーのように、実際に計算する前にクエリ全体を最適化します。大規模データでは特に効果的です。

Eager vs Lazy

# Eager: 各ステップで即座に計算(小〜中規模データ向け)
result = (
    df
    .filter(pl.col("sales") > 100)
    .group_by("region")
    .agg(pl.col("sales").sum())
)

# Lazy: クエリを構築してから一括実行(大規模データ向け)
result = (
    df.lazy()
    .filter(pl.col("sales") > 100)
    .group_by("region")
    .agg(pl.col("sales").sum())
    .collect()  # ここで初めて計算実行
)

ファイルから直接 Lazy 読み込み

# scan_* はファイルを Lazy で読み込む(メモリに全部載せない)
result = (
    pl.scan_csv("data/large_sales.csv")  # 数 GB のファイルも OK
    .filter(pl.col("region") == "East")
    .group_by("date")
    .agg(pl.col("sales").sum())
    .sort("date")
    .collect()
)

クエリプランの確認

lf = pl.scan_csv("data/large_sales.csv").filter(pl.col("sales") > 100)

print(lf.explain())                 # 最適化後
print(lf.explain(optimized=False))  # 最適化前

DuckDB との連携

Polars と DuckDB は互いにデータを渡し合えます。DuckDB の SQL 表現力と Polars の高速処理を組み合わせるのが強力なパターンです。

import polars as pl
import duckdb

df = pl.read_parquet("data/sales.parquet")

# Polars DataFrame を直接 SQL で参照
result = duckdb.sql("""
    SELECT
        region,
        DATE_TRUNC('month', sale_date) AS month,
        SUM(sales) AS total_sales
    FROM df
    WHERE sale_date >= '2025-01-01'
    GROUP BY region, month
    ORDER BY month, total_sales DESC
""").pl()  # .pl() で Polars DataFrame として返す

ハマりやすいポイント

Polars の DataFrame はイミュータブル

# NG: Polars では動かない
df["tax"] = df["sales"] * 0.1

# OK
df = df.with_columns(
    (pl.col("sales") * 0.1).alias("tax")
)

group_by の結果は順序が不定

並列処理のため、結果の行順が毎回異なります。順序を保証したい場合は sort を明示します。

result = (
    df
    .group_by("region")
    .agg(pl.col("sales").sum())
    .sort("region")
)

Lazy API で .collect() を忘れる

result = pl.scan_csv("data.csv").filter(pl.col("age") > 25)
print(type(result))  # <class 'polars.LazyFrame'> ← collect() が必要

result = result.collect()
print(type(result))  # <class 'polars.DataFrame'>

まとめ

  • Polars は Rust 製・Apache Arrow ベースの高速 DataFrame ライブラリ
  • 基本 API は Pandas に似ているが、イミュータブル・Lazy 評価などの違いがある
  • scan_* + Lazy API で数 GB のファイルをメモリ効率よく処理できる
  • DuckDB と Arrow 経由でゼロコピー連携でき、SQL と DataFrame 処理を使い分けられる
  • Pandas からの移行は段階的に行える。まず小さなデータで試して感覚をつかむのがおすすめ