2014-02-05 68 views
4

我有一个大的数据集和查找表。我需要为数据集中的每一行返回满足条件的查找中行的最小值。R多个条件加入使用data.table

鉴于我的数据集的大小,我不愿意通过交叉连接一起破解一个iffy解决方案,因为这会创建数百万条记录。我希望有人可以提出一个解决方案(理想情况下)利用base r或data.table,因为这些解决方案已经以有效的方式被使用。

A<-seq(1e4,9e4,1e4) 
B<-seq(0,1e4,1e3) 

dt1<-data.table(expand.grid(A,B),ID=1:nrow(expand.grid(A,B))) 
setnames(dt1, c("Var1","Var2"),c("A","B")) 

lookup<-data.table(minA=c(1e4,1e4,2e4,2e4,5e4), 
       maxA=c(2e4,3e4,7e4,6e4,9e4), 
       minB=rep(2e3,5), 
       Val=seq(.1,.5,.1)) 

# Sample Desired Value 
    A  B ID Val 
99: 90000 10000 99 0.5 

在SQL中,我会接着写沿着这将加入所有匹配的记录从lookupdt1并返回最小Val

SELECT ID, A, B, min(Val) as Val 
FROM dt1 
LEFT JOIN lookup on dt1.A>=lookup.minA 
       and dt1.A<=lookup.maxA 
       and dt1.B>=lookup.minB 
GROUP BY ID, A, B 

线的东西。

更新

我的解决方法到目前为止是这样的:

CJ.table<-function(X,Y) setkey(X[,c(k=1,.SD)],k)[Y[,c(k=1,.SD)],allow.cartesian=TRUE][,k:=NULL] 

dt1.lookup<- CJ.table(dt1,lookup)[A>=minA & A<=maxA & B>=minB, 
            list(Val=Val[which.min(Val)]), 
            by=list(ID,A,B)] 
dt1.lookup<-rbind.fill(dt1.lookup, dt1[!ID %in% dt1.lookup$ID]) 

这将检索所有的记录,并允许其他列从查找表中返回,如果我需要他们。它也有强制选择最小Val的好处。

+1

注意:使用'CJ'(在'data.table'包中实现)比'expand.grid'快。 – Arun

+0

@阿伦这是伟大的建议!非常感谢您 –

+0

请参阅我的编辑,对您现有的解决方案进行小调整。 –

回答

1

我发现没有交叉连接第一A解决方案需要通过摆脱行的准备数据,其中AB超出完全范围:

Prep = dt1[A >= min(lookup$minA) & A <= max(lookup$maxA) & B >= min(lookup$minB)] 

然后就使其中每个的数据的表条件满足对应于最低可能Val

0:

Indices = Prep[,list(min(which(A >= lookup$minA)), 
        min(which(A <= lookup$maxA)), 
        min(which(B >= lookup$minB)), A, B),by=ID] 

然后,你必须在所有三个条件都满足的最低点得到

Indices[,list(Val=lookup$Val[max(V1,V2,V3)], A, B),by=ID] 

看是否有此得到你,你找什么:

ID Val  A  B 
1: 19 0.1 10000 2000 
2: 20 0.1 20000 2000 
3: 21 0.2 30000 2000 
4: 22 0.3 40000 2000 
5: 23 0.3 50000 2000 
6: 24 0.3 60000 2000 
7: 25 0.3 70000 2000 
8: 26 0.5 80000 2000 
9: 27 0.5 90000 2000 
10: 28 0.1 10000 3000 
+0

好主意!将我的头围绕它并回复给你 –

+0

感谢这个@Senor,不幸的是,这看起来不能保证当多于一个查找记录匹配时选择最小值Val。在这种情况下,交叉连接对性能不利,但是我采用了您的解决方案,并根据另一个SO答案对其进行了一些更改:cross join http://stackoverflow.com/questions/10600060/how-to-do-cross-join -in -r/14165493#14165493 –

1

我首先想到的是设法使索引像塞纳Ø一样。然而,min(Val)使我的指数表更难以考虑。我认为这样做的方式是循环查找表。

dt1[,Val:=as.numeric(NA)] 
for (row in 1:NROW(lookup)) { 
    dt1[A>=lookup[order(Val)][row,minA]&A<=lookup[order(Val)][row,maxA]&B>=lookup[order(Val)][row,minB]&is.na(Val),Val:=lookup[order(Val)][row,Val]] 
    } 

我认为这应该工作,因为它首先将新列NA值。

然后它按Val的顺序放置查找表,以便获得最低值。

在每一个循环,如果他们仍然在 NAVal只会潜在的变化值 dt1因为我们正在通过 lookup循环,以最小的 Val到最大,将确保你得到你想要的 min(Val)

rbindlist(list(dt1.lookup,dt1[!ID %in% dt1.lookup[,ID]][,list(ID, A, B, Val=as.numeric(NA))])) 

更换rbind.fill线将消除对reshape包依赖,我认为这将是更快。

+2

我不确定循环概念对于我的800k记录是否可行 - 当然要看看 –

+1

我做了一些快速的'system.time'测试,Senor的速度快了3倍将'dt1'增加到900900行,但两者都在1秒以内。当Senor的答案出现时(至少在我的屏幕上),我写了一半的内容,所以我感觉完成了。 –

+1

谢谢Dean,不幸的是,结果我的查找表超过了300条记录,结果耗时太长,而且在我的实际任务中,我需要根据来自Val的派生变量进行选择,这证明超出了我的技能,无法使其正常工作按要求。我发布了我目前的工作解决方案,所以如果您有任何进一步的想法,我会很高兴听到他们 –