Pythonデータ分析 #7 polars体験 — pandasが遅くなったときの次の一手

シリーズ最終回です。ここまで 6 回にわたって、pandas ひとつで読み込みから可視化まで一周してきました。最終回では、視野を一段広げてみます。pandas が苦しくなる瞬間はいつなのか、そのとき取り出せる次の一手である polars がどんなツールなのかを、コード比較を中心に体験します。新しい文法を覚える記事ではなく、「こういう選択肢がある」という地図を手に入れる記事です。

pandasが苦しくなる瞬間 #

pandas は、数十万行まではほとんど悩みなくよく動きます。問題はデータが大きくなったときです。だいたい 3 つの壁にぶつかります。

  • 行数: 数百万〜数千万行になると、groupbymerge 1 回に数十秒かかり始めます。
  • メモリ: pandas はファイル全体をメモリに載せます。2GB の CSV がメモリ上ではその数倍に膨らみ、ノート PC で MemoryError に出会うことになります。
  • シングルコア: pandas の演算の大半は、CPU コアを 1 つしか使いません。8 コアのマシンでも 7 つは遊んでいます。

小さいデータではまったく問題にならなかった設計が、データが大きくなった瞬間、一斉にコストとして返ってきます。

polars: Rustで作り直されたDataFrame #

polars は Rust で書かれた DataFrame ライブラリです。Python から import polars でそのまま使え、設計自体が上の 3 つの壁を狙っています。

  • Rust ベース: 中核の演算が、コンパイル済みのネイティブコードで動きます。
  • マルチコア: 特別な設定なしに、すべてのコアを使って並列に演算します。
  • Arrow メモリフォーマット: Apache Arrow という列指向の標準フォーマットを使うため、メモリ効率が良く、他のツールとデータをコピーなしでやり取りできます。

インストールは 1 行です。

polarsのインストール
uv add polars

同じ作業を並べて比較 #

文法がどれだけ似ていて、どこが違うのか。シリーズを通して使ってきた販売データ sales.csv(city、category、price、quantity の列)で、同じ作業を両方にやらせてみます。

まずは読み込みです。

読み込み: pandas
import pandas as pd

df = pd.read_csv("sales.csv")
読み込み: polars
import polars as pl

df = pl.read_csv("sales.csv")

ここまではほぼ同じです。polars の read_csv はマルチコアでパースするため、同じファイルでも体感速度が違います。次はフィルタです。

フィルタ: pandas
expensive = df[df["price"] > 10000]
フィルタ: polars
expensive = df.filter(pl.col("price") > 10000)

pandas はブールマスクを角括弧に入れましたが、polars は pl.col("price") > 10000 という 式(expression)filter メソッドに渡します。この違いが polars 文法の中心です。最後に groupby 集計です。

groupby: pandas
result = (
    df[df["price"] > 10000]
    .groupby("city")["price"]
    .mean()
    .sort_values(ascending=False)
)
groupby: polars
result = (
    df.filter(pl.col("price") > 10000)
    .group_by("city")
    .agg(pl.col("price").mean())
    .sort("price", descending=True)
)

読んでみると、やっていることは同じです。価格が 10,000 を超える取引だけを残し、都市ごとの平均価格を求め、降順に並べ替えます。

式APIという考え方 #

上のコードに現れた違いを一文にまとめるとこうなります。pandas は「DataFrame オブジェクトをメソッドで加工し続ける」、polars は「何を計算するかを式として組み立て、まとめて渡す」です。

pl.col("price").mean() は、それ自体では何も計算しません。「price 列の平均」という計算の計画にすぎず、agg() のような場所にはめ込まれたとき、初めて実行されます。式が独立した部品なので、組み合わせは自由です。

複数の式を一度に
result = df.group_by("city").agg(
    pl.col("price").mean().alias("avg_price"),
    pl.col("price").max().alias("max_price"),
    pl.col("quantity").sum().alias("total_qty"),
)

pandas で agg({"price": ["mean", "max"], ...}) のように辞書で書いていたものが、polars では式のリストとしてフラットに並びます。そして polars は、こうして受け取った式を内部で並列に実行します。

lazyモード: 計画を立てて一度に実行 #

