0

たとえばab2つのデータフレームです。f(a,b)目標は、マージと同じ方法で、マージされたデータフレームを生成する 関数 を作成するmerge(a,b,all=TRUE)ことです。つまり、欠落している変数をNA内またはNAで埋めaますb。(問題は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ではループを使用して友達を避け、ループを避けなければならないと思っていました。速度の観点から機能を改善する方法についてのアイデアを歓迎します。

4

1 に答える 1

3

実際、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.tablesmerge.data.tableメソッドは非常に高速になりますが、テストデータを使用すると、連続するマージごとに指数関数的に増加するデータセットが作成されます(結果を希望する場合は7500 x 5ではありません)。

簡単な解決策はrbind.fillplyrパッケージから使用することです。

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必要としません。

于 2012-11-01T00:07:02.077 に答える