【问题标题】:Conditional cumsum with reset带重置的条件 cumsum
【发布时间】:2016-04-04 13:10:05
【问题描述】:

我有一个数据框,该数据框已经根据需要进行了排序,但现在我想将其“切片”成组。

该组的最大累积值应为10。当累积值> 10时,应重置累积和并重新开始

library(dplyr)
id <- sample(1:15)
order <- 1:15
value  <- c(4, 5, 7, 3, 8, 1, 2, 5, 3, 6, 2, 6, 3, 1, 4)
df  <- data.frame(id, order, value)
df

这是我正在寻找的输出(我是“手动”完成的)

cumsum_10  <- c(4, 9, 7, 10, 8, 9, 2, 7, 10, 6, 8, 6, 9, 10, 4)
group_10 <- c(1, 1, 2, 2, 3, 3, 4, 4, 4, 5, 5, 6, 6, 6, 7)
df1  <- data.frame(df, cumsum_10, group_10)
df1

所以我有两个问题

  1. 如何创建一个累积变量,每次超过上限(本例中为 10)时都会重置
  2. 如何对每个组进行计数/分组

对于第一部分,我尝试了一些 group_by 和 cumsum 的组合,但没有运气

df1 <- df %>% group_by(cumsum(c(False, value < 10)))

我更喜欢管道 (%>%) 解决方案而不是 for 循环

谢谢

【问题讨论】:

  • 如果你能弄清楚第一部分,第二部分将是微不足道的 (group_by)。我认为如果没有for 循环,将很难做到第一个,除非有人变得非常聪明。您想要管道以提高效率、优雅……吗?如果在辅助函数中隐藏了一个 for 循环,那可以吗?
  • group_by(bin(value, 10))%&gt;% mutate(cumsum(value)) 在链接中使用bin 函数

标签: r dplyr reset cumsum


【解决方案1】:

我认为这不容易矢量化....至少我不知道如何。

您可以通过by hand 进行操作:

my_cumsum <- function(x){
  grp = integer(length(x))
  grp[1] = 1
  for(i in 2:length(x)){
    if(x[i-1] + x[i] <= 10){
      grp[i] = grp[i-1]
      x[i] = x[i-1] + x[i]
    } else {
      grp[i] = grp[i-1] + 1
    }
  }
  data.frame(grp, x)
}

对于您的数据,这给出了:

> my_cumsum(df$value)
   grp  x
1    1  4
2    1  9
3    2  7
4    2 10
5    3  8
6    3  9
7    4  2
8    4  7
9    4 10
10   5  6
11   5  8
12   6  6
13   6  9
14   6 10
15   7  4

对于我的“反例”,这也给出了:

> my_cumsum(c(10,6,4))
  grp  x
1   1 10
2   2  6
3   2 10

正如@Khashaa 指出的,这可以通过Rcpp 更有效地实现。他链接到这个答案How to speed up or vectorize a for loop?,我觉得这非常有用

