为了完整起见,还有来自purrr 包的accumulate() 函数。
所以,以Sotos 和ThomasIsCoding 的答案为基础:
df <- data.frame(a = 1:10)
df$b <- purrr::accumulate(df$a, paste, sep = "-")
df
a b
1 1 1
2 2 1-2
3 3 1-2-3
4 4 1-2-3-4
5 5 1-2-3-4-5
6 6 1-2-3-4-5-6
7 7 1-2-3-4-5-6-7
8 8 1-2-3-4-5-6-7-8
9 9 1-2-3-4-5-6-7-8-9
10 10 1-2-3-4-5-6-7-8-9-10
与Reduce() 的区别是
-
accumulate() 本身就是一个函数动词(不需要额外的参数accumulate = TRUE)
- 并且像
sep = "-" 这样的附加参数可以传递给映射函数,这可能有助于避免创建匿名函数。
编辑
如果我正确理解 OP 对问题的编辑,OP 会询问是否可以将迭代计算结果的 for 循环替换为 lapply()。
这对我来说很难回答。以下是一些想法和观察:
首先,accumulate() 仍然可以工作:
set.seed(1L) # required for reproducible data
df <- data.frame(a = sample(0:1, 10L, TRUE))
df$b <- purrr::accumulate(df$a, paste, sep = "-")
df
a b
1 0 0
2 1 0-1
3 0 0-1-0
4 0 0-1-0-0
5 1 0-1-0-0-1
6 0 0-1-0-0-1-0
7 0 0-1-0-0-1-0-0
8 0 0-1-0-0-1-0-0-0
9 1 0-1-0-0-1-0-0-0-1
10 1 0-1-0-0-1-0-0-0-1-1
这是可能的,因为a 的计算可以脱离循环,因为它不依赖于b。
恕我直言,accumulate() 和 Reduce() 执行 OP 正在寻找但未被称为 lapply() 的操作:例如,它们获取上一次迭代的结果并将其与实际值相结合
Reduce(`+`, 1:3)
通过迭代计算 (((0 + 1) + 2) + 3) 返回 1、2 和 3 的总和。这可以通过使用accumulate 参数来可视化
Reduce(`+`, 1:3, accumulate = TRUE)
[1] 1 3 6
第二,for 循环和lapply() 系列的函数之间有一个主要区别:lapply(X, FUN, ...) 需要在@987654352 的每个元素上调用函数FUN @。因此,适用函数的范围规则。
当我们将循环体移植到lapply()内的匿名函数中时
a <- c()
b <- c()
set.seed(1L) # required for reproducible data
lapply(1:10, function(i) {
a <- c(a, sample(c(0,1), 1))
b <- c(b, (paste(a, collapse = "-")))
})
我们得到
[[1]]
[1] "0"
[[2]]
[1] "1"
[[3]]
[1] "0"
[[4]]
[1] "0"
[[5]]
[1] "1"
[[6]]
[1] "0"
[[7]]
[1] "0"
[[8]]
[1] "0"
[[9]]
[1] "1"
[[10]]
[1] "1"
data.frame(a, b)
data frame with 0 columns and 0 rows data.frame(a, b)
由于范围规则,a 和b inside 函数被认为是函数的本地函数。未提及在函数外部定义的a 和b。
这可以通过全局赋值使用全局赋值运算符来修复<<-:
a <- c()
b <- c()
set.seed(1L) # required for reproducible data
lapply(1:10, function(i) {
a <<- c(a, sample(c(0,1), 1))
b <<- c(b, (paste(a, collapse = "-")))
})
data.frame(a, b)
a b
1 0 0
2 1 0-1
3 0 0-1-0
4 0 0-1-0-0
5 1 0-1-0-0-1
6 0 0-1-0-0-1-0
7 0 0-1-0-0-1-0-0
8 0 0-1-0-0-1-0-0-0
9 1 0-1-0-0-1-0-0-0-1
10 1 0-1-0-0-1-0-0-0-1-1
但是,global assignment 被认为是不好的编程习惯,应该避免,例如,请参阅 Patrick Burns' The R Inferno 的第 6 圈 以及许多关于 SO 的问题。
第三,循环的编写方式在循环中增长了向量。这也被认为是不好的做法,因为它需要一遍又一遍地复制数据,这可能会随着大小的增加而大大减慢。参见,例如,Patrick Burns' The R Inferno 的 第二个圈子。
但是原来的代码
a <- c()
b <- c()
set.seed(1L) # required for reproducible data
for (i in 1:10) {
a <- c(a, sample(c(0,1), 1))
b <- c(b, (paste(a, collapse = "-")))
}
data.frame(a, b)
可以改写为
a <- integer(10)
b <- character(10)
set.seed(1L) # required for reproducible data
for (i in seq_along(a)) {
a[i] <- sample(c(0,1), 1)
b[i] <- if (i == 1L) a[1] else paste(b[i-1], a[i], sep = "-")
}
data.frame(a, b)
在这里,向量被预先分配了所需的大小来保存结果。要更新的元素由下标标识。
b[i] 的计算仍然仅取决于 OP 要求的前一次迭代 b[i-1] 的值和实际值 a[i]。