有几种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
答案 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
),因此必须包含在unlist
或c
等函数中以返回一个向量。mapply
,moreArgs参数没有默认值,因此必须明确地输入NULL。