Tengo la costumbre de agrupar tareas similares en una sola línea. Por ejemplo, si necesito filtrar a, by cen una tabla de datos, voy a poner juntos en un solo []con AND. Ayer, noté que en mi caso particular esto era increíblemente lento y probé encadenar filtros. He incluido un ejemplo a continuación.
Primero, siembro el generador de números aleatorios, cargo data.table y creo un conjunto de datos ficticios.
# 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))
A continuación, defino mis métodos. El primer enfoque encadena los filtros juntos. El segundo Y une los filtros.
# 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)]
}
Aquí, compruebo que dan los mismos resultados.
# Check both give same result
identical(chain_filter(), and_filter())
#> [1] TRUE
Finalmente, los comparo.
# 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
Creado el 25/10/2019 por el paquete reprex (v0.3.0)
En este caso, el encadenamiento reduce el tiempo de ejecución en aproximadamente un 70%. ¿Por qué es este el caso? Quiero decir, ¿qué está pasando bajo el capó en la tabla de datos? No he visto ninguna advertencia contra el uso &, por lo que me sorprendió que la diferencia sea tan grande. En ambos casos, evalúan las mismas condiciones, por lo que eso no debería ser una diferencia. En el caso AND, &es un operador rápido y luego solo tiene que filtrar la tabla de datos una vez (es decir, usando el vector lógico resultante de los AND), en lugar de filtrar tres veces en el caso de encadenamiento.
Pregunta extra
¿Este principio es válido para las operaciones de la tabla de datos en general? ¿Las tareas de modularización son siempre una mejor estrategia?
baseobservación similar con vectores haciendo lo siguiente: chain_vec <- function() { x <- which(a < .001); x[which(b[x] > .999)] }y and_vec <- function() { which(a < .001 & b > .999) }. (donde ay bson vectores de la misma longitud desde runif- Utilicé n = 1e7para estos puntos de corte).