在R中合并/连接data.frames的最快方法是什么?

例如(不知道如果最有代表性的例子):

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 

当第一个数据框中的每个键值有第二个数据框中的唯一键时,匹配方法起作用。 如果在第二个dataframe中有重复,则匹配和合并的方法是不一样的。 比赛当然更快,因为它没有那么多。 特别是它从不查找重复的键。 (在代码后继续)

 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) bac 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] abce Levels: abcde > DF1 abc 1 1 1 a 2 1 2 b 3 2 3 c 4 2 4 e 

在问题中发布的sqldf代码中,似乎在两个表上使用了索引,但事实上,它们被放置在sqlselect运行之前被覆盖的表上,这部分解释了为什么它太慢了。 sqldf的思想是你的R会话中的dataframe构成数据库,而不是sqlite中的表。 因此,每次代码引用一个非限定的表名时,它都会在你的R工作区中查找 – 而不是在sqlite的主数据库中。 因此,显示的select语句从工作区中读取d1和d2到sqlite的主数据库中,并将那些与索引一起的数据库打开。 结果它做了没有索引的连接。 如果您想使用sqlite主数据库中的d1和d2版本,则必须将其引用为main.d1和main.d2,而不是d1和d2。 另外,如果要尽可能快地运行,请注意简单的连接不能使用两个表上的索引,因此可以节省创build其中一个索引的时间。 在下面的代码中,我们将说明这些观点。

值得注意的是,精确的计算可以在最快的包上产生巨大的影响。 例如,我们在下面进行合并和聚合。 我们看到两者的结果几乎相反。 在第一个从最快到最慢的例子中,我们得到:data.table,plyr,merge和sqldf,而在第二个例子中,sqldf,aggregate,data.table和plyr – 几乎与第一个例子相反。 在第一个例子中,sqldf比data.table慢3倍,第二个例子比plyr快200倍,比data.table快100倍。 下面我们显示input代码,合并的输出时间和聚合的输出时间。 同样值得注意的是,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")) ) 

两个基准调用比较合并计算的输出是:

 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 

Gabor的data.table结果中报告的132秒实际上是基于时间的函数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,所以请在依靠这里的plyr时间之前与Hadley进行plyr 。 另外请注意, data.table包括转换为data.table的时间并设置密钥,以获得优惠。


自2010年12月以来,这个答案已经被更新。以前的基准testing结果如下。 请参阅此答案的修订历史logging,以查看更改的内容。

  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 

对于简单的任务(连接两侧的唯一值)我使用match

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

这比合并(在我的机器上0.13s到3.37s)要快得多。

我的时间:

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

认为在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打开(i7,16GB ram)。

用data.table 1.9和dplyr与dataframe:

  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) 

在运行基准testing之前,花费10-13GB的内存来保存数据。

结果:

  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 

试了十亿,但炸毁了公羊。 32GB将处理它没有问题。


(由Arun编辑)(dotcomken,你可以运行这个代码并粘贴你的基准testing结果吗?谢谢)。

 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的确切function与其数据结构方法进行比较? 我几乎宁愿分开,因为大部分数据都需要在group_by之前清理或者创builddata.table。 这可能是一个有趣的问题,但我认为最重要的部分是数据如何有效地build模。

通过使用合并函数及其可选参数:

内部连接:合并(df1,df2)将适用于这些示例,因为R通过公共variables名自动连接框架,但是您很可能想要指定合并(df1,df2,by =“CustomerId”)以确保您只匹配你想要的领域。 如果匹配variables在不同数据框中具有不同的名称,则也可以使用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)