【问题标题】:Saving the outputs of a loop in R (into a dataframe)在 R 中保存循环的输出(到数据帧中)
【发布时间】:2021-09-29 00:01:59
【问题描述】:

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

此循环输出一个数据框,其中包含公式中每个变量的 R 平方和 p 值,由阿多尼斯在 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,])}

【问题讨论】:

  • 您没有包含adonis 的代码,因此无法重现
  • 为什么不在每次迭代后为其创建一个空的dataframerbind
  • @JasonMathews,这是个坏主意,imo。向data.frame 迭代地添加行对一个或两个来说效果很好,但重复这样做会很差(参见The R Inferno 中的“Chapter 2: Growing Objects”)。
  • 感谢您的信息

标签: r dataframe function loops


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

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

一般来说,我建议为此使用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)(或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) ... 返回 integer(0)for 循环根本不会迭代。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-01-03
    • 2014-08-13
    • 1970-01-01
    • 2020-05-30
    • 2019-10-24
    • 1970-01-01
    • 1970-01-01
    • 2012-03-30
    相关资源
    最近更新 更多