首页 > 解决方案 > 在 R 中保存循环的输出(到数据帧中)

问题描述

我有一个循环遍历公式列表(其中包括我的输入数据的不同变量)并在我的数据上运行 adonis 函数。

此循环输出一个数据框,其中包含公式中每个变量的 R 平方和 p 值,由 adonis 在 PERMANOVA 分析中计算得出。

如何保存每次迭代生成的数据框(即每个公式?)

示例数据:

sampleID <- c("ID1", "ID2", "ID3", "ID4", "ID5")
 variables <- c("stage", "type", "treatment")
 n <- length(variables)
 id <- unlist(lapply(1:n, function(i)combn(1:n,i,simplify=FALSE)),recursive=FALSE)
 formulas <- sapply(id,function(i) paste("data~",paste(variables[i],collapse="+")))
    
stage <- c(1,2,2,3,3)
type <- c("cancer", "dysplasia", "dysplasia", "cancer", "cancer")
treatment <- c("yes", "no", "yes", "no", "no")
metadata <- data.frame(cbind(sampleID, stage, type, treatment))
data <- data.frame(sampleID, X = sample(1:5), Y = sample(1:5), Z = sample(1:5), A = sample(1:5))
data <- data.matrix(data)

这是循环:

library(vegan)    
adonis_test <- for (i in 1:length(formulas)){
  z =  adonis(as.formula(formulas[i]) , data=metadata)
  return(data.frame(name = rownames(z$aov.tab), R2 = z$aov.tab$R2, 'Pr(>F)' = z$aov.tab$'Pr(>F)')[1,])}

标签: rdataframefunctionloops

解决方案


  1. for循环什么也不返回。
  2. 迭代地将行添加到data.frame一个或两个左右的作品中,但通常它的扩展性很差;请参阅The R Inferno中的“第 2 章:不断增长的对象”,了解一些有趣/轻松的阅读内容。长话短说:添加的每一行都要求将内存中的所有行复制到一个新对象中。这意味着当您有 500 行时,添加 1 行会导致需要将这 500 行(现在是内存中的 1000 行)复制到新对象。这对于小帧来说已经足够快了,但它的渐近增长是可怕的。

一般来说,最好将结果放入 alist中(这不涉及每次向其附加一个东西时复制所有数据),然后rbind一次将它们全部 -ing。

一般来说,我建议使用lapply这个,也许像

res <- lapply(seq_along(formulas), function(i) {
  z =  adonis(as.formula(formulas[i]) , data=metadata)
  data.frame(
    name = rownames(z$aov.tab), R2 = z$aov.tab$R2,
    'Pr(>F)' = z$aov.tab$'Pr(>F)'
  )[1,]
})
resDF <- do.call(rbind, res)

如果您真的更喜欢for循环(并且有一些优点),那么

res <- list()
for (i in seq_along(formulas)) {
  z =  adonis(as.formula(formulas[i]) , data=metadata)
  res[[i]] <- data.frame(
    name = rownames(z$aov.tab), R2 = z$aov.tab$R2,
    'Pr(>F)' = z$aov.tab$'Pr(>F)'
  )[1,]
}
resDF <- do.call(rbind, res)

for此处使用循环代替 的一个优点lapply:如果一个或多个迭代可能失败,则lapply将失败并且不返回任何内容,而for循环实现会将所有先前成功完成的模型存储在res. 当然,如果你想继续,你需要手动修改for循环索引以从你离开的地方继续(可选地跳过问题)。

但是……如果您希望发生这种情况,那么tryCatch您的朋友就是您,即使lapply实施也不会因一次失败而失去所有进展。做好这件事需要的不仅仅是这个问答主题,但要意识到有一些方法。


旁注:在编程中更具防御性,请使用seq_along(formulas)(or seq_len(length(formulas))) 而不是1:length(.). 为什么?以自动方式,如果formulas为空,则为length(.)0,并1:0返回长度为 2 的向量,如c(1, 0). 由于for循环的目的是仅在需要时执行,因此这是一个逻辑流错误,并且几乎肯定会Error:(或至少破坏或不必要地处理数据)。另一方面,seq_along(formulas)seq_len(length(.))​​解析到seq_len(0)which ... 返回integer(0),并且for循环根本不会迭代。


推荐阅读