如何更快地按组计算CJ? (data.table的交叉联接)

时间:2019-10-23 22:14:41

标签: r performance group-by data.table

我需要使用一个大型数据集来(多次)按组计算交叉联接,而且相当慢。 你能告诉我更快的方法吗?

玩具示例:

set.seed(1)
totletter <- 10
LLL <- LETTERS[1:totletter]
nID <- 100000
neach <- 5
nnn <- rep(1:nID, each=neach)  # In my real problem each is not constant
myDT <- data.table(id=paste0("ID",nnn), group=sample(LLL,nID*neach,replace=T))

现在我想使该功能更快。将每个组的字母与所有可能的字母结合起来。

combi <- myDT[,CJ( unique(group) ,LLL), by=id] 

在我的计算机上,nID = 100000个组需要92秒。
对于nID = 1M,大约需要920秒。 (我需要一百万)。

我知道这与类似的问题有关。在许多子组上运行任何功能都很慢:

https://github.com/Rdatatable/data.table/issues/3988 https://github.com/Rdatatable/data.table/issues/3739

对于CJ,我只需要任何技巧就可以更快地完成它。

1 个答案:

答案 0 :(得分:4)

我认为一个合理的问题是,您将如何处理大量的组合。无论如何,这里有2个选项:

1)通过ID获取唯一的组,然后执行交叉连接(请参阅参考资料)

import torch.nn as nn  
nn.Sequential(nn.Conv2d(in_planes, out_planes, kernel_size=kernel_size, stride=stride, padding=padding, dilation=dilation, bias=True), 
    nn.BatchNorm2d(out_planes), 
    nn.ReLU(inplace=True))

2)获取唯一的组,然后CJ索引并提取与这些索引相对应的行

ug <- myDT[, unique(group), id]
ug[, c(.SD, .(LLL=LLL)), seq_len(ug[, .N])][, (1) := NULL]

计时代码:

ug <- myDT[, unique(group), id]
idx <- CJ(ug[,seq_len(.N)], seq_along(LLL))
ug[idx$V1, c(.SD, .(LLL=LLL[idx$V2]))]

时间:

set.seed(1L)
totletter <- 10
LLL <- LETTERS[1:totletter]
nID <- 1e5
neach <- 5
nnn <- rep(1:nID, each=neach)  # In my real problem each is not constant
myDT <- data.table(id=paste0("ID",nnn), group=sample(LLL,nID*neach,replace=T))

mtd0 <- function() myDT[,CJ( unique(group) ,LLL), by=id]

mtd1 <- function() {
    ug <- myDT[, unique(group), id]
    ug[, c(.SD, .(LLL=LLL)), seq_len(ug[, .N])][, (1) := NULL]
}

mtd2 <- function() {
    ug <- myDT[, unique(group), id]
    idx <- CJ(ug[,seq_len(.N)], seq_along(LLL))
    ug[idx$V1, c(.SD, .(LLL=LLL[idx$V2]))]
}    

combi <- mtd0()
setorder(combi, id, V1, LLL)
ans1 <- mtd1()
setorder(ans1, id, V1, LLL)
ans2 <- mtd2()
setorder(ans2, id, V1, LLL)
identical(combi, ans1)
# [1] TRUE
identical(ans1, ans2)
# [1] TRUE

bench::mark(mtd0(), mtd1(), mtd2(), check=FALSE)

参考:

2个data.tables的交叉联接:https://github.com/Rdatatable/data.table/issues/1717#issuecomment-515002560


编辑以解决OP的评论:

实际上,除了OP方法的内存使用之外,我认为# A tibble: 3 x 13 expression min median `itr/sec` mem_alloc `gc/sec` n_itr n_gc total_time result memory time gc <bch:expr> <bch:tm> <bch:tm> <dbl> <bch:byt> <dbl> <int> <dbl> <bch:tm> <list> <list> <list> <list> 1 mtd0() 1.14m 1.14m 0.0146 1.84GB 0.583 1 40 1.14m <df[,3] [4,094,950 x 3]> <df[,3] [522,766 x 3]> <bch:tm> <tibble [1 x 3]> 2 mtd1() 1.67s 1.67s 0.600 265.05MB 1.80 1 3 1.67s <df[,3] [4,094,950 x 3]> <df[,3] [1,753 x 3]> <bch:tm> <tibble [1 x 3]> 3 mtd2() 926.29ms 926.29ms 1.08 257.22MB 1.08 1 1 926.29ms <df[,3] [4,094,950 x 3]> <df[,3] [23,859 x 3]> <bch:tm> <tibble [1 x 3]> 的使用也会减慢速度,这可以从以下经验时间看出:

by

时间:

set.seed(1L)
totletter <- 10
LLL <- LETTERS[1:totletter]
nID <- 1e5
neach <- 5
nnn <- rep(1:nID, each=neach)  # In my real problem each is not constant
myDT <- data.table(id=paste0("ID",nnn), group=sample(LLL,nID*neach,replace=T))

mtd00 <- function() myDT[,CJ(unique(group), LLL), by=id]
mtd01 <- function() myDT[,CJ(group, LLL, unique=TRUE), by=id]
mtd02 <- function() myDT[, .(group=unique(group)), id][, CJ(group ,LLL), by=id]

mtd1 <- function() {
    ug <- myDT[, unique(group), id]
    ug[, c(.SD, .(LLL=LLL)), seq_len(ug[, .N])][, (1) := NULL]
}

mtd2 <- function() {
    ug <- myDT[, unique(group), id]
    idx <- CJ(ug[,seq_len(.N)], seq_along(LLL))
    ug[idx$V1, c(.SD, .(LLL=LLL[idx$V2]))]
}