如何以tidyverse风格为deSolve包组织一系列模拟(场景)?

huangapple go评论123阅读模式
英文:

How to organize a series of simulations (scenarios) for the deSolve package in tidyverse style?

问题

以下是翻译好的部分:

先前的帖子中,原帖作者提出了一个问题,询问如何使用deSolve包来组织一系列具有不同状态和参数的模拟场景。已经提供了一个解决方案,但我想知道是否可以以更适合管道友好风格的方式来实现,但不使用嵌套数据框。目标是将其用作使用清晰的表格数据结构制作最终ggplot的教学示例。

方法1:经典的“基本-R”风格

到目前为止,我通常使用apply函数的列表方法。然后,可以将生成的数据框列表适应于deSolve::plot方法:

  1. library("deSolve")
  2. model <- function(t, y, p) {
  3. with(as.list(c(y, p)), {
  4. dN <- r * N * (1 - N/K)
  5. list(c(dN))
  6. })
  7. }
  8. times <- 0:10
  9. y0 <- c(N = 0.5) # 状态变量
  10. p <- c(r = 0.2, K = 1) # 模型参数
  11. ## 运行单个模拟
  12. out <- ode(y0, times, model, p)
  13. plot(out)
  14. ## 创建包含一些状态和参数组合的数据框
  15. scenarios <- expand.grid(N = seq(0.5, 1.5, 0.2), K = 1, r = seq(0.2, 1, 0.2))
  16. ## 用于运行数据框的单行模拟的函数
  17. ## 注意 scenarios 和 scenario 之间的差异(复数/单数)
  18. simulate <- function(scenario) {
  19. ## 拆分场景设置为初始状态(y0)和参数(p)
  20. y0 <- scenario["N"]
  21. p <- scenario[c("r", "K")]
  22. ode(y0, times, model, p)
  23. }
  24. ## MARGIN = 1:每行是一个场景
  25. ## simplify = FALSE:函数应返回一个列表
  26. outputs <- apply(scenarios, MARGIN = 1, FUN = simulate, simplify = FALSE)
  27. ## plot.deSolve方法可与列表作为第二个参数一起使用
  28. plot(out, outputs)

方法2:迈向管道

基于这个示例,我创建了一个名为simulate_inout的函数,以一种ggplot兼容的方式返回单个场景的输入和输出。然后,应该在管道中为所有场景(所有行)调用它。

以下是示例:

  1. ## 保留输入和输出的simulate版本
  2. simulate_inout <- function(scenario) {
  3. scenario <- unlist(scenario)
  4. ## 拆分场景设置为初始状态(y0)和参数(p)
  5. y0 <- scenario["N"]
  6. p <- scenario[c("r", "K")]
  7. ## 集成模型
  8. output <- ode(y0, times, model, p)
  9. ## 复制输入的行
  10. input <- do.call("rbind", replicate(length(times),
  11. scenario, simplify = FALSE))
  12. ## 返回包含输入和输出的数据框
  13. cbind(input, output)
  14. }
  15. ## 单个场景
  16. simulate_inout(scenarios[1,])
  17. simulate_all <- function(scenarios) {
  18. ## 遍历所有行
  19. ret <- NULL
  20. for (i in 1:nrow(scenarios)) {
  21. ret <- rbind(ret, simulate_inout(scenarios[i,]))
  22. }
  23. data.frame(ret)
  24. }
  25. ## 使用ggplot绘图
  26. library("ggplot2")
  27. scenarios |> simulate_all() |> ggplot(aes(time, N.1)) +
  28. geom_path() + facet_grid(r ~ N)

问题

我希望以一致的tidyverse风格简化此代码,并摆脱simulate_all中的for循环以及其他特定技巧,如do.call

英文:

In a previous post the original poster asked a question on how to organize a series of scenarios with varying states and parameters for simulations with the deSolve package. A solution was given, but I wonder if it can be made easier in a pipeline-friendly style, but without the use of nested data frames. The goal is to use it as a teaching example using clear tabular data structures that can be fit to a final ggplot.

Approach 1: Classical "base-R" style

Until now, I usually use a list approach with the apply-function. The resulting list of data frames can then be fit to the deSolve::plot-method:

  1. library(&quot;deSolve&quot;)
  2. model &lt;- function(t, y, p) {
  3. with(as.list(c(y, p)), {
  4. dN &lt;- r * N * (1 - N/K)
  5. list(c(dN))
  6. })
  7. }
  8. times &lt;- 0:10
  9. y0 &lt;- c(N = 0.5) # state variables
  10. p &lt;- c(r = 0.2, K = 1) # model parameters
  11. ## run a single simulation
  12. out &lt;- ode(y0, times, model, p)
  13. plot(out)
  14. ## create a data frame with some combinations of states and parameters
  15. scenarios &lt;- expand.grid(N = seq(0.5, 1.5, 0.2), K = 1, r = seq(0.2, 1, 0.2))
  16. ## a function to run a simulation for a single line of the data frame
  17. ## note difference between scenarios and scenario (plural/singular)
  18. simulate &lt;- function(scenario) {
  19. ## split scenario settings to initial states (y0) and parameters (p)
  20. y0 &lt;- scenario[&quot;N&quot;]
  21. p &lt;- scenario[c(&quot;r&quot;, &quot;K&quot;)]
  22. ode(y0, times, model, p)
  23. }
  24. ## MARGIN = 1: each row is a scenario
  25. ## simplify = FALSE: function should return a list
  26. outputs &lt;- apply(scenarios, MARGIN = 1, FUN = simulate, simplify = FALSE)
  27. ## the plot.deSolve method works with lists as second argument
  28. plot(out, outputs)

