Eliminar filas con todos o algunos NA (valores faltantes) en data.frame

852

Me gustaría eliminar las líneas en este marco de datos que:

a) contienen NAs en todas las columnas. A continuación se muestra mi marco de datos de ejemplo.

             gene hsap mmul mmus rnor cfam
1 ENSG00000208234    0   NA   NA   NA   NA
2 ENSG00000199674    0   2    2    2    2
3 ENSG00000221622    0   NA   NA   NA   NA
4 ENSG00000207604    0   NA   NA   1    2
5 ENSG00000207431    0   NA   NA   NA   NA
6 ENSG00000221312    0   1    2    3    2

Básicamente, me gustaría obtener un marco de datos como el siguiente.

             gene hsap mmul mmus rnor cfam
2 ENSG00000199674    0   2    2    2    2
6 ENSG00000221312    0   1    2    3    2

b) contiene NAs solo en algunas columnas , por lo que también puedo obtener este resultado:

             gene hsap mmul mmus rnor cfam
2 ENSG00000199674    0   2    2    2    2
4 ENSG00000207604    0   NA   NA   1    2
6 ENSG00000221312    0   1    2    3    2
Benoit B.
fuente

Respuestas:

1063

También verifique complete.cases:

> final[complete.cases(final), ]
             gene hsap mmul mmus rnor cfam
2 ENSG00000199674    0    2    2    2    2
6 ENSG00000221312    0    1    2    3    2

na.omites más agradable para simplemente eliminar todo NA. complete.casespermite la selección parcial al incluir solo ciertas columnas del marco de datos:

> final[complete.cases(final[ , 5:6]),]
             gene hsap mmul mmus rnor cfam
2 ENSG00000199674    0    2    2    2    2
4 ENSG00000207604    0   NA   NA    1    2
6 ENSG00000221312    0    1    2    3    2

Tu solución no puede funcionar. Si insiste en usar is.na, entonces tiene que hacer algo como:

> final[rowSums(is.na(final[ , 5:6])) == 0, ]
             gene hsap mmul mmus rnor cfam
2 ENSG00000199674    0    2    2    2    2
4 ENSG00000207604    0   NA   NA    1    2
6 ENSG00000221312    0    1    2    3    2

pero el uso complete.caseses mucho más claro y rápido.

Joris Meys
fuente
8
¿Cuál es el significado de la coma final final[complete.cases(final),]?
hertzsprung 01 de
66
@hertzsprung Debe seleccionar filas, no columnas. ¿De qué otra forma harías eso?
Joris Meys
44
¿Hay una simple negación de complete.cases? ¿Si quisiera mantener las filas con NA en lugar de descartarlas? final[ ! complete.cases(final),]no coopera ...
tumultous_rooster
2
final¿Es variable el marco de datos?
Morse
1
@Prateek de hecho, lo es.
Joris Meys
256

Tratar na.omit(your.data.frame). En cuanto a la segunda pregunta, intente publicarla como otra pregunta (para mayor claridad).

Roman Luštrik
fuente
na.omit elimina las filas pero conserva los números de fila. ¿Cómo arreglarías esto para que esté correctamente numerado?
Oso
3
@ Tenga en cuenta que si no le importan los números de fila, simplemente hágalo rownames(x) <- NULL.
Roman Luštrik
tenga en cuenta que na.omit()suelta las filas que contienen NAen cualquier columna
Victor Maxwell
116

tidyrtiene una nueva función drop_na:

