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

662

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

a) содержат NA по всем столбцам. Ниже приведен примерный кадр данных.

             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

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

             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
Теги:
dataframe
filter
missing-data
r-faq

15 ответов

841
Лучший ответ

Также проверьте 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),] ?
  • 5
    @hertzsprung Вам нужно выбирать строки, а не столбцы. Как еще ты это сделаешь?
Показать ещё 8 комментариев
215

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

  • 0
    na.omit удаляет строки, но сохраняет номера строк. Как бы вы исправили это, чтобы он был правильно пронумерован?
  • 1
    @ Имейте в виду, что если вас не интересуют номера строк, просто rownames(x) <- NULL .
75

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

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

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

sum(row.has.na)

и в конечном итоге отбросить их

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

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

  • 1
    Это очень медленно. Намного медленнее, чем, например, вышеупомянутое решение complete.cases (). По крайней мере, в моем случае, на хтс данных.
  • 1
    rowSum(!is.na(final)) кажется лучше подходит, чем apply()
55

Если вам нравятся трубы (%>%), tidyr new 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
  • 2
    Нет реальной связи между трубами и drop_na . Например, df %>% drop_na() , df %>% na.omit() и drop_na(df) все в основном эквивалентны.
  • 4
    @ Правда, я не согласен. na.omit добавляет дополнительную информацию, такую как индексы пропущенных случаев, и, что более важно, не позволяет вам выбирать столбцы - это то, где drop_na светит.
Показать ещё 3 комментария
37

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

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 как для rnor AND cfam. Логическая логика может быть затем изменена в соответствии с конкретными требованиями.

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

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

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

По умолчанию он устранит все НС:

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
14

Это вернет строки, имеющие хотя бы одно значение, отличное от NA.

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

Это вернет строки, которые имеют по меньшей мере два значения, отличных от NA.

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

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

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

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

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

Это даст только те строки, у которых нет NA в обоих mmul и rnor

11

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

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

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

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

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

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

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

Шкала журнала регистрации по оси y.

Изображение 4271

Сценарий производительности

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

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

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

Во втором вопросе код является просто чередованием предыдущего решения.

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

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

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

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

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

Предполагая 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
1
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 и ? change dart=c('NA') в параметре функции dart=c('NA', '?')

0

Я предполагаю, что это может быть более элегантно решено таким образом

  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
  • 2
    это сохранит ряды с NA . Я думаю, что ОП хочет: df %>% filter_all(all_vars(!is.na(.)))

Ещё вопросы

Сообщество Overcoder
Наверх
Меню