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

mzsu5hc0  于 2023-09-27  发布在  其他
关注(0)|答案(6)|浏览(95)

例如(不确定是否最具代表性的例子):

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
ghhkc1vu

ghhkc1vu1#

当第二个 Dataframe 中的每个键值对应于第一个 Dataframe 中的每个键值时,匹配方法有效。如果在第二 Dataframe 中存在重复,则匹配和合并方法不相同。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会话中的 Dataframe 构成数据库,而不是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
cgyqldqp

cgyqldqp2#

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
czfnxgou

czfnxgou3#

对于简单的任务(join两端的唯一值),我使用match

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

它比merge快得多(在我的机器上0.13s到3.37s)。
我的时间:

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

8gsdolmq4#

我想在混合中发布一个使用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))

并使用数据表为dfur设置数据:

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

**更新:**我删除了data.tableBad和plyr,除了RStudio open(i7,16 GB RAM)之外什么都没有。

带数据。表1.9和带 Dataframe 的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

带数据表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'

为了保持一致性,这里是原始的所有和数据。表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

我认为这个数据对于新的数据来说太小了。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的内存来保存数据。
结果如下:

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。32 GB可以处理它没有问题。
[Edit(dotcomken,你能运行这段代码并粘贴你的基准测试结果吗?Thanks).

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与 Dataframe 一起使用似乎是处理摘要的效率较低的方法。这个方法是比较data.table和dplyr的确切功能以及它们的数据结构方法吗?我更倾向于将其分开,因为在我们group_by或创建data. table之前,大多数数据都需要清理。这可能是一个品味的问题,但我认为最重要的部分是如何有效地建模数据。

4dc9hkyq

4dc9hkyq5#

通过使用merge函数及其可选参数:
Inner join: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)
woobm2wo

woobm2wo6#

一个老问题的最新答案:collapse::join为其他选项提供了一个(非常)快速的替代方案。我还在这里添加了一种使用on进行data.table连接的最新方法。使用nomatch = 0执行内部连接。

library(collapse)
join(df1, df2, how = "inner")

library(data.table)
df1[df2, nomatch = 0]

collapse::join使用collapse::fmatch作为主力。

d2$y2[fmatch(d1$x, d2$x)]

dplyrmatchmerge相比,collapse::join绝对是快速连接的首选!

Unit: milliseconds
             expr        min         lq       mean     median         uq        max neval
   collapse::join   19.75553   20.15307   36.39752   20.47490   24.74434  108.15935    10
               dt  214.19317  226.41604  332.75642  261.51367  464.08229  564.80028    10
            merge 1078.30088 1089.78414 1134.86070 1135.04200 1148.33343 1209.98451    10
            dplyr  247.66431  254.57919  284.47596  273.53812  289.88379  387.11334    10

 collapse::fmatch   19.40087   19.64550   35.49663   20.17656   47.41584   88.66751    10
            match   32.03875   34.54625   50.08660   39.77932   50.85990  106.79755    10

数据和基准代码

library(data.table)
library(microbenchmark)
library(dplyr)
remotes::install_github("SebKrantz/collapse") #joins are only available in the development version of collapse so far.
library(collapse)

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

mb <- 
  microbenchmark(
    "collapse::join" = join(d1, d2),
    "dt" = dt1[dt2, on = "x", nomatch = 0], 
    "merge" = merge(d1, d2),
    "dplyr" = inner_join(d1, d2),
    
    #The match alternatives for one column joins
    "collapse::fmatch" = d2$y2[fmatch(d1$x, d2$x)],
    "match" = d2$y2[match(d1$x, d2$x)],
    times = 10
    )

相关问题