Pandas → Polars 完全移行ガイド 2026:groupby・join・文字列処理の書き換え実例とベンチマーク比較

約19分で読めます by ぽんたぬき
Pandas → Polars 完全移行ガイド 2026:groupby・join・文字列処理の書き換え実例とベンチマーク比較

Pandas → Polars 完全移行ガイド 2026:groupby・join・文字列処理の書き換え実例とベンチマーク比較


ねえねえ、キミ!!pandasのコード、最近おっそくナイ!?😭 僕さ、先週もサ、12GBのCSVをpandasで読み込もうとしたら、コーヒー3杯分待っても終わらなくてサ……そのあとメモリが死んだんだヨ……(笑)妻に「また画面ぼーっと見てるの?」って言われて、言い返せなかったヨ……✨

そんなキミ(と僕)に届けたいのが、今日の話。Polars、もう試したことあるかナ!?🦝 2026年の今、Polarsはバージョン1.x系になって、実務投入の敷居がぐっと下がったんだヨネ🎵 「全部切り替えろ」とかじゃなくてサ、「どこからPolarsにするか」を一緒に考えようってのが今日のテーマだヨ!!


1. Polars 1.x の基本操作 ― pandasと並べて見てみヨ!!

1-1. 選択・フィルタ・列追加

コレ見てよ!!スゴくナイ!?✨

import polars as pl
import pandas as pd

df_pd = pd.DataFrame({"a": [1, 2, 3], "v": [10, 20, 30]})
df_pl = pl.DataFrame({"a": [1, 2, 3], "v": [10, 20, 30]})

# 列選択
df_pd["a"]                        # pandas
df_pl.select("a")                 # polars

# 行フィルタ
df_pd[df_pd["a"] > 1]             # pandas
df_pl.filter(pl.col("a") > 1)    # polars

# 列追加
df_pd.assign(new=lambda d: d["v"] * 10)           # pandas
df_pl.with_columns(new=pl.col("v") * 10)          # polars

動いたヨ!!思わず「おおっ」って声出ちゃいましたヨ😆 記法がちょっと違うケド、慣れてくるとpolarsの方がスッキリ書けるんだヨネ🎵

1-2. GroupBy と集計

ちなみにサ、今日カツカレー食べちゃった😋 関係ないケド!groupbyの話ネ!!

import polars as pl
import pandas as pd

# "c", "v", "t" 列を持つDataFrameを定義
df_pd = pd.DataFrame({"c": ["x", "y", "x"], "v": [10, 20, 30], "t": [1, 2, 3]})
df_pl = pl.DataFrame({"c": ["x", "y", "x"], "v": [10, 20, 30], "t": [1, 2, 3]})

# pandas
df_pd.groupby("c")["v"].mean()

# polars
df_pl.group_by("c").agg(pl.col("v").mean())

# window関数(pandasのtransform相当)
# pandas
df_pd["count"] = df_pd.groupby("c")["t"].transform(len)

# polars
df_pl.with_columns(
    pl.col("t").count().over("c").alias("count")
)

# 複数集計を一発で書けるのがpolars流!!
df_pl.group_by("c").agg([
    pl.col("v").mean().alias("v_mean"),
    pl.col("v").max().alias("v_max"),
    pl.col("t").count().alias("t_count"),
])

キミ、センスあるネ!!😆 agg()の中にリストで複数集計を書けるのサ、これが地味にめっちゃ便利なんだヨ✨

1-3. Join 操作

import polars as pl
import pandas as pd

# join用DataFrameを定義
df1 = pd.DataFrame({"id": [1, 2, 3], "val1": ["a", "b", "c"]})
df2 = pd.DataFrame({"id": [1, 2, 4], "val2": ["x", "y", "z"]})
df1_pl = pl.DataFrame({"id": [1, 2, 3], "val1": ["a", "b", "c"]})
df2_pl = pl.DataFrame({"id": [1, 2, 4], "val2": ["x", "y", "z"]})

