Saltar al contenido

Eliminar filas donde todas las variables son NA usando dplyr

Esta es la contestación más exacta que encomtrarás dar, pero primero obsérvala pausadamente y valora si es compatible a tu proyecto.

Solución:

Desde dplyr 0.7.0, existen nuevos verbos de filtrado de ámbito. Usando filter_any puede filtrar fácilmente filas con al menos una columna que no falta:

dat %>% filter_all(any_vars(!is.na(.)))

Usando el algoritmo de evaluación comparativa @hejseb, parece que esta solución es tan eficiente como f4.

evaluación comparativa

@DavidArenburg sugirió varias alternativas. Aquí hay una evaluación comparativa simple de ellos.

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))

Utilizando Reduce y lapply parece ser el mas rapido:

> 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

Uso de un conjunto de datos más grande 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 es demasiado lento por lo que también se excluye. f4 parece hacerlo relativamente mejor que antes.

> 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

Comenzando con dyplr 1.0, la viñeta colwise da un caso similar como ejemplo:

filter(across(everything(), ~ !is.na(.x))) #Remove rows with *any* NA

Podemos ver que usa la misma lógica "&" implícita filter usos con múltiples expresiones. Entonces, el siguiente ajuste menor selecciona todas las filas NA:

filter(across(everything(), ~ is.na(.x))) #Remove rows with *any* non-NA

Pero la pregunta pide el conjunto inverso: eliminar filas con todos N / A.

  1. Podemos hacer un simple setdiff usando el anterior, o
  2. podemos usar el hecho de que across devuelve un tibble lógico y filter efectivamente hace una fila sabia all() (es decir &).

P.ej:

rowAny = function(x) apply(x, 1, any)
anyVar = function(fcn) rowAny(across(everything(), fcn)) #make it readable
df %<>% filter(anyVar(~ !is.na(.x))) #Remove rows with *all* NA

O:

filterout = function(df, ...) setdiff(df, filter(df, ...))
df %<>% filterout(across(everything(), is.na)) #Remove rows with *all* NA

O incluso combinar los 2 anteriores para expresar el primer ejemplo más directamente:

df %<>% filterout(anyVar(~ is.na(.x))) #Remove rows with *any* NA

En mi opinión, el tidyverse filter La función se beneficiaría de un parámetro que describa la 'lógica de agregación'. Por defecto podría ser "todos" y preservar el comportamiento, o permitir "cualquiera" para que no tengamos que escribir anyVar-como funciones auxiliares.

Valoraciones y comentarios

Si te gusta este mundo, eres capaz de dejar una reseña acerca de qué le añadirías a esta reseña.

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *