data.table対dplyr:他の人ができないことやうまくできないことはできますか?


759

概観

私はに比較的慣れていますがdata.table、にはあまり詳しくありませんdplyr。私はSOに現れたdplyrビネットと例をいくつか読みましたが、これまでのところ、私の結論は次のとおりです。

  1. data.tableそしてdplyrスピードに匹敵する、多くの(すなわち> 10-100K)グループがある場合を除いて、いくつかの他の状況(下のベンチマークを参照)
  2. dplyr よりアクセスしやすい構文があります
  3. dplyr 潜在的なDB相互作用を抽象化します(またはそうします)
  4. いくつかの小さな機能の違いがあります(以下の「例/使用法」を参照)

私の心の中で2.私はそれにかなり慣れているのでdata.table、それほど重くはありませんが、両方に不慣れなユーザーにとっては、それが大きな要因になることは理解しています。どちらがより直感的であるかについての議論は避けたいと思います。これは、すでに詳しい人の観点から尋ねられた私の特定の質問とは無関係であるためdata.tableです。また、「より直感的」な方が分析が速くなることについての議論は避けたいと思います(確かにそうですが、ここでも、私が最も興味を持っていることはありません)。

質問

私が知りたいのは:

  1. パッケージに精通している人にとっては、どちらか一方のパッケージを使用してコーディングする方がはるかに簡単な分析タスクがあります(つまり、必要なキーストロークと難解性の必要なレベルの組み合わせ。
  2. あるパッケージと別のパッケージで大幅に(つまり2倍以上)より効率的に実行される分析タスクはありますか?

最近のSOの質問の 1つで、これについてもう少し考えるようになりました。それまでは、dplyr私がすでにできることをはるかに超えるとは思わなかったからdata.tableです。ここに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ませんが、慣れていれば(つまり、より難解なトリックを使用しない場合)、実際にはかなり簡単です。

理想的には私が見てみたいことはいくつかの良い例がなかったですdplyrdata.tableより簡潔であるか、パフォーマンスが大幅に優れているか方法であるです。

使用法
  • dplyr任意の行数を返すグループ化された操作は許可されません(eddiの質問から、注:これはdplyr 0.5で実装されるようです。また、do@ beginneRは@eddiの質問への回答で使用される潜在的な回避策を示しています)。
  • data.tableローリング結合(@dholstiusに感謝)およびオーバーラップ結合をサポート
  • data.table内部形式の表現最適化DT[col == value]又はDT[col %in% values]ためのスピードを介して自動インデキシング使用してバイナリ検索を同じベースR構文を使用しています。いくつかの詳細と小さなベンチマークについては、こちらご覧ください
  • dplyrのプログラムによる使用を簡素化できる関数の標準評価バージョン(regroupなどsummarize_each_)を提供しますdplyr(プログラムによる使用data.tableは間違いなく可能であり、少なくとも私の知る限り、慎重な検討、置換/引用などが必要です)
ベンチマーク

データ

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

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))

9
読んで同様なソリューションdplyrものである:as.data.table(dat)[, .SD[job != "Boss" | year == min(year)][, cumjob := cumsum(job2)], by = list(name, job)]
エディ・

7
#1の場合dplyrdata.tableチームとチームの両方がベンチマークに取り組んでいるため、ある時点で答えが出ます。#2(構文)imOは厳密に偽ですが、それは明らかに意見の領域に進入するので、私も閉じることに投票します。
eddi、2014年

13
さて、再びimOで、より明確に表現され(d)plyrている一連の問題の測定値は0です
eddi

28
@BrodieGの両方dplyrplyr構文に関して本当に私を悩ませ、基本的に私がそれらの構文を嫌う主な理由である1つのことは、あまりにも多くの(1つ以上読む)追加の関数(まだ名前が私にとって意味をなさないでください)、彼らが何をするか、彼らが取る議論などを覚えておいてください。
eddi、2014年

