重叠连接开始和结束位置

时间:2014-06-29 20:34:46

标签: r join merge 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

第二个数据集具有相同的分组变量&#39; x&#39;和位置&#39; pos&#39;在每个小组中:

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

最终,我想在&#39; d2&#39;中提取行。在哪里&#39; pos&#39;落在&#39; start&#39;定义的范围内?并且在每个组x内结束&#39;期望的结果是

#    x pos start  end
# 1: a   2     1    3
# 2: a   3     1    3
# 3: c  20    19   22
# 4: e  10     7   25

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

现在,我相信我应该使用滚动连接。据我所知,我不能使用&#34; end&#34;连接中的列。

我已经尝试了

d1[d2, roll = TRUE, nomatch = 0, mult = "all"][start <= end]

得到了

#    x start end
# 1: a     2   3
# 2: a     3   3
# 3: c    20  22
# 4: e    10  25

这是我想要的正确行集;然而&#34; pos&#34;已成为&#34;开始&#34;原来&#34;开始&#34;已经迷路了。有没有办法用滚动连接保留所有列,这样我就可以报告&#34;开始&#34;,&#34; pos&#34;,&#34; end&#34;根据需要?

4 个答案:

答案 0 :(得分:38)

Overlap joins已在commit 1375中与data.table v1.9.3一起实施,并在current stable release, v1.9.4中提供。该函数称为foverlaps。来自NEWS

  

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

我们考虑x,一个定义为[a, b]的区间,其中a <= b和y,另一个区间定义为[c, d],其中c <= dd >= a。如果c <= b a <= c,d <= b 1,则间隔y被称为重叠 x。并且y完全包含 x中,iff ?foverlaps 2。对于实施的不同类型的重叠,请查看d1

您的问题是重叠加入的一个特例:在start中,您的endd2位置具有真实的物理区间。另一方面,在pos中,只有位置(d2),而不是间隔。为了能够进行重叠连接,我们还需要在pos2中创建间隔。这是通过创建一个与posd2[, pos2 := pos])相同的其他变量d2来实现的。因此,我们现在在d2中有一个间隔,尽管具有相同的 start end 坐标。这个虚拟的零宽度间隔&#39;然后可以在foverlap中使用d1require(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 进行重叠加入:

by.y

key(y)默认为by.x,因此我们跳过它。默认情况下,key(x)key(y)(如果存在),如果不是d2。但y并不存在密钥,我们无法设置by.x的列,因为它们不具有相同的名称。因此,我们明确设置了foverlaps

重叠类型 中的,我们希望所有匹配,只有匹配时才匹配。

注意:roll使用data.table的二进制搜索功能(必要时还带有findOverlaps()),但有些函数参数(重叠类型,maxgap,minoverlap等)。 。)受到来自Bioconductor包IRanges的函数IRanges的启发,这是一个很好的包(GenomicRanges也是如此,它扩展了foverlaps()用于基因组学。


那么优势是什么?

以上代码对您的数据的基准测试导致x慢于Gabor的答案(时间:Gabor的data.table解决方案= 0.004 vs foverlaps = 0.021秒)。但这种粒度真的很重要吗?

真正有趣的是从速度内存两个方面来看它的扩展程度。在Gabor的回答中,我们根据关键列d1加入。 然后过滤结果。

如果d2有大约40K行且d2有100K行(或更多),该怎么办?对于x中匹配d1require(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)) 的每行所有这些行将匹配并返回,仅进行过滤后来。以下是您的Q缩放范围的示例:

生成数据:

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 

foverlaps:

ans1

总共占用了大约1GB的内存,其中verbose=TRUE为420MB。这里花费的大部分时间都在子集上。您可以通过设置参数## 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 来检查它。

Gabor的解决方案:

sqldf

这总共花了大约3.5GB。

我刚才注意到Gabor已经提到了中间结果所需的内存。所以,试试# 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

pos2

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

[从ans1移除d2并在两个答案上设置关键后,答案已经过验证相同。]

请注意,此重叠连接的设计存在d2不一定具有相同的起点和终点坐标的问题(例如:基因组学,我来自的字段,foverlaps()通常在哪里约30-150万或更多行)。


GenomicRanges是稳定的,但仍处于开发阶段,这意味着某些参数和名称可能会发生变化。

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

答案 1 :(得分:20)

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

library(sqldf)

sqldf("select * from d1 join d2 using (x) where pos between start and end")

,并提供:

  x start end pos
1 a     1   3   2
2 a     1   3   3
3 c    19  22  20
4 e     7  25  10

2)data.table 对于data.table答案,请尝试:

library(data.table)

setkey(d1, x)
setkey(d2, x)
d1[d2][between(pos, start, end)]

,并提供:

   x start end pos
1: a     1   3   2
2: a     1   3   3
3: c    19  22  20
4: e     7  25  10

请注意,这确实具有形成SQL可能不会执行的可能较大的intermeidate结果d1[d2]的缺点。剩下的解决方案也可能有这个问题。

3)dplyr 这表明相应的dplyr解决方案。我们还使用data.table中的between

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

d1 %>% 
   inner_join(d2) %>% 
   filter(between(pos, start, end))

,并提供:

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

4)合并/子集仅使用R的基础:

subset(merge(d1, d2), start <= pos & pos <= end)

,并提供:

   x start end pos
1: a     1   3   2
2: a     1   3   3
3: c    19  22  20
4: e     7  25  10

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

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

答案 2 :(得分:14)

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

答案 3 :(得分:1)

使用fuzzyjoin

result <- fuzzyjoin::fuzzy_inner_join(d1, d2, 
                           by = c('x', 'pos' = 'start', 'pos' = 'end'),
                           match_fun = list(`==`, `>=`, `<=`))
result

#  x.x     pos x.y   start   end
#  <chr> <dbl> <chr> <dbl> <dbl>
#1 a         2 a         1     3
#2 a         3 a         1     3
#3 c        20 c        19    22
#4 e        10 e         7    25

由于fuzzyjoin返回了所有列,我们可能需要进行一些清理以保留所需的列。

library(dplyr)
result %>% select(x = x.x, pos, start, end)

# A tibble: 4 x 4
#  x       pos start   end
#  <chr> <dbl> <dbl> <dbl>
#1 a         2     1     3
#2 a         3     1     3
#3 c        20    19    22
#4 e        10     7    25