【问题标题】:a faster implementation of merge.data.frame() in R在 R 中更快地实现 merge.data.frame()
【发布时间】:2012-10-18 20:11:46
【问题描述】:

假设ab 是两个数据帧。目标是写一个函数 f(a,b) 生成合并的数据框,与合并的方式相同 merge(a,b,all=TRUE) 可以,即用 NA 填充 ab 中缺失的变量。 (问题是merge()似乎很慢。)

这可以通过以下方式完成(伪代码):

for each variable `var` found in either `a` or `b`, do:
    unlist(list(a.srcvar, b.srcvar), recursive=FALSE, use.names=FALSE)

where:
x.srcvar is x$var if x$var exists, or else
            rep(NA, nrow(x)) if y$var !is.factor, or else
            as.factor(rep(NA, nrow(x)))

然后将所有内容包装在数据框中。

这是一个“幼稚”的实现:

merge.datasets1 <- function(a, b) {
  a.fill <- rep(NA, nrow(a))
  b.fill <- rep(NA, nrow(b))
  a.fill.factor <- as.factor(a.fill)
  b.fill.factor <- as.factor(b.fill)
  out <- list()
  for (v in union(names(a), names(b))) {
    if (!v %in% names(a)) {
      b.srcvar <- b[[v]]
      if (is.factor(b.srcvar))
        a.srcvar <- a.fill.factor
      else
        a.srcvar <- a.fill
    } else {
      a.srcvar <- a[[v]]
      if (v %in% names(b))
        b.srcvar <- b[[v]]
      else if (is.factor(a.srcvar))
        b.srcvar <- b.fill.factor
      else
        b.srcvar <- b.fill
    }
    out[[v]] <- unlist(list(a.srcvar, b.srcvar),
                       recursive=FALSE, use.names=FALSE)
  }
  data.frame(out)
}

这是一个使用“矢量化”函数的不同实现:

merge.datasets2 <- function(a, b) {
  srcvar <- within(list(var=union(names(a), names(b))), {
    a.exists <- var %in% names(a)
    b.exists <- var %in% names(b)
    a.isfactor <- unlist(lapply(var, function(v) is.factor(a[[v]])))
    b.isfactor <- unlist(lapply(var, function(v) is.factor(b[[v]])))
    a <- ifelse(a.exists, var, ifelse(b.isfactor, 'fill.factor', 'fill'))
    b <- ifelse(b.exists, var, ifelse(a.isfactor, 'fill.factor', 'fill'))
  })
  a <- within(a, {
    fill <- NA
    fill.factor <- factor(fill)
  })
  b <- within(b, {
    fill <- NA
    fill.factor <- factor(fill)
  })
  out <- mapply(function(x,y) unlist(list(a[[x]], b[[y]]),
                                     recursive=FALSE, use.names=FALSE),
                srcvar$a, srcvar$b, SIMPLIFY=FALSE, USE.NAMES=FALSE)
  out <- data.frame(out)
  names(out) <- srcvar$var
  out
}

现在我们可以测试了:

sample.datasets <- lapply(1:50, function(i) iris[,sample(names(iris), 4)])

system.time(invisible(Reduce(merge.datasets1, sample.datasets)))
>>   user  system elapsed 
>>  0.192   0.000   0.190 
system.time(invisible(Reduce(merge.datasets2, sample.datasets)))
>>   user  system elapsed 
>>  2.292   0.000   2.293 

因此,天真的版本比其他版本快几个数量级。怎么能 这是?我一直认为for 循环很慢,应该 宁可使用lapply 和朋友,并避开 R 中的循环。我欢迎任何关于如何在速度方面改进我的功能的想法。

【问题讨论】:

  • 使用data.table
  • For 循环本身并不慢,如果你使用它们是明智的,特别是如果你预先分配内存,不要在循环中扩展对象等。
  • @JoshuaUlrich data.table 不起作用,因为它的 merge 方法在结果 data.table 中不包含非常见变量。
  • 我敢打赌,data.table 有办法做你想做的事。

标签: performance r dataframe


【解决方案1】:

事实上,您根本没有尝试复制merge(a,b, all = TRUE),因为您没有尝试合并任何列。相反,您只是堆叠数据,在不存在列的地方填充 NA

 # note  that this is not what you want/
dim(merge(sample.datasets[[1]], sample.datasets[[2]], all = T))
 [1] 314   5

merge(a,b, all = TRUE) 会慢的原因是它默认通过名称的交集进行合并。如果您转换为data.tables,那么merge.data.table 方法的速度非常快,但是对于您的测试数据,它将在每次连续合并上创建一个呈指数增长的数据集(而不是您希望结果的 7500 x 5)

一个简单的解决方案是使用plyr 包中的rbind.fill

library(plyr)
system.time({.x <- Reduce(rbind.fill, sample.datasets)})
## user  system elapsed 
## 0.16    0.00    0.15 
# which is almost identical to
system.time(.old <- Reduce(merge.datasets1, sample.datasets))
##   user  system elapsed 
##   0.14    0.00    0.14 

2012 年 2 月 11 日编辑

进一步考虑,注意到您可以将data.frames 的列表传递给rbind.fill 是非常有用的

 system.time(super_fast <- rbind.fill(sample.datasets))
 ##  user  system elapsed 
 ##  0.02    0.00    0.02 

identical(super_fast, .old)
[1] TRUE

大部分时间花在Reduce 的开销上,而rbind.fill 不需要。

【讨论】:

    猜你喜欢
    • 2010-12-17
    • 2019-07-24
    • 2012-06-11
    • 1970-01-01
    • 2016-03-04
    • 2016-02-12
    • 2018-06-06
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多