带重置的条件累积和

问题描述 投票:0回答:5

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

该组的最大累积值应为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 循环

谢谢

r dplyr reset cumsum
5个回答
9
投票

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

您可以通过以下方式进行

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
更有效地实现。他链接到这个答案如何加速或矢量化 for 循环?我发现这非常有用


7
投票

对于像这样的迭代计算,我们可以使用

purrr
中的包
tidyverse
。我们这里有一个函数
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

此外,这也可以使用基 R 的

Reduce
(注意此处为大写 R)通过设置其参数
accumulate = TRUE
来获得,然后它将返回所有中间值,而不是仅返回最后一个值。

library(dplyr)
df %>% 
  mutate(cumsum_10 = Reduce(\(x, y) if (x + y <= 10) x + y else y,
                            x = value,
                            accumulate = TRUE),
         group_10 = cumsum(value == cumsum_10))

#>    id order value cumsum_10 group_10
#> 1   2     1     4         4        1
#> 2  15     2     5         9        1
#> 3   1     3     7         7        2
#> 4   7     4     3        10        2
#> 5   9     5     8         8        3
#> 6  12     6     1         9        3
#> 7   4     7     2         2        4
#> 8   6     8     5         7        4
#> 9  11     9     3        10        4
#> 10  5    10     6         6        5
#> 11  3    11     2         8        5
#> 12 13    12     6         6        6
#> 13 14    13     3         9        6
#> 14  8    14     1        10        6
#> 15 10    15     4         4        7

5
投票

您可以定义自己的函数,然后在 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()

4
投票

我们可以利用

cumsumbinning
包中的函数
MESS
来执行此任务:

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

3
投票

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

1)

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

2)

i
:
vec
中起始位置的索引。

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 is hard-coded. The function can of course be generalized to deal with these limitations.

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

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

© www.soinside.com 2019 - 2024. All rights reserved.