【问题标题】:adding geom_* in a for loop在 for 循环中添加 geom_*
【发布时间】:2020-07-22 07:10:01
【问题描述】:

我想在一张图表中比较真实世界的数据和模拟数据。代码应该接受任意数量的线来绘制。我想出了这个:

simulationRuns <- 5 #Variable to be changed depending on how many simulations were made

plotLoop <- ggplot() + 
  geom_line(data = relWorldData, 
            mapping = aes(x = DateTime, y = VALUE, color = "realWorldData"))

for (i in 1:simulationRuns){
    plotLoop <- plotLoop +
      geom_line(data = listOfSimResults[[i]], 
                mapping = aes(x = DateTime, y = VALUE, color = paste0("simRun-", i)))
  }

figureLoop <- ggplotly(plotLoop)

问题是,所有行都显示为 simRun-5,因此不是独立的 -

我是 R 新手,所以请多多包涵;) 在此先感谢帕特里克


后续问题 bc。在评论中阅读代码很糟糕:

我阅读了 Lapply 并将代码重写为:

plotLoop <- ggplot() + geom_line(data = relWorldData, mapping = aes(x = DateTime, y = VALUE, color = "RealWorldData"))

  addGeomLine <- function (i, obj){
    obj <- obj +
      geom_line(data = listOfSimResults[[i]], mapping = aes(x = DateTime, y = VALUE, color = paste0("simRun-", i)))
  }
  lapply(1:runs, addGeomLine, plotLoop)

  figureLoop <- ggplotly(plotLoop)

这一次,只显示 RealWorldData,但没有显示任何 Simulations。你能告诉我我错过了什么吗?

【问题讨论】:

    标签: r for-loop ggplot2


    【解决方案1】:

    欢迎来到 SO!

    您遇到了一个微妙的问题,让很多经验远比您自己丰富的人感到困惑。问题是ggplot2 评估懒惰。简而言之,这意味着当你告诉它你想要什么时,它会“记下”它需要做什么,但实际上直到最后一刻才做任何事情。

    在这里,你告诉 ggplot 你想在你的for 循环中添加一个geom。 ggplot 记下geom 的定义,但不对其进行评估。 “在最后一刻”是当你打电话给ggplotly。现在ggplot 意识到它有一些工作要做。对于每个geom,它注意到它需要知道i 的值。所以它查找它并找到值5。因此你的问题。

    有几种方法可以解决这个问题。使用您的代码,我的首选选项是用lapply 替换for 循环。与for 循环不同,lapply 强制在执行时评估变量。

    我相信您也可以保留for 循环并将每个对i 的引用包装在force() 中,尽管我没有亲自尝试过。

    在我看来,从长远来看,最好的方法是让您的工作流程tidy 并完全避免使用for 循环或lapply。这还将为您带来更紧凑、更健壮和可读的代码的好处,这些代码几乎肯定会运行得更快。 [前几天我做了一些工作,将类似于你的循环转换为一个整洁的解决方案,运行时间从近 40 秒减少到 2 秒以下。]

    另外,请阅读this post 以获取有关如何创建最小工作示例的建议。提供 MWE 将最大限度地提高您获得有用答案的机会。

    更新

    扩展我对使用整洁数据方法的优势的评论...

    首先合成一些您没有提供的数据。我将尝试匹配您的数据结构,但不匹配您的值。与您的数据集的唯一区别是我添加了一个 ID 变量来标识每个观察来自的模拟运行/真实世界数据集。

    library(lubridate)
    library(tidyverse)
    
    inVivoBG <- tibble(
                  ID="Real-world data",
                  DateTime2=seq(as_date("2006-03-01"), as_date("2015-03-01"), "3 months"),
                  VALUE=100 + rnorm(37, mean=150, sd=20)
                ) 
    
    listOfSimResults <- lapply(
                          1:5, 
                          function(x) {
                            tibble(
                              ID=paste0("simRun-", x),
                              DateTime2=seq(as_date("2006-03-01"), as_date("2015-03-01"), "3 months"),
                              VALUE=100 + rnorm(37, mean=150, sd=20)
                            )
                          }
                        )
    

    现在将各种数据帧合并为一个。

    data <- bind_rows(inVivoBG, listOfSimResults)
    

    此时,您的情节的构建是单行调用。

    data %>% 
      ggplot() + 
        geom_line(mapping = aes(x = DateTime2, y = VALUE, color = ID)) 
    

    给予

    这种方法无需自定义函数或lapply。它在所需的行数及其标签方面也很健壮。就个人而言,我也认为它更容易理解。

    【讨论】:

    • 快速跟进问题:我阅读了 lapply 并重新编写了代码:plotLoop &lt;- ggplot() + geom_line(data = inVivoBG, mapping = aes(x = DateTime2, y = VALUE, color = "RealWorldData")) addGeomLine &lt;- function (i, obj){ obj &lt;- obj + geom_line(data = listOfSimResults[[i]], mapping = aes(x = DateTime, y = subjE.Gp.conc, color = paste0("simRun-", i))) } lapply(1:runs, addGeomLine, plotLoop) figureLoop &lt;- ggplotly(plotLoop) 这次只绘制了 RealWorldData 线,没有绘制其他线。你能告诉我我错过了什么吗?
    • 是的。您的函数 addGeomLine 没有返回任何内容。删除obj &lt;- 或添加return(obj)
    • 也考虑过,但即使添加了这个,它也不起作用。仅显示 RealWorldData
    • 这说明了为什么提供 MWE 如此有帮助,以便您的代码可以被测试。您的问题是您使用本地分配来更新 addGeomLine 函数中的绘图对象。你需要一个全局分配。这个版本的函数给出了想要的结果。 addGeomLine &lt;- function (i) { plotLoop &lt;&lt;- plotLoop + geom_line(data = listOfSimResults[[force(i)]], mapping = aes(x = DateTime2, y = VALUE, color = paste0("simRun-", force(i)))) } lapply(1:5, addGeomLine)。但我敦促您在我更新的答案中采用该解决方案。
    猜你喜欢
    • 2020-07-09
    • 2012-06-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-01-01
    • 1970-01-01
    相关资源
    最近更新 更多