library(tidyr)
df %>% drop_na()
#              gene hsap mmul mmus rnor cfam
# 2 ENSG00000199674    0    2    2    2    2
# 6 ENSG00000221312    0    1    2    3    2
df %>% drop_na(rnor, cfam)
#              gene hsap mmul mmus rnor cfam
# 2 ENSG00000199674    0    2    2    2    2
# 4 ENSG00000207604    0   NA   NA    1    2
# 6 ENSG00000221312    0    1    2    3    2
lukeA
fuente
3
No hay una conexión real entre tuberías y drop_na. Por ejemplo, df %>% drop_na(), df %>% na.omit()y drop_na(df)son básicamente equivalentes.
Ista
44
@Ista no estoy de acuerdo. na.omitagrega información adicional, como los índices de casos omitidos, y, lo que es más importante, no le permite seleccionar columnas, aquí es donde drop_nabrilla.
lukeA
3
Claro, mi punto es que nada de eso tiene nada que ver con las tuberías. Puede usar na.omitcon o sin tuberías, tal como puede usar drop_nacon o sin tuberías.
Ista
1
Es cierto, nada que ver con las tuberías en absoluto. drop_na () es solo una función como cualquier otra y, como tal, se puede llamar directamente o usando una tubería. Desafortunadamente, drop_na (), a diferencia de los otros métodos mencionados, no se puede usar en tipos de objetos zoo o xts. Esto podría ser un problema para algunos.
Dave
Bien, entonces edité la respuesta para que no mencione tuberías.
Arthur Yip
91

Prefiero la siguiente forma de verificar si las filas contienen NA:

row.has.na <- apply(final, 1, function(x){any(is.na(x))})

Esto devuelve un vector lógico con valores que indican si hay NA en una fila. Puede usarlo para ver cuántas filas tendrá que soltar:

sum(row.has.na)

y finalmente dejarlos caer

final.filtered <- final[!row.has.na,]

Para filtrar filas con cierta parte de NA, se vuelve un poco más complicado (por ejemplo, puede alimentar 'final [, 5: 6]' para 'aplicar'). En general, la solución de Joris Meys parece ser más elegante.

donshikin
fuente
2
Esto es extremadamente lento. Mucho más lento que, por ejemplo, la solución complete.cases () antes mencionada. Al menos, en mi caso, en datos xts.
Dave
3
rowSum(!is.na(final))parece más adecuado queapply()
sindri_baldur
45

Otra opción si desea un mayor control sobre cómo las filas se consideran no válidas es

final <- final[!(is.na(final$rnor)) | !(is.na(rawdata$cfam)),]

Usando lo anterior, esto:

             gene hsap mmul mmus rnor cfam
1 ENSG00000208234    0   NA   NA   NA   2
2 ENSG00000199674    0   2    2    2    2
3 ENSG00000221622    0   NA   NA   2   NA
4 ENSG00000207604    0   NA   NA   1    2
5 ENSG00000207431    0   NA   NA   NA   NA
6 ENSG00000221312    0   1    2    3    2

Se convierte en:

             gene hsap mmul mmus rnor cfam
1 ENSG00000208234    0   NA   NA   NA   2
2 ENSG00000199674    0   2    2    2    2
3 ENSG00000221622    0   NA   NA   2   NA
4 ENSG00000207604    0   NA   NA   1    2
6 ENSG00000221312    0   1    2    3    2

... donde solo se elimina la fila 5, ya que es la única fila que contiene NA para ambos rnorAND cfam. La lógica booleana se puede cambiar para adaptarse a requisitos específicos.

llegar allí
fuente
55
pero ¿cómo puede usar esto si desea verificar muchas columnas, sin escribir cada una, puede usar un rango final [, 4: 100]?
Herman Toothrot
40

Si desea controlar cuántos NA son válidos para cada fila, pruebe esta función. Para muchos conjuntos de datos de encuestas, demasiadas respuestas de preguntas en blanco pueden arruinar los resultados. Por lo tanto, se eliminan después de un cierto umbral. Esta función le permitirá elegir cuántos NA puede tener la fila antes de que se elimine:

delete.na <- function(DF, n=0) {
  DF[rowSums(is.na(DF)) <= n,]
}

Por defecto, eliminará todas las NA:

delete.na(final)
             gene hsap mmul mmus rnor cfam
2 ENSG00000199674    0    2    2    2    2
6 ENSG00000221312    0    1    2    3    2

O especifique el número máximo de NA permitido:

delete.na(final, 2)
             gene hsap mmul mmus rnor cfam
2 ENSG00000199674    0    2    2    2    2
4 ENSG00000207604    0   NA   NA    1    2
6 ENSG00000221312    0    1    2    3    2
velas_y_naranjas
fuente
39

Si el rendimiento es una prioridad, use data.tabley na.omit()con param opcional cols=.

na.omit.data.table es el más rápido en mi punto de referencia (ver más abajo), ya sea para todas las columnas o para columnas seleccionadas (pregunta OP, parte 2).

