我有一个数据框,该数据框已经根据需要进行了排序,但现在我想将其分组“切片”。
该组的最大累积值应为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
所以我有两个问题
对于第一部分,我尝试了一些 group_by 和 cumsum 的组合,但没有成功
df1 <- df %>% group_by(cumsum(c(False, value < 10)))
我更喜欢管道 (%>%) 解决方案而不是 for 循环
谢谢
我认为这不容易矢量化......至少我不知道如何实现。
您可以通过以下方式进行
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 循环?我发现这非常有用
对于像这样的迭代计算,我们可以使用
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
您可以定义自己的函数,然后在 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()
我们可以利用
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
下面的函数使用递归来构造一个具有每组长度的向量。对于小数据向量(长度小于大约一百个值),它比循环更快,但对于较长的数据向量,速度较慢。它需要三个参数:
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 中的递归函数,并且对有关递归对于此类问题是否有意义以及是否可以改进(尤其是执行速度)的任何评论和建议感兴趣。