
Pythonでマルチギガバイトのデータ変換にまだPandasを使っているなら、あなたは「Pandas税」を大量に支払っています。それは、シングルスレッドのCPU実行、積極的なインメモリコピー、そしてOutOfMemoryErrorをスローする前に生データセットサイズの5倍から10倍のRAMを必要とするメモリ肥大化です。
現代のデータパイプラインでは、高性能なシングルノード分析のための標準ツールキットとして、Pandasに代わってPolarsとDuckDBが使われています。どちらもカラムナー(列指向)ストレージ、ベクトル化されたSIMD実行、Apache Arrowを活用し、物理RAMよりも大きなデータセットを処理しながら10倍から50倍の高速化を実現しています。
ここでは、それらがどのように機能し、どのように比較され、本番環境でどのように連携して使用するかを説明します。
Dataset on Disk (Parquet/CSV): 2.0 GB
Pandas in RAM: 12.0 GB - 18.0 GB (Peak during merge/groupby)
Polars in RAM: 2.3 GB (Zero-copy Arrow + streaming)
DuckDB in RAM: 0.8 GB (Chunked vectorized execution engine)
Pandasは2008年にNumPyの1D配列をベースに設計されました。これには3つの根本的なボトルネックがあります。
NaNを含む整数列をfloat64にキャストし、メモリ使用量を2倍にしていました。PolarsはRustでゼロから書かれており、Apache Arrowの列指向メモリフォーマット上に直接構築されています。PythonのGIL制限なしに、利用可能なすべてのCPUコアを使用してデータを処理します。
本番環境では、ほとんどの場合、PolarsのLazy API(LazyFrame)を使用すべきです。Polarsは計算をすぐに実行する代わりに、論理プランを構築し、それを最適化(述語プッシュダウン、射影プッシュダウン、スライスプッシュダウン)し、並行して実行します。
import polars as pl
# Construct lazy query plan - zero disk I/O occurs here
lazy_query = (
pl.scan_parquet("s3://analytics-bucket/events/*.parquet")
.filter(pl.col("timestamp") >= pl.date(2026, 1, 1))
.filter(pl.col("event_type").is_in(["purchase", "subscription"]))
.with_columns([
(pl.col("amount_cents") / 100.0).alias("amount_usd"),
pl.col("user_id").n_unique().over("country").alias("unique_users_per_country")
])
.group_by(["country", "event_type"])
.agg([
pl.col("amount_usd").sum().alias("total_revenue"),
pl.col("amount_usd").mean().alias("avg_order_value"),
pl.len().alias("transaction_count")
])
.sort("total_revenue", descending=True)
)
# Inspect the optimized query plan
print(lazy_query.explain())
# Execute optimized plan in parallel across all CPU cores
result_df = lazy_query.collect(streaming=True)
print(result_df)
フィルター付きで.scan_parquet()を呼び出すと、次のようになります。
timestamp >= 2026-01-01に一致しない行グループ全体をディスクからデータを読み取ることなくスキップします。timestamp、event_type、amount_cents、country)のみを読み取り、ファイル内の残りの50列は無視します。streaming=True): データをストリーミングマイクロバッチで処理するため、マシンの物理RAMを超えるデータセットでも変換が可能です。PolarsがデータフレームAPIを提供する一方で、DuckDBは組み込みのインプロセスSQL OLAPデータベースです。外部サーバーの依存関係なし、ネットワーク遅延なし、ネイティブSQL方言サポートでPythonプロセス内で実行されます。
DuckDBは、ディスク上またはリモートS3にある圧縮されたParquet、CSV、またはJSONファイルに対して、最初にデータベーステーブルにロードすることなく、SQLクエリを直接実行できます。
import duckdb
# Connect to in-process DuckDB instance (or persist to 'analytics.duckdb')
con = duckdb.connect()
# Enable S3 / HTTP filesystem extension
con.execute("INSTALL httpfs; LOAD httpfs;")
con.execute("""
SET s3_region='us-east-1';
SET s3_access_key_id='YOUR_KEY';
SET s3_secret_access_key='YOUR_SECRET';
""")
# Query 100GB of remote Parquet files using Vectorized SQL
query = """
SELECT
country,
event_type,
COUNT(DISTINCT user_id) AS unique_users,
ROUND(SUM(amount_cents) / 100.0, 2) AS total_revenue_usd,
ROUND(AVG(amount_cents) / 100.0, 2) AS aov_usd
FROM read_parquet('s3://analytics-bucket/events/year=2026/*/*.parquet')
WHERE event_type IN ('purchase', 'subscription')
GROUP BY country, event_type
HAVING total_revenue_usd > 10000
ORDER BY total_revenue_usd DESC
LIMIT 20;
"""
# Execute and fetch directly to Arrow, Polars, or Python dictionaries
results = con.execute(query).pl() # Returns native Polars DataFrame
print(results)
PolarsとDuckDBはどちらもメモリ表現にApache Arrowを使用しているため、メモリのシリアライズやコピーのオーバーヘッドなしでデータを相互に渡すことができます。
import polars as pl
import duckdb
# 1. Load and clean data with Polars
df = pl.DataFrame({
"user_id": [101, 102, 103, 104],
"scores": [88.5, 92.0, 79.5, 95.0],
"tier": ["gold", "platinum", "gold", "platinum"]
})
# 2. Run complex analytical window SQL in DuckDB directly on the Polars DataFrame
con = duckdb.connect()
# DuckDB can reference the 'df' Python variable directly in the FROM clause!
sql_result = con.execute("""
SELECT
user_id,
scores,
tier,
RANK() OVER (PARTITION BY tier ORDER BY scores DESC) as rank_in_tier,
AVG(scores) OVER (PARTITION BY tier) as tier_avg
FROM df
""").arrow() # Zero-copy Arrow Table
# 3. Convert back to Polars instantaneously
final_df = pl.from_arrow(sql_result)
print(final_df)
8コア、16GB RAMの開発者向けラップトップで1000万行のデータセットを集計およびフィルタリングした結果です。
| 操作 | Pandas 2.2 | Polars (Eager) | Polars (Lazy) | DuckDB (SQL) |
|---|---|---|---|---|
| Parquet読み込み + フィルタリング | 4.82秒 | 0.82秒 | 0.29秒 | 0.31秒 |
| グループ化 + 4つの集計 | 3.15秒 | 0.41秒 | 0.28秒 | 0.24秒 |
| ウィンドウ関数 | 2.40秒 | 0.35秒 | 0.26秒 | 0.21秒 |
| ピークRAM使用量 | 約4.6 GB | 約1.4 GB | 約0.7 GB | 約0.4 GB |
Dataset Scale & Use Case
│
┌─────────────────────────────────┴─────────────────────────────────┐
▼ ▼
Single Machine (< 500GB) Distributed Cluster (> 1TB)
│ │
┌────┴──────────────────────────┐ ┌─────────┴─────────┐
▼ ▼ ▼ ▼
SQL-Heavy / S3 Parquet DataFrame Transformations Batch Pipeline Real-time OLAP
▼ ▼ ▼ ▼
DuckDB Polars Apache Spark ClickHouse
(Embedded SQL) (Rust Multi-thread) / Ray / StarRocks
データエンジニアが16GBのRAMを搭載したマシンで50GBのParquetデータセットを処理しています。彼らは各'product_category'の'sales_amount'の合計を計算し、その後、合計売上が1,000,000ドルを超えるカテゴリをフィルタリングする必要があります。`OutOfMemoryError`を回避し、効率的な実行を保証するために最も適切なPolarsのアプローチは何ですか?
AI CLIログの整形、Cron式の作成、JWTの解析、chmod権限の計算をオフラインで実行。

2026年のカラム型データについて、Apache Arrowメモリレイアウト、Parquetストレージ、DuckDBベクタライゼーションを、本番環境レベルのアーキテクチャとコード例で網羅的に解説する総合ガイドです。
続きを読む
FastAPIのBackgroundTasksとCeleryのアーキテクチャ上のトレードオフ、イベントループブロッキングのリスク、Redisメッセージブローカー、メモリベンチマーク、そして切り替え時期を解説。
続きを読む
モダンWebアプリにおけるduckdb-wasmの包括的なガイド。クライアントサイドOLAP、Parquetストリーミング、そして本番環境レベルのアーキテクチャとコード例による超高速ダッシュボードについて解説します。
続きを読む