Rでdata.framesをマージ/結合する最も速い方法は何ですか?


97

たとえば(ただし、最も代表的な例かどうかはわかりません):

N <- 1e6
d1 <- data.frame(x=sample(N,N), y1=rnorm(N))
d2 <- data.frame(x=sample(N,N), y2=rnorm(N))

これは私がこれまでに得たものです:

d <- merge(d1,d2)
# 7.6 sec

library(plyr)
d <- join(d1,d2)
# 2.9 sec

library(data.table)
dt1 <- data.table(d1, key="x")
dt2 <- data.table(d2, key="x")
d <- data.frame( dt1[dt2,list(x,y1,y2=dt2$y2)] )
# 4.9 sec

library(sqldf)
sqldf()
sqldf("create index ix1 on d1(x)")
sqldf("create index ix2 on d2(x)")
d <- sqldf("select * from d1 inner join d2 on d1.x=d2.x")
sqldf()
# 17.4 sec

sqldfの方法を実行する適切な方法は、Gaborによって以下で指摘されています。1つのインデックスのみ(たとえば、d1)を作成し、selectステートメントでd1の代わりにd1.mainを使用します(それ以外の場合、インデックスを使用しません)。この場合のタイミングは13.6秒です。data.tableの場合も、両方のテーブルにインデックスを作成する必要はありません。「dt2 <-data.table(d2)」を実行するだけで、タイミングは3.9秒になります。
datasmurf 2010

どちらの回答も貴重な情報を提供します。どちらも読む価値があります(ただし、「受け入れられる」のは1つだけです)。
datasmurf 2010

あなたの質問で左結合と内部結合を比較しています
jangorecki

回答:


46

最初のキー値ごとに2番目のデータフレームに一意のキーがある場合、一致アプローチが機能します。2番目のデータフレームに重複がある場合、一致とマージのアプローチは同じではありません。もちろん、マッチはそれほど多くはないので、より高速です。特に、重複するキーを探すことはありません。(コードの後に​​続く)

DF1 = data.frame(a = c(1, 1, 2, 2), b = 1:4)
DF2 = data.frame(b = c(1, 2, 3, 3, 4), c = letters[1:5])
merge(DF1, DF2)
    b a c
  1 1 1 a
  2 2 1 b
  3 3 2 c
  4 3 2 d
  5 4 2 e
DF1$c = DF2$c[match(DF1$b, DF2$b)]
DF1$c
[1] a b c e
Levels: a b c d e

> DF1
  a b c
1 1 1 a
2 1 2 b
3 2 3 c
4 2 4 e

質問に投稿されたsqldfコードでは、2つのテーブルでインデックスが使用されているように見えるかもしれませんが、実際には、sql selectが実行される前に上書きされたテーブルに配置され、その理由の一部を説明していますとても遅い。sqldfの考え方は、Rセッションのデータフレームはsqliteのテーブルではなくデータベースを構成するということです。したがって、コードが修飾されていないテーブル名を参照するたびに、SQLiteのメインデータベースではなく、Rワークスペースでコードが検索されます。したがって、表示された選択ステートメントは、ワークスペースからsqliteのメインデータベースにd1とd2を読み取り、そこにインデックスがあったものを壊します。その結果、インデックスなしで結合が行われます。sqliteのメインデータベースにあるd1とd2のバージョンを利用したい場合は、それらをmain.d1とmainとして参照する必要があります。d2およびd1およびd2ではありません。また、できるだけ高速に実行する場合は、単純な結合では両方のテーブルのインデックスを使用できないため、インデックスの1つを作成する時間を節約できることに注意してください。以下のコードでは、これらのポイントを示しています。

正確な計算により、どのパッケージが最も高速であるかが大きく異なることに注意してください。たとえば、以下のマージと集計を行います。2つの結果はほぼ逆転しています。最初の例では、最も速いものから最も遅いものまで取得します。data.table、plyr、merge、sqldfですが、2番目の例では、sqldf、aggregate、data.table、およびplyrです。最初のものとほぼ逆です。最初の例では、sqldfはdata.tableより3倍遅く、2番目の例では、plyrより200倍速く、data.tableより100倍速くなっています。以下に、入力コード、マージの出力タイミング、および集約の出力タイミングを示します。また、sqldfはデータベースに基づいているため、Rが処理できるよりも大きいオブジェクトを処理できる(sqldfのdbname引数を使用する場合)一方で、他のアプローチはメインメモリでの処理に限定されていることにも注意してください。また、sqliteを使用してsqldfを示しましたが、H2およびPostgreSQLデータベースもサポートしています。

library(plyr)
library(data.table)
library(sqldf)

