# install.packages("pak")
pak::pak("duckdb")はじめに
大量のデータを扱う際、メモリ制限やパフォーマンスの問題に直面することがあります。そんなとき便利なのが、軽量で高速な組み込み型データベース DuckDB です。本記事では、RとPythonの両方でDuckDBを使う方法を体系的に解説します。
DuckDBとは
DuckDBは、組み込み型のSQLデータベースであり、特に分析ワークロードに最適化されています。SQLiteのように軽量でありながら、大規模なデータセットを効率的に処理できる点が特徴です。
組み込み型データベースとは、アプリケーションに組み込まれて動作するデータベースのことです。PostgreSQLなどのサーバー型とは異なり、外部のデータベースサーバーを必要とせず、R/Python上で完結します。
また、DuckDBは列指向ストレージを採用しています。データを行ではなく列ごとに格納するため、特定の列に対するクエリが高速に処理されます。列指向については、以下の記事でも触れています。
インストール
duckdbパッケージをインストールします。
もしくは
install.packages("duckdb")duckdbパッケージをインストールします。
# pipを使用する場合
pip install duckdb
# uvを使用する場合
uv add duckdbuvについては以下の記事をご参照ください。
基本的な使い方
データベースへの接続
DuckDBにはメモリ内データベースと永続データベースの2種類があります。
- メモリ内データベース: セッション終了時にデータが消去される。一時的な分析に便利。
- 永続データベース:
.duckdbファイルにデータが保存され、セッションを跨いで再利用できる。
library(duckdb)# メモリ内データベースに接続
con <- dbConnect(duckdb())# 永続データベースに接続
con <- dbConnect(duckdb(), dbdir = here::here("data/iris.duckdb"))永続データベースの場合、指定したパスに.duckdbファイルが作成されます。
import duckdb
# メモリ内データベースに接続
con = duckdb.connect()
# 永続データベースに接続する場合
con = duckdb.connect('data/iris.duckdb')データの読み込み
DuckDBはCSVやParquetなど、様々なデータ形式を直接読み込めます。ここではCSVファイルの読み込みを例に紹介します。
SQLで読み込む
dbExecute(con, "CREATE TABLE iris AS SELECT * FROM read_csv('data/iris.csv')")con.sql("CREATE TABLE iris AS SELECT * FROM read_csv('data/iris.csv')")このSQL文は、read_csv('data/iris.csv')から全列(*)を選択し、irisという名前のテーブルを作成しています。
この方法のメリットは、R/Pythonに一度もデータを読み込まずに、DuckDBが直接ファイルを処理できる点です。R/Pythonに読み込まれるのはクエリの結果だけなので、メモリ消費を抑えられます。また、CREATE TABLE ... AS SELECT ...構文で、列の選択やフィルタリングなどの前処理を行いながらテーブルを作成できます。
dplyrで読み込む(R限定)
SQLになじみがない方向けに、Rではdplyrを使ったデータベース操作も可能です。dplyrで書いたコードが裏でSQLに変換・実行されます。
library(tidyverse)# Rでデータを読み込み、DuckDBに登録
iris_data <- read_csv(here::here("data/iris.csv"))
duckdb_register(con, iris, "iris_data")duckdb_register()は、RのデータフレームをDuckDB上のテーブルとして登録します。これをすると、dbGetQuery()やtbl()でirisテーブルを参照できるようになります。
永続データベースに書き込みたい場合は、代わりにdbWriteTable()を使います。
dbWriteTable(con, "iris", iris_data)永続データベースに書き込めば、1つの.duckdbファイルに複数のテーブルを保存でき、後で再利用できます。
SQLで直接ファイルを読む方法とは異なり、dplyr経由では一度Rにデータを読み込んでからデータベースに登録するため、大きなデータではメモリ消費が増える点に注意してください。
なお、データベースにはテーブルを複数作成できるので、iris以外のデータも好きな名前で追加できます1。
遅延評価について
データベースを使うメリットのひとつに、遅延評価(lazy evaluation)があります。通常のデータ処理では書いた順に即座に実行されますが、DuckDBでは処理を記録しておき、最後にまとめて実行します。
通常:データ読み込み → 処理を即実行 → 結果を返す
遅延評価:データ読み込み → 処理を記録 → まとめて実行(SQL発行)→ 結果を返す
このおかげで不要な計算が省かれ、大規模データでも効率的に処理できます。
データの操作
SQLで操作する
result <- dbGetQuery(con, "SELECT * FROM iris WHERE Species = 'setosa'")result = con.sql("SELECT * FROM iris WHERE Species = 'setosa'")この例では、irisテーブルからSpeciesがsetosaの行を取得しています。
dplyrで操作する(R限定)
result <- tbl(con, "iris") |>
filter(Species == "setosa") |>
collect()tbl(con, "iris")でデータベース上のテーブルを参照し、filter()などおなじみのdplyr関数で操作できます。
ここで重要なのは、collect()を呼び出すまではSQLが実行されないという点です。filter()などの処理を重ねるたびに裏でSQLクエリが組み立てられ、collect()で初めて実行されます。つまり、collect()の前まではデータはRに読み込まれず、「SQLを組み立てているだけ」の状態です。
データベースになじみがない方にとっては少し不思議かもしれませんが、conはデータそのものではなく、データベースへの接続(窓口)を表すオブジェクトです。この窓口を通してテーブルを参照し、最終的にcollect()で結果を手元に取り込む、という流れになります。
テーブルの削除
# テーブルを削除
dbExecute(con, "DROP TABLE iris")
# テーブルが存在しない場合でもエラーにならない書き方
dbExecute(con, "DROP TABLE IF EXISTS iris")# テーブルを削除
con.sql("DROP TABLE iris")
# テーブルが存在しない場合でもエラーにならない書き方
con.sql("DROP TABLE IF EXISTS iris")データベースからの切断
操作が終わったら、接続を切断します。
dbDisconnect(con, shutdown = TRUE)shutdown = TRUEを指定すると、メモリ内データベースの場合はデータが消去され、ファイルベースの場合は接続が閉じられます。
con.close(){duckplyr}を使う(R限定)
ここまではDuckDBをデータベースとして使う方法を紹介してきましたが、Rにはduckplyrというもうひとつのアプローチがあります。
duckplyrとは
duckplyrは、dplyrのドロップイン置き換えとして機能するパッケージです。library(duckplyr)するだけで、既存のdplyrコードがそのまま動きながら、裏側でDuckDBの高速な分析エンジンが使われます。
duckdbパッケージとの違い
| duckdb + dbplyr | duckplyr | |
|---|---|---|
| データベース接続 | 必要 | 不要 |
| データの場所 | DB上(collect()でRへ) |
Rのメモリ上 |
| 仕組み | dplyr → SQL翻訳 → DuckDB実行 | Rのデータを直接DuckDBエンジンで処理 |
| 用途 | 大規模データのDB管理・分析 | メモリ内データの高速化 |
duckdb + dbplyr は、データベースに接続してSQLベースで処理する方法(前のセクションで紹介)、duckplyr は、データベースを意識せずに、普段のdplyrコードをそのまま高速化する方法です。
duckplyrの特徴
- ドロップイン置き換え:
library(duckplyr)するだけで、多くのdplyr動詞が高速化される - 自動フォールバック: 対応できない操作は自動的に通常のdplyrにフォールバックするため、エラーで止まらない
- 大規模データに最適: DuckDBの並列処理エンジンとメモリ効率の良い処理を活用
- 互換性: tidyverseエコシステムとシームレスに連携
使用例
library(tidyverse)
library(duckplyr)
#> Loading required package: dplyr
#> ✔ Overwriting dplyr methods with duckplyr methods.
#> ℹ Turn off with `duckplyr::methods_restore()`.
# 通常のdplyr構文がそのまま使えます
mtcars |>
group_by(cyl) |>
summarise(
mean_mpg = mean(mpg),
mean_hp = mean(hp),
.groups = "drop"
)# A tibble: 3 × 3
cyl mean_mpg mean_hp
<dbl> <dbl> <dbl>
1 4 26.7 82.6
2 6 19.7 122.
3 8 15.1 209.
読み込み順序が重要です。 library(tidyverse)の後にlibrary(duckplyr)を読み込んでください。逆の順序だと、通常のdplyrが優先されてしまいます。
小さいデータだと速さを実感しにくいかもしれませんが、大きいデータを扱うときに効果を発揮します。
まとめ
DuckDBは軽量で高速な組み込み型データベースであり、R/Python上で手軽に大規模データを扱えます。
- SQLで直接ファイルを読み込み・操作する方法が、最もメモリ効率が良い
- Rではdplyrを使ったデータベース操作も可能で、SQLに不慣れでも扱いやすい
- さらにRではduckplyrを使えば、既存のdplyrコードをほぼそのまま高速化できる
ぜひお試しあれ。
参考

注
とはいえ、プロジェクトごとなどで
.duckdbは分けた方が良いとは思います。↩︎