dplyr中subset和filter的区别是什么?

55

在我看来,使用dplyr中的subset和filter函数可以得到相同的结果。但我的问题是:在速度、处理数据大小等方面,它们是否存在潜在差异?在某些情况下,使用其中之一更好吗?

示例:

library(dplyr)

df1<-subset(airquality, Temp>80 & Month > 5)
df2<-filter(airquality, Temp>80 & Month > 5)

summary(df1$Ozone)
# Min. 1st Qu.  Median    Mean 3rd Qu.    Max.    NA's 
# 9.00   39.00   64.00   64.51   84.00  168.00      14 

summary(df2$Ozone)
# Min. 1st Qu.  Median    Mean 3rd Qu.    Max.    NA's 
# 9.00   39.00   64.00   64.51   84.00  168.00      14 

3
本文比较了 subsetfilterwith[ 四种方法,并介绍了在使用 dplyr 子集化数据框时如何使用 |& 进行逻辑判断。原文链接为:如何使用 | 和 & 在 dplyr 中子集化数据框 - Silence Dogood
1
主要区别在于subset?subset中带有警告:“这是一个旨在交互使用的便利函数。对于编程,最好使用标准的子集函数,如[,特别是参数子集的非标准评估可能会产生意想不到的后果。” filter旨在与dplyr和tidyverse的其余部分稳健地交互使用,无论是交互式还是编程式,并且具有单独的标准评估版本filter_(必要时)。此外,它将逗号视为& - alistaire
@alistaire,更新一下,dplyr函数中的filter_()_版本通常已被弃用,取而代之的是整洁评估语义。有关当前最佳实践的详细信息,请参阅使用dplyr进行编程 - Bryan Shalloway
2
我知道,那个评论是四年前的。 - alistaire
6个回答

71

实际上,它们产生了相同的结果,并且在概念上非常相似。

subset 的优点是它是基于 R 的一部分,不需要任何额外的软件包。在小样本量的情况下,它似乎比 filter 更快(在您的示例中快6倍,但这是以微秒为单位测量的)。

随着数据集的增长,filter 在效率上占据优势。在 15,000 条记录时,filtersubset 快约 300 微秒。而在 153,000 条记录时,filter 快三倍(以毫秒为单位测量)。

因此,在人类时间方面,我认为两者之间没有太大的区别。

另一个优点(这有点小众的优势)是 filter 可以在 SQL 数据库上操作而不需要将数据读入内存。而 subset 简单地无法做到这一点。

个人而言,我倾向于使用 filter,但只是因为我已经在使用 dplyr 框架。如果您不处理超出内存的数据,则不会有太大的区别。

library(dplyr)
library(microbenchmark)

# Original example
microbenchmark(
  df1<-subset(airquality, Temp>80 & Month > 5),
  df2<-filter(airquality, Temp>80 & Month > 5)
)

Unit: microseconds
   expr     min       lq     mean   median      uq      max neval cld
 subset  95.598 107.7670 118.5236 119.9370 125.949  167.443   100  a 
 filter 551.886 564.7885 599.4972 571.5335 594.993 2074.997   100   b


# 15,300 rows
air <- lapply(1:100, function(x) airquality) %>% bind_rows

microbenchmark(
  df1<-subset(air, Temp>80 & Month > 5),
  df2<-filter(air, Temp>80 & Month > 5)
)

Unit: microseconds
   expr      min        lq     mean   median       uq      max neval cld
 subset 1187.054 1207.5800 1293.718 1216.671 1257.725 2574.392   100   b
 filter  968.586  985.4475 1056.686 1023.862 1036.765 2489.644   100  a 

# 153,000 rows
air <- lapply(1:1000, function(x) airquality) %>% bind_rows

microbenchmark(
  df1<-subset(air, Temp>80 & Month > 5),
  df2<-filter(air, Temp>80 & Month > 5)
)

Unit: milliseconds
   expr       min        lq     mean    median        uq      max neval cld
 subset 11.841792 13.292618 16.21771 13.521935 13.867083 68.59659   100   b
 filter  5.046148  5.169164 10.27829  5.387484  6.738167 65.38937   100  a 

1
先生,对我来说结果恰好相反!在我的机器上,对于这两种情况,子集的表现比过滤器要好。 - Namrata Tolani
可能有半打的原因造成这种情况。执行上的差异是否足够重要而值得关注呢? - Benjamin
子集 1.164632 1.220479 1.717666 1.266967 1.421527,过滤器 5.314198 5.440985 5.669854 5.595846 5.793876。 - Namrata Tolani

39

还有一个尚未提到的附加区别是,filter 会丢弃行名称,而 subset 不会:

filter(mtcars, gear == 5)

  mpg    cyl   disp      hp  drat wt    qsec  vs am   gear carb
1 26.0   4     120.3     91  4.43 2.140 16.7  0  1    5    2
2 30.4   4     95.1      113 3.77 1.513 16.9  1  1    5    2
3 15.8   4     351.0     264 4.22 3.170 14.5  0  1    5    4
4 19.7   4     145.0     175 3.62 2.770 15.5  0  1    5    6
5 15.0   4     301.0     335 3.54 3.570 14.6  0  1    5    8

subset(mtcars, gear == 5)
               mpg    cyl   disp      hp  drat wt    qsec vs  am   gear carb
Porsche 914-2  26.0   4     120.3     91  4.43 2.140 16.7  0  1    5    2
Lotus Europa   30.4   4     95.1      113 3.77 1.513 16.9  1  1    5    2
Ford Pantera L 15.8   4     351.0     264 4.22 3.170 14.5  0  1    5    4
Ferrari Dino   19.7   4     145.0     175 3.62 2.770 15.5  0  1    5    6
Maserati Bora  15.0   4     301.0     335 3.54 3.570 14.6  0  1    5    8

3
在某些应用场景中,行名称非常重要,并且将它们从主数据中分离出来具有优势,比如在计算聚类的距离矩阵时,这一点就非常关键。 - Gang Su

25

在主要的使用情况下,它们的行为是相同的:

library(dplyr)
identical(
  filter(starwars, species == "Wookiee"),
  subset(starwars, species == "Wookiee"))
# [1] TRUE

但是它们有很多差异,其中包括:

  • subset 可以用于矩阵
  • filter 可以用于数据库
  • filter 删除行名称
  • subset 除了类、名称和行名称外还会删除其他属性。
  • subset 有一个 select 参数
  • subset 会对其条件参数进行循环利用
  • filter 支持将条件作为单独的参数
  • filter 保留列的类别
  • filter 支持 .data 代词
  • filter 支持某些 rlang 特性
  • filter 支持分组
  • filter 支持 n()row_number()
  • filter 更为严格
  • filter 在计数时略快一些
  • subset 在其他包中具有方法

subset 可以用于矩阵

subset(state.x77, state.x77[,"Population"] < 400)
#         Population Income Illiteracy Life Exp Murder HS Grad Frost   Area
# Alaska         365   6315        1.5    69.31   11.3    66.7   152 566432
# Wyoming        376   4566        0.6    70.29    6.9    62.9   173  97203

虽然在 subset 参数中不能直接使用列作为变量

subset(state.x77, Population < 400)

在 subset.matrix(state.x77, Population < 400) 中出现错误:未找到对象'Population'

filter 也无法使用。

filter(state.x77, state.x77[,"Population"] < 400)

UseMethod("filter_") 函数出错:对于类为 "c('matrix', 'double', 'numeric')" 的对象,没有适用的 'filter_' 方法。

filter(state.x77, Population < 400)

Error in UseMethod("filter_") : no applicable method for 'filter_' applied to an object of class "c('matrix', 'double', 'numeric')"

filter可以用于数据库

library(DBI)
con <- dbConnect(RSQLite::SQLite(), ":memory:")
dbWriteTable(con, "mtcars", mtcars)
tbl(con,"mtcars") %>% 
  filter(hp < 65)

# # Source:   lazy query [?? x 11]
# # Database: sqlite 3.19.3 [:memory:]
#       mpg   cyl  disp    hp  drat    wt  qsec    vs    am  gear  carb
#     <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
#   1  24.4     4 146.7    62  3.69 3.190 20.00     1     0     4     2
#   2  30.4     4  75.7    52  4.93 1.615 18.52     1     1     4     2

subset 无法

tbl(con,"mtcars") %>% 
  subset(hp < 65)

在 subset.default(., hp < 65) 中出现错误:未找到对象 'hp'

filter 函数会删除行名称

filter(mtcars, hp < 65)
#    mpg cyl  disp hp drat    wt  qsec vs am gear carb
# 1 24.4   4 146.7 62 3.69 3.190 20.00  1  0    4    2
# 2 30.4   4  75.7 52 4.93 1.615 18.52  1  1    4    2
< p >subset 不起作用。

subset(mtcars, hp < 65)
#              mpg cyl  disp hp drat    wt  qsec vs am gear carb
# Merc 240D   24.4   4 146.7 62 3.69 3.190 20.00  1  0    4    2
# Honda Civic 30.4   4  75.7 52 4.93 1.615 18.52  1  1    4    2

subset方法可以丢弃除class、names和row names之外的属性。

cars_head <- head(cars)
attr(cars_head, "info") <- "head of cars dataset"
attributes(subset(cars_head, speed > 0))
#> $names
#> [1] "speed" "dist" 
#> 
#> $row.names
#> [1] 1 2 3 4 5 6
#> 
#> $class
#> [1] "data.frame"

attributes(filter(cars_head, speed > 0))
#> $names
#> [1] "speed" "dist" 
#> 
#> $row.names
#> [1] 1 2 3 4 5 6
#> 
#> $class
#> [1] "data.frame"
#> 
#> $info
#> [1] "head of cars dataset"

subset有一个select参数

dplyr遵循tidyverse的原则,每个函数只做一件事情,因此select是一个单独的函数。

identical(
subset(starwars, species == "Wookiee", select = c("name", "height")),
filter(starwars, species == "Wookiee") %>% select(name, height)
)
# [1] TRUE

它还有一个drop参数,这个参数在使用select参数的上下文中才有意义。

subset会循环使用条件参数。

half_iris <- subset(iris,c(TRUE,FALSE))
dim(iris) # [1] 150   5
dim(half_iris) # [1] 75  5

filter不能做到

half_iris <- filter(iris,c(TRUE,FALSE))

筛选器实现中的错误(.data,quo):结果的长度必须为150而不是2

filter支持将条件作为单独的参数

将条件提供给...,因此我们可以将多个条件作为不同的参数,这与使用&相同,但有时由于逻辑运算符优先级和自动缩进,可能更易读。

identical(
  subset(starwars, 
         (species == "Wookiee" | eye_color == "blue") &
           mass > 120),
  filter(starwars, 
         species == "Wookiee" | eye_color == "blue", 
         mass > 120)
)

filter 保留列的类(class)

df <- data.frame(a=1:2, b = 3:4, c= 5:6)
class(df$a) <- "foo"
class(df$b) <- "Date"

# subset preserves the Date, but strips the "foo" class
str(subset(df,TRUE))
#> 'data.frame':    2 obs. of  3 variables:
#>  $ a: int  1 2
#>  $ b: Date, format: "1970-01-04" "1970-01-05"
#>  $ c: int  5 6

# filter keeps both
str(dplyr::filter(df,TRUE))
#> 'data.frame':    2 obs. of  3 variables:
#>  $ a: 'foo' int  1 2
#>  $ b: Date, format: "1970-01-04" "1970-01-05"
#>  $ c: int  5 6

filter 函数支持使用 .data 代词

mtcars %>% filter(.data[["hp"]] < 65)

#    mpg cyl  disp hp drat    wt  qsec vs am gear carb
# 1 24.4   4 146.7 62 3.69 3.190 20.00  1  0    4    2
# 2 30.4   4  75.7 52 4.93 1.615 18.52  1  1    4    2

filter支持一些rlang功能

x <- "hp"
library(rlang)
mtcars %>% filter(!!sym(x) < 65)
# m   pg cyl  disp hp drat    wt  qsec vs am gear carb
# 1 24.4   4 146.7 62 3.69 3.190 20.00  1  0    4    2
# 2 30.4   4  75.7 52 4.93 1.615 18.52  1  1    4    2


filter65 <- function(data,var){
  data %>% filter(!!enquo(var) < 65)
}
mtcars %>% filter65(hp)
#    mpg cyl  disp hp drat    wt  qsec vs am gear carb
# 1 24.4   4 146.7 62 3.69 3.190 20.00  1  0    4    2
# 2 30.4   4  75.7 52 4.93 1.615 18.52  1  1    4    2

filter支持分组

iris %>%
  group_by(Species) %>%
  filter(Petal.Length < quantile(Petal.Length,0.01))

# # A tibble: 3 x 5
# # Groups:   Species [3]
#   Sepal.Length Sepal.Width Petal.Length Petal.Width    Species
#          <dbl>       <dbl>        <dbl>       <dbl>     <fctr>
# 1          4.6         3.6          1.0         0.2     setosa
# 2          5.1         2.5          3.0         1.1 versicolor
# 3          4.9         2.5          4.5         1.7  virginica

iris %>%
  group_by(Species) %>%
  subset(Petal.Length < quantile(Petal.Length,0.01))

# # A tibble: 2 x 5
# # Groups:   Species [1]
#     Sepal.Length Sepal.Width Petal.Length Petal.Width Species
#            <dbl>       <dbl>        <dbl>       <dbl>  <fctr>
#   1          4.3         3.0          1.1         0.1  setosa
#   2          4.6         3.6          1.0         0.2  setosa

filter 支持 n()row_number()

filter(iris, row_number() < n()/30)
# Sepal.Length Sepal.Width Petal.Length Petal.Width Species
# 1          5.1         3.5          1.4         0.2  setosa
# 2          4.9         3.0          1.4         0.2  setosa
# 3          4.7         3.2          1.3         0.2  setosa
# 4          4.6         3.1          1.5         0.2  setosa

filter更加严格

如果输入不合规,则将触发错误。

filter(iris, Species = "setosa")
# Error: `Species` (`Species = "setosa"`) must not be named, do you need `==`?

identical(subset(iris, Species = "setosa"), iris)
# [1] TRUE

df1 <- setNames(data.frame(a = 1:3, b=5:7),c("a","a"))
# df1
# a a
# 1 1 5
# 2 2 6
# 3 3 7

filter(df1, a > 2)
#Error: Column `a` must have a unique name
subset(df1, a > 2)
# a a.1
# 3 3   7

filter在计数时速度更快

借用Benjamin在他的答案中构建的数据集(153 k行),尽管它应该很少成为瓶颈,但速度快了一倍。

air <- lapply(1:1000, function(x) airquality) %>% bind_rows
microbenchmark::microbenchmark(
  subset = subset(air, Temp>80 & Month > 5),
  filter = filter(air, Temp>80 & Month > 5)
)

# Unit: milliseconds
#   expr      min        lq      mean    median        uq      max neval cld
# subset 8.771962 11.551255 19.942501 12.576245 13.933290 108.0552   100   b
# filter 4.144336  4.686189  8.024461  6.424492  7.499894 101.7827   100  a 

subset在其他包中有方法

subset是一个S3通用函数,就像dplyr::filter一样,但是subset作为基本函数更有可能在其他包中开发方法,一个突出的例子是zoo:::subset.zoo


TODO: 过滤器失败时提供更有帮助的错误信息。 - moodymudskipper

1
一个区别是 subset 比 filter 做更多的事情,你也可以选择和删除,而在 dplyr 中有两个不同的函数。
subset(df, select=c("varA", "varD"))

dplyr::select(df,varA, varD)

1
有趣。我试图通过结果数据集来看差异,但我无法解释"["操作符为什么表现不同(即为什么它也返回NA):
# Subset for year=2013
sub<-brfss2013 %>% filter(iyear == "2013")
dim(sub)
#[1] 486088    330
length(which(is.na(sub$iyear))==T)
#[1] 0

sub2<-filter(brfss2013, iyear == "2013")
dim(sub2)
#[1] 486088    330
length(which(is.na(sub2$iyear))==T)
#[1] 0

sub3<-brfss2013[brfss2013$iyear=="2013", ]
dim(sub3)
#[1] 486093    330
length(which(is.na(sub3$iyear))==T)
#[1] 5

sub4<-subset(brfss2013, iyear=="2013")
dim(sub4)
#[1] 486088    330
length(which(is.na(sub4$iyear))==T)
#[1] 0

0

filter 的另一个优点是它与分组数据兼容。而 subset 则忽略分组。

因此,当数据被分组时,subset 仍然会引用整个数据,但 filter 只会引用该组。

# setup
library(tidyverse)

data.frame(a = 1:2) %>% group_by(a) %>% subset(length(a) == 1) 
# returns empty table

data.frame(a = 1:2) %>% group_by(a) %>% filter(length(a) == 1) 
# returns all rows

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接