优化性能以查找tibble / data.frame中的范围

时间:2019-06-08 16:30:15

标签: r

我有以下数据集(实际上超过30.000行):

dt <- tibble::tibble(x=seq.Date(as.Date("2019-01-01"), as.Date("2019-01-10"), "days"), y=c(1,2,3,2,1,1,3,1,2,1))

A tibble: 10 x 2
   x              y
   <date>     <dbl>
 1 2019-01-01     1
 2 2019-01-02     2
 3 2019-01-03     3
 4 2019-01-04     2
 5 2019-01-05     1
 6 2019-01-06     1
 7 2019-01-07     3
 8 2019-01-08     1
 9 2019-01-09     2
10 2019-01-10     1

我想标识一个给定阈值以上的日期范围,例如y> =2。该范围的第一个出现日期应另存为“开始”,而最后一个出现的日期应保存为“结束”。每个“开始” /“结束”组合应保存在单独的行中。范围由低于阈值(例如y <2)的“ y”值彼此分隔。结果应如下表所示:

result <- tibble::tibble(start=as.Date(c("2019-01-02", "2019-01-07", "2019-01-09")), end=as.Date(c("2019-01-04", "2019-01-07", "2019-01-09")))

A tibble: 3 x 2
   start      end       
   <date>     <date>    
 1 2019-01-02 2019-01-04
 2 2019-01-07 2019-01-07
 3 2019-01-09 2019-01-09

我当前的解决方案是使用for循环。但是,这导致执行非常缓慢。

有什么想法可以改善性能并更优雅地解决此问题?

谢谢您的想法。

2 个答案:

答案 0 :(得分:1)

这应该起作用,并且基于这样的想法,即日期的差异减去1将具有彼此相等的累积和。这就是为什么我们可以按它分组。

dt%>%
  filter(y >= 2)%>%
  group_by(grouping = cumsum(c(0, diff.Date(x) - 1)))%>%
  summarize(start = min(x)
            , end = max(x))%>%
  select(-grouping)

# A tibble: 3 x 2
  start      end       
  <date>     <date>    
1 2019-01-02 2019-01-04
2 2019-01-07 2019-01-07
3 2019-01-09 2019-01-09

答案 1 :(得分:1)

这是一个识别状态向量的连续值组的函数

f = function(x, min) {
    ## 'run length encoding' of values satisfying the condition
    r = rle(x > min)
    ## replace TRUE values with a grouping variable; FALSE values are coerced to 0
    ## (probably better to also replace r$values[!r$values] = NA)
    r$values[r$values] = seq_len(sum(r$values))
    ## expand the modified run length encoding to the shape of the original vector
    inverse.rle(r)
}

为您提供数据,

> mutate(dt, grp = f(y, 1))
# A tibble: 10 x 3
   x              y   grp
   <date>     <dbl> <int>
 1 2019-01-01     1     0
 2 2019-01-02     2     1
 3 2019-01-03     3     1
 4 2019-01-04     2     1
 5 2019-01-05     1     0
 6 2019-01-06     1     0
 7 2019-01-07     3     2
 8 2019-01-08     1     0
 9 2019-01-09     2     3
10 2019-01-10     1     0

然后我们可以使用标准的dplyr操作

mutate(dt, grp = f(y, 1)) %>% 
    filter(grp != 0) %>% 
    group_by(grp) %>% 
    summarize(start = min(x), end = max(x), n = n())

与输出

# A tibble: 3 x 4
    grp start      end            n
  <int> <date>     <date>     <int>
1     1 2019-01-02 2019-01-04     3
2     2 2019-01-07 2019-01-07     1
3     3 2019-01-09 2019-01-09     1