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

whi*_*ile 37 r coalesce

背景

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

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

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

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

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
Run Code Online (Sandbox Code Playgroud)

coalesce在R中有没有有效的方法?

mri*_*rip 41

在我的机器上,使用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
Run Code Online (Sandbox Code Playgroud)

  • 如果其中一个是“NULL”而不是“NA”,则失败。推荐使用`is.null`。 (2认同)

Mar*_*gan 21

貌似coalesce1仍然可用

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

哪个更快(但是或多或少的手重写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
Run Code Online (Sandbox Code Playgroud)

或者对于较大的数据比较

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

显示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
Run Code Online (Sandbox Code Playgroud)


zx8*_*754 15

使用dplyr包:

library(dplyr)
coalesce(a, b, c)
# [1]  1  2 NA  4  6
Run Code Online (Sandbox Code Playgroud)

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 
Run Code Online (Sandbox Code Playgroud)

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

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
Run Code Online (Sandbox Code Playgroud)


krl*_*mlr 9

我有一个coalesce.na我的misc包中调用的即用型实现.它看起来很有竞争力,但不是最快的.它也适用于不同长度的载体,并对长度为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
Run Code Online (Sandbox Code Playgroud)

这是代码:

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
}
Run Code Online (Sandbox Code Playgroud)

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


Hen*_*rik 6

data.table >= 1.12.3您可以使用fcoalesce

library(data.table)
fcoalesce(a, b, c)
# [1]  1  2 NA  4  6
Run Code Online (Sandbox Code Playgroud)

有关更多信息(包括基准),请参阅新闻项#18(开发版本1.12.3)


sdg*_*sdh 5

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

coalesce3 <- function(x, y) {

    ifelse(is.na(x), y, x)
}
Run Code Online (Sandbox Code Playgroud)

虽然看起来比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 
Run Code Online (Sandbox Code Playgroud)

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

coalesce4 <- function(...) {

    Reduce(coalesce3, list(...))
}
Run Code Online (Sandbox Code Playgroud)

  • 有趣的是,显式的 if-else 不会将日期转换为数字,但 ifelse() 可以。所以你对日期的函数不起作用:`coalesce4(NULL, lubridate::ymd('2019-05-01'))`返回`18017` (2认同)