43
@eddi [舌の頬] data.table構文について本当に私を悩ませている1つのことは、あまりに多くの関数引数がどのように相互作用するか、そして不可解なショートカットが何を意味するか(例えば.SD)を学ばなければならないということです。[真剣に]これらは、さまざまな人々にアピールする正当なデザインの違いだと思います
ハドリー2014年

回答:


532

:私たちは、(重要性の順不同)包括的な答え/比較を提供するために、少なくともこれらの側面をカバーする必要があるSpeedMemory usageSyntaxFeatures

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

注:特に明記されていない限り、dplyrを参照することにより、内部がRcppを使用してC ++にあるdplyrのdata.frameインターフェイスを参照します。


data.table構文の形式は一貫しています- DT[i, j, by]。維持することijおよびby一緒にすることは設計によるものです。関連する操作をまとめることにより、操作の速度最適化し、より重要なメモリ使用量簡単に最適化できます。また、構文の一貫性を維持しながら、いくつかの強力な機能を提供できます。

1.スピード

かなりの数のベンチマーク(ほとんどの操作をグループ化するにかかわらず)は、すでにdata.tableが取得する表示の質問に追加された高速化を含め、増加によりdplyrより基および/または行数などのグループにマットにより、ベンチマークからのグループ化にまで千万1億から1000万のグループとさまざまなグループ化列で20億行(RAMで100 GB)pandas。これも比較されます。およびを含む更新されたベンチマークも参照してください。Sparkpydatatable

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

  • 行のサブセットを含むグループ化操作-つまり、DT[x > val, sum(y), by = z]タイプ操作。

  • 更新結合などの他の操作のベンチマーク。

  • ランタイムに加えて、各操作のメモリフットプリントもベンチマークします。

2.メモリ使用量

  1. dplyr を含む、filter()またはslice()dplyrでの操作は、(data.framesとdata.tablesの両方で)非効率的なメモリになる可能性があります。この投稿を参照してください

    ハドリーのコメント速度について述べている(dplyrは彼にとっては十分に速い)が、ここでの主な関心事はメモリであることに注意してください。

  2. 現時点で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))

    これに対する懸念は、参照の透明性です。特に関数内での参照によるdata.tableオブジェクトの更新は、常に望ましいとは限りません。しかし、これは非常に便利な機能です。興味深い事例についてこの記事とこの投稿を参照してください。そしてそれを守りたいのです。

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

    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()、元のオブジェクトを変更したくないことがわかります。私たちはすべてを内部で処理し、変更が必要な場合にのみ変更した列を確実にコピーするようにします。これを実装すると、参照の透明性の問題がすべて解決され、ユーザーに両方の可能性が提供されます。

    また、一度shallow()エクスポートされたdplyrのdata.tableインターフェイスは、ほとんどすべてのコピーを回避する必要があります。したがって、dplyrの構文を好む人は、data.tablesでそれを使用できます。

    しかし、参照による(サブ)割り当てなど、data.tableが提供する多くの機能はまだ不足しています。

  3. 参加中の集計:

    次の2つのdata.tablesがあるとします。

    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

    そして、列sum(z) * mulごとにDT2結合しながら、各行を取得したいとしますx,y。次のいずれかを実行できます。

    • 1)DT1取得する集約sum(z)、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)すべてを一度に実行する(by = .EACHI機能を使用):

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

    利点は何ですか?

    • 中間結果にメモリを割り当てる必要はありません。

    • グループ化/ハッシュを2回行う必要はありません(1つは集約用、もう1つは結合用)。

    • さらに重要なことにj、(2)を見ると、実行したい操作が明確です。

    の詳細な説明については、この投稿を確認してくださいby = .EACHI。中間結果は具体化されず、join + aggregateはすべて一度に実行されます。

    見ていこれをこれこれが本当の使用シナリオのためのポスト。

    では、最初dplyr結合して集約するか、または集約してから結合する必要がありますが、どちらもメモリの点で効率的ではありません(つまり、速度に変換されます)。

  4. 更新と参加:

    以下に示すdata.tableコードを検討してください。

    DT1[DT2, col := i.mul]

    のキー列が一致する行にfromを使用してDT1の列colを追加/更新します。私には、この操作と完全に同等の操作はないと思います。つまり、操作を回避せずに、新しい列を追加するために全体をコピーする必要があり、これは不要です。mulDT2DT2DT1dplyr*_joinDT1

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

