是否可以使用R data.table函数foverlaps来查找两个表中重叠范围的交集?

时间:2014-12-19 22:26:55

标签: r data.table

我想使用foverlaps查找两个床文件的交叉范围,并将包含重叠范围的任何行折叠成一行。在下面的例子中,我有两个基因组范围表。这些表被称为" bed"具有从零开始的坐标和染色体中特征的一个基础结束位置的文件。例如,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坐标不与下一行的起始坐标重叠。例如,染色体Y:1-15和染色体Y:10-50需要折叠到染色体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)
 )

5 个答案:

答案 0 :(得分:10)

@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")
)

现在创建一个新的床文件,其中包含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[,-c(1:2),with=F])
}


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

答案 1 :(得分:3)

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应该是一个不同的问题。它实际上是我所拥有的,所以也许我会问它并在我得到一个好答案时回到这里。

答案 2 :(得分:2)

如果您没有停留在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

答案 3 :(得分:2)

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

foverlaps()中,我们不必setkey()更大的数据集x - 这是一项相当昂贵的操作。但是y需要拥有它的关键字。对于您的情况,从此示例中可能table2更大= xtable1 = 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参数使用其他值intersectmult=

答案 4 :(得分:0)

这是一个完全基于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
}