【问题标题】:Find the intersection of overlapping ranges in two tables using data.table function foverlaps使用 data.table 函数 foverlaps 查找两个表中重叠范围的交集
【发布时间】:2015-02-18 21:42:40
【问题描述】:

我想使用 foverlaps 来查找两个床文件的相交范围,并将包含重叠范围的任何行折叠成一行。在下面的示例中,我有两个包含基因组范围的表。这些表格被称为“床”文件,它们在染色体中具有从零开始的坐标和从一开始的特征结束位置。例如,START=9、STOP=20 被解释为跨越基数 10 到 20,包括 10 到 20。这些床文件可以包含数百万行。无论提供要相交的两个文件的顺序如何,该解决方案都需要给出相同的结果。

第一个表

> table1
   CHROMOSOME START STOP
1:          1     1   10
2:          1    20   50
3:          1    70  130
4:          X     1   20
5:          Y     5  200

第二张桌子

> table2
   CHROMOSOME START STOP
1:          1     5   12
2:          1    15   55
3:          1    60   65
4:          1   100  110
5:          1   130  131
6:          X    60   80
7:          Y     1   15
8:          Y    10   50

我在想新的 foverlaps 函数可能是一种非常快速的方法,可以在这两个表中找到相交范围以生成如下所示的表:

结果表:

> resultTable
   CHROMOSOME START STOP
1:          1     5   10
2:          1    20   50
3:          1   100  110
4:          Y     5   50  

这可能吗,或者在 data.table 中有更好的方法吗?

我还想首先确认在一个表中,对于任何给定的 CHROMOSOME,STOP 坐标不与下一行的起始坐标重叠。例如,CHROMOSOME Y:1-15 和 CHROMOSOME Y:10-50 需要折叠为 CHROMOSOME Y:1-50(参见第二个表第 7 行和第 8 行)。这不应该是这种情况,但该功能可能应该检查这一点。下面是如何折叠潜在重叠的真实示例:

   CHROM  START   STOP
1:     1 721281 721619
2:     1 721430 721906
3:     1 721751 722042

期望的输出:

   CHROM  START   STOP
1:     1 721281 722042

创建示例表的功能如下:

table1 <- data.table(
   CHROMOSOME = as.character(c("1","1","1","X","Y")) ,
   START = c(1,20,70,1,5) ,
   STOP = c(10,50,130,20,200)
)

table2 <- data.table(
   CHROMOSOME = as.character(c("1","1","1","1","1","X","Y","Y")) ,
   START = c(5,15,60,100,130,60,1,10) ,
   STOP = c(12,55,65,110,131,80,15,50)
 )

【问题讨论】:

  • @Arun 真实数据集在表 1 中有约 400 万行,在表 2 中有约 23 万行。

标签: r data.table bioinformatics


【解决方案1】:

@Seth 使用 data.table foverlaps 函数提供了解决交叉重叠问题的最快方法。然而,这个解决方案没有考虑到输入的床文件可能有重叠的范围,需要减少到单个区域。 @Martin Morgan 通过使用 GenomicRanges 包的解决方案解决了这个问题,该解决方案同时进行了相交和范围缩小。但是,Martin 的解决方案没有使用 foverlaps 函数。 @Arun 指出,表中不同行中的重叠范围目前无法使用 foverlaps。感谢提供的答案,以及对 stackoverflow 的一些额外研究,我想出了这个混合解决方案。

在每个文件中创建没有重叠区域的示例 BED 文件。

chr <- c(1:22,"X","Y","MT")

#bedA contains 5 million rows
bedA <- data.table(
    CHROM = as.vector(sapply(chr, function(x) rep(x,200000))),
    START = rep(as.integer(seq(1,200000000,1000)),25),
    STOP = rep(as.integer(seq(500,200000000,1000)),25),
    key = c("CHROM","START","STOP")
    )