Si no quiere usar data.table, use complete.cases().

En una vainilla data.frame, complete.caseses más rápido que na.omit()o dplyr::drop_na(). Tenga en cuenta que na.omit.data.frameno es compatible cols=.

Resultado de referencia

Aquí hay una comparación de los métodos base (azul), dplyr(rosa) y data.table(amarillo) para descartar todas o seleccionar las observaciones faltantes, en un conjunto de datos nocionales de 1 millón de observaciones de 20 variables numéricas con un 5% de probabilidad de que falten, y un subconjunto de 4 variables para la parte 2.

Sus resultados pueden variar según la longitud, el ancho y la escasez de su conjunto de datos en particular.

Observe la escala logarítmica en el eje y.

ingrese la descripción de la imagen aquí

Script de referencia

#-------  Adjust these assumptions for your own use case  ------------
row_size   <- 1e6L 
col_size   <- 20    # not including ID column
p_missing  <- 0.05   # likelihood of missing observation (except ID col)
col_subset <- 18:21  # second part of question: filter on select columns

#-------  System info for benchmark  ----------------------------------
R.version # R version 3.4.3 (2017-11-30), platform = x86_64-w64-mingw32
library(data.table); packageVersion('data.table') # 1.10.4.3
library(dplyr);      packageVersion('dplyr')      # 0.7.4
library(tidyr);      packageVersion('tidyr')      # 0.8.0
library(microbenchmark)

#-------  Example dataset using above assumptions  --------------------
fakeData <- function(m, n, p){
  set.seed(123)
  m <-  matrix(runif(m*n), nrow=m, ncol=n)
  m[m<p] <- NA
  return(m)
}
df <- cbind( data.frame(id = paste0('ID',seq(row_size)), 
                        stringsAsFactors = FALSE),
             data.frame(fakeData(row_size, col_size, p_missing) )
             )
dt <- data.table(df)

par(las=3, mfcol=c(1,2), mar=c(22,4,1,1)+0.1)
boxplot(
  microbenchmark(
    df[complete.cases(df), ],
    na.omit(df),
    df %>% drop_na,
    dt[complete.cases(dt), ],
    na.omit(dt)
  ), xlab='', 
  main = 'Performance: Drop any NA observation',
  col=c(rep('lightblue',2),'salmon',rep('beige',2))
)
boxplot(
  microbenchmark(
    df[complete.cases(df[,col_subset]), ],
    #na.omit(df), # col subset not supported in na.omit.data.frame
    df %>% drop_na(col_subset),
    dt[complete.cases(dt[,col_subset,with=FALSE]), ],
    na.omit(dt, cols=col_subset) # see ?na.omit.data.table
  ), xlab='', 
  main = 'Performance: Drop NA obs. in select cols',
  col=c('lightblue','salmon',rep('beige',2))
)
C8H10N4O2
fuente
18

Usando el paquete dplyr podemos filtrar NA de la siguiente manera:

dplyr::filter(df,  !is.na(columnname))
Raminsu
fuente
1
Esto funciona aproximadamente 10.000 veces más lento quedrop_na()
Zimano
17

Esto devolverá las filas que tienen al menos UN valor no NA.

final[rowSums(is.na(final))<length(final),]

Esto devolverá las filas que tienen al menos DOS valores no NA.

final[rowSums(is.na(final))<(length(final)-1),]
León
fuente
16

Para su primera pregunta, tengo un código con el que me siento cómodo para deshacerme de todas las NA. Gracias por @Gregor para hacerlo más simple.

final[!(rowSums(is.na(final))),]

Para la segunda pregunta, el código es solo una alternancia de la solución anterior.

final[as.logical((rowSums(is.na(final))-5)),]

Observe que -5 es el número de columnas en sus datos. Esto eliminará las filas con todos los NA, ya que rowSums suma hasta 5 y se convierten en ceros después de la resta. Esta vez, as.logical es necesario.

LegitMe
fuente
final [as.logical ((rowSums (is.na (final)) - ncol (final))),] para una respuesta universal
Ferroao
14

También podemos usar la función de subconjunto para esto.

finalData<-subset(data,!(is.na(data["mmul"]) | is.na(data["rnor"])))

