1. ホーム
  2. r

[解決済み] dplyr on data.table、私は本当にdata.tableを使っているのでしょうか?

2023-02-20 16:03:20

質問

もし私が dplyr 構文の上に データテーブル の構文を使用しながら、datatableのすべての速度の利点を得ることができますか?言い換えると、dplyr 構文でクエリを実行すると、datatable を誤って使用することになりますか? または、そのパワーをすべて利用するために、純粋な datatable 構文を使用する必要がありますか。

アドバイスをいただき、ありがとうございます。コード例です。

library(data.table)
library(dplyr)

diamondsDT <- data.table(ggplot2::diamonds)
setkey(diamondsDT, cut) 

diamondsDT %>%
    filter(cut != "Fair") %>%
    group_by(cut) %>%
    summarize(AvgPrice = mean(price),
                 MedianPrice = as.numeric(median(price)),
                 Count = n()) %>%
    arrange(desc(Count))

結果

#         cut AvgPrice MedianPrice Count
# 1     Ideal 3457.542      1810.0 21551
# 2   Premium 4584.258      3185.0 13791
# 3 Very Good 3981.760      2648.0 12082
# 4      Good 3928.864      3050.5  4906

以下は、私が思いついたdatatableの等価性です。DTのグッドプラクティスに準拠しているかどうかはわかりません。しかし、私は、このコードが裏でdplyr構文よりも本当に効率的であるかどうか疑問に思っています。

diamondsDT [cut != "Fair"
        ] [, .(AvgPrice = mean(price),
                 MedianPrice = as.numeric(median(price)),
                 Count = .N), by=cut
        ] [ order(-Count) ]

どのように解決するのですか?

これらのパッケージの哲学はある側面で異なっているため、単純明快な答えはありません。そのため、いくつかの妥協は避けられません。ここでは、対処または考慮する必要がある懸念事項をいくつか紹介します。

以下を含む操作 i (== filter()slice() を含む)

仮定 DT に10個のカラムがあるとします。これらのdata.table式を考えてみましょう。

DT[a > 1, .N]                    ## --- (1)
DT[a > 1, mean(b), by=.(c, d)]   ## --- (2)

(1)は DT ここで、列 a > 1 . (2) リターン mean(b) でグループ化された c,d にある同じ式に対して i を(1)と同じにする。

一般的に使われる dplyr の表現になります。

DT %>% filter(a > 1) %>% summarise(n())                        ## --- (3) 
DT %>% filter(a > 1) %>% group_by(c, d) %>% summarise(mean(b)) ## --- (4)

明らかに、data.tableのコードの方が短いです。加えて、それらは よりメモリ効率が良い 1 . なぜか?なぜなら,(3)と(4)の両方で filter() の行を返します。 まず、(3)では行の数だけ、(4)では列の数だけ必要なときに b, c, d が必要なだけです。これを克服するために、我々は select() の列を事前に作成しておく必要があります。

DT %>% select(a) %>% filter(a > 1) %>% summarise(n()) ## --- (5)
DT %>% select(a,b,c,d) %>% filter(a > 1) %>% group_by(c,d) %>% summarise(mean(b)) ## --- (6)

2つのパッケージの間の主要な哲学的な違いを強調することが不可欠です。

  • data.table を見ると、これらの関連する操作を一緒にしておきたいと思います。 j-expression (同じ関数呼び出しから)を見て、(1)の列が必要ないことに気づくことができます。の式は i の式は計算され .N は単に行数を与える論理ベクトルの和であり、部分集合全体が実現されることはない。(2)では、単に列 b,c,d だけがサブセットで実現され、他の列は無視されます。

  • しかし dplyr では、関数が正確に1つのことを行うことが哲学です。 さて . の後の演算がうまくいくかどうかを判断する方法は(少なくとも今のところ)ありません。 filter() の後のオペレーションがフィルタリングした全てのカラムを必要とするかどうかを知る方法は(少なくとも現在は)ありません。このような作業を効率的に行いたいのであれば、先のことを考える必要があるでしょう。個人的にはこの場合、逆に直感的だと思います。

なお、(5)(6)では、まだサブセット列の a をサブセットしていることに注意してください。しかし、それを回避する方法はよくわからない。もし filter() 関数に返す列を選択する引数があれば、この問題を避けることができますが、その場合、関数は1つのタスクだけを行うわけではありません(これはdplyrの設計上の選択でもあります)。

参照によるサブアサイン

dplyrは 決して を参照によって更新しません。これは、2つのパッケージの間のもう1つの大きな(哲学的な)違いです。

例えば、data.tableでは、こんなことができます。

DT[a %in% some_vals, a := NA]

カラムを更新する a 参照で を、条件を満たすこれらの行だけに適用します。現時点では、dplyrは新しい列を追加するために内部的にdata.table全体を深くコピーしています。@BrodieGは彼の回答ですでにこれに言及しました。

しかし、ディープコピーをシャローコピーに置き換えることができるのは、次のような場合です。 FR #617 が実装されている場合、ディープコピーをシャローコピーに置き換えることができます。また、関連する dplyr: FR#614 . それでも、あなたが変更したカラムは常にコピーされることに注意してください(したがって、より遅く/より少ないメモリ効率)。参照によってカラムを更新する方法はありません。

その他の機能

  • data.tableでは、結合しながら集約することができます。これはより分かりやすく、中間結合結果が実体化しないのでメモリ効率も良いです。確認 この記事 を参照してください。dplyrのdata.table/data.frame構文を使用してそれを行うことはできません(現時点では)。

  • data.tableの ローリングジョイン の機能は、dplyrの構文でもサポートされていません。

  • 私たちは最近、data.tableにオーバーラップジョインを実装し、区間範囲にわたって結合するようにしました ( はその例です。 ) を実装しましたが、これは別の関数 foverlaps() であり、したがってパイプ演算子(magrittr / pipeR? - 自分で試したことはありません)と一緒に使うことができます。

    しかし、最終的には、私たちの目標は、それを [.data.table に統合して、グループ化、集約、結合などのような他の機能を利用できるようにすることですが、これには上記のような制限があります。

  • 1.9.4 以降、data.table は、通常の R 構文に基づくサブセットの高速なバイナリ検索用に、セカンダリキーを使用した自動インデックス作成を実装しています。例 DT[x == 1]DT[x %in% some_vals] は、最初の実行で自動的にインデックスを作成し、それを同じ列からの連続したサブセットでバイナリサーチを使って高速にサブセットを作成します。この機能は今後も進化していく予定です。チェック この gist をご覧ください。

    方法から filter() がdata.tablesで実装されていることから、この機能を利用することはできません。

  • dplyrの特徴として、data.tablesの他に データベースへのインタフェース を提供することです。data.tableには今のところありません。

したがって、これら(およびおそらく他の点)を比較検討し、これらのトレードオフがあなたにとって許容できるかどうかに基づいて決定する必要があります。

ありがとうございました。


(1) ほとんどの場合、ボトルネックはメイン メモリからキャッシュにデータを移動すること (およびキャッシュ内のデータをできる限り使用し、キャッシュ ミスを減らすことで、メイン メモリへのアクセスを減らすこと) なので、メモリ効率は (特にデータが大きくなるほど) 速度に直接影響することに留意してください。詳細についてはここでは触れません。