У меня есть привычка объединять похожие задачи в одну строку. Например, если мне нужно отфильтровать a
, b
и c
в таблице данных, я положу их вместе в одном[]
с Андами. Вчера я заметил, что в моем конкретном случае это было невероятно медленно, и вместо этого проверил фильтры цепочки. Я включил пример ниже.
Сначала я запускаю генератор случайных чисел, загружаю data.table и создаю фиктивный набор данных.
# 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))
Далее я определяю свои методы. Первый подход объединяет фильтры. Второй и фильтры вместе.
# 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)]
}
Здесь я проверяю, что они дают одинаковые результаты.
# Check both give same result
identical(chain_filter(), and_filter())
#> [1] TRUE
Наконец, я оцениваю их.
# 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
Создано в 2019-10-25 по пакету представитель (v0.3.0)
В этом случае сцепление сокращает время выполнения примерно на 70%. Почему это так? Я имею в виду, что происходит под капотом в таблице данных? Я не видел никаких предупреждений против использования &
, поэтому я был удивлен, что разница настолько велика. В обоих случаях они оценивают одни и те же условия, поэтому разницы не должно быть. В случае И,&
это быстрый оператор, и тогда он должен фильтровать таблицу данных только один раз (т. Е. Используя логический вектор, полученный из AND), в отличие от фильтрации три раза в случае цепочки.
Бонусный вопрос
Действует ли этот принцип для операций с таблицами данных в целом? Всегда ли модульные задачи - лучшая стратегия?
источник
base
наблюдение с векторами, выполнив следующие действия:chain_vec <- function() { x <- which(a < .001); x[which(b[x] > .999)] }
иand_vec <- function() { which(a < .001 & b > .999) }
. (гдеa
иb
- векторы одинаковой длиныrunif
- я использовалn = 1e7
для этих срезов).Ответы:
Главным образом, ответ был дан в комментариях aleady: «метод цепочки» для
data.table
в этом случае быстрее, чем «метод цепочки», поскольку цепочка запускает условия один за другим. По мере того, как каждый шаг уменьшает размер,data.table
все меньше нужно оценивать для следующего. «Anding» оценивает условия для полноразмерных данных каждый раз.Мы можем продемонстрировать это на примере: когда отдельные шаги НЕ уменьшают размер
data.table
(т.е. условия для проверки одинаковы для обеих аппроксимаций):Используя те же данные, но
bench
пакет, который автоматически проверяет, совпадают ли результаты:Как вы можете видеть здесь, подход в этом случае быстрее в 2,43 раза . Это означает, что на самом деле цепочка добавляет некоторые накладные расходы , предполагая, что обычно андинг должен быть быстрее.ИСКЛЮЧИТЬ, если условия уменьшают размер
data.table
шаг за шагом. Теоретически, подход цепочки может быть даже медленнее (даже если оставить в стороне накладные расходы), а именно, если условие увеличит размер данных. Но практически я думаю, что это невозможно, так как повторное использование логических векторов не допускаетсяdata.table
. Я думаю, что это отвечает на ваш бонусный вопрос.Для сравнения, оригинальные функции на моей машине с
bench
:источник