Разница между подмножеством и фильтром от dplyr

Мне кажется, что подмножество и фильтр (из dplyr) имеют одинаковый результат. Но мой вопрос: есть ли в какой-то момент разность потенциалов, например. скорость, размеры данных, которые он может обрабатывать и т.д.? Есть ли случаи, когда лучше использовать один или другой?

Пример:

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 
# 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 
# 9.00   39.00   64.00   64.51   84.00  168.00      14 

Ответ 1

Они, действительно, производят один и тот же результат, и они очень похожи по понятию.

Преимущество subset заключается в том, что он является частью базы R и не требует каких-либо дополнительных пакетов. При небольших размерах выборки он, кажется, немного быстрее, чем filter (в вашем примере в 6 раз быстрее, но это измеряется в микросекундах).

По мере роста наборов данных filter, по-видимому, выигрывает в эффективности. В 15 000 записей filter превышает subset примерно на 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 

Ответ 2

Еще одно отличие, которое еще не упомянуто, заключается в том, что фильтр отбрасывает имена розеток, в то время как подмножество не имеет значения:

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

Интересно. Я пытался увидеть разницу в результирующем наборе данных, и я не могу объяснить, почему оператор "[" ведет себя по-другому (то есть, почему он также возвращает 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

Ответ 4

Разница также в том, что подмножество делает больше вещей, чем фильтр, который вы также можете выбрать и удалить, когда у вас есть две разные функции в dplyr

subset(df, select=c("varA", "varD"))

dplyr::select(df,varA, varD)

Ответ 5

Дополнительным преимуществом 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

Ответ 6

В основных случаях использования они ведут себя одинаково:

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

Но у них есть довольно много различий, включая (я был настолько исчерпывающим, насколько это возможно, но, возможно, пропустил некоторые):

  • subset может использоваться на матрицах
  • filter может быть использован в базах данных
  • filter отбрасывает имена строк
  • subset имеет аргумент select
  • subset повторяет свой аргумент условия
  • 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_"): нет применимого метода для 'filter_', примененного к объекту класса "c ('matrix', 'double', 'numeric')"

filter(state.x77, Population < 400)

Ошибка в UseMethod ("filter_"): нет применимого метода для 'filter_', примененного к объекту класса "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

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 имеет аргумент 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))

Ошибка в filter_impl (.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 поддерживает использование местоимения .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 немного быстрее, когда он рассчитывает

Занимая набор данных, который Бенджамин построил в своем ответе (153 тыс. Строк), он в два раза быстрее, хотя и не должен быть узким местом.

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