- Published on
Polars 1.x vs Pandas — Pandas 時代の終わりか? Rust・Arrow・lazy 時代の dataframe 深掘り 2026
- Authors

- Name
- Youngju Kim
- @fjvbn20031
プロローグ —「Pandas の時代が終わった」という言葉の正確な意味
2025〜2026 年に dataframe コミュニティで最も聞いた一文はこれだ。
「Polars に乗り換えたら、同じコードが 10 倍速くなった」
そして必ずもう一文が並ぶ。
「でも sklearn に流す直前で結局
to_pandas()を呼んでる」
この 2 文が 2026 年の dataframe 世界を正確に要約している。Polars は新規ワークロードのほぼ全部を持っていきつつある。 それでも Pandas は消えない。 15 年分の ecosystem inertia — sklearn 入力、statsmodels、可視化ライブラリ、何十万件もの Stack Overflow 回答、すべての授業資料 — が Pandas を「安全資産」にしている。
この記事はその均衡点を正確に見る。Polars 1.x の stability の約束、Rust + Arrow アーキテクチャ、lazy evaluation と query optimizer が実際に何をするのか、Pandas 2.2 + PyArrow backend がどこまで追いつき、どこで止まったか、DuckDB が同じ Arrow-native query engine としてどこで Polars に勝ち、どこで負けるか、Dask・Ibis の位置、移行の本当のコスト。そして最後に — それでも Pandas を離れるべきでない場合。
1 章 · Arrow が下に敷かれている — dataframe revolution の本当の主役
Polars vs Pandas 比較で最もよく抜け落ちる点: 両者とも Apache Arrow の上に立っている。 本当の革命は Polars ではない。Arrow だ。
なぜ Arrow が重要なのか
Arrow は columnar in-memory フォーマットだ。NumPy も columnar だが、違いは大きい。
- 言語中立なメモリレイアウト — Python・Rust・C++・Java・Go が同じメモリを zero-copy で共有できる。
- column-major — 同じ column の値がメモリ上で連続。SIMD に最適。
- 型システム内蔵 — string、list、struct、dictionary(category)、date / time / decimal まで標準で定義。
- null bitmap が独立 —
NaNを null として乱用していた NumPy / Pandas 1.x の宿痾が消える。 - chunked array — 巨大な column を chunk に分け、lazy / streaming の土台となる。
要点はこれだ。
Arrow の上では、Polars・Pandas・DuckDB・Spark が同じメモリを見る。変換コストはほぼゼロに収束する。
df.to_arrow() → DuckDB がそのまま読む。DuckDB が書いた Arrow → Polars がそのまま受け取る。zero-copy。これが 2020 年代に dataframe 世界が収束している理由だ。
Arrow と NumPy backend の実用差
| 項目 | NumPy backend (Pandas 1.x) | Arrow backend (Polars / Pandas 2.2+) |
|---|---|---|
| 文字列 | object dtype、Python オブジェクトの list | string dtype、連続メモリ、5〜10 倍速 |
| null | NaN 強制、int column が float に昇格 | 正式な null bitmap、dtype を保つ |
| カテゴリ | category dtype、独自実装 | 標準の dictionary 型 |
| 時系列 | datetime64[ns] 固定 | timestamp[us, tz] などリッチ |
| zero-copy 共有 | 難しい | DuckDB・Polars・Spark と即座に |
| SIMD 活用 | 限定的 | columnar 構造に自然にハマる |
この表が、Polars が Pandas を圧倒するほぼ全ての理由だ。Polars は最初からここで設計し、Pandas は 2.x の opt-in backend で追いついている。
2 章 · Polars 1.x — Rust コアと安定性の約束
Polars は Ritchie Vink が 2020 年に始めた Rust の dataframe ライブラリだ。2024 年 8 月に 1.0 がリリースされ、その後 minor を高速に上げながら public API の stability を約束している。2026 年現在は 1.x のかなり後ろまで進んでおり(リリースノートは Polars GitHub Releases)、破壊的変更は新 API の opt-in として入り、既存 API は deprecation サイクルを経る。
Rust コア + Python binding
- コアは Rust —
polarscrate。memory safety、SIMD、no-GIL の並列。 - Python は binding —
py-polars。Rust コアを PyO3 で包む。 - R、NodeJS binding も同じコアの上。
意味するところ: Python の GIL は Polars の足を引っ張らない。group-by が本当に全コアを使う。NumPy も BLAS で並列だが、dataframe 操作そのもの(group-by、join)は Pandas では GIL に縛られる。
Polars のデータモデル
import polars as pl
df = pl.DataFrame({
"user_id": [1, 2, 3, 4],
"country": ["KR", "US", "KR", "JP"],
"amount": [120.0, 95.0, 200.0, 75.0],
})
print(df.schema)
# Schema({'user_id': Int64, 'country': String, 'amount': Float64})
schema は column 名から Arrow dtype への OrderedDict。すべての dtype が明示的で、推論の魔術が少ない。
Eager と Lazy
Polars には 2 つのモードがある。
- eager —
pl.DataFrame。Pandas のように即実行。 - lazy —
pl.LazyFrame。式を plan として積み、.collect()で最適化と実行を一括で。
# eager
df = pl.read_parquet("sales.parquet")
big_kr = df.filter(pl.col("country") == "KR").select(["user_id", "amount"])
# lazy — query plan を積んで .collect() で実行
plan = (
pl.scan_parquet("sales.parquet") # I/O を遅延
.filter(pl.col("country") == "KR")
.select(["user_id", "amount"])
)
big_kr = plan.collect()
違いが見えるか? scan_parquet は まだファイルを読んでいない。 次の章でその意味が爆発する。
3 章 · Lazy evaluation と query optimizer — Polars が魔法のように速い理由
Polars の性能の半分は lazy plan を最適化する query optimizer から来る。何をするのか。
3.1 Predicate pushdown — filter を I/O 段階に押し込む
plan = (
pl.scan_parquet("sales.parquet") # 1 億 row
.filter(pl.col("country") == "KR") # KR は 100 万 row
.select(["user_id", "amount"])
)
素朴な実行: 1 億 row 全部を読んでからメモリ上で filter。100 倍高くつく。
optimizer がやること: filter を scan に押し込む。 Parquet の row group 統計(min / max)を見て、country != "KR" な row group をスキップする。ディスク I/O が 100 倍減る。
3.2 Projection pushdown — 必要な column だけ読む
select(["user_id", "amount"]) が末尾にあっても、Parquet は columnar なので その 2 column だけがディスクから読まれる。 Pandas は pd.read_parquet() の時点で全 column を読んでから select するので遅い(columns= 引数はあるが、plan 単位の自動推論ではない)。
3.3 Common subexpression elimination
plan = (
df.lazy()
.with_columns([
(pl.col("price") * pl.col("qty")).alias("revenue"),
(pl.col("price") * pl.col("qty") * 0.1).alias("commission"),
])
)
price * qty が 2 回出ている。optimizer は 1 度だけ計算して使い回す。
3.4 Slice pushdown、型変換、dead column 除去
.head(100) が plan の末尾にあれば、その 100 行を満たすぶんだけ上流から引き上げる。join の後で使わない column は join の前に消す。暗黙の cast を減らす。
3.5 streaming engine
collect(streaming=True)(2026 年現在、新しい streaming engine が stable に近づきつつある — Polars blog の streaming 関連記事を参照)はメモリに収まらないデータセットを chunk 単位で処理する。out-of-core を一つのライブラリ内で完結させる。
plan の可視化
plan.explain() # 人間可読 plan
plan.show_graph() # graphviz。最適化前と後を比較できる
これが Polars の脳だ。Pandas にはこれがない。命令を受けたらそのまま実行する。
4 章 · Polars expression API —「Series 1 個 = 式 1 個」の思考
Polars で最も大きい mental model の変化は expression だ。
import polars as pl
df.select([
pl.col("amount").sum().alias("total"),
pl.col("amount").mean().over("country").alias("country_avg"),
(pl.col("amount") - pl.col("amount").mean()).alias("diff_from_mean"),
])
各 expression は column 変換の式(DAG) だ。Pandas の column 操作が imperative なら、Polars の expression は declarative — Spark DataFrame や SQL に近い。
Pandas との思考差:
| 操作 | Pandas | Polars |
|---|---|---|
| 新しい column を作る | df["x"] = df["a"] + df["b"](代入) | df.with_columns((pl.col("a") + pl.col("b")).alias("x"))(式) |
| group-by 集計 | df.groupby("k")["x"].sum()(連鎖) | df.group_by("k").agg(pl.col("x").sum())(expression のリスト) |
| window | df.groupby("k")["x"].transform("mean") | pl.col("x").mean().over("k") |
| 複数 column 同時変換 | for loop か apply | expression list で一気に |
最初は違和感があるが、数日で慣れる。そして expression が plan optimizer のノードであることが重要だ。式を declarative に書くからこそ Polars は最適化できる。
5 章 · Pandas 2.2 — PyArrow backend でどこまで追いついたか
Pandas は 2.0(2023)で PyArrow backend を導入し、2.2(2024)で安定化させた。2026 年現在 Pandas 2.2.x が事実上の標準で、3.0 は長い beta 中。
Arrow backend のオン
import pandas as pd
# 方法 A — Arrow dtype を明示
df = pd.read_parquet("sales.parquet", dtype_backend="pyarrow")
# 方法 B — Series の dtype を Arrow に
s = pd.Series([1, 2, None], dtype="int64[pyarrow]")
これでよくなること:
- 文字列 column が本当に速くなり、メモリも半分以上削れる。
- int column に null があっても float に昇格しない(
Int64[pyarrow]は nullable)。 - date / time が Arrow の timestamp のまま。
- DuckDB・Polars・Spark と zero-copy。
それでも Pandas 2.2 ができないこと
- lazy evaluation がない。 全演算が eager。predicate pushdown は
read_parquet(filters=)だけ部分対応。 - query optimizer がない。 書かれた順に実行する。
- デフォルトで GIL 内の single thread。
numba/numexpr/pyarrow.computeをケースごとに呼び分けて並列を得る。 - expression API がない。 group-by 直後の agg で multi-column 式を綺麗に書けない。
- API 表面が巨大。 Pandas 自体が巨大な dictionary で、暗黙の魔法が多い。
要約: データ構造は Arrow に来たが、実行エンジンは依然として 1990 年代式の imperative だ。 だから同じ Arrow の上でも Polars が速い。
6 章 · Polars vs Pandas — 本格比較マトリクス
同じ作業を 2 つのライブラリで並べる。
6.1 Parquet 読込・filter・group-by
Pandas:
import pandas as pd
df = pd.read_parquet("sales.parquet", dtype_backend="pyarrow")
kr = df[df["country"] == "KR"]
result = (
kr.groupby("user_id", as_index=False)["amount"]
.agg(["sum", "mean", "count"])
)
Polars(lazy):
import polars as pl
result = (
pl.scan_parquet("sales.parquet")
.filter(pl.col("country") == "KR")
.group_by("user_id")
.agg([
pl.col("amount").sum().alias("total"),
pl.col("amount").mean().alias("avg"),
pl.col("amount").count().alias("n"),
])
.collect()
)
コード量は似ているが、plan 段階で起きることが違う。Polars は filter を scan に押し込み、amount・user_id・country の 3 column しかディスクから読まない。
6.2 join
Pandas:
merged = pd.merge(orders, users, on="user_id", how="left")
Polars:
merged = orders.lazy().join(users.lazy(), on="user_id", how="left").collect()
Polars の join はデフォルトで parallel hash join。データが大きくなるほど差が広がる。
6.3 window 関数
Pandas:
df["avg_country"] = df.groupby("country")["amount"].transform("mean")
df["rank_in_country"] = df.groupby("country")["amount"].rank(method="dense")
Polars:
df = df.with_columns([
pl.col("amount").mean().over("country").alias("avg_country"),
pl.col("amount").rank(method="dense").over("country").alias("rank_in_country"),
])
複数の window を 1 つの plan にまとめると、Polars は同じ partition を 1 度しか計算しない。
6.4 比較マトリクス
| 項目 | Pandas 2.2 + Arrow | Polars 1.x |
|---|---|---|
| メモリバックエンド | NumPy 既定 / Arrow オプション | Arrow 専用 |
| 文字列性能 | Arrow を有効化すれば良い | 最初から速い |
| null handling | Arrow dtype なら正常 | 標準 null bitmap |
| マルチコア | numba・numexpr 呼出し時に部分的 | 既定で並列、GIL に縛られない |
| lazy plan | なし | LazyFrame |
| query optimizer | なし | predicate / projection / CSE など |
| streaming | なし | streaming engine(out-of-core) |
| expression API | なし(連鎖中心) | 第一級 |
| 時系列 | 強力で成熟 | 改善中だが一部機能不足 |
| 可視化統合 | seaborn・matplotlib 直結 | to_pandas() が要るケース多 |
| sklearn 互換 | 入力としてそのまま | to_pandas() か to_numpy() が必要 |
| 学習資料 | 圧倒的に多い | 急速に増えているが少ない |
性能だけ見れば Polars の圧勝。ecosystem 統合・資料・legacy を加えると Pandas が依然安全。
7 章 · TPC-H ベンチマーク — 実際の数字
Polars チームは TPC-H の 22 query を自前で全部回し結果を公開している(原典は Polars ベンチマークページ。具体的な値は SF・ハードウェア・バージョンで変動するので、下表は形だけの「意思決定の出発点」として扱い、結論は原典の最新表を確認してほしい)。
| Query | Pandas 2.x | Polars 1.x (lazy) | DuckDB | Dask |
|---|---|---|---|---|
| Q1(単純 group-by) | 基準 | おおよそ 1/10 程度 | おおよそ 1/15 | おおよそ 1/3 |
| Q3(join + filter) | 基準 | おおよそ 1/8 | おおよそ 1/10 | おおよそ 1/2 |
| Q9(multi-join、複雑) | 基準 | おおよそ 1/7 | おおよそ 1/8 | 同等〜やや速 |
| Q21(correlated subquery) | しばしば OOM | 普通に動く | 非常に速い | OOM の可能性 |
| メモリに収まらない dataset | 不可 | streaming engine で可 | 可 | 分散で可 |
主な観察:
- Polars と DuckDB は single-node で同じリーグ — query optimizer + Arrow + multi-core。Pandas はそこから一段下。
- 分散が必要な規模なら Dask / Spark / Ray。Polars は単一ノード(または cloud の大型 VM)最適化に集中。
- TPC-H のような SQL スタイルの分析では DuckDB が首位になることが多い。dataframe API のままなら Polars。
ベンチマークは「どんなワークロードか」で全部決まる。上の表は意思決定の出発点で、結論ではない。
8 章 · Polars vs DuckDB — 同じ engine の 2 つの顔
この比較が 2026 年で一番面白い。両方とも Arrow-native、両方とも vectorized columnar engine、両方とも single-node で大きいデータを薙ぎ倒す。違うのはインターフェースだ。
- DuckDB — embeddable SQL engine。
duckdb.sql("SELECT ... FROM parquet_scan('sales.parquet') WHERE ...")。重い分析 SQL、OLAP に強い。window 関数・複雑な join・subquery は最強クラス。 - Polars — dataframe API。expression ベースの declarative な dataframe。
# 同じ仕事、2 つのインターフェース
# DuckDB
import duckdb
result = duckdb.sql("""
SELECT user_id, SUM(amount) AS total
FROM 'sales.parquet'
WHERE country = 'KR'
GROUP BY user_id
""").to_df()
# Polars
import polars as pl
result = (
pl.scan_parquet("sales.parquet")
.filter(pl.col("country") == "KR")
.group_by("user_id")
.agg(pl.col("amount").sum().alias("total"))
.collect()
)
DuckDB が勝つ場面
- 複雑な multi-table join、深い CTE、correlated subquery — SQL の方が自然。
- アナリストがすでに SQL を書いている — 認知コストがゼロ。
- BI ツール・Jupyter Magic・dbt 統合 — ほぼ全部の BI ツールが DuckDB を読める。
Polars が勝つ場面
- column 単位の変換パイプラインが長い — expression は CTE よりきれい。
- 既存コードが Pandas — 移行表面が小さい。
- ML feature engineering のように、Python の中で dataframe を手で握りたい。
- ユーザー定義 Python 関数が挟まる(
map_elements、pipe)。
両方使うパターン
# Polars で受け、重い SQL は DuckDB に渡し、結果を Polars に戻す
import duckdb, polars as pl
lf = pl.scan_parquet("sales.parquet").filter(pl.col("amount") > 100)
out = duckdb.sql("SELECT country, percentile_cont(0.95) WITHIN GROUP (ORDER BY amount) AS p95 FROM lf GROUP BY country").pl()
Arrow の上では両者が zero-copy で行き来する。「どちらか一つ」という強迫観念を捨てよう。
9 章 · Dask と Ibis — 脇役たちの位置
Dask — out-of-core・分散が必要なとき
Dask は Pandas API を持つ分散 dataframe。partition 単位で Pandas DataFrame を立て、lazy graph で束ねて cluster で実行する。
import dask.dataframe as dd
df = dd.read_parquet("s3://bucket/sales-*.parquet")
result = df[df.country == "KR"].groupby("user_id").amount.sum().compute()
Dask は「Pandas の分散版」というアイデンティティを保つ。2026 年現在の Dask 2024.x 以降は query optimizer を段階的に導入(Coiled が主導)し、Pandas 2.x の PyArrow dtype と相性がよい。ただし単一ノード性能では Polars / DuckDB に負ける — そこを狙っていないからだ。数十 TB が S3 に散らばっているケースが真の用途。
Ibis — backend-agnostic な dataframe spec
Ibis は dataframe 式を書き、それを DuckDB・BigQuery・Snowflake・Polars・Pandas・Spark など 20 以上の backend にコンパイルするライブラリだ。
import ibis
t = ibis.read_parquet("sales.parquet") # 既定の backend は DuckDB
expr = t.filter(t.country == "KR").group_by("user_id").agg(t.amount.sum().name("total"))
expr.execute() # Pandas で返る
同じ query を DuckDB で回しても、BigQuery に投げても、Snowflake に流しても — コードは変えない。data warehouse に重い処理を投げて、ローカルで同じコードをテストする。
2026 年の Ibis のポジション:
- 「dataframe interface 標準」候補。PyData ecosystem がここに集まりつつある雰囲気。
- アナリストチームが cloud warehouse を使っているなら強い選択肢。
- ただし backend ごとの quirks が漏れることがある — 式が全 backend で同じに動くわけではない。
10 章 · 移行の罠 — Pandas → Polars の本当のコスト
性能だけ見ると Polars に行くのは自明に見える。しかし実際の移行で人が躓くポイントがある。
10.1 index がない
Pandas のすべてが index 中心なら、Polars には index がない。 .set_index()・.reset_index()・.loc[]・MultiIndex がすべて消える。join は明示的なキー column、ソートは明示的に。最初は不便だが、結果として バグが減る — 暗黙の index alignment による silent な data corruption が消える。
10.2 inplace がない
# Pandas
df["x"] = df["a"] + df["b"] # 動く
df.rename(columns={"a": "A"}, inplace=True)
# Polars
df = df.with_columns((pl.col("a") + pl.col("b")).alias("x"))
df = df.rename({"a": "A"})
すべての演算が新しい DataFrame を返す。immutable な思考に矯正される。
10.3 apply の死(という意味)
# Pandas — apply は定型句のように多い
df["y"] = df.apply(lambda r: complicated_func(r["a"], r["b"]), axis=1)
Polars では map_elements は 明示的に遅い道。可能なら expression を合成する。どうしても必要なら map_batches で batch 単位の vectorized 関数を使う。
10.4 group-by の戻り値の形
Pandas の group-by が Series か DataFrame か曖昧な場面はすべて Polars では DataFrame に統一される。ただし column 名規則が違って KeyError になる場合がある — Polars は明示的に .alias() を要求する。
10.5 datetime が違う
Pandas の datetime64[ns] は nanosecond 固定。Polars(Arrow)は Datetime("us", "UTC") のような形。tz の比較が厳格になる。mixed-tz の column を放置すると Polars は怒る。最初は煩わしく、後に祝福となる。
10.6 csv の型推論差
read_csv の dtype 推論が違う。Pandas は一部だけ見て推測し、Polars はより保守的だ。dtype を明示する習慣をつけよう。
10.7 メソッド名が微妙に違う
reset_index → そもそも無い。concat(axis=1) → pl.concat([..], how="horizontal")。pd.melt → pl.DataFrame.unpivot。merge → join。rename → シグネチャが違う。検索置換だけでは無理で、人が読む必要がある。
10.8 段階的移行戦略
# データ受け入れの段階から Polars
df = pl.read_parquet("a.parquet")
# 重い変換は Polars
df = df.filter(...).group_by(...).agg(...)
# sklearn / seaborn の境界だけで pandas に
pdf = df.to_pandas()
これが最も現実的なパターン。すべてのコードを一度に書き換えないこと。 新規パイプラインから Polars、既存は触らない。
11 章 · それでも Pandas を離れるべきでない場合
この記事で最も正直な章。2026 年でも Pandas に留まる方が正しい場面は確実にある。
11.1 sklearn 入力が終点のとき
sklearn は pandas DataFrame を第一級として扱う。ColumnTransformer、OneHotEncoder(sparse_output=False)、pipeline のすべての段階が column 名を保持したまま流れる。Polars に行っても結局 .to_pandas() の直前で止まるなら、行く理由は薄い。
11.2 statsmodels・因果推論ライブラリ
統計系ライブラリ(statsmodels、lifelines、dowhy、econml)はほとんど Pandas を前提にする。式表記(y ~ x1 + x2)が column 名に縛られる。
11.3 可視化統合
seaborn、plotnine、多くの plotly の例が Pandas を期待する。Polars も __dataframe__ プロトコル対応で直接受けるライブラリが増えているが、最も滑らかなのは依然 Pandas。
11.4 小さいデータと素早い prototyping
100 万行未満なら性能差は数十〜数百 ms — 意味がない。手に馴染んだライブラリが勝つ。
11.5 チームが Pandas しか知らず、移行コスト > 効果
ボトルネックが dataframe ではなく別の場所(ネットワーク I/O、モデル推論)なら、Polars に行っても end-to-end は変わらない。移行は純粋なコスト。
11.6 教育
15 年分の講義資料・Stack Overflow・書籍が Pandas ベース。学習資料の量がそのまま学習コストを決める。
Polars は 新規パイプラインの既定値になりつつあり、Pandas は glue 言語として生き残る。両方知るのが合理的だ。
12 章 · 2026 年の dataframe stack — 決定マップ
同じ表を別角度で — 「このワークロードなら何を使うか」視点。
| ワークロード | 第 1 候補 | 第 2 候補 |
|---|---|---|
| メモリに収まる分析 dataframe、Python の中で | Polars | DuckDB |
| 重い SQL 分析、BI / Jupyter | DuckDB | Polars |
| TB 級・S3 に散らばるデータ | Dask / Spark | Ray Data |
| Snowflake / BigQuery に push down | Ibis | (warehouse 固有 SDK) |
| ML feature engineering | Polars | Pandas(+Arrow) |
| sklearn モデル入力 | Pandas | Polars → to_pandas() |
| 時系列分析(statsmodels) | Pandas | (単純なら Polars) |
| 小データ・素早い prototyping | Pandas | Polars |
| 標準化された backend-agnostic 式 | Ibis | (独自 wrapper) |
エピローグ — バランスの取れた一文
この記事の一文要約:
Polars は新規ワークロードのほぼ全部を持っていきつつある。Pandas は sklearn・可視化・教育という ecosystem の沼で生き残る。2026 年の正解は両方を知り、ワークロードに合わせて選ぶことだ。
データエンジニアとしての決定木:
- 新規パイプラインで単一ノード分析なら → Polars を既定。
- SQL の方が自然でアナリストと共有するなら → DuckDB。
- データが 1 ノードに収まらないなら → Dask / Spark / Ray。
- cloud warehouse が真の保管庫なら → Ibis + warehouse。
- ML 終点が sklearn なら → 変換は Polars、最後に
to_pandas()。 - 可視化や統計が最後の段階なら → Pandas に受け渡す。
12 項目チェックリスト
- dataframe バックエンドは Arrow か(Pandas は
dtype_backend="pyarrow")? - I/O は Parquet か?(CSV に縛られると、どのライブラリでも損)
- lazy plan を使っているか?(Polars の最大の利点)
- filter / select を plan の上部に置いているか?(predicate / projection pushdown)
- group-by 内の式を expression list にまとめているか?
- 同じ式が 2 回出ていないか確認したか?(CSE の対象)
- join キーの dtype が両側で一致しているか?
- timestamp の tz は明示されているか?
map_elementsを乱用していないか?- streaming engine が必要なデータ規模か測定したか?
- 可視化や ML に渡す前に
to_pandas()を 1 度にまとめたか? - ベンチマークを自分の環境と自分のデータで再実行したか?
10 アンチパターン
- Polars で受けた直後に
to_pandas()して Pandas コードを続行 — 効果ゼロ。 - lazy plan を作らず毎段
collect()— 最適化機会を全部捨てる。 - vectorized expression に書き換えられる
map_elementsを放置。 - Parquet ではなく CSV にすべて保存 — Arrow の利点が半減。
- Pandas の
inplace=True思考を Polars に持ち込む — 通じない。 - tz を無視した datetime 比較 — Polars が怒って初めて気づく。
- 分散の不要なデータに Dask / Spark を先んじて導入 — 複雑度爆発、性能劣化。
- SQL が自然な分析を dataframe API に無理やり押し込む。
- ベンチマーク表を自データ・自環境で再現せずそのまま引用 — 意思決定の根拠が弱い。
- 「Pandas はすぐ消える」前提で急な全面移行 — コストだけが残る。
次回予告
候補: DuckDB 深掘り — embedded analytical engine のすべて、Apache Arrow — Flight・DataFusion・ADBC まで、PySpark 4.x と Polars / Ray Data — 2026 年の分散 dataframe マップ、Pandas → Polars 実戦移行ケーススタディ。
「engine は同じ — Arrow だ。その上に座る dataframe API が違うだけ。2026 年のデータエンジニアは、一つのライブラリの信者ではなく、その上に積まれた stack を読む人だ。」
— Polars 1.x vs Pandas、終わり。
参考 / References
- Polars 公式サイト
- Polars GitHub リポジトリ
- Polars GitHub Releases(1.x ノート)
- Polars User Guide
- Polars Python API リファレンス
- Polars ベンチマークページ
- Polars ブログ(streaming など)
- Pandas 公式ドキュメント
- Pandas 2.2 What's New
- Pandas PyArrow backend ガイド
- Apache Arrow 公式
- Apache Arrow Columnar Format 仕様
- DuckDB 公式
- DuckDB Python API
- DuckDB Polars 連携
- TPC-H 仕様
- Dask 公式
- Dask DataFrame Query Optimizer の案内
- Ibis 公式
- Ibis バックエンド一覧
- PyO3(Rust to Python)
- Apache Spark 4.x リリース
- Modin 公式(Pandas 互換の分散オプション)
- Ray Data
- scikit-learn 入力型の案内
- DataFrame Interchange Protocol
- Awkward Array(Arrow 連携の参考)
- DataFusion(Arrow + Rust の query engine)