【问题标题】:R - Nested for loops and slow performanceR - 嵌套 for 循环和缓慢的性能
【发布时间】:2016-03-23 23:10:21
【问题描述】:

我正在尝试实现一个函数来根据另一个表从一个表中获取值。实际的数据帧有 > 50,000 个观察值,所以实现这个嵌套的 for 循环是无效的。在过去的几天里,我一直试图通过 SO 来寻找一些有用的东西,但一直没能找到。我的数据没有特定的顺序(个人、细分等),因此即使出现问题,它也需要能够工作。

以下是我可以使用的数据的玩具示例:

region_map <- data.frame(Start = c(721290, 1688193), End= c(1688192, 2926555))
individual <- c("Ind1","Ind2","Ind3","Ind4")
segment <- data.frame(SampleID = c("Ind1","Ind1","Ind2","Ind2","Ind3","Ind3","Ind4","Ind4","Ind4"),
                      Start = c(721290, 1688194, 721290, 1688200, 721290, 2926600, 721290, 1688193, 690),
                      End = c(1688192, 2926555,1688190, 2900000, 2926555, 3000000, 1500000, 2005000, 500000),
                      State = c(1,2,2,5,4,2,2,6,5))

这是我正在尝试做的一个简化示例:

Generate.FullSegmentList <- function(segments, individuals, regionmap){
     FullSegments <- data.frame()
     for(region in 1:nrow(regionmap)){

          for(ind in individuals){
               # If there is not a segment within that region for that individual
               if(nrow(
                    segments[segments$start >= regionmap$Start[region] & 
                                  segments$End <= regionmap$End[region] &
                                  segments$SampleID == ind , ]
               ) == 0){
                    Temp <- data.frame(SampleID = ind, 
                                       Start = regionmap$Start[region],
                                       End = regionmap$End[region],
                                       State = 3
                    )
               }
               # If there is a segment within that region for that individual
               if(nrow(
                    segments[segments$Start >= regionmap$Start[region] & 
                                  segments$End <= regionmap$End[region] &
                                  segments$SampleID == ind , ]
               ) == 1){
                    Temp <- data.frame(SampleID = segments$SampleID, 
                                       Start = regionmap$Start[region],
                                       End = regionmap$End[region],
                                       State = segments$State[segments$Start >= regionmap$Start[region] & 
                                                                  segments$SampleID == ind ]
                    )
               }
               FullSegments <- list(FullSegments, Temp)              
          }
     }
     FullSegments
}

换句话说,我需要查看每个区域 (~53,000) 并为每个 individual 的区域分配一个值(State,如果不存在,则赋予值 3),然后创建一个新数据.frame 与每个地区的每个人。为此,我遍历区域和个体,找到与区域重叠的 segment(其中有大约 25,000 个),然后将其附加到表中。

以下是上述玩具数据的输出结果:

SampleID       Start       End        State
Ind1          721290      1688192      1
Ind1          1688193     2926555      2
Ind2          721290      1688192      2
Ind2          1688193     2926555      5
Ind3          721290      1688192      4
Ind3          1688193     2926555      4
Ind4          721290      1688192      2
Ind4          1688193     2926555      6

这个函数完全按照我的需要工作,除了它需要很长时间才能运行(使用 system.time,我知道它需要 3 个多月的时间才能运行)。我知道必须有更好的方法来做到这一点。我已经尝试实现应用函数,并且在其他一些问题中看到使用列表而不是 data.frame。我还看到有 data.table 和 plyr 选项可以简化这一点。我已经尝试过这些方法,但未能成功使其与带有 if 语句的嵌套循环一起工作。

我会很感激任何答案的解释,因为这是我第一次写这么复杂的东西。

我认为相关的问题:

关于嵌套 for 循环的许多其他问题涉及执行适用于执行应用函数的计算(例如 apply(df, 1, function(x){ mean(x) }),但我无法采用它来将值从 data.frame 映射到 data.frame。

【问题讨论】:

    标签: r


    【解决方案1】:

    Bioconductor 包IRanges 适用于“整数范围”,例如区域和段的开始和结束坐标。安装包

    source("https://bioconductor.org/biocLite.R")
    biocLite("IRanges")
    

    加载它并创建感兴趣范围的表示

    library(IRanges)
    r <- with(region_map, IRanges(Start, End))
    s <- with(segments, IRanges(Start, End))
    

    目前的结果是

    > r
    IRanges object with 2 ranges and 0 metadata columns:
              start       end     width
          <integer> <integer> <integer>
      [1]    721290   1688192    966903
      [2]   1688193   2926555   1238363
    > s
    IRanges object with 9 ranges and 0 metadata columns:
              start       end     width
          <integer> <integer> <integer>
      [1]    721290   1688193    966904
      [2]   1688194   2926555   1238362
      [3]    721290   1688190    966901
      [4]   1688200   2900000   1211801
      [5]    721290   2926555   2205266
      [6]   2926600   3000000     73401
      [7]    721290   1500000    778711
      [8]   1688193   2005000    316808
      [9]       690    500000    499311
    

    您有兴趣找到“查询”片段和“主题”区域地图之间的重叠

    olaps <- findOverlaps(s, r)
    

    给予

    > olaps
    Hits object with 9 hits and 0 metadata columns:
          queryHits subjectHits
          <integer>   <integer>
      [1]         1           1
      [2]         1           2
      [3]         2           2
      [4]         3           1
      [5]         4           2
      [6]         5           1
      [7]         5           2
      [8]         7           1
      [9]         8           2
      -------
      queryLength: 9 / subjectLength: 2
    

    这将很好地扩展到数百万个重叠。

    您说您对所有区域中所有个体的状态感兴趣,并且从您的代码看来,不在某个区域中的个体具有状态 3。我创建了一个所有状态为 3 的矩阵

    state <- matrix(3, nrow(region_map), length(individual),
                    dimnames=list(NULL, individual))
    

    然后根据我们发现的重叠在矩阵中创建一个两列索引

    idx <- matrix(c(subjectHits(olaps),
                    match(segments$SampleID[queryHits(olaps)], individual)),
                  ncol=2)
    

    并使用索引矩阵更新状态

    state[idx] <- segments$State[queryHits(olaps)]
    

    这实际上总结了您想要的结果 - 每个区域 x 单独组合中的状态。一个可能的问题是,当同一个体的两个片段与单个区域重叠时,这些片段具有不同的状态;只会分配一个状态。

    > state
         Ind1 Ind2 Ind3 Ind4
    [1,]    1    2    4    2
    [2,]    2    5    4    6
    

    将其转换为 data.frame,例如,

    data.frame(SampleID=colnames(state)[col(state)],
               Start=region_map[row(state), "Start"],
               End=region_map[row(state), "End"],
               State=as.vector(state))
    

    【讨论】:

    • 这对我有用,我能够理解并根据我的真实数据修改它。顺便说一句,我不得不为我的数据使用 GenomicRanges 包,因为我也有染色体信息。我花了一段时间才理解所有内容,但感谢您提供非常彻底和有用的解释!
    • 哦,我使用 system.time 来计时:用户:0.46,系统:0.06,经过:0.51。相当惊人。
    • @GaiusAugustus 听起来你今天过得很充实;如果您的问题与 Bioconductor 相关,最好将它们发送到 Bioconductor support site
    • 这对我有用了一段时间,但在更新 data.table 后,突然我收到一个错误:[.data.table (regionmap, row(state), "Chr") : i is invalid type (matrix). Perhaps in future a 2 column matrix could return a list of elements of DT (in the spirit of A[B] in FAQ 2.14). Please let datatable-help know if you'd like this, or add your comments to FR #657. 中的错误任何想法如何解决它?我真的很喜欢这段代码的工作方式。
    • @GaiusAugustus 我的回答不使用 data.table;也许您打算评论另一个答案?或者更清楚地指出生成问题的代码出在哪里。
    【解决方案2】:

    您的代码中有很多行是nrow(some-subset-of-your-data)。如果您将它们切换到sum(the-conditions),您会看到性能快速提升。例如:

    转弯:

    nrow(segments[segments$start >= regionmap$Start[region] & 
                                       segments$End <= regionmap$End[region] &
                                      segments$SampleID == ind , ]) == 0
    

    进入

    sum(segments$start >= regionmap$Start[region] & 
                                       segments$End <= regionmap$End[region] &
                                      segments$SampleID == ind) == 0
    

    这样,R 不会每次都将子集数据帧存储在内存中。

    此外,将此操作存储为布尔值,因此您只需在每个循环中调用一次。

    isEmpty <- sum(segments$start >= regionmap$Start[region] & 
                                       segments$End <= regionmap$End[region] &
                                      segments$SampleID == ind) == 0
    
    if(isEmpty){
    ### do something
    } else if(!isEmpty) {
    ### do something else
    }
    

    【讨论】:

      【解决方案3】:

      我认为你不需要任何“这么复杂”的东西。您可以通过几次加入来完成您所追求的一切。在这种情况下,我将使用data.table

      您已要求对任何答案进行解释,但是,为此,我最好将您指向data.table homepage 的方向。了解set*:= 命令的作用以及“按引用更新”的工作原理非常重要。

      将您的数据设置为data.tables。

      library(data.table)
      
      dt_individual <- data.table(SampleID = individual)
      dt_region <- data.table(region_map)
      dt_segment <- data.table(segment)
      

      大家一起来

      ## Change some column names of `dt_segment` so we can identify them after the joins
      setnames(dt_segment, c("Start", "End"), c("seg_Start", "seg_End"))
      
      ## create a 'key_col' to join all the individuals to the regions
      dt_join <- dt_individual[, key_col := 1][ dt_region[, key_col := 1], on="key_col", allow.cartesian=T][, key_col := NULL]
      #    SampleID   Start     End
      # 1:     Ind1  721290 1688192
      # 2:     Ind2  721290 1688192
      # 3:     Ind3  721290 1688192
      # 4:     Ind4  721290 1688192
      # 5:     Ind1 1688193 2926555
      # 6:     Ind2 1688193 2926555
      # 7:     Ind3 1688193 2926555
      # 8:     Ind4 1688193 2926555
      

      现在使用foverlaps 函数查找重叠区域

      setkey(dt_join, SampleID, Start, End)
      setkey(dt_segment, SampleID, seg_Start, seg_End)
      
      foverlaps(dt_join,
                dt_segment,
                type="any")
      
      #    SampleID seg_Start seg_End State   Start     End
      # 1:     Ind1    721290 1688192     1  721290 1688192
      # 2:     Ind1   1688194 2926555     2 1688193 2926555
      # 3:     Ind2    721290 1688190     2  721290 1688192
      # 4:     Ind2   1688200 2900000     5 1688193 2926555
      # 5:     Ind3    721290 2926555     4  721290 1688192
      # 6:     Ind3    721290 2926555     4 1688193 2926555
      # 7:     Ind4    721290 1500000     2  721290 1688192
      # 8:     Ind4   1688193 2005000     6 1688193 2926555
      

      要查看所有数据(即属于区域内的数据和不属于区域内的数据),您可以进行cartesian 连接,然后根据需要为区域内和区域外的数据分配值

      dt_join[dt_segment, on="SampleID", nomatch=0, allow.cartesian=T]
      

      【讨论】:

      • 我对此有点困惑。 1)当我只想要区域文件中的 2 个时,您有 Ind3 的 4 个输出(在我的真实数据中,每个段都将落在 >= 1 个区域内)2)我如何修改它以使超出所需间隔的段是给定一个值(我的数据中的值 = 3)?我用过 data.table 包,但从来没有用过这么复杂的东西。
      • 为了澄清,请注意,我的输出在每个人的区域文件中每个区域有 1 行,该区域内的状态(由属于该区域的段标识)。而您的输出(例如第 2 行)具有不重叠的区域和列出的状态。
      • @GaiusAugustus - 我已将答案更改为使用foverlaps
      猜你喜欢
      • 2020-11-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-11-25
      • 1970-01-01
      相关资源
      最近更新 更多