要約すると、あらゆる最適化が重要であることを認識することが重要です。以下のようグレース・ホッパーは言うでしょう、あなたのナノ秒マインド

3.構文

構文を見てみましょう。ハドリーはここにコメントしました

データテーブルは非常に高速ですが、その簡潔さのために学習が難しくなり、それを使用するコードは作成した後に読むことが難しくなります ...

この発言は非常に主観的であるため、無意味だと思います。おそらく私たちが試すことができるのは、構文の一貫性を対比することですです。data.tableとdplyrの構文を並べて比較します。

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

DT = data.table(x=1:10, y=11:20, z=rep(1:2, each=5))
DF = as.data.frame(DT)
  1. 基本的な集計/更新操作。

    # 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)では、を要約しfilter()ながらdplyrで使用する必要がありました。しかし、更新中、ロジックを内部に移動する必要がありました。ただし、data.tableでは、両方の操作を同じロジックで表現します。最初の場合はget である行を操作し、2番目の場合はそれらの行を累積合計で更新します。mutate()x > 2sum(y)y

      これは、DT[i, j, by]フォームが一貫していると言うときの意味です。

    • 同様に(c)の場合、if-else条件がある場合、data.tableとdplyrの両方で「現状のまま」のロジックを表現できます。ただし、if条件が満たされる行のみを返し、それ以外の場合はスキップする場合は、summarise()直接使用できません(AFAICT)。常に単一の値を期待しているfilter()ため、最初に要約summarise()する必要があります

      同じ結果を返しますが、filter()ここを使用すると実際の操作がわかりにくくなります。

      filter()最初のケースでも使用できる可能性は十分にあります(私には自明ではないようです)が、私のポイントは、そうする必要がないということです。

  2. 複数の列での集計/更新

    # 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導入*_each()funs()ます。

    • data.tableに:=は列名を指定する必要がありますが、dplyrはそれを自動的に生成します。

    • (b)の場合、dplyrの構文は比較的単純です。複数の関数の集計/更新の改善は、data.tableのリストにあります。

    • ただし(c)の場合、dplyrはn()1回だけではなく、何倍もの列を返します。data.tableでは、リストをで返すだけですj。リストの各要素は結果の列になります。したがって、もう一度、おなじみの基本関数c()を使用.Nして、listを返すa に連結できますlist

    注:もう一度、data.tableで行う必要があるのは、にリストを返すことだけですj。リストの各要素は結果として列になります。あなたが使用することができc()as.list()lapply()list()など...基本機能をすべての新しい機能を学ぶことなく、これを達成するために。

    あなただけの特別な変数を習得する必要があります- .N.SD、少なくとも。dplyrで同等のものはn().

  3. 参加

    dplyrは、結合のタイプごとに個別の関数を提供します。ここで、data.tableは、同じ構文DT[i, j, by](および理由あり)を使用した結合を可能にします。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's DT[i, j, by]や、merge()ベースRに似ている場合もあります。

    • ただし、dplyr結合はそれを行います。これ以上何もない。何も少ない。

    • data.tablesは結合(2)中に列を選択できます。dplyrでは、select()上記のように結合する前に両方のdata.framesで最初に行う必要があります。それ以外の場合は、不要な列との結合を具体化して後で削除するだけであり、これは非効率的です。

    • data.tablesは、機能を使用して、結合中(3)に集約したり、結合(4)に更新したりできby = .EACHIます。結合結果全体をマテリアライズして、数列だけを追加/更新するのはなぜですか?

    • data.tableができるローリング参加する(5) -ロールフォワード、LOCF後方ロール、NOCB最寄りの

    • data.tableには、最初最後、またはすべての一致mult =を選択する引数もあります(6)。

    • data.tableにはallow.cartesian = TRUE、偶発的な無効な結合から保護するための引数があります。

