如何在 R 中将这些 for 循环重写为 lapply

How do I rewrite these for loops to lapply in R

多个图和一个 for 循环

我正在研究绘图函数并编写了这些 for 循环。我一直在阅读 for loops are bad for memory use in R 并且我应该使用 apply 或其变体之一进行编程。 但是我不明白应该将什么数据框或列表作为第一个参数传递。

我想将此代码替换为使用 apply 的代码:

BasicPlot(depth, var[,1], xtitle=xlab)
for(i in 2:ncol(var))
    BasicPlot(depth, var[,i], add=TRUE, xtitle=xlab, ...) 

如果您想知道我想要实现的目标,请参阅我的 DepthPlotter project on github

编辑:阅读 this site 关于应用函数后我找到了一个解决方案:

lapply(2:ncol(var), function(i) { BasicPlot(depth, var[,i], add=TRUE, xtitle=xlab, ...)})

这有效但给了我愚蠢的输出,在这种情况下是 [[1]] NULL [[2]] NULL 等的列表,我可以通过用 invisible(...).

包围代码来使其静音

这实际上比以前的代码更好吗?它 a) 更容易阅读和 b) 更快吗?


读取多个文件夹中的多个文件:双 for 循环

我试图用应用函数代替我目前正在考虑的 for 循环来解决的第二个问题:

我想读取多个光栅图像(名称为 1.png8.png),它们位于单独的文件夹中(名称为 959D22959D41)。 我想根据文件夹和文件名将行名分配给列表项。这应该 return 一个光栅图像列表,然后我可以将其添加到我的特定值的地块中。

cores <- list.files("data/core_splitpics/") # folder names
pics <- list() # according to replies below, this is the sort of thing that makes for-loops bad in R, because I'm expanding a list step by step.
for(i in 1:length(cores)){ # loop over folders
    imgs  <- list.files(paste("data/core_splitpics/", cores[i]) # filenames in folder i
    for(j in 1:length(imgs)){ # loop over files
        pics[[i, j]] <- as.raster(readPNG(paste("data/core_splitpics", 
            cores[i], paste(j, ".png", sep=""), sep="/"))) # something like this
    }
}

看完之后,我仍然不知道构建此列表的最佳方法是什么。也许首先创建列表名称,然后将光栅图像添加到这些条目?因为我想 return 一个值,所以 apply 的变体在这里更好吗?

对于您的第一个问题,lapply 会增加复杂性,因为它会返回您未使用的列表,for 循环更直接但更慢,开销可能会或可能不会很大,具体取决于速度调用函数的。


对于第二部分我会这样做(例如虚拟输入,所以我保留了内部循环,如果不在内部循环中调用像 readPNG 这样的标量输入函数就可以避免):

cores <- list("A", "B", "C")
pics <- rep(list(vector("character",1)),length(cores))
for(i in 1:length(cores)) {
  imgs <- list("1","2","3")
  pics[[i]] <- vector("character",length(imgs))
  for(j in 1:length(imgs)) {
    pics[[i]][j] <- paste(cores[[i]],imgs[j],sep="/")
  }
}

这样你就不会在每次迭代时增长和复制,而是分配一次尽可能少的时间。

输出:

> pics
[[1]]
[1] "A/1" "A/2" "A/3"

[[2]]
[1] "B/1" "B/2" "B/3"

[[3]]
[1] "C/1" "C/2" "C/3"

为了更方便地访问,您可以执行 names(pics) <- cores 以获取:

> pics
$A
[1] "A/1" "A/2" "A/3"

$B
[1] "B/1" "B/2" "B/3"

$C
[1] "C/1" "C/2" "C/3"

因此您可以单独访问每个核心,例如 pics$A .

最后,如果你想处理所有文件 unlist(pics) 以获得所有文件的向量,你可以传递给 for 循环或 sapply 或任何其他函数一个向量作为输入。

> for(p in unlist(pics)) { print(p) }
[1] "A/1"
[1] "A/2"
[1] "A/3"
[1] "B/1"
[1] "B/2"
[1] "B/3"
[1] "C/1"
[1] "C/2"
[1] "C/3"

为了了解性能差异,我做了一些基准测试:

test.for <- function() {
  cores <- LETTERS[1:26]
  pics <- rep(list(vector("character",1)),length(cores))
  for(i in 1:length(cores)) {
    imgs <- 1:8
    pics[[i]] <- vector("character",length(imgs))
    for(j in 1:length(imgs)) {
      pics[[i]][j] <- paste(cores[[i]],imgs[j],sep="/")
    }
  }
  return(pics)
}

test.lapply <- function() {
  cores <- LETTERS[1:26]
  pics <- lapply( seq_along(cores), 
                   function(i) {
                     imgs <- 1:8
                     return(unlist(lapply( seq_along(imgs), 
                                          function(j) {
                                            paste( cores[[i]],
                                                   imgs[j],
                                                   sep="/"
                                                 )
                                          })
                                   )
                            )
                   })
  return(pics)
}

identical(test.for(),test.lapply())
microbenchmark(test.for(),test.lapply(),times=10L)

结果:

> identical(test.for(),test.lapply())
[1] TRUE
> microbenchmark(test.for(),test.lapply(),times=10L)
Unit: microseconds
          expr      min       lq     mean   median       uq      max neval
    test.for() 1241.166 1279.239 1392.894 1318.636 1405.375 1724.522    10
 test.lapply()  997.502 1013.393 1044.083 1024.152 1042.196 1155.090    10

在这个用例中,对于 26 个字母 x 8 个数字,for 循环并没有那么慢,但也许 lapply 也可以改进。