好问题!!这个例子数据特别清晰易懂,解释得也很好。
首先我将展示答案,然后逐步解释。
> ids = 1:3 # or from the data: unique(ds$ID)
> pos = 1:6 # or from the data: unique(ds$Pos)
> setkey(ds,ID,Pos)
> ds[CJ(ids,pos), roll=-Inf, nomatch=0][, .N, by=Pos]
Pos N
1: 1 3
2: 2 3
3: 3 3
4: 4 3
5: 5 2
6: 6 1
>
这对于您的大数据也应该非常高效。
逐步执行
首先我尝试了一个交叉连接(CJ); 即,对于每个训练数据的每个位置。
> ds[CJ(ids,pos)]
ID Pos Obs
1: 1 1 1.50
2: 1 2 NA
3: 1 3 2.50
4: 1 4 NA
5: 1 5 0.00
6: 1 6 1.25
7: 2 1 NA
8: 2 2 1.45
9: 2 3 1.50
10: 2 4 NA
11: 2 5 2.50
12: 2 6 NA
13: 3 1 NA
14: 3 2 0.00
15: 3 3 1.25
16: 3 4 1.45
17: 3 5 NA
18: 3 6 NA
我看到每列火车有6排。我看到3列火车。我得到了我预期的18排。在那列火车没有观察到时,我看到NA
。很好。检查一下。笛卡尔积似乎正常工作。现在让我们构建查询。
你写道,如果在位置n观察到一列火车,则必须经过之前的位置。我立即想到了roll
。让我们试试。
ds[CJ(ids,pos), roll=TRUE]
ID Pos Obs
1: 1 1 1.50
2: 1 2 1.50
3: 1 3 2.50
4: 1 4 2.50
5: 1 5 0.00
6: 1 6 1.25
7: 2 1 NA
8: 2 2 1.45
9: 2 3 1.50
10: 2 4 1.50
11: 2 5 2.50
12: 2 6 2.50
13: 3 1 NA
14: 3 2 0.00
15: 3 3 1.25
16: 3 4 1.45
17: 3 5 1.45
18: 3 6 1.45
哦,这使得每个列车的观测结果向前滚动了。 对于第2和第3列车,在位置1留下了一些NA
,但是您说如果在位置2观察到火车,则必须经过位置1。 它还将第2和第3辆火车的最后一次观测向前滚动到位置6,但是您说火车可能会爆炸。 因此,我们想要向后滚动! 这就是roll=-Inf
。 这是一个复杂的-Inf
,因为您还可以控制向后滚动多远,但对于这个问题我们不需要; 我们只想无限向后滚动。 让我们尝试roll=-Inf
,看看会发生什么。
> ds[CJ(ids,pos), roll=-Inf]
ID Pos Obs
1: 1 1 1.50
2: 1 2 2.50
3: 1 3 2.50
4: 1 4 0.00
5: 1 5 0.00
6: 1 6 1.25
7: 2 1 1.45
8: 2 2 1.45
9: 2 3 1.50
10: 2 4 2.50
11: 2 5 2.50
12: 2 6 NA
13: 3 1 0.00
14: 3 2 0.00
15: 3 3 1.25
16: 3 4 1.45
17: 3 5 NA
18: 3 6 NA
好了,差不多了。现在我们只需要计数。但是,在第2和第3次火车爆炸之后,那些讨厌的NA
还在那里。让我们把它们移除。
> ds[CJ(ids,pos), roll=-Inf, nomatch=0]
ID Pos Obs
1: 1 1 1.50
2: 1 2 2.50
3: 1 3 2.50
4: 1 4 0.00
5: 1 5 0.00
6: 1 6 1.25
7: 2 1 1.45
8: 2 2 1.45
9: 2 3 1.50
10: 2 4 2.50
11: 2 5 2.50
12: 3 1 0.00
13: 3 2 0.00
14: 3 3 1.25
15: 3 4 1.45
顺便提一下,data.table
喜欢尽可能多地放在一个单独的 DT[...]
中,因为这样它可以优化查询。内部并不会创建 NA
然后把它们删除;它从一开始就不会创建 NA
。这个概念对于效率很重要。
最后,我们只需要计数即可。我们可以将此作为复合查询附加在末尾。
> ds[CJ(ids,pos), roll=-Inf, nomatch=0][, .N, by=Pos]
Pos N
1: 1 3
2: 2 3
3: 3 3
4: 4 3
5: 5 2
6: 6 1
ds[ , list( Pos = 1:Pos[.N] ) , by = ID ][ , .N , by = Pos ]
相比如何? - Simon O'HanlonPos[.N]
将是一个新的长度为1的向量,传递给:
函数以创建一个新的1:Pos[.N]
向量。我预计所有这些小向量会使内存卡顿并导致更多的垃圾回收。随着列车数量的增加,这将比位置数量的增加更加严重(更多的组)。如果你测试它,我对结果很感兴趣! - Matt DowlenAtMax
?也许这就是@SimonO'Hanlon正在做的事情? - Martin Morgands[,max(Pos),by=ID][,rev(cumsum(rev(tabulate(V1))))]
。我还在你的答案上添加了更长的评论。 - Matt Dowle