【特報】
AceroDuckDBPolars
on R

2023-01-21 第103回R勉強会@東京
@eitsupi

はじめに

自己紹介

  • @eitsupi
  • Excelが嫌になりRを触り初めて4年弱
  • Dockerイメージrocker/r-ver他のメンテナー
  • 昨日初RパッケージがCRANリリースされました🎉

2022年末……

r-polars襲来!

Polarsとは

  • “Blazingly fast DataFrames in Rust, Python & Node.js”
    • 内部でApache Arrow形式を使用するRust製の高速なテーブルデータ操作パッケージ。
    • Rのdata.tableパッケージの高速さのアピールに使用されたh2oai/db-benchmark(最終更新2021年7月)の数々の項目でPython版polarsが上位に。
  • メソッドチェーンによる分かりやすいAPI。
  • Windows関数なども利用可能で多機能。

h2oベンチマーク

出典:https://h2oai.github.io/db-benchmark/, 2023年1月21日閲覧

しかしそれは1年半前の話……

Acero

Aceroとは

  • 2021年当時には名無しだった、Apache Arrow C++ライブラリのストリーミングクエリエンジン。(今後libaceroとして分割されそう1
  • RのarrowパッケージのdplyrAPIで呼び出されるやつ。
  • 2021年のh2oベンチマーク実行結果に「Arrow」と表示されているのはこれを指している。
    • ……が、当時はsummariseにもjoinにも対応していなかったので何もしておらず全部素のdplyrで計算されていたはず。

DuckDB

DuckDBとは

  • SQLiteライクに使用できる組み込み向け列指向RDBMS。
  • 公式でRを含む多数の言語のパッケージが存在し、広く使用されている。Quartoの{ojs}コードブロック内でも何もせずに呼べる。
  • Postgres互換のSQLを採用。
    • 「FROM句から始まるクエリ」「SELECT内でEXCLUDEによる列の除外」などの使い勝手改良にも積極的。
  • 「ビッグデータは死んだ。イージーデータ万歳」2
  • 「あなたのラップトップはあなたのデータウェアハウスよりも高速です」3

DuckDBLabs所属の方がフォークした4h2oベンチマーク最新の結果

出典:https://tmonster.github.io/h2oai-db-benchmark/, 2023年1月21日閲覧

on R

遅延評価とプッシュダウン

Parquetに対するクエリでは遅延評価を行い不要な行や列を読み飛ばせるため、CSVを読み込んで処理する場合に比べて高速かつ省メモリで処理を完了できる。

いつものParquet5(およそ600万行)で試してみましょう。

curl::curl_download(
  "https://github.com/duckdb/duckdb-data/releases/download/v1.0/lineitemsf1.snappy.parquet",
  "lineitemsf1.snappy.parquet"
)

Acero

library(arrow, warn.conflicts = FALSE)
library(dplyr, warn.conflicts = FALSE)

open_dataset("lineitemsf1.snappy.parquet") |>
  filter(
    l_shipdate >= "1994-01-01",
    l_shipdate < "1995-01-01",
    l_discount >= 0.05,
    l_discount < 0.07,
    l_quantity < 24
  ) |>
  summarise(revenue = sum(l_extendedprice * l_discount, na.rm = TRUE)) |>
  collect()

DuckDB

library(duckdb)

con <- dbConnect(duckdb(), ":memory:")

query <- "
FROM
  'lineitemsf1.snappy.parquet'
SELECT
  SUM(l_extendedprice * l_discount) AS revenue
WHERE
  l_shipdate >= '1994-01-01'
  AND l_shipdate < '1995-01-01'
  AND l_discount >= 0.05
  AND l_discount < 0.07
  AND l_quantity < 24
"

dbGetQuery(con, query)

DuckDB (dbplyr)

library(duckdb)
library(dplyr, warn.conflicts = FALSE)
library(dbplyr, warn.conflicts = FALSE)

con <- dbConnect(duckdb(), ":memory:")

tbl(con, "lineitemsf1.snappy.parquet") |>
  filter(
    l_shipdate >= "1994-01-01",
    l_shipdate < "1995-01-01",
    l_discount >= 0.05,
    l_discount < 0.07,
    l_quantity < 24
  ) |>
  summarise(revenue = sum(l_extendedprice * l_discount, na.rm = TRUE)) |>
  collect()

Polars

library(rpolars)

scan_parquet("lineitemsf1.snappy.parquet")$filter(
  (pl$col("l_shipdate") >= "1994-01-01") &
    (pl$col("l_shipdate") < "1995-01-01") &
    (pl$col("l_discount") >= 0.05) &
    (pl$col("l_discount") < 0.07) &
    (pl$col("l_quantity") < 24)
)$select(
  (pl$col("l_extendedprice") * pl$col("l_discount"))$sum()$alias("revenue")
)$collect() |>
  as.data.frame()

衝撃の結末を君の目で確かめよう!

脚注

  1. https://github.com/apache/arrow/issues/15280

  2. Big Data is dead. Long live Easy Data.

  3. Your laptop is faster than your data warehouse.Why wait for the cloud?

  4. https://github.com/Tmonster/h2oai-db-benchmark

  5. DuckDB quacks Arrow: A zero-copy data integration between Apache Arrow and DuckDB