filtrar para casos completos en data.frame usando dplyr (eliminación de casos)

97

¿Es posible filtrar un data.frame para casos completos usando dplyr? complete.casescon una lista de todas las variables funciona, por supuesto. Pero eso es a) detallado cuando hay muchas variables yb) imposible cuando no se conocen los nombres de las variables (por ejemplo, en una función que procesa cualquier data.frame).

library(dplyr)
df = data.frame(
    x1 = c(1,2,3,NA),
    x2 = c(1,2,NA,5)
)

df %.%
  filter(complete.cases(x1,x2))
usuario2503795
fuente
4
complete.casesno solo acepta vectores. También se necesitan marcos de datos completos.
joran
Pero eso no funciona como parte de dplyrla función de filtro de. Supongo que no fui lo suficientemente claro y actualicé mi pregunta.
user2503795
1
Sería útil si pudiera demostrar exactamente cómo no funciona con dplyr, pero cuando lo intento con filter, funciona bien.
joran

Respuestas:

185

Prueba esto:

df %>% na.omit

o esto:

df %>% filter(complete.cases(.))

o esto:

library(tidyr)
df %>% drop_na

Si desea filtrar según la falta de una variable, use un condicional:

df %>% filter(!is.na(x1))

o

df %>% drop_na(x1)

Otras respuestas indican que una de las soluciones anteriores na.omites mucho más lenta, pero eso debe equilibrarse con el hecho de que devuelve índices de fila de las filas omitidas en el na.actionatributo, mientras que las otras soluciones anteriores no lo hacen.

str(df %>% na.omit)
## 'data.frame':   2 obs. of  2 variables:
##  $ x1: num  1 2
##  $ x2: num  1 2
##  - attr(*, "na.action")= 'omit' Named int  3 4
##    ..- attr(*, "names")= chr  "3" "4"

AÑADIDO Se actualizó para reflejar la última versión de dplyr y comentarios.

AÑADIDO Se actualizó para reflejar la última versión de tidyr y comentarios.

G. Grothendieck
fuente
¡Regresé para responder y vi tu útil respuesta!
infominer
1
¡Gracias! Agregué algunos resultados de referencia. na.omit()funciona bastante mal, pero el uno es rápido.
user2503795
1
Esto funciona ahora así: df %>% filter(complete.cases(.)). No estoy seguro de si los cambios recientes en dplyr lo hicieron posible.
user2503795
Como @ Ene-katins señala, la función Tidyverse se llama drop_na, por lo que ahora puede hacer: df %>% drop_na().
Cbrnr
26

Esto funciona para mi:

df %>%
  filter(complete.cases(df))    

O un poco más general:

library(dplyr) # 0.4
df %>% filter(complete.cases(.))

Esto tendría la ventaja de que los datos podrían haberse modificado en la cadena antes de pasarlos al filtro.

Otro punto de referencia con más columnas:

set.seed(123)
x <- sample(1e5,1e5*26, replace = TRUE)
x[sample(seq_along(x), 1e3)] <- NA
df <- as.data.frame(matrix(x, ncol = 26))
library(microbenchmark)
microbenchmark(
  na.omit = {df %>% na.omit},
  filter.anonymous = {df %>% (function(x) filter(x, complete.cases(x)))},
  rowSums = {df %>% filter(rowSums(is.na(.)) == 0L)},
  filter = {df %>% filter(complete.cases(.))},
  times = 20L,
  unit = "relative")

#Unit: relative
#             expr       min        lq    median         uq       max neval
 #         na.omit 12.252048 11.248707 11.327005 11.0623422 12.823233    20
 #filter.anonymous  1.149305  1.022891  1.013779  0.9948659  4.668691    20
 #         rowSums  2.281002  2.377807  2.420615  2.3467519  5.223077    20
 #          filter  1.000000  1.000000  1.000000  1.0000000  1.000000    20
Miha Trošt
fuente
1
Actualicé tu respuesta con "." en los casos completos y el punto de referencia agregado, espero que no le importe :-)
talat
:) Yo no. Gracias.
Miha Trošt
1
Encontré un df %>% slice(which(complete.cases(.)))rendimiento ~ 20% más rápido que el enfoque de filtro en el punto de referencia anterior.
talat
Vale la pena señalar que si está utilizando este filtro en una tubería dplyr con otros comandos dplyr (como group_by ()), deberá agregar %>% data.frame() %>%antes de intentar filtrar en complete.cases (.) Porque no funcionará en tibbles o tibbles agrupados o algo así. O al menos, esa ha sido la experiencia que he tenido.
C.Denney
16

