51

背景

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

在处理包含大量 NA 的不那么结构化的数据时,我在 R 的很多场景中也遇到了这种情况。

我自己做了一个幼稚的实现,但速度非常慢。

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

问题

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

4

8 回答 8

46

在我的机器上,使用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
于 2013-10-08T17:34:48.923 回答
24

看起来像 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
于 2013-10-08T20:40:45.523 回答
19

使用dplyr包:

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

基准测试,不如公认的解决方案快:

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
于 2017-01-24T08:19:57.580 回答
18

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

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

fcoalesce也可以采用“单个普通列表、data.table 或 data.frame”。因此,如果上面的向量是 a data.frame(或 a data.table)中的列,我们可以简单地提供数据集的名称:

d = data.frame(a, b, c)
# or d = data.table(a, b, c) 
fcoalesce(d)
# [1]  1  2 NA  4  6

有关更多信息,包括基准,请参阅NEWS item #18 for development version 1.12.3

于 2019-06-26T08:41:47.657 回答
9

我的misc 包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
}

当然,正如 Kevin 所指出的,Rcpp 解决方案可能会快几个数量级。

于 2013-11-29T09:07:44.947 回答
4

一个非常简单的解决方案是使用包中的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(...))
}
于 2015-08-25T12:22:49.560 回答
1

这是我的解决方案:

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

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

如何使用:

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

于 2015-11-02T14:43:44.203 回答
1

另一种应用方法,使用mapply.

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

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

也许可以使用barebones.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 中的函数并行馈送的参数集必须在点参数的列表中给出。
  • 最后,mapplymoreArgs 参数没有默认值,因此必须显式输入 NULL。
于 2017-01-23T21:51:30.727 回答