фильтр для полных случаев в data.frame с помощью dplyr (удаление по регистру)

99

Можно ли фильтровать data.frame для полных случаев с помощью dplyr? complete.casesсо списком всех переменных работает, конечно. Но это а) многословно, когда есть много переменных, и б) невозможно, когда имена переменных неизвестны (например, в функции, которая обрабатывает любой data.frame).

library(dplyr)
df = data.frame(
    x1 = c(1,2,3,NA),
    x2 = c(1,2,NA,5)
)

df %.%
  filter(complete.cases(x1,x2))
user2503795
источник
4
complete.casesне только принимает векторы. Также требуются целые фреймы данных.
joran
Но это не работает как часть dplyrфункции фильтрации. Думаю, я не совсем понял и обновил свой вопрос.
user2503795
1
Было бы полезно, если бы вы могли точно продемонстрировать, как это не работает с dplyr, но когда я пробую его с фильтром, он работает нормально.
joran

Ответы:

187

Попробуй это:

df %>% na.omit

или это:

df %>% filter(complete.cases(.))

или это:

library(tidyr)
df %>% drop_na

Если вы хотите выполнить фильтрацию на основе отсутствия одной переменной, используйте условное выражение:

df %>% filter(!is.na(x1))

или

df %>% drop_na(x1)

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

str(df %>% na.omit)
## 'data.frame':   2 obs. of  2 variables:
##  $ x1: num  1 2
##  $ x2: num  1 2
##  - attr(*, "na.action")= 'omit' Named int  3 4
##    ..- attr(*, "names")= chr  "3" "4"

ДОБАВЛЕНО Обновлены, чтобы отразить последнюю версию dplyr и комментарии.

ДОБАВЛЕНО Обновлены, чтобы отразить последнюю версию tidyr и комментарии.

Г. Гротендик
источник
Только что вернулся, чтобы ответить, и увидел ваш полезный ответ!
infominer
1
Спасибо! Я добавил несколько результатов тестов. na.omit()работает довольно плохо, но тот быстрый.
user2503795
1
Это работает и сейчас: df %>% filter(complete.cases(.)). Не уверен, что недавние изменения в dplyr сделали это возможным.
user2503795
Как @ Jan-katins указывает, функция Tidyverse называется drop_na, так что теперь вы можете сделать: df %>% drop_na().
cbrnr
26

Это работает для меня:

df %>%
  filter(complete.cases(df))    

Или немного более общий:

library(dplyr) # 0.4
df %>% filter(complete.cases(.))

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

Еще один тест с большим количеством столбцов:

set.seed(123)
x <- sample(1e5,1e5*26, replace = TRUE)
x[sample(seq_along(x), 1e3)] <- NA
df <- as.data.frame(matrix(x, ncol = 26))
library(microbenchmark)
microbenchmark(
  na.omit = {df %>% na.omit},
  filter.anonymous = {df %>% (function(x) filter(x, complete.cases(x)))},
  rowSums = {df %>% filter(rowSums(is.na(.)) == 0L)},
  filter = {df %>% filter(complete.cases(.))},
  times = 20L,
  unit = "relative")

#Unit: relative
#             expr       min        lq    median         uq       max neval
 #         na.omit 12.252048 11.248707 11.327005 11.0623422 12.823233    20
 #filter.anonymous  1.149305  1.022891  1.013779  0.9948659  4.668691    20
 #         rowSums  2.281002  2.377807  2.420615  2.3467519  5.223077    20
 #          filter  1.000000  1.000000  1.000000  1.0000000  1.000000    20
Миха Трошт
источник
1
Я обновил ваш ответ "." в complete.cases и добавлен тест - надеюсь, вы не против :-)
talat
:) Я не. Спасибо.
Miha Trošt
1
Я обнаружил, что работает df %>% slice(which(complete.cases(.)))примерно на 20% быстрее, чем подход с фильтрами в тесте выше.
talat
Стоит отметить, что если вы используете этот фильтр в канале dplyr с другими командами dplyr (такими как group_by ()), вам нужно будет добавить, %>% data.frame() %>%прежде чем пытаться фильтровать по complete.cases (.), Потому что он не будет работать на столы или сгруппированные плитки или что-то в этом роде. По крайней мере, у меня был такой опыт.
C. Denney
16

