【问题标题】:Cumulative sums over run lengths. Can this loop be vectorized?运行长度的累积总和。这个循环可以向量化吗?
【发布时间】:2011-12-31 13:19:03
【问题描述】:

我有一个数据框,我在该数据框上计算特定列的运行长度编码。 dir 列的值为 -1、0 或 1。

dir.rle <- rle(df$dir)

然后,我获取运行长度并计算数据框中另一列的分段累积总和。我正在使用 for 循环,但我觉得应该有一种更智能的方法。

ndx <- 1
for(i in 1:length(dir.rle$lengths)) {
    l <- dir.rle$lengths[i] - 1
    s <- ndx
    e <- ndx+l
    tmp[s:e,]$cumval <- cumsum(df[s:e,]$val)
    ndx <- e + 1
}

dir 的运行长度定义了每次运行的开始 s 和结束 e。上面的代码有效,但感觉不像是惯用的 R 代码。我觉得好像应该有另一种没有循环的方法。

【问题讨论】:

  • 您能提供一些示例数据吗?这会有所帮助。
  • 如果我误解了您的数据框的结构,请告诉我。您可以使用dput(yourDataHere) 的输出更新您的问题。如果太大,请使用subset()head() 使示例大小合适。

标签: r vectorization plyr data.table


【解决方案1】:

这可以分解为两步问题。首先,如果我们基于rle 创建一个索引列,那么我们可以使用它来分组并运行cumsum。然后可以通过任意数量的聚合技术来执行分组。我将展示两个选项,一个使用data.table,另一个使用plyr

library(data.table)
library(plyr)
#data.table is the same thing as a data.frame for most purposes
#Fake data
dat <- data.table(dir = sample(-1:1, 20, TRUE), value = rnorm(20))
dir.rle <- rle(dat$dir)
#Compute an indexing column to group by
dat <- transform(dat, indexer = rep(1:length(dir.rle$lengths), dir.rle$lengths))


#What does the indexer column look like?
> head(dat)
     dir      value indexer
[1,]   1  0.5045807       1
[2,]   0  0.2660617       2
[3,]   1  1.0369641       3
[4,]   1 -0.4514342       3
[5,]  -1 -0.3968631       4
[6,]  -1 -2.1517093       4


#data.table approach
dat[, cumsum(value), by = indexer]

#plyr approach
ddply(dat, "indexer", summarize, V1 = cumsum(value))

【讨论】:

  • plyr 和 data.table 之间的速度差异有什么迹象吗?
  • 一些无耻的自我推销:)。在我的博客上,我发布了一个比较 ave、ddply 和 data.table 的帖子:numbertheory.nl/2011/10/28/…
  • @paul - 我的直觉似乎证实了你的博文。在大多数情况下,当 N 变大时,Data.table 是最快的。我对 plyr 更满意,但最近一直在重构瓶颈。
  • Hadley Wickham(plyr 作者)回应了我帖子中的代码示例,称新版本的 plyr 将使用 data.table 等速度增强功能。所以也许不久之后速度差异就会得到解决:)。
【解决方案2】:

Spacedman 和 Chase 都强调了分组变量可以简化一切的关键点(Chase 提出了两种从那里着手的好方法)。

我将提出另一种方法来形成该分组变量。它不使用rle,至少对我来说,感觉更直观。基本上,在diff() 检测到值变化的每个点,将形成您的分组变量的cumsum 都会增加一:

df$group <- c(0, cumsum(!(diff(df$dir)==0)))

# Or, equivalently
df$group <- c(0, cumsum(as.logical(diff(df$dir))))

【讨论】:

    【解决方案3】:

    在数据框中添加一个“组”列。比如:

    df=data.frame(z=rnorm(100)) # dummy data
    df$dir = sign(df$z) # dummy +/- 1
    rl = rle(df$dir)
    df$group = rep(1:length(rl$lengths),times=rl$lengths)
    

    然后使用 tapply 在组内求和:

    tapply(df$z,df$group,sum)
    

    【讨论】:

    • 很好,除了我认为 OP 想要在每个组内重新开始的累积总和(而不是按组计算的总数)。
    • 这就是为什么我们喜欢问题中可重复的例子:)事实上他非常接近。
    • 哈利路亚,兄弟!我还希望有一个功能可以通过 StackOverflow 的搜索引擎运行 paste([r], title_of_question),并在提供“提交”按钮之前返回结果。这对于减少涉及"digits""round*"NA" 的问题尤其有用! (不是对当前问题的抱怨,这很有趣,并且在发布之前显示了很多想法)。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-04-17
    • 2023-02-08
    • 1970-01-01
    • 1970-01-01
    • 2021-12-10
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多