data.tableでできるdtplyrでできないこと


9

Rで、特にとの間dplyrでデータをラングリングするための学習努力を投資する必要がdtplyrありdata.tableますか?

  • dplyrほとんど使用しますが、データが大きすぎる場合はを使用しますがdata.table、これはまれなケースです。したがって、dtplyrv1.0がのインターフェイスとしてdata.table公開されたので、一見すると、data.tableインターフェイスの使用について二度と心配する必要はないようです。

  • だから、ほとんどの便利な機能や側面何をしているdata.tableことはできません使用して行われdtplyrた瞬間に、それはそうで行われることはありませんかdtplyr

  • その顔、上dplyrの利点とdata.tableなり、それはのように聞こえるdtplyr追い越すだろうdplyr。使用する理由はあるのでしょうdplyr一度dtplyr完全に成熟していますか?

注:私はdplyrvs については質問していませんdata.tabledata.table vs dplyrの場合のように、他の人がうまくやることができるか、うまくいかないことがあるのでしょうか?) tはdtplyr、使用するツールとなります。


1
そこに何かあるあなたが井戸に行うことができますdplyrあなたがうまくやることができないということはdata.table?そうでない場合は、に切り替えるdata.table方がに勝るでしょうdtplyr
sindri_baldur

2
dtplyrReadme から、「一部のdata.table式には直接dplyr同等のものはありません。たとえば、クロス結合またはローリング結合をで表す方法はありませんdplyr。また、「dplyrセマンティクスを一致させるために、mutate()はデフォルトでは変更されません。これは、関係するほとんどの式mutate()が、data.table直接使用している場合には不要なコピーを作成する必要があることを意味します。その2番目の部分を回避する方法は多少ありますが、mutate使用頻度を考えると、それは私の目にはかなり大きな欠点です。
ClancyStats

回答:


15

私は最善のガイドを提供しようとしますが、{data.table}、{dplyr}、{dtplyr}、および{base R}のすべてに精通している必要があるため、簡単ではありません。私は{data.table}と多くの{tidy-world}パッケージ({dplyr}を除く)を使用しています。両方が大好きですが、私はdplyrよりもdata.tableの構文を好みます。すべての整頓された世界のパッケージが必要なときにいつでもバックエンドとして{dtplyr}または{data.table}を使用することを願っています。

他の変換と同様に(dplyr-to-sparkly / SQLを考えてください)、少なくとも現時点では、変換できるものとできないものがあります。つまり、ある日{dtplyr}が100%翻訳できるようになるかもしれません。以下のリストは完全なものではなく、100%正しいものです。関連するトピック/パッケージ/問題/その他に関する私の知識に基づいて、最善を尽くしてお答えします。

重要な点として、完全に正確ではない回答については、{data.table}のどの側面に注意を払うべきかについてのガイドを提供し、それを{dtplyr}と比較して、自分で回答を見つけられることを願っています。これらの答えを当たり前のことと考えないでください。

また、この投稿が{dplyr}、{data.table}、または{dtplyr}のすべてのユーザー/作成者のリソースの1つとしてディスカッションやコラボレーションに使用され、#RStatsがさらに改善されることを願っています。

{data.table}は、高速でメモリ効率の高い操作に使用されるだけではありません。私を含め、多くの人が{data.table}のエレガントな構文を好みます。またfrollapply、Cで記述されたローリングファミリ(つまり)などの時系列関数のような他の高速操作も含まれます。tidyverseを含む任意の関数で使用できます。私は{data.table} + {purrr}をたくさん使用しています!

操作の複雑さ

これは簡単に翻訳できます

library(data.table)
library(dplyr)
library(flights)
data <- data.table(diamonds)

# dplyr 
diamonds %>%
  filter(cut != "Fair") %>% 
  group_by(cut) %>% 
  summarize(
    avg_price    = mean(price),
    median_price = as.numeric(median(price)),
    count        = n()
  ) %>%
  arrange(desc(count))

# data.table
data [
  ][cut != 'Fair', by = cut, .(
      avg_price    = mean(price),
      median_price = as.numeric(median(price)),
      count        = .N
    )
  ][order( - count)]

{data.table}は(ほとんど?)すべてがCからゼロから構築されているため、update-by-reference、キー(SQLを考える)、およびパッケージ内のあらゆる場所での絶え間ない最適化により、非常に高速でメモリ効率が高い(つまりfifelsefread/freadベースRで採用されている基数ソート順)、構文が簡潔で一貫していることを確認しながら、それがエレガントだと思うのです。

