data.table
从1.9.8版(CRAN 2016 年 11 月 25 日)开始,可以进行non-equi-joins。
非设备自连接
dta[dta, on = .(date > date), allow.cartesian = TRUE, nomatch = 0L,
.(id, x.date, i.date, i.id, i.var)][
id != i.id][order(id)]
id x.date i.date i.id i.var
1: 1 2 1 2 2
2: 1 2 1 3 3
3: 1 2 1 4 4
4: 1 2 1 5 5
5: 2 2 1 1 1
6: 2 2 1 3 3
7: 2 2 1 4 4
8: 2 2 1 5 5
9: 3 2 1 1 1
10: 3 2 1 2 2
11: 3 2 1 4 4
12: 3 2 1 5 5
13: 4 2 1 1 1
14: 4 2 1 2 2
15: 4 2 1 3 3
16: 4 2 1 5 5
17: 5 2 1 1 1
18: 5 2 1 2 2
19: 5 2 1 3 3
20: 5 2 1 4 4
正如 Arun 指出的那样,组合随着唯一日期/ID 数量的增加而爆炸式增长。因此,allow.cartesian = TRUE
不得不设置。
不幸的是,只有>=
, >
, <=
, < 和==
二元运算符可以在非等值连接中使用,但不能!=
。因此,连接的结果必须在id
之后被过滤为相等。
基准
OP 发布了他自己的答案,要求进一步加快代码速度。Arun 的回答包括不同问题大小的时间安排。
因此,下面的基准测试尝试重复 Arun 的测量并比较迄今为止发布的三种不同方法。
library(bench)
library(magrittr)
bm <- press(
n_date = c(2, 10, 50),
n_id = c(5, 10, 50),
{
dt0 <- CJ(date = seq_len(n_date), id = seq_len(n_id))
dt0[, var := .I]
mark(
arun = {
dt <- copy(dt0)
setkey(dt, date, id)
dt[!J(1), {
d.tmp = date-1
id.tmp = id
dt[CJ(1:d.tmp, setdiff(id, id.tmp))]
}, by=list(id, date)] -> arun
},
chameau13 = {
dta <- copy(dt0)
dta[,dta[dta[.I]$id!=dta$id & dta[.I]$date>dta$date],by=list(id,date)]
},
uwe = {
dta <- copy(dt0)
dta[dta, on = .(date > date), allow = TRUE, nomatch = 0L,
.(id = x.id, date = x.date, date.1 = i.date, id.1 = i.id, var = i.var)][
id != id.1]
},
check = my_check
)
}
)
由于 Arun 的解决方案通过引用修改数据集,因此所有运行都从一个新副本开始。这三种解决方案在列名和行顺序上有所不同。因此,使用自定义检查函数来确保结果相同:
my_check <- function(x, y) {
setnames(x, make.unique(names(x)))
setorder(x, id, date, date.1, id.1)
setnames(y, make.unique(names(y)))
setorder(y, id, date, date.1, id.1)
all.equal(x, y, check.attributes = FALSE) %T>%
{if (!isTRUE(.)) print(.)}
}
基准时间可以通过以下方式可视化
ggplot2::autoplot(bm)
迄今为止,非等连接是所有问题规模最快的方法,而 OP 自己的解决方案几乎总是最慢,尽管 OP 有预期。