#bedB contains 500 thousand rows
bedB <- data.table(
  CHROM = as.vector(sapply(chr, function(x) rep(x,20000))),
  START = rep(as.integer(seq(200,200000000,10000)),25),
  STOP = rep(as.integer(seq(600,200000000,10000)),25),
  key = c("CHROM","START","STOP")
)

现在创建一个新的bed文件,其中包含bedA和bedB中的相交区域。

#This solution uses foverlaps
system.time(tmpA <- intersectBedFiles.foverlaps(bedA,bedB))

user  system elapsed 
1.25    0.02    1.37 

#This solution uses GenomicRanges
system.time(tmpB <- intersectBedFiles.GR(bedA,bedB))

user  system elapsed 
12.95    0.06   13.04 

identical(tmpA,tmpB)
[1] TRUE

现在,修改 bedA 和 bedB 使其包含重叠区域:

#Create overlapping ranges
makeOverlaps <-  as.integer(c(0,0,600,0,0,0,600,0,0,0))
bedC <- bedA[, STOP := STOP + makeOverlaps, by=CHROM]
bedD <- bedB[, STOP := STOP + makeOverlaps, by=CHROM]

使用 foverlaps 或 GenomicRanges 函数测试具有重叠范围的床文件相交的时间。

#This solution uses foverlaps to find the intersection and then run GenomicRanges on the result
system.time(tmpC <- intersectBedFiles.foverlaps(bedC,bedD))

user  system elapsed 
1.83    0.05    1.89 

#This solution uses GenomicRanges
system.time(tmpD <- intersectBedFiles.GR(bedC,bedD))

user  system elapsed 
12.95    0.04   12.99 

identical(tmpC,tmpD)
[1] TRUE

获胜者:foverlaps!

使用的功能

这是基于 foverlaps 的函数,只有在存在重叠范围(使用 rowShift 函数检查)时才会调用 GenomicRanges 函数 (reduceBed.GenomicRanges)。

intersectBedFiles.foverlaps <- function(bed1,bed2) {
  require(data.table)
  bedKey <- c("CHROM","START","STOP")
  if(nrow(bed1)>nrow(bed2)) {
    bed <- foverlaps(bed1, bed2, nomatch = 0)
  } else {
    bed <- foverlaps(bed2, bed1, nomatch = 0)
  }
  bed[, START := pmax(START, i.START)]
  bed[, STOP := pmin(STOP, i.STOP)]
  bed[, `:=`(i.START = NULL, i.STOP = NULL)]
  if(!identical(key(bed),bedKey)) setkeyv(bed,bedKey)
  if(any(bed[, STOP+1 >= rowShift(START), by=CHROM][,V1], na.rm = T)) {
    bed <- reduceBed.GenomicRanges(bed)
  }
  return(bed)
}

rowShift <- function(x, shiftLen = 1L) {
  #Note this function was described in this thread:
  #http://stackoverflow.com/questions/14689424/use-a-value-from-the-previous-row-in-an-r-data-table-calculation
  r <- (1L + shiftLen):(length(x) + shiftLen)
  r[r<1] <- NA
  return(x[r])
}

reduceBed.GenomicRanges <- function(bed) {
  setnames(bed,colnames(bed),bedKey)
  if(!identical(key(bed),bedKey)) setkeyv(bed,bedKey)
  grBed <- makeGRangesFromDataFrame(bed,
    seqnames.field = "CHROM",start.field="START",end.field="STOP")
  grBed <- reduce(grBed)
  grBed <- data.table(
    CHROM=as.character(seqnames(grBed)),
    START=start(grBed),
    STOP=end(grBed),
    key = c("CHROM","START","STOP"))
  return(grBed)
}

此函数严格使用 GenomicRanges 包,产生相同的结果,但比 foverlaps 函数慢约 10 倍。

