duckdb について
DuckDB は、Python のデータサイエンス向けインプロセス分析 SQL エンジンです。大規模なローカルデータを扱う分析者や研究者に適し、CSV、Parquet、Python のデータフレームを直接処理し、メモリまたはファイルに保存できます。
主な機能
- 01インプロセス SQL 分析実行
- 02CSV と Parquet の直接クエリ
- 03Pandas と Polars データのクエリ
- 04複数データソースの結合
- 05ウィンドウ関数と集計
- 06メモリまたはファイルでの保存
活用シーン
- 01大規模な CSV や Parquet ファイルの分析
- 02ファイルデータと Pandas DataFrame の結合
- 03SQL によるデータ整形と集計の構築
- 04機械学習向け特徴量の生成
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ tondevrel/ scientific-agent-skills --skill duckdbインストール後、Claude Code で/duckdbを入力して読み込まれたか確認します。
詳細
DuckDB — 科学データのための SQL エンジン
DuckDB は、Python のデータサイエンススタックにプロフェッショナルな SQL の力をもたらします。「オンライン分析処理」(OLAP)向けに最適化されており、メモリよりも大きなデータセットに対する大規模な集計、結合、複雑なクエリを得意とします。
使用する場面
- Pandas または Polars のデータに対して、複雑な SQL クエリ(JOIN、ウィンドウ関数)を実行する場合。
- 大きな Parquet ファイルや CSV ファイルをメモリに読み込まずに直接クエリする場合。
- 異なるソース(例:CSV ファイルと Pandas DataFrame)のデータを効率的に結合する場合。
- SQL のほうが DataFrame のコードより簡潔または高速な分析パイプラインを構築する場合。
- Excel では扱いきれないものの、PostgreSQL サーバーを丸ごと用意する必要はないローカルデータセットを管理する場合。
- 機械学習のための中間データ保存や特徴量エンジニアリングを行う場合。
リファレンスドキュメント
公式ドキュメント: https://duckdb.org/docs/
Python API: https://duckdb.org/docs/api/python/overview
検索パターン: duckdb.sql、duckdb.query、duckdb.read_parquet、duckdb.from_df
基本原則
プロセス内実行
DuckDB は Python プロセス内で動作します。起動や管理が必要なサーバーはありません。データはファイル(.db)に保存することも、すべてメモリ内に保持することもできます。
列指向エンジン
Polars と同様に、DuckDB は列指向ストレージとベクトル化実行エンジンを使用しており、分析タスクでは行ベースのデータベース(SQLite など)より桁違いに高速です。
シームレスな相互運用性
DuckDB は Python の変数を「認識」できます。データベース内のテーブルであるかのように、Pandas DataFrame の変数に対して SQL クエリを直接実行できます。
クイックリファレンス
インストール
pip install duckdb
標準インポート
import duckdb
import pandas as pd
import numpy as np
基本パターン — Python データのクエリ
import duckdb
import pandas as pd
# 1. Create a sample DataFrame
df = pd.DataFrame({"id": [1, 2, 3], "val": [10.5, 20.0, 15.2]})
# 2. Query the DataFrame directly via SQL
# DuckDB automatically finds the 'df' variable in the local scope
result_df = duckdb.sql("SELECT id, val * 2 AS doubled FROM df WHERE val > 12").df()
print(result_df)
重要なルール
✅ 推奨事項
- ファイルを直接クエリする — ファイルを先に読み込むのではなく、
SELECT * FROM 'data.parquet'を使用します。DuckDB は必要な列と行だけを読み込みます。 - .df()、.pl()、.arrow() メソッドを使用する — クエリ結果を好みの形式(Pandas、Polars、Arrow)に効率よく変換できます。
- 大きなデータには永続ストレージを使用する — スクリプトの実行間でデータを保持したい場合は、
duckdb.connect('my_data.db')を使用します。 - Parquet を活用する — DuckDB は Parquet ファイルに最適なエンジンです。最大限の速度を得るために活用しましょう。
- ワイルドカードを使用する —
FROM 'data/*.parquet'を使って、何千ものファイルを一度にクエリできます。 - EXPLAIN を使用する — クエリの前に
EXPLAIN ANALYZEを付けると、DuckDB によるクエリの実行方法を確認し、ボトルネックを特定できます。
❌ 避けるべきこと
- 高頻度の書き込み(OLTP)に使用する — DuckDB は分析向けです。1 秒間に何千回も行を 1 つずつ挿入する必要がある場合は、SQLite または PostgreSQL を使用してください。
- 接続を放置する — ファイルベースのデータベースを使用する場合は、ファイルロックを避けるため、接続を閉じるかコンテキストマネージャーを使用してください。
- 不要な CSV の手動読み込み —
pd.read_csv()で読み込んでからクエリするのは避けてください。パフォーマンスを高めるには、ファイルパスを直接クエリします。
アンチパターン(絶対に避けること)
import duckdb
import pandas as pd
# ❌ BAD: Loading everything into Pandas just to do a simple filter
# df = pd.read_csv("massive.csv")
# result = df[df['val'] > 100]
# ✅ GOOD: Let DuckDB filter while reading (saves RAM)
result = duckdb.sql("SELECT * FROM 'massive.csv' WHERE val > 100").df()
# ❌ BAD: Manual string formatting for SQL queries (SQL Injection risk)
# duckdb.sql(f"SELECT * FROM data WHERE name = '{user_input}'")
# ✅ GOOD: Use prepared statements or parameters
duckdb.execute("SELECT * FROM data WHERE name = ?", [user_input]).df()
# ❌ BAD: Re-reading the same file in a loop
# for i in range(10):
# res = duckdb.sql("SELECT mean(val) FROM 'large.parquet'").df()
# ✅ GOOD: Create a VIEW or TABLE first
duckdb.sql("CREATE VIEW data_view AS SELECT * FROM 'large.parquet'")
for i in range(10):
res = duckdb.sql("SELECT mean(val) FROM data_view").df()
SQL の機能と操作
異なるソースへのクエリ
# Query a CSV
res_csv = duckdb.sql("SELECT * FROM read_csv_auto('data.csv')").df()
# Query a Parquet file
res_pq = duckdb.sql("SELECT * FROM 'data.parquet' WHERE price > 50").df()
# Query multiple Parquet files and join with a Pandas DF
df_metadata = pd.DataFrame(...)
query = """
SELECT p.*, m.category
FROM 'raw_data/*.parquet' p
JOIN df_metadata m ON p.id = m.id
LIMIT 10
"""
res = duckdb.sql(query).df()
リレーショナル API(プログラムによる SQL)
生の SQL より Python らしいメソッドチェーン形式を使いたい場合:
rel = duckdb.from_df(df)
res = rel.filter("val > 15").project("id, val * 2").order("val").limit(5)
print(res.df())
高度な SQL:ウィンドウ関数と集計
DuckDB は最新の SQL を完全にサポートしており、複雑な統計処理では Pandas より簡単な場合がよくあります。
query = """
SELECT
date,
station_id,
temp,
AVG(temp) OVER (PARTITION BY station_id ORDER BY date ROWS BETWEEN 7 PRECEDING AND CURRENT ROW) as rolling_7d_avg,
temp - LAG(temp) OVER (PARTITION BY station_id ORDER BY date) as daily_change
FROM 'weather_data.parquet'
"""
df_stats = duckdb.sql(query).df()
永続データベースの操作
# Create or open a database file
con = duckdb.connect('scientific_project.db')
# Create a table from a dataframe
con.execute("CREATE TABLE experiment_results AS SELECT * FROM df")
# Check tables
print(con.execute("SHOW TABLES").df())
# Close connection
con.close()
パフォーマンスの最適化
1. 外部集計(ディスクへのスピル)
クエリが RAM に収まらない場合、DuckDB は計算を完了するためにデータを「ディスクにスピル」できます。
# Enable temp directory for large queries
duckdb.sql("SET temp_directory='/tmp/duckdb_temp/'")
duckdb.sql("SET max_memory='4GB'") # Limit RAM usage
2. 並列処理
DuckDB はデフォルトで並列処理を行います。スレッド数は調整できます。
duckdb.sql("SET threads TO 8")
3. 探索的分析のためのサンプリング
巨大なファイルのサンプルをクエリすれば、瞬時に結果を得られます。
# Random 10% sample
df_sample = duckdb.sql("SELECT * FROM 'huge.parquet' USING SAMPLE 10%").df()
実践的なワークフロー
1. 「ビッグデータ」のクリーニングパイプライン
def process_experiment_logs(glob_pattern):
"""Clean and aggregate TBs of log data across many files."""
query = f"""
WITH clean_data AS (
SELECT
timestamp::TIMESTAMP as ts,
sensor_id,
value
FROM read_csv_auto('{glob_pattern}')
WHERE value IS NOT NULL AND value != -999
)
SELECT
time_bucket(INTERVAL '1 hour', ts) as hour,
sensor_id,
AVG(value) as avg_val
FROM clean_data
GROUP BY 1, 2
ORDER BY 1, 2
"""
return duckdb.sql(query).df()
2. ML 向けの高速な特徴量エンジニアリング
def create_features(df_train):
# Use SQL to create complex lag and moving average features
return duckdb.sql("""
SELECT *,
AVG(price) OVER (PARTITION BY item ORDER BY date ROWS 3 PRECEDING) as ma3,
COUNT(*) OVER (PARTITION BY user) as user_activity_count
FROM df_train
""").df()
3. 相互運用:DuckDB から PyTorch/TensorFlow へ
# Query data and convert to Arrow for zero-copy transfer to Deep Learning
arrow_table = duckdb.sql("SELECT * FROM 'data.parquet'").arrow()
# Then in PyTorch (requires torch.utils.dlpack or similar)
# Or just use the fast arrow-to-numpy/tensor path
よくある問題と解決策
「変数が見つかりません」エラー
DuckDB は、duckdb.sql() が呼び出されたスコープ内で変数を探します。
# ✅ Solution: If calling inside a function, ensure the variable is local or passed
def my_query(my_df):
return duckdb.sql("SELECT * FROM my_df").df()
データベースファイルがロックされる
2 つのプロセスが同じ .db ファイルを「書き込み」モードで開こうとすると、失敗します。
# ✅ Solution: Open as read-only for secondary processes
con = duckdb.connect('data.db', read_only=True)
日付/時刻の解析
CSV では、形式が不規則な日付がよく使われています。
# ✅ Solution: Use strptime or let DuckDB auto-detect
# SELECT strptime(date_col, '%d/%m/%Y') FROM 'data.csv'
ベストプラクティス
- ファイルを直接クエリする - まずメモリに読み込むのではなく、
SELECT * FROM 'file.parquet'を使用します。 - 適切な出力形式を使用する - Pandas には
.df()、Polars には.pl()、Arrow には.arrow()を使用します。 - 繰り返し実行するクエリにはビューを作成する - 同じファイルを何度も読み込まないようにします。
- プリペアドステートメントを使用する - SQL インジェクションを防ぎ、繰り返し実行するクエリのパフォーマンスを向上させます。
- Parquet 形式を活用する - DuckDB は Parquet に優れているため、最高のパフォーマンスを得るには Parquet を使用します。
- 複数のファイルにはワイルドカードを使用する -
FROM 'data/*.parquet'を使って、一度に多数のファイルをクエリします。 - 大規模なクエリではディスクへのスピルを有効にする - メモリ不足時の処理に備えて、
temp_directoryとmax_memoryを設定します。 - EXPLAIN ANALYZE を使用する - クエリの実行状況を把握し、ボトルネックを特定します。
- 接続を適切に閉じる - コンテキストマネージャーを使用するか、ファイルベースの接続を明示的に閉じます。
- 複雑な集計には SQL を優先する - ウィンドウ関数や複雑な結合は、DataFrame コードよりも SQL のほうが分かりやすいことがよくあります。
DuckDB は、SQL の分析能力と Python の柔軟性をつなぐ架け橋です。「データ読み込みのコスト」をなくし、科学者がデータに対する複雑な問いに超高速で取り組めるようにします。