重叠连接开始和结束位置

MrF*_*ick 36 merge join r data.table

考虑以下问题data.table.第一个定义了一组具有每个组'x'的起始位置和结束位置的区域:

library(data.table)

d1 <- data.table(x = letters[1:5], start = c(1,5,19,30, 7), end = c(3,11,22,39,25))
setkey(d1, x, start)

#    x start end
# 1: a     1   3
# 2: b     5  11
# 3: c    19  22
# 4: d    30  39
# 5: e     7  25
Run Code Online (Sandbox Code Playgroud)

第二个数据集具有相同的分组变量"x",并在每个组中定位"pos":

d2 <- data.table(x = letters[c(1,1,2,2,3:5)], pos = c(2,3,3,12,20,52,10))
setkey(d2, x, pos)

#    x pos
# 1: a   2
# 2: a   3
# 3: b   3
# 4: b  12
# 5: c  20
# 6: d  52
# 7: e  10
Run Code Online (Sandbox Code Playgroud)

最后,我想在'd2'中提取行,其中'pos'在每个组内的'start'和'end'定义的范围内x.期望的结果是

#    x pos start  end
# 1: a   2     1    3
# 2: a   3     1    3
# 3: c  20    19   22
# 4: e  10     7   25
Run Code Online (Sandbox Code Playgroud)

任何组的开始/结束位置x永远不会重叠,但可能存在不在任何区域中的值的间隙.

现在,我相信我应该使用滚动连接.据我所知,我不能在联接中使用"结束"列.

我试过了

d1[d2, roll = TRUE, nomatch = 0, mult = "all"][start <= end]
Run Code Online (Sandbox Code Playgroud)

得到了

#    x start end
# 1: a     2   3
# 2: a     3   3
# 3: c    20  22
# 4: e    10  25
Run Code Online (Sandbox Code Playgroud)

这是我想要的正确行集; 然而,"pos"已成为"开始"而原始的"开始"已经丢失.有没有办法用滚动连接保留所有列,所以我可以根据需要报告"开始","pos","结束"?

Aru*_*run 40

重叠连接是在data.table v1.9.3中使用commit 1375实现的,并且在当前稳定版本v1.9.4中可用.该函数被调用.来自新闻:foverlaps

29)Overlap joins #528现在在这里,终于!! 除了type="equal"maxgapminoverlap参数之外,其他一切都已实现.查看?foverlaps有关其用法的示例和示例.这是一个主要功能data.table.

让我们考虑x,一个区间定义为[a, b],where a <= b和y,另一个区间定义为[c, d],where c <= d.据说间隔y完全 x 重叠,iff d >= a c <= b 1.并且y完全包含 x,iff a <= c,d <= b 2中.对于实施的不同类型的重叠,请查看?foverlaps.

您的问题是重叠连接的特殊情况:d1您拥有真实的物理间隔startend位置.在d2另一方面,只有位置(pos),没有间隔.为了能够进行重叠连接,我们还需要创建间隔d2.这是通过创建一个pos2pos(d2[, pos2 := pos])相同的附加变量来实现的.因此,我们现在有一个间隔d2,尽管具有相同的起点终点坐标.d2然后可以使用此"虚拟零宽度间隔" foverlap来执行重叠连接d1:

require(data.table) ## 1.9.3
setkey(d1)
d2[, pos2 := pos]
foverlaps(d2, d1, by.x = names(d2), type = "within", mult = "all", nomatch = 0L)
#    x start end pos pos2
# 1: a     1   3   2    2
# 2: a     1   3   3    3
# 3: c    19  22  20   20
# 4: e     7  25  10   10
Run Code Online (Sandbox Code Playgroud)

by.y默认是key(y),所以我们跳过它.by.x默认情况下,key(x)如果它存在,则采取,如果不存在key(y).但是密钥不存在d2,我们无法设置列y,因为它们没有相同的名称.所以,我们by.x明确地设定.

类型重叠范围内,我们希望有所有比赛,只要有一个匹配.

