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.