2015-10-16 23 views
4

我有兩個data.tables:一個實驗數據表x和一個類別查找表dict如何在data.table中執行「串行連接」?

library(data.table) 
set.seed(123) 

x = data.table(samp=c(1,1,2,3,3,3,4,5,5,5,6,7,7,7,8,9,9,10,10), y=rnorm(19)) 
x 

    samp y 
#1: 1 -0.56047565 
#2: 1 -0.23017749 
#3: 2 1.55870831 
#4: 3 0.07050839 
#5: 3 0.12928774 
#6: 3 1.71506499 
#7: 4 0.46091621 
#8: 5 -1.26506123 
#9: 5 -0.68685285 
#10: 5 -0.44566197 
#11: 6 1.22408180 
#12: 7 0.35981383 
#13: 7 0.40077145 
#14: 7 0.11068272 
#15: 8 -0.55584113 
#16: 9 1.78691314 
#17: 9 0.49785048 
#18: 10 -1.96661716 
#19: 10 0.70135590 

dict = data.table(samp=c(1:5, 4:8, 7:10), cat=c(rep(1,length(1:5)), rep(2,length(4:8)), rep(3,length(7:10)))) 

dict 
#  samp cat 
# 1: 1 1 
# 2: 2 1 
# 3: 3 1 
# 4: 4 1 
# 5: 5 1 
# 6: 4 2 
# 7: 5 2 
# 8: 6 2 
# 9: 7 2 
# 10: 8 2 
# 11: 7 3 
# 12: 8 3 
# 13: 9 3 
# 14: 10 3 

對於每個samp,我需要先計算所有與之相關的y的產物。然後我需要計算在dict$cat中指定的每個樣品類別的這些產品的總和。請注意,每個samp都映射到多個dict$cat。這樣做的

一種方法是合併xdict向右走,讓行重複(allow.cartesian=T):但是

setkey(dict, samp) 
setkey(x, samp) 
step0 = dict[x, allow.cartesian=T] 
setkey(step0, samp, cat) 
step1 = step0[, list(prodY=prod(y)[1], cat=cat[1]), by=c("samp", "cat")] 
resMet1 = step1[, sum(prodY), by="cat"] 

不知是否可以避免這種連接步驟。這有幾個原因 - 例如,如果x是巨大的,重複將使用額外的內存(我是嗎?)。而且,這些具有重複行的彙總表也相當混亂,從而使分析更容易出錯。

所以相反,我想在每個dict$cat中使用樣本進行x中的二分搜索。我知道如何做到這一點的一個類別,這樣做是爲了所有的人的醜陋的方式將與一個循環:

setkey(x, samp) 
setkey(dict,samp) 

pool = vector("list") 
for(n in unique(dict$cat)){ 
    thisCat = x[J(dict[cat==n])] 
    setkey(thisCat, samp) 
    step1 = thisCat[, list(prodY=prod(y)[1], cat=cat[1]), by="samp"] 
    pool[[n]] = step1[, sum(prodY), by="cat"]   
} 
resMet2 = rbindlist(pool) 

但是,當然,這樣的循環是要避免的。所以我想知道是否有任何方法可以讓data.table遍歷J()中的關鍵值?

回答

2

IIUC,我會制定了你的問題如下:每個dict$cat,我想獲得prod(y)對應於每個samplecat,然後sum他們都放棄了。

讓一步,現在建造這一步:

  1. 對於每個dict$cat - 聽起來像是你需要按cat

    dict[, ,by=cat] 
    

    所有剩下的就是正確填寫j

  2. 你需要從x獲得prod(y)每個樣品這組

    x[samp %in% .SD$samp, prod(y), by=samp] 
    

    x提取對應於那些行這個羣體的samp(使用.SD它代表數據的子集),並計算prod(y),分組爲samp。大!

  3. 我們仍然需要總結它們。

    sum(x[samp %in% .SD$samp, prod(y), by=samp]$V1) 
    
  4. 我們已經有了完整的j表達式。讓我們插上這一切:

    dict[, sum(x[samp %in% .SD$samp, prod(y), by=samp]$V1), by=cat] 
    # cat   V1 
    # 1: 1 1.7770272 
    # 2: 2 0.7578771 
    # 3: 3 -1.0295633 
    

希望這有助於。


注1:有年代prod(y)這裏一些冗餘計算,但好處是,我們沒有實現太多的中間數據。所以它的內存效率。如果你已經太多組,這可能會比較慢..你可能要計算prod(y)另一個變量,像這樣:

x_p = x[, .(p = prod(y)), by=samp] 

有了這個,我們可以簡化j如下:

dict[, x_p[samp %in% .SD$samp, sum(p)], by=cat] 

注2:表示%in%表達式在第一次運行時創建一個自動索引x'ssamp列使用二進制搜索基於此的子集。因此,由於矢量掃描,不需要擔心性能。

+0

很酷,非常感謝! – msp

+0

我不知道%的百分比是以這種方式優化的。 – msp

+1

請參閱[此自動索引的要點](https://gist.github.com/arunsrinivasan/dacb9d1cac301de8d9ff)。在某個時候,我會把它包裝成一個小插曲。 – Arun

2

你可能會崩潰xsamp第一級。

xprod = x[, .(py = prod(y)), by=samp] 

合併

res2 <- xprod[dict, on = "samp"][, sum(py), by=cat] 

identical(res2, resMet2) # test passed 

或子

如果sampxprod行號(在這裏),你可以子集,而不是合併:

res3 <- xprod[(dict$samp), sum(py), by=.(cat=dict$cat)] 

identical(res3, resMet2) # test passed 

重新標記樣本ID非常簡單,因此確實如此。

+1

'foverlaps()'會在這裏過度殺傷。像更簡單的解決方案。 – Arun