Introduction to data.tableから、サブセット、グループ、更新、結合などの主要なデータ操作操作は、

  • 簡潔で一貫した構文...

  • 各操作をマッピングする必要のない認知的負担なしに流動的に分析を実行します...

  • 各操作に必要なデータを正確に把握することで、操作を内部的に、非常に効果的に自動的に最適化し、非常に高速でメモリ効率の高いコードを実現

最後のポイントは、例として、

# Calculate the average arrival and departure delay for all flights with “JFK” as the origin airport in the month of June.
flights[origin == 'JFK' & month == 6L,
        .(m_arr = mean(arr_delay), m_dep = mean(dep_delay))]
  • 最初にiのサブセットを作成して、出発空港が「JFK」、月が6Lに一致する行インデックスを見つけます。これらの行に対応するdata.table全体はまだサブセット化していません。

  • ここで、jを見ると、2つの列しか使用していないことがわかります。そして、私たちがしなければならないのは、それらの平均を計算することです。したがって、一致する行に対応する列だけをサブセット化し、それらの平均を計算します。

クエリ3つの主要コンポーネント(i、j、by)が一緒に[...]内にあるため、data.table は3つすべてを表示し、個別にではなく、評価前にクエリをすべて最適化できます。したがって、速度とメモリ効率の両方について、サブセット全体(つまり、arr_delayとdep_delay以外の列のサブセット)を回避できます。

そのため、{data.table}のメリットを享受するには、その点で{dtplr}の翻訳が正しい必要があります。操作が複雑になるほど、翻訳は難しくなります。上記のような単純な操作の場合、簡単に翻訳できます。複雑なもの、または{dtplyr}でサポートされていないものについては、上記のように自分自身を見つける必要があります。翻訳された構文とベンチマークを比較し、よく知られた関連パッケージである必要があります。

複雑な操作またはサポートされていない操作の場合、以下の例をいくつか提供できるかもしれません。繰り返しますが、私は最善を尽くしています。私に優しくしてください。

参照による更新

イントロ/詳細には入りませんが、ここにいくつかのリンクがあります

主なリソース:リファレンスセマンティクス

詳細:data.tableが別のdata.tableへの参照(のコピー)である場合を正確に理解する

私の意見では、更新による参照は、{data.table}の最も重要な機能であり、それにより、高速かつメモリ効率が高くなっています。dplyr::mutateデフォルトではサポートしていません。私は{dtplyr}に詳しくないので、{dtplyr}でサポートできる操作とサポートできない操作の量がわかりません。上記のように、それはまた、翻訳に影響を与える操作の複雑さにも依存します。

{data.table}でupdate-by-referenceを使用する方法は2つあります

  • {data.table}の代入演算子 :=

  • set-family: 、setsetnamessetcolordersetkeysetDTfsetdiffと、より多くの

:=と比較して、より一般的に使用されsetます。複雑で大規模なデータセットの場合、参照による更新が最高の速度とメモリ効率を実現する鍵となります。簡単な考え方(100%正確ではありません。ハード/浅いコピーやその他の多くの要因が関係するため、詳細はこれよりもはるかに複雑であるため)は、10列とそれぞれ1GBの10GBの大きなデータセットを扱っているとしましょう。 。1つの列を操作するには、1GBのみを処理する必要があります。

重要な点は、update-by-referenceを使用すると、必要なデータのみを処理する必要があるということです。そのため、{data.table}を使用する場合、特に大きなデータセットを扱う場合は、可能な限り常に参照による更新を使用します。たとえば、大規模なモデリングデータセットの操作

# Manipulating list columns

df <- purrr::map_dfr(1:1e5, ~ iris)
dt <- data.table(df)

# data.table
dt [,
    by = Species, .(data   = .( .SD )) ][,  # `.(` shorthand for `list`
    model   := map(data, ~ lm(Sepal.Length ~ Sepal.Width, data = . )) ][,
    summary := map(model, summary) ][,
    plot    := map(data, ~ ggplot( . , aes(Sepal.Length, Sepal.Width)) +
                           geom_point())]

# dplyr
df %>% 
  group_by(Species) %>% 
  nest() %>% 
  mutate(
    model   = map(data, ~ lm(Sepal.Length ~ Sepal.Width, data = . )),
    summary = map(model, summary),
    plot    = map(data, ~ ggplot( . , aes(Sepal.Length, Sepal.Width)) +
                          geom_point())
  )

list(.SD)tidyverseユーザーが使用しているため、ネスト操作は{dtlyr}でサポートされていない可能性がありますtidyr::nestか?したがって、{data.table}の方が高速でメモリが少ないため、後続の操作を変換できるかどうかはわかりません。

