2014-01-08 28 views
18

我正在重写一些分析大量数据(大约1700万行)的R脚本,我想我会尝试使用data.table包(我只是在学习!)来提高内存效率。复杂的汇总函数 - 是否可以使用R data.table包来解决?

代码的一部分一直令我困惑。我不能发布我的原始解决方案,因为(1)它是废话(慢!),和(2)关于数据非常细微,并且会使这个问题复杂化。

相反,我作出这个玩具例子(它确实是一个玩具为例):

ds <- data.table(ID=c(1,1,1,1,2,2,2,3,3,3), 
Obs=c(1.5,2.5,0.0,1.25,1.45,1.5,2.5,0.0,1.25,1.45), 
Pos=c(1,3,5,6,2,3,5,2,3,4)) 

,看起来像这样:

ID Obs Pos 
1: 1 1.50 1 
2: 1 2.50 3 
3: 1 0.00 5 
4: 1 1.25 6 
5: 2 1.45 2 
6: 2 1.50 3 
7: 2 2.50 5 
8: 3 0.00 2 
9: 3 1.25 3 
10: 3 1.45 4 

为了便于说明,我会假装我们正在观测列车(每列列车都有自己的ID),在一个线性单程轨道上移动,并且观测(某些值,不是导入到问题中)列车正在设定的位置上进行(pos,在这里从1-6)沿着赛道。预计一列火车将不会使它成为整个轨道的长度(也许它在达到6的位置之前爆炸),并且有时观察者错过了一个观察结果......这些位置是连续的(因此如果我们错过了观察列车在位置4,但我们在位置5观察到它,我们知道它必须通过位置4)。

从上面data.table,我需要生成一个像这样的表:

Pos Count 
1: 1  3 
2: 2  3 
3: 3  3 
4: 4  3 
5: 5  2 
6: 6  1 

如果为每一个独特波什我data.table DS,我有列车数的计数不管观察是否在赛道上的那个位置进行,这使得它位于赛道上(或更远)的那个位置。

如果任何人有任何想法或建议如何解决这个问题,将不胜感激。不幸的是,我对data.table不够熟悉,不知道这是否可以完成!或者它可能是难以置信的简单问题来解决,我只是缓慢:)

回答

14

很好的问题!示例数据特别适合构建和解释清楚。

首先我会展示这个答案,然后我会一步一步解释它。

> 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上留下了一些NA的位置1,但是你说如果在位置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 
+0

+1非常好的解决方案,甚至更好的解释。你可以说一些关于你如何期望这样比较的话:当数据变大时,ds [,list(Pos = 1:Pos [.N]),by = ID] [,.N,by = Pos]' –

+0

@ SimonO'Hanlon不错的选择。 'Pos [.N]'将是一个新的长度为1的向量,传递给':'函数来创建一个新的'1:Pos [.N]'向量。我希望所有这些小型媒体都会堵塞内存并导致更多的垃圾收集。随着列车数量的增加,可能会增加(更多组)的数量,而不是增加的数量。如果你测试它,我对结果感兴趣! –

+0

我不太了解data.table语法,但CJ看起来很贵(概念上,如果不是真的?);有没有类似我的解决方案,其中data.table通过ID标识最大Pos,即让我们快速访问'nAtMax'?也许这就是@ SimonO'Hanlon正在做什么? –

3

你可以尝试如下。我有目的地将其分解成多个步骤解决方案,以便更好地理解。您可以将所有这些组合成一个步骤,只需链接[]即可。

这里的逻辑是我们首先找到每个ID的最终位置。然后我们汇总数据以查找每个最终排名的ID数量。由于最终排名6的所有ID也应计入最终排名5,因此我们使用cumsum将所有更高的ID计数添加到其各自的较低ID。

ds2 <- ds[, list(FinalPos=max(Pos)), by=ID] 

ds2 
## ID FinalPos 
## 1: 1  6 
## 2: 2  5 
## 3: 3  4 

ds3 <- ds2[ , list(Count = length(ID)), by = FinalPos][order(FinalPos, decreasing=TRUE), list(FinalPos, Count = cumsum(Count))] 

ds3 
## FinalPos Count 
## 1:  4  3 
## 2:  5  2 
## 3:  6  1 

setkey(ds3, FinalPos) 

ds3[J(c(1:6)), roll = 'nearest'] 

## FinalPos Count 
## 1:  1  3 
## 2:  2  3 
## 3:  3  3 
## 4:  4  3 
## 5:  5  2 
## 6:  6  1 
+0

+1,非常好的使用'roll =“nearest”'。我不认为'ds3'是必要的? - 'setkey(ds [,list(N = max(Pos)),keyby = ID],N)[J(1:6),roll =“nearest”]' – Arun

+1

想一下这个'roll ='例如,如果数据中不存在“6”,并且您从1:6执行连接,是不是(会给出“2”而不是NA或0)? – Arun

+0

@Arun你是对的!让我修改我的答案。 –

8

data.table听起来像是一个很好的解决方案。从这样的数据是有序的,人们可以找到与

maxPos = ds$Pos[!duplicated(ds$ID, fromLast=TRUE)] 

最高每辆火车然后制表即到达那个位置

nAtMax = tabulate(maxPos) 

,并在每个位置计算列车的累积和火车,计数从结尾

rev(cumsum(rev(nAtMax))) 
## [1] 3 3 3 3 2 1 

我认为这对大数据来说是相当快的,尽管不是完全有效的内存。

+0

+1我从问题和标题中得到了一个印象,那就是因为Meep解释说他的示例数据和任务被削减了,所以请求了一个'data.table'演示。 'rev(cumsum(rev(tabulate())))'按照要求完成确切的任务,但如果列车开始于不同的地点,观测的价值变得有趣,列车不再爆炸,或者也有卡车(2列ID)?这些是对data.table查询的简单更改(开关),而在基础中可能会出现一些头部划痕? –

+0

谢谢你的解决方案,这个解决方案比我的解决方案还要好得多! :)马特正确地建议数据可能更复杂,这就是为什么我接受了他的答案。如果你好奇,我正在研究的其实是DNA测序跟踪数据,与火车无关:) – Meep

相关问题