Сообщение с любым вариантом слова Fast в заголовке будет неполным без тестов. Прежде чем мы опубликуем какие-либо тесты, я просто хотел бы упомянуть, что с тех пор, как был опубликован этот вопрос, для R
были выпущены два высоко оптимизированных пакета arrangements
и RcppAlgos
(я являюсь автором) для генерации комбинаций. Обратите внимание, что начиная с версии 2.3.0
для RcppAlgos
мы можем использовать преимущества нескольких потоков для еще большей эффективности.
Чтобы дать вам представление об их скорости по сравнению с combn
и gRbase::combnPrim
, вот базовый тест:
## We test generating just over 3 million combinations
choose(25, 10)
[1] 3268760
microbenchmark(arrngmnt = arrangements::combinations(25, 10),
combn = combn(25, 10),
gRBase = gRbase::combnPrim(25, 10),
serAlgos = RcppAlgos::comboGeneral(25, 10),
parAlgos = RcppAlgos::comboGeneral(25, 10, nThreads = 4),
unit = "relative", times = 20)
Unit: relative
expr min lq mean median uq max neval
arrngmnt 2.979378 3.072319 1.898390 3.756307 2.139258 0.4842967 20
combn 226.470755 230.410716 118.157110 232.905393 125.718512 17.7778585 20
gRBase 34.219914 34.209820 18.789954 34.218320 19.934485 3.6455493 20
serAlgos 2.836651 3.078791 2.458645 3.703929 2.231475 1.1652445 20
parAlgos 1.000000 1.000000 1.000000 1.000000 1.000000 1.0000000 20
Теперь мы проводим сравнительный анализ других функций, опубликованных для очень конкретного случая создания комбинаций select 2 и создания объекта data.table
.
Функции следующие:
funAkraf <- function(d) {
a <- comb2.int(length(d$id)) ## comb2.int from the answer given by @akraf
setDT(list(V1 = d$id[a[,1]], V2 = d$id[a[,2]]))
}
funAnirban <- function(d) {
indices <- combi2inds(d$id)
ans2 <- setDT(list(d$id[indices$xid], d$id[indices$yid]))
ans2
}
funArun <- function(d) {
d[, `:=`(id1 = 1L, id2 = .I)] ## add interval columns for overlaps
setkey(d, id1, id2)
olaps <- foverlaps(d, d, type="within", which=TRUE)[xid != yid]
ans <- setDT(list(d$id[olaps$xid], d$id[olaps$yid]))
ans
}
funArrangements <- function(d) {
a <- arrangements::combinations(x = d$id, k = 2)
setDT(list(a[, 1], a[, 2]))
}
funGRbase <- function(d) {
a <- gRbase::combnPrim(d$id,2)
setDT(list(a[1, ], a[2, ]))
}
funOPCombn <- function(d) {
a <- combn(d$id, 2)
setDT(list(a[1, ], a[2, ]))
}
funRcppAlgos <- function(d) {
a <- RcppAlgos::comboGeneral(d$id, 2, nThreads = 4)
setDT(list(a[, 1], a[, 2]))
}
Бенчмарк с данными OP
И вот тесты на примере, приведенном OP:
d <- data.table(id=as.character(paste0("A", 10001:15000)))
microbenchmark(funAkraf(d),
funAnirban(d),
funArrangements(d),
funArun(d),
funGRbase(d),
funOPCombn(d),
funRcppAlgos(d),
times = 10, unit = "relative")
Unit: relative
expr min lq mean median uq max neval
funAkraf(d) 3.220550 2.971264 2.815023 2.665616 2.344018 3.383673 10
funAnirban(d) 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 10
funArrangements(d) 1.464730 1.689231 1.834650 1.960233 1.932361 1.693305 10
funArun(d) 3.256889 2.908075 2.634831 2.729180 2.432277 2.193849 10
funGRbase(d) 3.513847 3.340637 3.327845 3.196399 3.291480 3.129362 10
funOPCombn(d) 30.310469 26.255374 21.656376 22.386270 18.527904 15.626261 10
funRcppAlgos(d) 1.676808 1.956696 1.943773 2.085968 1.949133 1.804180 10
Мы видим, что функция, предоставляемая @AnirbanMukherjee, является самой быстрой для этой задачи, за ней следует _12 _ / _ 13_. Для этой задачи nThreads
не действует, поскольку переданный вектор - это character
, что не является потокобезопасным. Что, если бы вместо этого мы преобразовали id
в коэффициент?
Контрольные показатели с факторами (т. Е. Категориальными переменными)
dFac <- d
dFac$id <- as.factor(dFac$id)
library(microbenchmark)
microbenchmark(funAkraf(dFac),
funAnirban(dFac),
funArrangements(dFac),
funArun(dFac),
funGRbase(dFac),
funOPCombn(dFac),
funRcppAlgos(dFac),
times = 10, unit = "relative")
Unit: relative
expr min lq mean median uq max neval
funAkraf(dFac) 10.898202 10.949896 7.589814 10.01369 8.050005 5.557014 10
funAnirban(dFac) 3.104212 3.337344 2.317024 3.00254 2.471887 1.530978 10
funArrangements(dFac) 2.054116 2.058768 1.858268 1.94507 2.797956 1.691875 10
funArun(dFac) 10.646680 12.905119 7.703085 11.50311 8.410893 3.802155 10
funGRbase(dFac) 16.523356 21.609917 12.991400 19.73776 13.599870 6.498135 10
funOPCombn(dFac) 108.301876 108.753085 64.338478 95.56197 65.494335 28.183104 10
funRcppAlgos(dFac) 1.000000 1.000000 1.000000 1.00000 1.000000 1.000000 10
Теперь мы видим, что RcppAlgos
примерно на 2x
быстрее, чем любое другое решение. В частности, RcppAlgos
решение примерно на 3x
, чем ранее самое быстрое решение, данное Anirban. Следует отметить, что это повышение эффективности стало возможным, потому что factor
переменные действительно integers
под капотом с некоторыми дополнительными attributes
.
Подтвердите равенство
Все они также дают одинаковый результат. Единственное предостережение: решение gRbase
не поддерживает факторы. То есть, если вы передадите factor
, он будет преобразован в character
. Таким образом, все решения дадут одинаковый результат, если вы пройдете dFac
, за исключением решения gRbase
:
identical(funAkraf(d), funOPCombn(d))
#[1] TRUE
identical(funAkraf(d), funArrangements(d))
#[1] TRUE
identical(funRcppAlgos(d), funArrangements(d))
#[1] TRUE
identical(funRcppAlgos(d), funAnirban(d))
#[1] TRUE
identical(funRcppAlgos(d), funArun(d))
#[1] TRUE
## different order... we must sort
identical(funRcppAlgos(d), funGRbase(d))
[1] FALSE
d1 <- funGRbase(d)
d2 <- funRcppAlgos(d)
## now it's the same
identical(d1[order(V1, V2),], d2[order(V1,V2),])
#[1] TRUE
Спасибо @Frank за то, что он указал, как сравнить два data.tables
, не пытаясь создать новые data.tables
и затем расположить их:
fsetequal(funRcppAlgos(d), funGRbase(d))
[1] TRUE
person
Joseph Wood
schedule
23.06.2018
data.table
с github? - person David Arenburg   schedule 09.11.2014d.2 <- d[, list(neighbor=d$id[-which(d$id==id)]), by=c("id")]
дает результат, идентичныйd.1 <- as.data.table(t(combn(d$id, 2)))
? Я получаю вдвое больше данных. Я мог воспроизвестиcmbn
сdata.table
, используяCJ
, что-то вродеCJ(d$id, d$id), V1, V2)[V2 > V1]
- person David Arenburg   schedule 09.11.2014data.table
. При использовании этого подхода каждая комбинация включается дважды. Мой вопрос заключался в том, как избежать добавления этих дубликатов, поскольку это критично, если вектор становится большим. - person majom   schedule 09.11.2014CJ(d$id, d$id)
, который будет выполняться менее секунды - person David Arenburg   schedule 09.11.2014CJ(d$id, d$id)
будет включать их. Извините, если я не смог это прояснить. - person majom   schedule 09.11.2014d.2 <- d[, list(neighbor=d$id[-which(d$id==id)]), by=c("id")]
эквивалентноCJ(d$id, d$id)
. В то время какd.1 <- as.data.table(t(combn(d$id, 2)))
равноCJ(d$id, d$id)[V2 > V1]
- person David Arenburg   schedule 09.11.2014d.2 <- d[, list(neighbor=d$id[-which(d$id==id)]), by=c("id")]
иCJ(d$id, d$id)
не эквивалентны. Первое не включает самопетли, тогда как второе включает. - person majom   schedule 09.11.2014CJ(d$id, d$id)[V1 != V2]
было бы равноценно, но в два раза быстрее - person David Arenburg   schedule 09.11.2014