我有一个关于动态子集化数据表的问题。我知道stackoverflow上有很多线程,它们的命名方式相似,但遗憾的是它们没有引导我找到想要的解决方案。
示例数据集:
require(data.table)
dt <- data.table(date=c(rep(1,5),rep(2,5)),id=rep(1:5,2),var=c(1:10))
对于每个ID ,我希望之前找到 所有期间的所有其他 ID的子集。在示例数据集中,有5个ID和两个句点。如果在时段2中查看ID = 5,则相应的子集将是ID = {1,2,3,4)和date = 1的子集。在这个简单的数据集中,我当然可以手工编写代码:
dt[,dt[-.I][date<2],by=id]
但我想自动这样做。我试过像
这样的东西dt[,dt[-.I][date < unique(dt$date[.I])],by=id]
但不幸的是,这不起作用。
任何有用的评论表示赞赏!谢谢!
答案 0 :(得分:3)
您必须意识到组合会随着唯一日期/ ID数量的增加而爆炸。即使对于date = 1:10和id = 1:10,答案是4050行(需要0.7秒),而对于date = 1:50和id = 1:50,它已经是3001250行(需要6.2秒)。话虽如此,这应该按预期工作:
setkey(dt, date, id)
ans <- dt[!J(1), {d.tmp = date-1; id.tmp = id; dt[CJ(1:d.tmp,
setdiff(id, id.tmp))]}, by=list(date, id)]
setnames(ans, make.unique(names(ans)))
setkey(ans, date, id, date.1)
date id date.1 id.1 var
1: 2 1 1 2 2
2: 2 1 1 3 3
3: 2 1 1 4 4
4: 2 1 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: 2 3 1 1 1
10: 2 3 1 2 2
11: 2 3 1 4 4
12: 2 3 1 5 5
13: 2 4 1 1 1
14: 2 4 1 2 2
15: 2 4 1 3 3
16: 2 4 1 5 5
17: 2 5 1 1 1
18: 2 5 1 2 2
19: 2 5 1 3 3
20: 2 5 1 4 4
答案 1 :(得分:2)
我认为这是更快的解决方案:
dta <- data.table(date=c(rep(1,5),rep(2,5)),id=rep(1:5,2),var=c(1:10))
dta[,dta[dta[.I]$id!=dta$id & dta[.I]$date>dta$date],by=list(id,date)]
有关如何更快地制作此代码的任何评论都非常感谢。
答案 2 :(得分:0)
从1.9.8版开始(2016年11月25日,CRAN),data.table
具有 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已发布his own answer,要求进一步加快代码速度。 Arun's answer包含针对不同问题大小的时间安排。
因此,下面的基准试图重复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自己的解决方案几乎总是最慢。