Entfernen Sie Zeilen mit allen oder einigen NAs (fehlenden Werten) in data.frame


852

Ich möchte die Zeilen in diesem Datenrahmen entfernen, die:

a) enthalten NAs über alle Spalten. Unten ist mein Beispieldatenrahmen.

             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

Grundsätzlich möchte ich einen Datenrahmen wie den folgenden erhalten.

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

b) enthält NAs nur in einigen Spalten , so dass ich auch dieses Ergebnis erhalten kann:

             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

Antworten:


1063

Überprüfen Sie auch 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.omitist schöner, wenn man nur alle entfernt NA. complete.casesermöglicht eine teilweise Auswahl, indem nur bestimmte Spalten des Datenrahmens eingeschlossen werden:

> 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

Ihre Lösung kann nicht funktionieren. Wenn Sie darauf bestehen, etwas zu verwenden is.na, müssen Sie Folgendes tun:

> 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

Die Verwendung complete.casesist jedoch viel klarer und schneller.


8
Welche Bedeutung hat das nachfolgende Komma final[complete.cases(final),]?
Hertzsprung

6
@hertzsprung Sie müssen Zeilen auswählen, keine Spalten. Wie würden Sie das sonst tun?
Joris Meys

4
Gibt es eine einfache Negation von complete.cases? Wenn ich die Zeilen mit NAs behalten wollte, anstatt sie zu verwerfen? final[ ! complete.cases(final),]kooperiert nicht ...
tumultous_rooster

2
finalist der Datenrahmen variabel?
Morse

1
@Prateek in der Tat ist es.
Joris Meys

256

Versuchen Sie es na.omit(your.data.frame). Versuchen Sie, die zweite Frage als weitere Frage zu veröffentlichen (aus Gründen der Übersichtlichkeit).


na.omit löscht die Zeilen, behält aber die Zeilennummern bei. Wie würden Sie das beheben, damit es richtig nummeriert ist?
Bär

3
@Bear, wenn Sie sich nicht für Zeilennummern interessieren, tun Sie es einfach rownames(x) <- NULL.
Roman Luštrik

Bitte beachten Sie, dass na.omit()Zeilen gelöscht werden, die NAin einer beliebigen Spalte enthalten
Victor Maxwell

116

tidyrhat eine neue Funktion 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
Es gibt keine echte Verbindung zwischen Rohren und drop_na. Zum Beispiel df %>% drop_na(), df %>% na.omit()und drop_na(df)alle sind im Grunde gleichwertig.
Ista

4
@Ista Ich bin anderer Meinung. na.omitFügt zusätzliche Informationen wie die Indizes ausgelassener Fälle hinzu und - was noch wichtiger ist - Sie können keine Spalten auswählen - hier drop_naleuchtet.
Luke am

3
Klar, mein Punkt ist, dass nichts davon mit Rohren zu tun hat. Sie können na.omitmit oder ohne Rohre verwenden, genauso wie Sie drop_namit oder ohne Rohre verwenden können.
Ista

1
Es stimmt, überhaupt nichts mit Rohren zu tun. drop_na () ist nur eine Funktion wie jede andere und kann als solche direkt oder über eine Pipe aufgerufen werden. Leider kann drop_na () im Gegensatz zu den anderen genannten Methoden nicht für zoo- oder xts-Objekttypen verwendet werden. Dies könnte für einige ein Problem sein.
Dave

Richtig, also habe ich die Antwort so bearbeitet, dass keine Pipes erwähnt werden.
Arthur Yip

91

Ich bevorzuge folgende Methode, um zu überprüfen, ob Zeilen NAs enthalten:

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

Dies gibt einen logischen Vektor mit Werten zurück, die angeben, ob eine NA in einer Zeile vorhanden ist. Sie können damit sehen, wie viele Zeilen Sie löschen müssen:

sum(row.has.na)

und sie schließlich fallen lassen

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

Das Filtern von Zeilen mit einem bestimmten Teil der NAs wird etwas schwieriger (z. B. können Sie 'final [, 5: 6]' mit 'apply' füttern). Im Allgemeinen scheint die Lösung von Joris Meys eleganter zu sein.


2
Das ist extrem langsam. Viel langsamer als zB die oben erwähnte Lösung complete.cases (). Zumindest in meinem Fall auf xts Daten.
Dave

3
rowSum(!is.na(final))scheint besser geeignet alsapply()
sindri_baldur

45

Eine weitere Option, wenn Sie mehr Kontrolle darüber haben möchten, wie Zeilen als ungültig eingestuft werden, ist

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

Mit den oben genannten, diese:

             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

