【问题标题】:Check if a value in one table (X) is between the values in two columns in another table (Y) with R data.table使用 R data.table 检查一个表 (X) 中的值是否介于另一表 (Y) 的两列中的值之间
【发布时间】:2014-07-22 02:40:27
【问题描述】:

可怕的标题问题,但这是我想要达到的目标。对于 Table1,我想添加“BETWEEN”列,验证“POSITION”是否介于 Table2 中相应“BIN”的“START”和“STOP”值之间。

表 1。 BIN 名称(字符)和 BIN 中的 POSITION(数字):

  BIN    POSITION
    1          12
    1          52
    1          86
    7           6
    7          22
    X         112
    X         139
   MT           3
   MT          26

Table2:BIN 名称(字符)和 START 和 STOP 位置(数字)

  BIN    START    STOP
    1        2      64
    1       90     110
    7       20     100
    7      105     200
    X        1       5
   MT        1    1000

以及想要的结果 - 表 1 带有“BETWEEN”:

CHROM    POSITION      BETWEEN
    1          12         TRUE
    1          52         TRUE
    1          86        FALSE
    7           6        FALSE
    7          22         TRUE
    X         112        FALSE
    X         139        FALSE
   MT           3         TRUE
   MT          26         TRUE

我的表 1 大约有 4,000,000 行,表 2 大约有 500,000 行,我想出的任何东西都很慢。

作为更大表的示例,请使用以下内容:

positions <- seq(1,100000,10)
bins <- c("A","B","C","D","E","F","G","H","I","J")

tab1 <- data.table(bin = rep(bins,1,each=length(positions)), pos = rep(positions,10))

tab2 <- data.table(bin = rep(bins,1,each=2000), start = seq(5,100000,50), stop = start+25)

期望的输出是:

tab1
        bin   pos    between
     1:   A     1    FALSE
     2:   A    11    TRUE
     3:   A    21    TRUE
     4:   A    31    FALSE
     5:   A    41    FALSE

【问题讨论】:

  • 如果您的 BIN 标识符不是 1 对 1 匹配,那么您的 Table1 和 Table2 的大小如何?您指出 Bin 1 - Position is 86 是 False。你能解释为什么它是假的吗?因为 Table2 中没有第三个 Chrom 1?为什么Table2第二行,Bin1 Start 90 Stop 110 不满足条件。
  • @Vlo 表 1 中的每一行都针对表 2 中所有匹配的 BIN 行进行测试,以生成一个输出行,如果表 2 的任何 START/STOP 值包含该表,则该输出行的 BETWEEN=TRUE 1 个职位。
  • @Pete 给定 BIN 的 START 和 STOP 对是否重叠?对于给定的 BIN,它是否按 START 排序?对于这两个问题,您的示例都是如此,但是如果总体而言,您可以使用二进制搜索来加快速度。另外,START 和 STOP 的最大值是多少?如果它们不是太大,则可以制作一个查找表....
  • Bin 1 - 位置 8 为 FALSE,因为表 2 中 Bin 1 的 START/STOP 边界是 2-64 和 90-110。 86 不在这些边界之间,所以它是 FALSE
  • @Spacedman BIN 中的 START 和 STOP 对不应重叠(如果重叠,它们会折叠成一行)。 BIN 1 是最大的,在表 2 中有大约 250,000,000 个位置和大约 350,000 个范围。

标签: r data.table


【解决方案1】:

以下方法要求对于给定的 bin,这些 bin 是互斥的。 (例如,您不能有边界为 1-5 的 bin A 和边界为 4-8 的另一个 bin A。)另外,我对您的示例进行了一些修改。

positions <- seq(1,100000,10)
bins <- c("A","B","C","D","E","F","G","H","I","J")
tab1 <- data.table(bin = rep(bins,1,each=length(positions)), pos = rep(positions,10))
setkey(tab1,"bin","pos")

tab2 <- data.table(bin = rep(bins,1,each=2000), start = seq(5,100000,50))
tab2[, end := start+25]

tab2[,pos:=start]
setkey(tab2,"bin","pos")
x<-tab2[tab1, roll=TRUE, nomatch=0]

tab2[,pos:=end]
setkey(tab2,"bin","pos")
y<-tab2[tab1, roll=-Inf, nomatch=0]

setkey(x,"bin","pos","start")
setkey(y,"bin","pos","start")
inBin<-x[y,nomatch=0]
inBin[, between:=TRUE]

setkey(tab1,"bin","pos")
setkey(inBin,"bin","pos")

result<-inBin[,list(bin,pos,between)][tab1]
result[is.na(between), between:=FALSE]

我现在没有时间深入解释我的解决方案。相反,我会采取便宜的方式,并建议您研究 data.table 的roll 参数。上面的基本方法是我加入 tab1 和 tab2,将 pos 向前滚动到最近的边界。然后我加入 tab1 和 tab2,将 pos 向后滚动到最近的起始边界。然后我对这两个集合进行内部连接,给我 tab1 中落在 bin 范围内的所有行。从那时起,这只是繁重的工作。

