Разница между подмножеством и фильтром от 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