polars の本当の武器は lazy モード です。ここまでのコードは、1 行ずつ即時に実行される eager 方式でした。lazy モードは違います。scan_csv で始めると演算をすぐには実行せず計画だけを積み上げ、最後に collect() を呼んだ瞬間に、全体の計画を一度に実行します。

lazyモード
result = (
    pl.scan_csv("sales.csv")
    .filter(pl.col("price") > 10000)
    .group_by("city")
    .agg(pl.col("price").mean())
    .collect()
)

買い物にたとえるとこうです。eager 方式は、「牛乳を買ってきて」「卵を買ってきて」と言われるたびに、スーパーへ 1 回ずつ行くやり方です。lazy 方式は、リストを最後まで書き取ってから、動線を考えてスーパーへ 1 回だけ 行くやり方です。

collect() の直前に、polars のクエリオプティマイザが計画を検討して組み直します。上の例なら「どうせ使うのは city と price の 2 列だけだから、残りの列は読まない」「price > 10000 のフィルタをファイル読み込みの段階に前倒しして、必要な行だけ載せる」といった最適化が自動で適用されます。メモリに載りきらなかったファイルが lazy モードでは処理できる、ということが珍しくない理由です。

どちらを使うか #

2 つから 1 つを選ぶ問題というより、状況に合わせたデフォルトを決める問題です。

基準pandaspolars
エコシステム・検索情報圧倒的に多い急速に成長中
可視化・ライブラリ連携ほとんどが pandas 基準変換を挟めば可能
速度・マルチコアシングルコア標準で並列
メモリより大きいデータ難しいlazy モードで可能

数十万行以下の探索作業、そして他のライブラリと組み合わせる作業なら、pandas が依然として楽な選択です。数百万行以上、あるいは同じパイプラインを繰り返し実行するなら、polars が時間を大きく節約してくれます。そして 2 つは敵同士ではありません。変換 1 行で行き来できるため、重い加工は polars でやり、最後に pandas に変換して可視化する、といった組み合わせが実務でよく使われます。

2つの間を行き来する
df_pl = pl.from_pandas(df_pd)   # pandas -> polars
df_pd = df_pl.to_pandas()       # polars -> pandas

より大きなデータへの地図 #

polars でも足りなくなる規模が来たら、その次のキーワードを 2 つだけ覚えておけば大丈夫です。Parquet は CSV を置き換える列指向のファイルフォーマットで、同じデータをはるかに小さく保存し、必要な列だけを読めます。DuckDB はファイルの上で直接 SQL を実行する分析用データベースで、メモリより大きいデータをノート PC 1 台で扱うときの標準のように使われています。どちらも Arrow フォーマットで polars と自然につながるので、このシリーズで学んだ考え方がそのまま活きます。

シリーズを終えて #

全 7 回を 1 行ずつ振り返ります。

  • #1 pandas入門: データ分析とは何か、環境をどう準備するかを整理しました。
  • #2 読み込みと探索: read_csv でデータを開き、headinfodescribe で第一印象をつかみました。
  • #3 選択とフィルタ: loc とブールマスクで、欲しい行と列だけを取り出しました。
  • #4 変形と欠損値: 新しい列を作り、空いている値を扱う基準を立てました。
  • #5 グループ・集計・結合: groupby でまとめて要約し、merge で表を結合しました。
  • #6 可視化: 数値の要約をグラフに変えて、パターンを目で確認しました。
  • #7 polars体験: pandas の限界と、その先のツールを見てきました。

ツールの話を長くしましたが、分析の本質はツールではなく問いです。「どの都市の売上が落ちているのか」「この変化は偶然なのか」という問いが先にあり、pandas も polars も、その問いに答えを持ってくる手段にすぎません。このシリーズで身につけた一周、つまり読み込んで → 眺めて → 選び出して → 整えて → 要約して → 描いてみるという流れは、ツールが変わってもそのまま残ります。

次の学習としては 2 つの道をおすすめします。繰り返しのデータ作業をコードとして固めたいなら Python自動化 シリーズが自然につながり、Python という言語そのものの筋力を鍛えたいなら モダンPython中級 が次のステップです。良い問いに出会ったとき、答えを求めるツールはもう手元にあります。

X