如何在R中有效地实现合并

时间:2013-10-08 16:56:40

标签: r coalesce

背景

有几种SQL语言(我主要使用postgreSQL)有一个名为coalesce的函数,它返回每行的第一个非空列元素。当表中包含大量NULL个元素时,这可以非常有效。

我在R中的很多场景中都遇到过这种情况,当处理不太结构化的数据时,其中包含很多NA。

我自己做了一个天真的实现,但它的速度非常慢。

coalesce <- function(...) {
  apply(cbind(...), 1, function(x) {
          x[which(!is.na(x))[1]]
        })
}

实施例

a <- c(1,  2,  NA, 4, NA)
b <- c(NA, NA, NA, 5, 6)
c <- c(7,  8,  NA, 9, 10)
coalesce(a,b,c)
# [1]  1  2 NA  4  6

问题

有没有有效的方法在R中实现coalesce

8 个答案:

答案 0 :(得分:38)

在我的计算机上,使用Reduce可获得5倍的性能提升:

coalesce2 <- function(...) {
  Reduce(function(x, y) {
    i <- which(is.na(x))
    x[i] <- y[i]
    x},
  list(...))
}

> microbenchmark(coalesce(a,b,c),coalesce2(a,b,c))
Unit: microseconds
               expr    min       lq   median       uq     max neval
  coalesce(a, b, c) 97.669 100.7950 102.0120 103.0505 243.438   100
 coalesce2(a, b, c) 19.601  21.4055  22.8835  23.8315  45.419   100

答案 1 :(得分:21)

看起来coalesce1仍然可用

coalesce1 <- function(...) {
    ans <- ..1
    for (elt in list(...)[-1]) {
        i <- is.na(ans)
        ans[i] <- elt[i]
    }
    ans
}

哪个更快(但是或多或少的手重写Reduce,所以不那么一般)

> identical(coalesce(a, b, c), coalesce1(a, b, c))
[1] TRUE
> microbenchmark(coalesce(a,b,c), coalesce1(a, b, c), coalesce2(a,b,c))
Unit: microseconds
               expr     min       lq   median       uq     max neval
  coalesce(a, b, c) 336.266 341.6385 344.7320 355.4935 538.348   100
 coalesce1(a, b, c)   8.287   9.4110  10.9515  12.1295  20.940   100
 coalesce2(a, b, c)  37.711  40.1615  42.0885  45.1705  67.258   100

或者对于较大的数据比较

coalesce1a <- function(...) {
    ans <- ..1
    for (elt in list(...)[-1]) {
        i <- which(is.na(ans))
        ans[i] <- elt[i]
    }
    ans
}

显示which()有时可能有效,即使它意味着第二次通过索引。

> aa <- sample(a, 100000, TRUE)
> bb <- sample(b, 100000, TRUE)
> cc <- sample(c, 100000, TRUE)
> microbenchmark(coalesce1(aa, bb, cc),
+                coalesce1a(aa, bb, cc),
+                coalesce2(aa,bb,cc), times=10)
Unit: milliseconds
                   expr       min        lq    median        uq       max neval
  coalesce1(aa, bb, cc) 11.110024 11.137963 11.145723 11.212907 11.270533    10
 coalesce1a(aa, bb, cc)  2.906067  2.953266  2.962729  2.971761  3.452251    10
  coalesce2(aa, bb, cc)  3.080842  3.115607  3.139484  3.166642  3.198977    10

答案 2 :(得分:15)

使用 dplyr 包:

library(dplyr)
coalesce(a, b, c)
# [1]  1  2 NA  4  6

Benchamark,没有公认解决方案那么快:

coalesce2 <- function(...) {
  Reduce(function(x, y) {
    i <- which(is.na(x))
    x[i] <- y[i]
    x},
    list(...))
}

microbenchmark::microbenchmark(
  coalesce(a, b, c),
  coalesce2(a, b, c)
)

# Unit: microseconds
#                expr    min     lq     mean median      uq     max neval cld
#   coalesce(a, b, c) 21.951 24.518 27.28264 25.515 26.9405 126.293   100   b
#  coalesce2(a, b, c)  7.127  8.553  9.68731  9.123  9.6930  27.368   100  a 

但是在更大的数据集上,它具有可比性:

