【问题标题】:How to perform a "serial join" in data.table?如何在 data.table 中执行“串行连接”?
【发布时间】:2015-10-16 10:34:46
【问题描述】:

我有两个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() 内部的键值?

【问题讨论】:

    标签: r data.table


    【解决方案1】:

    IIUC,我将您的问题表述如下:对于每个dict$cat,我想得到prod(y) 对应于每个sample 对于那个cat,然后是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's samp 列时创建一个自动索引,以便从那时起使用基于 二分搜索 的子集.因此,矢量扫描无需担心性能问题。

    【讨论】:

    • 我不知道 %in% 是这样优化的。
    • this gist on auto indexing。在某个时候,我会把它包装在一个小插图中......
    【解决方案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 非常简单,因此这是真的。

    【讨论】:

    • foverlaps() 在这里有点过分了。喜欢更简单的解决方案。
    猜你喜欢
    • 2013-09-28
    • 2015-02-13
    • 2021-04-22
    • 2011-04-27
    • 1970-01-01
    • 2013-02-16
    • 2017-01-29
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多