Как отмечает @jangorecki, оба запроса уже используют ключ - последний просто требует небольшого дополнительного времени для преобразования формы «векторного сканирования» в форму двоичного поиска. Вы можете увидеть это с помощью verbose=TRUE
:
DT[ x == "a" & y == 5L, .N, nomatch = NULL, verbose = TRUE]
показывает вывод:
Optimized subsetting with key 'x, y'
forder.c received 1 rows and 2 columns
forder took 0.001 sec
x is already ordered by these columns, no need to call reorder
i.x has same type (character) as x.x. No coercion needed.
i.y has same type (integer) as x.y. No coercion needed.
on= matches existing key, using key
Starting bmerge ...
bmerge done in 0.000s elapsed (0.000s cpu)
Constructing irows for '!byjoin || nqbyjoin' ... 0.000s elapsed (0.000s cpu)
Detected that j uses these columns: <none>
Сравните с версией прямого двоичного поиска:
DT[.(c("a"), c(5L)), .N, nomatch = NULL, verbose = TRUE]
i.V1 has same type (character) as x.x. No coercion needed.
i.V2 has same type (integer) as x.y. No coercion needed.
on= matches existing key, using key
Starting bmerge ...
forder.c received 1 rows and 2 columns
bmerge done in 0.001s elapsed (0.000s cpu)
Constructing irows for '!byjoin || nqbyjoin' ... 0.000s elapsed (0.000s cpu)
Detected that j uses these columns: <none>
Но это в два раза медленнее, верно? Также, как указано, масштаб времени очень мал. Более полезное сравнение - это случай, когда ключ вообще не используется. Давайте создадим несортированную копию ваших данных:
DTrand = DT[sample(.N)]
Еще один быстрый шаг вперед - мы должны быть осторожны при сравнительном анализе, потому что data.table
также выполняет некоторые автоматические c оптимизации, чтобы помочь сортировать ваши данные даже в этот несортированный случай:
DTrand[ x == "a" & y == 5L, .N, nomatch = NULL, verbose = TRUE]
Внимательно прочитайте вывод:
Creating new index 'y__x'
Creating index y__x done in ... forder.c received 10000000 rows and 3 columns
forder took 0.424 sec
0.286s elapsed (1.117s cpu)
Optimized subsetting with index 'y__x'
forder.c received 1 rows and 2 columns
forder took 0.002 sec
x is already ordered by these columns, no need to call reorder
i.y has same type (integer) as x.y. No coercion needed.
i.x has same type (character) as x.x. No coercion needed.
on= matches existing index, using index
Starting bmerge ...
bmerge done in 0.000s elapsed (0.000s cpu)
Constructing irows for '!byjoin || nqbyjoin' ... 0.000s elapsed (0.001s cpu)
Reorder irows for 'mult=="all" && !allGrp1' ... forder.c received 360 rows and 2 columns
0.000s elapsed (0.002s cpu)
Detected that j uses these columns: <none>
[1] 360
data.table
автоматически применил setindex
к вашей таблице, что (хотя и не так быстро, как физическая сортировка, как с setkey
), тем не менее, ускорит любые будущие подмножества; просто повторяя (как это было бы с тестом):
DTrand[ x == "a" & y == 5L, .N, nomatch = NULL, verbose = TRUE]
Обратите внимание на сходство с регистром с ключом (swap key
для index
):
Optimized subsetting with index 'y__x'
forder.c received 1 rows and 2 columns
forder took 0 sec
x is already ordered by these columns, no need to call reorder
i.y has same type (integer) as x.y. No coercion needed.
i.x has same type (character) as x.x. No coercion needed.
on= matches existing index, using index
Starting bmerge ...
bmerge done in 0.000s elapsed (0.000s cpu)
Constructing irows for '!byjoin || nqbyjoin' ... 0.000s elapsed (0.000s cpu)
Reorder irows for 'mult=="all" && !allGrp1' ... forder.c received 360 rows and 2 columns
0.001s elapsed (0.001s cpu)
Detected that j uses these columns: <none>
[1] 360
Таким образом, Наивный тест даже на DTrand
не будет настоящим сравнением - после первого запуска теста таблица будет проиндексирована, и последующие подмножества будут использовать этот & бинарный поиск. См. виньетка для вторичных индексов для получения более подробной информации.
Мы можем обойти это и получить надлежащий тест, установив для параметра datatable.auto.index
значение FALSE
и сбросив существующий индекс:
options(datatable.auto.index = FALSE)
setindex(DTrand, NULL)
Теперь data.table
забывает, как сортировать DTrand
по x
и y
, и мы можем сравнить подход двоичного поиска и подмножество истинного вектора:
microbenchmark::microbenchmark(
times = 50L,
vector = DTrand[ x == "a" & y == 5L, .N, nomatch = NULL],
binary = DT[ x == "a" & y == 5L, .N, nomatch = NULL]
)
# Unit: milliseconds
# expr min lq mean median uq max neval
# vector 101.43306 114.325340 134.154362 119.367909 128.05273 345.721296 50
# binary 1.06033 1.160188 1.631119 1.367017 1.57334 5.508802 50
Так что пока прямой подход с использованием .()
в два раза быстрее, чем оптимизированный подход с использованием ==
, ==
все еще в 100 раз быстрее, чем true векторное подмножество.
Вы также можете выгода от data.table
бенчмаркинга