У меня есть матрица из 3 столбцов. Для каждой строки должно быть выбрано непропущенное значение, - если значение не найдено в столбце 1, будет выполняться поиск в столбце 2, затем в столбце 3, и порядок будет задан пользователем.
Я умеренно доволен своим запутанным вложенным ifelse
подходом — увы, это зависит от одинаковой длины заданных столбцов. Но количество столбцов должно быть гибким (таким образом, гибкое количество вложенных операторов ifelse). Это означает, что если пользователь выбирает только один или два столбца, результат будет NA, даже если нежелательный столбец содержит значение.
foo_mat <- structure(c(
NA, 30L, 15, 0, NA, 100L, 87L, NA, 0, NA, 2L, NA,
10, 0, NA
), .Dim = c(5L, 3L), .Dimnames = list(NULL, c(
"a", "b", "c"
)))
foo <- function(x, preced) {
ifelse(!is.na(x[, preced[1]]), x[, preced[1]],
ifelse(!is.na(x[, preced[2]]), x[, preced[2]],
x[, preced[3]]
)
)
}
foo_mat
#> a b c
#> [1,] NA 100 2
#> [2,] 30 87 NA
#> [3,] 15 NA 10
#> [4,] 0 0 0
#> [5,] NA NA NA
foo(foo_mat, c("a", "c", "b"))
#> [1] 2 30 15 0 NA
foo(foo_mat, preced = c("b", "a"))
#> Error in x[, preced[3]]: subscript out of bounds #(of course)
# desired output
#> [1] 100 87 15 0 NA
мой плохой, спасибо, что увидел это ... но мое перетаскивание мышью-копирование / вставка не захватило его, поэтому в любом случае может быть полезно отредактировать его вместе. Спасибо, извините за шум.
Вместо вложенного ifelse
может быть лучше создать функцию с coalesce
foo <- function(data, preced) {
do.call(dplyr::coalesce, as.data.frame(data[, preced]))
}
foo(foo_mat, c("a", "c", "b"))
#[1] 2 30 15 0 NA
foo(foo_mat, c("b", "a"))
#[1] 100 87 15 0 NA
coalesce
автоматически выбирает первый не-NA для каждой строки на основе столбцов в выбранном наборе данных
Или мы можем использовать векторизованный вариант в base R
с max.col
foo1 <- function(data, preced) {
tmp <- data[, preced]
i1 <- seq_len(nrow(tmp))
j1 <- max.col(!is.na(tmp), "first")
out <- tmp[cbind(i1, j1)]
out
}
foo1(foo_mat, c("a", "c", "b"))
#[1] 2 30 15 0 NA
foo1(foo_mat, c("b", "a"))
#[1] 100 87 15 0 NA
База Р:
apply(foo_mat[,c("a","c","b")], 1, function(z) c(na.omit(z), NA)[1])
# [1] 2 30 15 0 NA
Анон-функция представляет собой двухэтапный процесс:
NA
, чтобы мы могли получить первое значение, отличное от NA
na.omit(.)
вернет integer(0)
, а это не то, что вам нужно, поэтому c(., NA)[1]
гарантирует, что после na.omit(.)
у нас всегда будет хотя бы одно значение в векторе c(.)
, и нам нужно первое из них; если na.omit
ничего не возвращает, то, по крайней мере, у нас есть один NA
.Выполнение этого по рядам выполняется с помощью apply(foo_mat, 1, ...)
. Вы управляете порядком предпочтения, перестраивая столбцы, входящие в данные apply
, как в моем использовании foo_mat[,c("a","c","b")]
.
Как функция:
foo <- function(data, preced = names(data)) apply(data[,preced,drop=FALSE], 1, function(z) c(na.omit(z), NA)[1])
foo(foo_mat, c("a", "c", "b"))
# [1] 2 30 15 0 NA
(drop=FALSE
является защитным. База R по умолчанию поведение foo_mat[,"a"]
представляет собой вектор, а не матрицу с 1 столбцом. Это нарушает многие вещи, в том числе apply
. Таким образом, добавление drop=FALSE
предотвращает поведение сокращения по умолчанию.)
Альтернатива, которая примерно такая же быстрая, как и другие ответы:
foo <- function(data, preced) apply(data[,preced,drop=FALSE], 1, function(z) z[!is.na(z)][1])
Та же функциональность, меньше вызовов, простая логика.
(Атрибуция: эта альтернатива представляет собой комбинацию работы @tmfmnk, @Tjebo и меня. Спасибо!)
К вашему сведению, ответ, предоставленный @RuiBarradas, дает те же результаты. Но на данных такого размера эта функция более чем в два раза быстрее.
Я думаю, что c(z[!is.na(z)], NA)[1]
можно даже написать z[!is.na(z)][1]
, потому что integer(0)[1]
это NA
Вот функция foo
, которая работает так, как требуется с опубликованными примерами.
foo <- function(x, preced){
apply(x[, preced], 1, function(y){
w <- !is.na(y)
if (any(w)) y[w][1] else NA
})
}
foo(foo_mat, c("a", "c", "b"))
#[1] 2 30 15 0 NA
foo(foo_mat, preced = c("b", "a"))
#[1] 100 87 15 0 NA
Ваши образцы данных — хорошее начало, но отсутствует последняя часть. Я думаю, что нужно добавить
"b", "c")))
, верно?