intersectBedFiles.GR <- function(bed1,bed2) {
  require(data.table)
  require(GenomicRanges)
  bed1 <- makeGRangesFromDataFrame(bed1,
    seqnames.field = "CHROM",start.field="START",end.field="STOP")
  bed2 <- makeGRangesFromDataFrame(bed2,
    seqnames.field = "CHROM",start.field="START",end.field="STOP")
  grMerge <- suppressWarnings(intersect(bed1,bed2))
  resultTable <- data.table(
    CHROM=as.character(seqnames(grMerge)),
    START=start(grMerge),
    STOP=end(grMerge),
    key = c("CHROM","START","STOP"))
  return(resultTable)
}

使用 IRanges 的额外比较

我找到了一种使用 IRanges 折叠重叠区域的解决方案,但它比 GenomicRanges 慢 10 倍以上。

reduceBed.IRanges <- function(bed) {
  bed.tmp <- bed
  bed.tmp[,group := { 
      ir <-  IRanges(START, STOP);
      subjectHits(findOverlaps(ir, reduce(ir)))
    }, by=CHROM]
  bed.tmp <- bed.tmp[, list(CHROM=unique(CHROM), 
              START=min(START), 
              STOP=max(STOP)),
       by=list(group,CHROM)]
  setkeyv(bed.tmp,bedKey)
  bed[,group := NULL]
  return(bed.tmp[, -(1:2)])
}


system.time(bedC.reduced <- reduceBed.GenomicRanges(bedC))

user  system elapsed 
10.86    0.01   10.89 

system.time(bedD.reduced <- reduceBed.IRanges(bedC))

user  system elapsed 
137.12    0.14  137.58 

identical(bedC.reduced,bedD.reduced)
[1] TRUE

