【问题标题】:What's the fastest way to merge/join data.frames in R?在 R 中合并/加入 data.frames 的最快方法是什么?
【发布时间】:2011-05-18 08:35:25
【问题描述】:

例如(虽然不确定是否最具代表性):

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

【问题讨论】:

  • Gabor 在下面指出了执行 sqldf 方式的正确方法:仅创建一个索引(例如在 d1 上)并在 select 语句中使用 d1.main 而不是 d1(否则它不会使用索引)。在这种情况下,时间是 13.6 秒。在 data.table 的情况下,实际上也不需要在两个表上建立索引,只需执行“dt2
  • 两个答案都提供了有价值的信息,值得一读(尽管只有一个可以“接受”)。
  • 您在问题中比较左连接和内连接

标签: performance r join merge data.table


【解决方案1】:

当第一个数据帧中的每个键值在第二个数据帧中都有一个唯一键时,匹配方法有效。如果第二个数据框中有重复项,则匹配和合并方法不一样。 Match 当然更快,因为它没有做那么多。特别是它从不寻找重复的键。 (代码后续)

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 代码中,似乎在两个表上使用了索引,但实际上,它们被放置在在 sql select 运行之前被覆盖的表上,并且部分地,解释了为什么它这么慢。 sqldf 的想法是 R 会话中的数据框构成数据库,而不是 sqlite 中的表。因此,每次代码引用一个不合格的表名时,它都会在你的 R 工作区中寻找它——而不是在 sqlite 的主数据库中。因此,显示的 select 语句将工作空间中的 d1 和 d2 读取到 sqlite 的主数据库中,从而破坏了带有索引的数据库。结果,它进行了没有索引的连接。如果您想使用 sqlite 主数据库中的 d1 和 d2 版本,您必须将它们称为 main.d1 和 main.d2,而不是 d1 和 d2。此外,如果您想让它尽可能快地运行,请注意简单的连接不能使用两个表上的索引,因此您可以节省创建索引之一的时间。在下面的代码中,我们说明了这些要点。

值得注意的是,精确的计算可以对哪个包最快产生巨大的影响。例如,我们在下面进行合并和聚合。我们看到两者的结果几乎相反。在第一个示例中,从最快到最慢我们得到:data.table、plyr、merge 和 sqldf,而在第二个示例中,sqldf、aggregate、data.table 和 plyr——几乎与第一个相反。在第一个示例中,sqldf 比 data.table 慢 3 倍,在第二个示例中,它比 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"))
)

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

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

【讨论】:

  • 谢谢你,加博尔。优秀的点,我通过 cmets 对原始问题进行了一些调整。实际上,我想即使在“合并”情况下,顺序也可能会根据表的相对大小、键的多样性等而改变(这就是为什么我说我不确定我的示例是否具有代表性)。尽管如此,很高兴看到该问题的所有不同解决方案。
  • 我也很欣赏关于“聚合”案例的评论。尽管这与问题中的“合并”设置不同,但它非常相关。我实际上会在一个单独的问题中询问它,但这里已经有一个 stackoverflow.com/questions/3685492/… 。您可能也想为此做出贡献,根据上面的结果,sqldf 解决方案可能会击败那里的所有现有答案;)
【解决方案2】:

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。另请注意,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 仅适用于数据帧,所以此示例会产生最差情况下的性能。我希望在以后的版本中能够为这类常用操作提供更好的界面。
  • 仅供参考:您不能在 CRAN 包中使用 .Internal 调用,请参阅 CRAN Repository Policy
  • @JoshuaUlrich 当答案写于 2 年前,iirc 时,你可以。我将更新这个答案,因为data.table 现在会自动优化mean(内部不调用.Internal)。
  • @MatthewDowle:是的,我不确定它何时/是否改变了。我只知道现在是这样。你的答案很好,只是不能在包中工作。
  • @AleksandrBlekh 谢谢。我已将您的 cmets 链接到现有功能请求 #599。让我们搬到那里。您的示例代码很好地显示了for 循环,这很好。您能否在该问题中添加有关“SEM 分析”的更多信息?例如,我猜测 SEM = 扫描电子显微镜?了解更多关于应用程序的信息会让我们对它更感兴趣,并帮助我们确定优先级。
【解决方案3】:

对于简单的任务(连接两边的唯一值)我使用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

【讨论】:

【解决方案4】:

认为发布一个包含 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 内存)。

使用 data.table 1.9 和带有数据框的 dplyr:

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

尝试了 10 亿但炸毁了 ram。 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 之前需要清理大多数数据。这可能是个人喜好问题,但我认为最重要的部分是数据建模的效率。

【讨论】:

  • 不错的更新。谢谢。我认为你的机器与这个数据集相比是一个野兽。你的 L2 缓存的大小是多少(如果存在 L3)?
  • i7 L2 是 2x256 KB 8 路,L3 是 4 MB 16 路。 128 GB SSD,戴尔 inspiron 上的 Win 7
  • 你能重新格式化你的例子吗?我有点困惑。 data.table 比 dplyr 更好(在这个例子中)吗?如果是,在什么情况下。
【解决方案5】:

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

内连接: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 示例的基准数据来显示它的性能更好,或者至少具有很强的竞争力。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-06-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多