Видаліть рядки з усіма чи деякими NA (відсутніми значеннями) у data.frame


851

Я хотів би видалити рядки з цього кадру даних, які:

а) містять 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

b) містять 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

Відповіді:


1062

Також перевірте 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
@Ista Я не згоден. na.omitдодає додаткову інформацію, як-от індекси пропущених випадків, і, що ще важливіше, - це не дозволяє вам вибирати стовпчики - ось де drop_naсвітить.
lukeA

3
Звичайно, моя думка полягає в тому, що це не має нічого спільного з трубами. Ви можете використовувати na.omitз трубами або без них, так само як і drop_naз трубами або без них.
Іста

1
Правда, з трубами взагалі немає нічого спільного. drop_na () - це лише функція, як і будь-яка інша, і, як таку, можна викликати безпосередньо або за допомогою труби. На жаль, drop_na (), на відміну від інших згаданих методів, не може використовуватися для типів об'єктів zoo або xts. Для деяких це може бути проблемою.
Дейв

Так, я відредагував відповідь, щоб вона не згадувала про труби.
Артур Іп

91

Я вважаю за краще наступний спосіб перевірити, чи містять рядки НС:

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]" у "застосувати"). Взагалі рішення Йоріс Мейс здається більш елегантним.


2
Це надзвичайно повільно. Набагато повільніше, ніж, наприклад, вищезазначене рішення komplet.cases (). Принаймні, у моєму випадку, на даних xts.
Дейв

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
але як ви можете скористатися цим, якщо ви хочете перевірити багато стовпців, не вводячи кожного, чи можете ви використовувати кінцевий діапазон [, 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, оскільки рядокSums додає до 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

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