Вот некоторые результаты тестов для ответа Гротендика. na.omit () занимает в 20 раз больше времени, чем два других решения. Я думаю, было бы неплохо, если бы в dplyr была функция для этого, возможно, как часть фильтра.

library('rbenchmark')
library('dplyr')

n = 5e6
n.na = 100000
df = data.frame(
    x1 = sample(1:10, n, replace=TRUE),
    x2 = sample(1:10, n, replace=TRUE)
)
df$x1[sample(1:n, n.na)] = NA
df$x2[sample(1:n, n.na)] = NA


benchmark(
    df %>% filter(complete.cases(x1,x2)),
    df %>% na.omit(),
    df %>% (function(x) filter(x, complete.cases(x)))()
    , replications=50)

#                                                  test replications elapsed relative
# 3 df %.% (function(x) filter(x, complete.cases(x)))()           50   5.422    1.000
# 1               df %.% filter(complete.cases(x1, x2))           50   6.262    1.155
# 2                                    df %.% na.omit()           50 109.618   20.217
user2503795
источник
12

Это короткая функция, которая позволяет вам указывать столбцы (в основном все, что dplyr::selectможно понять), которые не должны иметь никаких значений NA (смоделированы после pandas df.dropna () ):

drop_na <- function(data, ...){
    if (missing(...)){
        f = complete.cases(data)
    } else {
        f <- complete.cases(select_(data, .dots = lazyeval::lazy_dots(...)))
    }
    filter(data, f)
}

[ drop_na теперь является частью tidyr : указанное выше можно заменить на library("tidyr")]

Примеры:

library("dplyr")
df <- data.frame(a=c(1,2,3,4,NA), b=c(NA,1,2,3,4), ac=c(1,2,NA,3,4))
df %>% drop_na(a,b)
df %>% drop_na(starts_with("a"))
df %>% drop_na() # drops all rows with NAs
Ян Катиньш
источник
Было бы еще полезнее иметь возможность добавить отсечку, например 0,5, и обработать ее по столбцам? Случай: исключить переменные с отсутствующими данными на 50% и более. Пример: data [, -which (colMeans (is.na (data))> 0.5)] Было бы неплохо сделать это с помощью tidyr.
Monduiz
@Monduiz Это будет означать, что добавление дополнительных данных (где переменная тогда имеет много NA) может привести к сбою следующего шага в конвейере, потому что необходимая переменная теперь отсутствует ...
Ян Катинс,
Верно, в этом есть смысл.
Monduiz
6

попробуй это

df[complete.cases(df),] #output to console

ИЛИ даже это

df.complete <- df[complete.cases(df),] #assign to a new data.frame

Приведенные выше команды позаботятся о проверке полноты для всех столбцов (переменных) в вашем data.frame.

информатор
источник
Спасибо. Думаю, я не совсем понял (вопрос обновлен). Я знаю о complete.cases (df), но я хотел бы сделать это dplyrкак часть функции фильтра. Это позволит аккуратно интегрировать цепочки dplyr и т. Д.
user2503795
Проверьте ответ @ G.Grothendieck
infominer
В dplyr:::do.data.frameзаявлении env$. <- .dataдобавляет точку в окружение. Нет такого заявления в magrittr :: "%>%" `
Г. Гротендик
Извините, наверное, вы ввели комментарий не в том месте.
Г. Гротендик
3

Просто для полноты картины dplyr::filterможно вообще избежать, но все же можно составлять цепочки, просто используя magrittr:extract(псевдоним [):

library(magrittr)
df = data.frame(
  x1 = c(1,2,3,NA),
  x2 = c(1,2,NA,5))

df %>%
  extract(complete.cases(.), )

Дополнительный бонус скорости, это самый быстрый способ среди filterи na.omitвариантов (тестировалось с помощью @Miha ТРОСТ microbenchmarks).

mbask
источник
Когда я проводил тест с данными Miha Trošt, я обнаружил, что использование extract()происходит почти в десять раз медленнее, чем filter(). Однако, когда я создаю меньший фрейм данных с помощью df <- df[1:100, 1:10], изображение меняется и extract()становится самым быстрым.
Stibu
Ты прав. Похоже, что magrittr::extractэто самый быстрый способ только n <= 5e3в тесте Miha Trošt.
mbask