2014-02-05 66 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的派生變量進行選擇,這證明超出了我的技能,無法使其正常工作按要求。我發佈了我目前的工作解決方案,所以如果您有任何進一步的想法,我會很高興聽到他們 –