新しいPolars Rパッケージの紹介

2025-12-06 Japan.R 2025
@eitsupi

はじめに

自己紹介

前回までのあらすじ

久しぶりに発表させてもらうので、
関連するこれまでの発表を振り返ってみましょう!

あの人いつもdplyr周りの話してる……。

本日話すこと

1年かけてpolarsパッケージを完全に書き直したので12
改めて紹介したい!

  • RでPolarsを使ってみる
    • Python Polars風構文
    • S3メソッド(Base R、dplyr)
  • 技術的な話
    • Rベクトルの型とArrow型の対応
    • mirai/reticulate統合
    • S7

Polarsについて

Polars

  • 「最速のデータフレームライブラリの一つ」3
  • Rust製
  • 内部データはApache Arrow形式
  • Pythonパッケージがpandasとの比較で有名
    • 公式パッケージ :Python、Rust
    • 非公式パッケージ:Nodo.js、R、Ruby
  • 現在はPolars社(オランダ)が本体を開発

R Polars

  • Rust PolarsのRバインディング
    • APIはRust PolarsではなくPython Polarsを模倣する方針
      • そのせいでRの行数が……(R 5万行超、Rust 1万行超)
  • 依存Rパッケージ(必須):rlang、S7
    • 旧R Polarsは依存パッケージなしに拘っていたが、
      辛過ぎたので「rlang依存しないとヤダヤダ」と私が駄々をこねた
    • S7については後述
    • 加えて、vctrs、blob、hmsもほぼ必須
  • Rust側依存関係:polars、savvy、flume

(良い感じのベンチマーク結果を入れたかったページ)

#> # A tibble: 7 × 6
#>   expression             min   median `itr/sec` mem_alloc `gc/sec`
#>   <bch:expr>        <bch:tm> <bch:tm>     <dbl> <bch:byt>    <dbl>
#> 1 acero_dplyr        149.7ms  156.1ms      6.25   41.83MB     0
#> 2 duckdb_dbplyr      109.4ms  113.7ms      8.35   24.18MB     2.09
#> 3 duckdb_duckplyr     62.8ms   69.1ms     14.4     3.71MB     0
#> 4 duckdb              59.6ms   61.5ms     14.9      4.9KB     0
#> 5 sedonadb            56.8ms   73.1ms     13.6      6.7MB     0
#> 6 polars_tidypolars   62.4ms   67.2ms     13.5    13.15MB     2.26
#> 7 polars                50ms   53.2ms     18.8    99.51KB     0

先んじてZennに記事を投稿済なので、そちらをご確認ください!

RでPolarsを使ってみる

Python Polars風構文

Python

import polars as pl
import polars.selectors as cs

pl.DataFrame(
    {
        "x": [1, 2, 3],
        "y": ["a", "b", "c"],
    }
).select(cs.by_name("x"))

R

library(polars)

pl$DataFrame(
  x = c(1, 2, 3),
  y = c("a", "b", "c"),
)$select(cs$by_name("x"))

何この構文!?

Python Polars風構文

Base R S3メソッド

df <- data.frame(
  x = c(1, 2, 3),
  y = c("a", "b", "c")
)

df["x"]
library(polars)

df <- data.frame(
  x = c(1, 2, 3),
  y = c("a", "b", "c")
) |>
  as_polars_df()

df["x"]
  • お馴染みの関数(の一部)を使える
  • Polarsの全機能にアクセスできるわけではない
  • 外部の関数で使用されることも想定される

dplyr / tidyr S3メソッド

tibble::tibble(
  x = c(1, 2, 3),
  y = c("a", "b", "c"),
) |>
  dplyr::select(x)
library(polars)
library(tidypolars)

tibble::tibble(
  x = c(1, 2, 3),
  y = c("a", "b", "c"),
) |>
  as_polars_df() |>
  dplyr::select(x)
  • tidypolarsパッケージによる提供
  • dbplyrやarrow等のように、dplyr動詞だけなく
    dplyr動詞内で評価される式の翻訳も行い、相当数の関数をサポート
  • できることに差があるので、duckplyrやdbplyr + duckdbと比較して
    使うのがオススメ

SQL

library(polars)

ctx <- pl$SQLContext(
  dat =data.frame(
    x = c(1, 2, 3),
    y = c("a", "b", "c")
  )
)

ctx$execute("SELECT x FROM dat")$collect()
  • 地味にサポートされている
  • そこまで機能が充実しているわけではなく開発の優先度も低いので
    普通に考えるとduckdbの方が便利

余談: インストール

MSRV問題

  • Minimum Supported Rust Version:ビルドに必要なRustバージョン
  • CRAN上で利用可能なRustは非常に古く、polarsをビルドできない
    → もちろんビルドできないためリリースもできない
  • R-multiverseを使おう

MSRV問題

CRAN上のRustは半年以上1.81.0から動いていない。

技術的な話

Arrow型対応

  • Apache Arrowの型の一部は、Base Rのベクトルで表現できない
    • Null
    • Time
    • Binary
  • S3ベクトルを良い感じに定義できるvctrsパッケージおよび
    vctrsに基づいたいくつかのパッケージでかなり対応できる
    • vctrs::unspecified()
    • hms::hms()
    • blob::blob()

Arrow型対応

日時型のパッケージとして最も表現力の高い(※私調べ)
clockパッケージをサポートしているのは現時点でpolarsのみ

clock::naive_time_parse(
  c(
    NA,
    "1900-01-01T12:34:56.123456789",
    "2020-01-01T12:34:56.123456789"
  ),
  precision = "nanosecond"
) |>
  polars::as_polars_series()

mirai統合 / reticulate統合

  • mirai:別のRセッション(同一マシン/別マシン)に
    polarsオブジェクトを転送する
  • reticulate:Pythonにpolarsオブジェクトを転送する
  • 計算能力の高いマシンにクエリを転送?
  • バックグラウンド実行?
  • Python Polarsにしかない関数の実行?

S7への移行

新しくて単純なクラスのS7への移行から開始

従来のpolarsオブジェクト:環境型にS3クラス属性を付けたもの

function(x, ...) {
  self <- new.env(parent = emptyenv())
  self$`_df` <- x

  class(self) <- c("polars_data_frame", "polars_object")
  self
}
  • 利点
    • 単純、依存関係なし
  • 欠点
    • is.environment()TRUEになる

環境型の限界

plot(polars::as_polars_df(mtcars))
Error in as.double(y): cannot coerce type 'environment' to vector of type 'double'

↑のように謎のエラーが発生し、plot()関数を使えない!

  • 関数内でis.environment()による判定が行われており、as.data.frame()まで到達できない
    • arrowパッケージのクラスでも同じ

多重ディスパッチ

演算子

s <- polars::as_polars_series(1L)
e <- polars::as_polars_expr(1L)

s + 1 # 2 (Series)
e + 1 # 2 (Expr)

s + e # エラー
e + s # エラー

複雑な関数

df <- polars::as_polars_df(mtcars)

df[, 1]
df[1]
df[, "mpg"]
df["mpg"]

S7を採用することで良い感じに書けるようになりそう

まとめ

今日はPolarsという名前だけでも覚えて帰ってください

ついでに気軽にインストールして試してみてください!

脚注

  1. 1人でやったわけではなく、Etienne Bacherさんもめっちゃ作業してくださった。

  2. 旧実装からそのままコピーしただけの部分もある。

  3. Polars is one of the fastest DataFrame libraries at the time of writing.
    https://datafusion.apache.org/user-guide/faq.html,
    2025-12-06閲覧

  4. Rust側から自動生成されたRコードをもう一段階ラップしているため大変ではある。