::pak("duckdb") pak
はじめに
Rで大量のデータを扱う際、メモリ制限やパフォーマンスの問題に直面することがあります。そんなとき、軽量で高速な組み込み型のデータベースであるDuckDBを利用することで、効率的にデータ操作が可能になります。本記事では、RでDuckDBを使用する方法について解説します。
DuckDBとは
DuckDBは、組み込み型のSQLデータベースであり、特に分析ワークロードに最適化されています。SQLiteのように軽量でありながら、大規模なデータセットを効率的に処理できる点が特徴です。DuckDBは、列指向ストレージを採用しており、分析クエリのパフォーマンスが向上します。
ちなみに組み込み型データベースとは、アプリケーションに組み込まれて動作するデータベースのことを指します。サーバー型のデータベースとは異なり、外部のデータベースサーバーを必要とせず、アプリケーション内で直接データベース操作が可能です。すなわちR上で完結するということです。
列指向については、ブログでparquetを扱った際に少し触れています。以下もご覧ください。簡潔に言うと、列指向データベースは、データを列ごとに格納するため、特定の列に対するクエリが高速に処理されるという特徴があります。
DuckDBのインストール
DuckDBをRで使用するには、duckdb
パッケージをインストールします。
もしくは
install.packages("duckdb")
DuckDBの基本的な使い方
データベースへの接続
まずは、DuckDBデータベースに接続します。以下のコードでは、メモリ内データベースに接続していますが、ファイルベースのデータベースを使用することも可能です。
メモリ内データベースはRセッションが終了すると消えてしまいますが、ファイルベースのデータベースは永続的に保存されます。
ファイルベースのデータベースではディスクにデータが保存されるため、Rセッションを終了してもデータが保持されます。一方、メモリ内データベースはRセッションが終了するとデータが失われてしまいますが、読み書きの速度が速いという利点があります。
library(duckdb)
# メモリ内データベースに接続
<- dbConnect(duckdb(), dbdir = ":memory:") con
ファイルベースのデータベースに接続する場合は、以下のようにします。
# ファイルベースのデータベースに接続
<- dbConnect(duckdb(), dbdir = here::here("data/iris.duckdb")) con
この方法だと、data
フォルダにiris.duckdb
という名前でデータベースファイルが作成されます。.duckdb
ファイルを置くパスは自由に変更してください。
データの読み込み
SQLを使う場合
DuckDBは様々なデータ形式をサポートしています。ここでは、CSVファイルを読み込む例を示します。
# CSVファイルを読み込む
dbExecute(con, "CREATE TABLE iris AS SELECT * FROM read_csv('data/iris.csv')")
さて、ここで突然SQL文が出てきましたが、なんとなく単語を見てもらえればわかる通り、read_csv('data/iris.csv')
から全ての列を選択(*
は全ての列を意味します)して、iris
という名前のテーブルを作成(CREATE TABLE iris AS
)しています。
ちなみに、僕はSQLはあまりわかりません。いちいち調べないと先ほどのコードも書けないレベルなのですが、この方法をとるメリットを先に説明しておきます。
この方法のメリットは、Rに一度もデータを読み込まずに、DuckDBが直接CSVファイルを処理できる点です。 そのため、大規模なCSVでもメモリを無駄に消費せず、高速にテーブルを作成できます。
また、CREATE TABLE ... AS SELECT ...
という形で書けば、SQLの標準的な構文で柔軟に前処理(列の選択やフィルタリングなど)を行いながら、テーブルを作成できます。
Rを使う場合
とはいえ新しくSQLを覚えるのは面倒だしRで書けたら嬉しいという僕なので、ここでRを使うことの意義を示します。
それは、今提示したコードを皆さんおなじみdplyr
を使って実行できる、ということです。データベースはなんだかよくわからないけれども、dplyr
が使えるならうれしいですよね。
ただ、先ほどのメリットの裏返しで、一度R上でデータを読み込んでからデータベースに送り込むことになるので、メモリを多く消費することと、データが大きいと処理速度が遅くなることには注意してください。
dplyr
を使って書いたコードをSQLに変換して実行してくれるのです。その場合のコードを見てみましょう。
library(tidyverse) # `dplyr`だけでもOKです。
<- read_csv(here::here("data/iris.csv"))
iris_data duckdb_register(con, iris, "iris_data")
まずはデータベースに入れたいデータをread_csv()
で読み込みます。ここではdata
フォルダにあるiris.csv
を読み込んでいます。
続いてduckdb_register()
を使って、iris_data
をiris
という名前でデータベースに登録しています。これでデータベース上にiris
テーブルが作成されました。
ちなみにファイルベースのデータベースを使いたい場合は、duckdb_register()
の代わりにdbWriteTable()
を使います。
dbWriteTable(con, "iris", iris_data)
これで.duckdb
ファイルにiris
テーブルが保存されます。
ちなみに、データベースにはテーブルを複数作成できるので、iris
以外のデータもも好きな名前でテーブルを追加できます1。
データの操作
ところで、データベースを使うメリットのひとつに、遅延評価(lazy evaluation) があります。これは、実際に結果を取り出すまでSQLが実行されない仕組みのことです。
通常のデータ処理は
データの読み込み → 書いた順に処理を実行 → 結果を返す
という流れですが、遅延評価では
データの読み込み → 書いた順に処理を記録 → 最後にまとめて実行(SQL発行) → 結果を返す
という流れになります。
このおかげで、大規模なデータセットでも不要な計算が省かれ、効率的に処理できます。
SQLを使う場合
SQLを使ってデータを操作する場合、以下のようにクエリを実行します。
# データをクエリする
<- dbGetQuery(con, "SELECT * FROM iris WHERE Species = 'setosa'") result
この例では、iris
テーブルからSpecies
がsetosa
の行を選択しています。
dplyr
を使う場合
dplyr
を使ってデータを操作する場合、以下のように記述します(おなじみの感じですね)。
<- tbl(con, "iris") |>
result filter(Species == "setosa") |>
collect()
tbl(con, "iris")
でデータベース上のiris
テーブルを参照し、filter()
で条件を指定しています。最後にcollect()
を使って結果をRのデータフレームとして取得します。
ここで重要なのは、collect()
を呼び出すまではSQLは実行されないという点です。filter()
などの処理を追加するたびに、裏でSQLクエリが少しずつ組み立てられていき、最後にcollect()
を呼び出した時点でまとめて実行されます。
つまり、collect()
を呼び出すまではデータはRに読み込まれず、操作はあくまで「SQLを組み立てているだけ」です。collect()
を実行した瞬間に初めてSQLが発行され、結果がRにデータフレームとして取り込まれます。
データベースになじみがない方は違和感があるかもしれませんが(かく言う僕もですが)、con
自体はデータそのものではなく、DuckDBデータベースへの接続(コネクション)を表すオブジェクトです。
普段のRではデータフレームを直接操作しますが、ここではまず接続オブジェクトcon
を通して「このデータベースの中にあるテーブルを参照しますよ」という指示を出します。
つまり、con
はデータの実体ではなく、「データベースへの窓口」や「リモコン」のようなものと考えると分かりやすいです(ChatGPT曰く)。
とりあえず、collect()
の前までは通常のデータ処理と同じようにdplyr
の関数を使って操作できるので、慣れ親しんだ方法でデータを扱うことができます。
データベースからの切断
データベースの操作が終わったら、接続を切断します。
dbDisconnect(con, shutdown = TRUE)
shutdown = TRUE
を指定すると、メモリ内データベースの場合はデータが消去され、ファイルベースのデータベースの場合は接続が閉じられます。
まとめ
今回は、RでDuckDBを使用する方法についてまとめました。
DuckDBは軽量で高速な組み込み型データベースであり、大規模なデータセットを効率的に処理できます。SQLを直接使う方法と、dplyr
を使う方法の両方が利用可能であり、遅延評価によりパフォーマンスが向上します。
最近は仕事ででかいデータを扱うことが増えてきたので、今後もDuckDBを活用していきたいと思います(というか活用し始めています)。ぜひ試しあれ。
参考
注
とはいえ、プロジェクトごとなどで
.duckdb
は分けた方が良いとは思います。↩︎