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

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

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

Ответ 1

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

Ответ 2

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

Ответ 3

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

Ответ 4

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

Ответ 5

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

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

Ответ 6

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

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

Ответ 7

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

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

Ответ 8

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

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

Ответ 9

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

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

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

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

Ответ 10

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

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

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

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

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

Ответ 11

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

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

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

Ответ 12

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

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

Ответ 13

Предполагая 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

Ответ 14

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', '?')

Ответ 15

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

  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

Ответ 16

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

Ответ 17

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