Usuń wiersze ze wszystkimi lub niektórymi NA (brakującymi wartościami) w data.frame


851

Chciałbym usunąć linie w tej ramce danych, które:

a) zawierają NAs we wszystkich kolumnach. Poniżej znajduje się moja przykładowa ramka danych.

             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

Zasadniczo chciałbym uzyskać ramkę danych, taką jak poniżej.

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

b) zawierają NAs tylko w niektórych kolumnach , więc mogę również uzyskać ten wynik:

             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

Odpowiedzi:


1062

Sprawdź także 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.omitjest ładniejszy po prostu usuwając wszystko NA. complete.casesumożliwia częściowy wybór poprzez włączenie tylko niektórych kolumn ramki danych:

> 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

Twoje rozwiązanie nie działa. Jeśli nalegasz na użycie is.na, musisz zrobić coś takiego:

> 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

ale używanie complete.casesjest o wiele wyraźniejsze i szybsze.


8
Jakie jest znaczenie przecinka końcowego final[complete.cases(final),]?
hertzsprung

6
@hertzsprung Musisz wybrać wiersze, a nie kolumny. Jak inaczej byś to zrobił?
Joris Meys

4
Czy istnieje prosta negacja complete.cases? Gdybym chciał zachować wiersze z NA zamiast odrzucać? final[ ! complete.cases(final),]nie współpracuje ...
tumultous_rooster

2
finaljest zmienna ramka danych?
Morse

1
@Prateek rzeczywiście tak jest.
Joris Meys,

256

Spróbować na.omit(your.data.frame). Jeśli chodzi o drugie pytanie, spróbuj opublikować je jako inne pytanie (dla jasności).


na.omit upuszcza wiersze, ale zachowuje numery wierszy. Jak to naprawić, aby był poprawnie numerowany?
Niedźwiedź

3
@Przysięgnij, jeśli nie zależy ci na numerach wierszy, po prostu zrób to rownames(x) <- NULL.
Roman Luštrik

należy pamiętać, że na.omit()upuszcza wiersze, które zawierają NAw dowolnej kolumnie
Victor Maxwell

116

tidyrma nową funkcję 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
Nie ma rzeczywistego połączenia między rurami i drop_na. Na przykład df %>% drop_na(), df %>% na.omit()i drop_na(df)wszystkie są w zasadzie równoważne.
Ista

4
@Ista Nie zgadzam się. na.omitdodaje dodatkowe informacje, takie jak wskaźniki pominiętych przypadków, i - co ważniejsze - nie pozwala na wybranie kolumn - tutaj drop_naświeci.
lukeA

3
Jasne, chodzi mi o to, że nie ma to nic wspólnego z rurami. Możesz używać na.omitz rurami lub bez, tak jak drop_naz rurkami lub bez.
Ista

1
To prawda, że ​​w ogóle nie ma to nic wspólnego z rurami. drop_na () jest po prostu funkcją jak każda inna i jako taka może być wywołana bezpośrednio lub za pomocą potoku. Niestety, drop_na (), w przeciwieństwie do innych wspomnianych metod, nie może być używany w typach obiektów zoo lub xts. Dla niektórych może to stanowić problem.
Dave

Racja, więc zredagowałem odpowiedź, aby nie wspominała o rurach.
Arthur Yip

91

Wolę następujący sposób, aby sprawdzić, czy wiersze zawierają jakieś NA:

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

Zwraca wektor logiczny z wartościami wskazującymi, czy jest jakaś NA w wierszu. Możesz go użyć, aby zobaczyć, ile wierszy będziesz musiał upuścić:

sum(row.has.na)

i ostatecznie upuść je

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

W przypadku filtrowania wierszy z określoną częścią NA staje się to trochę trudniejsze (na przykład możesz podać „końcowy [, 5: 6]”, aby „zastosować”). Ogólnie rzecz biorąc, rozwiązanie Jorisa Meysa wydaje się bardziej eleganckie.


2
To jest bardzo wolne. Znacznie wolniej niż np. Wyżej wspomniane rozwiązanie complete.cases (). Przynajmniej w moim przypadku na danych xts.
Dave

3
rowSum(!is.na(final))wydaje się lepiej nadaje się niżapply()
sindri_baldur

45

Inną opcją, jeśli chcesz mieć większą kontrolę nad tym, jak wiersze są uważane za nieprawidłowe, jest

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

