data.tableとdplyr:どちらか一方はうまくやってもらえませんか?

概要

私はdata.tableをよく知っていますが、dplyrではそれほどではありません。私はdplyr vignettesを読んだことがあります。そして、それが現れた例は、これまでの私の結論は次のとおりです。

> data.tableおよびdplyrは、多くの(すなわち、> 10-100K)グループが存在する場合を除き、他のいくつかの状況(下記のベンチマークを参照)を除いて、
> dplyrにアクセス可能な構文があります
> dplyrは潜在的なDBのやりとりを抽象化する(またはそうする)
>機能に若干の違いがあります(下記の「例/使用法」を参照)

私の考えでは、data.tableにかなり精通しているので、重いものではありません。私は両方の新しいユーザーにとって大きな要因になることを理解しています。私はdata.tableに精通している人の観点から質問された私の具体的な質問とは関係がないので、より直感的な議論を避けたいと思います。私はまた、より直感的な方法がより速い分析(確かに真実ですが、ここでも私がここで最も興味を持っているものではありません)につながることについての議論を避けたいと思います。

質問

私が知りたいことは:

>パッケージに精通している人(つまりキーストロークの組み合わせと必要なレベルの秘密主義、それぞれが良いことが少ない場合)のために、他のパッケージを使ってコードを記述するのはずっと簡単です。
> 1つのパッケージでより効率的に(つまり2倍以上)実行される分析タスクがあるのか​​、それとも別のパッケージで実行されているのか

その時点まで、私はdplyrがdata.tableですでにできることをはるかに超えて提供するとは思っていなかったので、recent SO questionはこれについてもう少し考えました。 dplyr解(Qの最後のデータ)は次のとおりです。

dat %.%
  group_by(name, job) %.%
  filter(job != "Boss" | year == min(year)) %.%
  mutate(cumu_job2 = cumsum(job2))