注意:foverlaps使用data.table的二进制搜索功能(以及roll必要时),但是一些函数参数(重叠类型,maxgap,minoverlap等)受到findOverlaps()Bioconductor包中的功能的启发,这是IRanges一个很好的包(和所以GenomicRanges,它延伸IRanges到Genomics).


那有什么好处?

上面的代码基准数据导致foverlaps()比Gabor的答案慢(Timings:Gabor的data.table解决方案= 0.004 vs foverlaps = 0.021秒).但这种粒度真的很重要吗?

真正有趣的是从速度记忆两方面来看它的扩展程度.在Gabor的回答中,我们基于关键专栏加入x.然后过滤结果.

如果d1有大约40K行并且d2有100K行(或更多)?对于每一行d2匹配xd1,所有这些行都会被匹配和退换,只能稍后过滤.以下是您的Q缩放范围的示例:

生成数据:

require(data.table)
set.seed(1L)
n = 20e3L; k = 100e3L
idx1 = sample(100, n, TRUE)
idx2 = sample(100, n, TRUE)
d1 = data.table(x = sample(letters[1:5], n, TRUE), 
                start = pmin(idx1, idx2), 
                end = pmax(idx1, idx2))

d2 = data.table(x = sample(letters[1:15], k, TRUE), 
                pos1 = sample(60:150, k, TRUE))
Run Code Online (Sandbox Code Playgroud)

foverlaps:

system.time({
    setkey(d1)
    d2[, pos2 := pos1]
    ans1 = foverlaps(d2, d1, by.x=1:3, type="within", nomatch=0L)
})
# user  system elapsed 
#   3.028   0.635   3.745 
Run Code Online (Sandbox Code Playgroud)

这需要大约1GB的内存,其中ans1420MB.这里花费的大部分时间都在子集上.您可以通过设置参数来检查它verbose=TRUE.

Gabor的解决方案:

## new session - data.table solution
system.time({
    setkey(d1, x)
    ans2 <- d1[d2, allow.cartesian=TRUE, nomatch=0L][between(pos1, start, end)]
})
#   user  system elapsed 
# 15.714   4.424  20.324 
Run Code Online (Sandbox Code Playgroud)

这总共花了~3.5GB.

我刚才注意到Gabor已经提到了中间结果所需的内存.所以,试试sqldf:

# new session - sqldf solution
system.time(ans3 <- sqldf("select * from d1 join 
            d2 using (x) where pos1 between start and end"))
#   user  system elapsed 
# 73.955   1.605  77.049 
Run Code Online (Sandbox Code Playgroud)

总共~1.4GB.因此,它肯定比上面显示的内存使用更少的内存.

[ pos2ans1两个答案中删除并设置关键后,答案被证实是相同的.]

请注意,此重叠连接的设计存在一些问题,d2即不一定具有相同的起点和终点坐标(例如:基因组学,我来自的领域,d2通常在30-150万或更多行的位置).


foverlaps() 是稳定的,但仍在开发中,这意味着一些参数和名称可能会改变.

注意:自从我GenomicRanges上面提到,它也完全有能力解决这个问题.它使用引擎盖下的间隔树,并且具有很高的内存效率.在我的基因组数据基准测试中,foverlaps()速度更快.但那是另一篇(博客)帖子,其他时间.


G. *_*eck 21

1)sqldf这不是data.table,但复杂的连接标准很容易在SQL中以直接的方式指定:

library(sqldf)

sqldf("select * from d1 join d2 using (x) where pos between start and end")
Run Code Online (Sandbox Code Playgroud)

赠送:

  x start end pos
1 a     1   3   2
2 a     1   3   3
3 c    19  22  20
4 e     7  25  10
Run Code Online (Sandbox Code Playgroud)

2)data.table对于data.table答案,试试这个:

library(data.table)

setkey(d1, x)
setkey(d2, x)
d1[d2][between(pos, start, end)]
Run Code Online (Sandbox Code Playgroud)

赠送:

   x start end pos
