variable - Rimuovi le righe con tutte o alcune NA(valori mancanti) in data.frame




sort dataframe in r (10)

Se la prestazione è una priorità, utilizzare data.table e na.omit() con param cols= .

na.omit.data.table è il più veloce sul mio benchmark (vedi sotto), sia per tutte le colonne che per le colonne selezionate (domanda OP parte 2).

Se non si desidera utilizzare data.table , utilizzare complete.cases() .

Su un data.frame vaniglia, complete.cases è più veloce di na.omit() o dplyr::drop_na() . Si noti che na.omit.data.frame non supporta cols= .

Risultato del benchmark

Ecco un confronto tra i metodi base (blu), dplyr (rosa) e data.table (giallo) per rilasciare tutti o selezionare le osservazioni mancanti, su un dataset nozionale di 1 milione di osservazioni di 20 variabili numeriche con probabilità indipendente del 5% di essere mancante e un sottoinsieme di 4 variabili per la parte 2.

I risultati possono variare in base alla lunghezza, alla larghezza e alla scarsità del set di dati specifico.

Scala del registro delle note sull'asse y.

Script di riferimento

#-------  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))
)

Mi piacerebbe rimuovere le linee in questo frame di dati che:

a) contiene NA s attraverso tutte le colonne. Di seguito è riportato il mio frame di dati di esempio.

             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

Fondamentalmente, mi piacerebbe ottenere un frame di dati come il seguente.

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

b) contengono NA s solo in alcune colonne , quindi posso anche ottenere questo risultato:

             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

Ciò restituirà le righe che hanno almeno UN valore non NA.

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

Ciò restituirà le righe che hanno almeno DUE valori non NA.

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

La mia ipotesi è che questo potrebbe essere risolto più elegantemente in questo modo

  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

Per la tua prima domanda, ho un codice con cui mi sento a mio agio per sbarazzarmi di tutte le NA. Grazie a @Gregor per renderlo più semplice.

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

Per la seconda domanda, il codice è solo un'alternativa alla soluzione precedente.

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

Si noti che -5 è il numero di colonne nei dati. Questo eliminerà le righe con tutte le NA, dal momento che rowSum aggiunge fino a 5 e diventano zero dopo la sottrazione. Questa volta, as.logical è necessario.


Preferisco seguire il modo per verificare se le righe contengono NA:

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

Questo restituisce il vettore logico con valori che indicano se esiste una NA in una riga. Puoi usarlo per vedere quante righe dovrai eliminare:

sum(row.has.na)

e alla fine lasciarli

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

Per filtrare le righe con una determinata parte di NA diventa un po 'più complicato (ad esempio, puoi alimentare' finale [, 5: 6] 'a' applicare '). In generale, la soluzione di Joris Meys sembra essere più elegante.


Prova na.omit(your.data.frame) . Per quanto riguarda la seconda domanda, prova a postarla come un'altra domanda (per chiarezza).


Se vuoi controllare il numero di NA validi per ogni riga, prova questa funzione. Per molti set di dati di sondaggi, troppe risposte a domande vuote possono rovinare i risultati. Quindi vengono cancellati dopo una certa soglia. Questa funzione ti permetterà di scegliere quanti NA la fila può avere prima che sia cancellata:

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

Per impostazione predefinita, eliminerà tutte le 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 specificare il numero massimo di NA consentiti:

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

Sono un sintetizzatore :). Qui ho combinato le risposte in un'unica funzione:

#' 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)
}

Un'altra opzione se si desidera un maggiore controllo su come le righe sono ritenute non valide è

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

Usando quanto sopra, questo:

             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

diventa:

             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

... dove viene rimossa solo la riga 5 poiché è l'unica riga che contiene NA per entrambi i rnor AND cfam . La logica booleana può quindi essere modificata per soddisfare requisiti specifici.


Usando il pacchetto dplyr possiamo filtrare NA come segue:

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




r-faq