set.seed(123)
N <- 1e5
d1 <- data.frame(x=sample(N,N), y1=rnorm(N))
d2 <- data.frame(x=sample(N,N), y2=rnorm(N))

g1 <- sample(1:1000, N, replace = TRUE)
g2<- sample(1:1000, N, replace = TRUE)
d <- data.frame(d1, g1, g2)

library(rbenchmark)

benchmark(replications = 1, order = "elapsed",
   merge = merge(d1, d2),
   plyr = join(d1, d2),
   data.table = { 
      dt1 <- data.table(d1, key = "x")
      dt2 <- data.table(d2, key = "x")
      data.frame( dt1[dt2,list(x,y1,y2=dt2$y2)] )
      },
   sqldf = sqldf(c("create index ix1 on d1(x)",
      "select * from main.d1 join d2 using(x)"))
)

set.seed(123)
N <- 1e5
g1 <- sample(1:1000, N, replace = TRUE)
g2<- sample(1:1000, N, replace = TRUE)
d <- data.frame(x=sample(N,N), y=rnorm(N), g1, g2)

benchmark(replications = 1, order = "elapsed",
   aggregate = aggregate(d[c("x", "y")], d[c("g1", "g2")], mean), 
   data.table = {
      dt <- data.table(d, key = "g1,g2")
      dt[, colMeans(cbind(x, y)), by = "g1,g2"]
   },
   plyr = ddply(d, .(g1, g2), summarise, avx = mean(x), avy=mean(y)),
   sqldf = sqldf(c("create index ix on d(g1, g2)",
      "select g1, g2, avg(x), avg(y) from main.d group by g1, g2"))
)

マージ計算を比較する2つのベンチマーク呼び出しからの出力は次のとおりです。

Joining by: x
        test replications elapsed relative user.self sys.self user.child sys.child
3 data.table            1    0.34 1.000000      0.31     0.01         NA        NA
2       plyr            1    0.44 1.294118      0.39     0.02         NA        NA
1      merge            1    1.17 3.441176      1.10     0.04         NA        NA
4      sqldf            1    3.34 9.823529      3.24     0.04         NA        NA

集計計算を比較するベンチマーク呼び出しからの出力は次のとおりです。

        test replications elapsed  relative user.self sys.self user.child sys.child
4      sqldf            1    2.81  1.000000      2.73     0.02         NA        NA
1  aggregate            1   14.89  5.298932     14.89     0.00         NA        NA
2 data.table            1  132.46 47.138790    131.70     0.08         NA        NA
3       plyr            1  212.69 75.690391    211.57     0.56         NA        NA

ありがとう、ガボール。すばらしい点、元の質問へのコメントを介していくつかの調整を行いました。実際には、テーブルの相対的なサイズ、キーの多重度などに応じて、「マージ」の場合でも順序が変わる可能性があります(そのため、私の例が代表的なものかどうかはわかりません)。それにもかかわらず、問題に対するさまざまな解決策をすべて見るのは素晴らしいことです。
datasmurf 2010

「集約」のケースについてのコメントにも感謝します。これは質問の「マージ」設定とは異なりますが、非常に重要です。実際には別の質問でそれについて尋ねたでしょうが、ここにはすでに1つありますstackoverflow.com/questions/3685492/…。上記の結果に基づいて、sqldfソリューションは既存のすべての回答に勝る可能性があるため、その問題にも貢献したいかもしれません;)
datasmurf

40

Gaborの結果で報告された132秒は、data.table実際にはタイミングの基本関数colMeanscbind(これらの関数を使用することによって引き起こされるメモリ割り当てとコピー)です。の使用にはdata.table、良い方法と悪い方法があります。

benchmark(replications = 1, order = "elapsed", 
  aggregate = aggregate(d[c("x", "y")], d[c("g1", "g2")], mean),
  data.tableBad = {
     dt <- data.table(d, key = "g1,g2") 
     dt[, colMeans(cbind(x, y)), by = "g1,g2"]
  }, 
  data.tableGood = {
     dt <- data.table(d, key = "g1,g2") 
     dt[, list(mean(x),mean(y)), by = "g1,g2"]
  }, 
  plyr = ddply(d, .(g1, g2), summarise, avx = mean(x), avy=mean(y)),
  sqldf = sqldf(c("create index ix on d(g1, g2)",
      "select g1, g2, avg(x), avg(y) from main.d group by g1, g2"))
  ) 

            test replications elapsed relative user.self sys.self
3 data.tableGood            1    0.15    1.000      0.16     0.00
5          sqldf            1    1.01    6.733      1.01     0.00
2  data.tableBad            1    1.63   10.867      1.61     0.01
1      aggregate            1    6.40   42.667      6.38     0.00
4           plyr            1  317.97 2119.800    265.12    51.05

