#' Logistic function
#'
#' @param theta vector of named parameters
#' @param x time
<- function(theta, x) {
logistic_f <- theta[["k"]]
k <- theta[["tau"]]
tau <- theta[["r"]]
r / ( 1+exp( -r*( x - tau ) ) )
k
}
#' First derivative of the logistic function
#'
#' @param theta vector of named parameters
#' @param x time
<- function(theta, x) {
logistic_f_first_d <- theta[["k"]]
k <- theta[["tau"]]
tau <- theta[["r"]]
r
* r * exp( -r * (x - tau) )) / (1+ exp( -r * (x - tau) ))^2
(k
}
#' Second derivative of the logistic function
#'
#' @param theta vector of named parameters
#' @param x time
<- function(theta, x) {
logistic_f_second_d <- theta[["k"]]
k <- theta[["tau"]]
tau <- theta[["r"]]
r
*((2*r^2 * exp(-2*r*(x - tau)))/
kexp(-r*(x - tau)) + 1)^3 -
(^2*exp(-r*(x - tau)))/(exp(-r*(x - tau)) + 1)^2)
(r }
6 Background on Phenomenological Models
This chapter provides a bit of background about phenomenological models that can be used to model an epidemic.
6.1 Simple phenomenological models
We will present three models to estimate the number of cases and the number of deaths: the logistic model, the Gompertz model, and the Richards model.
6.1.1 A Generic Equation
Wang, Wu, and Yang (2012) develop the similarity between the SIR model and the Richards population model Richards (1959) which lead them to consider the following growth equation for confirmed cases noted
A general solution to this equation has the form:
A crucial question is to characterize the speed at which the process will reach its peak and when. Tsoularis and Wallace (2002) have shown that the value of the peak is given by the value of
6.1.2 Logistic Model
The logistic model initiated by Verhulst (1845) provides the most simple solution to the Equation 6.1 and corresponds to
It has been recently applied to study the evolution of an epidemic Ma (2020). We have introduced a parameterization where
In R, we define the logistic function as follows:
Now, let us make some graph to give an idea of how the dynamic changes with the parameters
library(tidyverse)
── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
✔ dplyr 1.1.2 ✔ readr 2.1.4
✔ forcats 1.0.0 ✔ stringr 1.5.0
✔ ggplot2 3.4.2 ✔ tibble 3.2.1
✔ lubridate 1.9.2 ✔ tidyr 1.3.0
✔ purrr 1.0.1
── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
✖ dplyr::filter() masks stats::filter()
✖ dplyr::lag() masks stats::lag()
ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
library(scales)
Attaching package: 'scales'
The following object is masked from 'package:purrr':
discard
The following object is masked from 'package:readr':
col_factor
<-function(a.gplot){
g_legend<- ggplot_gtable(ggplot_build(a.gplot))
tmp <- which(sapply(tmp$grobs, function(x) x$name) == "guide-box")
leg <- tmp$grobs[[leg]]
legend return(legend)}
The different values for
<-
situations expand_grid(tau = c(25,35), r = c(.15, .25, .35)) |>
mutate(k = 5000)
Let us apply, for a set of parameters, at different time horizon (from 0 to 60 by steps of .1) the logistic function, its first and second derivatives with respect to time. To do so, we define a “simulation” function:
#' Simulation of the logistic model for some parameters
#'
#' @param i row number of situations
<- function(i) {
simu <- situations |> slice(i)
current_params <- c(as_vector(current_params)) |> as.list()
current_params
<- 60
n <- .01
step <- logistic_f(
sim theta = current_params,
x = seq(0,n, by = step)
)<- logistic_f_first_d(
simD theta = current_params,
x = seq(0,n, by = step)
)<- logistic_f_second_d(
simD2 theta = current_params,
x = seq(0,n, by = step)
)
tibble(
t = seq(0, n, by = step),
k = current_params[["k"]],
tau = current_params[["tau"]],
r = current_params[["r"]],
sim = sim
|>
) mutate(
simD = simD,
simD2 = simD2
) }
Let us do so for all the different sets of parameters:
<- map_df(1:nrow(situations), simu) simu_res
We would like to show the thresholds on the graph:
<-
threshold_times |>
simu_res group_by(k, tau, r) |>
summarise(threshold_time = unique(tau)) |>
ungroup() |>
mutate(
r = str_c("$\\r = ", r, "$"),
tau = str_c("$\\tau = ", tau, "$")
)
`summarise()` has grouped output by 'k', 'tau'. You can override using the
`.groups` argument.
threshold_times
# A tibble: 6 × 4
k tau r threshold_time
<dbl> <chr> <chr> <dbl>
1 5000 "$\\tau = 25$" "$\\r = 0.15$" 25
2 5000 "$\\tau = 25$" "$\\r = 0.25$" 25
3 5000 "$\\tau = 25$" "$\\r = 0.35$" 25
4 5000 "$\\tau = 35$" "$\\r = 0.15$" 35
5 5000 "$\\tau = 35$" "$\\r = 0.25$" 35
6 5000 "$\\tau = 35$" "$\\r = 0.35$" 35
Now, we are ready to create the plots:
<-
p_simu_logis ggplot(
data = simu_res |>
mutate(
r = str_c("$\\r = ", r, "$"),
tau = str_c("$\\tau = ", tau, "$")
),mapping = aes(x = t)
+
) geom_line(mapping = aes(y = sim, linetype = "sim")) +
geom_line(mapping = aes(y = simD*10, linetype = "simD")) +
geom_line(mapping = aes(y = simD2*10, linetype = "simD2")) +
geom_vline(
data = threshold_times,
mapping = aes(xintercept = threshold_time),
colour = "red", linetype = "dotted") +
facet_grid(
~tau,
rlabeller = as_labeller(
::TeX,
latex2expdefault = label_parsed
)+
) labs(x = "Time", y = latex2exp::TeX("$F(t)$")) +
scale_y_continuous(
labels = scales::comma,
sec.axis = sec_axis(
~./10,
name = latex2exp::TeX(
"$\\partial F(t) / \\partial t$, $\\partial^2 F(t) / \\partial t^2$"
),labels = comma)) +
scale_linetype_manual(
NULL,
values = c("sim" = "solid", "simD" = "dashed", "simD2" = "dotdash"),
labels = c(
"sim" = latex2exp::TeX("$F(t)$"),
"simD" = latex2exp::TeX("$\\partial F(t) / \\partial t$"),
"simD2" = latex2exp::TeX("$\\partial^2 F(t) / \\partial t^2$"))) +
theme(axis.ticks.y = element_blank())
+
p_simu_logis theme_minimal() +
theme(legend.position = "bottom")
6.1.2.1 Key moments
Let us focus on the key moment of the epidemics. Let us illustrate them using one of the scenarios.
<- simu(1) data_sim
The starting point:
<-
df_starting %>%
data_sim filter(sim >= 1) %>%
slice(1)
The acceleration point
<-
df_acceleration %>%
data_sim filter(simD2 == max(simD2)) %>%
slice(1)
The peak:
<-
df_peak %>%
data_sim filter(simD == max(simD)) %>%
slice(1)
The deceleration point:
<-
df_deceleration %>%
data_sim filter(simD2 == min(simD2)) %>%
slice(1)
The return point:
<-
df_return %>%
data_sim filter(sim > k - 1) %>%
slice(1)
Let us reshape the data:
<-
df_plot_key_moments %>%
data_sim gather(key, value, sim, simD, simD2)
<-
df_plot_key_moments_points %>% mutate(label = "$t_A$") %>%
df_acceleration bind_rows(df_peak %>% mutate(label = "$t_P$")) %>%
bind_rows(df_deceleration %>% mutate(label = "$t_D$"))
And the plot:
<-
p_key_moments ggplot() +
geom_hline(yintercept = 0, col = "grey") +
geom_line(
data = df_plot_key_moments,
mapping = aes(x = t, y = value, linetype = key)
+
) geom_segment(
data = df_plot_key_moments_points,
mapping = aes(x = t, xend = t, y = 0, yend = sim),
linetype = "dotted", colour = "red"
+
) geom_point(
data = df_plot_key_moments_points,
mapping = aes(x = t, y = sim), size = 2
+
) geom_point(
data = df_plot_key_moments_points,
mapping = aes(x = t, y = sim),
size = 1, colour = "white"
+
) geom_text(
data = df_plot_key_moments_points,
mapping = aes(x = t-2, y = sim + .05*k, label = label)
+
) labs(x = "Time", y = latex2exp::TeX("$F(t)$")) +
scale_linetype_manual(
NULL,
values = c("sim" = "solid", "simD" = "dashed", "simD2" = "dotdash"),
labels = c(
"sim" = latex2exp::TeX("$F(t)$"),
"simD" = latex2exp::TeX("$\\partial F(t) / \\partial t$"),
"simD2" = latex2exp::TeX("$\\partial^2 F(t) / \\partial t^2$")
)+
) theme(axis.text = element_blank(), axis.ticks = element_blank())
p_key_moments
6.1.3 Gompertz Model
The Gompertz model (Gompertz 1825) has three parameters, but corresponds to alternative restrictions with
In R, this translates to:
#' Gompertz function with three parameters
#'
#' @param theta vector of named parameters
#' @param x time
<- function(theta, x) {
gompertz_f <- theta[["k"]]
k <- theta[["tau"]]
tau <- theta[["r"]]
r *exp( -exp( -r * (x - tau) ) )
k
}#' First order derivative of Gompertz wrt x
#'
#' @param theta vector of named parameters
#' @param x time
<- function(theta, x) {
gompertz_f_first_d <- theta[["k"]]
k <- theta[["tau"]]
tau <- theta[["r"]]
r
* (x - tau) * exp(r * (tau - x) - exp(r * (tau - x)))
k
}
#' Second order derivative of Gompertz
#'
#' @param theta vector of named parameters
#' @param x time
<- function(theta, x) {
gompertz_f_second_d <- theta[["k"]]
k <- theta[["tau"]]
tau <- theta[["r"]]
r
-k * r^2 * exp(-r * (x - tau)) *
exp(-exp(-r * (x - tau))) +
* r^2 * (exp(-r * (x - tau)))^2 * exp(-exp(-r * (x - tau)))
k }
Let us make some graph to give an idea of how the dynamic changes with the parameters
<- 5000
k <-
situations expand_grid(tau = c(25,35), r = c(.15, .25, .35)) |>
mutate(k = k)
#' Simulation of the logistic model for some parameters
#'
#' @param i row number of situations
<- function(i) {
simu_gomp <- situations |> slice(i)
current_params <- c(as_vector(current_params)) |> as.list()
current_params
<- 60
n <- .01
step <- gompertz_f(theta = current_params, x = seq(1,n, by = step))
sim <- gompertz_f_first_d(theta = current_params, x = seq(1,n, by = step))
simD <- gompertz_f_second_d(theta = current_params, x = seq(1,n, by = step))
simD2
tibble(
t = seq(1, n, by = step),
k = current_params[["k"]],
tau = current_params[["tau"]],
r = current_params[["r"]],
sim = sim) |>
mutate(simD = simD,
simD2 = simD2)
}
The values of
<- map_df(1:nrow(situations), simu_gomp) simu_gomp_res
The threshold for each scenario:
<-
threshold_times |>
simu_gomp_res group_by(k, tau, r) |>
summarise(threshold_time = unique(tau)) |>
ungroup() |>
mutate(
r = str_c("$\\r = ", r, "$"),
tau = str_c("$\\tau = ", tau, "$")
)
`summarise()` has grouped output by 'k', 'tau'. You can override using the
`.groups` argument.
And we can plot the results:
<-
p_simu_gomp ggplot(
data = simu_gomp_res |>
mutate(
r = str_c("$\\r = ", r, "$"),
tau = str_c("$\\tau = ", tau, "$")
),mapping = aes(x = t)) +
geom_line(mapping = aes(y = sim, linetype = "sim")) +
geom_line(mapping = aes(y = simD, linetype = "simD")) +
geom_line(mapping = aes(y = simD2, linetype = "simD2")) +
facet_grid(
~tau,
rlabeller = as_labeller(
::TeX,
latex2expdefault = label_parsed
)+
) geom_vline(
data = threshold_times,
mapping = aes(xintercept = threshold_time),
colour = "red", linetype = "dotted") +
labs(x = "Time", y = latex2exp::TeX("$F(t)$")) +
scale_linetype_manual(
NULL,
values = c("sim" = "solid", "simD" = "dashed", "simD2" = "dotdash"),
labels = c("sim" = latex2exp::TeX("$F(t)$"),
"simD" = latex2exp::TeX("$\\partial F(t) / \\partial t$"),
"simD2" = latex2exp::TeX("$\\partial^2 F(t) / \\partial t^2$")
)+
) theme(axis.ticks.y = element_blank())
+
p_simu_gomp theme_minimal() +
theme(legend.position = "bottom")
6.1.4 Richards Model
The question “what happens with
In R, this translates to the following functions:
#' Richards function with four parameters
#'
#' @param theta vector of named parameters
#' @param x time
<- function(theta, x) {
richards_f <- theta[["k"]]
k <- theta[["tau"]]
tau <- theta[["r"]]
r <- theta[["delta"]]
delta
/ (1 + delta * exp(-r * delta * (x - tau)))^(1 / delta)
k
}
#' First order derivative of Richards function wrt time (x)
#'
#' @param theta vector of named parameters
#' @param x time
<- function(theta, x) {
richards_f_first_d <- theta[["k"]]
k <- theta[["tau"]]
tau <- theta[["r"]]
r <- theta[["delta"]]
delta
* k * r * exp(delta * (-r) * (x - tau)) *
delta * exp(delta * (-r) * (x - tau)) + 1)^(-1 / delta - 1)
(delta
}
#' Second order derivative of Richards function wrt time (x)
#'
#' @param theta vector of named parameters
#' @param x time
<- function(theta, x) {
richards_f_second_d <- theta[["k"]]
k <- theta[["tau"]]
tau <- theta[["r"]]
r <- theta[["delta"]]
delta
* ((-1 / delta - 1) *
k ^3 * r^2 * (-exp(-2 * delta * r * (x - tau))) *
delta* exp(delta * (-r) * (x - tau)) + 1)^(-1 / delta - 2) -
(delta ^2 * r^2 * exp(delta * (-r) * (x - tau)) *
delta* exp(delta * (-r) * (x - tau)) + 1)^(-1 / delta - 1))
(delta }
Again, let us provide different scenarios to plot the curves for
<-
situations expand_grid(delta = c(.5, 1.5), r = c(.15, .25, .35)) |>
mutate(k = 5000, tau = 25)
We create a function to get the values of
#' Simulation of Richards model for some parameters
#'
#' @param i row number of situations
<- function(i) {
simu_richards <- situations |> slice(i)
current_params <- c(as_vector(current_params)) |> as.list()
current_params
<- 60
n <- .01
step <- richards_f(theta = current_params, x = seq(1,n, by = step))
sim <- richards_f_first_d(theta = current_params, x = seq(1,n, by = step))
simD <- richards_f_second_d(theta = current_params, x = seq(1,n, by = step))
simD2
tibble(
t = seq(1, n, by = step),
k = current_params[["k"]],
tau = current_params[["tau"]],
r = current_params[["r"]],
delta = current_params[["delta"]],
sim = sim
|>
) mutate(simD = simD,
simD2 = simD2)
}
This function is then applied to the different scenarios:
<- map_df(1:nrow(situations), simu_richards) simu_richards_res
The thresholds:
<-
threshold_times |>
simu_richards_res group_by(k, tau, r, delta) |>
summarise(threshold_time = unique(tau)) |>
ungroup() |>
mutate(
r = str_c("$r = ", r, "$"),
delta = str_c("$\\delta = ", delta, "$")
)
`summarise()` has grouped output by 'k', 'tau', 'r'. You can override using the
`.groups` argument.
<-
p_simu_richards ggplot(
data = simu_richards_res |>
mutate(
r = str_c("$r = ", r, "$"),
delta = str_c("$\\delta = ", delta, "$")
),mapping = aes(x = t)
+
) geom_line(mapping = aes(y = sim, linetype = "sim")) +
geom_line(mapping = aes(y = simD * 10, linetype = "simD")) +
geom_line(mapping = aes(y = simD2 * 10, linetype = "simD2")) +
facet_grid(
~ delta,
r labeller = as_labeller(
::TeX,
latex2expdefault = label_parsed
)+
) geom_vline(
data = threshold_times,
mapping = aes(xintercept = threshold_time),
colour = "red", linetype = "dotted") +
labs(x = "Time", y = latex2exp::TeX("$F(t)$")) +
scale_y_continuous(
labels = comma,
breaks = seq(0, 5000, by = 1000),
sec.axis = sec_axis(
~./10,
name = latex2exp::TeX(
"$\\partial F(t)/\\partial t$, $\\partial^2 F(t)/\\partial t^2$"
),labels = comma)
+
) scale_linetype_manual(
NULL,
values = c("sim" = "solid", "simD" = "dashed", "simD2" = "dotdash"),
labels = c("sim" = latex2exp::TeX("$F(t)$"),
"simD" = latex2exp::TeX("$\\partial F(t) / \\partial t$"),
"simD2" = latex2exp::TeX("$\\partial^2 F(t) / \\partial t^2$"))
+
) theme(axis.ticks.y = element_blank())
+
p_simu_richards theme_minimal() +
theme(legend.position = "bottom")
6.2 Double sigmoid functions to account for a second wave
One possibility to account for two distinct phases is to use a double sigmoid. This is done by adding or multiplying two sigmoid functions, as follows:
We limit ourselves here to the presentation of a double-Gompertz curve:
6.2.1 Double Logistic
The double logistic function, its first and second derivatives with respect to time can be coded as follows:
# Double logistic
<- function(theta, x) {
double_logistic_f <- theta[["K1"]]
K1 <- theta[["tau1"]]
tau1 <- theta[["r1"]]
r1 <- theta[["K2"]]
K2 <- theta[["tau2"]]
tau2 <- theta[["r2"]]
r2
/(1 + exp(-r1 * (x - tau1)))) + ((K2 - K1)/(1 + exp(-r2 * (x - tau2))))
(K1
}# Double logistic in two parts
<- function(theta, x) {
double_logistic_f_2 <- theta[["K1"]]
K1 <- theta[["tau1"]]
tau1 <- theta[["r1"]]
r1 <- theta[["K2"]]
K2 <- theta[["tau2"]]
tau2 <- theta[["r2"]]
r2
<- K1 / (1 + exp(-r1 * (x - tau1)))
f_1 <- (K2 - K1)/(1 + exp(-r2 * (x - tau2)))
f_2 tibble(x = x, f_1 = f_1, f_2 = f_2)
}
# First derivative
<- function(theta, x) {
double_logistic_f_first_d <- theta[["K1"]]
K1 <- theta[["tau1"]]
tau1 <- theta[["r1"]]
r1 <- theta[["K2"]]
K2 <- theta[["tau2"]]
tau2 <- theta[["r2"]]
r2
* K1 * exp(-r1 * (x - tau1))) / ((exp(-r1 * (x-tau1)) + 1)^2) +
(r1 * (K2-K1) * exp(-r2 * (x - tau2))) / ((exp(-r2 * (x-tau2)) + 1)^2)
(r2
}# Second derivative
<- function(theta, x) {
double_logistic_f_second_d <- theta[["K1"]]
K1 <- theta[["tau1"]]
tau1 <- theta[["r1"]]
r1 <- theta[["K2"]]
K2 <- theta[["tau2"]]
tau2 <- theta[["r2"]]
r2
* ((2 * r1^2 * exp(-2 * r1 * (x - tau1)))/(exp(-r1 * (x - tau1)) + 1)^3 -
K1 ^2 * exp(- r1 * (x - tau1)))/(exp(-r1 * (x - tau1)) + 1)^2) +
( r1
-K1) *
(K22 * r2^2 * exp(-2 * r2 * (x - tau2)))/(exp(-r2 * (x - tau2)) + 1)^3 -
((^2 * exp(- r2 * (x - tau2)))/(exp(-r2 * (x - tau2)) + 1)^2)
( r2
}
And now, let us make some illustrations by varying
<-
situations expand_grid(r1 = c(.2, .5), r2 = c(.2, .5)) |>
mutate(
K1 = 2500,
K2 = 5000,
tau1 = 20,
tau2 = 40
)
The function that simulates
#' Simulation of the logistic model for some parameters
#'
#' @param i row number of situations
<- function(i) {
simu <- situations |> slice(i)
current_params <- c(as_vector(current_params)) |> as.list()
current_params
<- 60
n <- .01
step <- double_logistic_f(theta = current_params, x = seq(1,n, by = step))
sim <- double_logistic_f_first_d(
simD theta = current_params, x = seq(1,n, by = step))
<- double_logistic_f_second_d(
simD2 theta = current_params, x = seq(1,n, by = step))
tibble(
t = seq(1, n, by = step),
K1 = current_params[["K1"]],
r1 = current_params[["r1"]],
tau1 = current_params[["tau1"]],
K2 = current_params[["K2"]],
r2 = current_params[["r2"]],
tau2 = current_params[["tau2"]],
sim = sim) |>
mutate(simD = simD,
simD2 = simD2)
}
The simulated values for all the scenarios:
<- map_df(1:nrow(situations), simu) simu_res
The thresholds:
<-
threshold_times |>
simu_res group_by(K1, tau1, r1, K2, tau2, r2) |>
summarise(
threshold_time_1 = unique(tau1),
threshold_time_2 = unique(tau2)
|>
) ungroup() |>
mutate(
r1 = str_c("$r_1 = ", r1, "$"),
r2 = str_c("$r_2 = ", r2, "$")
)
`summarise()` has grouped output by 'K1', 'tau1', 'r1', 'K2', 'tau2'. You can
override using the `.groups` argument.
<-
p_simu_double_logis ggplot(
data = simu_res |>
mutate(
r1 = str_c("$r_1 = ", r1, "$"),
r2 = str_c("$r_2 = ", r2, "$")
),mapping = aes(x = t)
+
) geom_line(aes(y = sim, linetype = "sim")) +
geom_line(aes(y = simD * 10, linetype = "simD")) +
geom_line(aes(y = simD2 * 10, linetype = "simD2")) +
geom_vline(
data = threshold_times,
mapping = aes(xintercept = threshold_time_1),
colour = "red", linetype = "dotted") +
geom_vline(
data = threshold_times,
mapping = aes(xintercept = threshold_time_2),
colour = "red", linetype = "dotted") +
facet_grid(
~ r1,
r2 labeller = as_labeller(
::TeX,
latex2expdefault = label_parsed
)+
) labs(x = "Time", y = "$F(t)$") +
scale_y_continuous(
labels = comma,
sec.axis = sec_axis(
~./10,
name = latex2exp::TeX(
"$\\partial F(t) / \\partial t$, $\\partial^2 F(t) / \\partial t^2$"
),labels = comma)
+
) scale_linetype_manual(
NULL,
values = c("sim" = "solid", "simD" = "dashed", "simD2" = "dotdash"),
labels = c("sim" = latex2exp::TeX("$F(t)$"),
"simD" = latex2exp::TeX("$\\partial F(t) / \\partial t$"),
"simD2" = latex2exp::TeX("$\\partial^2 F(t) / \\partial t^2$"))
+
) theme(axis.ticks.y = element_blank())
+
p_simu_double_logis theme_minimal() +
theme(legend.position = "bottom")
6.2.2 Double Gompertz Model
The double Gompertz function, its first and second derivatives with respect to time can be coded as follows:
# Double-Gompertz function
#' @param theta vector of named parameters
#' @param x observation / training example
<- function(theta, x) {
double_gompertz_f <- theta[["K1"]]
K1 <- theta[["tau1"]]
tau1 <- theta[["r1"]]
r1 <- theta[["K2"]]
K2 <- theta[["tau2"]]
tau2 <- theta[["r2"]]
r2
<- K1 * exp(-exp(-r1 * (x - tau1)))
f_1 <- (K2-K1) * exp(-exp(-r2 * (x - tau2)))
f_2
+ f_2
f_1
}
#' Double-Gompertz function in two parts
#'
<- function(theta, x) {
double_gompertz_f_2 <- theta[["K1"]]
K1 <- theta[["tau1"]]
tau1 <- theta[["r1"]]
r1 <- theta[["K2"]]
K2 <- theta[["tau2"]]
tau2 <- theta[["r2"]]
r2
<- K1 * exp(-exp(-r1 * (x - tau1)))
f_1 <- (K2-K1) * exp(-exp(-r2 * (x-tau2)))
f_2 tibble(x = x, f_1 = f_1, f_2 = f_2)
}
#' First derivative
#'
<- function(theta, x) {
double_gompertz_f_first_d <- theta[["K1"]]
K1 <- theta[["tau1"]]
tau1 <- theta[["r1"]]
r1 <- theta[["K2"]]
K2 <- theta[["tau2"]]
tau2 <- theta[["r2"]]
r2
<- r1 * K1 * exp(-r1 * (x - tau1) - exp(- r1 * (x - tau1)))
f_1_d <- r2 * (K2-K1) * exp(-r2 * (x - tau2) - exp(- r2 * (x - tau2)))
f_2_d
+ f_2_d
f_1_d
}
#' Second derivative
<- function(theta, x) {
double_gompertz_f_second_d <- theta[["K1"]]
K1 <- theta[["tau1"]]
tau1 <- theta[["r1"]]
r1 <- theta[["K2"]]
K2 <- theta[["tau2"]]
tau2 <- theta[["r2"]]
r2
-K1 * r1^2 * exp(-r1*(x - tau1)) * exp(-exp(-r1*(x - tau1))) +
* r1^2 * (exp( -r1*(x - tau1)))^2 * exp(-exp(-r1*(x - tau1))) -
K1 - K1) * r2^2 * exp( -r2 * (x - tau2)) * exp( -exp(-r2*(x - tau2))) +
(K2 - K1) * r2^2 * (exp( -r2 * (x - tau2)))^2 * exp(-exp(-r2*(x-tau2)))
(K2 }
Let us make some illustrations by varying
<-
situations expand_grid(r1 = c(.2, .5), r2 = c(.2, .5)) |>
mutate(
K1 = 2500,
K2 = 5000,
tau1 = 20,
tau2 = 40
)
The function that simulates
#' Simulation of the logistic model for some parameters
#'
#' @param i row number of situations
<- function(i) {
simu <- situations |> slice(i)
current_params <- c(as_vector(current_params)) |> as.list()
current_params
<- 60
n <- .01
step <- double_gompertz_f(
sim theta = current_params, x = seq(1,n, by = step))
<- double_gompertz_f_first_d(
simD theta = current_params, x = seq(1,n, by = step))
<- double_gompertz_f_second_d(
simD2 theta = current_params, x = seq(1,n, by = step))
tibble(
t = seq(1, n, by = step),
K1 = current_params[["K1"]],
r1 = current_params[["r1"]],
tau1 = current_params[["tau1"]],
K2 = current_params[["K2"]],
r2 = current_params["r2"],
tau2 = current_params[["tau2"]],
sim = sim) |>
mutate(simD = simD,
simD2 = simD2)
}
Let us apply this function to all scenarios:
<- map_df(1:nrow(situations), simu) simu_res
The thresholds:
<-
threshold_times |>
simu_res group_by(K1, tau1, r1, K2, tau2, r2) |>
summarise(threshold_time_1 = unique(tau1),
threshold_time_2 = unique(tau2)) |>
ungroup() |>
mutate(
r1 = str_c("$\\r_1 = ", r1, "$"),
r2 = str_c("$\\r_2 = ", r2, "$")
)
`summarise()` has grouped output by 'K1', 'tau1', 'r1', 'K2', 'tau2'. You can
override using the `.groups` argument.
And the the plots:
<-
p_simu_double_gompertz ggplot(
data = simu_res |>
mutate(r1 = str_c("$\\r_1 = ", r1, "$"),
r2 = str_c("$\\r_2 = ", r2, "$")
),mapping = aes(x = t)) +
geom_line(aes(y = sim, linetype = "sim")) +
geom_line(aes(y = simD*10, linetype = "simD")) +
geom_line(aes(y = simD2*10, linetype = "simD2")) +
geom_vline(
data = threshold_times,
mapping = aes(xintercept = threshold_time_1),
colour = "red", linetype = "dotted") +
geom_vline(
data = threshold_times,
mapping = aes(xintercept = threshold_time_2),
colour = "red", linetype = "dotted") +
facet_grid(
~ r1,
r2 labeller = as_labeller(
::TeX,
latex2expdefault = label_parsed
)+
) labs(x = "Time", y = latex2exp::TeX("$F(t)$")) +
scale_y_continuous(
labels = comma,
sec.axis = sec_axis(
~./10,
name = latex2exp::TeX(
"$\\partial F(t) / \\partial t$, $\\partial^2 F(t) / \\partial t^2$"
),labels = comma)) +
scale_linetype_manual(
NULL,
values = c("sim" = "solid", "simD" = "dashed", "simD2" = "dotdash"),
labels = c("sim" = latex2exp::TeX("$F(t)$"),
"simD" = latex2exp::TeX("$\\partial F(t) / \\partial t$"),
"simD2" = latex2exp::TeX("$\\partial^2 F(t) / \\partial t^2$"))
+
) theme(axis.ticks.y = element_blank())
+
p_simu_double_gompertz theme_minimal() +
theme(legend.position = "bottom")
6.2.3 Double Richards Model
The double Richards function, its first and second derivatives with respect to time can be coded as follows:
#' Double Richards
#'
<- function(theta, x) {
double_richards_f <- theta[["K1"]]
K1 <- theta[["tau1"]]
tau1 <- theta[["r1"]]
r1 <- theta[["delta1"]]
delta1 <- theta[["K2"]]
K2 <- theta[["tau2"]]
tau2 <- theta[["r2"]]
r2 <- theta[["delta2"]]
delta2
* (1 + delta1 * exp(-r1 * (x - tau1)))^(-1 / delta1) +
K1 - K1) * (1 + delta2 * exp(-r2 * (x - tau2)))^(-1 / delta2)
(K2
}
#' Double Richards in two parts
#'
<- function(theta, x) {
double_richards_f_2
<- theta[["K1"]]
K1 <- theta[["tau1"]]
tau1 <- theta[["r1"]]
r1 <- theta[["delta1"]]
delta1 <- theta[["K2"]]
K2 <- theta[["tau2"]]
tau2 <- theta[["r2"]]
r2 <- theta[["delta2"]]
delta2
<- K1 * (1 + delta1 * exp(-r1 * (x - tau1)))^(-1 / delta1)
f_1 <- (K2 - K1) * (1 + delta2 * exp(-r2 * (x - tau2)))^(-1 / delta2)
f_2
tibble(x = x, f_1 = f_1, f_2 = f_2)
}
#' First derivative
#'
<- function(theta, x) {
double_richards_f_first_d <- theta[["K1"]]
K1 <- theta[["tau1"]]
tau1 <- theta[["r1"]]
r1 <- theta[["delta1"]]
delta1 <- theta[["K2"]]
K2 <- theta[["tau2"]]
tau2 <- theta[["r2"]]
r2 <- theta[["delta2"]]
delta2
* K1 * exp(-r1 * (x - tau1)) *
r1 * exp(-r1 * (x - tau1)) + 1)^(-1/delta1 - 1) +
(delta1 * (K2-K1) * exp(-r2 * (x - tau2)) *
r2 * exp(-r2 * (x - tau2)) + 1)^(-1/delta2 - 1)
(delta2
}
#' Second derivative
#'
<- function(theta, x) {
double_richards_f_second_d <- theta[["K1"]]
K1 <- theta[["tau1"]]
tau1 <- theta[["r1"]]
r1 <- theta[["delta1"]]
delta1 <- theta[["K2"]]
K2 <- theta[["tau2"]]
tau2 <- theta[["r2"]]
r2 <- theta[["delta2"]]
delta2
* (r1^2 * (-1/delta1 - 1) * delta1 * (-exp(-2 * r1 * (x - tau1))) *
K1 * exp(-r1 * (x - tau1)) + 1)^(-1/delta1 - 2) - r1^2 *
(delta1 exp(-r1 * (x - tau1)) *
* exp(-r1 * (x - tau1)) + 1)^(-1/delta1 - 1)) +
(delta1
- K1) *
(K2 ^2 * (-1 / delta2 - 1) *
(r2* (-exp(-2 * r2 * (x - tau2))) *
delta2 * exp(-r2 * (x - tau2)) + 1)^(-1/delta2 - 2) -
(delta2 ^2 *
r2exp(-r2 * (x - tau2)) *
* exp(-r2 * (x - tau2)) + 1)^(-1/delta2 - 1))
(delta2 }
Let us make some illustrations by varying
<-
situations expand_grid(r1 = c(.2, .5), r2 = c(.2, .5)) |>
mutate(
K1 = 2500,
K2 = 5000,
tau1 = 20,
tau2 = 40,
delta1 = .5,
delta2 = .5
)
The function that simulates
#' Simulation of the logistic model for some parameters
#'
#' @param i row number of situations
<- function(i) {
simu <- situations |> slice(i)
current_params <- c(as_vector(current_params)) |> as.list()
current_params
<- 60
n <- .01
step <- double_richards_f(
sim theta = current_params, x = seq(1,n, by = step))
<- double_richards_f_first_d(
simD theta = current_params, x = seq(1,n, by = step))
<- double_richards_f_second_d(
simD2 theta = current_params, x = seq(1,n, by = step))
tibble(
t = seq(1, n, by = step),
K1 = current_params[["K1"]],
r1 = current_params[["r1"]],
tau1 = current_params[["tau1"]],
delta1 = current_params[["delta1"]],
K2 = current_params[["K2"]],
r2 = current_params["r2"],
tau2 = current_params[["tau2"]],
delta2 = current_params[["delta2"]],
sim = sim) |>
mutate(simD = simD,
simD2 = simD2)
}
Let us apply this function to all scenarios:
<- map_df(1:nrow(situations), simu) simu_res
The thresholds:
<-
threshold_times |>
simu_res group_by(K1, tau1, r1, K2, tau2, r2, delta1, delta2) |>
summarise(threshold_time_1 = unique(tau1),
threshold_time_2 = unique(tau2)) |>
ungroup() |>
mutate(
r1 = str_c("$\\r_1 = ", r1, "$"),
r2 = str_c("$\\r_2 = ", r2, "$")
)
`summarise()` has grouped output by 'K1', 'tau1', 'r1', 'K2', 'tau2', 'r2',
'delta1'. You can override using the `.groups` argument.
And the the plots:
<-
p_simu_double_richards ggplot(
data = simu_res |>
mutate(r1 = str_c("$\\r_1 = ", r1, "$"),
r2 = str_c("$\\r_2 = ", r2, "$")
),mapping = aes(x = t)) +
geom_line(aes(y = sim, linetype = "sim")) +
geom_line(aes(y = simD*10, linetype = "simD")) +
geom_line(aes(y = simD2*10, linetype = "simD2")) +
geom_vline(
data = threshold_times,
mapping = aes(xintercept = threshold_time_1),
colour = "red", linetype = "dotted") +
geom_vline(
data = threshold_times,
mapping = aes(xintercept = threshold_time_2),
colour = "red", linetype = "dotted") +
facet_grid(
~ r1,
r2 labeller = as_labeller(
::TeX,
latex2expdefault = label_parsed
)+
) labs(x = "Time", y = latex2exp::TeX("$F(t)$")) +
scale_y_continuous(
labels = comma,
sec.axis = sec_axis(
~./10,
name = latex2exp::TeX(
"$\\partial F(t) / \\partial t$, $\\partial^2 F(t) / \\partial t^2$"
),labels = comma)) +
scale_linetype_manual(
NULL,
values = c("sim" = "solid", "simD" = "dashed", "simD2" = "dotdash"),
labels = c("sim" = latex2exp::TeX("$F(t)$"),
"simD" = latex2exp::TeX("$\\partial F(t) / \\partial t$"),
"simD2" = latex2exp::TeX("$\\partial^2 F(t) / \\partial t^2$"))
+
) theme(axis.ticks.y = element_blank())
+
p_simu_double_richards theme_minimal() +
theme(legend.position = "bottom")