Remova as colunas do quadro de dados em que TODOS os valores são NA


149

Estou tendo problemas com um quadro de dados e realmente não consegui resolver esse problema:
o quadro de dados tem propriedades arbitrárias como colunas e cada linha representa um conjunto de dados .

A questão é:
como se livrar de colunas onde, para TODAS as linhas, o valor é NA ?

Respostas:


155

Tente o seguinte:

df <- df[,colSums(is.na(df))<nrow(df)]

3
Isso cria um objeto do tamanho do objeto antigo, o que é um problema de memória em objetos grandes. Melhor usar uma função para reduzir o tamanho. A resposta abaixo, usando Filtro ou usando data.table, ajudará no uso da memória.
mtelesha

3
Isso não parece funcionar com colunas não numéricas.
verbamour

Ele muda nome da coluna se eles são duplicados
Peter.k

97

As duas abordagens oferecidas até agora falham com grandes conjuntos de dados, pois (entre outros problemas de memória) eles criam is.na(df) , que serão um objeto do mesmo tamanho que df.

Aqui estão duas abordagens que são mais eficientes em termos de memória e tempo

Uma abordagem usando Filter

Filter(function(x)!all(is.na(x)), df)

e uma abordagem usando data.table (para eficiência geral de tempo e memória)

library(data.table)
DT <- as.data.table(df)
DT[,which(unlist(lapply(DT, function(x)!all(is.na(x))))),with=F]

exemplos usando dados grandes (30 colunas, 1e6 linhas)

big_data <- replicate(10, data.frame(rep(NA, 1e6), sample(c(1:8,NA),1e6,T), sample(250,1e6,T)),simplify=F)
bd <- do.call(data.frame,big_data)
names(bd) <- paste0('X',seq_len(30))
DT <- as.data.table(bd)

system.time({df1 <- bd[,colSums(is.na(bd) < nrow(bd))]})
# error -- can't allocate vector of size ...
system.time({df2 <- bd[, !apply(is.na(bd), 2, all)]})
# error -- can't allocate vector of size ...
system.time({df3 <- Filter(function(x)!all(is.na(x)), bd)})
## user  system elapsed 
## 0.26    0.03    0.29 
system.time({DT1 <- DT[,which(unlist(lapply(DT, function(x)!all(is.na(x))))),with=F]})
## user  system elapsed 
## 0.14    0.03    0.18 

6
Muito agradável. Você poderia fazer o mesmo com data.frame, no entanto. Não há nada aqui que realmente precise data.table. A chave é a lapply, que evita a cópia de todo o objeto feito por is.na(df). +10 por apontar isso.
precisa saber é o seguinte

1
Como você faria isso com um data.frame? @ matt-dowle
s_a

8
@s_a, bd1 <- bd[, unlist(lapply(bd, function(x), !all(is.na(x))))]
mnel

6
@mnel Acho que você precisa para remover as ,depois function(x)- graças para o exemplo btw
Thieme Hennis

1
Você pode fazer isso mais rápido com: = ou com um set ()?
skan

49

dplyragora tem um select_ifverbo que pode ser útil aqui:

library(dplyr)
temp <- data.frame(x = 1:5, y = c(1,2,NA,4, 5), z = rep(NA, 5))
not_all_na <- function(x) any(!is.na(x))
not_any_na <- function(x) all(!is.na(x))

> temp
  x  y  z
1 1  1 NA
2 2  2 NA
3 3 NA NA
4 4  4 NA
5 5  5 NA

> temp %>% select_if(not_all_na)
  x  y
1 1  1
2 2  2
3 3 NA
4 4  4
5 5  5

> temp %>% select_if(not_any_na)
  x
1 1
2 2
3 3
4 4
5 5

Vim aqui procurando a dplyrsolução. Não fiquei desapontado. Obrigado!
Andrew Brēza 1/08/19

Achei que isso tinha o problema de que ele também
excluiria

15

Outra maneira seria usar a apply()função

Se você tiver o data.frame

df <- data.frame (var1 = c(1:7,NA),
                  var2 = c(1,2,1,3,4,NA,NA,9),
                  var3 = c(NA)
                  )

então você pode usar apply()para ver quais colunas atendem à sua condição e, portanto, pode simplesmente fazer o mesmo subconjunto da resposta de Musa, apenas com uma applyabordagem.

> !apply (is.na(df), 2, all)
 var1  var2  var3 
 TRUE  TRUE FALSE 

> df[, !apply(is.na(df), 2, all)]
  var1 var2
1    1    1
2    2    2
3    3    1
4    4    3
5    5    4
6    6   NA
7    7   NA
8   NA    9

3
Eu esperava que isso fosse mais rápido, pois a solução colSum () parecia estar fazendo mais trabalho. Mas no meu conjunto de testes (213 obs. De 1614 variáveis ​​antes, contra 1377 variáveis ​​depois) demora exatamente 3 vezes mais. (Mas +1 para uma abordagem interessante.)
Darren Cozinhe

10

Tarde para o jogo, mas você também pode usar o janitorpacote. Esta função removerá as colunas que são todas NA e pode ser alterada para remover as linhas que também são NA.

df <- janitor::remove_empty(df, which = "cols")



4

A resposta aceita não funciona com colunas não numéricas. A partir desta resposta , o seguinte funciona com colunas contendo diferentes tipos de dados

Filter(function(x) !all(is.na(x)), df)

Alguém já postou a mesma resposta neste tópico 4 anos antes de você ... Veja a resposta de mnel abaixo.
André.B 18/02

2

Outras opções com o purrrpacote:

library(dplyr)

df <- data.frame(a = NA,
                 b = seq(1:5), 
                 c = c(rep(1, 4), NA))

df %>% purrr::discard(~all(is.na(.)))
df %>% purrr::keep(~!all(is.na(.)))

1

Espero que isso também ajude. Poderia ser transformado em um único comando, mas achei mais fácil ler dividindo-o em dois comandos. Fiz uma função com as seguintes instruções e trabalhei muito rápido.

naColsRemoval = function (DataTable) { na.cols = DataTable [ , .( which ( apply ( is.na ( .SD ) , 2 , all ) ) )] DataTable [ , unlist (na.cols) := NULL , with = F] }

.SD permitirá limitar a verificação a parte da tabela, se você desejar, mas tomará a tabela inteira como


1

Uma base Ropção útil pode ser colMeans():

df[, colMeans(is.na(df)) != 1]

0

Você pode usar o pacote Janitor remove_empty

library(janitor)

df %>%
  remove_empty(c("rows", "cols")) #select either row or cols or both

Além disso, outra abordagem dplyr

 library(dplyr) 
 df %>% select_if(~all(!is.na(.)))

OU

df %>% select_if(colSums(!is.na(.)) == nrow(df))

isso também é útil se você deseja excluir / manter apenas a coluna com um certo número de valores ausentes, por exemplo

 df %>% select_if(colSums(!is.na(.))>500)
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.