Supprimer les colonnes du dataframe où TOUTES les valeurs sont NA

149

J'ai des problèmes avec une trame de données et je n'ai pas vraiment pu résoudre ce problème moi-même:
la trame de données a des propriétés arbitraires sous forme de colonnes et chaque ligne représente un ensemble de données .

La question est:
comment se débarrasser des colonnes où pour TOUTES les lignes la valeur est NA ?

Gnark
la source

Réponses:

155

Essaye ça:

df <- df[,colSums(is.na(df))<nrow(df)]
teucer
la source
3
Cela crée un objet de la taille de l'ancien objet qui pose un problème de mémoire sur les grands objets. Mieux vaut utiliser une fonction pour réduire la taille. La réponse ci-dessous en utilisant Filter ou en utilisant data.table vous aidera à utiliser votre mémoire.
mtelesha
3
Cela ne semble pas fonctionner avec des colonnes non numériques.
verbamour
Il change le nom de la colonne s'ils sont dupliqués
Peter.k
97

Les deux approches proposées jusqu'à présent échouent avec de grands ensembles de données car (entre autres problèmes de mémoire) ils créent is.na(df), qui sera un objet de la même taille que df.

Voici deux approches plus efficaces en termes de mémoire et de temps

Une approche utilisant Filter

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

et une approche utilisant data.table (pour le temps général et l'efficacité de la mémoire)

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

exemples utilisant des données volumineuses (30 colonnes, 1e6 lignes)

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 
mnel
la source
6
Très agréable. Vous pourriez faire la même chose avec data.frame, cependant. Il n'y a rien ici qui ait vraiment besoin data.table. La clé est le lapply, ce qui évite la copie de l'ensemble de l'objet effectuée par is.na(df). +10 pour l'avoir signalé.
Matt Dowle
1
Comment feriez-vous avec un data.frame? @ matt-dowle
s_a
8
@s_a, bd1 <- bd[, unlist(lapply(bd, function(x), !all(is.na(x))))]
mnel
6
@mnel Je pense que vous devez supprimer l' ,après function(x)- merci pour l'exemple btw
Thieme Hennis
1
Pouvez-vous le faire plus rapidement avec: = ou avec un set ()?
skan
49

dplyra maintenant un select_ifverbe qui peut être utile ici:

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
zack
la source
Je suis venu ici à la recherche de la dplyrsolution. N'a pas été déçu. Merci!
Andrew Brēza
J'ai trouvé que cela avait le problème de supprimer également les variables avec la plupart des valeurs, mais pas toutes, comme manquantes
MBorg
15

Une autre façon serait d'utiliser la apply()fonction.

Si vous avez le data.frame

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

Ensuite, vous pouvez utiliser apply()pour voir quelles colonnes remplissent votre condition et ainsi vous pouvez simplement faire le même sous-ensemble que dans la réponse de Musa, uniquement avec une applyapproche.

> !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
mropa
la source
3
Je m'attendais à ce que ce soit plus rapide, car la solution colSum () semblait faire plus de travail. Mais sur mon ensemble de tests (213 obs. De 1614 variables avant, contre 1377 variables après), cela prend exactement 3 fois plus de temps. (Mais +1 pour une approche intéressante.)
Darren Cook
10

En retard dans le jeu mais vous pouvez également utiliser le janitorpackage. Cette fonction supprimera les colonnes qui sont toutes NA et peut être modifiée pour supprimer les lignes qui sont toutes NA également.

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

André.B
la source
5
df[sapply(df, function(x) all(is.na(x)))] <- NULL
jpmorris
la source
4

La réponse acceptée ne fonctionne pas avec les colonnes non numériques. À partir de cette réponse , ce qui suit fonctionne avec des colonnes contenant différents types de données

Filter(function(x) !all(is.na(x)), df)
jeromeRecherche
la source
Quelqu'un d'autre a déjà publié la même réponse dans ce fil 4 ans avant vous ... Voir la réponse de mnel ci-dessous.
André.B
2

Autres options avec purrrpackage:

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(.)))
AlexB
la source
1

J'espère que cela peut également aider. Il pourrait être transformé en une seule commande, mais je l'ai trouvé plus facile à lire en le divisant en deux commandes. J'ai créé une fonction avec les instructions suivantes et j'ai travaillé très vite.

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

.SD permettra de limiter la vérification à une partie du tableau, si vous le souhaitez, mais il prendra le tableau entier comme

Luis M. Nieves
la source
1

Une base Roption pratique pourrait être colMeans():

df[, colMeans(is.na(df)) != 1]
tmfmnk
la source
0

Vous pouvez utiliser le package Janitor remove_empty

library(janitor)

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

Aussi, une autre approche dplyr

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

OU

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

ceci est également utile si vous souhaitez uniquement exclure / conserver la colonne avec un certain nombre de valeurs manquantes, par exemple

 df %>% select_if(colSums(!is.na(.))>500)
Ashish Singhal
la source