【问题标题】:Optimising a calculation on every cumulative subset of a vector in R优化 R 中向量的每个累积子集的计算
【发布时间】:2018-02-27 18:24:02
【问题描述】:

我收集了不同长度的 DNA 测序读数,从最长到最短排序。我想知道我可以在一组中包含的最大读取数,以使该组的 N50 高于某个阈值t

对于任何给定的读取集,数据总量只是读取长度的累积总和。 N50 被定义为读取的长度,这样一半的数据包含在读取中,至少有那么长。

我在下面有一个解决方案,但对于非常大的读取集来说它很慢。我尝试对其进行矢量化处理,但速度较慢(可能是因为我的阈值通常相对较大,因此我在下面的解决方案很早就停止了计算)。

这是一个有效的例子:

df = data.frame(l = 100:1) # read lengths
df$cs = cumsum(df$l) # getting the cumulative sum is easy and quick

t = 95 # let's imagine that this is my threshold N50

for(i in 1:nrow(df)){
    N50 = df$l[min(which(df$cs>df$cs[i]/2))]
    if(N50 < t){ break }
}

# the loop will have gone one too far, so I subtract one
number.of.reads = as.integer(i-1)

这适用于小型数据集,但我的实际数据更像是 5m 读取,长度从 ~200,000 到 1 不等(更长的读取很少见),我对 100,000 的 N50 感兴趣,然后它变得漂亮慢。

这个例子更接近现实。在我的桌面上大约需要 15 秒。

l = ceiling(runif(100000, min = 0, max = 19999))
l = sort(l, decreasing = T)

df = data.frame(l = l)
df$cs = cumsum(df$l)

t = 18000

for(i in 1:nrow(df)){
    n = df$l[min(which(df$cs>df$cs[i]/2))]
    if(n < t){ break }
}

result = as.integer(i-1)

所以,我对任何可以显着优化此功能的想法、提示或技巧都很感兴趣。看起来这应该是可能的,但我没有想法。

【问题讨论】:

    标签: r loops optimization vectorization


    【解决方案1】:

    由于ni 递减,您应该使用binary search algorithm

    binSearch <- function(min, max) {
      print(mid <- floor(mean(c(min, max))))
      if (mid == min) {
        if (df$l[min(which(df$cs>df$cs[min]/2))] < t) {
          return(min - 1)
        } else {
          return(max - 1)
        }
      }
    
      n = df$l[min(which(df$cs>df$cs[mid]/2))]
      if (n >= t) {
        return(binSearch(mid, max))
      } else {
        return(binSearch(min, mid))
      }
    }
    

    然后,只需调用

    binSearch(1, nrow(df))
    

    【讨论】:

    • 啊,非常感谢。有趣的是,我对很久以前学习过这样的东西有一个模糊的记忆。但我不记得名字了,当然不可能写出这么简洁的版本。
    • 可以确认我已经实现了这一点,它给出的答案与我上面的代码完全相同,而且速度大大加快(如预期的那样)。
    【解决方案2】:

    由于您的数据是按 DNA/读取长度排序的,也许您可​​以避免测试每一行。相反,您可以在每次迭代时迭代和测试有限数量的行(间隔合理)(例如使用while()),从而逐渐接近您的解决方案。这应该会使事情变得更快。只要确保一旦接近解决方案,就停止迭代。

    这是你的解决方案

    set.seed(111)
    l = ceiling(runif(100000, min = 0, max = 19999))
    l = sort(l, decreasing = T)
    
    df = data.frame(l = l)
    df$cs = cumsum(df$l)
    
    t = 18000
    
    for(i in 1:nrow(df)){
      n = df$l[min(which(df$cs>df$cs[i]/2))]
      if(n < t){ break }
    }
    
    result = as.integer(i-1)
    result 
    # 21216, in ~29 seconds
    

    我们不是测试每一行,而是设置一个范围

    i1 <- 1
    i2 <- nrow(df)
    i.range <- as.integer(seq(i1, i2, length.out = 10))
    

    现在,只测试这 10 行。通过重新定义范围,获得最接近的并“聚焦”。当您无法增加粒度时停止。

    while(sum(duplicated(i.range))==0){
      for(i in 1:length(i.range)){
        N50 = df$l[min(which(df$cs>df$cs[i.range[i]]/2))]
        if(N50 < t){ break }
      }
    
      #update i1 and i2
      i1 <- i.range[(i-1)]
      i2 <- i.range[i]
      i.range <- as.integer(seq(i1, i2, length.out = 10))
    
    }
    
    i.range <- seq(i1, i2, by=1)
    for(i in i.range){
      N50 = df$l[min(which(df$cs>df$cs[i]/2))]
      if(N50 < t){ break }
    }
    result <- as.integer(i-1)
    result 
    #21216, in ~ 0.06 seconds
    
    Same result in a fraction of the time.
    

    【讨论】:

    • 我没有证据,但这看起来很像二进制搜索一样快,因为您将测试的行数减少到 1。
    猜你喜欢
    • 2021-08-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多