英文:
How to organize a series of simulations (scenarios) for the deSolve package in tidyverse style?
问题
以下是翻译好的部分:
在先前的帖子中,原帖作者提出了一个问题,询问如何使用deSolve
包来组织一系列具有不同状态和参数的模拟场景。已经提供了一个解决方案,但我想知道是否可以以更适合管道友好风格的方式来实现,但不使用嵌套数据框。目标是将其用作使用清晰的表格数据结构制作最终ggplot
的教学示例。
方法1:经典的“基本-R”风格
到目前为止,我通常使用apply
函数的列表方法。然后,可以将生成的数据框列表适应于deSolve::plot
方法:
library("deSolve")
model <- function(t, y, p) {
with(as.list(c(y, p)), {
dN <- r * N * (1 - N/K)
list(c(dN))
})
}
times <- 0:10
y0 <- c(N = 0.5) # 状态变量
p <- c(r = 0.2, K = 1) # 模型参数
## 运行单个模拟
out <- ode(y0, times, model, p)
plot(out)
## 创建包含一些状态和参数组合的数据框
scenarios <- expand.grid(N = seq(0.5, 1.5, 0.2), K = 1, r = seq(0.2, 1, 0.2))
## 用于运行数据框的单行模拟的函数
## 注意 scenarios 和 scenario 之间的差异(复数/单数)
simulate <- function(scenario) {
## 拆分场景设置为初始状态(y0)和参数(p)
y0 <- scenario["N"]
p <- scenario[c("r", "K")]
ode(y0, times, model, p)
}
## MARGIN = 1:每行是一个场景
## simplify = FALSE:函数应返回一个列表
outputs <- apply(scenarios, MARGIN = 1, FUN = simulate, simplify = FALSE)
## plot.deSolve方法可与列表作为第二个参数一起使用
plot(out, outputs)
方法2:迈向管道
基于这个示例,我创建了一个名为simulate_inout
的函数,以一种ggplot
兼容的方式返回单个场景的输入和输出。然后,应该在管道中为所有场景(所有行)调用它。
以下是示例:
## 保留输入和输出的simulate版本
simulate_inout <- function(scenario) {
scenario <- unlist(scenario)
## 拆分场景设置为初始状态(y0)和参数(p)
y0 <- scenario["N"]
p <- scenario[c("r", "K")]
## 集成模型
output <- ode(y0, times, model, p)
## 复制输入的行
input <- do.call("rbind", replicate(length(times),
scenario, simplify = FALSE))
## 返回包含输入和输出的数据框
cbind(input, output)
}
## 单个场景
simulate_inout(scenarios[1,])
simulate_all <- function(scenarios) {
## 遍历所有行
ret <- NULL
for (i in 1:nrow(scenarios)) {
ret <- rbind(ret, simulate_inout(scenarios[i,]))
}
data.frame(ret)
}
## 使用ggplot绘图
library("ggplot2")
scenarios |> simulate_all() |> ggplot(aes(time, N.1)) +
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:
library("deSolve")
model <- function(t, y, p) {
with(as.list(c(y, p)), {
dN <- r * N * (1 - N/K)
list(c(dN))
})
}
times <- 0:10
y0 <- c(N = 0.5) # state variables
p <- c(r = 0.2, K = 1) # model parameters
## run a single simulation
out <- ode(y0, times, model, p)
plot(out)
## create a data frame with some combinations of states and parameters
scenarios <- expand.grid(N = seq(0.5, 1.5, 0.2), K = 1, r = seq(0.2, 1, 0.2))
## a function to run a simulation for a single line of the data frame
## note difference between scenarios and scenario (plural/singular)
simulate <- function(scenario) {
## split scenario settings to initial states (y0) and parameters (p)
y0 <- scenario["N"]
p <- scenario[c("r", "K")]
ode(y0, times, model, p)
}
## MARGIN = 1: each row is a scenario
## simplify = FALSE: function should return a list
outputs <- apply(scenarios, MARGIN = 1, FUN = simulate, simplify = FALSE)
## the plot.deSolve method works with lists as second argument
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:
## version of simulate that preserves inputs and outputs
simulate_inout <- function(scenario) {
scenario <- unlist(scenario)
## split scenario settings to initial states (y0) and parameters (p)
y0 <- scenario["N"]
p <- scenario[c("r", "K")]
## integrate the model
output <- ode(y0, times, model, p)
## replicate rows of inputs
input <- do.call("rbind", replicate(length(times),
scenario, simplify = FALSE))
## return a data frame with inputs and outputs
cbind(input, output)
}
## a single scenario
simulate_inout(scenarios[1,])
simulate_all <- function(scenarios) {
## iterate over all rows
ret <- NULL
for (i in 1:nrow(scenarios)) {
ret <- rbind(ret, simulate_inout(scenarios[i,]))
}
data.frame(ret)
}
## plot with ggplot
library("ggplot2")
scenarios |> simulate_all() |> ggplot(aes(time, N.1)) +
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(...) |> list_rbind()
. Use ...
to pass the arguments to simulate_inout
.
library(deSolve)
library(ggplot2)
library(purrr)
model <- function(t, y, p) {
N <- y
r <- p[[1]]
K <- p[[2]]
dN <- r * N * (1 - N / K)
list(dN)
}
times <- 0:10
simulate_inout <- function(...) {
args <- list(...)
y0 <- args[["N"]]
p <- args[c("r", "K")]
output <- ode(y0, times, model, p)
data.frame(args, output)
}
scenarios <- expand.grid(
N = seq(0.5, 1.5, 0.2),
K = 1,
r = seq(0.2, 1, 0.2)
)
scenarios |>
purrr::pmap(simulate_inout) |>
list_rbind() |> # or dplyr::bind_rows()
ggplot(aes(time, X1)) +
geom_path() +
facet_grid(r ~ N)
<!-- -->
英文:
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(...) |> list_rbind()
. Use ...
to pass the arguments to simulate_inout
.
library(deSolve)
library(ggplot2)
library(purrr)
model <- function(t, y, p) {
N <- y
r <- p[[1]]
K <- p[[2]]
dN <- r * N * (1 - N / K)
list(dN)
}
times <- 0:10
simulate_inout <- function(...) {
args <- list(...)
y0 <- args[["N"]]
p <- args[c("r", "K")]
output <- ode(y0, times, model, p)
data.frame(args, output)
}
scenarios <- expand.grid(
N = seq(0.5, 1.5, 0.2),
K = 1,
r = seq(0.2, 1, 0.2)
)
scenarios |>
purrr::pmap(simulate_inout) |>
list_rbind() |> # or dplyr::bind_rows()
ggplot(aes(time, X1)) +
geom_path() +
facet_grid(r ~ N)
<!-- -->
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论