filter untuk kasus lengkap di data.frame menggunakan dplyr (penghapusan kasus)

97

Apakah mungkin untuk memfilter data.frame untuk kasus lengkap menggunakan dplyr? complete.casesdengan daftar semua variabel bekerja, tentu saja. Tapi itu a) verbose ketika ada banyak variabel dan b) tidak mungkin ketika nama variabel tidak diketahui (misalnya dalam fungsi yang memproses 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))
pengguna2503795
sumber
4
complete.casestidak hanya menerima vektor. Dibutuhkan kerangka data utuh, juga.
joran
Tapi itu tidak berfungsi sebagai bagian dari dplyrfungsi filter. Saya kira saya tidak cukup jelas dan memperbarui pertanyaan saya.
pengguna2503795
1
Ini akan membantu jika Anda dapat mendemonstrasikan secara tepat bagaimana itu tidak bekerja dengan dplyr, tetapi ketika saya mencobanya dengan filter, itu berfungsi dengan baik.
joran

Jawaban:

185

Coba ini:

df %>% na.omit

atau ini:

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

atau ini:

library(tidyr)
df %>% drop_na

Jika Anda ingin memfilter berdasarkan salah satu variabel yang tidak ada, gunakan kondisional:

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

atau

df %>% drop_na(x1)

Jawaban lain menunjukkan bahwa solusi di atas na.omitjauh lebih lambat tetapi harus diseimbangkan dengan fakta bahwa ia mengembalikan indeks baris dari baris yang dihilangkan dalam na.actionatribut sedangkan solusi lain di atas tidak.

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"

DITAMBAH Telah diperbarui untuk mencerminkan versi terbaru dplyr dan komentar.

ADDED Telah diperbarui untuk mencerminkan versi terbaru dari tidyr dan komentar.

G. Grothendieck
sumber
Baru saja kembali untuk menjawab dan melihat jawaban Anda yang berguna!
infominer
1
Terima kasih! Saya menambahkan beberapa hasil benchmark. na.omit()berkinerja sangat buruk tetapi yang cepat.
pengguna2503795
1
Ini bekerja sekarang juga: df %>% filter(complete.cases(.)). Tidak yakin apakah perubahan terbaru di dplyr memungkinkan hal ini.
user2503795
Sebagai @ Januari-katins poin, fungsi Tidyverse disebut drop_na, sehingga Anda sekarang dapat melakukan: df %>% drop_na().
cbrnr
26

Ini bekerja untuk saya:

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

Atau sedikit lebih umum:

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

Ini akan memiliki keuntungan bahwa data dapat dimodifikasi dalam rantai sebelum meneruskannya ke filter.

Tolok ukur lain dengan lebih banyak kolom:

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
Miha Trošt
sumber
1
Saya memperbarui jawaban Anda dengan "." dalam kasus complete.cases dan menambahkan patokan - harap Anda tidak keberatan :-)
talat
:) Bukan saya. Terima kasih.
Miha Trošt
1
Saya menemukan df %>% slice(which(complete.cases(.)))kinerja ~ 20% lebih cepat daripada pendekatan filter pada tolok ukur di atas.
talat
Perlu dicatat bahwa jika Anda menggunakan filter ini dalam pipa dplyr dengan perintah dplyr lain (seperti group_by ()), Anda perlu menambahkan %>% data.frame() %>%sebelum Anda mencoba dan memfilter complete.cases (.) Karena tidak akan berfungsi tibbles atau tibbles yang dikelompokkan atau sesuatu. Atau setidaknya, itulah pengalaman yang saya alami.
C. Denney
16

Berikut beberapa hasil benchmark untuk balasan Grothendieck. na.omit () membutuhkan waktu 20x lebih lama dari dua solusi lainnya. Saya pikir alangkah baiknya jika dplyr memiliki fungsi untuk ini mungkin sebagai bagian dari filter.

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
pengguna2503795
sumber
12

Ini adalah fungsi singkat yang memungkinkan Anda menentukan kolom (pada dasarnya semua yang dplyr::selectdapat dipahami) yang seharusnya tidak memiliki nilai NA (dimodelkan setelah 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 sekarang menjadi bagian dari tidyr : yang di atas dapat diganti dengan library("tidyr")]

Contoh:

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
Jan Katins
sumber
Tidakkah akan lebih berguna jika dapat menambahkan batas seperti 0,5 dan memprosesnya menurut kolom? Kasus: menghilangkan variabel dengan 50% atau lebih data yang hilang. Contoh: data [, -which (colMeans (is.na (data))> 0.5)] Alangkah baiknya bisa melakukan ini dengan tidyr.
Monduiz
@Monduiz Ini berarti bahwa penambahan lebih banyak data (di mana variabel kemudian memiliki banyak NA) dapat gagal pada langkah berikutnya dalam pipeline karena variabel yang dibutuhkan sekarang hilang ...
Jan Katins
Benar, itu masuk akal.
Monduiz
6

coba ini

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

ATAU bahkan ini

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

Perintah di atas menangani pemeriksaan kelengkapan untuk semua kolom (variabel) di data.frame Anda.

infominer
sumber
Terima kasih. Saya rasa saya tidak cukup jelas (pertanyaan diperbarui). Saya tahu tentang complete.cases (df) tetapi saya ingin melakukannya dplyrsebagai bagian dari fungsi filter. Itu akan memungkinkan integrasi rapi dalam rantai dplyr dll.
user2503795
Periksa jawabannya oleh @ G.Grothendieck
infominer
Dalam dplyr:::do.data.framepernyataan itu env$. <- .datamenambahkan titik ke lingkungan. Tidak ada pernyataan seperti itu di magrittr :: "%>%" `
G. Grothendieck
Maaf pasti salah memasukkan komentar di tempat yang salah.
G. Grothendieck
3

Hanya demi kelengkapan, dplyr::filterbisa dihindari sama sekali tapi tetap bisa merangkai rantai hanya dengan menggunakan magrittr:extract(alias [):

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

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

Bonus tambahannya adalah kecepatan, ini adalah metode tercepat di antara filterdan na.omitvarian (diuji menggunakan microbenchmark @Miha Trošt).

mbask.dll
sumber
Ketika saya melakukan benchmark dengan data dari Miha Trošt, saya menemukan bahwa penggunaan extract()hampir sepuluh kali lebih lambat dari filter(). Namun, saat saya membuat bingkai data yang lebih kecil df <- df[1:100, 1:10], gambar berubah dan extract()menjadi yang tercepat.
Stibu
Anda benar. Sepertinya magrittr::extractadalah cara tercepat hanya ketika n <= 5e3di patokan Miha Trošt.
mbask