通过 R 中的因子向量化 cumsum
vectorize cumsum by factor in R
我正在尝试在一个非常大的数据框(约 220 万行)中创建一个列,该列计算每个因子水平的 1 的累积和,并在达到新的因子水平时重置。下面是一些类似于我自己的基础数据。
itemcode <- c('a1', 'a1', 'a1', 'a1', 'a1', 'a2', 'a2', 'a3', 'a4', 'a4', 'a5', 'a6', 'a6', 'a6', 'a6')
goodp <- c(0, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 1)
df <- data.frame(itemcode, goodp)
我希望输出变量 cum.goodp 看起来像这样:
cum.goodp <- c(0, 1, 2, 0, 1, 1, 2, 0, 0, 1, 1, 1, 2, 0, 1)
我知道有很多使用规范的拆分-应用-组合方法的方法,从概念上讲很直观,但我尝试使用以下方法:
k <- transform(df, cum.goodp = goodp*ave(goodp, c(0L, cumsum(diff(goodp != 0)), FUN = seq_along, by = itemcode)))
当我尝试 运行 这段代码时,它非常非常慢。我知道转换是部分原因('by' 也无济于事)。 itemcode 变量有超过 70K 个不同的值,因此它可能应该被向量化。有没有办法使用 cumsum 对此进行矢量化?如果没有,我们将不胜感激任何帮助。非常感谢。
基本的 R 方法是在整个向量上计算 cumsum,并使用 运行 长度编码捕获子列表的几何形状。找出每个组的开始,并创建新组
start <- c(TRUE, itemcode[-1] != itemcode[-length(itemcode)]) | !goodp
f <- cumsum(start)
将这些总结为运行长度的编码,并计算总和
r <- rle(f)
x <- cumsum(x)
然后利用几何得到每个嵌入和需要修正的偏移量
offset <- c(0, x[cumsum(r$lengths)])
并计算更新值
x - rep(offset[-length(offset)], r$lengths)
这是一个函数
cumsumByGroup <- function(x, f) {
start <- c(TRUE, f[-1] != f[-length(f)]) | !x
r <- rle(cumsum(start))
x <- cumsum(x)
offset <- c(0, x[cumsum(r$lengths)])
x - rep(offset[-length(offset)], r$lengths)
}
这是应用于示例数据的结果
> cumsumByGroup(goodp, itemcode)
[1] 0 1 2 0 1 1 2 0 0 1 1 1 2 0 1
它的性能
> n <- 1 + rpois(1000000, 1)
> goodp <- sample(c(0, 1), sum(n), TRUE)
> itemcode <- rep(seq_along(n), n)
> system.time(cumsumByGroup(goodp, itemcode))
user system elapsed
0.55 0.00 0.55
dplyr 解决方案大约需要 70 秒。
@alexis_laz 解决方案既优雅又比我的快 2 倍
cumsumByGroup1 <- function(x, f) {
start <- c(TRUE, f[-1] != f[-length(f)]) | !x
cs = cumsum(x)
cs - cummax((cs - x) * start)
}
使用修改后的示例 input/output,您可以使用以下基本 R 方法(以及其他方法):
transform(df, cum.goodpX = ave(goodp, itemcode, cumsum(goodp == 0), FUN = cumsum))
# itemcode goodp cum.goodp cum.goodpX
#1 a1 0 0 0
#2 a1 1 1 1
#3 a1 1 2 2
#4 a1 0 0 0
#5 a1 1 1 1
#6 a2 1 1 1
#7 a2 1 2 2
#8 a3 0 0 0
#9 a4 0 0 0
#10 a4 1 1 1
#11 a5 1 1 1
#12 a6 1 1 1
#13 a6 1 2 2
#14 a6 0 0 0
#15 a6 1 1 1
注意:我将列 cum.goodp
添加到输入 df
并创建了一个新列 cum.goodpX
以便您可以轻松地比较两者。
但是,当然您可以使用许多其他方法来处理包,无论是@MartinMorgan 建议的方法还是例如使用 dplyr 或 data.table,仅举两个选项。对于大型数据集,这些方法可能比基本 R 方法快得多。
这是在 dplyr 中的实现方式:
library(dplyr)
df %>%
group_by(itemcode, grp = cumsum(goodp == 0)) %>%
mutate(cum.goodpX = cumsum(goodp))
您的问题的评论中已经提供了一个 data.table 选项。
我正在尝试在一个非常大的数据框(约 220 万行)中创建一个列,该列计算每个因子水平的 1 的累积和,并在达到新的因子水平时重置。下面是一些类似于我自己的基础数据。
itemcode <- c('a1', 'a1', 'a1', 'a1', 'a1', 'a2', 'a2', 'a3', 'a4', 'a4', 'a5', 'a6', 'a6', 'a6', 'a6')
goodp <- c(0, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 1)
df <- data.frame(itemcode, goodp)
我希望输出变量 cum.goodp 看起来像这样:
cum.goodp <- c(0, 1, 2, 0, 1, 1, 2, 0, 0, 1, 1, 1, 2, 0, 1)
我知道有很多使用规范的拆分-应用-组合方法的方法,从概念上讲很直观,但我尝试使用以下方法:
k <- transform(df, cum.goodp = goodp*ave(goodp, c(0L, cumsum(diff(goodp != 0)), FUN = seq_along, by = itemcode)))
当我尝试 运行 这段代码时,它非常非常慢。我知道转换是部分原因('by' 也无济于事)。 itemcode 变量有超过 70K 个不同的值,因此它可能应该被向量化。有没有办法使用 cumsum 对此进行矢量化?如果没有,我们将不胜感激任何帮助。非常感谢。
基本的 R 方法是在整个向量上计算 cumsum,并使用 运行 长度编码捕获子列表的几何形状。找出每个组的开始,并创建新组
start <- c(TRUE, itemcode[-1] != itemcode[-length(itemcode)]) | !goodp
f <- cumsum(start)
将这些总结为运行长度的编码,并计算总和
r <- rle(f)
x <- cumsum(x)
然后利用几何得到每个嵌入和需要修正的偏移量
offset <- c(0, x[cumsum(r$lengths)])
并计算更新值
x - rep(offset[-length(offset)], r$lengths)
这是一个函数
cumsumByGroup <- function(x, f) {
start <- c(TRUE, f[-1] != f[-length(f)]) | !x
r <- rle(cumsum(start))
x <- cumsum(x)
offset <- c(0, x[cumsum(r$lengths)])
x - rep(offset[-length(offset)], r$lengths)
}
这是应用于示例数据的结果
> cumsumByGroup(goodp, itemcode)
[1] 0 1 2 0 1 1 2 0 0 1 1 1 2 0 1
它的性能
> n <- 1 + rpois(1000000, 1)
> goodp <- sample(c(0, 1), sum(n), TRUE)
> itemcode <- rep(seq_along(n), n)
> system.time(cumsumByGroup(goodp, itemcode))
user system elapsed
0.55 0.00 0.55
dplyr 解决方案大约需要 70 秒。
@alexis_laz 解决方案既优雅又比我的快 2 倍
cumsumByGroup1 <- function(x, f) {
start <- c(TRUE, f[-1] != f[-length(f)]) | !x
cs = cumsum(x)
cs - cummax((cs - x) * start)
}
使用修改后的示例 input/output,您可以使用以下基本 R 方法(以及其他方法):
transform(df, cum.goodpX = ave(goodp, itemcode, cumsum(goodp == 0), FUN = cumsum))
# itemcode goodp cum.goodp cum.goodpX
#1 a1 0 0 0
#2 a1 1 1 1
#3 a1 1 2 2
#4 a1 0 0 0
#5 a1 1 1 1
#6 a2 1 1 1
#7 a2 1 2 2
#8 a3 0 0 0
#9 a4 0 0 0
#10 a4 1 1 1
#11 a5 1 1 1
#12 a6 1 1 1
#13 a6 1 2 2
#14 a6 0 0 0
#15 a6 1 1 1
注意:我将列 cum.goodp
添加到输入 df
并创建了一个新列 cum.goodpX
以便您可以轻松地比较两者。
但是,当然您可以使用许多其他方法来处理包,无论是@MartinMorgan 建议的方法还是例如使用 dplyr 或 data.table,仅举两个选项。对于大型数据集,这些方法可能比基本 R 方法快得多。
这是在 dplyr 中的实现方式:
library(dplyr)
df %>%
group_by(itemcode, grp = cumsum(goodp == 0)) %>%
mutate(cum.goodpX = cumsum(goodp))
您的问题的评论中已经提供了一个 data.table 选项。