注:data.tableの結果は「ミリ秒」、dplyrは「分」です

df <- purrr::map_dfr(1:1e5, ~ iris)
dt <- copy(data.table(df))

bench::mark(
  check = FALSE,

  dt[, by = Species, .(data = list(.SD))],
  df %>% group_by(Species) %>% nest()
)
# # A tibble: 2 x 13
#   expression                                   min   median `itr/sec` mem_alloc `gc/sec` n_itr  n_gc
#   <bch:expr>                              <bch:tm> <bch:tm>     <dbl> <bch:byt>    <dbl> <int> <dbl>
# 1 dt[, by = Species, .(data = list(.SD))] 361.94ms 402.04ms   2.49      705.8MB     1.24     2     1
# 2 df %>% group_by(Species) %>% nest()        6.85m    6.85m   0.00243     1.4GB     2.28     1   937
# # ... with 5 more variables: total_time <bch:tm>, result <list>, memory <list>, time <list>,
# #   gc <list>

update-by-referenceの多くのユースケースがあり、{data.table}ユーザーでさえ、より多くのコードを必要とするため、常にその高度なバージョンを使用することはありません。{dtplyr}が標準でこれらをサポートしているかどうかは、自分で調べる必要があります。

同じ関数に対する複数の参照による更新

主なリソース:lapply()を使用してdata.tableに複数の列をエレガントに割り当てる

これは、より一般的に使用されるのいずれかが含ま:=set

dt <- data.table( matrix(runif(10000), nrow = 100) )

# A few variants

for (col in paste0('V', 20:100))
  set(dt, j = col, value = sqrt(get(col)))

for (col in paste0('V', 20:100))
  dt[, (col) := sqrt(get(col))]

# I prefer `purrr::map` to `for`
library(purrr)
map(paste0('V', 20:100), ~ dt[, (.) := sqrt(get(.))])

{data.table}の作成者によるMatt Dowle

(多数の列よりも多数の行にループセットを設定する方が一般的である場合があります。)

Join + setkey + update-by-reference

最近、比較的大きなデータと同様の結合パターンを使用した高速結合が必要になったため、通常の結合ではなく、参照による更新機能を使用します。彼らはより多くのコードを必要とするので、私はそれを私が呼ぶところの再利用性と可読性のための非標準の評価でプライベートパッケージにそれらをラップしますsetjoin

ここでいくつかのベンチマークを行いました:data.table join + update-by-reference + setkey

概要

# For brevity, only the codes for join-operation are shown here. Please refer to the link for details

# Normal_join
x <- y[x, on = 'a']

# update_by_reference
x_2[y_2, on = 'a', c := c]

# setkey_n_update
setkey(x_3, a) [ setkey(y_3, a), on = 'a', c := c ]

注:dplyr::left_joinまた、試験し、それは両方の{} data.tableより多くのメモリを使用しての、9000ミリ秒〜で最も遅いのだupdate_by_referencesetkey_n_update、しかし、{} data.tableより少ないメモリ使用のnormal_joinを。約2.0GBのメモリを消費しました。{data.table}だけに焦点を当てたいので、これは含めませんでした。

主な調査結果

  • setkey + updateそして、それぞれupdate〜11と〜6.5倍高速normal joinです
  • 最初の参加時のパフォーマンスは、のオーバーヘッドが自身のパフォーマンスの向上を大幅に相殺setkey + updateするのと同じですupdatesetkey
  • 目以降は参加する上で、などsetkey、必要とされていないsetkey + updateよりも速いですupdate〜1.8倍(またはより速いnormal join〜11倍)

画像

パフォーマンスが高くメモリ効率の高い結合を行うには、updateまたはを使用します。setkey + update後者の方がコードが多くなりますが高速です。

簡潔にするために、いくつかの疑似コードを見てみましょう。ロジックは同じです。

1つまたはいくつかの列

a <- data.table(x = ..., y = ..., z = ..., ...)
b <- data.table(x = ..., y = ..., z = ..., ...)

# `update`
a[b, on = .(x), y := y]
a[b, on = .(x),  `:=` (y = y, z = z, ...)]
# `setkey + update`
setkey(a, x) [ setkey(b, x), on = .(x), y := y ]
setkey(a, x) [ setkey(b, x), on = .(x),  `:=` (y = y, z = z, ...) ]

多くのカラム

