【问题标题】:How to compute (i+1 - i) in a list of vectors如何在向量列表中计算 (i+1 - i)
【发布时间】:2018-11-27 14:48:55
【问题描述】:

我有一个包含 29 个向量(每个向量具有不同长度)的列表,如下所示:

my_list
[1] 1 12 23 34 38 
[2] 2 12 21 38 47 56 71  
 .
 .
[29] 14 22 81 88 91 94   

我需要为列表 (my_list) 的每个向量计算 (i+1 - i)。 示例:

my_list
[1] (12-1) (23-12)  (34-23) (38-34)
[2] (12-2) (21-12)  (38-21) (47-38) (56-47) (71-56)
 .
 .
[29] (22-14) (81-22)  (88-81) (91-88) (94-91) 

我尝试了一个 for 循环:

res <- list()
for(i in 1:29) {
    for(j in 1:length(my_list[[i]])){
        my_res <- list(my_list[[i]][j+1] - my_list[[i]][j])
        res[i] <- my_res

但是结果只给出了列表中每个向量的第一个值:

res
[1] 11
[2] 10
 .
 .
[29] 8

有一种方法可以使用类似应用的函数吗?

【问题讨论】:

  • lapply(my_list, diff)?
  • 您的代码似乎不完整。
  • @Parfait,我根本没有将 OP 解释为建议对相同向量长度的约束。我建议将data.frame 带入此讨论可能会让 R 新手非常困惑。

标签: r for-loop lapply


【解决方案1】:

我不太了解您的双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 和返回值。

旁注:当我需要在forlapply 之间做出选择(例如)时,我会问自己是否关心每个元素的计算(例如这种情况,我想要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) 更简洁(而且速度更快)。

【讨论】:

  • 效果很好。非常感谢您的详细回复。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-01-19
  • 1970-01-01
  • 1970-01-01
  • 2020-05-24
相关资源
最近更新 更多