如何在R中高效实现合并

问题描述 投票:0回答:9

背景

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

r coalesce
9个回答
52
投票

在我的机器上,使用

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

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

23
投票

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

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


21
投票

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

9
投票

我在

我的杂项包
中有一个名为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 解决方案可能会快几个数量级。


5
投票

一个非常简单的解决方案是使用 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(...)) }
    

2
投票
这是我的解决方案:

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)]


    


2
投票
BASE 中一个优雅的解决方案是定义:

coalesce <- function(...) na.omit(c(...))[1]


对于向量:

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
    

1
投票
另一种应用方法,用

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
    ),因此必须包装在诸如
    unlist
    c
    之类的函数中才能返回向量。
  • 与 FUN 中的函数并行输入的参数集必须在点参数的列表中给出。
  • 最后,
  • mapply
    ,moreArgs 参数没有默认值,因此必须显式输入 NULL。
© www.soinside.com 2019 - 2024. All rights reserved.