Wird:

             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

... wo nur Zeile 5 entfernt wird, da dies die einzige Zeile ist, die NAs für rnorUND enthält cfam. Die boolesche Logik kann dann geändert werden, um bestimmten Anforderungen zu entsprechen.


5
Aber wie können Sie dies verwenden, wenn Sie viele Spalten überprüfen möchten, ohne jede einzelne einzugeben? Können Sie einen Bereich final [, 4: 100] verwenden?
Herman Toothrot

40

Wenn Sie steuern möchten, wie viele NAs für jede Zeile gültig sind, versuchen Sie diese Funktion. Bei vielen Umfragedatensätzen können zu viele leere Fragenantworten die Ergebnisse ruinieren. Sie werden also nach einem bestimmten Schwellenwert gelöscht. Mit dieser Funktion können Sie auswählen, wie viele NAs die Zeile haben kann, bevor sie gelöscht wird:

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

Standardmäßig werden alle NAs entfernt:

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

Oder geben Sie die maximal zulässige Anzahl von NAs an:

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

Wenn Leistung Priorität hat, verwenden Sie data.tableund na.omit()mit optionalem Parameter cols=.

na.omit.data.table ist die schnellste in meinem Benchmark (siehe unten), ob für alle Spalten oder für ausgewählte Spalten (OP-Frage Teil 2).

Wenn Sie nicht verwenden möchten data.table, verwenden Siecomplete.cases() .

Auf einer Vanille data.frame, complete.casesist schneller als na.omit()oder dplyr::drop_na(). Beachten Sie, dass na.omit.data.framedies nicht unterstützt wird cols=.

Benchmark-Ergebnis

Hier ist ein Vergleich der Basismethoden (blau), dplyr(rosa) und data.table(gelb) zum Löschen aller oder der Auswahl fehlender Beobachtungen anhand eines fiktiven Datensatzes von 1 Million Beobachtungen von 20 numerischen Variablen mit einer unabhängigen Wahrscheinlichkeit von 5%, dass sie fehlen, und a Teilmenge von 4 Variablen für Teil 2.

Ihre Ergebnisse können je nach Länge, Breite und Sparsamkeit Ihres bestimmten Datensatzes variieren.

Beachten Sie die Protokollskala auf der y-Achse.

Geben Sie hier die Bildbeschreibung ein

Benchmark-Skript

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

Mit dem Paket dplyr können wir NA wie folgt filtern:

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

1
Dies ist ungefähr 10.000 Mal langsamer alsdrop_na()
Zimano

17

Dies gibt die Zeilen zurück, die mindestens EINEN Nicht-NA-Wert haben.

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

Dies gibt die Zeilen zurück, die mindestens ZWEI Nicht-NA-Werte haben.

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

16

Für Ihre erste Frage habe ich einen Code, mit dem ich alle NAs loswerden kann. Vielen Dank für @Gregor, um es einfacher zu machen.

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

Bei der zweiten Frage ist der Code nur eine Abwechslung zur vorherigen Lösung.

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

Beachten Sie, dass -5 die Anzahl der Spalten in Ihren Daten ist. Dadurch werden Zeilen mit allen NAs eliminiert, da sich die rowSums zu 5 addieren und sie nach Subtraktion zu Nullen werden. Diesmal ist as.logical notwendig.


final [as.logical ((rowSums (is.na (final)) - ncol (final))], für eine universelle Antwort
Ferroao

14

Wir können hierfür auch die Teilmengenfunktion verwenden.

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

Dies gibt nur die Zeilen an, die in mmul und rnor keine NA haben


9

Ich bin ein Synthesizer :). Hier habe ich die Antworten zu einer Funktion zusammengefasst:

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

Angenommen, datals Ihr Datenrahmen kann die erwartete Ausgabe mit erreicht werden

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

Ein Ansatz, der sowohl die allgemeine ist und ergibt ziemlich lesbaren Code ist es, die verwendet werden filterFunktion und seine Varianten im dplyr Paket ( 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)

Die obige Funktion löscht alle Zeilen aus dem Datenrahmen mit 'NA' in einer beliebigen Spalte und gibt die resultierenden Daten zurück. Wenn Sie für mehrere Werte wie zu überprüfen NAund ?ändern dart=c('NA')in Funktion Paramdart=c('NA', '?')


3

Ich vermute, dass dies auf diese Weise eleganter gelöst werden könnte:

  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
Dadurch bleiben die Zeilen mit erhalten NA. Ich denke, was die OP will, ist:df %>% filter_all(all_vars(!is.na(.)))
Asifzuba
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.