filtrowanie pełnych obserwacji w data.frame przy użyciu dplyr (usuwanie z uwzględnieniem wielkości liter)

98

Czy jest możliwe filtrowanie data.frame dla pełnych przypadków przy użyciu dplyr? complete.casesoczywiście z listą wszystkich zmiennych. Ale to jest a) rozwlekłe, gdy jest dużo zmiennych ib) niemożliwe, gdy nazwy zmiennych nie są znane (np. W funkcji przetwarzającej dowolną ramkę 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))
user2503795
źródło
4
complete.casesnie tylko akceptuje wektory. Zajmuje również całe ramki danych.
joran
Ale to nie działa jako część dplyrfunkcji filtrującej. Wydaje mi się, że nie byłem wystarczająco jasny i zaktualizowałem moje pytanie.
user2503795
1
Byłoby pomocne, gdybyś mógł dokładnie zademonstrować, jak to nie działa z dplyrem, ale kiedy spróbuję go z filtrem, działa dobrze.
joran

Odpowiedzi:

186

Spróbuj tego:

df %>% na.omit

albo to:

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

albo to:

library(tidyr)
df %>% drop_na

Jeśli chcesz filtrować na podstawie braków jednej zmiennej, użyj warunku:

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

lub

df %>% drop_na(x1)

Inne odpowiedzi wskazują, że z powyższych rozwiązań na.omitjest znacznie wolniejszy, ale należy to zrównoważyć faktem, że zwraca indeksy wierszy pominiętych w na.actionatrybucie, podczas gdy inne powyższe rozwiązania nie.

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"

DODANE Zaktualizowano, aby odzwierciedlić najnowszą wersję dplyr i komentarzy.

DODANE Zaktualizowano, aby odzwierciedlić najnowszą wersję tidyr i komentarzy.

G. Grothendieck
źródło
Właśnie wróciłem, aby odpowiedzieć i zobaczyłem twoją przydatną odpowiedź!
infominer
1
Dzięki! Dodałem kilka wyników testów porównawczych. na.omit()działa dość słabo, ale ta jest szybka.
user2503795
1
To działa teraz także: df %>% filter(complete.cases(.)). Nie jestem pewien, czy ostatnie zmiany w dplyr umożliwiły to.
user2503795
Jak @ jan-katins zaznacza, funkcja Tidyverse nazywa drop_na, więc można teraz zrobić: df %>% drop_na().
cbrnr
26

To działa dla mnie:

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

Lub trochę bardziej ogólnie:

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

Miałoby to tę zaletę, że dane mogły zostać zmodyfikowane w łańcuchu przed przekazaniem ich do filtra.

Kolejny test porównawczy z większą liczbą kolumn:

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
źródło
1
Zaktualizowałem Twoją odpowiedź o „.” w kompletnych przypadkach i dodany benchmark - mam nadzieję, że nie masz nic przeciwko :-)
talat
:) Ja nie. Dziękuję Ci.
Miha Trošt
1
Okazało się, że df %>% slice(which(complete.cases(.)))działa ~ 20% szybciej niż podejście z filtrem w powyższym teście porównawczym.
talat
Warto zauważyć, że jeśli używasz tego filtru w potoku dplyr z innymi poleceniami dplyr (takimi jak group_by ()), musisz dodać go %>% data.frame() %>%przed próbą i przefiltrować na complete.cases (.), Ponieważ nie będzie on działać na lub coś w tym stylu. A przynajmniej takie było moje doświadczenie.
C. Denney
16

Oto kilka wyników testów porównawczych odpowiedzi Grothendiecka. na.omit () zajmuje 20 razy więcej czasu niż pozostałe dwa rozwiązania. Myślę, że byłoby miło, gdyby dplyr miał do tego funkcję, może jako część filtra.

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
user2503795
źródło
12

Jest to krótka funkcja, która pozwala określić kolumny (w zasadzie wszystko, co dplyr::selectmoże zrozumieć), które nie powinny mieć żadnych wartości NA (wzorowane na 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 jest teraz częścią tidyr : powyższe można zastąpić library("tidyr")]

Przykłady:

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
źródło
Czy nie byłoby nawet bardziej przydatne, gdyby można było dodać wartość odcięcia, taką jak 0.5 i przetwarzać ją według kolumn? Przypadek: wyeliminuj zmienne z 50% i więcej brakującymi danymi. Przykład: data [, -which (colMeans (is.na (data))> 0.5)] Byłoby fajnie móc to zrobić z tidyr.
Monduiz
@Monduiz Oznaczałoby to, że dodanie większej ilości danych (gdzie zmienna ma wtedy dużo NA) może zakończyć się niepowodzeniem w następnym kroku w potoku, ponieważ brakuje potrzebnej zmiennej ...
Jan Katins,
Racja, to ma sens.
Monduiz,
6

Spróbuj tego

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

ALBO nawet to

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

Powyższe polecenia dbają o sprawdzenie kompletności wszystkich kolumn (zmiennych) w data.frame.

infominer
źródło
Dzięki. Myślę, że nie byłem wystarczająco jasny (zaktualizowano pytanie). Wiem o complete.cases (df), ale chciałbym to zrobić dplyrw ramach funkcji filtru. Pozwoliłoby to na zgrabną integrację w łańcuchach dplyr itp.
user2503795
Sprawdź odpowiedź @ G.Grothendieck
infominer.
W dplyr:::do.data.frameoświadczeniu env$. <- .datadodaje kropkę do środowiska. Brak takiego stwierdzenia w magrittr :: "%>%" ``
G. Grothendieck
Przepraszam, że komentarz musiał zostać wpisany w złym miejscu.
G. Grothendieck
3

Ze względu na kompletność, dplyr::filtermożna całkowicie uniknąć, ale nadal można tworzyć łańcuchy za pomocą magrittr:extract(aliasu [):

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

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

Dodatkowym bonusem jest szybkość, jest to najszybsza metoda spośród wariantów filteri na.omit(przetestowana przy użyciu mikroprocesorów @Miha Trošt).

mbask
źródło
Kiedy wykonuję test porównawczy z danymi Miha Trošt, stwierdzam, że używanie extract()jest prawie dziesięć razy wolniejsze niż filter(). Jednak gdy tworzę mniejszą ramkę danych za pomocą df <- df[1:100, 1:10], obraz zmienia się i extract()jest najszybszy.
Stibu,
Masz rację. Wygląda na magrittr::extractto, że najszybszy sposób tylko n <= 5e3w benchmarku Miha Trošt.
mbask