使用dplyr过滤data.frame中的完整案例(逐案删除)


97

是否可以使用dplyr过滤data.frame以获取完整案例?complete.cases当然,列出所有变量的列表是可行的。但这是a)当有很多变量时冗长; b)当变量名未知时(例如,在处理任何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不只是接受向量。它也需要整个数据帧。
joran 2014年

但这不适用于dplyr的过滤器功能。我想我还不够清楚,所以更新了我的问题。
user2503795 2014年

1
如果您可以确切演示它如何与dplyr结合使用,将会有所帮助,但是当我尝试使用filter时,它就可以正常工作。
joran 2014年

Answers:


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"

ADDED已经更新,以反映最新版本dplyr和评论。

ADDED已经更新,以反映最新版本tidyr和评论。


刚回来回答,看到了有用的答案!
infominer 2014年

1
谢谢!我添加了一些基准测试结果。na.omit()表现很差,但那一个很快。
user2503795 2014年

1
现在也可以使用:df %>% filter(complete.cases(.))。不确定dplyr的最新更改是否可以实现这一点。
user2503795 2015年

正如@ 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和添加的基准测试中-希望您不要介意:-)
塔拉塔

:) 我不。谢谢。
MihaTrošt2015年

1
我发现 df %>% slice(which(complete.cases(.)))执行速度比上述基准中的过滤方法快约20%。
塔拉特2015年

值得注意的是,如果您在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

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)可能会导致下一步无法通过,因为现在缺少所需的变量……
Jan Katins

是的,那很有意义。
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 2014年

dplyr:::do.data.frame语句中,env$. <- .data将点添加到环境中。magrittr ::“%>%”`中没有这样的声明
G. Grothendieck

抱歉,必须在错误的地方输入了评论。
G. Grothendieck,2014年

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(.), )

额外的好处是速度,这是filterna.omit变体中最快的方法(使用@MihaTrošt微基准测试)。


当我用MihaTrošt的数据进行基准测试时,我发现使用extract()速度几乎比慢十倍filter()。但是,当我使用创建一个较小的数据框时df <- df[1:100, 1:10],图片会更改并且extract()是最快的。
Stibu

你是对的。看起来magrittr::extract只有n <= 5e3在MihaTrošt基准测试中,这才是最快的方法。
mbask
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.