dataframe remove - Eliminar filas con todos o algunos NA(valores perdidos)en data.frame




value missing (13)

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

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

             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 NA s 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

Answers

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 algún NA en una fila. Puedes usarlo para ver cuántas filas tendrás que soltar:

sum(row.has.na)

y finalmente soltarlos

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

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


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://.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)
}

También puedes ver las casillas 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.omit es más agradable solo para eliminar todas las NA . complete.cases permite la selección parcial incluyendo 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 insistes en usar is.na , entonces tienes 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 de complete.cases es mucho más claro y más rápido.


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

Si el rendimiento es una prioridad, use data.table y na.omit() con na.omit() opcionales 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 2 de la parte OP).

Si no desea utilizar data.table , use complete.cases() .

En un data.frame vainilla, complete.cases es más rápido que na.omit() o dplyr::drop_na() . Tenga en cuenta que na.omit.data.frame no admite cols= .

Resultado de referencia

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

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

Observe la escala de registro en el eje y.

Guión 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))
)

Otra opción si desea un mayor control sobre cómo se considera que las filas no son 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 rnor AND cfam . La lógica booleana se puede cambiar para que se ajuste a requisitos específicos.


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

dplyr::filter(df,  !is.na(columnname))

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),]

Si desea controlar la cantidad de NA que son válidas para cada fila, pruebe esta función. Para muchos conjuntos de datos de encuestas, demasiadas respuestas de preguntas en blanco pueden arruinar los resultados. Así que 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 AN:

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 permitidas:

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

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


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

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

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

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

Observe que el -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.


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 tanto en mmul como en rnor


solo aísle su marco de datos a las columnas que necesita, luego use la función única: D

# in the above example, you only need the first three columns
deduped.data <- unique( yourdata[ , 1:3 ] )
# the fourth column no longer 'distinguishes' them, 
# so they're duplicates and thrown out.




r dataframe filter missing-data r-faq