filtrar para casos completos em data.frame usando dplyr (exclusão por caso)

97

É possível filtrar um data.frame para casos completos usando dplyr? complete.casescom uma lista de todas as variáveis ​​funciona, é claro. Mas isso é a) detalhado quando há muitas variáveis ​​eb) impossível quando os nomes das variáveis ​​não são conhecidos (por exemplo, em uma função que processa qualquer 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
fonte
4
complete.casesnão aceita apenas vetores. Leva quadros de dados inteiros também.
joran
Mas isso não funciona como parte da dplyrfunção de filtro de. Acho que não fui claro o suficiente e atualizei minha pergunta.
user2503795
1
Ajudaria se você pudesse demonstrar exatamente como ele não funciona com o dplyr, mas quando eu tento com o filtro, ele funciona muito bem.
joran

Respostas:

185

Experimente isto:

df %>% na.omit

ou isto:

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

ou isto:

library(tidyr)
df %>% drop_na

Se você deseja filtrar com base na falta de uma variável, use uma condicional:

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

ou

df %>% drop_na(x1)

Outras respostas indicam que uma das soluções acima na.omité muito mais lenta, mas isso deve ser balanceado em relação ao fato de que retorna índices de linha das linhas omitidas no na.actionatributo, enquanto as outras soluções acima não.

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"

ADICIONADO Foi atualizado para refletir a versão mais recente do dplyr e comentários.

ADICIONADO Atualizei para refletir a versão mais recente do tidyr e comentários.

G. Grothendieck
fonte
Acabei de responder e vi sua resposta útil!
infominer
1
Obrigado! Eu adicionei alguns resultados de benchmark. na.omit()executa muito mal, mas aquele é rápido.
user2503795
1
Isso funciona bem como agora: df %>% filter(complete.cases(.)). Não tenho certeza se mudanças recentes no dplyr tornaram isso possível.
user2503795
Como @ Jan-katins pontos fora, a função Tidyverse é chamado drop_na, então agora você pode fazer: df %>% drop_na().
cbrnr
26

Isso funciona para mim:

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

Ou um pouco mais geral:

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

Isso teria a vantagem de que os dados poderiam ser modificados na cadeia antes de passá-los para o filtro.

Outro benchmark com mais colunas:

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
fonte
1
Atualizei sua resposta com "." em complete.cases e benchmark adicionado - espero que você não se importe :-)
talat
:) Eu não. Obrigado.
Miha Trošt
1
Achei um df %>% slice(which(complete.cases(.)))desempenho ~ 20% mais rápido do que a abordagem de filtro no benchmark acima.
talat
É importante notar que se você estiver usando este filtro em um canal dplyr com outros comandos dplyr (como group_by ()), você precisará adicionar %>% data.frame() %>%antes de tentar filtrar em complete.cases (.) Porque não funcionará em tibbles ou tibbles agrupados ou algo assim. Ou, pelo menos, foi essa a experiência que tive.
C. Denney
16

Aqui estão alguns resultados de referência para a resposta de Grothendieck. na.omit () leva 20x mais tempo que as outras duas soluções. Acho que seria bom se o dplyr tivesse uma função para isso talvez como parte do 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
user2503795
fonte
12

Esta é uma função curta que permite especificar colunas (basicamente tudo o que dplyr::selectpode entender) que não deve ter nenhum valor NA (modelado após 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 agora faz parte do tidyr : o acima pode ser substituído por library("tidyr")]

Exemplos:

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
fonte
Não seria ainda mais útil poder adicionar um corte como 0,5 e processá-lo por colunas? Caso: elimine variáveis ​​com 50% ou mais de dados perdidos. Exemplo: data [, -which (colMeans (is.na (data))> 0.5)] Seria bom poder fazer isso com tidyr.
Monduiz
@Monduiz Isso significaria que a adição de mais dados (onde uma variável tem muito NA) poderia falhar na próxima etapa do pipeline porque uma variável necessária agora está faltando ...
Jan Katins
Certo, isso faz sentido.
Monduiz
6

tente isso

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

OU mesmo isso

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

Os comandos acima se encarregam de verificar a integridade de todas as colunas (variáveis) em seu data.frame.

infominer
fonte
Obrigado. Acho que não fui claro o suficiente (pergunta atualizada). Eu sei sobre complete.cases (df), mas gostaria de fazer isso dplyrcomo parte da função de filtro. Isso permitiria uma integração perfeita em cadeias dplyr etc.
user2503795
Verifique a resposta por @ G.Grothendieck
infominer
Na dplyr:::do.data.framedeclaração env$. <- .dataadiciona um ponto ao meio ambiente. Essa declaração não existe em magrittr :: "%>%" `
G. Grothendieck
Desculpe, devo ter inserido o comentário no lugar errado.
G. Grothendieck
3

Apenas por uma questão de completude, dplyr::filterpode ser evitado completamente, mas ainda pode compor cadeias apenas usando magrittr:extract(um alias de [):

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

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

O bônus adicional é a velocidade, este é o método mais rápido entre as variantes filtere na.omit(testado usando microbenchmarks @Miha Trošt).

mbask
fonte
Quando faço o benchmark com os dados de Miha Trošt, descobri que o uso extract()é quase dez vezes mais lento do que o filter(). No entanto, quando eu crio um quadro de dados menor com df <- df[1:100, 1:10], a imagem muda e extract()é mais rápida.
Stibu de
Você está certo. Parece que magrittr::extracté o caminho mais rápido apenas n <= 5e3no benchmark Miha Trošt.
mbask