【问题标题】:vectorize cumsum by factor in R在 R 中按因子矢量化 cumsum
【发布时间】:2016-06-24 05:38:47
【问题描述】:

我正在尝试在一个非常大的数据框(约 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 对其进行矢量化?如果没有,任何帮助将不胜感激。非常感谢。

【问题讨论】:

  • 你能显示预期的输出吗?
  • @akrun 这是一个问题
  • 也许您正在寻找transform(df, cum.goodp = ave(goodp, itemcode, FUN = cumsum)),但我真的不清楚..
  • dt[,cum_goodp := cumsum(goodp), by = "itemcode"]dt &lt;- data.table(df) 呢?您的 transform(...) 呼叫为我返回了一个错误,所以我不确定所需的输出是什么样的。
  • @jvalenti,那么你可以使用transform(df, cum.goodpX = ave(goodp, itemcode, cumsum(goodp == 0), FUN = cumsum))

标签: r vectorization cumsum


【解决方案1】:

基本 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)
}

【讨论】:

  • 除非对所有 0 和 1 有一个警告,否则类似的方法可能是:cs = cumsum(x); cs - cummax((cs - x) * start)
【解决方案2】:

使用修改后的示例输入/输出,您可以使用以下基本 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

注意:我在输入 df 中添加了列 cum.goodp 并创建了一个新列 cum.goodpX,因此您可以轻松地比较两者。

当然,您可以使用许多其他方法来处理包,无论是@MartinMorgan 建议的方法,还是例如使用 dplyr 或 data.table,仅举两个选项。对于大型数据集,这些方法可能比基本 R 方法快得多。

这是在 dplyr 中如何完成的:

library(dplyr)
df %>% 
   group_by(itemcode, grp = cumsum(goodp == 0)) %>% 
   mutate(cum.goodpX = cumsum(goodp))

cmets 中已为您的问题提供了一个 data.table 选项。

【讨论】:

    猜你喜欢
    • 2016-05-30
    • 2018-10-01
    • 2020-05-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多