cols <- c('x', 'y', ...)
# `update`
a[b, on = .(x), (cols) := mget( paste0('i.', cols) )]
# `setkey + update`
setkey(a, x) [ setkey(b, x), on = .(x), (cols) := mget( paste0('i.', cols) ) ]

高速でメモリ効率の高い結合のラッパー...それらの多く...同様の結合パターンで、setjoin上記のようにラップします-ありupdate -ありまたはなしsetkey

setjoin(a, b, on = ...)  # join all columns
setjoin(a, b, on = ..., select = c('columns_to_be_included', ...))
setjoin(a, b, on = ..., drop   = c('columns_to_be_excluded', ...))
# With that, you can even use it with `magrittr` pipe
a %>%
  setjoin(...) %>%
  setjoin(...)

を使用するとsetkey、引数onを省略できます。特に読みやすくするために含めることもできます。

大規模な行操作

  • 上記のように、 set
  • テーブルに事前入力し、参照による更新手法を使用する
  • キーを使用したサブセット(つまりsetkey

関連リソース:data.tableオブジェクトの最後に参照によって行を追加します

参照による更新の要約

これらはupdate-by-referenceのいくつかの使用例です。他にもたくさんあります。

ご覧のとおり、大きなデータを処理する高度な使用法には、大きなデータセットに対して参照による更新を使用する多くのユースケースと手法があります。{data.table}での使用はそれほど簡単ではなく、{dtplyr}がそれをサポートしているかどうかは、自分で確認できます。

この投稿ではupdate-by-referenceに焦点を当てています。これは、高速でメモリ効率の高い操作のための{data.table}の最も強力な機能だと思います。そうは言っても、それを非常に効率的にする他の多くの側面があり、それは{dtplyr}によってネイティブにサポートされていないと思います。

その他の重要な側面

サポートされているものとサポートされていないものは、操作の複雑さ、およびupdate-by-referenceやのようなdata.tableのネイティブ機能が関係しているかどうかにも依存しますsetkey。そして、変換されたコードがより効率的なもの(data.tableユーザーが書くもの)であるかどうかも、別の要因です(つまり、コードは変換されますが、効率的なバージョンですか?)。多くのものが相互に関連しています。

これらの側面の多くは、上記のポイントと相互に関連しています

  • 運用の複雑さ

  • 参照による更新

特にこれらが組み合わされている場合、{dtplyr}がこれらの操作をサポートしているかどうかを確認できます。

小規模または大規模なデータセットを処理するときのもう1つの便利なトリックは、対話型セッション中に、{data.table}はプログラミング計算時間を大幅に削減するという約束を実際に果たします。

速度と「スーパーチャージされた行名」の両方で繰り返し使用される変数の設定キー(変数名を指定しないサブセット)。

dt <- data.table(iris)
setkey(dt, Species) 

dt['setosa',    do_something(...), ...]
dt['virginica', do_another(...),   ...]
dt['setosa',    more(...),         ...]

# `by` argument can also be omitted, particularly useful during interactive session
# this ultimately becomes what I call 'naked' syntax, just type what you want to do, without any placeholders. 
# It's simply elegant
dt['setosa', do_something(...), Species, ...]

最初の例のように、操作に単純な操作のみが含まれる場合、{dtplyr}は仕事を完了できます。複雑でサポートされていないものについては、このガイドを使用して、{dtplyr}の翻訳されたものと、熟練したdata.tableユーザーがdata.tableのエレガントな構文を使用して高速かつメモリ効率の良い方法でコーディングする方法を比較できます。大規模なデータのさまざまなケースを処理するためのさまざまな手法が存在する可能性があるため、変換が最も効率的な方法であるとは限りません。さらに大きなデータセットの場合、{data.table}を{disk.frame}{ fst}、および{ drake}と組み合わせて、それを最大限に活用することができます。{big.data.table}もありますが、現在は非アクティブです。

皆さんのお役に立てば幸いです。良い一日を


2

非等価結合とローリング結合が思い浮かびます。同等の関数をdplyrに組み込む計画はないようで、dtplyrが変換するものは何もありません。

dplyrにはない再成形(reshape2の同じ関数に相当する最適化されたdcastとMelt)もあります。

すべての* _if関数と* _at関数は、現在dtplyrでも変換できませんが、それらは現在作業中です。


0

結合時に列を更新します。いくつかの.SDトリック多数のf関数そして、#rdatatableは単なるライブラリではなく、いくつかの関数で要約できないため、神は他に何を知っていますか

それ自体がエコシステム全体です

Rを始めた日以来、dplyrは必要ありませんでした。data.tableは非常に優れているため

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.