web-dev-qa-db-fra.com

Différence entre le sous-ensemble et le filtre de dplyr

Il me semble que le sous-ensemble et le filtre (de dplyr) ont le même résultat… .. Mais ma question est la suivante: existe-t-il à un moment une différence potentielle, par exemple la vitesse, la taille des données qu'il peut gérer, etc.? Y a-t-il des occasions où il vaut mieux utiliser l'un ou l'autre?

Exemple:

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 
23
Ruthger Righart

Ils produisent effectivement le même résultat et leur concept est très similaire.

L’avantage de subset est qu’il fait partie de la base R et ne nécessite aucun paquet supplémentaire. Avec des échantillons de petite taille, il semble être un peu plus rapide que filter (6 fois plus rapide dans votre exemple, mais cela se mesure en microsecondes).

À mesure que les ensembles de données augmentent, filter semble gagner en efficacité. À 15 000 enregistrements, filter dépasse subset d’environ 300 microsecondes. Et avec 153 000 enregistrements, filter est trois fois plus rapide (mesurée en millisecondes).

Donc, en termes de temps humain, je ne pense pas qu'il y ait beaucoup de différence entre les deux. 

L’autre avantage (et c’est un avantage de niche) est que filter peut fonctionner sur des bases de données SQL sans extraire les données en mémoire. subset ne fait tout simplement pas cela.

Personnellement, j’ai tendance à utiliser filter, mais uniquement parce que j’utilise déjà le framework dplyr. Si vous ne travaillez pas avec des données insuffisantes, cela ne fera pas une grande différence.

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 
34
Benjamin

Une différence supplémentaire non encore mentionnée est que le filtre élimine les noms de domaine, contrairement au sous-ensemble:

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
20
rsmith54

Intéressant. J'essayais de voir la différence en termes de jeu de données résultant et je ne pourrais pas comprendre pourquoi l'opérateur "[" s'est comporté différemment (c'est-à-dire pourquoi il a également renvoyé des 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
1

Dans les principaux cas d'utilisation, ils se comportent de la même manière:

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

Mais ils ont quelques différences, notamment (j’ai été aussi exhaustif que possible mais j’en ai peut-être oublié certaines):

  • subset peut être utilisé sur des matrices
  • filter peut être utilisé sur des bases de données
  • filter supprime les noms de ligne
  • subset a un argument select
  • subset recycle son argument de condition
  • filter supporte les conditions en tant qu'arguments séparés
  • filter supporte l'utilisation du pronom .data
  • filter supporte certaines fonctionnalités rlang
  • filter prend en charge le regroupement
  • filter prend en charge n() et row_number()
  • filter est plus strict
  • filter est un peu plus rapide quand ça compte
  • subset a des méthodes dans d'autres paquets

subset peut être utilisé sur des matrices

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

Bien que les colonnes ne puissent pas être utilisées directement en tant que variables dans l'argument subset

subset(state.x77, Population < 400)

Erreur dans subset.matrix (state.x77, Population <400): object 'Population' introuvable

Ni ne fonctionne avec filter

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

Error in UseMethod ("filter_"): aucune méthode applicable pour 'filter _' appliqué à un objet de classe "c ('matrice', 'double', 'numérique')"

filter(state.x77, Population < 400)

Error in UseMethod ("filter_"): aucune méthode applicable pour 'filter _' appliqué à un objet de classe "c ('matrice', 'double', 'numérique')"

filter peut être utilisé sur des bases de données

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 ne peut pas

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

Erreur dans subset.default (., Hp <65): objet 'hp' introuvable.

filter supprime les noms de ligne

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 n'a pas

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 a un argument select

Alors que dplyr suit les principes tidyverse qui visent à ce que chaque fonction fasse une chose, donc select est une fonction séparée.

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

Il a également un argument drop, qui a principalement du sens dans le contexte de l’utilisation de l’argument select.

subset recycle son argument de condition

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

filter n'a pas

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

Erreur dans filter_impl (.data, quo): le résultat doit avoir une longueur de 150, et non 2

filter supporte les conditions en tant qu'arguments séparés

Les conditions sont transmises à ... afin que nous puissions en avoir plusieurs comme arguments différents, ce qui revient au même que d'utiliser & mais peut être plus lisible parfois en raison de la priorité des opérateurs logiques et de l'identification automatique.

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

filter supporte l'utilisation du pronom .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 supporte certaines fonctionnalités 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 prend en charge le regroupement

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 prend en charge n() et 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 est plus strict

Cela déclenche des erreurs si l'entrée est suspecte.

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 est un peu plus rapide quand ça compte

Empruntant le jeu de données que Benjamin a construit dans sa réponse (153 000 lignes), il est deux fois plus rapide, même si cela devrait rarement être un goulot d'étranglement.

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 a des méthodes dans d'autres paquets

subset est un générique S3, tout comme dplyr::filter est, mais subset en tant que fonction de base est plus susceptible d'avoir des méthodes développées dans d'autres packages, un exemple important est Zoo:::subset.Zoo.

0
Moody_Mudskipper

Un avantage supplémentaire de filter est qu’il joue Nice avec des données groupées. subset ignore les groupements.

Ainsi, lorsque les données sont groupées, subset fera toujours référence à l’ensemble des données, mais filter fera uniquement référence au groupe.

# 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
0
Albert

Une différence est également que le sous-ensemble fait plus de choses que le filtre que vous pouvez également sélectionner et supprimer tout en ayant deux fonctions différentes

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

dplyr::select(df,varA, varD)
0
R. Prost