検索
連載

[Python]Polars公式が「全部書き換えるな」と言う理由 pandasからの移行、3戦略Deep Insider Brief ― 技術の“今”にひと言コメント

Polars公式が、pandasからPolarsへの移行戦略を3つに整理した記事を公開した。区間ごとに移す手順を整理しつつ、AIに任せれば片付くのかという疑問についても筆者の見解を述べる。

PC用表示 関連情報
Share
Tweet
LINE
Hatena
「Deep Insider Brief ― 技術の“今”にひと言コメント」のインデックス

連載目次

 pandas(パンダス)もPolars(ポーラーズ)も、Pythonで表形式のデータ(Excelの表のようなもの)を扱うためのライブラリだ。pandasは10年以上使われてきた定番であり、Polarsは後発で、Rust製で高速なことを売りにしている。速いことは知っている。将来的にはそちらが主流になりそうだということも、なんとなく分かっている。それでも、目の前の仕事はpandasで書いてしまう。既存のコードがpandasで書かれていて、身に付いたスキルもpandasで、そしてAIに書かせても出てくるのはpandasだからである。

 この停滞に、どこから手を付ければよいのか。その問いに正面から答える記事が、2026年8月6日にPolars公式ブログで公開された。筆者はPolarsのThijs Nieuwdorp氏。新機能の発表ではなく、既にpandasで書かれた資産をどう移すかという、きわめて実務的な内容である。しかも記事は、意外なことに「全部書き換えろ」とは言っていない。

pandasからPolarsへの3つの移行戦略(記事の内容を基に筆者がAIで作成)
pandasからPolarsへの3つの移行戦略(記事の内容を基に筆者がAIで作成)
記事は、パイプライン(データを取り込み、加工し、出力する一連の処理)を「区間」に分けて1つずつ移す手順を土台に、1区間だけ移す方法、全体を手作業で移す方法、全体をLLM(大規模言語モデル)に移させる方法の3つを比較している。

まず「全部書き換えろ」とは言っていない

 意外に思えるかもしれないが、この記事は全面移行を勧めていない。よくテストされていて、めったに変更されず、信頼性・メモリ・コストのどれにも問題がないパイプラインは、そのままでよいと明言している。Polars非対応の独自ライブラリに依存している場合や、厳格な変更管理下にあって再検証の手間が翻訳作業より重い場合も同様だ。

 つまり移行の判断基準は「Polarsが新しいから」ではなく、名指しできる問題があるかどうかに置かれている。メモリ不足で落ちるジョブや、高価なマシンが必要になったジョブがそれに当たる。

 実例として、Check Technologiesが100超のAirflow DAG(処理の流れを定義したもの)を2週間弱で移行してクラウド費用を25%削減した件と、Rabobankがコードベースの中核を作り直して性能が30倍になった件が挙げられている。

――3つの戦略のうち一つは、移行そのものをLLM(大規模言語モデル)に任せるというものだ。ならば、AIに投げれば片付くのだろうか。ここで『Deep Insider Brief』恒例の“ひと言コメント”として、筆者なりの見解を述べたい。その後で、具体的な移行手順を整理していく。