Korzystając z powyższego, to:

             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

Staje się:

             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

... gdzie tylko wiersz 5 jest usuwany, ponieważ jest to jedyny wiersz zawierający NA dla obu rnorAND cfam. Logikę logiczną można następnie zmienić w celu dopasowania do określonych wymagań.


5
ale jak możesz tego użyć, jeśli chcesz sprawdzić wiele kolumn, bez wpisywania każdej z nich, czy możesz użyć końcowego zakresu [, 4: 100]?
Herman Toothrot,

40

Jeśli chcesz kontrolować, ile NA jest ważnych dla każdego wiersza, wypróbuj tę funkcję. W przypadku wielu zestawów danych ankiet zbyt wiele pustych odpowiedzi na pytania może zepsuć wyniki. Więc są usuwane po pewnym progu. Ta funkcja pozwoli ci wybrać, ile NA może mieć rząd, zanim zostanie usunięty:

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

Domyślnie wyeliminuje wszystkie NA:

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

Lub określ maksymalną dozwoloną liczbę NA:

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

Jeśli wydajność jest priorytetem, użyj data.tablei na.omit()z opcjonalnym parametrem cols=.

na.omit.data.table jest najszybszy w moim teście porównawczym (patrz poniżej), czy to dla wszystkich kolumn, czy dla wybranych kolumn (pytanie OP część 2).

Jeśli nie chcesz używać data.table , użyj complete.cases().

Na wanilii data.frame, complete.casesjest szybszy niż na.omit()lub dplyr::drop_na(). Zauważ, żena.omit.data.frame nie obsługuje cols=.

Wynik testu

Oto porównanie bazy (niebieski), dplyr(różowy) idata.table (żółtych) metod usuwania wszystkich lub wybranych brakujących obserwacji na hipotetycznym zbiorze danych z 1 milionem obserwacji 20 zmiennych numerycznych z niezależnym 5% prawdopodobieństwem braku oraz podzbiór 4 zmiennych dla części 2.

Wyniki mogą się różnić w zależności od długości, szerokości i rzadkości określonego zestawu danych.

Uwaga skala dziennika na osi y.

wprowadź opis zdjęcia tutaj

Skrypt porównawczy

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

Za pomocą pakietu dplyr możemy filtrować NA w następujący sposób:

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

1
To działa około 10.000 razy wolniej niżdrop_na()
Zimano

17

Zwróci to wiersze, które mają co najmniej JEDNĄ wartość inną niż NA.

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

Zwróci to wiersze, które mają co najmniej DWIE wartości inne niż NA.

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

16

Na twoje pierwsze pytanie mam kod, dzięki któremu mogę się pozbyć wszystkich NA. Dzięki za @Gregor, aby uprościć.

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

W przypadku drugiego pytania kod jest tylko alternatywą dla poprzedniego rozwiązania.

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

Zauważ, że -5 to liczba kolumn w twoich danych. To wyeliminuje wiersze ze wszystkimi NA, ponieważ rowSums sumuje się do 5 i stają się zerami po odjęciu. Tym razem niezbędny jest as.logical.


final [as.logical ((rowSums (is.na (final))) - ncol (final)))], aby uzyskać uniwersalną odpowiedź
Ferroao

14

W tym celu możemy również użyć funkcji podzestawu.

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

To da tylko te wiersze, które nie mają NA w mmul i rnor


9

Jestem syntezatorem :). Tutaj połączyłem odpowiedzi w jedną funkcję:

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

Zakładając, że datjako ramka danych, oczekiwany wynik można uzyskać za pomocą

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

Jednym ze sposobów, który jest zarówno ogólne i daje dość czytelny kod jest użycie filterfunkcji i jego warianty w pakiecie 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)

Powyższa funkcja usuwa wszystkie wiersze z ramki danych, która ma „NA” w dowolnej kolumnie i zwraca wynikowe dane. Jeśli chcesz sprawdzić wiele wartości, takich jak NAi ?zmienić dart=c('NA')parametr funkcji nadart=c('NA', '?')


3

Domyślam się, że można to bardziej elegancko rozwiązać w ten sposób:

  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
to zachowa wiersze z NA. Myślę, że OP chce:df %>% filter_all(all_vars(!is.na(.)))
asifzuba
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.