Aquí hay algunos resultados de referencia para la respuesta de Grothendieck. na.omit () toma 20 veces más tiempo que las otras dos soluciones. Creo que sería bueno si dplyr tuviera una función para esto, tal vez como parte del filtro.

library('rbenchmark')
library('dplyr')

n = 5e6
n.na = 100000
df = data.frame(
    x1 = sample(1:10, n, replace=TRUE),
    x2 = sample(1:10, n, replace=TRUE)
)
df$x1[sample(1:n, n.na)] = NA
df$x2[sample(1:n, n.na)] = NA


benchmark(
    df %>% filter(complete.cases(x1,x2)),
    df %>% na.omit(),
    df %>% (function(x) filter(x, complete.cases(x)))()
    , replications=50)

#                                                  test replications elapsed relative
# 3 df %.% (function(x) filter(x, complete.cases(x)))()           50   5.422    1.000
# 1               df %.% filter(complete.cases(x1, x2))           50   6.262    1.155
# 2                                    df %.% na.omit()           50 109.618   20.217
usuario2503795
fuente
12

Esta es una función corta que le permite especificar columnas (básicamente todo lo que dplyr::selectpueda entender) que no deberían tener ningún valor NA (modelado a partir de pandas df.dropna () ):

drop_na <- function(data, ...){
    if (missing(...)){
        f = complete.cases(data)
    } else {
        f <- complete.cases(select_(data, .dots = lazyeval::lazy_dots(...)))
    }
    filter(data, f)
}

[ drop_na ahora es parte de tidyr : lo anterior se puede reemplazar por library("tidyr")]

Ejemplos:

library("dplyr")
df <- data.frame(a=c(1,2,3,4,NA), b=c(NA,1,2,3,4), ac=c(1,2,NA,3,4))
df %>% drop_na(a,b)
df %>% drop_na(starts_with("a"))
df %>% drop_na() # drops all rows with NAs
Jan Katins
fuente
¿No sería aún más útil poder agregar un límite como 0.5 y hacer que se procese por columnas? Caso: eliminar variables con 50% y más de datos faltantes. Ejemplo: data [, -which (colMeans (is.na (data))> 0.5)] Sería bueno poder hacer esto con tidyr.
Monduiz
@Monduiz Esto significaría que la adición de más datos (donde una variable tiene muchos NA) podría fallar en el siguiente paso en la tubería porque falta una variable necesaria ...
Jan Katins
Bien, eso tiene sentido.
Monduiz
6

prueba esto

df[complete.cases(df),] #output to console

O incluso esto

df.complete <- df[complete.cases(df),] #assign to a new data.frame

Los comandos anteriores se encargan de verificar la integridad de todas las columnas (variable) en su data.frame.

infominer
fuente
Gracias. Aunque supongo que no fui lo suficientemente claro (pregunta actualizada). Sé sobre complete.cases (df) pero me gustaría hacerlo dplyrcomo parte de la función de filtro. Eso permitiría una integración ordenada en cadenas dplyr, etc.
user2503795
Verifique la respuesta por @ G.Grothendieck
infominer
En dplyr:::do.data.framela declaración env$. <- .dataagrega punto al medio ambiente. No existe tal declaración en magrittr :: "%>%" `
G. Grothendieck
Lo sentimos, debe haber ingresado el comentario en el lugar equivocado.
G. Grothendieck
3

Solo en aras de la integridad, dplyr::filterse puede evitar por completo, pero aún se puede componer cadenas simplemente usando magrittr:extract(un alias de [):

library(magrittr)
df = data.frame(
  x1 = c(1,2,3,NA),
  x2 = c(1,2,NA,5))

df %>%
  extract(complete.cases(.), )

La ventaja adicional es la velocidad, este es el método más rápido entre las variantes filtery na.omit(probado usando microbenchmarks @Miha Trošt).

mbask
fuente
Cuando hago el benchmark con los datos de Miha Trošt, encuentro que usar extract()es casi diez veces más lento que filter(). Sin embargo, cuando creo un marco de datos más pequeño con df <- df[1:100, 1:10], la imagen cambia y extract()es la más rápida.
Stibu
Estás en lo correcto. Parece que magrittr::extractes la forma más rápida solo cuando está n <= 5e3en el punto de referencia de Miha Trošt.
mbask