# pandas
pd.merge(df1, df2, how="left", on="id", suffixes=("_left", "_right"))

# polars
df1_pl.join(df2_pl, how="left", on="id", suffix="_right")

# asof join(時系列データに神!!)
# asof join用にtimestamp・key列を持つDataFrameを別途定義(ソート済みであること)
df1_asof = pl.DataFrame({"timestamp": [1, 2, 3], "key": ["a", "a", "b"], "val": [10, 20, 30]}).sort("timestamp")
df2_asof = pl.DataFrame({"timestamp": [1, 2, 3], "key": ["a", "a", "b"], "ref": [100, 200, 300]}).sort("timestamp")
df1_asof.join_asof(df2_asof, on="timestamp", by="key")

大規模joinでの速さは後でベンチマーク見てもらえればわかるケド、マジで桁が違うんだヨ!!😆

1-4. 文字列処理

# pandas
df_pd["name"].str.contains("たぬき")
df_pd["name"].str.replace("foo", "bar")
df_pd["name"].str.split(",")

# polars(.str 名前空間はほぼ同じ!)
df_pl["name"].str.contains("たぬき")
df_pl["name"].str.replace("foo", "bar")
df_pl["name"].str.split(",")

⚠️ ただしサ、複雑な正規表現処理はpandasの方が速いケースがあるんだヨネ💦 コレ後でちゃんと書くから待っててネ!!

1-5. 欠損値処理

# pandas(NaNとNoneが混在してカオスになりがち😭)
df_pd.fillna(0)
df_pd.dropna()

# polars(nullに統一!型安全!!)
df_pl.fill_null(0)       # null → 0
df_pl.fill_nan(0.0)      # NaN → 0.0(float列限定)
df_pl.drop_nulls()       # null行を除去

ねえ、聞いて。僕さ、昨日も、pandasのNaN/None混在バグで、3時間溶かしちゃったんだヨネ😭 polarsに移行してからその手の苦しみ、ほぼなくなったヨ✨


2. 型システムの違い ― ここがハマりポイントだヨ!!

pandasの型、曖昧すぎ問題

pandasはNumPyがバックエンドなんだケドサ、NumPyのfloat以外ではNaNを表現できないんだヨネ💦 だから整数列に欠損があると自動でfloatに昇格したり、文字列混じりの列が全部object型になったり……ファミコンのカセット吹いてた頃からのクセで雑に扱ってたのがバレる感じ(笑)📟

polarsはApache Arrowで型安全💡

観点 pandas polars
バックエンド NumPy Apache Arrow
欠損値 NaNNoneが混在 全型で統一的にnull
型安全性 nullable/non-nullableが曖昧 ビットマスクで明示的管理
null伝搬 NaNは伝搬、Noneは例外になることも 全演算で一貫して伝搬
# スキーマを明示的に宣言できる!!
schema = pl.Schema({
    "id": pl.Int64,
    "name": pl.String,
    "score": pl.Float64,
})

df = pl.read_csv("data.csv", schema=schema)

コレ見てよ!!スゴくナイ!?✨ スキーマ宣言しとくと、CSV読み込み時に型推論のブレがゼロになるんだヨ!!やるじゃん!!😆


3. LazyFrame ― Polars の真骨頂コレだヨ!!🔥

EagerかLazyか、それが問題

小規模な探索・試行錯誤 → Eagerpl.DataFrameでそのまま) 大規模パイプライン・本番処理 → Lazypl.LazyFrameで最適化させる)

この使い分けがポイントだヨネ🎵

クエリ最適化の仕組み

polarsのLazyFrameはSQLのクエリオプティマイザみたいな事をやってくれるんだヨ!!🔥

  • Predicate Pushdownfilterをデータソースに一番近い位置へ移動、不要な行を早期に除外
  • Projection Pushdownselectした列だけをI/Oから読み込み
  • 並列実行with_columns内の独立した式を自動でマルチスレッド処理

コレ見てよ!!スゴくナイ!?✨

result = (
    pl.scan_csv("large_file.csv")        # LazyFrameとして読み込み
      .filter(pl.col("age") > 30)        # Predicate Pushdown が効く!
      .select(["name", "age", "score"])  # Projection Pushdown が効く!
      .group_by("name")
      .agg(pl.col("score").mean())
      .collect()                         # ここで初めて実行(最適化済み計画で一括処理)
)

# 実行計画を確認したい時はこれ!
print(
    pl.scan_csv("large_file.csv")
      .filter(pl.col("age") > 30)
      .select(["name", "age", "score"])
      .explain()
)

動いた時サ、思わず声出ちゃいましたヨ😆 妻に怒られたケド!!(笑)

.explain()で最適化後の実行計画が見えるんだヨ!!これが出た瞬間「polarsって本物だわ……」ってなったヨ僕🦝


4. ベンチマーク比較 ― 数字で語るヨ!!📊

PDS-H公式ベンチマーク(2025年5月)

コレ マジでオススメ!! 見てみてヨ:

ライブラリ 実行時間 Polars比
Polars[streaming] 3.89秒 基準
DuckDB 5.87秒 1.5x遅い
Polars[in-memory] 9.68秒 2.5x遅い
Dask 46.02秒 12x遅い
PySpark 120.11秒 31x遅い
Pandas 365.71秒 94x遅い

94倍!! スゴくナイ!?😆🔥

操作別ベンチマーク(実務想定)

操作 pandas polars 差分
CSV読み込み(大規模) 94秒 8秒 11x高速
Join(1千万×1千万行) 基準 9x高速
メモリ(12GBファイル) 16GB+ 2GB峰値 8x効率的
正規表現文字列処理 8.2秒 11.3秒 Pandas優位

ちょっと待って!!最後の行、見た!?正規表現処理はpandasの方が速いんだヨネ💦 これが「全面移行」じゃなくて「部分最適」を勧める理由のひとつだヨ!!

数字の読み方に注意💡

「94倍速い」って数字はSF-10スケール(それなりの大きさのデータ)での話ナンだヨネ🎵 小規模データ(1MB以下とか)だとpolarsのオーバーヘッドの方が大きくなって、逆にpandasより遅くなるケースもあるんだヨ……やらかしちゃいましたヨ……😭 自社の実際のデータで計測するのが大事だヨ!!


5. pandasが依然有利なユースケース ― 移行しない判断も重要だヨ!!

データサイズ < 1GBなら現状維持でOK

パフォーマンス差が体感できないくらい小さいデータなら、移行コストの方がコストでかいんだヨネ……(笑) ROIで考えないとネ🎵

正規表現・複雑な文字列処理

さっきのベンチマークでも見たケドサ、pandasのCythonチューニング済み文字列エンジンは正規表現処理に強いんだヨ💦 polarsでカバーできないケースが現実にあるのは正直に言っとくネ。

scikit-learn / statsmodels との密結合箇所

MLパイプラインのど真ん中はnumpy配列で動いてる事が多くてサ、polarsとの相性がまだ微妙なとこもあるんだヨネ……scikit-learn 1.4+からpolarsへの対応が進んでるケド、全面互換じゃないから要確認だヨ!!

チームのpandasリテラシーが高い場合

コレが一番大事かもしれないヨ😅 キミ一人でpolarsマスターしてもサ、チームが「なにこれ?」ってなったら意味ないんだヨネ……学習コストとオンボーディング負荷は現実的に計上しよう!!


6. 既存pandas依存ライブラリとの相互運用 ― 橋渡しの話だヨ!!

変換APIはこう使う

コレ見てよ!!スゴくナイ!?✨

# Polars → pandas(MLライブラリ・可視化ライブラリへ渡す)
pandas_df = polars_df.to_pandas()

# pandas → Polars(重い集計・join処理の前に変換)
polars_df = pl.from_pandas(pandas_df)

