¿Cómo eliminar columnas que contienen SOLO NA?

83

Tengo un data.frame que contiene algunas columnas con todos los valores NA, ¿cómo puedo eliminarlas del data.frame?

¿Puedo usar la función?

na.omit(...) 

especificando algunos argumentos adicionales?

Lorenzo Rigamonti
fuente
1
¡Hola! Haga que su publicación sea reproducible. Lea la publicación sobre cómo hacer un gran ejemplo reproducible sobre cómo hacer esto. Gracias.
Arun
¿Ayuda esta publicación? stackoverflow.com/questions/4862178/…
Arun
puedes publicar head(data)? ¿Quiere eliminar las columnas o filas correspondientes?
Nishanth
@ e4e5f4 Quiero eliminar las columnas correspondientes (todos los valores de las columnas que quiero eliminar son NA)
Lorenzo Rigamonti

Respuestas:

123

Una forma de hacerlo:

df[, colSums(is.na(df)) != nrow(df)]

Si el recuento de NA en una columna es igual al número de filas, debe ser totalmente NA.

O de manera similar

df[colSums(!is.na(df)) > 0]
MadScone
fuente
1
¿Cómo puedo eliminar columnas que tienen más de un umbral de NA? o en porcentaje (digamos más del 50%)?
discipulus
2
@lovedynasty Probablemente sea mejor enviar una pregunta por separado, asumiendo que no lo ha hecho desde que publicó su comentario. Pero de todos modos, siempre puede hacer algo como, por df[, colSums(is.na(df)) < nrow(df) * 0.5]ejemplo, mantener solo columnas con al menos un 50% de espacios en blanco.
MadScone
2
Las personas que trabajan con una matriz de correlación deben usar, df[, colSums(is.na(df)) != nrow(df) - 1]ya que la diagonal es siempre1
Boern
9
También puede usar esto con la función dplyr (versión 0.5.0) select_if. df %>% select_if(colSums(!is.na(.)) > 0)
Stefan Avey
@MadScone me está dando un error de sintaxis en "," para df [, colSums (is.na (df))! = Nrow (df)] y error de sintaxis en "!" en df [colSums (! is.na (df))> 0]. ¿Me estoy perdiendo algo
Aravind S
52

Aquí hay una solución dplyr:

df %>% select_if(~sum(!is.na(.)) > 0)
Brad Cannell
fuente
4
En ~ 15k filas y ~ 5k columnas, esto realmente está tardando una eternidad.
EngrStudent
@EngrStudent ¿Fue más rápido con la solución de respuesta aceptada?
johnny
Han pasado varios años. No recuerdo DJV tiene una buena publicación de sincronización a continuación.
EngrStudent
25

Otra opción es el janitorpaquete:

df <- remove_empty_cols(df)

https://github.com/sfirke/janitor

jsta
fuente
10
janitor::remove_empty_cols()está en desuso - usedf <- janitor::remove_empty(df, which = "cols")
André.B
24

Parece que desea eliminar SOLO columnas con TODAS las NA s, dejando columnas con algunas filas que tienen NAs. Haría esto (pero estoy seguro de que hay una solución vectorizada eficiente:

#set seed for reproducibility
set.seed <- 103
df <- data.frame( id = 1:10 , nas = rep( NA , 10 ) , vals = sample( c( 1:3 , NA ) , 10 , repl = TRUE ) )
df
#      id nas vals
#   1   1  NA   NA
#   2   2  NA    2
#   3   3  NA    1
#   4   4  NA    2
#   5   5  NA    2
#   6   6  NA    3
#   7   7  NA    2
#   8   8  NA    3
#   9   9  NA    3
#   10 10  NA    2

#Use this command to remove columns that are entirely NA values, it will elave columns where only some vlaues are NA
df[ , ! apply( df , 2 , function(x) all(is.na(x)) ) ]
#      id vals
#   1   1   NA
#   2   2    2
#   3   3    1
#   4   4    2
#   5   5    2
#   6   6    3
#   7   7    2
#   8   8    3
#   9   9    3
#   10 10    2

Si se encuentra en la situación en la que desea eliminar columnas que tienen algún NAvalor, simplemente puede cambiar el allcomando anterior a any.

Simon O'Hanlon
fuente
El data.frame tiene dos tipos de columnas: una en whohc todos los valores son números y la otra en la que todos los valores son NA
Lorenzo Rigamonti
Entonces esto funcionará. Solo elimina las columnas donde están TODOS los valores NA.
Simon O'Hanlon
1
Buena solución. Sin apply(is.na(df), 1, all)embargo, lo haría solo porque es un poco más ordenado y is.na()se usa en todas en dflugar de en una fila a la vez (demuestre ser un poco más rápido).
MadScone
@MadScone buen consejo: se ve más ordenado. Sin embargo, debe aplicar en columnas, no en filas.
Simon O'Hanlon
Las ediciones de @MadScone se bloquean después de 5 minutos en los comentarios. ¡No debería preocuparme, no es gran cosa! :-)
Simon O'Hanlon
19