data.tableのソリューションでの私のハッキングよりもはるかに優れていました。つまり、良いdata.tableソリューションもかなり良いです(Jean-Robert、Arun、ありがとうございました。

setDT(dat)[,
  .SD[job != "Boss" | year == min(year)][, cumjob := cumsum(job2)], 
  by=list(id, job)
]

後者の構文は非常に難解かもしれませんが、data.tableに慣れていれば(つまり、より秘密のトリックを使用していない)、実際はかなり簡単です。

理想的には、私が見たいと思っているのは、dplyrやdata.tableの方法が実質的により簡潔であるか、実質的に優れたものであるという良い例です。

使用法

> dplyrは、eddi’s questionから任意の行数を返すグループ化された操作を許可していません(dplyr 0.5から実装されるように見えます。@beginneRは@ eddiの質問に対する答えでdoを使用する可能性があります)。
> data.tableはrolling joins(ありがとう@dholstius)とoverlap joinsをサポートしています
> data.tableは、同じ基本R構文を使用しながらバイナリ検索を使用する自動索引付けによって、速度のためにDT [col == value]またはDT [col%in%values]という形式の式を内部的に最適化します。 See hereと小さなベンチマークがあります。
> dplyrは、dplyrのプログラムによる使用を簡素化する関数の標準評価版(例えばregroup、summarize_each_)を提供しています(data.tableのプログラム的な使用は間違いありません。知識)

ベンチマーク

>私はmy own benchmarksを実行し、data.tableのポイントがかなり速くなる非常に多数のグループ(> 100K)がある場合を除いて、両方のパッケージが “分割適用結合”スタイル分析で比較可能であることを発見しました。
> @Arunはbenchmarks on joinsをいくつか実行し、グループの数が増えるにつれてdata.tableがdplyrよりも優れていることを示しています(パッケージと最近のRの両方の最新の機能拡張で更新されました)。また、unique valuesを取得しようとするときのベンチマークは、data.table〜6x速くなります。
>(Unverified)は、グループ/ apply / sortのより大きいバージョンではdata.tableの方が75%高速ですが、dplyrの方が小さい(another SO question from comments、danasに感謝します)の方が40%高速です。
> Matt、data.tableの主著者はbenchmarked grouping operations on data.table, dplyr and python pandas on up to 2 billion rows (~100GB in RAM)です。
> older benchmark on 80K groupsにはdata.table〜8x高速があります

データ

これは私が質問セクションで示した最初の例です。

dat <- structure(list(id = c(1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 2L, 2L, 
2L, 2L, 2L, 2L, 2L, 2L), name = c("Jane", "Jane", "Jane", "Jane", 
"Jane", "Jane", "Jane", "Jane", "Bob", "Bob", "Bob", "Bob", "Bob", 
"Bob", "Bob", "Bob"), year = c(1980L, 1981L, 1982L, 1983L, 1984L, 
1985L, 1986L, 1987L, 1985L, 1986L, 1987L, 1988L, 1989L, 1990L, 
1991L, 1992L), job = c("Manager", "Manager", "Manager", "Manager", 
"Manager", "Manager", "Boss", "Boss", "Manager", "Manager", "Manager", 
"Boss", "Boss", "Boss", "Boss", "Boss"), job2 = c(1L, 1L, 1L, 
1L, 1L, 1L, 0L, 0L, 1L, 1L, 1L, 0L, 0L, 0L, 0L, 0L)), .Names = c("id", 
"name", "year", "job", "job2"), class = "data.frame", row.names = c(NA, 
-16L))
包括的な答え/比較(特に重要な順序はありません):スピード、メモリ使用量、構文、および機能を提供するには、少なくともこれらの側面をカバーする必要があります。

私の意図は、これらのそれぞれをdata.tableの観点から可能な限り明確にすることです。

Note: unless explicitly mentioned otherwise, by referring to dplyr, we refer to dplyr’s data.frame interface whose internals are in C++ using Rcpp.

data.tableの構文は、DT [i、j、by]という形式で一貫しています。私とjを一緒に保つことは設計によるものです。関連するオペレーションをまとめておくことで、スピードとより重要なメモリ使用のためのオペレーションを簡単に最適化でき、構文の一貫性を維持しながら強力な機能もいくつか提供できます。

1.スピード

かなりの数のベンチマーク(グループ化操作を主にしていますが)はすでにデータを表示している質問に追加されています。グループとグループの数を増やすことでdplyrよりも速くなります。 100万〜1000万のグループとさまざまなグループ化コラム(100GBのRAM)で、パンダを比較します。

ベンチマークでは、これらの残りの側面についてもカバーするのは素晴らしいことです。

>行のサブセットを含むグループ化操作、すなわちDT [x> val、sum(y)、by = z]型演算。
>更新や結合などの他の操作をベンチマークする。
>また、ランタイムに加えて、各操作のベンチマークメモリフットプリント。

2.メモリ使用量

> dplyrのfilter()またはslice()に関連する操作は、(data.framesとdata.tablesの両方の)メモリ非効率的になります。 See this post

Note that 07002 talks about speed (that dplyr is plentiful fast for him), whereas the major concern here is memory.

> data.tableインターフェイスは、参照でカラムを変更/更新することができます(結果を変数に代入する必要はありません)。

# sub-assign by reference, updates 'y' in-place
DT[x >= 1L, y := NA]

しかし、dplyrは決して参照によって更新されません。 dplyrの等価物は次のようになります(結果を再割り当てする必要があることに注意してください)。

# copies the entire 'y' column
ans <- DF %>% mutate(y = replace(y, which(x >= 1L), NA))

これに関する懸念はreferential transparencyです。特に、関数内で参照によってdata.tableオブジェクトを更新することは、必ずしも望ましいとは限りません。しかし、これは非常に便利な機能です:興味のあるケースのthisthisの記事を参照してください。そして私たちはそれを保ちたいと思っています。

したがって、我々は両方の可能性をユーザに提供するdata.tableのshallow()関数のエクスポートに取り組んでいます。例えば、関数内の入力データテーブルを変更したくない場合は、次のようにします。

foo <- function(DT) {
    DT = shallow(DT)          ## shallow copy DT
    DT[, newcol := 1L]        ## does not affect the original DT 
    DT[x > 2L, newcol := 2L]  ## no need to copy (internally), as this column exists only in shallow copied DT
    DT[x > 2L, x := 3L]       ## have to copy (like base R / dplyr does always); otherwise original DT will 
                              ## also get modified.
}

shallow()を使用しないと、古い機能が保持されます。

bar <- function(DT) {
    DT[, newcol := 1L]        ## old behaviour, original DT gets updated by reference
    DT[x > 2L, x := 3L]       ## old behaviour, update column x in original DT.
}

shallow()を使用して浅いコピーを作成することによって、元のオブジェクトを変更したくないことがわかります。絶対に必要なときにのみ変更する列のコピーを保証しながら、すべてを内部的に管理します。これを実装すると、両方の可能性をユーザに提供しながら、参照透過性の問題を完全に解決するはずです。

Also, once shallow() is exported dplyr’s data.table interface should avoid almost all copies. So those who prefer dplyr’s syntax can use it with data.tables.

But it will still lack many features that data.table provides, including (sub)-assignment by reference.

>参加中の集計:

次のように2つのデータテーブルがあるとします。

DT1 = data.table(x=c(1,1,1,1,2,2,2,2), y=c("a", "a", "b", "b"), z=1:8, key=c("x", "y"))
#    x y z
# 1: 1 a 1
# 2: 1 a 2
# 3: 1 b 3
# 4: 1 b 4
# 5: 2 a 5
# 6: 2 a 6
# 7: 2 b 7
# 8: 2 b 8
DT2 = data.table(x=1:2, y=c("a", "b"), mul=4:3, key=c("x", "y"))
#    x y mul
# 1: 1 a   4
# 2: 2 b   3

DT2の各行に対してsum(z)* mulを取得し、列x、yで結合したいとします。次のいずれかを行うことができます

> 1)総和(z)を得るためにDT1を集計する、2)結合を実行する、3)

