Удалить строки со всеми или некоторыми NA (отсутствующими значениями) в data.frame


852

Я хотел бы удалить строки в этом фрейме данных, которые:

а) содержать NAs во всех столбцах. Ниже мой пример фрейма данных.

             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

В основном, я хотел бы получить фрейм данных, такой как следующий.

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

б) содержать NAs только в некоторых столбцах , поэтому я также могу получить этот результат:

             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

Ответы:


1063

Также проверьте 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лучше просто удалить все NA. complete.casesпозволяет частичный выбор, включая только определенные столбцы данных:

> 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

Ваше решение не может работать. Если вы настаиваете на использовании is.na, то вы должны сделать что-то вроде:

> 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

но использование complete.casesнамного понятнее и быстрее.


8
В чем заключается значение запятой final[complete.cases(final),]?
hertzsprung

6
@hertzsprung Вам нужно выбирать строки, а не столбцы. Как еще ты это сделаешь?
Йорис Мейс

4
Есть ли простое отрицание complete.cases? Если бы я хотел сохранить ряды с NA, а не отбрасывать? final[ ! complete.cases(final),]не сотрудничает ...
tumultous_rooster

2
finalпеременная датафрейма?
Морзе

1
@Prateek действительно, это так.
Йорис Мейс

256

Попробуй na.omit(your.data.frame). Что касается второго вопроса, попробуйте опубликовать его как другой вопрос (для ясности).


na.omit удаляет строки, но сохраняет номера строк. Как бы вы исправили это, чтобы он был правильно пронумерован?
Медведь

3
@ Не бойся, если тебе не нравятся номера строк, просто делай rownames(x) <- NULL.
Роман Луштрик

обратите внимание, что na.omit()отбрасывает строки, которые содержатся NAв любом столбце
Виктор Максвелл

116

tidyrимеет новую функцию 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
Там нет реальной связи между трубами и drop_na. Так , например, df %>% drop_na(), df %>% na.omit()и drop_na(df)все в основном эквивалентны.
Иста

4
@ Правда, я не согласен. na.omitдобавляет дополнительную информацию, такую ​​как индексы пропущенных регистров, и, что более важно, не позволяет выбирать столбцы - это то, где drop_naсветит.
lukeA

3
Конечно, я хочу сказать, что это не имеет ничего общего с трубами. Вы можете использовать na.omitс или без труб, так же, как вы можете использовать drop_naс или без труб.
Иста

1
Правда, вообще ничего общего с трубами. drop_na () - это просто функция, как и любая другая, и, как таковая, ее можно вызывать напрямую или с помощью канала. К сожалению, drop_na (), в отличие от других упомянутых методов, не может использоваться для типов объектов zoo или xts. Это может быть проблемой для некоторых.
Дейв

Правильно, поэтому я отредактировал ответ так, чтобы он не упоминал трубы.
Артур Ип

91

Я предпочитаю следующий способ проверить, содержат ли строки какие-либо NA:

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

Это возвращает логический вектор со значениями, обозначающими, есть ли какой-либо NA в строке. Вы можете использовать его, чтобы увидеть, сколько строк вам нужно отбросить:

sum(row.has.na)

и в конце концов уронить их

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

Для фильтрации строк с определенной частью NA становится немного сложнее (например, вы можете указать 'final [, 5: 6]' для 'apply'). В общем, решение Joris Meys кажется более элегантным.


2
Это очень медленно. Гораздо медленнее, чем, например, вышеупомянутое решение complete.cases (). По крайней мере, в моем случае, на хтс данных.
Дэйв

3
rowSum(!is.na(final))кажется лучше подходит чемapply()
sindri_baldur

45

Другой вариант, если вы хотите лучше контролировать, как строки считаются недействительными, это

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

Используя вышеизложенное, это:

             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

становится:

             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

... где удаляется только строка 5, поскольку это единственная строка, содержащая NA для обоих rnorAND cfam. Затем логическая логика может быть изменена в соответствии с конкретными требованиями.


5
но как вы можете использовать это, если хотите проверить много столбцов, не вводя каждый из них, можете ли вы использовать диапазон final [, 4: 100]?
Герман Тутрот

40

Если вы хотите контролировать, сколько NA допустимо для каждой строки, попробуйте эту функцию. Для многих наборов данных опроса слишком много пустых ответов на вопросы могут испортить результаты. Поэтому они удаляются после определенного порога. Эта функция позволит вам выбрать, сколько NA может иметь строка, прежде чем она будет удалена:

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

По умолчанию он удалит все NA:

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

Или укажите максимально допустимое количество 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

Если производительность является приоритетом, используйте data.tableи na.omit()с необязательным параметром cols=.

na.omit.data.table является самым быстрым в моем тесте (см. ниже), будь то для всех столбцов или для выбранных столбцов (вопрос OP часть 2).

Если вы не хотите использовать data.table, используйте complete.cases().

На ванили data.frame, complete.casesбыстрее чем na.omit()или dplyr::drop_na(). Обратите внимание, что na.omit.data.frameне поддерживает cols=.

Результат теста

Ниже приведено сравнение базовых (синих), dplyr(розовых) и data.table(желтых) методов для отбрасывания всех или выборки пропущенных наблюдений с условным набором данных из 1 миллиона наблюдений 20 числовых переменных с независимой 5% вероятностью пропуска и подмножество 4 переменных для части 2.

Ваши результаты могут отличаться в зависимости от длины, ширины и разреженности вашего конкретного набора данных.

Отметьте масштаб журнала на оси Y.

введите описание изображения здесь

Контрольный скрипт

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

Используя пакет dplyr, мы можем отфильтровать NA следующим образом:

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

1
Это работает примерно в 10.000 раз медленнее, чемdrop_na()
Zimano

17

Это вернет строки, которые имеют по крайней мере ОДНО значение, отличное от NA

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

Это вернет строки, которые имеют по крайней мере ДВА значения, отличного от NA.

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

16

На ваш первый вопрос у меня есть код, с которым мне удобно избавиться от всех НС. Спасибо за @Gregor, чтобы сделать это проще.

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

По второму вопросу код - это просто альтернатива предыдущему решению.

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

Обратите внимание, что -5 - это количество столбцов в ваших данных. Это исключит строки со всеми NA, так как rowSums добавляет до 5, и они становятся нулями после вычитания. На этот раз, как логично, необходимо.


final [as.logical ((rowSums (is.na (final)) - ncol (final))),] для универсального ответа
Ferroao

14

Мы также можем использовать функцию подмножества для этого.

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

Это даст только те строки, которые не имеют NA как в mmul, так и в rnor


9

Я синтезатор :). Здесь я объединил ответы в одну функцию:

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

Предполагая, что в datкачестве вашего кадра данных, ожидаемый результат может быть достигнут с помощью

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

Один из подходов, это как общие , так и дает достаточно читаемый код, чтобы использовать filterфункцию и ее варианты в пакете 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)

Выше функция удаляет все строки из фрейма данных, который имеет «NA» в любом столбце и возвращает результирующие данные. Если вы хотите проверить наличие нескольких значений, таких как NAи ?изменить dart=c('NA')в параметре функции наdart=c('NA', '?')


3

Я думаю, что это может быть более элегантно решено следующим образом:

  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

7
это сохранит строки с NA. Я думаю, что ОП хочет:df %>% filter_all(all_vars(!is.na(.)))
asifzuba
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.