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

5cg8jx4n  于 2023-09-27  发布在  其他
关注(0)|答案(9)|浏览(106)

后台

几种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

t5zmwmid

t5zmwmid1#

在我的机器上,使用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
jk9hmnmh

jk9hmnmh2#

看起来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
eimct9ow

eimct9ow3#

data.table >= 1.12.3可以使用fcoalesce

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

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

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

有关更多信息,包括基准测试,请参阅NEWS项目#18开发版本1.12.3。

bvjveswy

bvjveswy4#

使用 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
mjqavswn

mjqavswn5#

我在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
}

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

w8f9ii69

w8f9ii696#

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

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(...))
}
e5nqia27

e5nqia277#

以下是我的解决方案:
coalesce <- function(x){ y <- head( x[is.na(x) == F] , 1) return(y) }它返回第一个不是NA的值,它适用于data.table,例如,如果你想在几个列上使用合并,这些列名是字符串的向量:
column_names <- c("col1", "col2", "col3")
如何用途:
ranking[, coalesce_column := coalesce( mget(column_names) ), by = 1:nrow(ranking)]

e7arh2l6

e7arh2l68#

BASE中的一个优雅的解决方案是定义:
coalesce <- function(...) na.omit(c(...))[1]
对于vector:

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

输出是所需的:

> mapply(coalesce, a, b,c)
[1]  1  2 NA  4  6

在我的机器上,这击败了使用Reduce的公认答案。

> microbenchmark(coalesce(a,b,c),coalesce2(a,b,c))
Unit: microseconds
               expr min  lq   mean median  uq    max neval
  coalesce(a, b, c) 5.6 5.7  6.527    5.9 6.1   43.6   100
 coalesce2(a, b, c) 7.6 7.9 39.191    8.0 8.4 3040.1   100
ao218c7q

ao218c7q9#

另一个apply方法,使用mapply

mycoalesce <- function(...) {
  temp <- c(...)
  temp[!is.na(temp)][1]
}
mapply(mycoalesce, 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),因此必须 Package 在一些函数中,如unlistc以返回向量。
  • 要并行馈送到FUN中的函数的参数集必须在dots参数的列表中给出。
  • 最后,mapply,moreArgs参数没有默认值,因此必须显式地提供NULL。

相关问题