プログラミング
Pandasは絶滅すべきである
Pandas Should Go Extinct (eddie.codes)
要約
この記事では、Pythonのデータ分析ライブラリであるPandasの非効率性を指摘し、その利用がユーザーを不必要に複雑な分散システムへと誘導していると論じています。著者は、多くのデータ分析のユースケースは、PolarsやDuckDBのような高性能な単一マシンツールで十分に対応可能であり、Pandasの限界を超える「ギャップ」を埋めることができると主張しています。
全文翻訳
Pandasは絶滅すべきである
12 September 2026
/ pandas , python , data , polars , duckdb , analytics
この記事は、私がLatency Conferenceで行った講演の内容をカバーしています。もし単に:
講演を見る
録画はこちらで見つけられます。
または
スライドを読む
こちらで見ることができます。
あなたが読んだのは正しいです、Pandasは絶滅すべきです。国際外交に使われるような可愛くてふわふわした生き物ではなく、PythonのDataFrameライブラリのことです。なぜか?Pandasの非効率性は、ワークロードが追加の複雑さを正当化する前に、分散クエリシステムを採用することを強制するからです。私は、ほとんどのワークロードがこれらのシステムを正当化することはないだろうと主張します。それらは単に「シルバーバレット」としてよくマーケティングされているだけです。
私が何を言っているのかを理解するために、まずPandasの典型的な採用経路を理解する必要があります。
なぜ私たちはPandasを使うのでしょうか?
下の図は、作業しているデータサイズに基づいて、特定のDataFrameライブラリを採用することを通常検討する時期のおおよその目安を示しています。左から右へたどると、データ分析ツールの典型的な採用経路と、Pandasユーザーがあるデータサイズを超えたときに経験する「崖」も見られます。人々は通常Excelから始め、GBの範囲でPandasに移行します。Pandasは10GBの範囲まで彼らをうまくサポートしますが、その後メモリの問題、計算速度の低下、またはPandasの複雑なAPIに不満を感じ始めます。この時点での従来の答えは、Spark、DataBricks、Snowflake、またはDaskのような「本物の」(つまり高価な)ビッグデータ™️用に設計されたツールに移行することです。
データサイズ別のDataFrameライブラリ採用のおおよその目安
問題は、この「Pandasの崖」と、分散システムが本当に必要とされるスケールの間には、成長し続けるギャップがあるということです。このギャップは、約100GBの範囲にあり、最新の高性能な単一マシンツールで効果的に埋めることができます。私は主にPolarsとDuckDBについて話しています。
なぜ私たちはこの約100GBのしきい値にそれほどこだわるのでしょうか?答えは、実際の「ビッグデータ」がどれだけ存在するのかを理解することにあります。
私にはビッグデータがある、そうですよね?
2024年、Amazonは「TPCだけでは不十分: Amazon Redshiftフリートの分析」というタイトルの論文を発表しました。この論文の目的は、Amazon自身の分散分析データベースであるRedshiftのテレメトリデータと、業界標準のデータベースベンチマークで使用されるクエリパターンを比較することでした。分析の一環として、Amazonはクエリ実行時間とテーブルサイズのフリート統計を公開しました。
Amazon Redshiftフリートのバケット別クエリ実行時間
Amazon Redshiftフリートのバケット別テーブルサイズ
いくつかの仮定を立てれば、Amazonの顧客が分析データベースをどのように使用しているかについて興味深い結論を導き出すことができます。次のように仮定しましょう:
Redshiftテーブルの行の平均サイズは1KBである
各RedShiftクラスターは、S3から8GB/秒の速度でデータを吸い上げることができ、それ以外のことは何もしない10台のマシンで構成されている
私たちは次のような発見をします:
Redshiftフリートのテーブルの94.68%は100GB未満のデータを含んでいる
クエリの86.9%は80GB以下のデータで操作されている
もしあなたがこのデータセットの別の、より深い分析に興味があるなら、MotherDuckのJordan Tiganiがここで詳細な分析を行っています。注: MotherDuckはDuckDBホスティングを販売するSaaSビジネスなので、ある程度の懐疑心は正当化されるかもしれません。
計算の説明
実行時間テーブルの最初の3行を合計すると、クエリの86.9%が1秒未満で実行されると計算できます。
1クラスターあたり10台のマシンが8GB/秒でデータを吸い上げると仮定すると、次のように計算できます:
10マシン * 8GB * 1秒 = 80GBのデータ
8GB/秒の仮定は、この認めがたいほど古いベンチマークに基づいています。
「94.68%のテーブルが100GB未満である」という主張に到達するために、10^8の制限までの行を合計すると、94.68%の行が得られます。次に、行あたり1KBという仮定を取り、次のように計算します:
テーブル内の10^8行 * 1KB = 100GB
行あたり1KBという仮定は楽観的すぎるかもしれませんが、たとえ10KBを仮定しても、依然として1TBのテーブルサイズに達します。
しかし、これはすべて何を意味するのでしょうか?
あなたはおそらくビッグデータを持っておらず、おそらくこれからも持つことはないでしょう。あなたはミディアムデータの問題を抱えており、ミディアムデータソリューションを必要としています。
代替案を紹介します
前述したように、私が提案する代替案はDuckDBとPolarsです。大まかに言うと、PolarsはRustベースのDataFrameライブラリで、Pandasに似ていますが、これから探求するいくつかの重要な点で異なります。DuckDBはインメモリ分析DBであり、本質的には分析用のSQLiteです。これらのツールとPandasとの違いを把握するために、例を見てみましょう。
10億行チャレンジは、10億行のCSV(気象観測所のデータを含む)の最小値、平均値、最大値を計算する最速のJavaプログラムを書くというチャレンジでした。コンペティションで受理された最速の実装は1.5秒で実行されました。元のチャレンジでは、32コアと128GBのRAMを搭載したDebian 12を実行するHetzner AX161ベアメタルサーバーが使用されました。著者はシリアルな先延ばし屋であり、ケチであり、Hetznerでは大きなボックスを借りるために「評判」を築く必要があるため、代わりにAWSのm7a.8xlargeがこれらのテストに使用されました。これもDebian 12を実行していました。この基本的な構成は、元のチャレンジと同じです。AMD CPU上で32コアと128GBのRAMです。ただし、ベアメタル専用ハードウェアを使用しないと、再現性にいくらか影響が出る可能性があります(申し訳ありません)。
黙ってコードを見せてください
早速ですが、いくつかの実装を見てみましょう。
Pandas
Pandasに触れたことがある人なら誰でも非常に馴染み深いでしょう。CSVからデータを読み込み、気象観測所ごとにグループ化し、集計された最小値、平均値、最大値を計算します。
出力シリアライゼーションはどこ?
パフォーマンステストでは、元のチャレンジで指定された出力シリアライゼーションはスキップされます。
実装にはすべて出力シリアライズ機能が含まれており、これは実装の単体テスト(例: Pandasコード)に使用されました。
10億行チャレンジの出力形式は標準的ではないため、シリアライゼーションをテストすることは、さまざまなライブラリの関連するテストとは感じられませんでした。
```python
def do_1brc_pandas(file_path: str):
df = (
pd.read_csv(file_path, sep=";", names=["station", "measurement"])
.groupby("station")
.agg({"measurement": ["min", "mean", "max"]})
.round(2)
)
```
この例で覚えておくべき重要な部分は、Pandasがこの計算の各ステップを逐次的に、かつ即座に実行することです。データセット全体を読み込み、グループ化し、集計を実行します。
Polars
PolarsのコードはPandasに似ていますが、実行時には非常に異なる動作をします。これから見ていきましょう。
```python
def do_1brc_polars(file_path: str):
df = (
pl.scan_csv(
file_path,
separator=";",
new_columns=["station", "measurement"],
has_header=False,
)
.group_by("station")
.agg(
pl.col("measurement").min().round(2).alias("min"),
pl.col("measurement").mean().round(2).alias("mean"),
pl.col("measurement").max().round(2).alias("max"),
)
.collect(new_streaming=True) # Stream the input data and perform computations in chunks
)
```
データはチャンクごとにスキャンされ、グループ化され、集計されます。ここで重要なのは、scan_csvは遅延評価され、.collectの呼び出しがクエリパイプラインを実行することです。これはデータベース用語のように聞こえるかもしれませんが、そうであるべきです。この遅延評価により、Polarsはデータベースと同様に最適化されたクエリグラフを構築し、40年分のデータベース最適化を活用して、データをチャンク単位で読み込み、必要に応じてスレッド間で作業を並列化することができます。データベースと同様に、.collectの呼び出しを.explain(streaming=True)の呼び出しに置き換えることで、最適化されていないクエリプランを、.explain(streaming=True, optimized=False)の呼び出しで可視化できます。
最適化されたクエリプラン
このクエリプランはそれほどエキサイティングではありません。CSVをスキャンし、2列の射影を行い、集計します。フィルタリングを含むクエリでは、述語プッシュダウンが適用されると予想されます。これは、集計が発生する前に行がフィルタリングされることを意味します。Pandasとは異なり、すべての行がメモリにロードされてからフィルタリングされます。
AGGREGATE [col("measurement").min().round().alias("min"), col("measurement").mean().round().alias("mean"), col("measurement").max