我不太了解您的双for 循环,但有几种更有效的方法可以解决此类问题。
Vectorization 是 R 做得很好的东西。好多了,事实上,在某些语言中很自然的蛮力方法仍然可以在 R 中工作,但速度要慢得多。
旁注:R 的 for 循环过去的效率不如现在,因此许多人仍然强烈不鼓励使用它们,而支持 apply 系列的函数。两点:事实不再正确;这是与我在这里讨论的不同类型的循环结构。因此,当我在这种情况下不鼓励 for 循环时,它有利于 矢量化 数学,而不是 applying 它。
这是一些数据:
my_list <- list(
c(1, 12, 23, 34, 38),
c(2, 12, 21, 38, 47, 56, 71),
c(14, 22, 81, 88, 91, 94)
)
我将在此列表的单个向量上进行演示:
v <- my_list[[1]]
v
对于索引序列中的每个i,我将您所说的解释为v[i+1] - v[i](1 除外,因为v[0] 未在R 中定义)。要将其作为向量进行,这是“从除第一个之外的所有数字开始,然后减去除最后一个之外的所有数字”。
v[-1]
# [1] 12 23 34 38
v[-length(v)]
# [1] 1 12 23 34
v[-1] - v[-length(v)]
# [1] 11 11 11 4
这是有效的
c(12, 23, 34, 38) - c(1, 12, 23, 34)
c(12-1, 23-12, 34-23, 38-34)
现在我们知道如何高效地完成此操作一次,让我们简化该操作并将其映射到列表中的每个向量。 R 确实有一个函数可以为我们做到这一点:
diff(v)
# [1] 11 11 11 4
但如果您未来的需求包括更具体(非一般)的操作,我们可以为这个具体操作编写自己的函数:
my_func <- function(vec) vec[-1] - vec[-length(vec)]
下面是其中一个映射函数的经典用法:lapply 将单个函数应用于list 的每个元素,并返回一个长度相同的list 和返回值。
旁注:当我需要在for 和lapply 之间做出选择(例如)时,我会问自己是否关心每个元素的计算(例如这种情况,我想要diff 的向量),或者如果我只是对side-effect 感兴趣(例如,绘图、保存文件)。如果是前者,那么lapply或其近亲是合适的;如果是后者,通常是 for 循环。这不是 100% 的启发式方法,但总体上还是不错的。
lapply(my_list, my_func)
# [[1]]
# [1] 11 11 11 4
# [[2]]
# [1] 10 9 17 9 9 15
# [[3]]
# [1] 8 59 7 3 3
(同样,lapply(my_list, diff) 有效。)有类似的*apply* 函数,它们的优点、要求和限制略有不同。 (还有几个教程已经进入其中,SO 并不是一个教程站点。)
我真的不鼓励在这里使用for 循环,部分用于lapply,部分用于矢量化,但为了帮助您了解为什么您的实现不起作用:
- 如果您需要遍历列表的每个元素:
- 最好不硬编码
1:29,而是使用依赖于向量本身的东西,例如length(my_list),所以1:length(my_list)可能看起来合适(因为你正确使用在您的第二个循环中),但是...
- 碰巧这个列表的长度是 0,但是
for (i in 1:0) 确实没有做人们希望的事情。需要明确的是,我希望它什么都不做,但是1:0 解析为一个向量,长度为 2,值 1 和 0(这在大多数使用此流控制的情况下是错误的)。我建议用for (i in seq_along(my_list)) 或for (i in seq_len(length(my_list))) 替换for (i in 1:length(my_list))(seq_along 提供沿向量/列表的索引,如果其列表的长度为 0,它将不给出数字;并且seq_len 巧妙地给出一个长度为 0 的向量,如果它的参数是0。两者都可以在?seq中找到。)
- 当
i为1且j为2时,将list(12-1)存储在res[1]中;当 j 为 3 时,您覆盖 res[1] 与 list(23-12),因此您丢失了之前在向量 1 中的计算。这就是列表中每个元素的长度为 1 的原因。
- 你的内循环(
j)一直到向量的末尾(length(my_list[[i]]));此时,my_list[[i]][j+1] 指向向量的末尾之外,因此它解析为NA(尝试my_list[[1]][999999]),这就是为什么res 中的所有值都是NA。要解决此问题,请使用1:(length(my_list[[i]])-1) 或最好使用seq_length(my_list[[i]])[-1] 删除第一个(因此我们将使用(j) - (j-1) 而不是(j+1) - (j))。
- 如果您必须保留
(j+1) - (j) 索引逻辑,则使用seq_along(my_list[[i]])[-length(my_list[[i]])] 或head(seq_along(my_list[[i]]),n=-1) 之类的内容,其中n=-1 表示除最后一个之外的所有内容。
这是您的代码的更正版本:
resouter <- list()
for (i in seq_along(my_list)) {
resinner <- numeric(0)
for (j in seq_along(my_list[[i]])[-1]) {
resinner[j] <- my_list[[i]][j] - my_list[[i]][j-1]
}
resouter[[i]] <- resinner[-1] # since j starts at 2, first one is always NA
}
resouter
# [[1]]
# [1] 11 11 11 4
# [[2]]
# [1] 10 9 17 9 9 15
# [[3]]
# [1] 8 59 7 3 3
但我认为lapply(my_list, my_func) 甚至lapply(my_list, diff) 更简洁(而且速度更快)。