一色政彦

 Deep Insider編集長の一色です。こんにちは。

 白状すると、私自身はついpandasで書いてしまいますが、指示しなければAIもpandasを書きますよね。これは恐らく、AIの学習データの量が10年分以上違うからだと思います。AIという最強のコード生成手段が、いまもpandasを書き続けている、というのが2026年の実情ではないでしょうか。

 ただし、これは言い訳になりません。Polarsが2026年6月に公開した検証では、Claude Opus 4.8にpandasのコード33件をワンショットでPolarsに翻訳させたところ31件が正しく動き、Claude Sonnet 4.6では33件全ての出力が一致しました。1年前は非推奨メソッドを使って動かないコードを出していたことを思えば、状況は変わっています。システムプロンプトに「PolarsをデフォルトのDataFrameライブラリとして使う」と1行書けば済む話でもあります。

 では、AIに投げれば片付くのか。片付かない部分が1つ残ります。翻訳である以上、AIは元のpandasの段取りを引きずるのです。集計用の別テーブルを作ってから結合し直す、といった組み立てが、最新の正しいPolars APIのまま再現される。メソッドは古くない。手順の設計がpandasのままなのです。

 厄介なのは、それが動くし、結果も正しいことです。エラーも警告も出ません。LLMに修復ループを回させられるのは、正しさにはassert_frame_equalという自動判定があるからでした。しかし訛(なま)りは何も落とさない。つまり、自動修復ループの外側にある問題です。しかもこの訛りはAI固有ではなく、pandas歴の長い開発者も同じパターンを持ち込むと記事は指摘しています。

 ここから私の意見です。移行戦略を考える前に、新規案件をPolarsで書くべきだと思います。既存資産の移行は問題が出てから着手すればよい。しかし「これはPolarsらしい書き方かどうか」を判定する目は、問題が出てからでは間に合いません。そして翻訳するかぎり元の構造を引きずるのなら、ゼロから書く新規開発こそが、引きずる元のない唯一の練習場です。

 なお、この検証はPolars社自身によるもので、記事も「1ケース1回の小規模な実験であり、ベンチマークではなく方向性を示す信号として扱ってほしい」と断っています。数字は割り引いて読むべきでしょう。

 正直に言えば、私自身まだPolarsを使いこなせていません。だからこそ、次の新規案件はPolarsで組むところから始めようと思っています。


 それでは、記事が示す具体的な移行ノウハウを整理してみよう。

移行の共通手順:パイプラインを「区間」に切る

 3つの戦略はどれも、同じ土台の上に立っている。パイプライン全体を一気に書き換えるのではなく、変換処理のまとまり(セグメント=区間)に分け、1区間ずつ移す。

区間ごとに移していく手順(記事の内容を基に筆者が作成)
区間ごとに移していく手順(記事の内容を基に筆者が作成)
全部pandasの状態から、性能がネックの1区間だけをPolarsに移す。最終的に全区間がPolarsになったら、間の変換を外して1本の遅延プランに融合する。

最初にやるべき3つのこと

  • 定量化できる問題がある区間を選ぶ: メモリ不足で落ちる、高価なマシンが必要になった、など数字で言える困りごとを起点にする
  • 入力と出力のペアを記録する: その区間が受け取るデータと、渡すデータを保存しておく。これがそのまま、移行後のコードを検証するための固定データ(フィクスチャ)になる
  • 移行前に測っておく: 実行時間とピークメモリを、移行前と移行後の両方で測る。ツールとしてhyperfine、time.perf_counter()、macOSなら/usr/bin/time -l、Linuxなら/usr/bin/time -v、CPU利用率の確認にはpy-spyが挙げられている

区間の両端で変換する

 pandasからPolarsに変換して処理し、次の処理がpandasを必要とするときだけ戻す。前後の処理には手を触れない。

import polars as pl

def step_2(pandas_df):  # 移行した区間
    polars_lf = pl.from_pandas(pandas_df).lazy()
    query = polars_lf.filter(...).group_by(...).agg(...)
    return query.collect().to_pandas(use_pyarrow_extension_array=True)

区間の両端だけでpandasとPolarsを行き来させる(公式ブログのコードを基に抜粋)

  • lazy()は遅延評価(すぐ実行せず、クエリを組み立ててから最後にまとめて実行する方式)への切り替え。最適化が効くようになる
  • collect()で初めて実際の計算が走る
  • use_pyarrow_extension_array=Trueを付けると、pandas側もArrow形式で受け取る

変換コストは「ほぼ無料、ただし常にではない」

  • PolarsはApache Arrow(データをメモリ上に置く共通の形式)を使い、pandasもArrowを使える。両側がArrowなら、2つのライブラリが同じメモリ領域を読むだけなのでコピーが起きない
  • ただしスキーマ(列の型定義)やpandasの設定次第では、部分的なコピーが発生する。コピーが起きると同じ列が両側に同時に存在することになる
  • 境界が無料だと仮定せず、自分のデータで実測すべきだと記事は強調している

3つの移行戦略

その1 性能がネックの1区間だけ移行する

  • 利点: すぐ試せて、完全に元に戻せて、その区間の外側には何も影響しない
  • 欠点: パイプラインが2つのライブラリを同居させることになる。境界の変換コストが型によってはゼロでなく、最適化エンジンが境界の向こう側を見通せない
  • 向く場面: 全面移行を約束せずに、性能面で低リスクな成果を出したいとき