【讨论】:

    【解决方案2】:

    foverlaps() 会很好。

    首先设置两个表的键:

    setkey(table1, CHROMOSOME, START, STOP)
    setkey(table2, CHROMOSOME, START, STOP)
    

    现在使用foverlaps() 和nomatch = 0 加入它们以删除table2 中不匹配的行。

    resultTable <- foverlaps(table1, table2, nomatch = 0)
    

    接下来为 START 和 STOP 选择适当的值,并删除多余的列。

    resultTable[, START := pmax(START, i.START)]
    resultTable[, STOP := pmin(STOP, i.STOP)]
    resultTable[, `:=`(i.START = NULL, i.STOP = NULL)]
    

    与未来 START 重叠的 STOP 应该是一个不同的问题。它实际上是我的一个,所以也许我会问它,当我有一个好的答案时会回到这里。

    【讨论】:

    • 它需要一个额外的聚合步骤来获得相交的范围。
    • @Arun 是的,需要聚合步骤。如果你得到答案,请告诉我
    • 我更新了问题以规定需要合并重叠范围。
    【解决方案3】:

    如果您没有卡在 data.table 解决方案上,GenomicRanges

    source("http://bioconductor.org/biocLite.R")
    biocLite("GenomicRanges")
    

    给予

    > library(GenomicRanges)
    > intersect(makeGRangesFromDataFrame(table1), makeGRangesFromDataFrame(table2))
    GRanges object with 5 ranges and 0 metadata columns:
          seqnames     ranges strand
             <Rle>  <IRanges>  <Rle>
      [1]        1 [  5,  10]      *
      [2]        1 [ 20,  50]      *
      [3]        1 [100, 110]      *
      [4]        1 [130, 130]      *
      [5]        Y [  5,  50]      *
      -------
      seqinfo: 3 sequences from an unspecified genome; no seqlengths
    

    【讨论】:

    • 所以这完全根据需要进行交叉并合并重叠范围。将输出转换回 data.table 需要一些额外的步骤。对于大床文件,虽然运行大约需要 8 秒,但 @Arun 建议的 foverlaps 回答大约需要 0.5 秒,但它不会折叠重叠。
    • 最近在“开发”版本中进行了改进,可提高性能并减少内存消耗。许多基因组学操作在 GenomicRanges 和朋友中是很自然的,例如 rtracklayer::import("your.bed") 所以也许你会想逗留......
    【解决方案4】:

    在基因组学中的大多数重叠范围问题中,我们有一个大型数据集x(通常是测序读取)和另一个较小的数据集y(通常是基因模型、外显子、内含子等)。我们的任务是找出x 中的哪些区间与y 中的哪些区间重叠,或者x 中有多少区间与每个y 区间重叠。

    在foverlaps() 中,我们不必在较大的数据集x 上进行setkey() - 这是一项相当昂贵的操作。但是y 需要设置它的密钥。对于您的情况,从这个例子来看,table2 似乎更大 = x,table1 = y。

    require(data.table)
    setkey(table1) # key columns = chr, start, end
    ans = foverlaps(table2, table1, type="any", nomatch=0L)
    ans[, `:=`(i.START = pmax(START, i.START), 
               i.STOP = pmin(STOP, i.STOP))]
    
    ans = ans[, .(i.START[1L], i.STOP[.N]), by=.(CHROMOSOME, START, STOP)]
    #    CHROMOSOME START STOP  V1  V2
    # 1:          1     1   10   5  10
    # 2:          1    20   50  20  50
    # 3:          1    70  130 100 130
    # 4:          Y     5  200   5  50
    

    但我同意能够一步完成这项工作会很棒。不知道如何,但可能使用附加值 reduce 和 intersect 为 mult= 参数。

    【讨论】:

    • 我不熟悉“.”的使用。在列表前面(例如“.(CHROMOSOME, START, STOP)”) - 它的目的是什么。该函数似乎确实聚合了 CHROMOSOME Y 上的重叠区域。但是,如果我交换 table1 和 table2,我会得到不同的答案。我希望它能够以任何一种方式工作 - 任何一张桌子都可以更长。
    • @Pete, foverlaps() 旨在通过重叠范围查找/合并。您所要求的虽然可以使用foverlaps() 获得,但并不简单(还)。我们必须弄清楚如何最好地做到这一点——要么在foverlaps() 中提供功能,以便在多个重叠的情况下获得相交范围,要么提供像 GenomicRanges 那样的另一个功能。我还没有考虑太多,而且我很快就无法工作了。
    • @Pete - 同样好奇,我搜索了 data.table-package 并间接了解到'.() 是 list()' 的别名。这可能值得一两行,阿伦......
    • @arun 有关是否有办法在 data.table 中本地减少间隔的任何更新?我想出了一个解决方案,但它似乎没有那么有效(见下面的答案)
    【解决方案5】:

    根据 Pete 的回答,这是一个完全在 data.table 中的解决方案。它实际上比他使用 GenomicRanges 和 data.table 的解决方案慢,但仍然比仅使用 GenomicRanges 的解决方案快。

    intersectBedFiles.foverlaps2 <- function(bed1,bed2) {
      require(data.table)
      bedKey <- c("CHROM","START","STOP")
      if(nrow(bed1)>nrow(bed2)) {
        if(!identical(key(bed2),bedKey)) setkeyv(bed2,bedKey)
        bed <- foverlaps(bed1, bed2, nomatch = 0)
      } else {
        if(!identical(key(bed1),bedKey)) setkeyv(bed1,bedKey)
        bed <- foverlaps(bed2, bed1, nomatch = 0)
      }
      bed[,row_id:=1:nrow(bed)]
      bed[, START := pmax(START, i.START)]
      bed[, STOP := pmin(STOP, i.STOP)]
      bed[, `:=`(i.START = NULL, i.STOP = NULL)]
    
      setkeyv(bed,bedKey)
      temp <- foverlaps(bed,bed)
    
      temp[, `:=`(c("START","STOP"),list(min(START,i.START),max(STOP,i.STOP))),by=row_id]
      temp[, `:=`(c("START","STOP"),list(min(START,i.START),max(STOP,i.STOP))),by=i.row_id]
      out <- unique(temp[,.(CHROM,START,STOP)])
      setkeyv(out,bedKey)
      out
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-07-04
      相关资源
      最近更新 更多