几种 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
?
在我的机器上,使用
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
看起来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
从
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。
使用 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
我在
我的杂项包中有一个名为
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 解决方案可能会快几个数量级。
一个非常简单的解决方案是使用 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(...))
}
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)]
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
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
之类的函数中才能返回向量。
mapply
,moreArgs 参数没有默认值,因此必须显式输入 NULL。