Rimuovere le righe con tutti o alcuni NA (valori mancanti) in data.frame


852

Vorrei rimuovere le righe in questo frame di dati che:

a) contiene NAs su tutte le colonne. Di seguito è riportato il mio esempio di frame di dati.

             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, vorrei 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) contiene NAs 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

Risposte:


1063

Controlla anche 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è più bello semplicemente rimuovendo tutto NA. complete.casesconsente la selezione parziale includendo solo determinate colonne del frame di dati:

> 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

La tua soluzione non può funzionare. Se insisti nell'usare is.na, devi fare qualcosa del tipo:

> 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

ma l'utilizzo complete.casesè molto più chiaro e veloce.


8
Qual è il significato della virgola finale in final[complete.cases(final),]?
hertzsprung,

6
@hertzsprung Devi selezionare le righe, non le colonne. In quale altro modo lo faresti?
Joris Meys,

4
C'è una semplice negazione di complete.cases? Se volessi mantenere le righe con NA invece di scartarle? final[ ! complete.cases(final),]non collabora ...
tumultous_rooster il

2
finalil dataframe è variabile?
Morse

1
@Prateek infatti, lo è.
Joris Meys,

256

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


na.omit elimina le righe ma conserva i numeri delle righe. Come lo aggiusteresti in modo che sia correttamente numerato?
Orso

3
@Bear se non ti importa dei numeri di riga, fallo e basta rownames(x) <- NULL.
Roman Luštrik,

si prega di notare che na.omit()elimina le righe che contengono NAin qualsiasi colonna
Victor Maxwell,

116

tidyrha una nuova funzione 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

3
Non esiste una connessione reale tra pipe e drop_na. Ad esempio, df %>% drop_na(), df %>% na.omit()e drop_na(df)sono tutti sostanzialmente equivalenti.
Ista,

4
@Ista Non sono d'accordo. na.omitaggiunge informazioni aggiuntive come gli indici dei casi omessi e, cosa ancora più importante, non consente di selezionare colonne: è qui che drop_nabrilla.
lukeA

3
Certo, il mio punto è che non di tutto ciò ha a che fare con le pipe. È possibile utilizzare na.omitcon o senza tubi, proprio come è possibile utilizzare drop_nacon o senza tubi.
Ista,

1
Vero, niente a che vedere con le pipe. drop_na () è una funzione come qualsiasi altra e, come tale, può essere chiamata direttamente o usando una pipe. Sfortunatamente, drop_na (), a differenza degli altri metodi citati, non può essere usato su tipi di oggetto zoo o xts. Questo potrebbe essere un problema per alcuni.
Dave,

Bene, quindi ho modificato la risposta in modo che non menzionasse le pipe.
Arthur Yip,

91

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

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

Ciò restituisce un vettore logico con valori che indicano se esiste una NA in una riga. Puoi usarlo per vedere quante righe devi eliminare:

sum(row.has.na)

e infine rilasciarli

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

Per filtrare le righe con determinate parti di NA diventa un po 'più complicato (ad esempio, puoi alimentare "final [, 5: 6]" per "applicare"). In generale, la soluzione di Joris Meys sembra essere più elegante.


2
Questo è estremamente lento. Molto più lento rispetto ad esempio alla soluzione complete.cases () sopra menzionata. Almeno, nel mio caso, sui dati xts.
Dave,

3
rowSum(!is.na(final))sembra più adatto diapply()
sindri_baldur

45

Un'altra opzione se si desidera un maggiore controllo sul modo in cui le righe sono considerate non valide è

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

Utilizzando 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 rnorAND cfam. La logica booleana può quindi essere modificata per adattarsi a requisiti specifici.


5
ma come puoi usarlo se vuoi controllare molte colonne, senza digitare ognuna, puoi usare un intervallo finale [, 4: 100]?
Herman Toothrot,

40

Se si desidera controllare il numero di NA validi per ogni riga, provare questa funzione. Per molti set di dati del sondaggio, troppe risposte vuote alle domande possono rovinare i risultati. Quindi vengono eliminati dopo una certa soglia. Questa funzione ti consentirà di scegliere il numero di NA che la riga può avere prima di essere eliminata:

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

Per impostazione predefinita, eliminerà tutti i NA:

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

Oppure specifica 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

39

Se le prestazioni sono una priorità, utilizzare data.tablee na.omit()con i parametri opzionali 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, utilizzarecomplete.cases() .

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

Risultato del benchmark

Ecco un confronto tra base (blu), dplyr(rosa) edata.table metodi (giallo) per eliminare tutti o selezionare le osservazioni mancanti, su un insieme di dati nozionale di 1 milione di osservazioni di 20 variabili numeriche con probabilità indipendente del 5% di mancare, e un sottoinsieme di 4 variabili per la parte 2.

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

Annotare la scala del registro sull'asse y.

inserisci qui la descrizione dell'immagine

Script di benchmark

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

18

Usando il pacchetto dplyr possiamo filtrare NA come segue:

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

1
Questo è circa 10.000 volte più lento didrop_na()
Zimano,

17

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

16

Per la tua prima domanda, ho un codice con cui mi sento a mio agio per sbarazzarmi di tutte le NA. Grazie per @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. Ciò eliminerà le righe con tutti i NA, poiché rowSums aggiunge fino a 5 e diventano zero dopo la sottrazione. Questa volta è necessario as.logical.


final [as.logical ((rowSums (is.na (final)) - ncol (final))),] per una risposta universale
Ferroao,

14

A tale scopo, possiamo anche utilizzare la funzione del sottoinsieme.

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

Ciò fornirà solo quelle righe che non hanno NA in mmul e rnor


9

Sono un sintetizzatore :). Qui ho combinato le risposte in una 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://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)
}

8

Supponendo datche il tuo frame di dati, l'output previsto può essere raggiunto utilizzando

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

7

Un approccio che è sia generale e produce codice abbastanza leggibile è quello di utilizzare la filterfunzione di e le sue varianti nel pacchetto 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(.))

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 funzione precedente elimina tutte le righe dal frame di dati che ha "NA" in qualsiasi colonna e restituisce i dati risultanti. Se si desidera verificare la presenza di più valori come NAe ?modificare la dart=c('NA')funzione param indart=c('NA', '?')


3

La mia ipotesi è che questo potrebbe essere risolto in modo più elegante 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

6
questo manterrà le righe con NA. Penso che ciò che l'OP vuole sia:df %>% filter_all(all_vars(!is.na(.)))
asifzuba, il
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.