【讨论】:

    【解决方案2】:

    您可以定义自己的函数,然后在 dplyr 的 mutate 语句中使用它,如下所示:

    df %>% group_by() %>%
      mutate(
        cumsum_10 = cumsum_with_reset(value, 10),
        group_10 = cumsum_with_reset_group(value, 10)
      ) %>% 
      ungroup()
    

    cumsum_with_reset() 函数采用一列和一个重置总和的阈值。 cumsum_with_reset_group() 类似,但标识已分组在一起的行。定义如下:

    # group rows based on cumsum with reset
    cumsum_with_reset_group <- function(x, threshold) {
      cumsum <- 0
      group <- 1
      result <- numeric()
    
      for (i in 1:length(x)) {
        cumsum <- cumsum + x[i]
    
        if (cumsum > threshold) {
          group <- group + 1
          cumsum <- x[i]
        }
    
        result = c(result, group)
    
      }
    
      return (result)
    }
    
    # cumsum with reset
    cumsum_with_reset <- function(x, threshold) {
      cumsum <- 0
      group <- 1
      result <- numeric()
    
      for (i in 1:length(x)) {
        cumsum <- cumsum + x[i]
    
        if (cumsum > threshold) {
          group <- group + 1
          cumsum <- x[i]
        }
    
        result = c(result, cumsum)
    
      }
    
      return (result)
    }
    
    # use functions above as window functions inside mutate statement
    df %>% group_by() %>%
      mutate(
        cumsum_10 = cumsum_with_reset(value, 10),
        group_10 = cumsum_with_reset_group(value, 10)
      ) %>% 
      ungroup()
    

    【讨论】:

      【解决方案3】:

      下面的函数使用递归来构造一个包含每个组长度的向量。它比小数据向量(长度小于大约一百个值)的循环快,但对于较长的数据向量慢。它需要三个参数:

      1) vec:我们要分组的值向量。

      2)ivec中起始位置的索引。

      3) glv:组长度向量。这是返回值,但我们需要初始化它并通过每个递归传递它。

      # Group a vector based on consecutive values with a cumulative sum <= 10
      gf = function(vec, i, glv) {
      
        ## Break out of the recursion when we get to the last group
        if (sum(vec[i:length(vec)]) <= 10) {
          glv = c(glv, length(i:length(vec)))
          return(glv)
        }
      
        ## Keep recursion going if there are at least two groups left
        # Calculate length of current group
        gl = sum(cumsum(vec[i:length(vec)]) <= 10)
      
        # Append to previous group lengths
        glv.append = c(glv, gl)
      
        # Call function recursively 
        gf(vec, i + gl, glv.append)
      }
      

      运行函数以返回组长度的向量:

      group_vec = gf(df$value, 1, numeric(0))
      [1] 2 2 2 3 2 3 1
      

      要向df 添加具有组长度的列,请使用rep

      df$group10 = rep(1:length(group_vec), group_vec)
      

      在其当前形式中,该函数仅适用于没有任何大于 10 的值的向量,并且按总和

      通过只向前看一定数量的值而不是向量的剩余长度的累积求和,可以在一定程度上加快该函数的速度。例如,如果值始终为正,您只需提前查看 10 个值,因为您永远不需要将超过 10 个数字相加即可达到 10。这也可以推广到任何目标值。即使进行了这种修改,对于具有大约一百多个值的向量,该函数仍然比循环慢。

      我之前没有在 R 中使用过递归函数,我对任何有关递归是否对此类问题有意义以及是否可以改进(尤其是执行速度)的 cmet 和建议感兴趣。

      【讨论】:

        【解决方案4】:

        我们可以利用包MESS中的函数cumsumbinning来执行这个任务:

        library(MESS)
        df %>%
          group_by(group_10 = cumsumbinning(value, 10)) %>%
          mutate(cumsum_10 = cumsum(value)) 
        

        输出

        # A tibble: 15 x 5
        # Groups:   group_10 [7]
              id order value group_10 cumsum_10
           <int> <int> <dbl>    <int>     <dbl>
         1     6     1     4        1         4
         2    10     2     5        1         9
         3     1     3     7        2         7
         4     5     4     3        2        10
         5     3     5     8        3         8
         6     9     6     1        3         9
         7    14     7     2        4         2
         8    11     8     5        4         7
         9    15     9     3        4        10
        10     8    10     6        5         6
        11    12    11     2        5         8
        12     2    12     6        6         6
        13     4    13     3        6         9
        14     7    14     1        6        10
        15    13    15     4        7         4
        

        【讨论】:

          【解决方案5】:

          这可以通过purrr::accumulate轻松完成

          library(dplyr)
          library(purrr)
          
          df %>% mutate(cumsum_10 = accumulate(value, ~ifelse(.x + .y <= 10, .x + .y, .y)),
                        group_10 = cumsum(value == cumsum_10))
          
             id order value cumsum_10 group_10
          1   8     1     4         4        1
          2  13     2     5         9        1
          3   7     3     7         7        2
          4   1     4     3        10        2
          5   4     5     8         8        3
          6  10     6     1         9        3
          7  12     7     2         2        4
          8   2     8     5         7        4
          9  15     9     3        10        4
          10 11    10     6         6        5
          11 14    11     2         8        5
          12  3    12     6         6        6
          13  5    13     3         9        6
          14  9    14     1        10        6
          15  6    15     4         4        7
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2019-04-04
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2018-05-24
            相关资源
            最近更新 更多