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


97

Чи можна відфільтрувати 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))

4
complete.casesне просто приймає вектори. Також потрібні цілі кадри даних.
Джоран

Але це не працює як частина dplyrфункції фільтра. Думаю, я був недостатньо чітким і оновив своє запитання.
user2503795

1
Це допомогло б, якби ви змогли продемонструвати, як це точно не працює з dplyr, але коли я пробую це з фільтром, це працює чудово.
joran

Відповіді:


185

Спробуйте це:

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 та коментарі.


Просто повернувся відповісти і побачив вашу корисну відповідь!
infomer

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. Денні

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

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 (дані))> 0,5)] Було б непогано мати можливість це зробити за допомогою tidyr.
Мондуїз

@Monduiz Це означатиме, що додавання більшої кількості даних (там, де змінна має багато NA) може провалити наступний крок в конвеєрі, тому що потрібна змінна зараз відсутня ...
Ян Катінс

Правильно, це має сенс.
Мондуїз

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 12.03.14

Перевірте відповідь @ G.Grothendieck
infominer 12.03.14

У 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 Trošt).


Коли я орієнтуюсь на дані Михи Трошта, я виявляю, що використання extract()майже в десять разів повільніше, ніж filter(). Однак, коли я створюю менший кадр даних за допомогою df <- df[1:100, 1:10], зображення змінюється і extract()є найшвидшим.
Стібу

Ви праві. Схоже, magrittr::extractце найшвидший спосіб лише тоді, коли n <= 5e3в тесті Міха Трошта.
mbask
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.