J'ai l'habitude de regrouper des tâches similaires en une seule ligne. Par exemple, si j'ai besoin de filtrer sur a
, b
et c
dans un tableau de données, je vais les regrouper en un []
avec des AND. Hier, j'ai remarqué que dans mon cas particulier, c'était des filtres de chaînage incroyablement lents et testés à la place. J'ai inclus un exemple ci-dessous.
Tout d'abord, j'amorce le générateur de nombres aléatoires, charge data.table et crée un ensemble de données factices.
# Set RNG seed
set.seed(-1)
# Load libraries
library(data.table)
# Create data table
dt <- data.table(a = sample(1:1000, 1e7, replace = TRUE),
b = sample(1:1000, 1e7, replace = TRUE),
c = sample(1:1000, 1e7, replace = TRUE),
d = runif(1e7))
Ensuite, je définis mes méthodes. Les premières chaînes d'approche filtrent ensemble. Le second ET combine les filtres.
# Chaining method
chain_filter <- function(){
dt[a %between% c(1, 10)
][b %between% c(100, 110)
][c %between% c(750, 760)]
}
# Anding method
and_filter <- function(){
dt[a %between% c(1, 10) & b %between% c(100, 110) & c %between% c(750, 760)]
}
Ici, je vérifie qu'ils donnent les mêmes résultats.
# Check both give same result
identical(chain_filter(), and_filter())
#> [1] TRUE
Enfin, je les compare.
# Benchmark
microbenchmark::microbenchmark(chain_filter(), and_filter())
#> Unit: milliseconds
#> expr min lq mean median uq max
#> chain_filter() 25.17734 31.24489 39.44092 37.53919 43.51588 78.12492
#> and_filter() 92.66411 112.06136 130.92834 127.64009 149.17320 206.61777
#> neval cld
#> 100 a
#> 100 b
Créé le 2019-10-25 par le package reprex (v0.3.0)
Dans ce cas, le chaînage réduit le temps d'exécution d'environ 70%. pourquoi est-ce le cas? Je veux dire, que se passe-t-il sous le capot dans le tableau de données? Je n'ai vu aucun avertissement contre l'utilisation &
, j'ai donc été surpris que la différence soit si grande. Dans les deux cas, ils évaluent les mêmes conditions, donc cela ne devrait pas faire de différence. Dans le cas ET, &
est un opérateur rapide et il n'a alors qu'à filtrer la table de données une fois (c'est-à-dire en utilisant le vecteur logique résultant des ET), par opposition au filtrage trois fois dans le cas de chaînage.
Question bonus
Ce principe est-il valable pour les opérations sur les tableaux de données en général? La modularisation des tâches est-elle toujours une meilleure stratégie?
base
observation similaire avec des vecteurs en procédant comme suit: chain_vec <- function() { x <- which(a < .001); x[which(b[x] > .999)] }
et and_vec <- function() { which(a < .001 & b > .999) }
. (où a
et b
sont des vecteurs de la même longueur runif
- j'ai utilisé n = 1e7
pour ces coupures).