Mam w zwyczaju zlepianie podobnych zadań w jedną linię. Na przykład, jeśli trzeba filtrować a
, b
i c
w tabeli danych, będę je razem w jednym []
z AND. Wczoraj zauważyłem, że w moim konkretnym przypadku było to niezwykle wolne i zamiast tego przetestowałem filtry łańcuchowe. Podałem przykład poniżej.
Najpierw zaszczepiam generator liczb losowych, ładuję tabelę danych i tworzę fikcyjny zestaw danych.
# 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))
Następnie określam swoje metody. Łańcuchy pierwszego podejścia filtrują razem. Drugi AND razem filtruje.
# 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)]
}
Tutaj sprawdzam, że dają te same wyniki.
# Check both give same result
identical(chain_filter(), and_filter())
#> [1] TRUE
Wreszcie dokonuję ich analizy porównawczej.
# 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
Utworzono 25.10.2019 przez pakiet reprezentx (v0.3.0)
W takim przypadku tworzenie łańcuchów skraca czas działania o około 70%. Dlaczego tak jest? Mam na myśli, co się dzieje pod maską w tabeli danych? Nie widziałem żadnych ostrzeżeń przed używaniem &
, więc byłem zaskoczony, że różnica jest tak duża. W obu przypadkach oceniają te same warunki, więc nie powinno być różnicy. W przypadku AND &
jest szybkim operatorem, a następnie musi tylko raz przefiltrować tabelę danych (tj. Przy użyciu wektora logicznego wynikającego z AND), a nie trzykrotnie w przypadku łańcucha.
Pytanie bonusowe
Czy zasada ta dotyczy ogólnie operacji na tabelach danych? Czy zadania modularne są zawsze lepszą strategią?
źródło
base
obserwacji za pomocą wektorów, wykonując następujące czynności:chain_vec <- function() { x <- which(a < .001); x[which(b[x] > .999)] }
iand_vec <- function() { which(a < .001 & b > .999) }
. (gdziea
ib
są wektory o tej samej długości odrunif
- użyłemn = 1e7
dla tych wartości granicznych).Odpowiedzi:
Przeważnie odpowiedź została podana w komentarzach aleady: „metoda łączenia”
data.table
jest w tym przypadku szybsza niż „metoda łączenia”, ponieważ łańcuch łączy warunki jeden po drugim. Ponieważ każdy krok zmniejsza rozmiar,data.table
jest mniej do oceny na następny. „Anding” każdorazowo ocenia warunki dla danych w pełnym rozmiarze.Możemy to zademonstrować na przykładzie: gdy poszczególne kroki NIE zmniejszają rozmiaru
data.table
(tj. Warunki do sprawdzenia są takie same dla obu podejść):Używając tych samych danych, ale
bench
pakietu, który automatycznie sprawdza, czy wyniki są identyczne:Jak widać tutaj, podejście anding jest w tym przypadku 2,43 razy szybsze . Oznacza to, że łączenie w łańcuchy faktycznie dodaje trochę narzutu , co sugeruje, że zwykle andinging powinien być szybszy. Z WYJĄTKIEM, jeśli warunki zmniejszają rozmiar
data.table
krok po kroku. Teoretycznie podejście łańcuchowe może być nawet wolniejsze (nawet z pominięciem kosztów ogólnych), a mianowicie, jeśli warunek zwiększyłby rozmiar danych. Ale praktycznie myślę, że nie jest to możliwe, ponieważ recykling wektorów logicznych nie jest dozwolonydata.table
. Myślę, że to odpowiada na twoje dodatkowe pytanie.Dla porównania, oryginalne funkcje na moim komputerze z
bench
:źródło