filtro per casi completi in data.frame utilizzando dplyr (eliminazione in base al caso)


97

È possibile filtrare un data.frame per casi completi utilizzando dplyr? complete.casescon un elenco di tutte le variabili funziona, ovviamente. Ma questo è a) verboso quando ci sono molte variabili eb) impossibile quando i nomi delle variabili non sono noti (ad esempio in una funzione che elabora qualsiasi 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))

4
complete.casesnon accetta solo vettori. Richiede anche interi frame di dati.
joran

Ma questo non funziona come parte della dplyrfunzione di filtro di. Immagino di non essere stato abbastanza chiaro e ho aggiornato la mia domanda.
user2503795

1
Sarebbe d'aiuto se potessi dimostrare esattamente come non funziona con dplyr, ma quando lo provo con il filtro, funziona perfettamente.
joran

Risposte:


185

Prova questo:

df %>% na.omit

o questo:

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

o questo:

library(tidyr)
df %>% drop_na

Se vuoi filtrare in base alla mancanza di una variabile, usa un condizionale:

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

o

df %>% drop_na(x1)

Altre risposte indicano che delle soluzioni precedenti na.omitè molto più lenta, ma deve essere bilanciata con il fatto che restituisce gli indici di riga delle righe omesse na.actionnell'attributo mentre le altre soluzioni sopra non lo fanno.

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"

AGGIUNTO Hanno aggiornato per riflettere l'ultima versione di dplyr e commenti.

AGGIUNTO Hanno aggiornato per riflettere l'ultima versione di tidyr e commenti.


Sono appena tornato per rispondere e ho visto la tua risposta utile!
infominer

1
Grazie! Ho aggiunto alcuni risultati di benchmark. na.omit()funziona piuttosto male ma quello è veloce.
user2503795

1
Questo funziona bene ora come: df %>% filter(complete.cases(.)). Non sono sicuro che i recenti cambiamenti in dplyr lo abbiano reso possibile.
user2503795

Come @ Gen-katins punti fuori, la funzione Tidyverse viene chiamata drop_na, in modo da ora è possibile farlo: df %>% drop_na().
cbrnr

26

Questo funziona per me:

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

O un po 'più generale:

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

Ciò avrebbe il vantaggio che i dati avrebbero potuto essere modificati nella catena prima di passarli al filtro.

Un altro benchmark con più colonne:

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

1
Ho aggiornato la tua risposta con "." nei casi completi e benchmark aggiunto - spero non ti dispiaccia :-)
talat

:) Io non. Grazie.
Miha Trošt

1
Ho trovato df %>% slice(which(complete.cases(.)))prestazioni del ~ 20% più veloci rispetto all'approccio del filtro nel benchmark sopra.
talat

Vale la pena notare che se stai usando questo filtro in una pipe dplyr con altri comandi dplyr (come group_by ()), dovrai aggiungerlo %>% data.frame() %>%prima di provare a filtrare su complete.cases (.) Perché non funzionerà su piatti o piatti raggruppati o qualcosa del genere. O almeno, questa è stata l'esperienza che ho avuto.
C. Denney

16

Ecco alcuni risultati di riferimento per la risposta di Grothendieck. na.omit () impiega 20 volte più tempo delle altre due soluzioni. Penso che sarebbe bello se dplyr avesse una funzione per questo, magari come 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

12

Questa è una breve funzione che ti consente di specificare colonne (praticamente tutto ciò che dplyr::selectpuò capire) che non dovrebbero avere alcun valore NA (modellato dopo 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 ora fa parte di tidyr : quanto sopra può essere sostituito da library("tidyr")]

Esempi:

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

Non sarebbe ancora più utile poter aggiungere un cutoff come 0,5 e farlo elaborare per colonne? Caso: elimina le variabili con il 50% e più di dati mancanti. Esempio: data [, -which (colMeans (is.na (data))> 0.5)] Sarebbe bello poterlo fare con tidyr.
Lunedì

@Monduiz Ciò significherebbe che l'aggiunta di più dati (dove una variabile ha quindi un sacco di NA) potrebbe non riuscire nel passaggio successivo nella pipeline perché una variabile necessaria ora manca ...
Jan Katins

Giusto, ha senso.
Lunedì

6

prova questo

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

O anche questo

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

I comandi precedenti si occupano di verificare la completezza di tutte le colonne (variabili) nel tuo data.frame.


Grazie. Immagino di non essere stato abbastanza chiaro (domanda aggiornata). Conosco complete.cases (df) ma vorrei farlo dplyrcome parte della funzione di filtro. Ciò consentirebbe un'integrazione pulita nelle catene dplyr ecc.
user2503795

Controlla la risposta di @ G.Grothendieck
infominer

Nella dplyr:::do.data.framedichiarazione env$. <- .dataaggiunge punto all'ambiente. Nessuna dichiarazione del genere in magrittr :: "%>%" "
G. Grothendieck

Spiacente, devo aver inserito il commento nel posto sbagliato.
G. Grothendieck

3

Solo per motivi di completezza, dplyr::filterpuò essere evitato del tutto ma è comunque possibile comporre catene semplicemente usando magrittr:extract(un alias di [):

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

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

Il bonus aggiuntivo è la velocità, questo è il metodo più veloce tra le varianti filtere na.omit(testato utilizzando i microbenchmark @Miha Trošt).


Quando eseguo il benchmark con i dati di Miha Trošt, trovo che l'utilizzo extract()sia quasi dieci volte più lento di filter(). Tuttavia, quando creo un data frame più piccolo con df <- df[1:100, 1:10], l'immagine cambia ed extract()è la più veloce.
Stibu

Hai ragione. Sembra che magrittr::extractsia il modo più veloce solo quando n <= 5e3nel benchmark Miha Trošt.
mbask
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.