【讨论】:

  • 这工作完美,速度惊人!在我的示例数据中,系统时间是 0.06 秒!
  • 一个后续问题是可以修改您的示例以处理边界上的位置吗?例如,如果仓位是 5 或 30,起停是 5-30。您的解决方案将为 5 返回 TRUE,为 30 返回 FALSE(结果证明这是我需要的行为)。但是,如果您想真正做到 >=start 和 start 和
  • @Pete 我认为您可以更改 data.table 的 rollends 参数来处理边缘情况,或者您可以在应用上述方法之前简单地将右边界添加 1。很抱歉,我目前对滚动加入并不完全满意,但我计划在接下来的几周内做一些研究并在我的博客上发布一篇关于它们的文章gormanalysis.com
  • @Pete 我今天完成了关于滚动连接的文章gormanalysis.com/?p=176
【解决方案2】:

最直接的方法是嵌套我认为的匹配循环。您可能需要稍微不同地处理因素。如果找不到 bin 匹配项,我还没有测试过会发生什么。

BIN <- c("1","1","1","7","7","X","X","MT","MT")
POSITION <- c(12,52,86,6,22,112,139,3,26)
npos <- length(POSITION)
BETWEEN <- vector(mode="logical",length=npos)
tab1 <- as.data.frame(cbind(BIN,POSITION))

BIN2 <- c("1","1","7","7","X","MT")
START <- c(2,90,20,105,1,1)
STOP <- c(64,110,100,200,5,1000)
tab2 <- as.data.frame(cbind(BIN2,START,STOP))

bins <- unique(tab1$BIN)

for(bin in bins){
  #print(paste("bin=",bin))
  t1.bin.matches <- which(tab1$BIN==bin)
  t2.bin.compares <- which(tab2$BIN2==bin)
  #print(t1.bin.matches)
  #print(t2.bin.compares)
  for(match in t1.bin.matches){
    between = FALSE
    candidate = as.numeric(as.vector(tab1$POSITION)[match])
    for(compare in t2.bin.compares){
      comp.start <- as.numeric(as.vector(tab2$START)[compare])
      comp.stop <- as.numeric(as.vector(tab2$STOP)[compare])
      if(candidate>=comp.start&&candidate<=comp.stop){
        between = TRUE
        break
      }
    }
    #print(paste(comp.start,candidate,comp.stop,between))
    BETWEEN[match] = between
  }
}
tab1 <- as.data.frame(cbind(tab1,BETWEEN))
tab1

【讨论】:

  • 一旦找到第一个 between=TRUE 匹配项,您就可以退出内部循环,因为如果有多个匹配项,那么答案仍然是 TRUE。
  • 我上面提到了代码但没有实现,我会编辑添加一个break语句。
  • 感谢@tom.purucker 该解决方案给出了正确的结果,但它不适用于我的较大表。对于每个有 1000 行的表,它工作得很好,大约 13 秒,有 10,000 行虽然是 1100 秒,但有 100,000 行我在四个小时后杀死了它(我的真实数据集接近 400 万行!)。
【解决方案3】:

确保您的 BIN 列是字符,POSITION、START、END 是数字。

Table1$BIN = as.character(Table1$BIN)
Table1$POSITION = as.numeric(Table1$POSITION)
Table2$BIN = as.character(Table2$BIN)
Table2$START = as.numeric(Table2$START)
Table2$STOP = as.numeric(Table2$STOP)

将您的 data.frame 转换为 library(data.table),因为下面的代码可能很慢。

Table1 = as.data.table(Table1)
Table2 = as.data.table(Table2)

生成所需的输出

z = apply(Table1, 1, function(x) {nrow(Table2[(as.numeric(x[2])>START) & (as.numeric(x[2])<STOP) & (BIN == as.character(x[1])),])>0})
cbind(Table1, z)

旧函数是 z(),新函数是 y()。使用示例 Table1、Table2,新函数的速度提高了 30%。我不知道随着 nrow 的增加,这种优势将如何扩大,但我猜这种扩大将是非常积极的。告诉我。

z = function(a){apply(Table1, 1, function(x) {z = subset(Table2, Table2$BIN == as.character(x[1])) 
                                                  any(as.numeric(x[2])>z$START & as.numeric(x[2])<z$STOP)})}

y = function(a){apply(Table1, 1, function(x) {nrow(Table2[(as.numeric(x[2])>START) & (as.numeric(x[2])<STOP) & (BIN == as.character(x[1])),])>0})}


microbenchmark(z(), y(), times = 1000L)

 expr      min       lq   median       uq      max neval
  z() 1168.283 1219.793 1237.791 1276.267 3481.576  1000
  y()  809.575  848.052  863.257  885.909 1683.383  1000

编辑:您可能需要在子集中使用 as.numeric 和 as.character。之前创建的data.table丢了,直接用了上面答案的data.frame。

【讨论】:

  • 这在 10,000 行(大约 20 秒)时运行良好,但在 100,000 行时变得非常慢(如果它完成,我稍后会更新评论)。
  • 这可能是一个真正的问题。 aaply 可以在应用上使用,可能会提供一些小的速度提升。但我认为通过将any(as.numeric(x[2])&gt;z$START &amp; as.numeric(x[2])&lt;z$STOP)}) 移动/翻译到subset.data.table 步骤可以大大改进代码。让我知道这是否会加快代码速度。
  • 由于某种原因无法让aaply 工作。编辑上面的代码以在 data.table 中进行所有测试和子集化。如果这仍然非常缓慢,您可能应该联系作者data.table。他在这里发帖。
猜你喜欢
  • 2021-02-23
  • 1970-01-01
  • 1970-01-01
  • 2017-05-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-10-27
  • 2017-01-13
相关资源
最近更新 更多