packageVersion("data.table")
# [1] ‘1.8.2’
packageVersion("plyr")
# [1] ‘1.7.1’
packageVersion("sqldf")
# [1] ‘0.4.6.4’
R.version.string
# R version 2.15.1 (2012-06-22)

プライがよくわからないので、plyrここのタイミングに頼る前にハドレーに確認してください。また、運賃のために、キーdata.tableに変換しdata.tableてキーを設定する時間も含まれていることに注意してください。


この回答は2010年12月に最初に回答されてから更新されています。以前のベンチマーク結果は以下のとおりです。変更点を確認するには、この回答の変更履歴をご覧ください。

              test replications elapsed   relative user.self sys.self
4   data.tableBest            1   0.532   1.000000     0.488    0.020
7            sqldf            1   2.059   3.870301     2.041    0.008
3 data.tableBetter            1   9.580  18.007519     9.213    0.220
1        aggregate            1  14.864  27.939850    13.937    0.316
2  data.tableWorst            1 152.046 285.800752   150.173    0.556
6 plyrwithInternal            1 198.283 372.712406   189.391    7.665
5             plyr            1 225.726 424.296992   208.013    8.004

ddplyはデータフレームでのみ機能するため、これは最悪の場合のパフォーマンスをもたらす例です。将来のバージョンでは、この種の一般的な操作のためのより良いインターフェースが欲しいと思います。
ハドリー

1
参考:.InternalCRANパッケージでは呼び出しを使用できません。CRANリポジトリポリシーを参照してください。
Joshua Ulrich

@JoshuaUlrich回答がほぼ2年前に書かれたとき、iircができました。data.table自動的に最適化されるmeanように(.Internal内部で呼び出すことなく)この回答を更新します。
Matt Dowle、2012年

@MatthewDowle:ええ、いつ変更されたかはわかりません。私は今それが事実であることを知っています。そして、それはあなたの答えで完全にうまくいきます、ただパッケージでは機能しません。
Joshua Ulrich

1
@AleksandrBlekhありがとう。ここでのコメントを既存の機能リクエスト#599にリンクしました。そこに移動しましょう。あなたのサンプルコードはforループをうまく示しています、それは良いことです。その問題に「SEM分析」に関する詳細情報を追加できますか?たとえば、SEM =走査型電子顕微鏡だと思いますか?アプリケーションについて詳しく知ることで、アプリケーションがより興味深くなり、優先順位付けに役立ちます。
Matt Dowle 2014

16

単純なタスク(結合の両側で一意の値)の場合、match次を使用します。

system.time({
    d <- d1
    d$y2 <- d2$y2[match(d1$x,d2$x)]
})

マージよりもはるかに高速です(私のマシンでは0.13秒から3.37秒)。

私のタイミング:

  • merge:3.32s
  • plyr:0.84秒
  • match:0.12s

4
マレク、ありがとう。これが非常に高速である理由のいくつかの説明(インデックス/ハッシュテーブルの作成)は、ここにあります:tolstoy.newcastle.edu.au/R/help/01c/2739.html
datasmurf

11

ミックスでdplyrを使用してベンチマークを投稿するのは興味深いだろうと思った:(多くのことが実行されていた)

            test replications elapsed relative user.self sys.self
5          dplyr            1    0.25     1.00      0.25     0.00
3 data.tableGood            1    0.28     1.12      0.27     0.00
6          sqldf            1    0.58     2.32      0.57     0.00
2  data.tableBad            1    1.10     4.40      1.09     0.01
1      aggregate            1    4.79    19.16      4.73     0.02
4           plyr            1  186.70   746.80    152.11    30.27

packageVersion("data.table")
[1]1.8.10’
packageVersion("plyr")
[1]1.8’
packageVersion("sqldf")
[1]0.4.7’
packageVersion("dplyr")
[1]0.1.2’
R.version.string
[1] "R version 3.0.2 (2013-09-25)"

追加したばかり:

dplyr = summarise(dt_dt, avx = mean(x), avy = mean(y))

そして、データテーブルでdplyrのデータを設定します。

dt <- tbl_dt(d)
dt_dt <- group_by(dt, g1, g2)

更新: data.tableBadとplyrを削除し、RStudio open(i7、16GB ram)のみを削除しました。

data.table 1.9とdplyr with data frameを使用:

            test replications elapsed relative user.self sys.self
2 data.tableGood            1    0.02      1.0      0.02     0.00
3          dplyr            1    0.04      2.0      0.04     0.00
4          sqldf            1    0.46     23.0      0.46     0.00
1      aggregate            1    6.11    305.5      6.10     0.02