ここでも、構文はDT[i, j, by]、出力をさらに制御できるようにする追加の引数と一致しています。

  1. 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()関数が常に単一の値を返すかどうかに応じて、頼りにする必要があります。そして、それはかなり遅いです。

ここでも、data.tableの構文はと一致していDT[i, j, by]ます。jこれらのことを心配することなく、式を使い続けることができます。

このSOの質問これを見てください。dplyrの構文を使用して答えを簡単に表現することは可能でしょうか...

要約すると、私は特にいくつかを強調しました dplyrの構文が非効率的であるか、制限されているか、操作を単純化できない例。これは特に、data.tableが「読み取り/学習が難しい」構文についてかなりの反発を受けるためです(上記で貼り付け/リンクした構文のように)。dplyrに関するほとんどの投稿では、最も簡単な操作について説明しています。そしてそれは素晴らしいことです。しかし、その構文と機能の制限も理解することが重要であり、私はまだそれについての投稿を見ていません。

data.tableにも奇妙な点があります(そのうちのいくつかは、修正を試みていることを指摘しました)。ここで強調しように、data.tableの結合も改善しようとしています

しかし、data.tableと比較して、dplyrに欠けている機能の数も考慮する必要があります。

4.機能

私はこことこの投稿でほとんどの機能を指摘しました。加えて:

  • fread-高速ファイルリーダーが長い間利用可能になっています。

  • fwriteの - 並列化高速ファイルライターが利用可能になりました。実装の詳細な説明についてはこの投稿を参照し、さらなる開発を追跡するための#1664を参照してください。

  • 自動インデックス作成 -ベースR構文をそのまま内部的に最適化するもう1つの便利な機能。

  • アドホックグループ化dplyrsummarise()に変数をグループ化することで結果を自動的に並べ替えますが、常に望ましいとは限りません。

  • 上記のdata.table結合(速度/メモリ効率と構文)の多くの利点。

  • 非エクイが参加する:他の演算子を使用してジョインできます<=, <, >, >=data.tableの他のすべての利点と一緒に参加します。

  • 最近、範囲結合の重複がdata.tableに実装されました。ベンチマークの概要については、この投稿を確認してください。

  • setorder() 参照によるdata.tablesの非常に高速な並べ替えを可能にするdata.tableの関数。

  • dplyrは、同じ構文を使用してデータベースへのインターフェースを提供しますが、data.tableは現時点では使用しません。

  • data.table速い同等提供集合演算 - (月Goreckiによって書かれた)fsetdifffintersectfunionおよびfsetequal追加のとall引数(SQLのように)。

  • data.tableは、マスキング警告なしでクリーンにロードされ、Rパッケージに渡される場合の互換性のためにここで説明さているメカニズムを備えて[.data.frameます。dplyrは、基本機能を変更しfilterlagそして[問題が発生する可能性があり、例えばここここ


最終的に:

  • データベース-data.tableが同様のインターフェースを提供できない理由はありませんが、これは現在優先事項ではありません。ユーザーがその機能を非常に好むとしたら、それはぶつかるかもしれません。

  • 並列処理について-誰かが先に行ってそれを行うまで、すべてが困難です。もちろん、(スレッドセーフであるため)努力は必要です。

    • 現在(v1.9.7開発版で)を使用して、既知の時間のかかる部分を並列化し、を使用してパフォーマンスを向上させていOpenMPます。

9
@bluefeet:その議論をチャットに移すことで、他の素晴らしいサービスを提供してくれたとは思いません。私はアルンが開発者の一人であるという印象を受けました、そしてこれは有用な洞察をもたらしたかもしれません。
IRTFM 2015年

2
あなたのリンクを使用してチャットに行ったところ、「フィルターを使用する必要があります」で始まるコメントに続く資料がすべてなくなっているようです。SOチャットのメカニズムについて何か不足していますか?
IRTFM 2015年

6
参照による割り当て(:=)を使用しているすべての場所で、dplyr同等のものを単に<-ではDF <- DF %>% mutate...なくasとして使用する必要があると思いますDF %>% mutate...
David Arenburg

4
構文について。構文にdplyr慣れていたユーザーにとっては簡単かもしれませんが、のような言語の構文やその背後にある関係代数をクエリするために使用されたユーザーにとっては簡単であると私は信じています。@Arunは、集合演算子は関数をラップすることで非常に簡単に実行でき、もちろん大幅なスピードアップをもたらすことに注意してください。plyrdata.tableSQLdata.table
jangorecki

9
私はこの投稿を何度も読みましたが、data.tableを理解し、それをよりよく使用するのに大いに役立ちました。私は、ほとんどの場合、dplyrやpandas、PL / pgSQLよりもdata.tableを好みます。でも、どう表現したらいいのか考えずにはいられませんでした。構文は簡単ではなく、明確でも冗長でもありません。実際、data.tableを何度も使用した後でも、自分のコードを理解するのに苦労していることが多く、文字どおり1週間前に作成しました。これは書き込み専用言語の実例です。en.wikipedia.org/wiki/Write-only_language では、data.tableでdplyrを使用できるようになることを願っています。
Ufos

385

これは、Arunの回答の大まかな概要に従っています(ただし、優先順位の違いに基づいて多少整理されています)。

構文

構文にはいくつかの主観性がありますが、data.tableを簡潔にすると、学習や読み取りが難しくなるという私の見解を支持します。これは、dplyrがはるかに簡単な問題を解決しているためです。

dplyrがあなたのために行う本当に重要なことの1つは、オプションを制約すること です。ほとんどの単一テーブルの問題は、「グループごと」の副詞とともに、5つの主要な動詞のフィルター、選択、変更、配置、および要約だけで解決できると主張します。この制約は、問題についての考えを整理するのに役立つため、データ操作を学ぶときに大きな助けになります。dplyrでは、これらの動詞のそれぞれが単一の関数にマップされます。各関数は1つの仕事を行い、分離して理解するのは簡単です。

これらの単純な操作を一緒にパイプすることによって複雑さを作成し %>%ます。これは、アルンの投稿の例ですリンクされてます。

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

以前にdplyr(またはR!)を見たことがない場合でも、関数はすべて英語の動詞であるため、何が起こっているのかを知ることができます。英語の動詞の短所は[、よりも多くのタイピングが必要になることですが 、オートコンプリートを改善することで大幅に軽減できると思います。

同等のdata.tableコードは次のとおりです。

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

data.tableに精通していない限り、このコードに従うのは困難です。(また、繰り返しをインデントする方法がわからない[ ように見えました)。個人的には、6か月前に書いたコードを見ると、見知らぬ人が書いたコードを見るようなものです。そのため、私は、冗長であるとしても、単純明快なコードを好むようになりました。

読みやすさをわずかに低下させると思う他の2つのマイナーな要素:

  • ほとんどすべてのデータテーブル操作は使用[しているため、何が起こっているのかを理解するために追加のコンテキストが必要です。たとえば、x[y] 2つのデータテーブルを結合したり、データフレームから列を抽出したりしていますか?これは小さな問題にすぎません。適切に記述されたコードでは、変数名が何が起こっているのかを示唆しているからです。

  • 私はそれgroup_by()がdplyrの別の操作であるのが好きです。それは計算を根本的に変更するので、コードをざっと見れば明白であるはずだと思います。そしてへgroup_by()by議論よりも簡単に見つけることができます[.data.table

また、パイプ は1つのパッケージに限定されないことも気に入っています。あなたがあなたのデータを片付けことから始めることができ tidyr、とのプロットで仕上げるggvis。そして、あなたは私が書くパッケージに限定されません-誰でもデータ操作パイプのシームレスな部分を形成する関数を書くことができます。実際、私は以前のdata.tableコードを次のように書き直した方がいいです%>%

diamonds %>% 
  data.table() %>% 
  .[cut != "Fair", 
    .(AvgPrice = mean(price),
      MedianPrice = as.numeric(median(price)),
      Count = .N
    ), 
    by = cut
  ] %>% 
  .[order(-Count)]

パイプラインのアイデアは、%>%データフレームだけに限定されず、他のコンテキストに簡単に一般化できます:インタラクティブなウェブグラフィックスウェブスクレイピング要点ランタイムコントラクトなど...)

メモリとパフォーマンス

私にはこれらはそれほど重要ではないので、私はこれらをひとまとめにしました。ほとんどのRユーザーは100万行をはるかに下回るデータを処理します。dplyrは、処理時間を認識していないそのサイズのデータ​​に対して十分に高速です。中程度のデータの表現力のためにdplyrを最適化します。より大きなデータの速度をそのままにするには、data.tableを自由に使用してください。

dplyrの柔軟性は、同じ構文を使用してパフォーマンス特性を簡単に調整できることも意味します。データフレームバックエンドを使用したdplyrのパフォーマンスが十分でない場合は、data.tableバックエンドを使用できます(ただし、機能がいくらか制限されています)。作業しているデータがメモリに収まらない場合は、データベースバックエンドを使用できます。

そうは言っても、dplyrのパフォーマンスは長期的には向上します。基数の順序付けや結合とフィルターに同じインデックスを使用するなど、data.tableの優れたアイデアのいくつかを確実に実装します。複数のコアを活用できるように、並列化にも取り組んでいます。

特徴

2015年に取り組む予定のいくつかの事項:

  • readrと同様に、ファイルをディスクからメモリに簡単に取得できるようにするためのパッケージfread()

  • 非等結合のサポートを含む、より柔軟な結合。

  • ブートストラップサンプル、ロールアップなどのより柔軟なグループ化

また、RのデータベースコネクタWeb APIと通信する機能、HTMLページのスクレイピングを容易にする機能 の改善にも時間を費やしてい ます


27
余談ですが、私はあなたの引数の多くに同意します(data.table私自身は構文を好みますが)が、スタイルが気に入らなければ%>%、パイプdata.table操作のために簡単に使用できます[%>%はに固有でdplyrはなく、別のパッケージ(たまたまあなたも共著者です)から提供されるため、構文のほとんどの段落であなたが何を言おうとしているのか理解できません。
David Arenburg、2015年

11
@DavidArenburg良い点。構文を書き直して、うまくいけば私の主なポイントが何であるかをより明確にし、%>%data.tableで使用できることを強調しました
ハドリー

5
Hadleyに感謝します。これは有用な視点です。私が通常インデントすることDT[\n\texpression\n][\texpression\n]gist)は、実際にはかなりうまくいきます。構文のアクセシビリティについてではなく、私の特定の質問に彼がより直接的に答えるので、私はアルンの答えを答えとして保持していますが、これはdplyrdata.table
BrodieG、2015年

33
すでにあるのに、なぜfastreadに取り組んでいるのfread()ですか?fread()の改善や他の(未開発の)作業に時間を費やしたほうがいいのではないでしょうか。
EDi、2015年

10
のAPIはdata.table[]表記法の乱用に基づいています。それが最大の強みであり、最大の弱みです。
Paul

65

質問のタイトルに直接応答して...

dplyr 絶対にdata.tableできないことをします。

あなたのポイント#3

dplyrは潜在的なDB相互作用を抽象化します

あなた自身の質問への直接の答えですが、十分に高いレベルに引き上げられていません。dplyrdata.table、単一の拡張機能である、複数のデータストレージメカニズムへの拡張可能なフロントエンドです。

dplyrターゲットとハンドラーを自由に拡張できる同じ文法を使用するすべてのターゲットを備えたバックエンド不可知論的インターフェースとして見てください。観点data.tableからdplyr見ると、これらのターゲットの1つです。

data.tableクエリを変換して、ディスク上またはネットワーク上のデータストアで動作するSQLステートメントを作成しようとする日は決して(私は望みません)目にしません。

dplyr可能性のあることもできないことも可能ですdata.table

インメモリで動作するように設計data.tableされているため、をクエリの並列処理に拡張するのは、よりもはるかに困難な場合がありdplyrます。


体内の質問に答えて...

使用法

パッケージに精通している人にとって、どちらか一方のパッケージコーディングする方がはるかに簡単な分析タスクはありますか(つまり、必要なキーストロークと難解さのレベルの組み合わせ。

これはパントのように見えるかもしれませんが、本当の答えはノーです。ツールに精通している人々は、彼らに最も精通しているもの、または実際に目前の仕事に適切なものを使用しているようです。そうは言っても、特定の読みやすさ、あるレベルのパフォーマンスを提示したい場合もあれば、十分に高いレベルの両方が必要な場合は、抽象化を明確にするためにすでに持っているものに対応するための別のツールが必要な場合もあります。 。

パフォーマンス

あるパッケージと別のパッケージで大幅に(つまり2倍以上)より効率的に実行される分析タスクはありますか?

繰り返しますが、違います。 data.tableすべてにおいて有効であることに優れて、それがどこdplyr基礎となるデータストアと登録されたハンドラにいくつかの点で制限されるの負担を取得します。

これは、パフォーマンスの問題が発生したdata.tableときに、クエリ関数にあることを確認でき、それ実際にボトルネックになっているdata.table場合は、レポートを作成する喜びを得たことを意味します。がバックエンドとしてdplyr使用さdata.tableれている場合も同様です。あなたがかもしれ参照いくつかのオーバーヘッドがdplyrなく、オッズは、それはあなたのクエリです。

dplyrバックエンドのパフォーマンスの問題がある場合、ハイブリッド評価用の関数を登録するか、(データベースの場合)実行前に生成されたクエリを操作することで回避できます。

また、どのplyrがdata.tableより優れているかについての受け入れられた回答も参照してください


3
dplyrはtbl_dtでdata.tableをラップできませんか?なぜ両方の世界を最大限に活用しないのですか?
aaa90210 14

22
逆のステートメント「data.tableは間違いなくdplyrができないことを行う」に言及するのを忘れますが、これも当てはまります。
jangorecki

25
アルンの答えはそれをよく説明しています。(パフォーマンスの観点から)最も重要なのは、恐怖、参照による更新、ローリング結合、結合のオーバーラップです。これらの機能と競合できるパッケージはありません(dplyrだけでなく)。このプレゼンテーションの最後のスライドが良い例です。
jangorecki

15
完全に、私がまだRを使用しているのはdata.tableです。それ以外の場合はパンダを使用します。パンダよりも良い/速いです。
marbel 16

8
data.tableは、そのシンプルさとSQL構文構造に似ているため、気に入っています。私の仕事は、統計モデリングのために非常に激しいアドホックデータ分析とグラフィックスを毎日行うことであり、本当に複雑なことを行うのに十分なほどシンプルなツールが必要です。これで、ツールキットをデータのdata.tableとグラフのラティスだけに減らすことができます。$ DT [group == 1、y_hat:= predict(fit1、data = .SD)、] $のような操作を実行できる例を挙げます。これは本当にすっきりしていて、SQLの大きな利点と見なしていますクラシックR環境。
xappppp
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.