1: a     1   3   2
2: a     1   3   3
3: c    19  22  20
4: e     7  25  10
Run Code Online (Sandbox Code Playgroud)

请注意,这确实具有形成d1[d2]SQL可能不会做的可能大的intermeidate结果的缺点.其余的解决方案也可能存在这个问题.

3)dplyr 这表明相应的dplyr解决方案.我们也使用betweendata.table:

library(dplyr)
library(data.table) # between

d1 %>% 
   inner_join(d2) %>% 
   filter(between(pos, start, end))
Run Code Online (Sandbox Code Playgroud)

赠送:

Joining by: "x"
  x start end pos
1 a     1   3   2
2 a     1   3   3
3 c    19  22  20
4 e     7  25  10
Run Code Online (Sandbox Code Playgroud)

4)merge/subset只使用R的基数:

subset(merge(d1, d2), start <= pos & pos <= end)
Run Code Online (Sandbox Code Playgroud)

赠送:

   x start end pos
1: a     1   3   2
2: a     1   3   3
3: c    19  22  20
4: e     7  25  10
Run Code Online (Sandbox Code Playgroud)

添加注意,此处的数据表解决方案比其他答案中的解决方案要快得多:

dt1 <- function() {
 d1 <- data.table(x=letters[1:5], start=c(1,5,19,30, 7), end=c(3,11,22,39,25))
 d2 <- data.table(x=letters[c(1,1,2,2,3:5)], pos=c(2,3,3,12,20,52,10))
 setkey(d1, x, start)
 idx1 = d1[d2, which=TRUE, roll=Inf] # last observation carried forwards

 setkey(d1, x, end)
 idx2 = d1[d2, which=TRUE, roll=-Inf] # next observation carried backwards

 idx = which(!is.na(idx1) & !is.na(idx2))
 ans1 <<- cbind(d1[idx1[idx]], d2[idx, list(pos)])
}

dt2 <- function() {
 d1 <- data.table(x=letters[1:5], start=c(1,5,19,30, 7), end=c(3,11,22,39,25))
 d2 <- data.table(x=letters[c(1,1,2,2,3:5)], pos=c(2,3,3,12,20,52,10))
 setkey(d1, x)
 ans2 <<- d1[d2][between(pos, start, end)]
}

all.equal(as.data.frame(ans1), as.data.frame(ans2))
## TRUE

benchmark(dt1(), dt2())[1:4]
##     test replications elapsed relative
##  1 dt1()          100    1.45    1.667  
##  2 dt2()          100    0.87    1.000  <-- from (2) above
Run Code Online (Sandbox Code Playgroud)


Aru*_*run 19

data.table v1.9.8+有一个新功能 - 非equi连接.有了这个,这个操作变得更加简单:

require(data.table) #v1.9.8+
# no need to set keys on `d1` or `d2`
d2[d1, .(x, pos=x.pos, start, end), on=.(x, pos>=start, pos<=end), nomatch=0L]
#    x pos start end
# 1: a   2     1   3
# 2: a   3     1   3
# 3: c  20    19  22
# 4: e  10     7  25
Run Code Online (Sandbox Code Playgroud)

  • 您介意为此方法添加内存使用和计时吗 (2认同)
  • 这里值得注意的是 x 指的是两件事。第一个 x 是列“x”,x.pos 中的第二个 x 指的是 d2。 (2认同)

Maë*_*aël 9

dplyr 1.1.0重叠连接可通过 函数获得join_by

使用join_by,您可以使用 进行重叠连接between,或者使用>=和手动进行重叠连接<=

library(dplyr)
inner_join(d2, d1, by = join_by(x, between(pos, start, end)))
#  x pos start end
#1 a   2     1   3
#2 a   3     1   3
#3 c  20    19  22
#4 e  10     7  25
Run Code Online (Sandbox Code Playgroud)
inner_join(d2, d1, by = join_by(x, pos >= start, pos <= end))
#  x pos start end
#1 a   2     1   3
#2 a   3     1   3
#3 c  20    19  22
#4 e  10     7  25
Run Code Online (Sandbox Code Playgroud)