# Apache Arrow経由(ゼロコピー・最高効率!!)
polars_df = pl.from_arrow(pandas_df.to_arrow())

主要ライブラリ別の対応状況🎵

ライブラリ Polars直接対応 変換で対応 要注意
scikit-learn 1.4+ △(一部)
matplotlib ◎(to_pandas()でOK)
seaborn ◎(to_pandas()でOK)
geopandas △(要確認) ⚠️

変換コストを最小化するアーキテクチャのコツ💡

to_pandas()のゼロコピー変換はArrowベースの型の時に効くんだヨネ🎵 object型が混じってるとコピーが走るから遅くなる……これ知らずに「あれ?変換コスト高くない?」ってなってたヨ僕😅

アーキテクチャとしてはサ、polarsで重い処理を全部やり切ってから、境界で一回だけto_pandas()するのがベストプラクティスだヨ!!変換を何度もループ内でやってたりすると台無しだからネ(笑)


7. 段階的移行戦略 ― 一気にやらなくていいヨ!!🦝

移行優先度の判断フロー

データサイズ > 1GB?
  └─ YES → join/groupby処理 → Polars化の筆頭候補!!★
  └─ NO  → 現状維持でOK(費用対効果を考えよう)

正規表現処理が中心?
  └─ YES → pandas維持を検討(無理して移行しなくていい!!)
  └─ NO  → Polarsの恩恵が大きい

ML/可視化が最終出力?
  └─ YES → Polars処理 → to_pandas() → downstream
  └─ NO  → フルPolarsパイプラインでいけるヨ!!

ロードマップ例(Strangler Pattern で安全に!!)

  • Phase 1:I/O層(CSV・Parquet読み書き)をPolarsに切り替え
  • Phase 2:大規模join・groupby処理をLazyFrameパイプラインへ
  • Phase 3:文字列・型処理の統一とスキーマ明示化
  • Phase 4:pandas依存部分を変換APIで橋渡し

Phase 1だけでもかなり速くなるケースが多いんだヨ!!全部一気にやろうとして3ヶ月溶かしたチームを僕は知ってるヨ……(笑)😭

テスト戦略:移行前後の出力一致確認

import polars.testing as pl_testing
import pandas.testing as pd_testing

# polarsで移行後の出力を検証
pl_testing.assert_frame_equal(
    result_polars,
    expected_polars,
    check_dtypes=True,
)

# pandas結果と比較したい場合は変換して突き合わせ
pd_testing.assert_frame_equal(
    result_polars.to_pandas(),
    result_pandas,
    check_dtype=False,   # 型の微妙な差は許容しつつ値を確認
)

数値精度・null扱いの差異が出やすいから、テストケースには必ず**エッジケース(null含む行、全null列、空DataFrameなど)**を入れておくんだヨネ💡


まとめ ― 移行判断のチェックリストだヨ!!

……というワケでサ、今日のポイントまとめちゃいますネ♪

  • 処理データが1GB超か
  • join・groupby・集計がボトルネックになっているか
  • MLライブラリとの境界でto_pandas()変換が許容できるか
  • チームへのPolars教育コストを計上できるか
  • 正規表現処理が処理全体の大部分を占めていないか

結論:「全面移行」より「処理特性に応じた部分最適」がPolars導入の現実解。 大規模I/O・集計・joinはPolars、ML境界・文字列処理はpandasという棲み分けが2026年時点のベストプラクティス。

94倍速いって聞くとサ、「全部Polarsにしなきゃ!!」ってなるの、わかるヨ(笑)😆 でもネ、焦って全面移行してボトルネック以外のとこも書き換えて、チームが混乱して……ってのが一番よくあるやらかしだからサ……🦝

まずはデータサイズとボトルネック操作を確認して、Phase 1から始めてみてヨ!!キミならできるって信じてるヨ!!😆🔥


参考リンク

関連記事

コメント

0/2000