如何执行"串行连接"在data.table?

时间:2015-10-16 10:34:46

标签: r data.table

我有两个data.tables:一个实验数据表x和一个类别查找表dict

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 个答案:

答案 0 :(得分:2)

您最好先将x折叠到samp级别。

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 :(得分:2)

IIUC,我会按照以下方式制定您的问题:为每个dict$cat,我希望prod(y)sample对应cat },然后sum他们全部。

让我们一步一步地构建这个:

  1. 对于每个dict$cat - 听起来需要按cat分组:

    dict[, ,by=cat]
    

    剩下的就是正确填写j

  2. 对于此群组的每个样本,您需要从prod(y)获取x

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

    从对应此论坛 x的{​​{1}}中提取这些行(使用代表数据子集的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
    
  5. 希望这有帮助。

    注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's samp列的第一次运行时创建自动索引,以使用二进制搜索从那时起基于子集。因此,由于矢量扫描,不需要担心性能。