EpidSim packagelibrary(EpidSim)
library(ape)
library(magrittr)
In epidsim, the reactions of the epidemiological model are described by the following formalism:
reactions <- list("S+I+=[beta*S*I]I+I", "I=[gamma*I]R")
One can alternatively adopt the formalism of the deSolve package and then translate to the formalism of the epidsim package thanks to the read.reactions() function:
reactions <- read.reactions(c("dI = beta * S * I - gamma * I",
"dS = -beta * S * I",
"dR = gamma * I"))
which gives:
reactions
#> [[1]]
#> [1] "I=[gamma*I]R"
#>
#> [[2]]
#> [1] "S+I+=[beta*S*I]I+I"
Let’s now define the names of the parameters:
paramNames <- list("beta", "gamma")
and of the compartments:
indivNames <- list("S", "*I", "R")
Note the * in front of the deme compartment. Next step is to build the simulator from reactions, paramNames and indivNames:
sir.simu <- build.simulator(reactions, paramNames, indivNames)
This typically takes 10-15’ as it involves compilation.
Let’s now define the initial values of the state variables:
initialStates <- c(I = 1, S = 9999, R = 0)
the time range of the simulations:
time <- c(0, 20)
and the parameters values:
theta <- list(c(gamma = 1, beta = 2e-4))
as well as the time step required by the \(\tau\)-leap and mixed algorithms:
dT <- .08
Sometimes the simulations fail. In order to bypass these failures, let’s define the following wrapper:
safe_run <- function(f, ...) {
out <- list()
while(! length(out)) {out <- f(...)}
out
}
And a safe version of sir.simu() is:
safe_sir.simu <- function(...) safe_run(sir.simu, ...)
traj_dm <- safe_sir.simu(
paramValues = theta,
initialStates = initialStates,
times = time)
which gives
head(traj_dm)
#> Time Reaction Nrep S I R
#> 1 0.0000000 init 1 9999 1 0
#> 2 0.6293988 S+I+=[beta*S*I]I+I 1 9998 2 0
#> 3 0.7088931 I=[gamma*I]R 1 9998 1 1
#> 4 0.9973502 S+I+=[beta*S*I]I+I 1 9997 2 1
#> 5 1.0454721 I=[gamma*I]R 1 9997 1 2
#> 6 1.0603469 S+I+=[beta*S*I]I+I 1 9996 2 2
and
plot(I ~ Time, traj_dm, type = "l", col = "red")
traj_tl <- safe_sir.simu(
paramValues = theta,
initialStates = initialStates,
times = time,
dT = dT)
which gives
head(traj_tl)
#> Time Reaction Nrep S I R
#> 1 0.00 init 1 9999 1 0
#> 2 0.40 S+I+=[beta*S*I]I+I 1 9998 2 0
#> 3 0.80 S+I+=[beta*S*I]I+I 1 9997 3 0
#> 4 0.88 S+I+=[beta*S*I]I+I 1 9996 4 0
#> 5 0.96 S+I+=[beta*S*I]I+I 2 9994 6 0
#> 6 1.04 S+I+=[beta*S*I]I+I 1 9993 7 0
and
plot(I ~ Time, traj_tl, type = "l", col = "blue")
traj_mm <- safe_sir.simu(
paramValues = theta,
initialStates = initialStates,
times = time,
dT = dT,
switchingMode = TRUE,
nTrials = 2)
which gives:
head(traj_mm)
#> Time Reaction Nrep S I R
#> 1 0.00000000 init 1 9999 1 0
#> 2 0.09781542 S+I+=[beta*S*I]I+I 1 9998 2 0
#> 3 0.23492386 S+I+=[beta*S*I]I+I 1 9997 3 0
#> 4 0.24771052 S+I+=[beta*S*I]I+I 1 9996 4 0
#> 5 0.47468083 I=[gamma*I]R 1 9996 3 1
#> 6 0.63350217 S+I+=[beta*S*I]I+I 1 9995 4 1
and
plot(I ~ Time, traj_mm, type = "l", col = "green")
nb <- 100
traj_dm100 <- purrr::rerun(nb,
safe_sir.simu(paramValues = theta,
initialStates = initialStates,
times = time))
traj_tl100 <- purrr::rerun(nb,
safe_sir.simu(paramValues = theta,
initialStates = initialStates,
times = time,
dT = dT))
traj_mm100 <- purrr::rerun(nb,
safe_sir.simu(paramValues = theta,
initialStates = initialStates,
times = time,
dT = dT,
switchingMode = TRUE,
nTrials = 2))
frame <- function(...) {
plot(NA, xlab = "Time", ylab = "I", xlim = c(0, 20), ...)
}
ylim <- c(0, max(unlist(purrr::map(c(traj_dm100, traj_tl100, traj_mm100), ~ .[["I"]]))))
alpha <- .5
frame(ylim = ylim)
purrr::walk(traj_dm100[1:10], with, lines(Time, I, col = adjustcolor("red", alpha)))
purrr::walk(traj_tl100[1:10], with, lines(Time, I, col = adjustcolor("blue", alpha)))
purrr::walk(traj_mm100[1:10], with, lines(Time, I, col = adjustcolor("green", alpha)))
legend("topright", c("direct method", "tau-leap", "mixed method"),
col = c("red", "blue", "green"), bty = "n", lty = 1, lwd = 2)
frame(ylim = ylim)
purrr::walk(traj_dm100, with, lines(Time, I, col = adjustcolor("red", alpha)))
frame(ylim = ylim)
purrr::walk(traj_tl100, with, lines(Time, I, col = adjustcolor("blue", alpha)))
frame(ylim = ylim)
purrr::walk(traj_mm100, with, lines(Time, I, col = adjustcolor("green", alpha)))
adaptivetau packagesir <- function(f = adaptivetau::ssa.adaptivetau) {
require(magrittr)
transitions <- list(c(S = -1, I = +1),
c(I = -1, R = +1))
lvrates <- function(x, params, t) {
with(c(x, params),
c(beta * S * I,
gamma * I)
)
}
f(initialStates, transitions, lvrates, as.list(unlist(theta)), tf = max(time)) %>%
data.frame() %>%
dplyr::rename(Time = time)
}
sir_dm <- purrr::rerun(nb, sir(adaptivetau::ssa.exact))
sir_tl <- purrr::rerun(nb, sir(adaptivetau::ssa.adaptivetau))
frame(ylim = ylim)
purrr::walk(sir_dm, with, lines(Time, I, col = adjustcolor("red", alpha)))
frame(ylim = ylim)
purrr::walk(sir_tl, with, lines(Time, I, col = adjustcolor("blue", alpha)))
The following function calculates quantiles from a list of simulation:
quantiles <- function(x) {
require(magrittr)
x %>%
dplyr::bind_rows() %>%
dplyr::select(Time, I) %>%
dplyr::mutate(Time = cut(Time, 0:ceiling(max(Time)), include.lowest = TRUE)) %>%
dplyr::group_by(Time) %>%
dplyr::summarise(list(tibble::enframe(quantile(I, c(.25, .5, .75)), "probs", "quantile"))) %>%
tidyr::unnest() %>%
tidyr::spread(probs, quantile) %>%
setNames(c("time", "lwr", "median", "upr"))
}
Let’s try it:
(traj_dm100q <- quantiles(traj_dm100))
#> # A tibble: 22 x 4
#> time lwr median upr
#> <fct> <dbl> <dbl> <dbl>
#> 1 [0,1] 2 4 6
#> 2 (1,2] 7 13 21
#> 3 (2,3] 20 36 62
#> 4 (3,4] 53 100 164
#> 5 (4,5] 136 252 402
#> 6 (5,6] 301 525 791
#> 7 (6,7] 595 905 1227
#> 8 (7,8] 954 1295 1483
#> 9 (8,9] 1246 1425 1509
#> 10 (9,10] 1181 1372 1483
#> # … with 12 more rows
And for the other ones:
traj_tl100q <- quantiles(traj_tl100)
traj_mm100q <- quantiles(traj_mm100)
sir_dm_q <- quantiles(sir_dm)
sir_tl_q <- quantiles(sir_tl)
The following function the quantiles to a plot:
add_distribution <- function(x, col = "red", alpha = .5) {
with(x, {
polygon(c(time, rev(time)), c(lwr, rev(upr)),
col = adjustcolor(col, alpha), border = NA)
lines(time, median, col = col)
})
}
Let’s try it: comparing the three methods of the EpidSim package:
alpha2 <- .3
frame(ylim = ylim)
add_distribution(traj_dm100q, "red", alpha2)
add_distribution(traj_tl100q, "blue", alpha2)
add_distribution(traj_mm100q, "green", alpha2)
legend("topright", c("direct method", "tau-leap", "mixed method"),
fill = adjustcolor(c("red", "blue", "green"), alpha2), bty = "n")
Comparing EpidSim and adaptivetau packages, for the direct method:
frame(ylim = ylim)
add_distribution(traj_dm100q, "red", alpha2)
add_distribution(sir_dm_q, "blue", alpha2)
legend("topright", c("EpidSim", "adaptivetau"),
fill = adjustcolor(c("red", "blue"), alpha2), bty = "n")
and for the tau-leap method:
frame(ylim = ylim)
add_distribution(traj_tl100q, "red", alpha2)
add_distribution(sir_tl_q, "blue", alpha2)
legend("topright", c("EpidSim", "adaptivetau"),
fill = adjustcolor(c("red", "blue"), alpha2), bty = "n")
TO DO: comparing execution times. But hard to do when
EpidSimsometimes fails.
Here we consider a simple BD process with 10 time intervals.
Specifying the reactions:
reactions <- list("I+=[beta*I]I+I", "I-=[gamma*I]")
Building the simulator:
gil.simu <- build.simulator(
Reactions = reactions,
ParameterNames = list("R0", "gamma", "beta"),
CompartmentNames = list("*I"))
A safe version of it:
safe_gil.simu <- function(...) safe_run(gil.simu, ...)
Initial states:
initialStates <- c(I = 1)
A list of parameters values:
theta <- tibble::tribble(
~R0, ~beta,
2.15, .8827628,
1.74, .714422,
1.80, .7390572,
1.43, .5871399,
1.35, .5542929,
1.54, .6323045,
2.03, .8334923,
1.39, .5707164,
1.26, .51734,
1.78, .7308455) %$%
purrr::map2(R0, beta, ~ c(R0 = .x, gamma = .410587333412841, beta = .y))
Time values:
times <- seq(1926.17, by = 8.683, le = 11)
dT:
dT <- .08
\(\tau\)-leap method:
trjctr_tl <- safe_gil.simu(
paramValues = theta,
initialStates = initialStates,
times = times,
dT = dT,
info = TRUE,
seed = 119700)
Mixed method:
trjctr_mm <- safe_gil.simu(
paramValues = theta,
initialStates = initialStates,
times = times,
dT = dT,
info = FALSE,
switchingMode = TRUE,
nTrials = 10)
Let’s compare the simulations:
plot(I ~ Time, trjctr_tl, type = "l", col = "red")
with(trjctr_mm, lines(Time, I, col = "blue"))
legend("center", c("tau-leap", "mixed method"),
col = c("red", "blue"), bty = "n", lty = 1, lwd = 2)
The sampling dates are in the following text file:
dates <- system.file("extdata", "BD-10_dates.txt", package = "EpidSim")
Here we used the simulated trajectory to simulate a phylogeny using the coalescent:
simulate.tree(
reactions = reactions,
trajectory = trjctr_tl,
dates = dates,
sampled = list("I" = 1), # the type of individuals that are sampled and their proportion (??)
root = "I", # type of individual at the root of the tree
outputTreeFile = "BD_Backward_Tree.newick",
isFullTrajectory = FALSE, # deads do not generate leaves
seed = 0,
nTrials = 1,
addInfos = FALSE, # additional info for each node
nexus = FALSE) # output format is newick
Reading the simulated tree and visualizing it:
treeFig <- read.tree("BD_Backward_Tree.newick")
ape::plot.phylo(treeFig, cex = .5)
Here we simulate phylogenies at the same time as the trajectories.
Reactions du modèle SIS
reactions <- list("S+I+=[beta*S*I]I+I", "I=[gamma*I]S")
Building the simulator.
This produces an ERROR:
forward.sis <- build.forward.tree.simulation(reactions,
list("gamma", "beta"),
list("*I", "S"))
Simulating the trajectory:
traj <- forward.sis(
paramValues = list(c(beta = .2, gamma = .1)),
initialStates = c(I = 1, S = 99999),
times = c(1980, 2020),
treeFile = "BD_Backward_Tree.newick",
samplingDates = system.file("extdata", "SIS-6.dates", package = "EpidSim"),
sampled = list("I" = 1))