【问题标题】:Efficient way to filter one data frame by ranges in another按范围过滤另一个数据帧的有效方法
【发布时间】:2016-07-27 01:24:49
【问题描述】:

假设我有一个数据框,其中包含一堆数据和一个日期/时间列,指示每个数据点的收集时间。我有另一个列出时间跨度的数据框,其中“开始”列表示每个跨度开始的日期/时间,“结束”列表示每个跨度结束的日期/时间。

我在下面使用简化数据创建了一个虚拟示例:

main_data = data.frame(Day=c(1:30))

spans_to_filter = 
    data.frame(Span_number = c(1:6),
               Start = c(2,7,1,15,12,23),
               End = c(5,10,4,18,15,26))

我尝试了几种解决这个问题的方法,最终得到了以下解决方案:

require(dplyr)    
filtered.main_data =
    main_data %>% 
    rowwise() %>% 
    mutate(present = any(Day >= spans_to_filter$Start & Day <= spans_to_filter$End)) %>% 
    filter(present) %>% 
    data.frame()

这工作得很好,但我注意到如果我有很多数据(我假设是因为我正在执行逐行比较),它可能需要一段时间来处理。我仍在学习 R 的细节,我想知道是否有更有效的方法来执行此操作,最好使用 dplyr/tidyr?

【问题讨论】:

    标签: r dplyr


    【解决方案1】:

    使用基础 R:

    main_data[unlist(lapply(main_data$Day, 
      function(x) any(x >= spans_to_filter$Start & x <= spans_to_filter$End))),]
    

    【讨论】:

      【解决方案2】:

      这是一个函数,您可以在 dplyr 中运行该函数,以使用 between 函数(来自 dplyr)查找给定范围内的日期。对于Day 的每个值,mapplyStartEnd 日期对中的每一对上运行between,如果Day 在至少一个日期之间,则该函数使用rowSums 返回TRUE其中。我不确定这是否是最有效的方法,但它可以将速度提高近四倍。

      test.overlap = function(vals) {
        rowSums(mapply(function(a,b) between(vals, a, b), 
                       spans_to_filter$Start, spans_to_filter$End)) > 0
      }
      
      main_data %>% 
        filter(test.overlap(Day))
      

      如果您使用日期(而不是日期时间),则创建特定日期的向量并测试成员资格可能会更有效(即使使用日期时间,这也可能是更好的方法):

      filt.vals = as.vector(apply(spans_to_filter, 1, function(a) a["Start"]:a["End"]))
      
      main_data %>% 
        filter(Day %in% filt.vals)
      

      现在比较执行速度。我缩短了你的代码,只需要过滤操作:

      library(microbenchmark)
      
      microbenchmark(
        OP=main_data %>% 
          rowwise() %>% 
          filter(any(Day >= spans_to_filter$Start & Day <= spans_to_filter$End)),
        eipi10 = main_data %>% 
          filter(test.overlap(Day)),
        eipi10_2 = main_data %>% 
          filter(Day %in% filt.vals)
        )
      
      Unit: microseconds
           expr      min       lq      mean    median       uq      max neval cld
             OP 2496.019 2618.994 2875.0402 2701.8810 2954.774 4741.481   100   c
         eipi10  658.941  686.933  782.8840  714.4440  770.679 2474.941   100  b 
       eipi10_2  579.338  601.355  655.1451  619.2595  672.535 1032.145   100 a   
      

      更新: 下面是一个包含更大数据框和一些额外日期范围的测试(感谢 @Frank 在他现在已删除的评论中提出的建议)。事实证明,在这种情况下,速度增益要大得多(mapply/between 方法大约是 200 倍,而第二种方法要大得多)。

      main_data = data.frame(Day=c(1:100000))
      
      spans_to_filter = 
        data.frame(Span_number = c(1:9),
                   Start = c(2,7,1,15,12,23,90,9000,50000),
                   End = c(5,10,4,18,15,26,100,9100,50100))
      
      microbenchmark(
        OP=main_data %>% 
          rowwise() %>% 
          filter(any(Day >= spans_to_filter$Start & Day <= spans_to_filter$End)),
        eipi10 = main_data %>% 
          filter(test.overlap(Day)),
        eipi10_2 = {
          filt.vals = unlist(apply(spans_to_filter, 1, function(a) a["Start"]:a["End"]))
          main_data %>% 
            filter(Day %in% filt.vals)}, 
        times=10
        )
      
      Unit: milliseconds
           expr         min          lq        mean      median          uq         max neval cld
             OP 5130.903866 5137.847177 5201.989501 5216.840039 5246.961077 5276.856648    10   b
         eipi10   24.209111   25.434856   29.526571   26.455813   32.051920   48.277326    10  a 
       eipi10_2    2.505509    2.618668    4.037414    2.892234    6.222845    8.266612    10  a 
      

      【讨论】:

        【解决方案3】:

        在从 v1.9.8 开始的 data.table 包中,实现了 non-equi 连接。有了这个,我已经为这类操作创建了一个包装函数inrange(),其中任务涉及查找一个点是否位于提供的任何间隔中,如果是,则返回TRUE,否则返回FALSE

        require(data.table) # v>=1.9.8
        setDT(main_data)[Day %inrange% spans_to_filter[, 2:3]] # inclusive bounds
        #     Day
        #  1:   1
        #  2:   2
        #  3:   3
        #  4:   4
        #  5:   5
        #  6:   7
        #  7:   8
        #  8:   9
        #  9:  10
        # 10:  12
        # 11:  13
        # 12:  14
        # 13:  15
        # 14:  16
        # 15:  17
        # 16:  18
        # 17:  23
        # 18:  24
        # 19:  25
        # 20:  26
        

        请参阅?inrange 了解更多信息。

        【讨论】:

        • 随着查找表的增长,这比接受的答案要快得多。
        • 最近进行了改进.. 在较小的查找表上也应该更快。
        猜你喜欢
        • 2021-05-07
        • 2021-02-16
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-06-25
        • 2021-01-07
        • 2019-04-07
        • 1970-01-01
        相关资源
        最近更新 更多