it-swarm.com.ru

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

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

a) содержит 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
756
Benoit B.

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

956
Joris Meys

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

231
Roman Luštrik

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

84
donshikin

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
82
lukeA

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

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

41
getting-there

Если вы хотите контролировать, сколько 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
36
Pierre Lafortune

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

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

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

В Vanilla 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))
)
29
C8H10N4O2

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

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

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

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

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

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

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

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

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

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

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

14
LegitMe

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

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

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

13
Ramya Ural

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

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

Принимая 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
Prradep
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', '?')

4
sapy

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

  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
3
Joni Hoppen

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

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

0
Isabelle