Una secuencia de comandos intuitiva: dplyr::select_if(~!all(is.na(.))). Literalmente mantiene solo las columnas que faltan no todos los elementos. (para eliminar las columnas que faltan todos los elementos).

> df <- data.frame( id = 1:10 , nas = rep( NA , 10 ) , vals = sample( c( 1:3 , NA ) , 10 , repl = TRUE ) )

> df %>% glimpse()
Observations: 10
Variables: 3
$ id   <int> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10
$ nas  <lgl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA
$ vals <int> NA, 1, 1, NA, 1, 1, 1, 2, 3, NA

> df %>% select_if(~!all(is.na(.))) 
   id vals
1   1   NA
2   2    1
3   3    1
4   4   NA
5   5    1
6   6    1
7   7    1
8   8    2
9   9    3
10 10   NA
Sibo Jiang
fuente
17

Otra opcion con Filter

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

NOTA: Datos de la publicación de @Simon O'Hanlon.

Akrun
fuente
5

Como el rendimiento era realmente importante para mí, comparé todas las funciones anteriores.

NOTA: Datos de la publicación de @Simon O'Hanlon. Solo con el tamaño 15000 en lugar de 10.

library(tidyverse)
library(microbenchmark)

set.seed(123)
df <- data.frame(id = 1:15000,
                 nas = rep(NA, 15000), 
                 vals = sample(c(1:3, NA), 15000,
                               repl = TRUE))
df

MadSconeF1 <- function(x) x[, colSums(is.na(x)) != nrow(x)]

MadSconeF2 <- function(x) x[colSums(!is.na(x)) > 0]

BradCannell <- function(x) x %>% select_if(~sum(!is.na(.)) > 0)

SimonOHanlon <- function(x) x[ , !apply(x, 2 ,function(y) all(is.na(y)))]

jsta <- function(x) janitor::remove_empty(x)

SiboJiang <- function(x) x %>% dplyr::select_if(~!all(is.na(.)))

akrun <- function(x) Filter(function(y) !all(is.na(y)), x)

mbm <- microbenchmark(
  "MadSconeF1" = {MadSconeF1(df)},
  "MadSconeF2" = {MadSconeF2(df)},
  "BradCannell" = {BradCannell(df)},
  "SimonOHanlon" = {SimonOHanlon(df)},
  "SiboJiang" = {SiboJiang(df)},
  "jsta" = {jsta(df)}, 
  "akrun" = {akrun(df)},
  times = 1000)

mbm

Resultados:

Unit: microseconds
         expr    min      lq      mean  median      uq      max neval  cld
   MadSconeF1  154.5  178.35  257.9396  196.05  219.25   5001.0  1000 a   
   MadSconeF2  180.4  209.75  281.2541  226.40  251.05   6322.1  1000 a   
  BradCannell 2579.4 2884.90 3330.3700 3059.45 3379.30  33667.3  1000    d
 SimonOHanlon  511.0  565.00  943.3089  586.45  623.65 210338.4  1000  b  
    SiboJiang 2558.1 2853.05 3377.6702 3010.30 3310.00  89718.0  1000    d
         jsta 1544.8 1652.45 2031.5065 1706.05 1872.65  11594.9  1000   c 
        akrun   93.8  111.60  139.9482  121.90  135.45   3851.2  1000 a


autoplot(mbm)

ingrese la descripción de la imagen aquí

mbm %>% 
  tbl_df() %>%
  ggplot(aes(sample = time)) + 
  stat_qq() + 
  stat_qq_line() +
  facet_wrap(~expr, scales = "free")

ingrese la descripción de la imagen aquí

DJV
fuente
A veces, la primera iteración es un JIT compilado, por lo que tiene tiempos muy pobres y no muy característicos. Creo que es interesante lo que hace el tamaño de muestra más grande en las colas correctas de la distribución. Este es un buen trabajo.
EngrStudent
Lo ejecuté una vez más, no estaba seguro de haber cambiado la trama. En cuanto a la distribución, de hecho. Probablemente debería comparar diferentes tamaños de muestra cuando tenga tiempo.
DJV
1
si qqplot ( ggplot2.tidyverse.org/reference/geom_qq.html ) una de las tendencias, como "akrun", apuesto a que hay un punto que es muy diferente de la distribución del resto. El resto representa cuánto tiempo lleva si lo ejecuta repetidamente, pero eso representa lo que sucede si lo ejecuta una vez. Hay un viejo dicho: puedes tener 20 años de experiencia o solo puedes tener un año de experiencia 20 veces.
IngrStudent
¡muy agradable! Me sorprende que varias muestras estén en la cola extrema. Me pregunto por qué son mucho más costosos. JIT puede ser 1 o 2 pero no 20. ¿Condición? ¿Interrumpe? ¿Otro? Gracias nuevamente por la actualización.
EngrStudent
De nada, gracias por los pensamientos. No sé, de hecho lo dejé correr "libremente".
DJV