その2 全体を手作業で移行する

  • 利点: ライブラリが1つになり、境界の変換もなくなる。全区間が1つの遅延プランになるため、最適化エンジンが端から端まで見通せる
  • 欠点: 最も費用がかかる。区間の数だけ作業が増え、それぞれ検証が要る
  • 向く場面: 問題がパイプライン全体に及ぶとき。あるいは重要な処理で、チームがコードを理解している状態を残したいとき
  • 隣り合う区間が両方Polarsになったら、間のcollect().to_pandas()from_pandas().lazy()を外し、最後にcollect()が1つだけ残る形に融合できる

その3 全体をLLMに移行させる

  • 利点: 同じ手順を、手作業では割に合わなかった数のパイプラインに適用できる
  • 欠点: null(欠損値)の扱いや行順、業務ルールの変化が許容できるかどうかをモデルは判断できない。またチームは新しいコードを「書く」のではなく「読む」ことでしか理解を得られない。加えて、フィクスチャの準備は先に人間がやるしかない
  • 向く場面: 翻訳作業が定型的で数が多く、区間ごとのフィクスチャとレビュー体制がそろっているとき
  • 先例として、Amazon Scienceの研究で、最大9700行のGoプロジェクトをRustへ翻訳した際に、等価性チェックによって73%の関数が自動的に検証されたことが挙げられている

本当に危ないのは「文法」ではなく「意味」

 記事は、翻訳ミスは文法ではなく意味の違いから生じると明言している。コードが間違って見えないまま、結果だけが変わるパターンだ。

気を付けるべき意味の差

  • Polarsにはインデックスがない(pandasのように行に名前や番号を付けて管理する仕組みを持たない)
  • NaNnull別種の欠損値として区別される
  • 日時の単位、タイムゾーンの扱い、行の並び順が、コードの見た目を変えないまま変わり得る

検証の仕方

  • polars.testing.assert_frame_equalで、移行前後の出力を比較する
  • まず最も厳しい設定から始め、キーワード引数を1つずつ緩めていく。行順を見ない(check_row_order=False)、型の違いを許す(check_dtypes=False)、浮動小数点の許容誤差を設定する(abs_tol。ただしrel_tolの既定値1e-5が優先されるため、rel_tol=0の併記が必要)など
  • 古いpandasの出力を絶対の正解として扱わないこと。意味のないソート順を課しているかもしれないし、null処理の癖や、再現したくない古いバグを含んでいるかもしれない
  • だから、まず「意図した挙動」を先に書き出してから比較するのがよいと記事は勧めている

LLMに任せるときの4つのコツ

 記事は、結果を左右するのはモデルの選択よりも仕事の渡し方だとして、次の4点を挙げている。

  • フィクスチャを仕様書にする: 区間ごとに記録した入出力のペアは、モデルが自分で実行できるチェックになる。これがないと、翻訳が正しいかどうか判断できない
  • プロンプトを1区間に絞る: 入力も差分も限定され、レビューの起点が明確になる。パイプライン全体を渡すと、巨大な差分だけが返ってくる
  • Polars固有のガイダンスを与える: Polars公式は、モデルに読み込ませるスキル(指示書)をGitHubで公開している
  • 修復ループは無人で回させる: モデルがassert_frame_equalを実行して自分の失敗を直せる状態にしておけば、チェックが通るコードで作業を終える
LLMの修復ループと、その外側に残るもの(記事の内容を基に筆者が作成)
LLMの修復ループと、その外側に残るもの(記事の内容を基に筆者が作成)
検証に失敗すればLLMは自分で直せる。しかし「動くし正しいが、書き方がpandas風」という訛りは何のエラーも出さないため、このループには引っ掛からない。

レビューで見るべき点

  • うっかり混入したPythonのループ
  • 不要なデータの実体化(パイプラインの途中で.item()を使うと、早過ぎるcollect()が強制され、1つのクエリが2つに割れてしまう)
  • pandasから持ち込まれたapply系のパターン。Polarsの式で置き換えられることが多い
「Deep Insider Brief ― 技術の“今”にひと言コメント」のインデックス

Deep Insider Brief ― 技術の“今”にひと言コメント

Copyright© Digital Advantage Corp. All Rights Reserved.

[an error occurred while processing this directive]
ページトップに戻る