# data.table way
DT1[, .(z=sum(z)), keyby=.(x,y)][DT2][, z := z*mul][]

# dplyr equivalent
DF1 %>% group_by(x,y) %>% summarise(z=sum(z)) %>% 
    right_join(DF2) %>% mutate(z=z*mul)

> 2)一度にすべてを行う(.EACHI機能を使用)。

DT1[DT2, list(z=sum(z) * mul), by=.EACHI]

利点は何ですか?

>中間結果にメモリを割り当てる必要はありません。
>グループ化/ハッシュを2回行う必要はありません(1つは集計用、もう1つは結合用)。
>さらに重要なのは、(2)のjを見ることで、実行したい操作がはっきりしていることです。

by = .EACHIの詳細な説明については、this postを参照してください。中間結果は実現されず、結合集約はすべて一度に実行されます。

実際の使用シナリオについては、thisthisthisの記事をご覧ください。

dplyrでは、join and aggregate or aggregate first and then joinにする必要があります。どちらも効率的ではなく、メモリの面ではスピードに変わります。
>更新と結合:

以下に示すdata.tableコードを考えてみましょう:

DT1[DT2, col := i.mul]

DT2のキー列がDT1と一致する行で、DT1の列colをDT2のmulで追加/更新します。私はdplyrにこの操作と全く同じことがあるとは考えていません。つまり、DT1全体をコピーして新しい列を追加する必要がある* _join操作を避ける必要はありません。

実際の使用シナリオについては、this postを確認してください。

To summarise, it is important to realise that every bit of optimisation matters. As 070012 would say, 070013!

3.構文

構文を見てみましょう。ハドリーコメントhere

Data tables are extremely fast but I think their concision makes it harder to learn and code that uses it is harder to read after you have written it

私は非常に主観的であるので、このコメントは無意味であることがわかります。我々が試みることができるのは、構文の一貫性を対比することです。 data.tableとdplyrの構文を並べて比較します。

以下に示すダミーデータを使用して作業します。

DT = data.table(x=1:10, y=11:20, z=rep(1:2, each=5))
DF = as.data.frame(DT)

>基本的な集約/更新操作。

# case (a)
DT[, sum(y), by=z]                       ## data.table syntax
DF %>% group_by(z) %>% summarise(sum(y)) ## dplyr syntax
DT[, y := cumsum(y), by=z]
ans <- DF %>% group_by(z) %>% mutate(y = cumsum(y))

# case (b)
DT[x > 2, sum(y), by=z]
DF %>% filter(x>2) %>% group_by(z) %>% summarise(sum(y))
DT[x > 2, y := cumsum(y), by=z]
ans <- DF %>% group_by(z) %>% mutate(y = replace(y, which(x>2), cumsum(y)))

# case (c)
DT[, if(any(x > 5L)) y[1L]-y[2L] else y[2L], by=z]
DF %>% group_by(z) %>% summarise(if (any(x > 5L)) y[1L]-y[2L] else y[2L])
DT[, if(any(x > 5L)) y[1L]-y[2L], by=z]
DF %>% group_by(z) %>% filter(any(x > 5L)) %>% summarise(y[1L]-y[2L])

> data.table構文はコンパクトであり、dplyrは非常に冗長です。事は多かれ少なかれ同等の場合(a)。
>(b)の場合、要約中にdplyrでfilter()を使用する必要がありました。しかし、更新中は、ロジックをmutate()内に移動しなければなりませんでした。しかし、data.tableでは、両方の演算を同じ論理で表現する。x> 2であるが、最初のケースでは、sum(y)を得るが、2番目のケースでは、それらの行を累積合計で更新する。

