Удалить строки со всеми или некоторыми NA (отсутствующими значениями) в data.frame - PullRequest
765 голосов
/ 01 февраля 2011

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

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

             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 s только в некоторых столбцах , поэтому я могу также получить этот результат:

             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

Ответы [ 16 ]

962 голосов
/ 01 февраля 2011

Также проверьте 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 намного понятнее и быстрее.

232 голосов
/ 01 февраля 2011

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

86 голосов
/ 16 августа 2016

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
84 голосов
/ 03 февраля 2011

Я предпочитаю следующий способ проверить, содержат ли строки какие-либо 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 кажется более элегантным.

41 голосов
/ 05 ноября 2013

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

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

36 голосов
/ 26 мая 2015

Если вы хотите контролировать, сколько 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
30 голосов
/ 16 февраля 2018

Если производительность является приоритетом, используйте 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.

enter image description here

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

#-------  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))
)
17 голосов
/ 12 апреля 2017

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

dplyr::filter(df,  !is.na(columnname))
16 голосов
/ 19 сентября 2014

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

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

Это вернет строки, которые имеют не менее ДВУХ значений, отличных от NA.

final[rowSums(is.na(final))<(length(final)-1),]
14 голосов
/ 09 февраля 2016

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

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

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

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

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

...