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?
fonte
base
observação semelhante com vetores, fazendo o seguinte:chain_vec <- function() { x <- which(a < .001); x[which(b[x] > .999)] }
eand_vec <- function() { which(a < .001 & b > .999) }
. (ondea
eb
são vetores do mesmo comprimento derunif
- usein = 1e7
para esses pontos de corte).Respostas:
Principalmente, a resposta foi dada nos comentários já:
data.table
neste caso, o "método de encadeamento" é mais rápido que o "método de anding", pois o encadeamento executa as condições uma após a outra. Como cada etapa reduz o tamanho da,data.table
há menos para avaliar na próxima. "Anding" avalia as condições para os dados em tamanho real a cada vez.Podemos demonstrar isso com um exemplo: quando as etapas individuais NÃO diminuem o tamanho da
data.table
(ou seja, as condições a serem verificadas são as mesmas para as duas abordagens):Usando os mesmos dados, mas o
bench
pacote, que verifica automaticamente se os resultados são idênticos:Como você pode ver aqui, a abordagem anding é 2,43 vezes mais rápida nesse caso . Isso significa que o encadeamento realmente adiciona alguma sobrecarga , sugerindo que o anding geralmente deve ser mais rápido. EXCETO se as condições estiverem reduzindo o tamanho do
data.table
passo a passo. Teoricamente, a abordagem de encadeamento pode até ser mais lenta (mesmo deixando a sobrecarga de lado), ou seja, se uma condição aumentaria o tamanho dos dados. Mas praticamente acho que isso não é possível, uma vez que a reciclagem de vetores lógicos não é permitidadata.table
. Acho que isso responde à sua pergunta sobre bônus.Para comparação, funções originais na minha máquina com
bench
:fonte