これは、DT [i、j、by]形式が一貫していると言うときの意味です。
>ケース(c)の場合と同様に、if-else条件が成立すると、data.tableとdplyrの両方で論理を「そのまま」表現することができます。しかし、if条件が満たされていない行を返す場合は、summarize()を直接使用することはできません(AFAICT)。 summarize()は常に単一の値を期待するため、filter()を最初に実行してから集計しなければなりません。

これは同じ結果を返しますが、ここでfilter()を使用すると、実際の操作はあまり明白になりません。

最初のケースでもfilter()を使うことは可能かもしれませんが(私には分かりません)、私の要点は私たちがするべきではないということです。

>複数の列での集計/更新

# case (a)
DT[, lapply(.SD, sum), by=z]                     ## data.table syntax
DF %>% group_by(z) %>% summarise_each(funs(sum)) ## dplyr syntax
DT[, (cols) := lapply(.SD, sum), by=z]
ans <- DF %>% group_by(z) %>% mutate_each(funs(sum))

# case (b)
DT[, c(lapply(.SD, sum), lapply(.SD, mean)), by=z]
DF %>% group_by(z) %>% summarise_each(funs(sum, mean))

# case (c)
DT[, c(.N, lapply(.SD, sum)), by=z]     
DF %>% group_by(z) %>% summarise_each(funs(n(), mean))

>(a)の場合、コードは多かれ少なかれ同等です。 data.tableは馴染みのある基底関数lapply()を使いますが、dplyrはfuns()にたくさんの関数と共に* _each()を導入しています。
> data.tableの:=は列名を指定する必要がありますが、dplyrは自動的にそれを生成します。
>(b)の場合、dplyrの構文は比較的単純です。複数の関数の集計/更新の改善はdata.tableのリストにあります。
>(c)の場合、dplyrはn()を何度も何度も返すでしょう。 data.tableでは、jでリストを返すだけです。リストの各要素は、結果の列になります。ですから、もう一度親しみやすい基本関数c()を使って.Nをリストを返すリストに連結することができます。

Note: Once again, in data.table, all we need to do is return a list in j. Each element of the list will become a column in result. You can use c(), as.list(), lapply(), list() etc… base functions to accomplish this, without having to learn any new functions.

You will need to learn just the special variables – .N and .SD at least. The equivalent in dplyr are n() and .

>結合

dplyrは、data.tableが同じ構文DT [i、j、by](およびreasonを使用)を使用して結合を許可するように、ジョインの各タイプに対して別々の関数を提供します。また、同等のmerge.data.table()関数を代替として提供します。

setkey(DT1, x, y)

# 1. normal join
DT1[DT2]            ## data.table syntax
left_join(DT2, DT1) ## dplyr syntax

# 2. select columns while join    
DT1[DT2, .(z, i.mul)]
left_join(select(DT2, x,y,mul), select(DT1, x,y,z))

# 3. aggregate while join
DT1[DT2, .(sum(z)*i.mul), by=.EACHI]
DF1 %>% group_by(x, y) %>% summarise(z=sum(z)) %>% 
    inner_join(DF2) %>% mutate(z = z*mul) %>% select(-mul)

# 4. update while join
DT1[DT2, z := cumsum(z)*i.mul, by=.EACHI]
??

# 5. rolling join
DT1[DT2, roll = -Inf]
??

# 6. other arguments to control output
DT1[DT2, mult = "first"]
??