aa <- sample(a, 100000, TRUE)
bb <- sample(b, 100000, TRUE)
cc <- sample(c, 100000, TRUE)

microbenchmark::microbenchmark(
  coalesce(aa, bb, cc),
  coalesce2(aa, bb, cc))

# Unit: milliseconds
#                   expr      min       lq     mean   median       uq      max neval cld
#   coalesce(aa, bb, cc) 1.708511 1.837368 5.468123 3.268492 3.511241 96.99766   100   a
#  coalesce2(aa, bb, cc) 1.474171 1.516506 3.312153 1.957104 3.253240 91.05223   100   a

答案 3 :(得分:9)

我在my misc package中有一个名为coalesce.na的即用型实现。它看起来很有竞争力,但不是最快的。 它也适用于不同长度的载体,并对长度为1的载体进行特殊处理:

                    expr        min          lq      median          uq         max neval
    coalesce(aa, bb, cc) 990.060402 1030.708466 1067.000698 1083.301986 1280.734389    10
   coalesce1(aa, bb, cc)  11.356584   11.448455   11.804239   12.507659   14.922052    10
  coalesce1a(aa, bb, cc)   2.739395    2.786594    2.852942    3.312728    5.529927    10
   coalesce2(aa, bb, cc)   2.929364    3.041345    3.593424    3.868032    7.838552    10
 coalesce.na(aa, bb, cc)   4.640552    4.691107    4.858385    4.973895    5.676463    10

以下是代码:

coalesce.na <- function(x, ...) {
  x.len <- length(x)
  ly <- list(...)
  for (y in ly) {
    y.len <- length(y)
    if (y.len == 1) {
      x[is.na(x)] <- y
    } else {
      if (x.len %% y.len != 0)
        warning('object length is not a multiple of first object length')
      pos <- which(is.na(x))
      x[pos] <- y[(pos - 1) %% y.len + 1]
    }
  }
  x
}

当然,正如凯文指出的那样,Rcpp解决方案可能会快几个数量级。

答案 4 :(得分:3)

非常简单的解决方案是使用ifelse包中的base函数:

coalesce3 <- function(x, y) {

    ifelse(is.na(x), y, x)
}

虽然它似乎比coalesce2更慢:

test <- function(a, b, func) {

    for (i in 1:10000) {

        func(a, b)
    }
}

system.time(test(a, b, coalesce2))
user  system elapsed 
0.11    0.00    0.10 

system.time(test(a, b, coalesce3))
user  system elapsed 
0.16    0.00    0.15 

您可以使用Reduce使其适用于任意数量的向量:

coalesce4 <- function(...) {

    Reduce(coalesce3, list(...))
}

答案 5 :(得分:2)

这是我的解决方案:

coalesce <- function(x){ y <- head( x[is.na(x) == F] , 1) return(y) } 它返回第一个不是NA的vaule,它可以在data.table上运行,例如,如果你想在几列上使用coalesce,并且这些列名在字符串向量中:

column_names <- c("col1", "col2", "col3")

使用方法:

ranking[, coalesce_column := coalesce( mget(column_names) ), by = 1:nrow(ranking)]

答案 6 :(得分:2)

data.table >= 1.12.3中,您可以使用coalesce

library(data.table)
coalesce(a, b, c)
# [1]  1  2 NA  4  6

有关更多信息(包括基准),请参见NEWS item #18 for development version 1.12.3。有关开发版本的安装,请参见here

答案 7 :(得分:1)

另一种申请方法,mapply

mapply(function(...) {temp <- c(...); temp[!is.na(temp)][1]}, a, b, c)
[1]  1  2 NA  4  6

如果存在多个NA值,则选择第一个非NA值。可以使用tail选择最后一个非缺失元素。

使用简单的.mapply函数可能会有更多的速度被挤出这个替代方案,这看起来有点不同。

unlist(.mapply(function(...) {temp <- c(...); temp[!is.na(temp)][1]},
               dots=list(a, b, c), MoreArgs=NULL))
[1]  1  2 NA  4  6

.mapply在重要方面与其非点缀表兄弟有所不同。

  • 它返回一个列表(如Map),因此必须包含在unlistc等函数中以返回一个向量。
  • 要与FUN中的函数并行馈送的参数集必须在列表参数中给出。
  • 最后,mapply,moreArgs参数没有默认值,因此必须明确地输入NULL。