Как эффективно реализовать coalesce в R
фон
несколько языков 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
вопрос
есть ли эффективный способ реализации coalesce
в R?
7 ответов
на моей машине, используя 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
используя 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
у меня есть готовая к использованию реализация под названием coalesce.na
на мой пакет смешанная. Кажется, он конкурентоспособен, но не самый быстрый.
Он также будет работать для векторов разной длины и имеет специальную обработку для векторов длины один:
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 может быть быстрее на порядок.
вот мое решение:
coalesce <- function(x){
y <- head( x[is.na(x) == F] , 1)
return(y)
}
Он возвращает первый vaule, который не является NA, и он работает на data.table
, например, если вы хотите использовать coalesce на нескольких Столбцах, и эти имена столбцов находятся в векторе строк:
column_names <- c("col1", "col2", "col3")
как использовать:
ranking[, coalesce_column := coalesce( mget(column_names) ), by = 1:nrow(ranking)]
другой метод применения, с mapply
.
mapply(function(...) {temp <- c(...); temp[!is.na(temp)][1]}, a, b, c)
[1] 1 2 NA 4 6
это выбирает первое значение не-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, должен быть задан в списке аргументу dots.
- наконец,
mapply
, аргумент moreArgs не имеет значения по умолчанию, поэтому необходимо явно ввести NULL.
A очень простое решение-использовать С 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(...))
}