Удалить строки, где все переменные NA с помощью dplyr
у меня возникли некоторые проблемы с, казалось бы, простая задача: удалить все строки, где все переменные NA
используя dplyr. Я знаю, что это можно сделать с помощью базы R (удалить строки в матрице R, где все данные NA и удаление пустых строк файла данных в R), но мне любопытно узнать, есть ли простой способ сделать это с помощью dplyr.
пример:
library(tidyverse)
dat <- tibble(a = c(1, 2, NA), b = c(1, NA, NA), c = c(2, NA, NA))
filter(dat, !is.na(a) | !is.na(b) | !is.na(c))
на filter
звонок выше делает то, что я хочу, но это невозможно в ситуации, с которой я сталкиваюсь (так как существует большое количество переменных). Думаю, это можно сделать, используя filter_
и сначала создаем строку с (длинным) логическим утверждением, но кажется, что должен быть более простой способ.
другой способ-использовать rowwise()
и do()
:
na <- dat %>%
rowwise() %>%
do(tibble(na = !all(is.na(.)))) %>%
.$na
filter(dat, na)
но это не выглядит слишком хорошо,хотя он получает работу. Другие идеи?
3 ответов
бенчмаркинг
@DavidArenburg предложил ряд альтернатив. Вот простой пример их сравнения.
library(tidyverse)
library(microbenchmark)
n <- 100
dat <- tibble(a = rep(c(1, 2, NA), n), b = rep(c(1, 1, NA), n))
f1 <- function(dat) {
na <- dat %>%
rowwise() %>%
do(tibble(na = !all(is.na(.)))) %>%
.$na
filter(dat, na)
}
f2 <- function(dat) {
dat %>% filter(rowSums(is.na(.)) != ncol(.))
}
f3 <- function(dat) {
dat %>% filter(rowMeans(is.na(.)) < 1)
}
f4 <- function(dat) {
dat %>% filter(Reduce(`+`, lapply(., is.na)) != ncol(.))
}
f5 <- function(dat) {
dat %>% mutate(indx = row_number()) %>% gather(var, val, -indx) %>% group_by(indx) %>% filter(sum(is.na(val)) != n()) %>% spread(var, val)
}
# f1 is too slow to be included!
microbenchmark(f2 = f2(dat), f3 = f3(dat), f4 = f4(dat), f5 = f5(dat))
используя Reduce
и lapply
кажется, самый быстрый:
> microbenchmark(f2 = f2(dat), f3 = f3(dat), f4 = f4(dat), f5 = f5(dat))
Unit: microseconds
expr min lq mean median uq max neval
f2 909.495 986.4680 2948.913 1154.4510 1434.725 131159.384 100
f3 946.321 1036.2745 1908.857 1221.1615 1805.405 7604.069 100
f4 706.647 809.2785 1318.694 960.0555 1089.099 13819.295 100
f5 640392.269 664101.2895 692349.519 679580.6435 709054.821 901386.187 100
использование большего набора данных 107,880 x 40
:
dat <- diamonds
# Let every third row be NA
dat[seq(1, nrow(diamonds), 3), ] <- NA
# Add some extra NA to first column so na.omit() wouldn't work
dat[seq(2, nrow(diamonds), 3), 1] <- NA
# Increase size
dat <- dat %>%
bind_rows(., .) %>%
bind_cols(., .) %>%
bind_cols(., .)
# Make names unique
names(dat) <- 1:ncol(dat)
microbenchmark(f2 = f2(dat), f3 = f3(dat), f4 = f4(dat))
f5
слишком медленно, поэтому он также исключается. f4
кажется, сделать относительно лучше, чем раньше.
> microbenchmark(f2 = f2(dat), f3 = f3(dat), f4 = f4(dat))
Unit: milliseconds
expr min lq mean median uq max neval
f2 34.60212 42.09918 114.65140 143.56056 148.8913 181.4218 100
f3 35.50890 44.94387 119.73744 144.75561 148.8678 254.5315 100
f4 27.68628 31.80557 73.63191 35.36144 137.2445 152.4686 100
поскольку dplyr 0.7.0 новые, ограниченные глаголы фильтрации существуют. Используя filter_any, вы можете легко фильтровать строки хотя бы с одним столбцом:
dat %>% filter_all(any_vars(!is.na(.)))
используя алгоритм бенчмаркинга @hejseb, кажется, что это решение так же эффективно, как f4.
вот еще одно решение, которое использует purrr::map_lgl()
и tidyr::nest()
:
library(tidyverse)
dat <- tibble(a = c(1, 2, NA), b = c(1, NA, NA), c = c(2, NA, NA))
any_not_na <- function(x) {
!all(map_lgl(x, is.na))
}
dat_cleaned <- dat %>%
rownames_to_column("ID") %>%
group_by(ID) %>%
nest() %>%
filter(map_lgl(data, any_not_na)) %>%
unnest() %>%
select(-ID)
## Warning: package 'bindrcpp' was built under R version 3.4.2
dat_cleaned
## # A tibble: 2 x 3
## a b c
## <dbl> <dbl> <dbl>
## 1 1. 1. 2.
## 2 2. NA NA
Я сомневаюсь, что этот подход сможет конкурировать с эталонами в ответе @hejseb, но я думаю, что он делает довольно хорошую работу, показывая, как nest %>% map %>% unnest
шаблон работает, и пользователи могут запускать его по очереди, чтобы выяснить, что происходит.