Approach 2: A step towards a pipeline

Based on this example, I created a function simulate_inout that returns both, inputs and outputs in a ggplot-compatible way for a single scenario. This should then be called for all scenarios (all rows) in a pipeline.

The following works:

  1. ## version of simulate that preserves inputs and outputs
  2. simulate_inout &lt;- function(scenario) {
  3. scenario &lt;- unlist(scenario)
  4. ## split scenario settings to initial states (y0) and parameters (p)
  5. y0 &lt;- scenario[&quot;N&quot;]
  6. p &lt;- scenario[c(&quot;r&quot;, &quot;K&quot;)]
  7. ## integrate the model
  8. output &lt;- ode(y0, times, model, p)
  9. ## replicate rows of inputs
  10. input &lt;- do.call(&quot;rbind&quot;, replicate(length(times),
  11. scenario, simplify = FALSE))
  12. ## return a data frame with inputs and outputs
  13. cbind(input, output)
  14. }
  15. ## a single scenario
  16. simulate_inout(scenarios[1,])
  17. simulate_all &lt;- function(scenarios) {
  18. ## iterate over all rows
  19. ret &lt;- NULL
  20. for (i in 1:nrow(scenarios)) {
  21. ret &lt;- rbind(ret, simulate_inout(scenarios[i,]))
  22. }
  23. data.frame(ret)
  24. }
  25. ## plot with ggplot
  26. library(&quot;ggplot2&quot;)
  27. scenarios |&gt; simulate_all() |&gt; ggplot(aes(time, N.1)) +
  28. geom_path() + facet_grid(r ~ N)

Question

I would like to streamline this code in consistent tidyverse style and to get of the for-loop in simulate_all and other specific tricks like do.call.

答案1

得分: 1

You could use purrr::map_dfr to loop over the rows of your scenarios df. Requires some rewriting of your functions such that it takes the parameters itself as arguments. Additionally, I simplified your code a bit.

EDIT Replaced the superseded pmap_dfr by pmap(...) |&gt; list_rbind(). Use ... to pass the arguments to simulate_inout.

  1. library(deSolve)
  2. library(ggplot2)
  3. library(purrr)
  4. model &lt;- function(t, y, p) {
  5. N &lt;- y
  6. r &lt;- p[[1]]
  7. K &lt;- p[[2]]
  8. dN &lt;- r * N * (1 - N / K)
  9. list(dN)
  10. }
  11. times &lt;- 0:10
  12. simulate_inout &lt;- function(...) {
  13. args &lt;- list(...)
  14. y0 &lt;- args[[&quot;N&quot;]]
  15. p &lt;- args[c(&quot;r&quot;, &quot;K&quot;)]
  16. output &lt;- ode(y0, times, model, p)
  17. data.frame(args, output)
  18. }
  19. scenarios &lt;- expand.grid(
  20. N = seq(0.5, 1.5, 0.2),
  21. K = 1,
  22. r = seq(0.2, 1, 0.2)
  23. )
  24. scenarios |&gt;
  25. purrr::pmap(simulate_inout) |&gt;
  26. list_rbind() |&gt; # or dplyr::bind_rows()
  27. ggplot(aes(time, X1)) +
  28. geom_path() +
  29. facet_grid(r ~ N)

如何以tidyverse风格为deSolve包组织一系列模拟(场景)?<!-- -->

英文:

You could use purrr::map_dfr to loop over the rows of your scenarios df. Requires some rewriting of your functions such that it takes the parameters itself as arguments. Additionally I simplified your code a bit.

EDIT Replaced the superseded pmap_dfr by pmap(...) |&gt; list_rbind(). Use ... to pass the arguments to simulate_inout.

  1. library(deSolve)
  2. library(ggplot2)
  3. library(purrr)
  4. model &lt;- function(t, y, p) {
  5. N &lt;- y
  6. r &lt;- p[[1]]
  7. K &lt;- p[[2]]
  8. dN &lt;- r * N * (1 - N / K)
  9. list(dN)
  10. }
  11. times &lt;- 0:10
  12. simulate_inout &lt;- function(...) {
  13. args &lt;- list(...)
  14. y0 &lt;- args[[&quot;N&quot;]]
  15. p &lt;- args[c(&quot;r&quot;, &quot;K&quot;)]
  16. output &lt;- ode(y0, times, model, p)
  17. data.frame(args, output)
  18. }
  19. scenarios &lt;- expand.grid(
  20. N = seq(0.5, 1.5, 0.2),
  21. K = 1,
  22. r = seq(0.2, 1, 0.2)
  23. )
  24. scenarios |&gt;
  25. purrr::pmap(simulate_inout) |&gt;
  26. list_rbind() |&gt; # or dplyr::bind_rows()
  27. ggplot(aes(time, X1)) +
  28. geom_path() +
  29. facet_grid(r ~ N)

如何以tidyverse风格为deSolve包组织一系列模拟(场景)?<!-- -->

huangapple
  • 本文由 发表于 2023年8月5日 00:35:48
  • 转载请务必保留本文链接:https://go.coder-hub.com/76837768.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定