Esto dará solo aquellas filas que no tienen NA en mmul y rnor

Ramya Ural
fuente
9

Soy un sintetizador :). Aquí combiné las respuestas en una función:

#' keep rows that have a certain number (range) of NAs anywhere/somewhere and delete others
#' @param df a data frame
#' @param col restrict to the columns where you would like to search for NA; eg, 3, c(3), 2:5, "place", c("place","age")
#' \cr default is NULL, search for all columns
#' @param n integer or vector, 0, c(3,5), number/range of NAs allowed.
#' \cr If a number, the exact number of NAs kept
#' \cr Range includes both ends 3<=n<=5
#' \cr Range could be -Inf, Inf
#' @return returns a new df with rows that have NA(s) removed
#' @export
ez.na.keep = function(df, col=NULL, n=0){
    if (!is.null(col)) {
        # R converts a single row/col to a vector if the parameter col has only one col
        # see https://radfordneal.wordpress.com/2008/08/20/design-flaws-in-r-2-%E2%80%94-dropped-dimensions/#comments
        df.temp = df[,col,drop=FALSE]
    } else {
        df.temp = df
    }

    if (length(n)==1){
        if (n==0) {
            # simply call complete.cases which might be faster
            result = df[complete.cases(df.temp),]
        } else {
            # credit: http://stackoverflow.com/a/30461945/2292993
            log <- apply(df.temp, 2, is.na)
            logindex <- apply(log, 1, function(x) sum(x) == n)
            result = df[logindex, ]
        }
    }

    if (length(n)==2){
        min = n[1]; max = n[2]
        log <- apply(df.temp, 2, is.na)
        logindex <- apply(log, 1, function(x) {sum(x) >= min && sum(x) <= max})
        result = df[logindex, ]
    }

    return(result)
}
Jerry T
fuente
8

Asumiendo datcomo su marco de datos, la salida esperada se puede lograr usando

1)rowSums

> dat[!rowSums((is.na(dat))),]
             gene hsap mmul mmus rnor cfam
2 ENSG00000199674    0   2    2    2    2
6 ENSG00000221312    0   1    2    3    2

2)lapply

> dat[!Reduce('|',lapply(dat,is.na)),]
             gene hsap mmul mmus rnor cfam
2 ENSG00000199674    0   2    2    2    2
6 ENSG00000221312    0   1    2    3    2
Prradep
fuente
7

Uno de los enfoques que a la vez general y produce bastante código legible es utilizar la filterfunción y sus variantes en el paquete (dplyr filter_all, filter_at, filter_if):

library(dplyr)

vars_to_check <- c("rnor", "cfam")

# Filter a specific list of columns to keep only non-missing entries
df %>% 
  filter_at(.vars = vars(one_of(vars_to_check)),
            ~ !is.na(.))

# Filter all the columns to exclude NA
df %>% 
  filter_all(~ !is.na(.))

# Filter only numeric columns
df %>%
  filter_if(is.numeric,
            ~ !is.na(.))
bschneidr
fuente
4
delete.dirt <- function(DF, dart=c('NA')) {
  dirty_rows <- apply(DF, 1, function(r) !any(r %in% dart))
  DF <- DF[dirty_rows, ]
}

mydata <- delete.dirt(mydata)

La función anterior elimina todas las filas del marco de datos que tiene 'NA' en cualquier columna y devuelve los datos resultantes. Si desea verificar múltiples valores como NAy ?cambiar dart=c('NA')en la función param adart=c('NA', '?')

sapy
fuente
3

Supongo que esto podría resolverse de manera más elegante de esta manera:

  m <- matrix(1:25, ncol = 5)
  m[c(1, 6, 13, 25)] <- NA
  df <- data.frame(m)
  library(dplyr) 
  df %>%
  filter_all(any_vars(is.na(.)))
  #>   X1 X2 X3 X4 X5
  #> 1 NA NA 11 16 21
  #> 2  3  8 NA 18 23
  #> 3  5 10 15 20 NA
Joni Hoppen
fuente
66
esto retendrá filas con NA. Creo que lo que quiere el OP es:df %>% filter_all(all_vars(!is.na(.)))
asifzuba