>他の人がdata.tableのDT [i、j、by]、またはmerge()のように好きかもしれない一方で、いくつかは、それぞれの別々の関数がより良く(左、右、内側、反、塩基Rと同様である。
>しかし、dplyr結合はそれだけです。これ以上何もない。それ以下のものはありません。
> data.tablesはjoin(2)の間に列を選択することができ、dplyrでは上記のように結合する前に両方のdata.framesを最初に選択する必要があります。それ以外の場合は、後で削除するために不要な列のみを使用して結合を行いますが、これは非効率的です。
> data.tablesは、結合(3)中に集約し、結合(4)中に更新することもできます。by = .EACHI機能を使用します。なぜ、いくつかの列を追加/更新するために結合結果全体をマテリアル化するのですか?
> data.tableはロール結合(5) – ロールforward, LOCFroll backward, NOCBnearestを実行できます。
> data.tableには、最初に、最後に、またはすべてのマッチ(6)を選択するmult =引数もあります。
> data.tableには、誤った無効な結合から保護するためのallow.cartesian=TRUE引数があります。

Once again, the syntax is consistent with DT[i, j, by] with additional arguments allowing for controlling the output further.

> do()…

dplyrの要約は、単一の値を返す関数用に特別に設計されています。あなたの関数が複数の/等しくない値を返す場合、do()を使う必要があります。関数の戻り値はすべて事前に知っておく必要があります。

DT[, list(x[1], y[1]), by=z]                 ## data.table syntax
DF %>% group_by(z) %>% summarise(x[1], y[1]) ## dplyr syntax
DT[, list(x[1:2], y[1]), by=z]
DF %>% group_by(z) %>% do(data.frame(.$x[1:2], .$y[1]))

DT[, quantile(x, 0.25), by=z]
DF %>% group_by(z) %>% summarise(quantile(x, 0.25))
DT[, quantile(x, c(0.25, 0.75)), by=z]
DF %>% group_by(z) %>% do(data.frame(quantile(.$x, c(0.25, 0.75))))

DT[, as.list(summary(x)), by=z]
DF %>% group_by(z) %>% do(data.frame(as.list(summary(.$x))))

> .SDはこれと同等です。
> data.tableでは、jの中に何かをスローすることができます。リストの各要素がカラムに変換されるようにリストを返すことだけが覚えておいてください。
> dplyrでは、できません。あなたの関数が常に単一の値を返すかどうかについての確信度に応じて、do()に頼らなければなりません。そしてそれはかなり遅いです。

Once again, data.table’s syntax is consistent with DT[i, j, by]. We can just keep throwing expressions in j without having to worry about these things.

this SO questionthis oneを見てください。私はdplyrの構文を使って簡単に答えを表現できるかどうか疑問に思っています…

To summarise, I have particularly highlighted several instances where dplyr’s syntax is either inefficient, limited or fails to make operations straightforward. This is particularly because data.table gets quite a bit of backlash about “harder to read/learn” syntax (like the one pasted/linked above). Most posts that cover dplyr talk about most straightforward operations. And that is great. But it is important to realise its syntax and feature limitations as well, and I am yet to see a post on it.

data.table has its quirks as well (some of which I have pointed out that we are attempting to fix). We are also attempting to improve data.table’s joins as I have highlighted 070021.

But one should also consider the number of features that dplyr lacks in comparison to data.table.

4.特長

私はhereの機能のほとんどとこのポストで指摘しています。加えて:

> fread – 高速のファイルリーダーが長い間利用可能になりました。
> fwrite – 現在の開発者v1.9.7のNEW、並列化された高速ファイルライターが利用可能になりました。実装の詳細な説明についてはthis postを参照し、さらに進展を追跡するためには#1664を参照してください。
> Automatic indexing – 基本R構文をそのまま内部最適化するためのもう1つの便利な機能。
> Ad-hoc grouping:dplyrは、summarize()中に変数をグループ化して結果を自動的にソートします。これは必ずしも望ましいとは限りません。
>上記のdata.table結合(速度/メモリ効率と構文のための)に多数の利点があります。
>非等価結合:v1.9.7から利用可能な新しい機能です。これは、他の演算子<=、>> =を使用して、データテーブルジョインの他のすべての利点と共にジョインを可能にします。
> Overlapping range joinsが最近data.tableに実装されました。ベンチマークの概要については、this postを参照してください。
> data.tableのsetorder()関数は、参照によってdata.tablesを本当に速く並べ替えることができます。
> dplyrは同じ構文を使用してinterface to databasesを提供しますが、data.tableは現時点ではありません。
> data.tableは、全ての引数を(SQLのように)追加して、fsetdiff()、fintersect()、funion()、およびfsetequal()のv1.9.7(Jan Goreckiによって書かれたもの)からのより速いセット操作を提供します。
> data.tableはマスキング警告なしできれいにロードされ、Rパッケージに渡されたときの[.data.frame互換性のためのhereのメカニズムについて説明しています。 dplyrは基本関数のフィルタ、遅れおよび[問題を引き起こす可能性があります。例えばhereおよびhere

最後に:

>データベース上で – data.tableが似たようなインターフェースを提供できない理由はありませんが、これは優先事項ではありません。ユーザーがその機能を非常に気に入っていれば、それはうまくいかないかもしれません。
>並列性 – 誰かが先に進んでそれをやるまでは、すべてが難しいです。もちろん、それは努力を要する(スレッドセーフである)。

>現在、OpenMPを使用して増分的な性能向上を行うために、既知の時間がかかる部分を並列化するための進化が行われています(v1.9.7)。

関連記事

    元のURL:http://stackoverflow.com/questions/21435339/data-table-vs-dplyr-can-one-do-something-well-the-other-cant-or-does-poorly

    元のテキストを転載:data.tableとdplyr:どちらか一方はうまくやってもらえませんか?