data.table 1.9とdplyrとデータテーブル:

            test replications elapsed relative user.self sys.self
2 data.tableGood            1    0.02        1      0.02     0.00
3          dplyr            1    0.02        1      0.02     0.00
4          sqldf            1    0.44       22      0.43     0.02
1      aggregate            1    6.14      307      6.10     0.01

packageVersion("data.table")
[1] '1.9.0'
packageVersion("dplyr")
[1] '0.1.2'

一貫性を保つために、ここではすべてのdata.table 1.9とデータテーブルを使用するdplyrのオリジナルを示します。

            test replications elapsed relative user.self sys.self
5          dplyr            1    0.01        1      0.02     0.00
3 data.tableGood            1    0.02        2      0.01     0.00
6          sqldf            1    0.47       47      0.46     0.00
1      aggregate            1    6.16      616      6.16     0.00
2  data.tableBad            1   15.45     1545     15.38     0.01
4           plyr            1  110.23    11023     90.46    19.52

このデータは新しいdata.tableとdplyrには小さすぎると思います:)

より大きなデータセット:

N <- 1e8
g1 <- sample(1:50000, N, replace = TRUE)
g2<- sample(1:50000, N, replace = TRUE)
d <- data.frame(x=sample(N,N), y=rnorm(N), g1, g2)

ベンチマークを実行する前にデータを保持するためだけに、約10〜13 GBのRAMを使用しました。

結果:

            test replications elapsed relative user.self sys.self
1          dplyr            1   14.88        1      6.24     7.52
2 data.tableGood            1   28.41        1     18.55      9.4

10億を試したが、ラムを爆破した。32GBなら問題なく処理できます。


[Arunによる編集](dotcomken、このコードを実行してベンチマーク結果を貼り付けてもらえますか?ありがとうございます)。

require(data.table)
require(dplyr)
require(rbenchmark)

N <- 1e8
g1 <- sample(1:50000, N, replace = TRUE)
g2 <- sample(1:50000, N, replace = TRUE)
d <- data.frame(x=sample(N,N), y=rnorm(N), g1, g2)

benchmark(replications = 5, order = "elapsed", 
  data.table = {
     dt <- as.data.table(d) 
     dt[, lapply(.SD, mean), by = "g1,g2"]
  }, 
  dplyr_DF = d %.% group_by(g1, g2) %.% summarise(avx = mean(x), avy=mean(y))
) 

ここでのArunのリクエストに従って、あなたが実行するために私に提供したものの出力:

        test replications elapsed relative user.self sys.self
1 data.table            5   15.35     1.00     13.77     1.57
2   dplyr_DF            5  137.84     8.98    136.31     1.44

混乱してごめんなさい、深夜になってしまいました。

データフレームでdplyrを使用することは、集計を処理するための非効率的な方法のようです。このメソッドは、data.tableおよびdplyrの正確な機能を、含まれているデータ構造メソッドと比較するためのものですか?ほとんどのデータはgroup_byまたはdata.tableを作成する前にクリーンアップする必要があるため、これを分離することをお勧めします。好みの問題かもしれませんが、最も重要な部分は、データをいかに効率的にモデル化できるかです。


1
素敵なアップデート。ありがとう。あなたのマシンは、このデータセットと比べると野獣だと思います。L2キャッシュ(および存在する場合はL3)のサイズはどれくらいですか?
アルン

i7 L2は2x256 KB 8ウェイ、L3は4 MB 16ウェイです。128 GB SSD、Dell Inspironで7を獲得
dotcomken

1
例を再フォーマットできますか?私は少し混乱しています。data.tableは(この例では)dplyrよりも優れていますか?もしそうなら、どのような状況下で。
csgillespie 2014年

1

マージ機能とそのオプションのパラメーターを使用することにより:

内部結合:merge(df1、df2)はこれらの例で機能します。これは、Rが共通の変数名でフレームを自動的に結合するためですが、merge(df1、df2、by = "CustomerId")を指定して、必要なフィールドのみに一致しました 一致する変数が異なるデータフレームで異なる名前を持つ場合は、by.xおよびby.yパラメータを使用することもできます。

Outer join: merge(x = df1, y = df2, by = "CustomerId", all = TRUE)

Left outer: merge(x = df1, y = df2, by = "CustomerId", all.x = TRUE)

Right outer: merge(x = df1, y = df2, by = "CustomerId", all.y = TRUE)

Cross join: merge(x = df1, y = df2, by = NULL)

問題はパフォーマンスについてでした。結合の構文を指定しただけです。役に立ちますが、質問には答えません。この回答には、OPの例を使用したベンチマークデータが不足しているため、パフォーマンスが優れているか、少なくとも競争力が高いことが示されています。
Michael Tuchman
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.