Eliminar columnas del marco de datos donde TODOS los valores son NA


149

Tengo problemas con un marco de datos y realmente no puedo resolver ese problema yo mismo:
el marco de datos tiene propiedades arbitrarias como columnas y cada fila representa un conjunto de datos .

La pregunta es:
¿Cómo deshacerse de las columnas donde para TODAS las filas el valor es NA ?

Respuestas:


155

Prueba esto:

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

3
Esto crea un objeto del tamaño del objeto antiguo que es un problema con la memoria en objetos grandes. Es mejor usar una función para reducir el tamaño. La respuesta a continuación usando Filter o data.table ayudará a su uso de memoria.
mtelesha

3
Esto no parece funcionar con columnas no numéricas.
verbamour

Cambia el nombre de la columna si están duplicados
Peter.k

97

Los dos enfoques ofrecidos hasta ahora fallan con los grandes conjuntos de datos que (entre otros problemas de memoria) crean is.na(df), que será un objeto del mismo tamaño que df.

Aquí hay dos enfoques que son más eficientes en cuanto a memoria y tiempo

Un enfoque usando Filter

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

y un enfoque usando data.table (para tiempo general y eficiencia de memoria)

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

ejemplos que usan datos grandes (30 columnas, 1e6 filas)

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 

66
Muy agradable. Sin embargo, podrías hacer lo mismo con data.frame. Aquí no hay nada que realmente necesite data.table. La clave es el lapply, que evita la copia de todo el objeto realizado por is.na(df). +10 por señalar eso.
Matt Dowle

1
¿Cómo lo harías con un data.frame? @ matt-dowle
s_a

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

66
@mnel Creo que necesitas eliminar el ,after function(x)- gracias por el ejemplo por cierto
Thieme Hennis

1
¿Puedes hacerlo más rápido con: = o con un conjunto ()?
skan

49

dplyrahora tiene un select_ifverbo que puede ser útil aquí:

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

Vine aquí buscando la dplyrsolución. No me decepcionó. ¡Gracias!
Andrew Brēza

Descubrí que esto tenía el problema de que también eliminaría variables con la mayoría pero no todos los valores como faltantes
MBorg

15

Otra forma sería usar la apply()función.

Si tienes el data.frame

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

entonces puede usar apply()para ver qué columnas cumplen con su condición y así puede simplemente hacer el mismo subconjunto que en la respuesta de Musa, solo con un applyenfoque.

> !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
Esperaba que esto fuera más rápido, ya que la solución colSum () parecía estar haciendo más trabajo. Pero en mi conjunto de pruebas (213 obs. De 1614 variables antes, frente a 1377 variables después) toma exactamente 3 veces más. (Pero +1 para un enfoque interesante.)
Darren Cook

10

Tarde al juego, pero también puedes usar el janitorpaquete. Esta función eliminará las columnas que son todas NA y también se puede cambiar para eliminar las filas que son todas NA.

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



4

La respuesta aceptada no funciona con columnas no numéricas. De esta respuesta , lo siguiente funciona con columnas que contienen diferentes tipos de datos

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

Alguien más ya ha publicado la misma respuesta en este hilo 4 años antes que usted ... Vea la respuesta de mnel a continuación.
André.B

2

Otras opciones con purrrpaquete:

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 esto también pueda ayudar. Podría convertirse en un solo comando, pero me resultó más fácil de leer al dividirlo en dos comandos. Hice una función con las siguientes instrucciones y trabajé a la velocidad del rayo.

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

.SD permitirá limitar la verificación a parte de la tabla, si lo desea, pero tomará toda la tabla como


1

Una base Ropción útil podría ser colMeans():

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

0

Puedes usar el paquete de conserje remove_empty

library(janitor)

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

Además, otro enfoque dplyr

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

O

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

Esto también es útil si desea excluir / mantener solo la columna con cierto número de valores faltantes, por ejemplo

 df %>% select_if(colSums(!is.na(.))>500)
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.