Eu tenho o hábito de agrupar tarefas semelhantes em uma única linha. Por exemplo, se eu preciso filtrar a
, b
e c
em uma tabela de dados, eu vou colocá-los juntos em um []
com ANDs. Ontem, notei que, no meu caso particular, isso era incrivelmente lento e testou os filtros de encadeamento. Eu incluí um exemplo abaixo.
Primeiro, eu proponho o gerador de números aleatórios, carrego data.table e crio um conjunto de dados fictícios.
# 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))
Em seguida, defino meus métodos. A primeira abordagem encadeia os filtros juntos. O segundo ANDs os filtros juntos.
# 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)]
}
Aqui, verifico se eles dão os mesmos resultados.
# Check both give same result
identical(chain_filter(), and_filter())
#> [1] TRUE
Finalmente, eu os 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
Criado em 2019-10-25 pelo pacote reprex (v0.3.0)
Nesse caso, o encadeamento reduz o tempo de execução em cerca de 70%. Por que esse é o caso? Quero dizer, o que está acontecendo sob o capô na tabela de dados? Não vi nenhum aviso contra o uso &
, então fiquei surpreso que a diferença seja tão grande. Nos dois casos, eles avaliam as mesmas condições, de modo que não deve haver diferença. No caso AND, &
é um operador rápido e só precisa filtrar a tabela de dados uma vez (ou seja, usando o vetor lógico resultante dos ANDs), em vez de filtrar três vezes no caso encadeamento.
Pergunta bônus
Esse princípio é válido para as operações da tabela de dados em geral? As tarefas de modularização são sempre uma estratégia melhor?
base
observação semelhante com vetores, fazendo o seguinte: chain_vec <- function() { x <- which(a < .001); x[which(b[x] > .999)] }
e and_vec <- function() { which(a < .001 & b > .999) }
. (onde a
e b
são vetores do mesmo comprimento de runif
- usei n = 1e7
para esses pontos de corte).