Ho l'abitudine di raggruppare compiti simili in un'unica linea. Ad esempio, se devo filtrare su a
, b
e c
in una tabella di dati, li metterò insieme in uno []
con AND. Ieri, ho notato che nel mio caso particolare questo è stato incredibilmente lento e testato invece i filtri di concatenamento. Ho incluso un esempio di seguito.
Innanzitutto, eseguo il seeding del generatore di numeri casuali, carico data.table e creo un set di dati fittizio.
# 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))
Successivamente, definisco i miei metodi. Le prime catene di approccio si filtrano insieme. Il secondo AND riunisce i filtri.
# 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)]
}
Qui, controllo che danno gli stessi risultati.
# Check both give same result
identical(chain_filter(), and_filter())
#> [1] TRUE
Infine, li benchmark.
# 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
Creato il 25-10-2019 dal pacchetto reprex (v0.3.0)
In questo caso, il concatenamento riduce il tempo di esecuzione di circa il 70%. Perché è così? Voglio dire, cosa sta succedendo sotto il cofano nella tabella dei dati? Non ho visto alcun avvertimento contro l'utilizzo &
, quindi sono rimasto sorpreso dal fatto che la differenza sia così grande. In entrambi i casi valutano le stesse condizioni, quindi non dovrebbe fare differenza. Nel caso AND, &
è un operatore rapido e quindi deve filtrare la tabella di dati una sola volta (ovvero, utilizzando il vettore logico risultante dagli AND), anziché filtrare tre volte nel caso concatenato.
Domanda bonus
Questo principio vale per le operazioni della tabella dei dati in generale? Le attività di modularizzazione sono sempre una strategia migliore?
base
un'osservazione simile con i vettori facendo quanto segue: chain_vec <- function() { x <- which(a < .001); x[which(b[x] > .999)] }
e and_vec <- function() { which(a < .001 & b > .999) }
. (dove a
e b
sono vettori della stessa lunghezza da runif
- ho usato n = 1e7
per questi tagli).