From a7477a0abb143fbd1daa1138293057632f54cbec Mon Sep 17 00:00:00 2001 From: jamesaazam Date: Thu, 13 Jun 2024 20:06:32 +0100 Subject: [PATCH 01/66] Initial mockup of speedups vignette --- vignettes/speedup_options.Rmd | 458 +++++++++++++++++++++++++++++ vignettes/speedup_options.Rmd.orig | 357 ++++++++++++++++++++++ 2 files changed, 815 insertions(+) create mode 100644 vignettes/speedup_options.Rmd create mode 100644 vignettes/speedup_options.Rmd.orig diff --git a/vignettes/speedup_options.Rmd b/vignettes/speedup_options.Rmd new file mode 100644 index 000000000..ee7bb644f --- /dev/null +++ b/vignettes/speedup_options.Rmd @@ -0,0 +1,458 @@ +--- +title: "Speeding up model runs" +output: + rmarkdown::html_vignette +csl: https://raw.githubusercontent.com/citation-style-language/styles/master/apa-numeric-superscript-brackets.csl +vignette: > + %\VignetteIndexEntry{Speeding up model runs} + %\VignetteEncoding{UTF-8} + %\VignetteEngine{knitr::rmarkdown} +editor_options: + chunk_output_type: console +--- + + + + +``` r +library(EpiNow2) +library(scoringutils) +library(data.table) +library(rstan) +library(cmdstanr) +library(ggplot2) +``` + +This vignette outlines available options for speeding up model runtimes in _EpiNow2_. We will compare the models by run time, quantitative, and qualitative performance of fit against the "true" trajectory of the data. + +## The benchmarking data + +We will simulate the "true" infections and Rt data using for benchmarking using `forecast_infections()`. + +`forecast_infections()` requires a fitted estimates object from `estimate_infections()` or `epinow()`, the trajectory of the reproduction number, `R`, and the number of samples to simulate. So, we will set these up first. + +To obtain the `estimates` object, we have to fit the `estimate_infections()` model to observed data to recover realistic parameter values for the simulation. To do this, we will use the first 100 observations of the `example_confirmed` dataset vendored with this package. This dataset provides a time series of cases by reporting date. We will also use the `example_generation_time`, `example_incubation_period`, and `example_reporting_delay` values that come with the package. + +Several of the parameters we will use through out this vignette will have the same value, so we will set these up first. + +``` r +# Set the number of cores to use +options(mc.cores = 4) + +# Generation time +generation_time <- Gamma( + shape = Normal(1.3, 0.3), + rate = Normal(0.37, 0.09), + max = 14 +) + +# Incubation period +incubation_period <- LogNormal( + meanlog = Normal(1.6, 0.05), + sdlog = Normal(0.5, 0.05), + max = 14 +) + +# Reporting delay +reporting_delay <- LogNormal( + meanlog = 0.5, + sdlog = 0.5, + max = 10 +) + +# Combine the incubation period and reporting delay into one delay +delay <- incubation_period + reporting_delay + +# Observation model options +obs <- obs_opts( + scale = list(mean = 0.1, sd = 0.025), + return_likelihood = TRUE +) + +# Forecast horizon +horizon <- 0 +``` + +Now, let's generate the `estimates` object. + +``` r +estimates <- estimate_infections( + example_confirmed[1:100], + generation_time = generation_time_opts(example_generation_time), + delays = delay_opts(example_incubation_period + example_reporting_delay), + rt = rt_opts(prior = list(mean = 2, sd = 0.1), rw = 14), + gp = NULL, + obs = obs, + horizon = horizon +) +``` + +``` +#> DEBUG [2024-06-13 20:04:09] estimate_infections: Running in exact mode for 2000 samples (across 4 chains each with a warm up of 250 iterations each) and 114 time steps of which 0 are a forecast +``` + +For the `R` data, we will set up an arbitrary trajectory and add some Gaussian noise. + +``` r +# Arbitrary reproduction number trajectory +R <- c( + rep(2, 40), rep(0.5, 10), rep(1, 10), 1 + 0.04 * 1:20, rep(1.4, 5), + 1.4 - 0.02 * 1:20, rep(1.4, 10), rep(0.8, 5), 0.8 + 0.02 * 1:20 +) +# Add Gaussian noise +R_noisy <- R * rnorm(length(R), 1, 0.05) +``` + +Now, we will simulate the true infections and $Rt$ data by sampling from $10$ posterior samples. + +``` r +# Forecast infections and the trajectory of Rt +forecast <- forecast_infections(estimates, R = R_noisy, samples = 10) +``` + +``` +#> DEBUG [2024-06-13 20:04:39] simulate_infections: Running in exact mode for 1 samples (across 1 chains each with a warm up of 1 iterations each) and 154 time steps of which 40 are a forecast +``` + +We will now extract the benchamrking data: +- `R_true`: the median of the simulated Rts, and +- `infections_true`: the infections by date of infection + + +``` r +R_true <- forecast$summarised[variable == "R"]$median + +# Get the posterior samples from which to extract the simulated infections and reported cases +posterior_sample <- forecast$samples[sample == 1] + +# Extract the simulated infections +infections_true <- posterior_sample[variable == "infections"]$value + +# Extract the simulated reported cases +reported_cases_true <- posterior_sample[ + variable == "reported_cases", .(date, confirm = value) +] +``` + +Now, to the main part of this vignette: we will compare the run times of different models in _EpiNow2_. + +## Model options + +We will use the following models: +- _gp_: A Gaussian process prior on the reproduction number $R_t$ and no random walk, using _MCMC sampling_. +- _vb_: A Gaussian process prior on the reproduction number $R_t$ and no random walk, using the _variational inference_ algorithm. +- _non_mechanistic_: A model with that uses _back calculation_. +- _rw1_no_gp_: A model with a 1-day random walk and Gaussian process turned off. +- _rw7_no_gp_: A model with a 7-day random walk and Gaussian process turned off. +- _rw7_gp_: A model with a 7-day random walk and Gaussian process turned on. +- _pf_: A model that uses the "pathfinder" algorithm (from the `{cmdstanr}` package) for sampling. +- _lp_: A model that uses the "Laplace" algorithm (from the `{cmdstanr}` package) for sampling. + + +``` r +models <- list( + # gp = list( + # rt = rt_opts(prior = list(mean = 2, sd = 0.1)) + # ) + # , + # The non-mechanistic model + non_mechanistic = list(rt = NULL), + # The model with a 1-day random walk and Gaussian process turned off + # rw1_no_gp = list( + # rt = rt_opts( + # prior = list(mean = 2, sd = 0.1), + # rw = 1 + # ), + # gp = NULL + # ), + # The model with a 7-day random walk and Gaussian process turned off + # rw7_no_gp = list( + # rt = rt_opts( + # prior = list(mean = 2, sd = 0.1), + # rw = 7 + # ), + # gp = NULL + # ), + # # The model with a 7-day random walk and Gaussian process (default) + # rw7_gp = list( + # rt = rt_opts( + # prior = list(mean = 2, sd = 0.1), + # rw = 7, + # gp_on = "R0" + # ) + # ), + # Model that uses the variational inference method instead of MCMC + vb = list( + stan = stan_opts( + method = "vb", + trials = 5, + samples = 2000 + ) + )#, + # The model that uses "pathfinder" approximate sampling from `cmdstanr` package + # pathfinder = list( + # stan = stan_opts( + # method = "pathfinder", + # backend = "cmdstanr", + # trials = 5, + # samples = 2000 + # ) + # ), + # # The model that uses Laplace approximate sampling from `cmdstanr` package + # laplace = list( + # stan = stan_opts( + # method = "laplace", + # backend = "cmdstanr", + # trials = 5, + # samples = 2000 + # ) + # ) +) +``` + +## Running the models + +Let's run the models and gather the results. We will sweep across the list of models `models` and shared model options `model_inputs`. We will use the `epinow()` function which is a wrapper around `estimate_infections()` and returns useful outputs like the timing of model runs. + +``` r +model_inputs <- list( + data = reported_cases_true, + generation_time = generation_time_opts(generation_time), + delays = delay_opts(delay), + obs = obs, + horizon = horizon +) +# Run the models +results <- lapply( + models, + function(model) { + do.call( + epinow, + c( + model_inputs, + model + ) + ) + } +) +``` + +``` +#> WARN [2024-06-13 20:04:39] epinow: partial match of 'length' to 'lengths' - $, consecutive, length +#> WARN [2024-06-13 20:04:39] epinow: partial match of 'length' to 'lengths' - $, consecutive, length +#> WARN [2024-06-13 20:04:39] epinow: partial match of 'length' to 'lengths' - $, consecutive, length +#> DEBUG [2024-06-13 20:04:39] epinow: Running in exact mode for 2000 samples (across 4 chains each with a warm up of 250 iterations each) and 147 time steps of which 0 are a forecast +#> WARN [2024-06-13 20:04:51] epinow: partial match of 'length' to 'lengths' - $, consecutive, length +#> WARN [2024-06-13 20:04:51] epinow: partial match of 'length' to 'lengths' - $, consecutive, length +#> WARN [2024-06-13 20:04:51] epinow: partial match of 'length' to 'lengths' - $, consecutive, length +#> DEBUG [2024-06-13 20:04:51] epinow: Running in approximate mode for 10000 iterations (with 5 attempts). Extracting 2000 approximate posterior samples for 154 time steps of which 0 are a forecast +#> Chain 1: ------------------------------------------------------------ +#> Chain 1: EXPERIMENTAL ALGORITHM: +#> Chain 1: This procedure has not been thoroughly tested and may be unstable +#> Chain 1: or buggy. The interface is subject to change. +#> Chain 1: ------------------------------------------------------------ +#> Chain 1: +#> Chain 1: +#> Chain 1: +#> Chain 1: Gradient evaluation took 0.000147 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 1.47 seconds. +#> Chain 1: Adjust your expectations accordingly! +#> Chain 1: +#> Chain 1: +#> Chain 1: Begin eta adaptation. +#> Chain 1: Iteration: 1 / 250 [ 0%] (Adaptation) +#> Chain 1: Iteration: 50 / 250 [ 20%] (Adaptation) +#> Chain 1: Iteration: 100 / 250 [ 40%] (Adaptation) +#> Chain 1: Iteration: 150 / 250 [ 60%] (Adaptation) +#> Chain 1: Iteration: 200 / 250 [ 80%] (Adaptation) +#> Chain 1: Success! Found best value [eta = 1] earlier than expected. +#> Chain 1: +#> Chain 1: Begin stochastic gradient ascent. +#> Chain 1: iter ELBO delta_ELBO_mean delta_ELBO_med notes +#> Chain 1: 100 -579019.403 1.000 1.000 +#> Chain 1: 200 -42010.589 6.891 12.783 +#> Chain 1: 300 -1765.230 12.194 12.783 +#> Chain 1: 400 -1532.261 9.183 12.783 +#> Chain 1: 500 -1441.657 7.359 1.000 +#> Chain 1: 600 -1451.699 6.134 1.000 +#> Chain 1: 700 -1316.190 5.272 0.152 +#> Chain 1: 800 -1243.534 4.621 0.152 +#> Chain 1: 900 -1193.091 4.112 0.103 +#> Chain 1: 1000 -1218.553 3.703 0.103 +#> Chain 1: 1100 -1153.465 3.608 0.063 MAY BE DIVERGING... INSPECT ELBO +#> Chain 1: 1200 -1168.177 2.331 0.058 MAY BE DIVERGING... INSPECT ELBO +#> Chain 1: 1300 -1156.719 0.053 0.056 +#> Chain 1: 1400 -1201.547 0.041 0.042 +#> Chain 1: 1500 -1162.847 0.038 0.037 +#> Chain 1: 1600 -1176.656 0.039 0.037 +#> Chain 1: 1700 -1153.839 0.030 0.033 +#> Chain 1: 1800 -1167.787 0.026 0.021 +#> Chain 1: 1900 -1298.298 0.031 0.021 +#> Chain 1: 2000 -1155.899 0.042 0.033 +#> Chain 1: 2100 -1148.349 0.037 0.020 +#> Chain 1: 2200 -1168.287 0.037 0.020 +#> Chain 1: 2300 -1147.215 0.038 0.020 +#> Chain 1: 2400 -1212.261 0.040 0.020 +#> Chain 1: 2500 -1154.181 0.041 0.020 +#> Chain 1: 2600 -1182.885 0.043 0.024 +#> Chain 1: 2700 -1147.512 0.044 0.031 +#> Chain 1: 2800 -1146.729 0.043 0.031 +#> Chain 1: 2900 -1181.801 0.035 0.030 +#> Chain 1: 3000 -1147.941 0.026 0.029 +#> Chain 1: 3100 -1156.345 0.026 0.029 +#> Chain 1: 3200 -1160.267 0.025 0.029 +#> Chain 1: 3300 -1152.493 0.024 0.029 +#> Chain 1: 3400 -1156.977 0.019 0.024 +#> Chain 1: 3500 -1202.479 0.017 0.024 +#> Chain 1: 3600 -1146.741 0.020 0.029 +#> Chain 1: 3700 -1147.768 0.017 0.007 MEDIAN ELBO CONVERGED +#> Chain 1: +#> Chain 1: Drawing a sample of size 2000 from the approximate posterior... +#> Chain 1: COMPLETED. +#> WARN [2024-06-13 20:04:52] epinow: partial match of 'log_p' to 'log_p__' - $, diagnostics, log_p +#> WARN [2024-06-13 20:04:52] epinow: partial match of 'log_g' to 'log_g__' - $, diagnostics, log_g +#> WARN [2024-06-13 20:04:53] epinow: Pareto k diagnostic value is 1.21. Resampling is disabled. Decreasing tol_rel_obj may help if variational algorithm has terminated prematurely. Otherwise consider using sampling instead. - +``` + +## Evaluating performance + +### Run times + +Let's see how long each model took to run. Note that the run time measured here uses a crude method that compares the start and end times of each simulation. It is meant to be approximate and may not be accurate. For accurate run times, we recommend using a more sophisticated benchmarking tool like `bench` or `microbenchmark`. + + +``` r +# Extract the run times +models <- names(models) +runtimes <- sapply( + results, + function(x) x$timing +) +knitr::kable(runtimes, col.names = c("models", "Run time"), caption = "Run times (seconds)") +``` + + + +Table: Run times (seconds) + +|models | Run time| +|:---------------|---------:| +|non_mechanistic | 11.789461| +|vb | 3.043279| + +### Forecast evaluation + +Now, we will compare the estimated and true values using the continuous ranked probability score (CRPS). The CRPS is a proper scoring rule that measures the accuracy of probabilistic forecasts. We will use the `crps()` function from the `{scoringutils}` package. + +To calculate the CRPS for the estimated Rt and infections, we will first set up a function that makes sure the data and estimates are of the same length and calls the `crps_sample()` function from the `{scoringutils}` package. + +``` r +# A function to calculate the CRPS +calc_crps <- function(x, truth) { + shortest_obs_length <- min(ncol(x), length(truth)) + reduced_truth <- tail(truth, shortest_obs_length) + reduced_x <- tail(t(x), shortest_obs_length) + return(crps_sample(reduced_truth, reduced_x)) +} +``` + +Now, we can calculate CRPS for the Rt and infection estimates of the various models and using `calc_crps()`. + + +``` r +# Get the Rt samples +Rt_estimated <- lapply(results, function(x) { + if ("R[1]" %in% names(x$estimates$fit)) { + extract(x$estimates$fit, "R")$R + } else { + extract(x$estimates$fit, "gen_R")$gen_R + } +}) +# CRPS for the Rt estimates +rt_crps <- lapply( + Rt_estimated, + calc_crps, + truth = R +) + +# Get the infection samples +infections_estimated <- lapply(results, function(x) { + extract(x$estimates$fit, "infections")$infections +}) + +# CRPS for the infections estimates +infections_crps <- lapply( + infections_estimated, + calc_crps, + truth = infections_true +) +``` + +We will now post-process the CRPS results to make them easier to visualise. + + +``` r +# Post-processing the CRPS results of the Rt estimates +rt_df <- as.data.table(rt_crps) +rt_df[, metric := "CRPS"] +rt_df[, time := 1:.N] +rt_df <- melt( + rt_df, + id.vars = c("metric", "time"), + variable.name = "model" +) + +# Post-processing the CRPS results of the infection estimates +infections_df <- as.data.table(infections_crps) +infections_df[, metric := "CRPS"] +infections_df[, time := 1:.N] +infections_df <- melt( + infections_df, + id.vars = c("metric", "time"), + variable.name = "model" +) +``` + +Let's visualise the CRPS results for the Rt and infection estimates. + + +``` r +rt_plot <- ggplot(rt_df, aes(x = time, y = value, colour = model)) + + geom_line() + + scale_colour_brewer("Model", palette = "Dark2") + + xlab("Time") + + ylab("CRPS") + + ggplot2::theme_bw() + + ggtitle("Reconstructing R") +plot(rt_plot) +``` + +![plot of chunk rt_plot](figure/rt_plot-1.png) + + +``` r +infections_plot <- ggplot(infections_df, aes(x = time, y = value, colour = model)) + + geom_line() + + scale_colour_brewer("Model", palette = "Dark2") + + xlab("Time") + + ylab("CRPS") + + ggplot2::theme_bw() + + ggtitle("Reconstructing infections") +plot(infections_plot) +``` + +![plot of chunk infections_plot](figure/infections_plot-1.png) + +## Some considerations + +### Mechanistic vs non-mechanistic models + +- Estimation in _EpiNow2_ using the mechanistic approaches (prior on Rt) is often much slower than the non- mechanistic approach. The mechanistic model is slower because it models intricate details about the processes and mechanisms that drive $Rt$ estimates. The nonmechanistic model, on the other hand, is faster because it does not model these intricate details. If performing a retrospective analysis, the _non-mechanistic model_ (where `rt = NULL`) is better suited and a faster alternative to the mechanistic $Rt$ model. + +### Exact vs approximate methods + +- The default method is `method = "sampling"`, which performs MCMC sampling. The MCMC sampling method is accurate but can often be slow. The Laplace, Pathfinder, and variational inference methods are the fastest because they are approximate methods. The first two require having the `cmdstanr` package installed. Also note that the methods are experimental and may not be as reliable as the default MCMC sampling method. For details on the Laplace and Pathfinder approximation methods, see respectively, [here](https://mc-stan.org/docs/cmdstan-guide/laplace_sample_config.html) and [here](https://mc-stan.org/docs/cmdstan-guide/pathfinder_config.html). + +### Granularity of estimates + +- The random walk method reduces granularity in estimates, compared to the other methods. diff --git a/vignettes/speedup_options.Rmd.orig b/vignettes/speedup_options.Rmd.orig new file mode 100644 index 000000000..a0c41cf60 --- /dev/null +++ b/vignettes/speedup_options.Rmd.orig @@ -0,0 +1,357 @@ +--- +title: "Speeding up model runs" +output: + rmarkdown::html_vignette +csl: https://raw.githubusercontent.com/citation-style-language/styles/master/apa-numeric-superscript-brackets.csl +vignette: > + %\VignetteIndexEntry{Speeding up model runs} + %\VignetteEncoding{UTF-8} + %\VignetteEngine{knitr::rmarkdown} +editor_options: + chunk_output_type: console +--- + +```{r setup, include=FALSE} + knitr::opts_chunk$set( + echo = TRUE, + cache = FALSE, + message = FALSE, + warning = FALSE, + collapse = FALSE, + comment = "#>", + fig.height = 6, + fig.width = 6 +) +``` + +```{r packages} +library(EpiNow2) +library(scoringutils) +library(data.table) +library(rstan) +library(cmdstanr) +library(ggplot2) +``` + +This vignette outlines available options for speeding up model runtimes in _EpiNow2_. We will compare the models by run time, quantitative, and qualitative performance of fit against the "true" trajectory of the data. + +## The benchmarking data + +We will simulate the "true" infections and Rt data using for benchmarking using `forecast_infections()`. + +`forecast_infections()` requires a fitted estimates object from `estimate_infections()` or `epinow()`, the trajectory of the reproduction number, `R`, and the number of samples to simulate. So, we will set these up first. + +To obtain the `estimates` object, we have to fit the `estimate_infections()` model to observed data to recover realistic parameter values for the simulation. To do this, we will use the first 100 observations of the `example_confirmed` dataset vendored with this package. This dataset provides a time series of cases by reporting date. We will also use the `example_generation_time`, `example_incubation_period`, and `example_reporting_delay` values that come with the package. + +Several of the parameters we will use through out this vignette will have the same value, so we will set these up first. +```{r, shared_inputs} +# Set the number of cores to use +options(mc.cores = 4) + +# Generation time +generation_time <- Gamma( + shape = Normal(1.3, 0.3), + rate = Normal(0.37, 0.09), + max = 14 +) + +# Incubation period +incubation_period <- LogNormal( + meanlog = Normal(1.6, 0.05), + sdlog = Normal(0.5, 0.05), + max = 14 +) + +# Reporting delay +reporting_delay <- LogNormal( + meanlog = 0.5, + sdlog = 0.5, + max = 10 +) + +# Combine the incubation period and reporting delay into one delay +delay <- incubation_period + reporting_delay + +# Observation model options +obs <- obs_opts( + scale = list(mean = 0.1, sd = 0.025), + return_likelihood = TRUE +) + +# Forecast horizon +horizon <- 0 +``` + +Now, let's generate the `estimates` object. +```{r estimates} +estimates <- estimate_infections( + example_confirmed[1:100], + generation_time = generation_time_opts(example_generation_time), + delays = delay_opts(example_incubation_period + example_reporting_delay), + rt = rt_opts(prior = list(mean = 2, sd = 0.1), rw = 14), + gp = NULL, + obs = obs, + horizon = horizon +) +``` + +For the `R` data, we will set up an arbitrary trajectory and add some Gaussian noise. +```{r R-data} +# Arbitrary reproduction number trajectory +R <- c( + rep(2, 40), rep(0.5, 10), rep(1, 10), 1 + 0.04 * 1:20, rep(1.4, 5), + 1.4 - 0.02 * 1:20, rep(1.4, 10), rep(0.8, 5), 0.8 + 0.02 * 1:20 +) +# Add Gaussian noise +R_noisy <- R * rnorm(length(R), 1, 0.05) +``` + +Now, we will simulate the true infections and $Rt$ data by sampling from $10$ posterior samples. +```{r true-data} +# Forecast infections and the trajectory of Rt +forecast <- forecast_infections(estimates, R = R_noisy, samples = 10) +``` + +We will now extract the benchamrking data: +- `R_true`: the median of the simulated Rts, and +- `infections_true`: the infections by date of infection + +```{r extract-true-data} +R_true <- forecast$summarised[variable == "R"]$median + +# Get the posterior samples from which to extract the simulated infections and reported cases +posterior_sample <- forecast$samples[sample == 1] + +# Extract the simulated infections +infections_true <- posterior_sample[variable == "infections"]$value + +# Extract the simulated reported cases +reported_cases_true <- posterior_sample[ + variable == "reported_cases", .(date, confirm = value) +] +``` + +Now, to the main part of this vignette: we will compare the run times of different models in _EpiNow2_. + +## Model options + +We will use the following models: +- _gp_: A Gaussian process prior on the reproduction number $R_t$ and no random walk, using _MCMC sampling_. +- _vb_: A Gaussian process prior on the reproduction number $R_t$ and no random walk, using the _variational inference_ algorithm. +- _non_mechanistic_: A model with that uses _back calculation_. +- _rw1_no_gp_: A model with a 1-day random walk and Gaussian process turned off. +- _rw7_no_gp_: A model with a 7-day random walk and Gaussian process turned off. +- _rw7_gp_: A model with a 7-day random walk and Gaussian process turned on. +- _pf_: A model that uses the "pathfinder" algorithm (from the `{cmdstanr}` package) for sampling. +- _lp_: A model that uses the "Laplace" algorithm (from the `{cmdstanr}` package) for sampling. + +```{r models} +models <- list( + # gp = list( + # rt = rt_opts(prior = list(mean = 2, sd = 0.1)) + # ) + # , + # The non-mechanistic model + non_mechanistic = list(rt = NULL), + # The model with a 1-day random walk and Gaussian process turned off + # rw1_no_gp = list( + # rt = rt_opts( + # prior = list(mean = 2, sd = 0.1), + # rw = 1 + # ), + # gp = NULL + # ), + # The model with a 7-day random walk and Gaussian process turned off + # rw7_no_gp = list( + # rt = rt_opts( + # prior = list(mean = 2, sd = 0.1), + # rw = 7 + # ), + # gp = NULL + # ), + # # The model with a 7-day random walk and Gaussian process (default) + # rw7_gp = list( + # rt = rt_opts( + # prior = list(mean = 2, sd = 0.1), + # rw = 7, + # gp_on = "R0" + # ) + # ), + # Model that uses the variational inference method instead of MCMC + vb = list( + stan = stan_opts( + method = "vb", + trials = 5, + samples = 2000 + ) + )#, + # The model that uses "pathfinder" approximate sampling from `cmdstanr` package + # pathfinder = list( + # stan = stan_opts( + # method = "pathfinder", + # backend = "cmdstanr", + # trials = 5, + # samples = 2000 + # ) + # ), + # # The model that uses Laplace approximate sampling from `cmdstanr` package + # laplace = list( + # stan = stan_opts( + # method = "laplace", + # backend = "cmdstanr", + # trials = 5, + # samples = 2000 + # ) + # ) +) +``` + +## Running the models + +Let's run the models and gather the results. We will sweep across the list of models `models` and shared model options `model_inputs`. We will use the `epinow()` function which is a wrapper around `estimate_infections()` and returns useful outputs like the timing of model runs. +```{r run-models} +model_inputs <- list( + data = reported_cases_true, + generation_time = generation_time_opts(generation_time), + delays = delay_opts(delay), + obs = obs, + horizon = horizon +) +# Run the models +results <- lapply( + models, + function(model) { + do.call( + epinow, + c( + model_inputs, + model + ) + ) + } +) +``` + +## Evaluating performance + +### Run times + +Let's see how long each model took to run. Note that the run time measured here uses a crude method that compares the start and end times of each simulation. It is meant to be approximate and may not be accurate. For accurate run times, we recommend using a more sophisticated benchmarking tool like `bench` or `microbenchmark`. + +```{r runtimes} +# Extract the run times +models <- names(models) +runtimes <- sapply( + results, + function(x) x$timing +) +knitr::kable(runtimes, col.names = c("models", "Run time"), caption = "Run times (seconds)") +``` + +### Forecast evaluation + +Now, we will compare the estimated and true values using the continuous ranked probability score (CRPS). The CRPS is a proper scoring rule that measures the accuracy of probabilistic forecasts. We will use the `crps()` function from the `{scoringutils}` package. + +To calculate the CRPS for the estimated Rt and infections, we will first set up a function that makes sure the data and estimates are of the same length and calls the `crps_sample()` function from the `{scoringutils}` package. +```{r crps-func} +# A function to calculate the CRPS +calc_crps <- function(x, truth) { + shortest_obs_length <- min(ncol(x), length(truth)) + reduced_truth <- tail(truth, shortest_obs_length) + reduced_x <- tail(t(x), shortest_obs_length) + return(crps_sample(reduced_truth, reduced_x)) +} +``` + +Now, we can calculate CRPS for the Rt and infection estimates of the various models and using `calc_crps()`. + +```{r fit_crps} +# Get the Rt samples +Rt_estimated <- lapply(results, function(x) { + if ("R[1]" %in% names(x$estimates$fit)) { + extract(x$estimates$fit, "R")$R + } else { + extract(x$estimates$fit, "gen_R")$gen_R + } +}) +# CRPS for the Rt estimates +rt_crps <- lapply( + Rt_estimated, + calc_crps, + truth = R +) + +# Get the infection samples +infections_estimated <- lapply(results, function(x) { + extract(x$estimates$fit, "infections")$infections +}) + +# CRPS for the infections estimates +infections_crps <- lapply( + infections_estimated, + calc_crps, + truth = infections_true +) +``` + +We will now post-process the CRPS results to make them easier to visualise. + +```{r fit_postprocessing} +# Post-processing the CRPS results of the Rt estimates +rt_df <- as.data.table(rt_crps) +rt_df[, metric := "CRPS"] +rt_df[, time := 1:.N] +rt_df <- melt( + rt_df, + id.vars = c("metric", "time"), + variable.name = "model" +) + +# Post-processing the CRPS results of the infection estimates +infections_df <- as.data.table(infections_crps) +infections_df[, metric := "CRPS"] +infections_df[, time := 1:.N] +infections_df <- melt( + infections_df, + id.vars = c("metric", "time"), + variable.name = "model" +) +``` + +Let's visualise the CRPS results for the Rt and infection estimates. + +```{r rt_plot} +rt_plot <- ggplot(rt_df, aes(x = time, y = value, colour = model)) + + geom_line() + + scale_colour_brewer("Model", palette = "Dark2") + + xlab("Time") + + ylab("CRPS") + + ggplot2::theme_bw() + + ggtitle("Reconstructing R") +plot(rt_plot) +``` + +```{r infections_plot} +infections_plot <- ggplot(infections_df, aes(x = time, y = value, colour = model)) + + geom_line() + + scale_colour_brewer("Model", palette = "Dark2") + + xlab("Time") + + ylab("CRPS") + + ggplot2::theme_bw() + + ggtitle("Reconstructing infections") +plot(infections_plot) +``` + +## Some considerations + +### Mechanistic vs non-mechanistic models + +- Estimation in _EpiNow2_ using the mechanistic approaches (prior on Rt) is often much slower than the non- mechanistic approach. The mechanistic model is slower because it models intricate details about the processes and mechanisms that drive $Rt$ estimates. The nonmechanistic model, on the other hand, is faster because it does not model these intricate details. If performing a retrospective analysis, the _non-mechanistic model_ (where `rt = NULL`) is better suited and a faster alternative to the mechanistic $Rt$ model. + +### Exact vs approximate methods + +- The default method is `method = "sampling"`, which performs MCMC sampling. The MCMC sampling method is accurate but can often be slow. The Laplace, Pathfinder, and variational inference methods are the fastest because they are approximate methods. The first two require having the `cmdstanr` package installed. Also note that the methods are experimental and may not be as reliable as the default MCMC sampling method. For details on the Laplace and Pathfinder approximation methods, see respectively, [here](https://mc-stan.org/docs/cmdstan-guide/laplace_sample_config.html) and [here](https://mc-stan.org/docs/cmdstan-guide/pathfinder_config.html). + +### Granularity of estimates + +- The random walk method reduces granularity in estimates, compared to the other methods. \ No newline at end of file From 0374e06f17bc0d4d5a7b55b7d96ab532b1e33be7 Mon Sep 17 00:00:00 2001 From: jamesaazam Date: Thu, 13 Jun 2024 22:27:46 +0100 Subject: [PATCH 02/66] reconstruct difftime objects --- vignettes/speedup_options.Rmd.orig | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/vignettes/speedup_options.Rmd.orig b/vignettes/speedup_options.Rmd.orig index a0c41cf60..7bf07c793 100644 --- a/vignettes/speedup_options.Rmd.orig +++ b/vignettes/speedup_options.Rmd.orig @@ -241,11 +241,23 @@ Let's see how long each model took to run. Note that the run time measured here ```{r runtimes} # Extract the run times models <- names(models) -runtimes <- sapply( +runtimes <- vapply( results, - function(x) x$timing + function(x) x$timing, + double(1) ) -knitr::kable(runtimes, col.names = c("models", "Run time"), caption = "Run times (seconds)") +# Extracting the difftime objects drops the units, so we need to extract them separately +runtimes_units <- vapply( + results, + function(x) attr(x$timing, "units"), + character(1) +) +# We then recompose the runtimes with the units +runtimes_vec <- paste(round(runtimes), runtimes_units) +# Add the model names +names(runtimes_vec) <- models +# Print table +knitr::kable(runtimes_vec, col.names = c("models", "Run time"), caption = "Run times") ``` ### Forecast evaluation From 86b7b74625fd38fcf7a665645c0cbce1a3416d11 Mon Sep 17 00:00:00 2001 From: jamesaazam Date: Thu, 13 Jun 2024 22:28:11 +0100 Subject: [PATCH 03/66] Uncomment all the rstan models --- vignettes/speedup_options.Rmd.orig | 52 +++++++++++++++--------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/vignettes/speedup_options.Rmd.orig b/vignettes/speedup_options.Rmd.orig index 7bf07c793..b6ceafd6a 100644 --- a/vignettes/speedup_options.Rmd.orig +++ b/vignettes/speedup_options.Rmd.orig @@ -147,36 +147,36 @@ We will use the following models: ```{r models} models <- list( - # gp = list( - # rt = rt_opts(prior = list(mean = 2, sd = 0.1)) - # ) - # , + gp = list( + rt = rt_opts(prior = list(mean = 2, sd = 0.1)) + ) + , # The non-mechanistic model non_mechanistic = list(rt = NULL), # The model with a 1-day random walk and Gaussian process turned off - # rw1_no_gp = list( - # rt = rt_opts( - # prior = list(mean = 2, sd = 0.1), - # rw = 1 - # ), - # gp = NULL - # ), + rw1_no_gp = list( + rt = rt_opts( + prior = list(mean = 2, sd = 0.1), + rw = 1 + ), + gp = NULL + ), # The model with a 7-day random walk and Gaussian process turned off - # rw7_no_gp = list( - # rt = rt_opts( - # prior = list(mean = 2, sd = 0.1), - # rw = 7 - # ), - # gp = NULL - # ), - # # The model with a 7-day random walk and Gaussian process (default) - # rw7_gp = list( - # rt = rt_opts( - # prior = list(mean = 2, sd = 0.1), - # rw = 7, - # gp_on = "R0" - # ) - # ), + rw7_no_gp = list( + rt = rt_opts( + prior = list(mean = 2, sd = 0.1), + rw = 7 + ), + gp = NULL + ), + # The model with a 7-day random walk and Gaussian process (default) + rw7_gp = list( + rt = rt_opts( + prior = list(mean = 2, sd = 0.1), + rw = 7, + gp_on = "R0" + ) + ), # Model that uses the variational inference method instead of MCMC vb = list( stan = stan_opts( From 750fac7b8efcb5be186945cf2d71412799aef3cc Mon Sep 17 00:00:00 2001 From: jamesaazam Date: Thu, 13 Jun 2024 22:28:42 +0100 Subject: [PATCH 04/66] Run all the rstan models --- vignettes/speedup_options.Rmd | 195 +++++++++++++++++++--------------- 1 file changed, 112 insertions(+), 83 deletions(-) diff --git a/vignettes/speedup_options.Rmd b/vignettes/speedup_options.Rmd index ee7bb644f..f77daff65 100644 --- a/vignettes/speedup_options.Rmd +++ b/vignettes/speedup_options.Rmd @@ -88,7 +88,7 @@ estimates <- estimate_infections( ``` ``` -#> DEBUG [2024-06-13 20:04:09] estimate_infections: Running in exact mode for 2000 samples (across 4 chains each with a warm up of 250 iterations each) and 114 time steps of which 0 are a forecast +#> DEBUG [2024-06-13 20:11:11] estimate_infections: Running in exact mode for 2000 samples (across 4 chains each with a warm up of 250 iterations each) and 114 time steps of which 0 are a forecast ``` For the `R` data, we will set up an arbitrary trajectory and add some Gaussian noise. @@ -111,7 +111,7 @@ forecast <- forecast_infections(estimates, R = R_noisy, samples = 10) ``` ``` -#> DEBUG [2024-06-13 20:04:39] simulate_infections: Running in exact mode for 1 samples (across 1 chains each with a warm up of 1 iterations each) and 154 time steps of which 40 are a forecast +#> DEBUG [2024-06-13 20:11:46] simulate_infections: Running in exact mode for 1 samples (across 1 chains each with a warm up of 1 iterations each) and 154 time steps of which 40 are a forecast ``` We will now extract the benchamrking data: @@ -151,36 +151,36 @@ We will use the following models: ``` r models <- list( - # gp = list( - # rt = rt_opts(prior = list(mean = 2, sd = 0.1)) - # ) - # , + gp = list( + rt = rt_opts(prior = list(mean = 2, sd = 0.1)) + ) + , # The non-mechanistic model non_mechanistic = list(rt = NULL), # The model with a 1-day random walk and Gaussian process turned off - # rw1_no_gp = list( - # rt = rt_opts( - # prior = list(mean = 2, sd = 0.1), - # rw = 1 - # ), - # gp = NULL - # ), + rw1_no_gp = list( + rt = rt_opts( + prior = list(mean = 2, sd = 0.1), + rw = 1 + ), + gp = NULL + ), # The model with a 7-day random walk and Gaussian process turned off - # rw7_no_gp = list( - # rt = rt_opts( - # prior = list(mean = 2, sd = 0.1), - # rw = 7 - # ), - # gp = NULL - # ), - # # The model with a 7-day random walk and Gaussian process (default) - # rw7_gp = list( - # rt = rt_opts( - # prior = list(mean = 2, sd = 0.1), - # rw = 7, - # gp_on = "R0" - # ) - # ), + rw7_no_gp = list( + rt = rt_opts( + prior = list(mean = 2, sd = 0.1), + rw = 7 + ), + gp = NULL + ), + # The model with a 7-day random walk and Gaussian process (default) + rw7_gp = list( + rt = rt_opts( + prior = list(mean = 2, sd = 0.1), + rw = 7, + gp_on = "R0" + ) + ), # Model that uses the variational inference method instead of MCMC vb = list( stan = stan_opts( @@ -238,14 +238,46 @@ results <- lapply( ``` ``` -#> WARN [2024-06-13 20:04:39] epinow: partial match of 'length' to 'lengths' - $, consecutive, length -#> WARN [2024-06-13 20:04:39] epinow: partial match of 'length' to 'lengths' - $, consecutive, length -#> WARN [2024-06-13 20:04:39] epinow: partial match of 'length' to 'lengths' - $, consecutive, length -#> DEBUG [2024-06-13 20:04:39] epinow: Running in exact mode for 2000 samples (across 4 chains each with a warm up of 250 iterations each) and 147 time steps of which 0 are a forecast -#> WARN [2024-06-13 20:04:51] epinow: partial match of 'length' to 'lengths' - $, consecutive, length -#> WARN [2024-06-13 20:04:51] epinow: partial match of 'length' to 'lengths' - $, consecutive, length -#> WARN [2024-06-13 20:04:51] epinow: partial match of 'length' to 'lengths' - $, consecutive, length -#> DEBUG [2024-06-13 20:04:51] epinow: Running in approximate mode for 10000 iterations (with 5 attempts). Extracting 2000 approximate posterior samples for 154 time steps of which 0 are a forecast +#> WARN [2024-06-13 20:11:46] epinow: partial match of 'length' to 'lengths' - $, consecutive, length +#> WARN [2024-06-13 20:11:46] epinow: partial match of 'length' to 'lengths' - $, consecutive, length +#> WARN [2024-06-13 20:11:46] epinow: partial match of 'length' to 'lengths' - $, consecutive, length +#> DEBUG [2024-06-13 20:11:46] epinow: Running in exact mode for 2000 samples (across 4 chains each with a warm up of 250 iterations each) and 154 time steps of which 0 are a forecast +#> WARN [2024-06-13 20:16:43] epinow: There were 37 divergent transitions after warmup. See +#> https://mc-stan.org/misc/warnings.html#divergent-transitions-after-warmup +#> to find out why this is a problem and how to eliminate them. - +#> WARN [2024-06-13 20:16:43] epinow: Examine the pairs() plot to diagnose sampling problems +#> - +#> WARN [2024-06-13 20:16:46] epinow: NAs introduced by coercion to integer range - .f, .x[[i]], ... +#> WARN [2024-06-13 20:16:46] epinow: NAs introduced by coercion to integer range - .f, .x[[i]], ... +#> WARN [2024-06-13 20:16:47] epinow: partial match of 'length' to 'lengths' - $, consecutive, length +#> WARN [2024-06-13 20:16:47] epinow: partial match of 'length' to 'lengths' - $, consecutive, length +#> WARN [2024-06-13 20:16:47] epinow: partial match of 'length' to 'lengths' - $, consecutive, length +#> DEBUG [2024-06-13 20:16:47] epinow: Running in exact mode for 2000 samples (across 4 chains each with a warm up of 250 iterations each) and 147 time steps of which 0 are a forecast +#> WARN [2024-06-13 20:17:00] epinow: partial match of 'length' to 'lengths' - $, consecutive, length +#> WARN [2024-06-13 20:17:00] epinow: partial match of 'length' to 'lengths' - $, consecutive, length +#> WARN [2024-06-13 20:17:00] epinow: partial match of 'length' to 'lengths' - $, consecutive, length +#> DEBUG [2024-06-13 20:17:00] epinow: Running in exact mode for 2000 samples (across 4 chains each with a warm up of 250 iterations each) and 154 time steps of which 0 are a forecast +#> WARN [2024-06-13 20:22:57] epinow: Bulk Effective Samples Size (ESS) is too low, indicating posterior means and medians may be unreliable. +#> Running the chains for more iterations may help. See +#> https://mc-stan.org/misc/warnings.html#bulk-ess - +#> WARN [2024-06-13 20:22:57] epinow: Tail Effective Samples Size (ESS) is too low, indicating posterior variances and tail quantiles may be unreliable. +#> Running the chains for more iterations may help. See +#> https://mc-stan.org/misc/warnings.html#tail-ess - +#> WARN [2024-06-13 20:22:58] epinow: NAs introduced by coercion to integer range - .f, .x[[i]], ... +#> WARN [2024-06-13 20:22:59] epinow: partial match of 'length' to 'lengths' - $, consecutive, length +#> WARN [2024-06-13 20:22:59] epinow: partial match of 'length' to 'lengths' - $, consecutive, length +#> WARN [2024-06-13 20:22:59] epinow: partial match of 'length' to 'lengths' - $, consecutive, length +#> DEBUG [2024-06-13 20:22:59] epinow: Running in exact mode for 2000 samples (across 4 chains each with a warm up of 250 iterations each) and 154 time steps of which 0 are a forecast +#> WARN [2024-06-13 20:25:23] epinow: NAs introduced by coercion to integer range - .f, .x[[i]], ... +#> WARN [2024-06-13 20:25:23] epinow: partial match of 'length' to 'lengths' - $, consecutive, length +#> WARN [2024-06-13 20:25:23] epinow: partial match of 'length' to 'lengths' - $, consecutive, length +#> WARN [2024-06-13 20:25:23] epinow: partial match of 'length' to 'lengths' - $, consecutive, length +#> DEBUG [2024-06-13 20:25:23] epinow: Running in exact mode for 2000 samples (across 4 chains each with a warm up of 250 iterations each) and 154 time steps of which 0 are a forecast +#> WARN [2024-06-13 20:31:38] epinow: NAs introduced by coercion to integer range - .f, .x[[i]], ... +#> WARN [2024-06-13 20:31:39] epinow: partial match of 'length' to 'lengths' - $, consecutive, length +#> WARN [2024-06-13 20:31:39] epinow: partial match of 'length' to 'lengths' - $, consecutive, length +#> WARN [2024-06-13 20:31:39] epinow: partial match of 'length' to 'lengths' - $, consecutive, length +#> DEBUG [2024-06-13 20:31:39] epinow: Running in approximate mode for 10000 iterations (with 5 attempts). Extracting 2000 approximate posterior samples for 154 time steps of which 0 are a forecast #> Chain 1: ------------------------------------------------------------ #> Chain 1: EXPERIMENTAL ALGORITHM: #> Chain 1: This procedure has not been thoroughly tested and may be unstable @@ -254,8 +286,8 @@ results <- lapply( #> Chain 1: #> Chain 1: #> Chain 1: -#> Chain 1: Gradient evaluation took 0.000147 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 1.47 seconds. +#> Chain 1: Gradient evaluation took 0.000129 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 1.29 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -269,49 +301,30 @@ results <- lapply( #> Chain 1: #> Chain 1: Begin stochastic gradient ascent. #> Chain 1: iter ELBO delta_ELBO_mean delta_ELBO_med notes -#> Chain 1: 100 -579019.403 1.000 1.000 -#> Chain 1: 200 -42010.589 6.891 12.783 -#> Chain 1: 300 -1765.230 12.194 12.783 -#> Chain 1: 400 -1532.261 9.183 12.783 -#> Chain 1: 500 -1441.657 7.359 1.000 -#> Chain 1: 600 -1451.699 6.134 1.000 -#> Chain 1: 700 -1316.190 5.272 0.152 -#> Chain 1: 800 -1243.534 4.621 0.152 -#> Chain 1: 900 -1193.091 4.112 0.103 -#> Chain 1: 1000 -1218.553 3.703 0.103 -#> Chain 1: 1100 -1153.465 3.608 0.063 MAY BE DIVERGING... INSPECT ELBO -#> Chain 1: 1200 -1168.177 2.331 0.058 MAY BE DIVERGING... INSPECT ELBO -#> Chain 1: 1300 -1156.719 0.053 0.056 -#> Chain 1: 1400 -1201.547 0.041 0.042 -#> Chain 1: 1500 -1162.847 0.038 0.037 -#> Chain 1: 1600 -1176.656 0.039 0.037 -#> Chain 1: 1700 -1153.839 0.030 0.033 -#> Chain 1: 1800 -1167.787 0.026 0.021 -#> Chain 1: 1900 -1298.298 0.031 0.021 -#> Chain 1: 2000 -1155.899 0.042 0.033 -#> Chain 1: 2100 -1148.349 0.037 0.020 -#> Chain 1: 2200 -1168.287 0.037 0.020 -#> Chain 1: 2300 -1147.215 0.038 0.020 -#> Chain 1: 2400 -1212.261 0.040 0.020 -#> Chain 1: 2500 -1154.181 0.041 0.020 -#> Chain 1: 2600 -1182.885 0.043 0.024 -#> Chain 1: 2700 -1147.512 0.044 0.031 -#> Chain 1: 2800 -1146.729 0.043 0.031 -#> Chain 1: 2900 -1181.801 0.035 0.030 -#> Chain 1: 3000 -1147.941 0.026 0.029 -#> Chain 1: 3100 -1156.345 0.026 0.029 -#> Chain 1: 3200 -1160.267 0.025 0.029 -#> Chain 1: 3300 -1152.493 0.024 0.029 -#> Chain 1: 3400 -1156.977 0.019 0.024 -#> Chain 1: 3500 -1202.479 0.017 0.024 -#> Chain 1: 3600 -1146.741 0.020 0.029 -#> Chain 1: 3700 -1147.768 0.017 0.007 MEDIAN ELBO CONVERGED +#> Chain 1: 100 -268499.784 1.000 1.000 +#> Chain 1: 200 -7761.034 17.298 33.596 +#> Chain 1: 300 -2335.360 12.306 2.323 +#> Chain 1: 400 -1532.517 9.361 2.323 +#> Chain 1: 500 -1347.813 7.516 1.000 +#> Chain 1: 600 -1288.467 6.271 1.000 +#> Chain 1: 700 -1177.122 5.389 0.524 +#> Chain 1: 800 -1158.795 4.717 0.524 +#> Chain 1: 900 -1154.629 4.193 0.137 +#> Chain 1: 1000 -1148.537 3.775 0.137 +#> Chain 1: 1100 -1231.497 3.681 0.095 MAY BE DIVERGING... INSPECT ELBO +#> Chain 1: 1200 -1149.984 0.329 0.071 +#> Chain 1: 1300 -1143.163 0.097 0.067 +#> Chain 1: 1400 -1154.361 0.046 0.046 +#> Chain 1: 1500 -1165.635 0.033 0.016 +#> Chain 1: 1600 -1196.434 0.031 0.016 +#> Chain 1: 1700 -1148.639 0.026 0.016 +#> Chain 1: 1800 -1150.040 0.024 0.010 MEDIAN ELBO CONVERGED #> Chain 1: #> Chain 1: Drawing a sample of size 2000 from the approximate posterior... #> Chain 1: COMPLETED. -#> WARN [2024-06-13 20:04:52] epinow: partial match of 'log_p' to 'log_p__' - $, diagnostics, log_p -#> WARN [2024-06-13 20:04:52] epinow: partial match of 'log_g' to 'log_g__' - $, diagnostics, log_g -#> WARN [2024-06-13 20:04:53] epinow: Pareto k diagnostic value is 1.21. Resampling is disabled. Decreasing tol_rel_obj may help if variational algorithm has terminated prematurely. Otherwise consider using sampling instead. - +#> WARN [2024-06-13 20:31:40] epinow: partial match of 'log_p' to 'log_p__' - $, diagnostics, log_p +#> WARN [2024-06-13 20:31:40] epinow: partial match of 'log_g' to 'log_g__' - $, diagnostics, log_g +#> WARN [2024-06-13 20:31:40] epinow: Pareto k diagnostic value is 2. Resampling is disabled. Decreasing tol_rel_obj may help if variational algorithm has terminated prematurely. Otherwise consider using sampling instead. - ``` ## Evaluating performance @@ -324,11 +337,23 @@ Let's see how long each model took to run. Note that the run time measured here ``` r # Extract the run times models <- names(models) -runtimes <- sapply( +runtimes <- vapply( + results, + function(x) x$timing, + double(1) +) +# Extracting the difftime objects drops the units, so we need to extract them separately +runtimes_units <- vapply( results, - function(x) x$timing + function(x) attr(x$timing, "units"), + character(1) ) -knitr::kable(runtimes, col.names = c("models", "Run time"), caption = "Run times (seconds)") +# We then recompose the runtimes with the units +runtimes_vec <- paste(round(runtimes), runtimes_units) +# Add the model names +names(runtimes_vec) <- models +# Print in table +knitr::kable(runtimes_vec, col.names = c("models", "Run time"), caption = "Run times") ``` @@ -337,8 +362,12 @@ Table: Run times (seconds) |models | Run time| |:---------------|---------:| -|non_mechanistic | 11.789461| -|vb | 3.043279| +|gp | 5.011109| +|non_mechanistic | 13.676094| +|rw1_no_gp | 5.972183| +|rw7_no_gp | 2.413113| +|rw7_gp | 6.252660| +|vb | 2.673222| ### Forecast evaluation From 0d3ee1ba9860a1b4cb90dd36b943d6e859210ced Mon Sep 17 00:00:00 2001 From: James Azam Date: Fri, 12 Jul 2024 18:06:33 +0100 Subject: [PATCH 05/66] Improve wording --- vignettes/speedup_options.Rmd.orig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vignettes/speedup_options.Rmd.orig b/vignettes/speedup_options.Rmd.orig index b6ceafd6a..f0a17ae75 100644 --- a/vignettes/speedup_options.Rmd.orig +++ b/vignettes/speedup_options.Rmd.orig @@ -358,7 +358,7 @@ plot(infections_plot) ### Mechanistic vs non-mechanistic models -- Estimation in _EpiNow2_ using the mechanistic approaches (prior on Rt) is often much slower than the non- mechanistic approach. The mechanistic model is slower because it models intricate details about the processes and mechanisms that drive $Rt$ estimates. The nonmechanistic model, on the other hand, is faster because it does not model these intricate details. If performing a retrospective analysis, the _non-mechanistic model_ (where `rt = NULL`) is better suited and a faster alternative to the mechanistic $Rt$ model. +- Estimation in _EpiNow2_ using the mechanistic approaches (prior on Rt) is often much slower than the non-mechanistic approach. The mechanistic model is slower because it models intricate details about the processes and mechanisms that drive $Rt$ estimates. On the other hand, the non-mechanistic model (where `rt = NULL`) is faster because it does not model these intricate details. If performing a retrospective analysis, the _non-mechanistic model_ is better suited and a faster alternative to modelling $Rt$. ### Exact vs approximate methods From cd06eebcb1170bb6594b874592ffac1471a28340 Mon Sep 17 00:00:00 2001 From: James Azam Date: Fri, 12 Jul 2024 18:08:56 +0100 Subject: [PATCH 06/66] Improve wording --- vignettes/speedup_options.Rmd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vignettes/speedup_options.Rmd b/vignettes/speedup_options.Rmd index f77daff65..8d081ca30 100644 --- a/vignettes/speedup_options.Rmd +++ b/vignettes/speedup_options.Rmd @@ -23,7 +23,7 @@ library(cmdstanr) library(ggplot2) ``` -This vignette outlines available options for speeding up model runtimes in _EpiNow2_. We will compare the models by run time, quantitative, and qualitative performance of fit against the "true" trajectory of the data. +This vignette explores available forecasting model options in _EpiNow2_ and how they vary by speed and forecast performance. We will compare the models by run time and forecast performance (quantitatively and qualitatively) against the "true" trajectory of the data. ## The benchmarking data From e0333a27b7cf35e02da700ced615a1422d08487c Mon Sep 17 00:00:00 2001 From: James Azam Date: Fri, 12 Jul 2024 18:39:19 +0100 Subject: [PATCH 07/66] Improve wording --- vignettes/speedup_options.Rmd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vignettes/speedup_options.Rmd b/vignettes/speedup_options.Rmd index 8d081ca30..4e2634fa2 100644 --- a/vignettes/speedup_options.Rmd +++ b/vignettes/speedup_options.Rmd @@ -27,7 +27,7 @@ This vignette explores available forecasting model options in _EpiNow2_ and how ## The benchmarking data -We will simulate the "true" infections and Rt data using for benchmarking using `forecast_infections()`. +To compare the model options, we will need a dataset for which we know the "true" values and trajectories. So, we will start by generating the "true" infections and Rt data using _EpiNow2_'s `forecast_infections()` function. `forecast_infections()` requires a fitted estimates object from `estimate_infections()` or `epinow()`, the trajectory of the reproduction number, `R`, and the number of samples to simulate. So, we will set these up first. From 61b7a44ca58450532e674a8bb00756950343fe14 Mon Sep 17 00:00:00 2001 From: James Azam Date: Fri, 12 Jul 2024 18:40:02 +0100 Subject: [PATCH 08/66] Apply suggestions from code review --- vignettes/speedup_options.Rmd | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/vignettes/speedup_options.Rmd b/vignettes/speedup_options.Rmd index 4e2634fa2..0dcd48e7d 100644 --- a/vignettes/speedup_options.Rmd +++ b/vignettes/speedup_options.Rmd @@ -31,9 +31,9 @@ To compare the model options, we will need a dataset for which we know the "true `forecast_infections()` requires a fitted estimates object from `estimate_infections()` or `epinow()`, the trajectory of the reproduction number, `R`, and the number of samples to simulate. So, we will set these up first. -To obtain the `estimates` object, we have to fit the `estimate_infections()` model to observed data to recover realistic parameter values for the simulation. To do this, we will use the first 100 observations of the `example_confirmed` dataset vendored with this package. This dataset provides a time series of cases by reporting date. We will also use the `example_generation_time`, `example_incubation_period`, and `example_reporting_delay` values that come with the package. +To obtain the `estimates` object, we will run the `estimate_infections()` model using real-world observed data and delay distributions to recover realistic parameter values. We will use the first 100 observations of the `example_confirmed` data set, the `example_generation_time`, `example_incubation_period`, and `example_reporting_delay` values that come with _EpiNow2_. -Several of the parameters we will use through out this vignette will have the same value, so we will set these up first. +Several of the parameters we will use throughout this vignette will have the same value, so we will set them up here in one place. ``` r # Set the number of cores to use @@ -115,7 +115,7 @@ forecast <- forecast_infections(estimates, R = R_noisy, samples = 10) ``` We will now extract the benchamrking data: -- `R_true`: the median of the simulated Rts, and +- `R_true`: the median of the simulated $Rt$, and - `infections_true`: the infections by date of infection @@ -128,25 +128,25 @@ posterior_sample <- forecast$samples[sample == 1] # Extract the simulated infections infections_true <- posterior_sample[variable == "infections"]$value -# Extract the simulated reported cases +# Extract the simulated reported cases and rename the "value" column to "confirm" (to match EpiNow2 requirements) reported_cases_true <- posterior_sample[ variable == "reported_cases", .(date, confirm = value) ] ``` -Now, to the main part of this vignette: we will compare the run times of different models in _EpiNow2_. +Now, to the main part of this vignette: we will compare the run times of different model options. ## Model options We will use the following models: - _gp_: A Gaussian process prior on the reproduction number $R_t$ and no random walk, using _MCMC sampling_. - _vb_: A Gaussian process prior on the reproduction number $R_t$ and no random walk, using the _variational inference_ algorithm. -- _non_mechanistic_: A model with that uses _back calculation_. +- _non_mechanistic_: A model with that uses _back calculation and sets no prior on $R_t$. - _rw1_no_gp_: A model with a 1-day random walk and Gaussian process turned off. - _rw7_no_gp_: A model with a 7-day random walk and Gaussian process turned off. - _rw7_gp_: A model with a 7-day random walk and Gaussian process turned on. -- _pf_: A model that uses the "pathfinder" algorithm (from the `{cmdstanr}` package) for sampling. -- _lp_: A model that uses the "Laplace" algorithm (from the `{cmdstanr}` package) for sampling. +- _pf_: A model that uses the "pathfinder" algorithm (from the [`{cmdstanr}`](https://github.com/stan-dev/cmdstanr) package) for sampling. +- _lp_: A model that uses the "Laplace" algorithm (from the [`{cmdstanr}`](https://github.com/stan-dev/cmdstanr) package) for sampling. ``` r @@ -373,7 +373,7 @@ Table: Run times (seconds) Now, we will compare the estimated and true values using the continuous ranked probability score (CRPS). The CRPS is a proper scoring rule that measures the accuracy of probabilistic forecasts. We will use the `crps()` function from the `{scoringutils}` package. -To calculate the CRPS for the estimated Rt and infections, we will first set up a function that makes sure the data and estimates are of the same length and calls the `crps_sample()` function from the `{scoringutils}` package. +To calculate the CRPS for the estimated $R_t$ and infections, we will first set up a function that makes sure the data and estimates are of the same length and calls the `crps_sample()` function from the `{scoringutils}` package. ``` r # A function to calculate the CRPS @@ -385,7 +385,7 @@ calc_crps <- function(x, truth) { } ``` -Now, we can calculate CRPS for the Rt and infection estimates of the various models and using `calc_crps()`. +Now, we can calculate CRPS for the $R_t$ and infection estimates of the various models using `calc_crps()`. ``` r @@ -472,7 +472,7 @@ plot(infections_plot) ![plot of chunk infections_plot](figure/infections_plot-1.png) -## Some considerations +## Some considerations when using the model options ### Mechanistic vs non-mechanistic models From 3f5b6cc501b81d531cabe68a3ae519a85e37d3c5 Mon Sep 17 00:00:00 2001 From: jamesaazam Date: Sun, 14 Jul 2024 00:23:11 +0100 Subject: [PATCH 09/66] Improve various sections --- vignettes/speedup_options.Rmd | 380 ++++++++++++++-------------------- 1 file changed, 152 insertions(+), 228 deletions(-) diff --git a/vignettes/speedup_options.Rmd b/vignettes/speedup_options.Rmd index 0dcd48e7d..a9a4e469e 100644 --- a/vignettes/speedup_options.Rmd +++ b/vignettes/speedup_options.Rmd @@ -7,14 +7,22 @@ vignette: > %\VignetteIndexEntry{Speeding up model runs} %\VignetteEncoding{UTF-8} %\VignetteEngine{knitr::rmarkdown} -editor_options: - chunk_output_type: console --- +```{r setup, include=FALSE} + knitr::opts_chunk$set( + echo = TRUE, + cache = FALSE, + message = FALSE, + warning = FALSE, + collapse = FALSE, + comment = "#>", + fig.height = 6, + fig.width = 6 +) +``` - - -``` r +```{r packages} library(EpiNow2) library(scoringutils) library(data.table) @@ -23,19 +31,18 @@ library(cmdstanr) library(ggplot2) ``` -This vignette explores available forecasting model options in _EpiNow2_ and how they vary by speed and forecast performance. We will compare the models by run time and forecast performance (quantitatively and qualitatively) against the "true" trajectory of the data. +This vignette explores available model options in _EpiNow2_ and how they vary by speed and accuracy of estimates. We will compare the models by run time and accuracy of estimates (quantitatively and qualitatively) against the "true" trajectory of the data. ## The benchmarking data -To compare the model options, we will need a dataset for which we know the "true" values and trajectories. So, we will start by generating the "true" infections and Rt data using _EpiNow2_'s `forecast_infections()` function. +To compare the model options, we will need a dataset for which we know the "true" values and trajectories. So, we will start by generating the "true" infections and $R_t$ data using _EpiNow2_'s `forecast_infections()` function. -`forecast_infections()` requires a fitted estimates object from `estimate_infections()` or `epinow()`, the trajectory of the reproduction number, `R`, and the number of samples to simulate. So, we will set these up first. +`forecast_infections()` requires a fitted estimates object from `epinow()` with `output` set to "fit", the trajectory of the reproduction number, `R`, and the number of samples to simulate. So, we will set these up first. -To obtain the `estimates` object, we will run the `estimate_infections()` model using real-world observed data and delay distributions to recover realistic parameter values. We will use the first 100 observations of the `example_confirmed` data set, the `example_generation_time`, `example_incubation_period`, and `example_reporting_delay` values that come with _EpiNow2_. +To obtain the `estimates` object, we will run the `epinow()` function using real-world observed data and delay distributions to recover realistic parameter values. We will use the first $100$ observations of the `example_confirmed` data set, the `example_generation_time`, `example_incubation_period`, and `example_reporting_delay` values that come with _EpiNow2_. -Several of the parameters we will use throughout this vignette will have the same value, so we will set them up here in one place. - -``` r +Several of the argument values we will use here will be kept the same for all the model runs, so we will set them up here. These include the generation time, incubation period, reporting delay, observation model options, and the forecast horizon. +```{r, shared_inputs} # Set the number of cores to use options(mc.cores = 4) @@ -69,31 +76,26 @@ obs <- obs_opts( return_likelihood = TRUE ) -# Forecast horizon +# No forecasting; just estimation horizon <- 0 ``` -Now, let's generate the `estimates` object. - -``` r -estimates <- estimate_infections( - example_confirmed[1:100], +Now, let's generate the `estimates` object from `epinow()`. +```{r estimates} +estimates <- epinow( + data = example_confirmed[1:100], generation_time = generation_time_opts(example_generation_time), delays = delay_opts(example_incubation_period + example_reporting_delay), rt = rt_opts(prior = list(mean = 2, sd = 0.1), rw = 14), gp = NULL, obs = obs, - horizon = horizon + horizon = horizon, + output = "fit" ) ``` -``` -#> DEBUG [2024-06-13 20:11:11] estimate_infections: Running in exact mode for 2000 samples (across 4 chains each with a warm up of 250 iterations each) and 114 time steps of which 0 are a forecast -``` - For the `R` data, we will set up an arbitrary trajectory and add some Gaussian noise. - -``` r +```{r R-data} # Arbitrary reproduction number trajectory R <- c( rep(2, 40), rep(0.5, 10), rep(1, 10), 1 + 0.04 * 1:20, rep(1.4, 5), @@ -103,23 +105,21 @@ R <- c( R_noisy <- R * rnorm(length(R), 1, 0.05) ``` -Now, we will simulate the true infections and $Rt$ data by sampling from $10$ posterior samples. - -``` r +Now, we are ready to simulate the true infections and $R_t$ data by sampling from $10$ posterior samples. +```{r true-data} # Forecast infections and the trajectory of Rt -forecast <- forecast_infections(estimates, R = R_noisy, samples = 10) -``` - -``` -#> DEBUG [2024-06-13 20:11:46] simulate_infections: Running in exact mode for 1 samples (across 1 chains each with a warm up of 1 iterations each) and 154 time steps of which 40 are a forecast +forecast <- forecast_infections( + estimates$estimates, + R = R_noisy, + samples = 10 +) ``` -We will now extract the benchamrking data: -- `R_true`: the median of the simulated $Rt$, and -- `infections_true`: the infections by date of infection - - -``` r +We will now extract the required data: +- `R_true`: the median of the simulated $R_t$ values, +- `infections_true`: the infections by date of infection, and +- `reported_cases_true`: the reported cases by date of report. +```{r extract-true-data} R_true <- forecast$summarised[variable == "R"]$median # Get the posterior samples from which to extract the simulated infections and reported cases @@ -134,61 +134,94 @@ reported_cases_true <- posterior_sample[ ] ``` -Now, to the main part of this vignette: we will compare the run times of different model options. +Now, to the main part of this vignette: we will define and run the different model options and evaluate their runtimes and estimation performance. ## Model options -We will use the following models: -- _gp_: A Gaussian process prior on the reproduction number $R_t$ and no random walk, using _MCMC sampling_. -- _vb_: A Gaussian process prior on the reproduction number $R_t$ and no random walk, using the _variational inference_ algorithm. -- _non_mechanistic_: A model with that uses _back calculation and sets no prior on $R_t$. -- _rw1_no_gp_: A model with a 1-day random walk and Gaussian process turned off. -- _rw7_no_gp_: A model with a 7-day random walk and Gaussian process turned off. -- _rw7_gp_: A model with a 7-day random walk and Gaussian process turned on. -- _pf_: A model that uses the "pathfinder" algorithm (from the [`{cmdstanr}`](https://github.com/stan-dev/cmdstanr) package) for sampling. -- _lp_: A model that uses the "Laplace" algorithm (from the [`{cmdstanr}`](https://github.com/stan-dev/cmdstanr) package) for sampling. - +```{r model-options} +models <- data.table( + model = c( + "gp", + "gp_stationary", + "non_mechanistic", + "rw1_no_gp", + "rw1_gp", + "rw7_no_gp", + "rw7_gp", + "pathfinder", + "laplace" + ), + description = c( + "The model with the Gaussian process turned on and a non-stationary prior on $R_t$ (default)", + "The model with the Gaussian process turned on and the stationary Gaussian process prior on $R_t$", + "The non-mechanistic model", + "The model with a 1-day random walk, the Gaussian process turned off and a non-stationary prior on $R_t$ (default)", + "The model with a 1-day random walk, the Gaussian process turned on, and a non-stationary prior on $R_t$ (default)", + "The model with a 7-day random walk, the Gaussian process turned off, and a non-stationary prior on $R_t$ (default)", + "The model with a 7-day random walk, the Gaussian process turned on, and a non-stationary prior on $R_t$ (default)", + "A model that uses the \"pathfinder\" algorithm (from the [`{cmdstanr}`](https://github.com/stan-dev/cmdstanr) package) for sampling", + "A model that uses the \"Laplace\" algorithm (from the [`{cmdstanr}`](https://github.com/stan-dev/cmdstanr) package) for sampling." + ) +) + +knitr::kable(models, caption = "Model options") +``` -``` r +```{r models} models <- list( - gp = list( - rt = rt_opts(prior = list(mean = 2, sd = 0.1)) - ) - , - # The non-mechanistic model - non_mechanistic = list(rt = NULL), - # The model with a 1-day random walk and Gaussian process turned off - rw1_no_gp = list( - rt = rt_opts( - prior = list(mean = 2, sd = 0.1), - rw = 1 - ), - gp = NULL - ), - # The model with a 7-day random walk and Gaussian process turned off - rw7_no_gp = list( - rt = rt_opts( - prior = list(mean = 2, sd = 0.1), - rw = 7 - ), - gp = NULL - ), - # The model with a 7-day random walk and Gaussian process (default) - rw7_gp = list( + # The model with the Gaussian process turned on and a non-stationary prior on R_t (default) + # gp = list( + # rt = rt_opts( + # prior = list(mean = 2, sd = 0.1) + # ) + # ), + # The model with the Gaussian process turned on and the stationary Gaussian process prior on R_t + gp_stationary = list( rt = rt_opts( prior = list(mean = 2, sd = 0.1), - rw = 7, gp_on = "R0" ) ), + # The non-mechanistic model + non_mechanistic = list(rt = NULL)#, + # The model with a 1-day random walk, the Gaussian process turned off and a non-stationary prior on R_t (default) + # rw1_no_gp = list( + # rt = rt_opts( + # prior = list(mean = 2, sd = 0.1), + # rw = 1 + # ), + # gp = NULL + # ), + # The model with a 1-day random walk, the Gaussian process turned on, and a non-stationary prior on R_t (default) + # rw1_gp = list( + # rt = rt_opts( + # prior = list(mean = 2, sd = 0.1), + # rw = 1 + # ) + # ), + # The model with a 7-day random walk, the Gaussian process turned off, and a non-stationary prior on R_t (default) + # rw7_no_gp = list( + # rt = rt_opts( + # prior = list(mean = 2, sd = 0.1), + # rw = 7 + # ), + # gp = NULL + # ), + # The model with a 7-day random walk, the Gaussian process turned on, and a non-stationary prior on R_t (default) + # rw7_gp = list( + # rt = rt_opts( + # prior = list(mean = 2, sd = 0.1), + # rw = 7 + # ) + # ), # Model that uses the variational inference method instead of MCMC - vb = list( - stan = stan_opts( - method = "vb", - trials = 5, - samples = 2000 - ) - )#, + # vb = list( + # stan = stan_opts( + # method = "vb", + # trials = 5, + # samples = 2000 + # ) + # ), # The model that uses "pathfinder" approximate sampling from `cmdstanr` package # pathfinder = list( # stan = stan_opts( @@ -212,16 +245,22 @@ models <- list( ## Running the models -Let's run the models and gather the results. We will sweep across the list of models `models` and shared model options `model_inputs`. We will use the `epinow()` function which is a wrapper around `estimate_infections()` and returns useful outputs like the timing of model runs. +Let's run the models and gather the results. -``` r +Let's combine the shared model inputs into a list for use across all the models. +```{r model_inputs} model_inputs <- list( data = reported_cases_true, generation_time = generation_time_opts(generation_time), delays = delay_opts(delay), obs = obs, - horizon = horizon + horizon = horizon, + verbose = FALSE ) +``` + +To run the models, we will sweep across the list of models `models` and shared model inputs `model_inputs`. We will use the `epinow()` function and return useful outputs like the timing of model runs. +```{r run-models} # Run the models results <- lapply( models, @@ -237,106 +276,17 @@ results <- lapply( ) ``` -``` -#> WARN [2024-06-13 20:11:46] epinow: partial match of 'length' to 'lengths' - $, consecutive, length -#> WARN [2024-06-13 20:11:46] epinow: partial match of 'length' to 'lengths' - $, consecutive, length -#> WARN [2024-06-13 20:11:46] epinow: partial match of 'length' to 'lengths' - $, consecutive, length -#> DEBUG [2024-06-13 20:11:46] epinow: Running in exact mode for 2000 samples (across 4 chains each with a warm up of 250 iterations each) and 154 time steps of which 0 are a forecast -#> WARN [2024-06-13 20:16:43] epinow: There were 37 divergent transitions after warmup. See -#> https://mc-stan.org/misc/warnings.html#divergent-transitions-after-warmup -#> to find out why this is a problem and how to eliminate them. - -#> WARN [2024-06-13 20:16:43] epinow: Examine the pairs() plot to diagnose sampling problems -#> - -#> WARN [2024-06-13 20:16:46] epinow: NAs introduced by coercion to integer range - .f, .x[[i]], ... -#> WARN [2024-06-13 20:16:46] epinow: NAs introduced by coercion to integer range - .f, .x[[i]], ... -#> WARN [2024-06-13 20:16:47] epinow: partial match of 'length' to 'lengths' - $, consecutive, length -#> WARN [2024-06-13 20:16:47] epinow: partial match of 'length' to 'lengths' - $, consecutive, length -#> WARN [2024-06-13 20:16:47] epinow: partial match of 'length' to 'lengths' - $, consecutive, length -#> DEBUG [2024-06-13 20:16:47] epinow: Running in exact mode for 2000 samples (across 4 chains each with a warm up of 250 iterations each) and 147 time steps of which 0 are a forecast -#> WARN [2024-06-13 20:17:00] epinow: partial match of 'length' to 'lengths' - $, consecutive, length -#> WARN [2024-06-13 20:17:00] epinow: partial match of 'length' to 'lengths' - $, consecutive, length -#> WARN [2024-06-13 20:17:00] epinow: partial match of 'length' to 'lengths' - $, consecutive, length -#> DEBUG [2024-06-13 20:17:00] epinow: Running in exact mode for 2000 samples (across 4 chains each with a warm up of 250 iterations each) and 154 time steps of which 0 are a forecast -#> WARN [2024-06-13 20:22:57] epinow: Bulk Effective Samples Size (ESS) is too low, indicating posterior means and medians may be unreliable. -#> Running the chains for more iterations may help. See -#> https://mc-stan.org/misc/warnings.html#bulk-ess - -#> WARN [2024-06-13 20:22:57] epinow: Tail Effective Samples Size (ESS) is too low, indicating posterior variances and tail quantiles may be unreliable. -#> Running the chains for more iterations may help. See -#> https://mc-stan.org/misc/warnings.html#tail-ess - -#> WARN [2024-06-13 20:22:58] epinow: NAs introduced by coercion to integer range - .f, .x[[i]], ... -#> WARN [2024-06-13 20:22:59] epinow: partial match of 'length' to 'lengths' - $, consecutive, length -#> WARN [2024-06-13 20:22:59] epinow: partial match of 'length' to 'lengths' - $, consecutive, length -#> WARN [2024-06-13 20:22:59] epinow: partial match of 'length' to 'lengths' - $, consecutive, length -#> DEBUG [2024-06-13 20:22:59] epinow: Running in exact mode for 2000 samples (across 4 chains each with a warm up of 250 iterations each) and 154 time steps of which 0 are a forecast -#> WARN [2024-06-13 20:25:23] epinow: NAs introduced by coercion to integer range - .f, .x[[i]], ... -#> WARN [2024-06-13 20:25:23] epinow: partial match of 'length' to 'lengths' - $, consecutive, length -#> WARN [2024-06-13 20:25:23] epinow: partial match of 'length' to 'lengths' - $, consecutive, length -#> WARN [2024-06-13 20:25:23] epinow: partial match of 'length' to 'lengths' - $, consecutive, length -#> DEBUG [2024-06-13 20:25:23] epinow: Running in exact mode for 2000 samples (across 4 chains each with a warm up of 250 iterations each) and 154 time steps of which 0 are a forecast -#> WARN [2024-06-13 20:31:38] epinow: NAs introduced by coercion to integer range - .f, .x[[i]], ... -#> WARN [2024-06-13 20:31:39] epinow: partial match of 'length' to 'lengths' - $, consecutive, length -#> WARN [2024-06-13 20:31:39] epinow: partial match of 'length' to 'lengths' - $, consecutive, length -#> WARN [2024-06-13 20:31:39] epinow: partial match of 'length' to 'lengths' - $, consecutive, length -#> DEBUG [2024-06-13 20:31:39] epinow: Running in approximate mode for 10000 iterations (with 5 attempts). Extracting 2000 approximate posterior samples for 154 time steps of which 0 are a forecast -#> Chain 1: ------------------------------------------------------------ -#> Chain 1: EXPERIMENTAL ALGORITHM: -#> Chain 1: This procedure has not been thoroughly tested and may be unstable -#> Chain 1: or buggy. The interface is subject to change. -#> Chain 1: ------------------------------------------------------------ -#> Chain 1: -#> Chain 1: -#> Chain 1: -#> Chain 1: Gradient evaluation took 0.000129 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 1.29 seconds. -#> Chain 1: Adjust your expectations accordingly! -#> Chain 1: -#> Chain 1: -#> Chain 1: Begin eta adaptation. -#> Chain 1: Iteration: 1 / 250 [ 0%] (Adaptation) -#> Chain 1: Iteration: 50 / 250 [ 20%] (Adaptation) -#> Chain 1: Iteration: 100 / 250 [ 40%] (Adaptation) -#> Chain 1: Iteration: 150 / 250 [ 60%] (Adaptation) -#> Chain 1: Iteration: 200 / 250 [ 80%] (Adaptation) -#> Chain 1: Success! Found best value [eta = 1] earlier than expected. -#> Chain 1: -#> Chain 1: Begin stochastic gradient ascent. -#> Chain 1: iter ELBO delta_ELBO_mean delta_ELBO_med notes -#> Chain 1: 100 -268499.784 1.000 1.000 -#> Chain 1: 200 -7761.034 17.298 33.596 -#> Chain 1: 300 -2335.360 12.306 2.323 -#> Chain 1: 400 -1532.517 9.361 2.323 -#> Chain 1: 500 -1347.813 7.516 1.000 -#> Chain 1: 600 -1288.467 6.271 1.000 -#> Chain 1: 700 -1177.122 5.389 0.524 -#> Chain 1: 800 -1158.795 4.717 0.524 -#> Chain 1: 900 -1154.629 4.193 0.137 -#> Chain 1: 1000 -1148.537 3.775 0.137 -#> Chain 1: 1100 -1231.497 3.681 0.095 MAY BE DIVERGING... INSPECT ELBO -#> Chain 1: 1200 -1149.984 0.329 0.071 -#> Chain 1: 1300 -1143.163 0.097 0.067 -#> Chain 1: 1400 -1154.361 0.046 0.046 -#> Chain 1: 1500 -1165.635 0.033 0.016 -#> Chain 1: 1600 -1196.434 0.031 0.016 -#> Chain 1: 1700 -1148.639 0.026 0.016 -#> Chain 1: 1800 -1150.040 0.024 0.010 MEDIAN ELBO CONVERGED -#> Chain 1: -#> Chain 1: Drawing a sample of size 2000 from the approximate posterior... -#> Chain 1: COMPLETED. -#> WARN [2024-06-13 20:31:40] epinow: partial match of 'log_p' to 'log_p__' - $, diagnostics, log_p -#> WARN [2024-06-13 20:31:40] epinow: partial match of 'log_g' to 'log_g__' - $, diagnostics, log_g -#> WARN [2024-06-13 20:31:40] epinow: Pareto k diagnostic value is 2. Resampling is disabled. Decreasing tol_rel_obj may help if variational algorithm has terminated prematurely. Otherwise consider using sampling instead. - -``` - ## Evaluating performance ### Run times -Let's see how long each model took to run. Note that the run time measured here uses a crude method that compares the start and end times of each simulation. It is meant to be approximate and may not be accurate. For accurate run times, we recommend using a more sophisticated benchmarking tool like `bench` or `microbenchmark`. +Let's see how long each model took to run. Note that the run time measured here uses a crude method that compares the start and end times of each simulation. It only measures the time taken for one model run and may not be accurate. For more accurate run time measurements, we recommend using a more sophisticated approach like those provided by packages like [`{bench}`](https://cran.r-project.org/web/packages/bench/index.html) and [`{microbenchmark}`](https://cran.r-project.org/web/packages/microbenchmark/index.html). +Another thing to note is that here, we used `r `getOption("mc.cores", 1L)` cores and so using more or fewer cores might change the runtime results. To speed up the model runs, we recommend checking the number of cores available on your machine using `parallel::detectCores()` and setting a high enough number of cores in `options()`. See the \ref{shared_inputs} chunk above for an example. -``` r +```{r runtimes} # Extract the run times -models <- names(models) +model_names <- names(models) runtimes <- vapply( results, function(x) x$timing, @@ -351,31 +301,17 @@ runtimes_units <- vapply( # We then recompose the runtimes with the units runtimes_vec <- paste(round(runtimes), runtimes_units) # Add the model names -names(runtimes_vec) <- models -# Print in table -knitr::kable(runtimes_vec, col.names = c("models", "Run time"), caption = "Run times") +names(runtimes_vec) <- model_names +# Print table +knitr::kable(runtimes_vec, col.names = c("models", "Run time"), caption = "Run times for various _EpiNow2_ models") ``` - - -Table: Run times (seconds) - -|models | Run time| -|:---------------|---------:| -|gp | 5.011109| -|non_mechanistic | 13.676094| -|rw1_no_gp | 5.972183| -|rw7_no_gp | 2.413113| -|rw7_gp | 6.252660| -|vb | 2.673222| - -### Forecast evaluation +### Estimation performance Now, we will compare the estimated and true values using the continuous ranked probability score (CRPS). The CRPS is a proper scoring rule that measures the accuracy of probabilistic forecasts. We will use the `crps()` function from the `{scoringutils}` package. -To calculate the CRPS for the estimated $R_t$ and infections, we will first set up a function that makes sure the data and estimates are of the same length and calls the `crps_sample()` function from the `{scoringutils}` package. - -``` r +To calculate the CRPS for the estimated R_t and infections, we will first set up a function that makes sure the true data and estimates are of the same length and calls the `crps_sample()` function from the `{scoringutils}` package. +```{r crps-func} # A function to calculate the CRPS calc_crps <- function(x, truth) { shortest_obs_length <- min(ncol(x), length(truth)) @@ -385,10 +321,9 @@ calc_crps <- function(x, truth) { } ``` -Now, we can calculate CRPS for the $R_t$ and infection estimates of the various models using `calc_crps()`. - +Now, we will extract the $R_t$ and infection estimates and calculate the CRPS using the `calc_crps()` function above. -``` r +```{r fit_crps} # Get the Rt samples Rt_estimated <- lapply(results, function(x) { if ("R[1]" %in% names(x$estimates$fit)) { @@ -417,66 +352,55 @@ infections_crps <- lapply( ) ``` -We will now post-process the CRPS results to make them easier to visualise. +We will now post-process the CRPS results to make them easier to visualise by adding a "metric" and "time" column and reshape the data to long format. - -``` r +```{r fit_postprocessing} # Post-processing the CRPS results of the Rt estimates rt_df <- as.data.table(rt_crps) -rt_df[, metric := "CRPS"] rt_df[, time := 1:.N] rt_df <- melt( rt_df, - id.vars = c("metric", "time"), + id.vars = c("time"), variable.name = "model" ) # Post-processing the CRPS results of the infection estimates infections_df <- as.data.table(infections_crps) -infections_df[, metric := "CRPS"] infections_df[, time := 1:.N] infections_df <- melt( infections_df, - id.vars = c("metric", "time"), + id.vars = c("time"), variable.name = "model" ) ``` -Let's visualise the CRPS results for the Rt and infection estimates. - +Let's visualise the CRPS results for the $R_t$ and infection estimates. -``` r +```{r rt_plot} rt_plot <- ggplot(rt_df, aes(x = time, y = value, colour = model)) + geom_line() + scale_colour_brewer("Model", palette = "Dark2") + - xlab("Time") + - ylab("CRPS") + - ggplot2::theme_bw() + - ggtitle("Reconstructing R") + scale_y_continuous(labels = scales::label_number_auto()) + + labs(x = "Time", y = "CRPS", title = "Reconstructing Rt") + + ggplot2::theme_bw() plot(rt_plot) ``` -![plot of chunk rt_plot](figure/rt_plot-1.png) - - -``` r +```{r infections_plot} infections_plot <- ggplot(infections_df, aes(x = time, y = value, colour = model)) + geom_line() + scale_colour_brewer("Model", palette = "Dark2") + - xlab("Time") + - ylab("CRPS") + - ggplot2::theme_bw() + - ggtitle("Reconstructing infections") + scale_y_continuous(labels = scales::comma) + + labs(x = "Time", y = "CRPS", title = "Reconstructing infections") + + ggplot2::theme_bw() plot(infections_plot) ``` -![plot of chunk infections_plot](figure/infections_plot-1.png) - ## Some considerations when using the model options ### Mechanistic vs non-mechanistic models -- Estimation in _EpiNow2_ using the mechanistic approaches (prior on Rt) is often much slower than the non- mechanistic approach. The mechanistic model is slower because it models intricate details about the processes and mechanisms that drive $Rt$ estimates. The nonmechanistic model, on the other hand, is faster because it does not model these intricate details. If performing a retrospective analysis, the _non-mechanistic model_ (where `rt = NULL`) is better suited and a faster alternative to the mechanistic $Rt$ model. +- Estimation in _EpiNow2_ using the mechanistic approaches (prior on R_t) is often much slower than the non- mechanistic approach. The mechanistic model is slower because it models intricate details about the processes and mechanisms that drive $R_t$ estimates. The nonmechanistic model, on the other hand, is faster because it does not model these intricate details. If performing a retrospective analysis, the _non-mechanistic model_ (where `rt = NULL`) is better suited and a faster alternative to the mechanistic $R_t$ model. ### Exact vs approximate methods @@ -484,4 +408,4 @@ plot(infections_plot) ### Granularity of estimates -- The random walk method reduces granularity in estimates, compared to the other methods. +- The random walk method reduces granularity in estimates, compared to the other methods. \ No newline at end of file From 70788835d4e19a8aad0f1c04feadb5d9ba7354f0 Mon Sep 17 00:00:00 2001 From: jamesaazam Date: Mon, 15 Jul 2024 13:13:34 +0100 Subject: [PATCH 10/66] First successful run of full vignette with exact models --- .../speedup-options-infections_plot-1.png | Bin 0 -> 38915 bytes vignettes/speedup-options-rt_plot-1.png | Bin 0 -> 72347 bytes vignettes/speedup_options.Rmd | 377 +++++++++++++----- vignettes/speedup_options.Rmd.orig | 240 ++++++----- 4 files changed, 423 insertions(+), 194 deletions(-) create mode 100644 vignettes/speedup-options-infections_plot-1.png create mode 100644 vignettes/speedup-options-rt_plot-1.png diff --git a/vignettes/speedup-options-infections_plot-1.png b/vignettes/speedup-options-infections_plot-1.png new file mode 100644 index 0000000000000000000000000000000000000000..a19eb9875b0ef4b5a81a1637df3df31146fff1ea GIT binary patch literal 38915 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 +Let's start by looking at the two broad individual model types (stationary vs non-stationary) +```{r infections_ns_gp_plot,class.source = 'fold-hide'} +rt_ns_gp_plot <- ggplot( + data = rt_crps_dt_final[rt_model_gp == "non_stationary"], # this model has extremely large crps + # data = infections_crps_dt_final + ) + + geom_line( + aes(x = date, + y = crps, + color = model + ) ) + - geom_line() + scale_colour_brewer("Model", palette = "Dark2") + - scale_y_continuous(labels = label_number_auto()) + + # scale_y_log10(labels = label_number_auto()) + + labs( + x = "Time", + y = "CRPS", + title = "Performance in estimating Rt (non-stationary models)" + ) + + ggplot2::theme_minimal() + + guides(color = guide_legend(title = "Model")) + + theme(legend.position = "bottom") + + facet_grid(~factor(epidemic_phase, levels = c("growth", "peak", "decline"))) + +plot(rt_ns_gp_plot) +``` + +```{r infections_ns_gp_plot,class.source = 'fold-hide'} +rt_st_gp_plot <- ggplot( + data = rt_crps_dt_final[rt_model_gp == "stationary"], + ) + geom_line( - data = rt_crps_ts[model == "default_mcmc_rstan", ], - aes(x = date, y = crps), - color = "blue", - linetype = 5, - linewidth = 1 + aes(x = date, + y = crps, + color = model + ) ) + - ggplot2::geom_vline( - xintercept = max(rt_crps_ts[type == "estimate"]$date), - linetype = 3, - linewidth = 1.2, - color = "tomato", - alpha = 0.3 + scale_colour_brewer("Model", palette = "Dark2") + + # scale_y_log10(labels = label_number_auto()) + + labs( + x = "Time", + y = "CRPS", + title = "Performance in estimating Rt (stationary models)" ) + - ggplot2::geom_vline( - xintercept = max(rt_crps_ts[type == "estimate based on partial data"]$date), - linetype = 3, - linewidth = 1.2, - color = "tomato", - alpha = 0.5 + ggplot2::theme_minimal() + + guides(color = guide_legend(title = "Model")) + + theme(legend.position = "bottom") + + facet_grid(~factor(epidemic_phase, levels = c("growth", "peak", "decline"))) + +plot(rt_st_gp_plot) +``` + +We will now plot all the models, grouped by the type of Gaussian process used to estimate $R_t$ (stationary vs non-stationary). +```{r infections_all_models_plot,class.source = 'fold-hide'} +rt_crps_all_models_plot <- ggplot( + data = rt_crps_dt_final[!is.na(crps)], #remove failed models ) + - ggplot2::geom_vline( - xintercept = max(rt_crps_ts[type == "forecast"]$date), - linetype = 3, - linewidth = 1.2, - color = "tomato", - alpha = 1 + geom_line( + aes(x = date, + y = crps, + color = rt_model_gp, + group = model + ) ) + + scale_colour_brewer("Model", palette = "Dark2") + + # scale_y_log10(labels = label_number_auto()) + labs( x = "Time", y = "CRPS", - title = "Time-varying performance of models (Rt)", - caption = "The default model is highlighted in blue for comparison" - ) + - ggplot2::theme_bw() + - guides(color = guide_legend(title = "Rt model Gaussian process")) + - theme(legend.position = "bottom") - -plot(rt_plot) + title = "Estimating Rt" + ) + + ggplot2::theme_minimal() + + guides(color = guide_legend(title = "Model type")) + + theme(legend.position = "bottom") + + facet_grid(rows = vars(factor(epidemic_phase, levels = c("growth", "peak", "decline")))) + +plot(rt_crps_all_models_plot) ``` -Now, let's compare the models by run time and total CRPS for $R_t$ estimation and forecasting. -```{r rt-crps-summaries,class.source = 'fold-hide'} -# Find total CRPS per estimate type -rt_crps_total_by_model <- lapply( - rt_crps_by_model, - function(x) x[, sum(crps), by = list(type)] %>% - setnames("V1", "crps_total") - ) %>% - rbindlist(idcol = "model") %>% - dcast(formula = model ~ type, value.var = "crps_total") - -# Combine the total CRPS for the Rt estimates -rt_crps_summaries <- merge.data.table( - rt_crps_total_by_model, - runtimes_dt[, c("model", "runtime", "description")], - by = "model" -) +Let's look at the total CRPS for each model over data snapshot. +```{r rt_total_crps,class.source = 'fold-hide'} +# We'll calculate the total crps per model, epidemic phase, and estimate type +# We'll drop the retrospective estimates as we are only interested in the real-time and forecast performance +rt_total_crps_dt <- rt_crps_dt_final[ + , + .( + total_crps = sum(crps), + rt_model_gp = rt_model_gp[1], + fitting = fitting[1], + package = package[1] + ), + by = .(model, epidemic_phase, type) +][ + type != "estimate" +] -# Order by run time and order the columns -rt_crps_summaries <- rt_crps_summaries[order(runtime), ] %>% - setcolorder(c("model", "runtime")) - -# Print table -knitr::kable( - rt_crps_summaries, - caption = "Run times and $R_t$ estimation/forecast performance - measured by total CRPS - of various _EpiNow2_ models (ordered by run times)", - format.args = list( - big.mark = ",", - scientific = FALSE, - digits = 2 +rt_total_crps_plot <- ggplot(data = rt_total_crps_dt) + + geom_col( + aes( + x = type, + y = total_crps, + fill = rt_model_gp + ), + position = position_dodge() + ) + + scale_fill_brewer(palette = "Dark2") + + labs( + x = "Estimate type", + y = "Total CRPS", + fill = "Model type", + title = "Estimating Rt" + ) + + facet_grid( + ~factor( + epidemic_phase, + levels = c("growth", "peak", "decline") + ) ) -) +rt_total_crps_plot ``` -Next, let's visualise model performance over time for the infection trajectory. We will group the models according to the type of $R_t$ model Gaussian process (stationary/non-stationary/none). The default model is highlighted in blue for comparison. The dashed red vertical lines represent, from left to right and in decreasing transparency, the end of estimation with complete data, partial data, and forecasting. -```{r infections_plot,class.source = 'fold-hide'} -# Prepare the data -infection_crps_ts <- infection_crps_by_model %>% - rbindlist(idcol = "model") %>% - merge.data.table(model_components, by = "model") -infections_plot <- ggplot( - infection_crps_ts, - aes(x = date, y = crps, group = model, color = rt_model_gp) +Next, let's visualise model performance over time in estimating and forecasting infections. + +We'll start by looking at the individual model groups (stationary vs non-stationary) +```{r infections_ns_gp_plot,class.source = 'fold-hide'} +infections_ns_gp_plot <- ggplot( + data = infections_crps_dt_final[rt_model_gp == "non_stationary"], # this model has extremely large crps + # data = infections_crps_dt_final ) + - geom_line() + - scale_colour_brewer("Model", palette = "Dark2") + - scale_y_continuous(labels = label_number_auto()) + geom_line( - data = infection_crps_ts[model == "default_mcmc_rstan", ], - aes(x = date, y = crps), - color = "blue", - linetype = 5, - linewidth = 1 + aes(x = date, + y = crps, + color = model + ) ) + - ggplot2::geom_vline( - xintercept = max(infection_crps_ts[type == "estimate"]$date), - linetype = 3, - linewidth = 1.2, - color = "tomato", - alpha = 0.3 + scale_colour_brewer("Model", palette = "Set1") + + # scale_y_log10(labels = label_number_auto()) + + labs( + x = "Time", + y = "CRPS (log-transformed)", + title = "Estimating and predicting infections (non-stationary models)" ) + - ggplot2::geom_vline( - xintercept = max(infection_crps_ts[type == "estimate based on partial data"]$date), - linetype = 3, - linewidth = 1.2, - color = "tomato", - alpha = 0.5 + ggplot2::theme_minimal() + + guides(color = guide_legend(title = "Model")) + + theme(legend.position = "bottom") + + facet_grid(~factor(epidemic_phase, levels = c("growth", "peak", "decline"))) + +plot(infections_ns_gp_plot) +``` + +```{r infections_ns_gp_plot,class.source = 'fold-hide'} +infections_st_gp_plot <- ggplot( + data = infections_crps_dt_final[rt_model_gp == "stationary"], ) + - ggplot2::geom_vline( - xintercept = max(infection_crps_ts[type == "forecast"]$date), - linetype = 3, - linewidth = 1.2, - color = "tomato", - alpha = 1 + geom_line( + aes(x = date, + y = crps, + color = model + ) ) + + scale_colour_brewer("Model", palette = "Dark2") + + # scale_y_log10(labels = label_number_auto()) + labs( x = "Time", y = "CRPS", - title = "Time-varying performance of models (infections)", - caption = "The default model is highlighted in blue for comparison" + title = "Estimating and predicting infections (stationary models)" ) + - ggplot2::theme_bw() + - guides(color = guide_legend(title = "Rt model Gaussian process")) + - theme(legend.position = "bottom") + ggplot2::theme_minimal() + + guides(color = guide_legend(title = "Model")) + + theme(legend.position = "bottom") + + facet_grid(~factor(epidemic_phase, levels = c("growth", "peak", "decline"))) -plot(infections_plot) +plot(infections_st_gp_plot) ``` -We will now compare the models by run time and estimation performance for infections. -```{r infections-crps-summaries,class.source = 'fold-hide'} -# Find total CRPS per estimate type -infection_crps_total_by_model <- lapply( - infection_crps_by_model, - function(x) x[, sum(crps), by = list(type)] %>% - setnames("V1", "crps_total") - ) %>% - rbindlist(idcol = "model") %>% - dcast(formula = model ~ type, value.var = "crps_total") - -# Combine the total CRPS for the infection estimates -infection_crps_summaries <- merge.data.table( - infection_crps_total_by_model, - runtimes_dt[, c("model", "runtime", "description")], - by = "model" -) +We will now plot all the models, grouped by the type of $R_t$ model Gaussian process (stationary/non-stationary/none). +```{r infections_all_models_plot,class.source = 'fold-hide'} +infections_all_models_plot <- ggplot( + data = infections_crps_dt_final[!is.na(crps)], #remove failed models + ) + + geom_line( + aes(x = date, + y = crps, + color = rt_model_gp, + group = model + ) + ) + + scale_colour_brewer("Model", palette = "Dark2") + + # scale_y_log10(labels = label_number_auto()) + + labs( + x = "Time", + y = "CRPS", + title = "Estimating and predicting infections" + ) + + ggplot2::theme_minimal() + + guides(color = guide_legend(title = "Model type")) + + theme(legend.position = "bottom") + + facet_grid(~factor(epidemic_phase, levels = c("growth", "peak", "decline"))) -# Order by run time and order the columns -infection_crps_summaries <- infection_crps_summaries[order(runtime), ] %>% - setcolorder(c("model", "runtime")) - -# Print table -knitr::kable( - infection_crps_summaries, - caption = "Run times and infection estimation/forecast performance - measured by total CRPS - of various _EpiNow2_ models (ordered by run times)", - format.args = list( - big.mark = ",", - scientific = FALSE, - digits = 2 - ) -) +plot(infections_all_models_plot) ``` -From the table of run times and CRPS measures, we can see that there is often a trade-off between run time/speed and estimation/forecasting performance, here measured with the CRPS. - -The fastest model was ``r as.character(rt_crps_summaries[1, ]$model)``, which was over `r round(as.numeric(rt_crps_summaries[model == "default_mcmc_rstan", "runtime"]/rt_crps_summaries[1, "runtime"]), 1)` times faster than the default model. Comparatively, it was `r as.character(ifelse(rt_crps_summaries[1, "estimate"] > rt_crps_summaries[model == "default_mcmc_rstan", "estimate"], "worse", "better"))` at estimating $R_t$ on complete data, `r as.character(ifelse(rt_crps_summaries[1, "estimate based on partial data"] > rt_crps_summaries[model == "default_mcmc_rstan", "estimate based on partial data"], "worse", "better"))` than the default model in estimating $R_t$ with partial data, and `r as.character(ifelse(rt_crps_summaries[1, "estimate based on partial data"] > rt_crps_summaries[model == "default_mcmc_rstan", "estimate based on partial data"], "worse", "better"))` at forecasting it. For estimating and forecasting infections, this model was `r as.character(ifelse(infection_crps_summaries[1, "estimate"] > infection_crps_summaries[model == "default_mcmc_rstan", "estimate"], "worse", "better"))` at estimating it using complete data, `r as.character(ifelse(infection_crps_summaries[1, "estimate based on partial data"] > infection_crps_summaries[model == "default_mcmc_rstan", "estimate based on partial data"], "worse", "better"))` with partial data, and `r as.character(ifelse(infection_crps_summaries[1, "forecast"] > infection_crps_summaries[model == "default_mcmc_rstan", "forecast"], "worse", "better"))` at forecasting it. +Let's look at the total CRPS for each model over data snapshot. +```{r rt_total_crps,class.source = 'fold-hide'} +# We'll calculate the total crps per model, epidemic phase, and estimate type +# We'll drop the retrospective estimates as we are only interested in the real-time and forecast performance +infections_total_crps_dt <- infections_crps_dt_final[ + , + .( + total_crps = sum(crps), + rt_model_gp = rt_model_gp[1], + fitting = fitting[1], + package = package[1] + ), + by = .(model, epidemic_phase, type) +][ + type != "estimate" +] -The non-mechanistic model is different to the other models in that it does not model $R_t$ mechanistically. It was over `r round(as.numeric(rt_crps_summaries[model == "default_mcmc_rstan", "runtime"]/rt_crps_summaries[model == "non_mechanistic", "runtime"]), 1)` times faster than the default model. Compared to the default model, it was `r as.character(ifelse(rt_crps_summaries[model == "non_mechanistic", "estimate"] > rt_crps_summaries[model == "default_mcmc_rstan", "estimate"], "worse", "better"))` at estimating $R_t$ on complete data, `r as.character(ifelse(rt_crps_summaries[model == "non_mechanistic", "estimate based on partial data"] > rt_crps_summaries[model == "default_mcmc_rstan", "estimate based on partial data"], "worse", "better"))` than the default model in estimating $R_t$ on partial data, and `r as.character(ifelse(rt_crps_summaries[model == "non_mechanistic", "estimate based on partial data"] > rt_crps_summaries[model == "default_mcmc_rstan", "estimate based on partial data"], "worse", "better"))` at forecasting it. For estimating and forecasting infections, this model was `r as.character(ifelse(infection_crps_summaries[model == "non_mechanistic", "estimate"] > infection_crps_summaries[model == "default_mcmc_rstan", "estimate"], "worse", "better"))` at estimating using complete, `r as.character(ifelse(infection_crps_summaries[model == "non_mechanistic", "estimate based on partial data"] > infection_crps_summaries[model == "default_mcmc_rstan", "estimate based on partial data"], "worse", "better"))` with partial data and `r as.character(ifelse(infection_crps_summaries[model == "non_mechanistic", "forecast"] > infection_crps_summaries[model == "default_mcmc_rstan", "forecast"], "worse", "better"))` at forecasting. +infections_total_crps_plot <- ggplot(data = infections_total_crps_dt) + + geom_col( + aes( + x = type, + y = total_crps, + fill = rt_model_gp + ), + position = position_dodge() + ) + + scale_fill_brewer(palette = "Dark2") + + labs( + x = "Estimate type", + y = "Total CRPS", + fill = "Model type", + title = "Estimating infections" + ) + + facet_grid( + ~factor( + epidemic_phase, + levels = c("growth", "peak", "decline") + ) + ) +infections_total_crps_plot +``` -Finally, let's compare the slowest model to the default. The slowest model was ``r (rt_crps_summaries[.N, ]$model)``. It was `r round(as.numeric(rt_crps_summaries[.N, "runtime"]/rt_crps_summaries[model == "default_mcmc_rstan", "runtime"]), 1)` times slower than the default model. Compared to the default model, it was `r as.character(ifelse(rt_crps_summaries[.N, "estimate"] > rt_crps_summaries[model == "default_mcmc_rstan", "estimate"], "worse", "better"))` at estimating $R_t$ on complete data, `r as.character(ifelse(rt_crps_summaries[.N, "estimate based on partial data"] > rt_crps_summaries[model == "default_mcmc_rstan", "estimate based on partial data"], "worse", "better"))` on estimating with partial data, and `r as.character(ifelse(rt_crps_summaries[.N, "estimate based on partial data"] > rt_crps_summaries[model == "default_mcmc_rstan", "estimate based on partial data"], "worse", "better"))` at forecasting $R_t$. In terms of estimating and forecasting infections, the `r print(rt_crps_summaries[.N, ]$model)` model was `r as.character(ifelse(infection_crps_summaries[.N, "estimate"] > infection_crps_summaries[model == "default_mcmc_rstan", "estimate"], "worse", "better"))` at estimating with complete data, and additionally, `r as.character(ifelse(infection_crps_summaries[.N, "estimate based on partial data"] > infection_crps_summaries[model == "default_mcmc_rstan", "estimate based on partial data"], "worse", "better"))` in estimating with partial data, and `r as.character(ifelse(infection_crps_summaries[.N, "forecast"] > infection_crps_summaries[model == "default_mcmc_rstan", "forecast"], "worse", "better"))` at forecasting. +From the results of the model run times and CRPS measures, we can see that there is often a trade-off between run times/speed and estimation/forecasting performance, here measured with the CRPS. These results show that choosing an appropriate model for a task requires carefully considering the use case and appropriate trade-offs. Below are a few considerations. @@ -709,7 +944,7 @@ Estimation in `{EpiNow2}` using the mechanistic approaches (prior on $R_t$) is o ### Exact vs approximate sampling methods -The default sampling method, set through `stan_opts()`, performs [MCMC sampling](https://en.wikipedia.org/wiki/Markov_chain_Monte_Carlo) using [`{rstan}`](https://cran.r-project.org/web/packages/rstan/vignettes/rstan.html). The MCMC sampling method is accurate but is often slow. The variational inference method is faster because it is an [approximate method](https://arxiv.org/abs/1506.03431). In `{EpiNow2}`, you can use this method with an `{rstan}` or [`{cmdstanr}`](https://mc-stan.org/cmdstanr/) backend but you must install the latter to access its functionalities. Additionally, `{EpiNow2}` supports using the [Laplace](https://mc-stan.org/docs/cmdstan-guide/laplace_sample_config.html) and [Pathfinder](https://mc-stan.org/docs/cmdstan-guide/pathfinder_config.html) approximate samplers through `{cmdstanr}` but these two methods are currently experimental in `{cmdstanr}` and have not been well tested here. Future enhancements to this vignette will include their benchmarks as well but initial tests show they are extremely fast but with varied estimation performance depending on the data. Moreover, they may not be as reliable as the default MCMC sampling method and we do not recommend using them in real-world inference. +The default sampling method, set through `stan_opts()`, performs [MCMC sampling](https://en.wikipedia.org/wiki/Markov_chain_Monte_Carlo) using [`{rstan}`](https://cran.r-project.org/web/packages/rstan/vignettes/rstan.html). The MCMC sampling method is accurate but is often slow. The Laplace, pathfinder, and variational inference methods are faster because they are approximate (See, for example, a detailed explanation for [automatic variational inference in Stan](https://arxiv.org/abs/1506.03431)). In `{EpiNow2}`, you can use varational inference with an `{rstan}` or [`{cmdstanr}`](https://mc-stan.org/cmdstanr/) backend but you must install the latter to access its functionalities. Additionally, `{EpiNow2}` supports using the [Laplace](https://mc-stan.org/docs/cmdstan-guide/laplace_sample_config.html) and [Pathfinder](https://mc-stan.org/docs/cmdstan-guide/pathfinder_config.html) approximate samplers through `{cmdstanr}` but these two methods are currently experimental in `{cmdstanr}` and have not been well tested in the wild. The approximate methods may not be as reliable as the default MCMC sampling method and we do not recommend using them in real-world inference. ### Smoothness/granularity of estimates @@ -719,6 +954,6 @@ The random walk method reduces smoothness/granularity of the estimates, compared The run times measured here use a crude method that compares the start and end times of each simulation. It only measures the time taken for one model run and may not be accurate. For more accurate run time measurements, we recommend using a more sophisticated approach like those provided by packages like [`{bench}`](https://cran.r-project.org/web/packages/bench/index.html) and [`{microbenchmark}`](https://cran.r-project.org/web/packages/microbenchmark/index.html). -Another thing to note is that here, we used `r getOption("mc.cores", 1L)` cores for the simulations and so using more or fewer cores might change the run time results. We, however, expect the relative rankings to be the same or similar. To speed up the model runs, we recommend checking the number of cores available on your machine using `parallel::detectCores()` and passing a high enough number of cores to `mc.cores` through the `options()` function. See the benchmarking data setup chunk above for an example. +Secondly, we used `r getOption("mc.cores", 1L)` cores for the simulations and so using more or fewer cores might change the run time results. We, however, expect the relative rankings to be the same or similar. To speed up the model runs, we recommend checking the number of cores available on your machine using `parallel::detectCores()` and passing a high enough number of cores to `mc.cores` through the `options()` function. See the benchmarking data setup chunk above for an example. -A final caveat is that the `R` trajectory used to generate the data for benchmarking only represents one scenario. Here, it is non-stationary and drawn from different data generating processes (i.e stepwise changes, linear changes, etc). This potentially biases the experiment towards the less mechanistic model options (more because there is less room for mechanistic choices to do well as none of the latent process models are well defined to fit to this data). +lastly, the `R` trajectory used to generate the data for benchmarking only represents one scenario. This could favour one model type or solver over another. From efe0977f1c0c840399d07648234799b51fe1b08f Mon Sep 17 00:00:00 2001 From: jamesmbaazam Date: Thu, 31 Oct 2024 16:46:00 +0000 Subject: [PATCH 53/66] Fix duplicate chunk names --- vignettes/speedup_options.Rmd.orig | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/vignettes/speedup_options.Rmd.orig b/vignettes/speedup_options.Rmd.orig index c9e04b264..f65999260 100644 --- a/vignettes/speedup_options.Rmd.orig +++ b/vignettes/speedup_options.Rmd.orig @@ -598,7 +598,7 @@ rt_crps_full[, epidemic_phase := names(snapshot_date_names)[ ``` Let's do the same for the infections. -```{r rt_crps,class.source = 'fold-hide'} +```{r infections_crps,class.source = 'fold-hide'} # Extract estimated infections infections_by_snapshot <- get_model_results(results, variable = "infections") @@ -688,7 +688,7 @@ infections_crps_dt_final <- merge( Let's first see how the models performed over time for the $R_t$ using the CRPS. We will group the models according to the type of Gaussian process used to estimate $R_t$ (stationary/non-stationary). Let's start by looking at the two broad individual model types (stationary vs non-stationary) -```{r infections_ns_gp_plot,class.source = 'fold-hide'} +```{r rt_ns_gp_plot,class.source = 'fold-hide'} rt_ns_gp_plot <- ggplot( data = rt_crps_dt_final[rt_model_gp == "non_stationary"], # this model has extremely large crps # data = infections_crps_dt_final @@ -714,7 +714,7 @@ rt_ns_gp_plot <- ggplot( plot(rt_ns_gp_plot) ``` -```{r infections_ns_gp_plot,class.source = 'fold-hide'} +```{r rt_st_gp_plot,class.source = 'fold-hide'} rt_st_gp_plot <- ggplot( data = rt_crps_dt_final[rt_model_gp == "stationary"], ) + @@ -740,7 +740,7 @@ plot(rt_st_gp_plot) ``` We will now plot all the models, grouped by the type of Gaussian process used to estimate $R_t$ (stationary vs non-stationary). -```{r infections_all_models_plot,class.source = 'fold-hide'} +```{r rt_all_models_plot,class.source = 'fold-hide'} rt_crps_all_models_plot <- ggplot( data = rt_crps_dt_final[!is.na(crps)], #remove failed models ) + @@ -838,7 +838,7 @@ infections_ns_gp_plot <- ggplot( plot(infections_ns_gp_plot) ``` -```{r infections_ns_gp_plot,class.source = 'fold-hide'} +```{r infections_st_gp_plot,class.source = 'fold-hide'} infections_st_gp_plot <- ggplot( data = infections_crps_dt_final[rt_model_gp == "stationary"], ) + @@ -891,7 +891,7 @@ plot(infections_all_models_plot) ``` Let's look at the total CRPS for each model over data snapshot. -```{r rt_total_crps,class.source = 'fold-hide'} +```{r infections_total_crps,class.source = 'fold-hide'} # We'll calculate the total crps per model, epidemic phase, and estimate type # We'll drop the retrospective estimates as we are only interested in the real-time and forecast performance infections_total_crps_dt <- infections_crps_dt_final[ From 20ea043a78ba3dc5139f8c3286bfe5db962adcb2 Mon Sep 17 00:00:00 2001 From: jamesmbaazam Date: Fri, 1 Nov 2024 15:41:51 +0000 Subject: [PATCH 54/66] Rename Rmd to benchmarks --- vignettes/benchmarks.Rmd.orig | 959 ++++++++++++++++++++++++++++++++++ 1 file changed, 959 insertions(+) create mode 100644 vignettes/benchmarks.Rmd.orig diff --git a/vignettes/benchmarks.Rmd.orig b/vignettes/benchmarks.Rmd.orig new file mode 100644 index 000000000..f65999260 --- /dev/null +++ b/vignettes/benchmarks.Rmd.orig @@ -0,0 +1,959 @@ +--- +title: "Model benchmarks: speed versus forecast accuracy tradeoffs" +output: + rmarkdown::html_vignette: + toc: true + code_folding: show +csl: https://raw.githubusercontent.com/citation-style-language/styles/master/apa-numeric-superscript-brackets.csl +vignette: > + %\VignetteEncoding{UTF-8} + %\VignetteIndexEntry{Model benchmarks: speed versus forecast accuracy tradeoffs} + %\VignetteEngine{knitr::rmarkdown} +editor_options: + chunk_output_type: console +--- + +```{r setup, include=FALSE} +knitr::opts_chunk$set( + collapse = TRUE, + comment = "#>", + message = FALSE, + fig.height = 6.5, + fig.width = 6.5, + fig.path = "vignettes/speedup_options-" +) +set.seed(9876) +``` + +```{r packages} +library(EpiNow2) +library(scoringutils) +library(data.table) +library(rstan) +library(cmdstanr) +library(ggplot2) +library(dplyr) +library(lubridate) +library(scales) +library(posterior) +``` + +In using `{EpiNow2}`, users will often be faced with two decision points that will guide their choice of an appropriate model: (i) use case: retrospective vs real-time analysis, and (ii) limited computing resources. `{EpiNow2}` provides a range of customisations of the default model to suit these decision points. + +The aim of this vignette is to benchmarks four (4) `{EpiNow2}` model options chosen to cover a range of use cases. We will benchmark the default model, the model with the 7-day random walk prior on $R_t$, the non-mechanistic model, which has no explicit prior on $R_t$, and the non-residual $R_t$ model, which assumes a stationary prior on $R_t$. + +We will analyse the estimation and forecasting performance of these models when used with MCMC sampling as well as three (3) approximate sampling methods: variational bayes, pathfinder, and laplace approximation. To achieve this, we will compare the models based on the accuracy of estimates given complete and partial data at 3 time points of a simulated epidemic: (i) epidemic growth phase, (ii) epidemic peak, and (iii) epidemic decline phase. + +## The benchmarking data + +We will start by setting up the "true" dataset with known trajectories of $R_t$ and infections using `{EpiNow2}`'s `forecast_infections()` function. + +`forecast_infections()` requires a fitted estimates object from `epinow()` with `output` set to "fit", the trajectory of the reproduction number, `R`, and the number of samples to simulate. So, we will set these up first. + +To obtain the `estimates` object, we will run the `epinow()` function using real-world observed data and delay distributions to recover realistic parameter values. For the `data`, we will use the first $60$ observations of the `example_confirmed` data set. We will use the `example_generation_time` for the generation time, and the `example_incubation_period`, and `example_reporting_delay` to specify and delays. These come with the package. For the `rt` model, we will use a 14-day random walk prior, with a mean of $2$ and standard deviation of $0.1$. As we only want to generate estimates, we will turn off forecasting by setting `horizon = 0`. + +Throughout this vignette, several argument values, including the observation model options, the rt model prior will be maintained, so we will define them here. + +```{r share_inputs} +# Observation model options +obs <- obs_opts( + scale = list(mean = 0.1, sd = 0.025), + return_likelihood = TRUE +) + +rt_prior_default <- list(mean = 2, sd = 0.1) +``` + +Now, we can generate the `estimates` object. +```{r estimates} +estimates <- epinow( + data = example_confirmed[1:60], + generation_time = generation_time_opts(example_generation_time), + delays = delay_opts(example_incubation_period + example_reporting_delay), + rt = rt_opts(prior = rt_prior_default, rw = 14), + gp = NULL, + obs = obs, + horizon = 0, # no forecasting + output = "fit" +) +``` + +For the `R` data, we will set up an arbitrary trajectory and add some Gaussian noise. +```{r R-data} +# Arbitrary reproduction number trajectory +R <- c( + rep(1.50, 20), + rep(1.25, 10), + rep(1, 10), + rep(0.5, 10), + rep(1, 10), + 1 + 0.04 * 1:20, + rep(1.4, 5), + 1.4 - 0.02 * 1:20, + rep(1.4, 10), + rep(0.8, 50), + 0.8 + 0.02 * 1:20 +) +# Add Gaussian noise +R_noisy <- R * rnorm(length(R), 1.1, 0.05) +# Plot +ggplot(data = data.frame(R = R_noisy)) + + geom_line(aes(x = seq_along(R_noisy), y = R)) + + labs(x = "Time") +``` + +Let's proceed to simulate the true infections and $R_t$ data by sampling from $10$ posterior samples. +```{r true-data} +# Forecast infections and the trajectory of Rt +forecast <- forecast_infections( + estimates$estimates, + R = R_noisy, + samples = 10 +) +``` + +Let's now extract the true data for benchmarking: +- `R_true`: the median of the simulated $R_t$ values, +- `infections_true`: the infections by date of infection, and +- `reported_cases_true`: the reported cases by date of report. +```{r extract-true-data} +R_true <- forecast$summarised[variable == "R"]$median + +# Get the posterior samples from which to extract the simulated infections and reported cases +posterior_sample <- forecast$samples[sample == 1] + +# Extract the simulated infections +infections_true <- posterior_sample[variable == "infections"]$value + +# Extract the simulated reported cases and rename the "value" column to "confirm" (to match EpiNow2 requirements) +reported_cases_true <- posterior_sample[ + variable == "reported_cases", .(date, confirm = value) +] +``` + +Let's see what the cases plot looks like +```{r plot-cases} +cases_traj <- ggplot(data = reported_cases_true) + + geom_line(aes(x = date, y = confirm)) + + scale_y_continuous(label = scales::label_comma()) + + scale_x_date(date_labels = "%b %d", date_breaks = "1 month") +cases_traj +``` + + +We will now proceed to define and run the different model options and evaluate their estimation and forecasting performance alongside their run times using the four sampling methods and time points described in the introduction. + +## Models + +### Descriptions + +Below we describe each model. +```{r model-descriptions,echo = FALSE} +model_descriptions <- dplyr::tribble( + ~model, ~description, + "default_mcmc", "Default model (non-stationary prior on $R_t$); fitting with mcmc", + "default_vb", "Default model (non-stationary prior on $R_t$); fitting with variational bayes", + "default_pathfinder", "Default model (non-stationary prior on $R_t$); fitting with pathfinder algorithm", + "default_laplace", "Default model (non-stationary prior on $R_t$); fitting with laplace approximation", + "non_mechanistic_mcmc", "no mechanistic prior on $R_t$; fitting with mcmc", + "non_mechanistic_vb", "no mechanistic prior on $R_t$; fitting with variational bayes", + "non_mechanistic_pathfinder", "no mechanistic prior on $R_t$; fitting with pathfinder algorithm", + "non_mechanistic_laplace", "no mechanistic prior on $R_t$; fitting with laplace approximation", + "rw7_mcmc", "7-day random walk prior on $R_t$; fitting with mcmc", + "rw7_vb", "7-day random walk prior on $R_t$; fitting with variational bayes", + "rw7_pathfinder", "7-day random walk prior on $R_t$; fitting with pathfinder algorithm", + "rw7_laplace", "7-day random walk prior on $R_t$; fitting with laplace approximation", + "non_residual_mcmc", "Stationary prior on $R_t$; fitting with mcmc", + "non_residual_vb", "Stationary prior on $R_t$; fitting with variational bayes", + "non_residual_pathfinder", "Stationary prior on $R_t$; fitting with pathfinder algorithm", + "non_residual_laplace", "Stationary prior on $R_t$; fitting with laplace algorithm" +) + +knitr::kable(model_descriptions, caption = "Model options") +``` + +```{r model-components,echo = FALSE} +model_components <- dplyr::tribble( + ~model, ~rt_model_gp, ~fitting, ~package, + "default_mcmc", "non_stationary", "mcmc", "rstan", + "default_vb", "non_stationary", "variational_bayes", "rstan", + "default_pathfinder", "non_stationary", "pathfinder", "cmdstanr", + "default_laplace", "non_stationary", "laplace", "cmdstanr", + "non_mechanistic_mcmc", "stationary", "mcmc", "rstan", + "non_mechanistic_vb", "stationary", "variational_bayes", "rstan", + "non_mechanistic_pathfinder", "stationary", "pathfinder", "cmdstanr", + "non_mechanistic_laplace", "stationary", "laplace", "cmdstanr", + "rw7_mcmc", "non_stationary", "mcmc", "rstan", + "rw7_vb", "non_stationary", "variational_bayes", "rstan", + "rw7_pathfinder", "non_stationary", "pathfinder", "cmdstanr", + "rw7_laplace", "non_stationary", "laplace", "cmdstanr", + "non_residual_mcmc", "stationary", "mcmc", "rstan", + "non_residual_vb", "stationary", "variational_bayes", "rstan", + "non_residual_pathfinder", "stationary", "pathfinder", "rstan", + "non_residual_laplace", "stationary", "laplace", "cmdstanr" + ) + +knitr::kable(model_components, caption = "Model components") +``` + +### Configurations + +These are the accompanying configurations for each model, which are modifications of the default model. +```{r model-configs, results = 'hide'} +model_configs <- list( + # The default model with MCMC fitting + default_mcmc = list( + rt = rt_opts() + ), + # The default model with variational bayes fitting + default_vb = list( + rt = rt_opts(), + stan = stan_opts(method = "vb", backend = "rstan") + ), + # The default model with pathfinder fitting + default_pathfinder = list( + rt = rt_opts(), + stan = stan_opts(method = "pathfinder", backend = "cmdstanr") + ), + # The default model with laplace fitting + default_laplace = list( + rt = rt_opts(), + stan = stan_opts(method = "laplace", backend = "cmdstanr") + ), + # The non-mechanistic model with MCMC fitting + non_mechanistic_mcmc = list( + rt = NULL + ), + # The non-mechanistic model with variational bayes fitting + non_mechanistic_vb = list( + rt = NULL, + stan = stan_opts(method = "vb", backend = "rstan") + ), + # The non-mechanistic model with pathfinder fitting + non_mechanistic_pathfinder = list( + rt = NULL, + stan = stan_opts(method = "pathfinder", backend = "cmdstanr") + ), + # The non-mechanistic model with laplace fitting + non_mechanistic_laplace = list( + rt = NULL, + stan = stan_opts(method = "laplace", backend = "cmdstanr") + ), + # The 7-day RW Rt model with MCMC fitting + rw7_mcmc = list( + rt = rt_opts( + prior = rt_prior_default, + rw = 7 + ) + ), + # The 7-day RW Rt model with variational bayes fitting + rw7_vb = list( + rt = rt_opts( + prior = rt_prior_default, + rw = 7 + ), + stan = stan_opts(method = "vb", backend = "rstan") + ), + # The 7-day RW Rt model with pathfinder fitting + rw7_pathfinder = list( + rt = rt_opts( + prior = rt_prior_default, + rw = 7 + ), + stan = stan_opts(method = "pathfinder", backend = "cmdstanr") + ), + # The 7-day RW Rt model with laplace fitting + rw7_laplace = list( + rt = rt_opts( + prior = rt_prior_default, + rw = 7 + ), + stan = stan_opts(method = "laplace", backend = "cmdstanr") + ), + # The non_residual model with MCMC fitting + non_residual_mcmc = list( + rt = rt_opts( + prior = rt_prior_default, + gp_on = "R0" + ) + ), + # The non_residual model with variational bayes fitting + non_residual_vb = list( + rt = rt_opts( + prior = rt_prior_default, + gp_on = "R0" + ), + stan = stan_opts(method = "vb", backend = "rstan") + ), + # The non_residual model with pathfinder fitting + non_residual_pathfinder = list( + rt = rt_opts( + prior = rt_prior_default, + gp_on = "R0" + ), + stan = stan_opts(method = "pathfinder", backend = "cmdstanr") + ), + # The non_residual model with laplace fitting + non_residual_laplace = list( + rt = rt_opts( + prior = rt_prior_default, + gp_on = "R0" + ), + stan = stan_opts(method = "pathfinder", backend = "cmdstanr") + ) +) +``` + +## Running the models + +All the models will share the configuration for the generation time, incubation period, reporting delay, and the forecast horizon, so we will define once and pass them to the models. + +```{r, constant_inputs} +# Generation time +generation_time <- Gamma( + shape = Normal(1.3, 0.3), + rate = Normal(0.37, 0.09), + max = 14 +) + +# Incubation period +incubation_period <- LogNormal( + meanlog = Normal(1.6, 0.05), + sdlog = Normal(0.5, 0.05), + max = 14 +) + +# Reporting delay +reporting_delay <- LogNormal( + meanlog = 0.5, + sdlog = 0.5, + max = 10 +) + +# Combine the incubation period and reporting delay into one delay +delay <- incubation_period + reporting_delay + +# 7-day forecast window +horizon <- 7 +``` + +Let's combine the shared model inputs into a list for use across all the models. +```{r model_inputs} +model_inputs <- list( + generation_time = generation_time_opts(generation_time), + delays = delay_opts(delay), + obs = obs, + horizon = horizon, + verbose = FALSE +) +``` + +Additionally, from the benchmarking data, we choose the following dates to represent the periods of growth, peak, and decline. +```{r snapshot_dates} +snapshot_dates <- as.Date(c("2020-05-15", "2020-06-21", "2020-06-28")) +``` + +Using the chosen dates, we'll create the data snapshots for fitting. +```{r data-snapshots} +data_snaps <- lapply( + snapshot_dates, + function(snap_date) { + reported_cases_true[date <= snap_date] + } +) +names(data_snaps) <- snapshot_dates +``` + +Now, we're ready to run the models. We will use the `epinow()` function and return useful outputs like the timing of model runs. We obtain forecasts for the data excluding the forecast horizon and then compare the forecasts to the data including the horizon in the evaluations. +```{r run-models, results = 'hide'} +# Create a version of epinow() that works like base::try() and works even if some models fail. +safe_epinow <- purrr::safely(epinow) +# Run the models over the different dates +results <- lapply( + data_snaps, function(data) { + lapply( + model_configs, + function(model) { + # Use subset of the data + data_to_fit <- data[1:(.N - model_inputs$horizon)] + model_inputs <- c(model_inputs, data = list(data_to_fit)) + do.call( + safe_epinow, + c( + model_inputs, + model + ) + ) + } + ) + } +) +``` + +## Evaluating performance + +To start with, we will do a few preparations. + +Let's set up a function, `extract_results()` that extracts desired results from the `epinow()` runs per model. This function can be used to extract the "timing", "Rt", and "infections" variables. +```{r extraction-funcs, class.source = 'fold-hide'} +# Function to extract the "timing", "Rt", "infections", and "reports" variables from an +# epinow() run. It expects a model run, x, which contains a "results" or "error" component. +# If all went well, "error" should be NULL. +extract_results <- function(x, variable) { + stopifnot( + "variable must be one of c(\"timing\", \"R\", \"infections\", \"reports\")" = + variable %in% c("timing", "R", "infections", "reports") + ) + # Return NA if there's an error + if (!is.null(x$error)) { + return(NA) + } + + if(variable == "timing") { + return(round(as.duration(x$result$timing), 1)) + } else { + obj <- x$result$estimates$fit + } + + # Extracting "Rt", "infections", and "reports" is different based on the object's class and + # other settings + if (inherits(obj, "stanfit")) { + # Depending on rt_opts(use_rt = TRUE/FALSE), R shows up as R or gen_R + if (variable == "R") { + # The non-mechanistic model returns "gen_R" where as the others sample "R". + if ("R[1]" %in% names(obj)) { + return(extract(obj, "R")$R) + } else { + return(extract(obj, "gen_R")$gen_R) + } + } else { + return(extract(obj, variable)[[variable]]) + } + } else { + obj_mat <- as_draws_matrix(obj) + # Extracting R depends on the value of rt_opts(use_rt = ) + if (variable == "R") { + if ("R[1]" %in% variables(obj_mat)) { + return(subset_draws(obj_mat, "R")) + } else { + return(subset_draws(obj_mat, "gen_R")) + } + } else { + return(subset_draws(obj_mat, variable)) + } + } +} + +# Wrapper for extracting the results and making them into a data.table +get_model_results <- function(results_by_snapshot, variable) { + # Get model results list + lapply( + results_by_snapshot, + function(model_results) { + lapply(model_results, extract_results, variable) + } + ) +} +``` + + +### Run times + +Let's see how long each model took to run. + +```{r runtimes,class.source = 'fold-hide'} +# Extract the run times and reshape to dt +runtimes_by_snapshot <- get_model_results(results, "timing") + +runtimes_dt <- lapply(runtimes_by_snapshot, function(x) as.data.table(x)) |> + rbindlist(idcol = "snapshot_date", ignore.attr = TRUE) + +# Reshape +runtimes_dt_long <- melt( + runtimes_dt, + id.vars = "snapshot_date", # Column to keep as an identifier + measure.vars = model_descriptions$model, # Dynamically select model columns by pattern + variable.name = "model", # Name for the 'model' column + value.name = "timing" # Name for the 'timing' column +) + +# Add model configurations +runtimes_dt_detailed <- merge( + runtimes_dt_long, + model_components, + by = "model" +) + +# snapshot dates dictionary +snapshot_date_names <- c(growth = "2020-05-15", peak = "2020-06-21", decline = "2020-06-28") + +# Replace snapshot_date based on the dictionary +runtimes_dt_detailed[, snapshot_date := names(snapshot_date_names)[match(snapshot_date, snapshot_date_names)]] + +# Move some columns around +setcolorder(runtimes_dt_detailed, "timing", after = "package") + +# Make the snapshot_date column a factor +runtimes_dt_detailed[, snapshot_date := factor(snapshot_date, levels = c("growth", "peak", "decline"))] +# Plot the timing +timing_plot <- ggplot(data = runtimes_dt_detailed) + + geom_col(aes(x = snapshot_date, + y = timing, + fill = rt_model_gp), + position = position_dodge() + ) + + scale_fill_brewer( + palette = "Dark2", + labels = c("stationary" = "stationary", + "non_stationary" = "non-stationary", + "none" = "non-mechanistic" + ) + ) + + labs(x = "Epidemic phase", + y = "Runtime (secs)", + fill = "Model type", + caption = "non-stationary Rt model uses Rt-1 * GP; stationary Rt model uses R0 * GP; non-mechanistic model uses no GP." + ) +timing_plot +``` + +### Estimation performance + +Now, we will evaluate the forecasts using the continuous ranked probability score (CRPS). The CRPS is a proper scoring rule that measures the accuracy of probabilistic forecasts. The smaller the CRPS, the better. We will use the [`crps_sample()`](https://epiforecasts.io/scoringutils/reference/crps_sample.html) function from the [`{scoringutils}`](https://epiforecasts.io/scoringutils/index.html) package. + +We will compare the performance of each model based on forecasts with partial data and complete data. + +To calculate the CRPS for the estimated $R_t$ and infections, we will first set up a function that ensures the true data and estimates are of the same length and then calls the `crps_sample()` function from the `{scoringutils}` package using log-transformed values of the true and estimated values. +```{r crps-func} +# A function to calculate the CRPS +calc_crps <- function(estimates, truth) { + # if the object is not a matrix, then it's an NA (failed run) + if (!inherits(estimates, c("matrix"))) return(rep(NA_real_, length(truth))) + # Assumes that the estimates object is structured with the samples as rows + shortest_obs_length <- min(ncol(estimates), length(truth)) + reduced_truth <- tail(truth, shortest_obs_length) + estimates_transposed <- t(estimates) # transpose to have samples as columns + reduced_estimates <- tail(estimates_transposed, shortest_obs_length) + return( + crps_sample( + log10(reduced_truth), + log10(reduced_estimates) + ) + ) +} +``` + +Now, we will extract the $R_t$ estimates and calculate the CRPS using the `calc_crps()` function above. + +```{r rt_crps,class.source = 'fold-hide'} +# Extract rt values +rt_by_snapshot <- get_model_results(results, variable = "R") + +# Apply function above to calculate CRPS for the estimated Rt +rt_crps_by_snapshot <- lapply( + rt_by_snapshot, + function(snapshot_results) { + lapply( + snapshot_results, function(model_results) { + calc_crps(estimates = model_results, truth = R) # compare against true R + } + ) + } +) + +# Add dates column based on snapshot length +rt_crps_with_dates <- lapply( + rt_crps_by_snapshot, + function(snapshot_results) { + lapply( + snapshot_results, + function (model_results) { + data.table(crps = model_results)[, + date:= min(reported_cases_true$date) + 0: (.N - 1) + ] + } + ) + }) + +# Flatten the results into one dt +rt_crps_flat <- lapply( + rt_crps_with_dates, + function(snapshot_results) { + rbindlist(snapshot_results, idcol = "model") + }) |> + rbindlist(idcol = "snapshot_date") + +# Add model configurations for facetting +rt_crps_full <- merge.data.table( + rt_crps_flat, + model_components, + by = "model" +) + +# Replace the snapshot dates with their description +# Replace snapshot_date based on the dictionary +rt_crps_full[, epidemic_phase := names(snapshot_date_names)[ + match(snapshot_date, snapshot_date_names) +]] +``` + +Let's do the same for the infections. +```{r infections_crps,class.source = 'fold-hide'} +# Extract estimated infections +infections_by_snapshot <- get_model_results(results, variable = "infections") + +# Apply function above to calculate CRPS for the estimated infections +infections_crps_by_snapshot <- lapply( + infections_by_snapshot, + function(snapshot_results) { + lapply( + snapshot_results, function(model_results) { + calc_crps( + estimates = model_results, + truth = infections_true # compare against true infections + ) + } + ) + } +) + +# Add dates column based on snapshot length +infections_crps_with_dates <- lapply( + infections_crps_by_snapshot, + function(snapshot_results) { + lapply( + snapshot_results, + function (model_results) { + data.table(crps = model_results)[ + , + date:= min(reported_cases_true$date) + 0: (.N - 1) + ]} + ) + }) + +# Flatten the results into one dt +infections_crps_flat <- lapply( + infections_crps_with_dates, + function(snapshot_results) { + rbindlist(snapshot_results, idcol = "model") + }) |> + rbindlist(idcol = "snapshot_date") + +# Add model configurations for facetting +infections_crps_full <- merge.data.table( + infections_crps_flat, + model_components, + by = "model" +) + +# Replace the snapshot dates with their description +# Replace snapshot_date based on the dictionary +infections_crps_full[,epidemic_phase := names(snapshot_date_names)[ + match(snapshot_date, snapshot_date_names) +]] +``` + +We will now post-process the CRPS results to make them easier to visualise and summarise. We will add the "type" column from the output of the model to indicate which subsets of the estimates are based on complete data, partial data, or are forecasts. +```{r fit_postprocessing,class.source = 'fold-hide'} +# Get date and fit type from the default model (the same across model outputs) +results_by_model <- list_transpose(results) + +fit_type_by_dates <- lapply( + results_by_model$default_mcmc, + function(results_by_snapshot) { + results_by_snapshot$result$estimates$summarised[ + variable == "reported_cases"][ + , + c("date", "type") + ] + } +) |> + rbindlist(idcol = "snapshot_date") + +# # Add the "type" column +rt_crps_dt_final <- merge( + rt_crps_full, + fit_type_by_dates, + by = c("date", "snapshot_date") +) + +# Add the "type" column +infections_crps_dt_final <- merge( + infections_crps_full, + fit_type_by_dates, + by = c("date", "snapshot_date") +) +``` + +Let's first see how the models performed over time for the $R_t$ using the CRPS. We will group the models according to the type of Gaussian process used to estimate $R_t$ (stationary/non-stationary). + +Let's start by looking at the two broad individual model types (stationary vs non-stationary) +```{r rt_ns_gp_plot,class.source = 'fold-hide'} +rt_ns_gp_plot <- ggplot( + data = rt_crps_dt_final[rt_model_gp == "non_stationary"], # this model has extremely large crps + # data = infections_crps_dt_final + ) + + geom_line( + aes(x = date, + y = crps, + color = model + ) + ) + + scale_colour_brewer("Model", palette = "Dark2") + + # scale_y_log10(labels = label_number_auto()) + + labs( + x = "Time", + y = "CRPS", + title = "Performance in estimating Rt (non-stationary models)" + ) + + ggplot2::theme_minimal() + + guides(color = guide_legend(title = "Model")) + + theme(legend.position = "bottom") + + facet_grid(~factor(epidemic_phase, levels = c("growth", "peak", "decline"))) + +plot(rt_ns_gp_plot) +``` + +```{r rt_st_gp_plot,class.source = 'fold-hide'} +rt_st_gp_plot <- ggplot( + data = rt_crps_dt_final[rt_model_gp == "stationary"], + ) + + geom_line( + aes(x = date, + y = crps, + color = model + ) + ) + + scale_colour_brewer("Model", palette = "Dark2") + + # scale_y_log10(labels = label_number_auto()) + + labs( + x = "Time", + y = "CRPS", + title = "Performance in estimating Rt (stationary models)" + ) + + ggplot2::theme_minimal() + + guides(color = guide_legend(title = "Model")) + + theme(legend.position = "bottom") + + facet_grid(~factor(epidemic_phase, levels = c("growth", "peak", "decline"))) + +plot(rt_st_gp_plot) +``` + +We will now plot all the models, grouped by the type of Gaussian process used to estimate $R_t$ (stationary vs non-stationary). +```{r rt_all_models_plot,class.source = 'fold-hide'} +rt_crps_all_models_plot <- ggplot( + data = rt_crps_dt_final[!is.na(crps)], #remove failed models + ) + + geom_line( + aes(x = date, + y = crps, + color = rt_model_gp, + group = model + ) + ) + + scale_colour_brewer("Model", palette = "Dark2") + + # scale_y_log10(labels = label_number_auto()) + + labs( + x = "Time", + y = "CRPS", + title = "Estimating Rt" + ) + + ggplot2::theme_minimal() + + guides(color = guide_legend(title = "Model type")) + + theme(legend.position = "bottom") + + facet_grid(rows = vars(factor(epidemic_phase, levels = c("growth", "peak", "decline")))) + +plot(rt_crps_all_models_plot) +``` + +Let's look at the total CRPS for each model over data snapshot. +```{r rt_total_crps,class.source = 'fold-hide'} +# We'll calculate the total crps per model, epidemic phase, and estimate type +# We'll drop the retrospective estimates as we are only interested in the real-time and forecast performance +rt_total_crps_dt <- rt_crps_dt_final[ + , + .( + total_crps = sum(crps), + rt_model_gp = rt_model_gp[1], + fitting = fitting[1], + package = package[1] + ), + by = .(model, epidemic_phase, type) +][ + type != "estimate" +] + +rt_total_crps_plot <- ggplot(data = rt_total_crps_dt) + + geom_col( + aes( + x = type, + y = total_crps, + fill = rt_model_gp + ), + position = position_dodge() + ) + + scale_fill_brewer(palette = "Dark2") + + labs( + x = "Estimate type", + y = "Total CRPS", + fill = "Model type", + title = "Estimating Rt" + ) + + facet_grid( + ~factor( + epidemic_phase, + levels = c("growth", "peak", "decline") + ) + ) +rt_total_crps_plot +``` + + +Next, let's visualise model performance over time in estimating and forecasting infections. + +We'll start by looking at the individual model groups (stationary vs non-stationary) +```{r infections_ns_gp_plot,class.source = 'fold-hide'} +infections_ns_gp_plot <- ggplot( + data = infections_crps_dt_final[rt_model_gp == "non_stationary"], # this model has extremely large crps + # data = infections_crps_dt_final + ) + + geom_line( + aes(x = date, + y = crps, + color = model + ) + ) + + scale_colour_brewer("Model", palette = "Set1") + + # scale_y_log10(labels = label_number_auto()) + + labs( + x = "Time", + y = "CRPS (log-transformed)", + title = "Estimating and predicting infections (non-stationary models)" + ) + + ggplot2::theme_minimal() + + guides(color = guide_legend(title = "Model")) + + theme(legend.position = "bottom") + + facet_grid(~factor(epidemic_phase, levels = c("growth", "peak", "decline"))) + +plot(infections_ns_gp_plot) +``` + +```{r infections_st_gp_plot,class.source = 'fold-hide'} +infections_st_gp_plot <- ggplot( + data = infections_crps_dt_final[rt_model_gp == "stationary"], + ) + + geom_line( + aes(x = date, + y = crps, + color = model + ) + ) + + scale_colour_brewer("Model", palette = "Dark2") + + # scale_y_log10(labels = label_number_auto()) + + labs( + x = "Time", + y = "CRPS", + title = "Estimating and predicting infections (stationary models)" + ) + + ggplot2::theme_minimal() + + guides(color = guide_legend(title = "Model")) + + theme(legend.position = "bottom") + + facet_grid(~factor(epidemic_phase, levels = c("growth", "peak", "decline"))) + +plot(infections_st_gp_plot) +``` + +We will now plot all the models, grouped by the type of $R_t$ model Gaussian process (stationary/non-stationary/none). +```{r infections_all_models_plot,class.source = 'fold-hide'} +infections_all_models_plot <- ggplot( + data = infections_crps_dt_final[!is.na(crps)], #remove failed models + ) + + geom_line( + aes(x = date, + y = crps, + color = rt_model_gp, + group = model + ) + ) + + scale_colour_brewer("Model", palette = "Dark2") + + # scale_y_log10(labels = label_number_auto()) + + labs( + x = "Time", + y = "CRPS", + title = "Estimating and predicting infections" + ) + + ggplot2::theme_minimal() + + guides(color = guide_legend(title = "Model type")) + + theme(legend.position = "bottom") + + facet_grid(~factor(epidemic_phase, levels = c("growth", "peak", "decline"))) + +plot(infections_all_models_plot) +``` + +Let's look at the total CRPS for each model over data snapshot. +```{r infections_total_crps,class.source = 'fold-hide'} +# We'll calculate the total crps per model, epidemic phase, and estimate type +# We'll drop the retrospective estimates as we are only interested in the real-time and forecast performance +infections_total_crps_dt <- infections_crps_dt_final[ + , + .( + total_crps = sum(crps), + rt_model_gp = rt_model_gp[1], + fitting = fitting[1], + package = package[1] + ), + by = .(model, epidemic_phase, type) +][ + type != "estimate" +] + +infections_total_crps_plot <- ggplot(data = infections_total_crps_dt) + + geom_col( + aes( + x = type, + y = total_crps, + fill = rt_model_gp + ), + position = position_dodge() + ) + + scale_fill_brewer(palette = "Dark2") + + labs( + x = "Estimate type", + y = "Total CRPS", + fill = "Model type", + title = "Estimating infections" + ) + + facet_grid( + ~factor( + epidemic_phase, + levels = c("growth", "peak", "decline") + ) + ) +infections_total_crps_plot +``` + +From the results of the model run times and CRPS measures, we can see that there is often a trade-off between run times/speed and estimation/forecasting performance, here measured with the CRPS. + +These results show that choosing an appropriate model for a task requires carefully considering the use case and appropriate trade-offs. Below are a few considerations. + +## Things to consider when interpreting these benchmarks + +### Mechanistic vs non-mechanistic models + +Estimation in `{EpiNow2}` using the mechanistic approaches (prior on $R_t$) is often much slower than the non-mechanistic approach. The mechanistic model is slower because it models aspects of the processes and mechanisms that drive $R_t$ estimates using the renewal equation. The non-mechanistic model, on the other hand, runs much faster but does not use the renewal equation to generate infections. Because of this none of the options defining the behaviour of the reproduction number are available in this case, limiting flexibility. The non-mechanistic model in `{EpiNow2}` is equivalent to that used in the [`{EpiEstim}`](https://mrc-ide.github.io/EpiEstim/index.html) R package as they both use a renewal equation to estimate $R_t$ from case time series and the generation interval distribution. + +### Exact vs approximate sampling methods + +The default sampling method, set through `stan_opts()`, performs [MCMC sampling](https://en.wikipedia.org/wiki/Markov_chain_Monte_Carlo) using [`{rstan}`](https://cran.r-project.org/web/packages/rstan/vignettes/rstan.html). The MCMC sampling method is accurate but is often slow. The Laplace, pathfinder, and variational inference methods are faster because they are approximate (See, for example, a detailed explanation for [automatic variational inference in Stan](https://arxiv.org/abs/1506.03431)). In `{EpiNow2}`, you can use varational inference with an `{rstan}` or [`{cmdstanr}`](https://mc-stan.org/cmdstanr/) backend but you must install the latter to access its functionalities. Additionally, `{EpiNow2}` supports using the [Laplace](https://mc-stan.org/docs/cmdstan-guide/laplace_sample_config.html) and [Pathfinder](https://mc-stan.org/docs/cmdstan-guide/pathfinder_config.html) approximate samplers through `{cmdstanr}` but these two methods are currently experimental in `{cmdstanr}` and have not been well tested in the wild. The approximate methods may not be as reliable as the default MCMC sampling method and we do not recommend using them in real-world inference. + +### Smoothness/granularity of estimates + +The random walk method reduces smoothness/granularity of the estimates, compared to the other methods. + +## Caveats + +The run times measured here use a crude method that compares the start and end times of each simulation. It only measures the time taken for one model run and may not be accurate. For more accurate run time measurements, we recommend using a more sophisticated approach like those provided by packages like [`{bench}`](https://cran.r-project.org/web/packages/bench/index.html) and [`{microbenchmark}`](https://cran.r-project.org/web/packages/microbenchmark/index.html). + +Secondly, we used `r getOption("mc.cores", 1L)` cores for the simulations and so using more or fewer cores might change the run time results. We, however, expect the relative rankings to be the same or similar. To speed up the model runs, we recommend checking the number of cores available on your machine using `parallel::detectCores()` and passing a high enough number of cores to `mc.cores` through the `options()` function. See the benchmarking data setup chunk above for an example. + +lastly, the `R` trajectory used to generate the data for benchmarking only represents one scenario. This could favour one model type or solver over another. From fc183305d2d7c032479dea047a700cc6b8385d88 Mon Sep 17 00:00:00 2001 From: jamesmbaazam Date: Fri, 1 Nov 2024 15:43:25 +0000 Subject: [PATCH 55/66] Update pkgdown entry for benchmarks vignette --- _pkgdown.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_pkgdown.yml b/_pkgdown.yml index 094eef0dd..1a3166329 100644 --- a/_pkgdown.yml +++ b/_pkgdown.yml @@ -50,7 +50,7 @@ navbar: - text: Using epinow() for running in production mode href: articles/epinow.html - text: Model benchmarks - href: articles/speedup_options.html + href: articles/benchmarks.html casestudies: text: Case studies menu: From 9a40769e560351064fe4a81dbe7c4aa209ef6dcf Mon Sep 17 00:00:00 2001 From: jamesmbaazam Date: Fri, 1 Nov 2024 15:49:46 +0000 Subject: [PATCH 56/66] remove plots and vignettes with old old --- .../speedup-options-infections_plot-1.png | Bin 38915 -> 0 bytes vignettes/speedup-options-rt_plot-1.png | Bin 72347 -> 0 bytes .../speedup_options-infections_plot-1.png | Bin 39418 -> 0 bytes vignettes/speedup_options-rt_plot-1.png | Bin 66208 -> 0 bytes vignettes/speedup_options.Rmd.orig | 959 ------------------ 5 files changed, 959 deletions(-) delete mode 100644 vignettes/speedup-options-infections_plot-1.png delete mode 100644 vignettes/speedup-options-rt_plot-1.png delete mode 100644 vignettes/speedup_options-infections_plot-1.png delete mode 100644 vignettes/speedup_options-rt_plot-1.png delete mode 100644 vignettes/speedup_options.Rmd.orig diff --git a/vignettes/speedup-options-infections_plot-1.png b/vignettes/speedup-options-infections_plot-1.png deleted file mode 100644 index a19eb9875b0ef4b5a81a1637df3df31146fff1ea..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 38915 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 -Let's start by looking at the two broad individual model types (stationary vs non-stationary) -```{r rt_ns_gp_plot,class.source = 'fold-hide'} -rt_ns_gp_plot <- ggplot( - data = rt_crps_dt_final[rt_model_gp == "non_stationary"], # this model has extremely large crps - # data = infections_crps_dt_final - ) + - geom_line( - aes(x = date, - y = crps, - color = model - ) - ) + - scale_colour_brewer("Model", palette = "Dark2") + - # scale_y_log10(labels = label_number_auto()) + - labs( - x = "Time", - y = "CRPS", - title = "Performance in estimating Rt (non-stationary models)" - ) + - ggplot2::theme_minimal() + - guides(color = guide_legend(title = "Model")) + - theme(legend.position = "bottom") + - facet_grid(~factor(epidemic_phase, levels = c("growth", "peak", "decline"))) - -plot(rt_ns_gp_plot) -``` - -```{r rt_st_gp_plot,class.source = 'fold-hide'} -rt_st_gp_plot <- ggplot( - data = rt_crps_dt_final[rt_model_gp == "stationary"], - ) + - geom_line( - aes(x = date, - y = crps, - color = model - ) - ) + - scale_colour_brewer("Model", palette = "Dark2") + - # scale_y_log10(labels = label_number_auto()) + - labs( - x = "Time", - y = "CRPS", - title = "Performance in estimating Rt (stationary models)" - ) + - ggplot2::theme_minimal() + - guides(color = guide_legend(title = "Model")) + - theme(legend.position = "bottom") + - facet_grid(~factor(epidemic_phase, levels = c("growth", "peak", "decline"))) - -plot(rt_st_gp_plot) -``` - -We will now plot all the models, grouped by the type of Gaussian process used to estimate $R_t$ (stationary vs non-stationary). -```{r rt_all_models_plot,class.source = 'fold-hide'} -rt_crps_all_models_plot <- ggplot( - data = rt_crps_dt_final[!is.na(crps)], #remove failed models - ) + - geom_line( - aes(x = date, - y = crps, - color = rt_model_gp, - group = model - ) - ) + - scale_colour_brewer("Model", palette = "Dark2") + - # scale_y_log10(labels = label_number_auto()) + - labs( - x = "Time", - y = "CRPS", - title = "Estimating Rt" - ) + - ggplot2::theme_minimal() + - guides(color = guide_legend(title = "Model type")) + - theme(legend.position = "bottom") + - facet_grid(rows = vars(factor(epidemic_phase, levels = c("growth", "peak", "decline")))) - -plot(rt_crps_all_models_plot) -``` - -Let's look at the total CRPS for each model over data snapshot. -```{r rt_total_crps,class.source = 'fold-hide'} -# We'll calculate the total crps per model, epidemic phase, and estimate type -# We'll drop the retrospective estimates as we are only interested in the real-time and forecast performance -rt_total_crps_dt <- rt_crps_dt_final[ - , - .( - total_crps = sum(crps), - rt_model_gp = rt_model_gp[1], - fitting = fitting[1], - package = package[1] - ), - by = .(model, epidemic_phase, type) -][ - type != "estimate" -] - -rt_total_crps_plot <- ggplot(data = rt_total_crps_dt) + - geom_col( - aes( - x = type, - y = total_crps, - fill = rt_model_gp - ), - position = position_dodge() - ) + - scale_fill_brewer(palette = "Dark2") + - labs( - x = "Estimate type", - y = "Total CRPS", - fill = "Model type", - title = "Estimating Rt" - ) + - facet_grid( - ~factor( - epidemic_phase, - levels = c("growth", "peak", "decline") - ) - ) -rt_total_crps_plot -``` - - -Next, let's visualise model performance over time in estimating and forecasting infections. - -We'll start by looking at the individual model groups (stationary vs non-stationary) -```{r infections_ns_gp_plot,class.source = 'fold-hide'} -infections_ns_gp_plot <- ggplot( - data = infections_crps_dt_final[rt_model_gp == "non_stationary"], # this model has extremely large crps - # data = infections_crps_dt_final - ) + - geom_line( - aes(x = date, - y = crps, - color = model - ) - ) + - scale_colour_brewer("Model", palette = "Set1") + - # scale_y_log10(labels = label_number_auto()) + - labs( - x = "Time", - y = "CRPS (log-transformed)", - title = "Estimating and predicting infections (non-stationary models)" - ) + - ggplot2::theme_minimal() + - guides(color = guide_legend(title = "Model")) + - theme(legend.position = "bottom") + - facet_grid(~factor(epidemic_phase, levels = c("growth", "peak", "decline"))) - -plot(infections_ns_gp_plot) -``` - -```{r infections_st_gp_plot,class.source = 'fold-hide'} -infections_st_gp_plot <- ggplot( - data = infections_crps_dt_final[rt_model_gp == "stationary"], - ) + - geom_line( - aes(x = date, - y = crps, - color = model - ) - ) + - scale_colour_brewer("Model", palette = "Dark2") + - # scale_y_log10(labels = label_number_auto()) + - labs( - x = "Time", - y = "CRPS", - title = "Estimating and predicting infections (stationary models)" - ) + - ggplot2::theme_minimal() + - guides(color = guide_legend(title = "Model")) + - theme(legend.position = "bottom") + - facet_grid(~factor(epidemic_phase, levels = c("growth", "peak", "decline"))) - -plot(infections_st_gp_plot) -``` - -We will now plot all the models, grouped by the type of $R_t$ model Gaussian process (stationary/non-stationary/none). -```{r infections_all_models_plot,class.source = 'fold-hide'} -infections_all_models_plot <- ggplot( - data = infections_crps_dt_final[!is.na(crps)], #remove failed models - ) + - geom_line( - aes(x = date, - y = crps, - color = rt_model_gp, - group = model - ) - ) + - scale_colour_brewer("Model", palette = "Dark2") + - # scale_y_log10(labels = label_number_auto()) + - labs( - x = "Time", - y = "CRPS", - title = "Estimating and predicting infections" - ) + - ggplot2::theme_minimal() + - guides(color = guide_legend(title = "Model type")) + - theme(legend.position = "bottom") + - facet_grid(~factor(epidemic_phase, levels = c("growth", "peak", "decline"))) - -plot(infections_all_models_plot) -``` - -Let's look at the total CRPS for each model over data snapshot. -```{r infections_total_crps,class.source = 'fold-hide'} -# We'll calculate the total crps per model, epidemic phase, and estimate type -# We'll drop the retrospective estimates as we are only interested in the real-time and forecast performance -infections_total_crps_dt <- infections_crps_dt_final[ - , - .( - total_crps = sum(crps), - rt_model_gp = rt_model_gp[1], - fitting = fitting[1], - package = package[1] - ), - by = .(model, epidemic_phase, type) -][ - type != "estimate" -] - -infections_total_crps_plot <- ggplot(data = infections_total_crps_dt) + - geom_col( - aes( - x = type, - y = total_crps, - fill = rt_model_gp - ), - position = position_dodge() - ) + - scale_fill_brewer(palette = "Dark2") + - labs( - x = "Estimate type", - y = "Total CRPS", - fill = "Model type", - title = "Estimating infections" - ) + - facet_grid( - ~factor( - epidemic_phase, - levels = c("growth", "peak", "decline") - ) - ) -infections_total_crps_plot -``` - -From the results of the model run times and CRPS measures, we can see that there is often a trade-off between run times/speed and estimation/forecasting performance, here measured with the CRPS. - -These results show that choosing an appropriate model for a task requires carefully considering the use case and appropriate trade-offs. Below are a few considerations. - -## Things to consider when interpreting these benchmarks - -### Mechanistic vs non-mechanistic models - -Estimation in `{EpiNow2}` using the mechanistic approaches (prior on $R_t$) is often much slower than the non-mechanistic approach. The mechanistic model is slower because it models aspects of the processes and mechanisms that drive $R_t$ estimates using the renewal equation. The non-mechanistic model, on the other hand, runs much faster but does not use the renewal equation to generate infections. Because of this none of the options defining the behaviour of the reproduction number are available in this case, limiting flexibility. The non-mechanistic model in `{EpiNow2}` is equivalent to that used in the [`{EpiEstim}`](https://mrc-ide.github.io/EpiEstim/index.html) R package as they both use a renewal equation to estimate $R_t$ from case time series and the generation interval distribution. - -### Exact vs approximate sampling methods - -The default sampling method, set through `stan_opts()`, performs [MCMC sampling](https://en.wikipedia.org/wiki/Markov_chain_Monte_Carlo) using [`{rstan}`](https://cran.r-project.org/web/packages/rstan/vignettes/rstan.html). The MCMC sampling method is accurate but is often slow. The Laplace, pathfinder, and variational inference methods are faster because they are approximate (See, for example, a detailed explanation for [automatic variational inference in Stan](https://arxiv.org/abs/1506.03431)). In `{EpiNow2}`, you can use varational inference with an `{rstan}` or [`{cmdstanr}`](https://mc-stan.org/cmdstanr/) backend but you must install the latter to access its functionalities. Additionally, `{EpiNow2}` supports using the [Laplace](https://mc-stan.org/docs/cmdstan-guide/laplace_sample_config.html) and [Pathfinder](https://mc-stan.org/docs/cmdstan-guide/pathfinder_config.html) approximate samplers through `{cmdstanr}` but these two methods are currently experimental in `{cmdstanr}` and have not been well tested in the wild. The approximate methods may not be as reliable as the default MCMC sampling method and we do not recommend using them in real-world inference. - -### Smoothness/granularity of estimates - -The random walk method reduces smoothness/granularity of the estimates, compared to the other methods. - -## Caveats - -The run times measured here use a crude method that compares the start and end times of each simulation. It only measures the time taken for one model run and may not be accurate. For more accurate run time measurements, we recommend using a more sophisticated approach like those provided by packages like [`{bench}`](https://cran.r-project.org/web/packages/bench/index.html) and [`{microbenchmark}`](https://cran.r-project.org/web/packages/microbenchmark/index.html). - -Secondly, we used `r getOption("mc.cores", 1L)` cores for the simulations and so using more or fewer cores might change the run time results. We, however, expect the relative rankings to be the same or similar. To speed up the model runs, we recommend checking the number of cores available on your machine using `parallel::detectCores()` and passing a high enough number of cores to `mc.cores` through the `options()` function. See the benchmarking data setup chunk above for an example. - -lastly, the `R` trajectory used to generate the data for benchmarking only represents one scenario. This could favour one model type or solver over another. From 168d03cae1522d696132bd912cd7a7429f966e0d Mon Sep 17 00:00:00 2001 From: jamesmbaazam Date: Fri, 8 Nov 2024 08:21:27 +0000 Subject: [PATCH 57/66] Add mc.cores option --- vignettes/benchmarks.Rmd.orig | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/vignettes/benchmarks.Rmd.orig b/vignettes/benchmarks.Rmd.orig index f65999260..717795936 100644 --- a/vignettes/benchmarks.Rmd.orig +++ b/vignettes/benchmarks.Rmd.orig @@ -60,8 +60,10 @@ obs <- obs_opts( scale = list(mean = 0.1, sd = 0.025), return_likelihood = TRUE ) - +# Rt prior rt_prior_default <- list(mean = 2, sd = 0.1) +# Number of cores +options(mc.cores = 6) ``` Now, we can generate the `estimates` object. From c4ec51bde949770faca9e8d1351361b42f2e68e9 Mon Sep 17 00:00:00 2001 From: jamesmbaazam Date: Fri, 8 Nov 2024 08:32:21 +0000 Subject: [PATCH 58/66] Add axis labels --- vignettes/benchmarks.Rmd.orig | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/vignettes/benchmarks.Rmd.orig b/vignettes/benchmarks.Rmd.orig index 717795936..b189fdae8 100644 --- a/vignettes/benchmarks.Rmd.orig +++ b/vignettes/benchmarks.Rmd.orig @@ -138,7 +138,8 @@ Let's see what the cases plot looks like cases_traj <- ggplot(data = reported_cases_true) + geom_line(aes(x = date, y = confirm)) + scale_y_continuous(label = scales::label_comma()) + - scale_x_date(date_labels = "%b %d", date_breaks = "1 month") + scale_x_date(date_labels = "%b %d", date_breaks = "1 month") + + labs(y = "Reported cases", x = "Date") cases_traj ``` From 48d6d7458cf7614ca017fe63fc96d0ae57b9ba73 Mon Sep 17 00:00:00 2001 From: jamesmbaazam Date: Fri, 8 Nov 2024 08:33:08 +0000 Subject: [PATCH 59/66] Add model basename to model descriptions --- vignettes/benchmarks.Rmd.orig | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/vignettes/benchmarks.Rmd.orig b/vignettes/benchmarks.Rmd.orig index b189fdae8..6a37a2dfa 100644 --- a/vignettes/benchmarks.Rmd.orig +++ b/vignettes/benchmarks.Rmd.orig @@ -153,23 +153,23 @@ We will now proceed to define and run the different model options and evaluate t Below we describe each model. ```{r model-descriptions,echo = FALSE} model_descriptions <- dplyr::tribble( - ~model, ~description, - "default_mcmc", "Default model (non-stationary prior on $R_t$); fitting with mcmc", - "default_vb", "Default model (non-stationary prior on $R_t$); fitting with variational bayes", - "default_pathfinder", "Default model (non-stationary prior on $R_t$); fitting with pathfinder algorithm", - "default_laplace", "Default model (non-stationary prior on $R_t$); fitting with laplace approximation", - "non_mechanistic_mcmc", "no mechanistic prior on $R_t$; fitting with mcmc", - "non_mechanistic_vb", "no mechanistic prior on $R_t$; fitting with variational bayes", - "non_mechanistic_pathfinder", "no mechanistic prior on $R_t$; fitting with pathfinder algorithm", - "non_mechanistic_laplace", "no mechanistic prior on $R_t$; fitting with laplace approximation", - "rw7_mcmc", "7-day random walk prior on $R_t$; fitting with mcmc", - "rw7_vb", "7-day random walk prior on $R_t$; fitting with variational bayes", - "rw7_pathfinder", "7-day random walk prior on $R_t$; fitting with pathfinder algorithm", - "rw7_laplace", "7-day random walk prior on $R_t$; fitting with laplace approximation", - "non_residual_mcmc", "Stationary prior on $R_t$; fitting with mcmc", - "non_residual_vb", "Stationary prior on $R_t$; fitting with variational bayes", - "non_residual_pathfinder", "Stationary prior on $R_t$; fitting with pathfinder algorithm", - "non_residual_laplace", "Stationary prior on $R_t$; fitting with laplace algorithm" + ~model, ~model_basename, ~description, + "default_mcmc", "default", "Default model (non-stationary prior on $R_t$); fitting with mcmc", + "default_vb", "default", "Default model (non-stationary prior on $R_t$); fitting with variational bayes", + "default_pathfinder", "default", "Default model (non-stationary prior on $R_t$); fitting with pathfinder algorithm", + "default_laplace", "default", "Default model (non-stationary prior on $R_t$); fitting with laplace approximation", + "non_mechanistic_mcmc", "non_mechanistic", "no mechanistic prior on $R_t$; fitting with mcmc", + "non_mechanistic_vb", "non_mechanistic", "no mechanistic prior on $R_t$; fitting with variational bayes", + "non_mechanistic_pathfinder", "non_mechanistic", "no mechanistic prior on $R_t$; fitting with pathfinder algorithm", + "non_mechanistic_laplace", "non_mechanistic", "no mechanistic prior on $R_t$; fitting with laplace approximation", + "rw7_mcmc", "rw7", "7-day random walk prior on $R_t$; fitting with mcmc", + "rw7_vb", "rw7", "7-day random walk prior on $R_t$; fitting with variational bayes", + "rw7_pathfinder", "rw7", "7-day random walk prior on $R_t$; fitting with pathfinder algorithm", + "rw7_laplace", "rw7", "7-day random walk prior on $R_t$; fitting with laplace approximation", + "non_residual_mcmc", "non_residual", "Stationary prior on $R_t$; fitting with mcmc", + "non_residual_vb", "non_residual", "Stationary prior on $R_t$; fitting with variational bayes", + "non_residual_pathfinder", "non_residual", "Stationary prior on $R_t$; fitting with pathfinder algorithm", + "non_residual_laplace", "non_residual", "Stationary prior on $R_t$; fitting with laplace algorithm" ) knitr::kable(model_descriptions, caption = "Model options") From 9f2bb02d23dc28d73a5eccefb3abf3682fe76029 Mon Sep 17 00:00:00 2001 From: jamesmbaazam Date: Fri, 8 Nov 2024 08:35:16 +0000 Subject: [PATCH 60/66] Rename rt_model_gp column to rt_gp_prior --- vignettes/benchmarks.Rmd.orig | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/vignettes/benchmarks.Rmd.orig b/vignettes/benchmarks.Rmd.orig index 6a37a2dfa..cb08a7716 100644 --- a/vignettes/benchmarks.Rmd.orig +++ b/vignettes/benchmarks.Rmd.orig @@ -177,15 +177,15 @@ knitr::kable(model_descriptions, caption = "Model options") ```{r model-components,echo = FALSE} model_components <- dplyr::tribble( - ~model, ~rt_model_gp, ~fitting, ~package, + ~model, ~rt_gp_prior, ~fitting, ~package, "default_mcmc", "non_stationary", "mcmc", "rstan", "default_vb", "non_stationary", "variational_bayes", "rstan", "default_pathfinder", "non_stationary", "pathfinder", "cmdstanr", "default_laplace", "non_stationary", "laplace", "cmdstanr", - "non_mechanistic_mcmc", "stationary", "mcmc", "rstan", - "non_mechanistic_vb", "stationary", "variational_bayes", "rstan", - "non_mechanistic_pathfinder", "stationary", "pathfinder", "cmdstanr", - "non_mechanistic_laplace", "stationary", "laplace", "cmdstanr", + "non_mechanistic_mcmc", "none", "mcmc", "rstan", + "non_mechanistic_vb", "none", "variational_bayes", "rstan", + "non_mechanistic_pathfinder", "none", "pathfinder", "cmdstanr", + "non_mechanistic_laplace", "none", "laplace", "cmdstanr", "rw7_mcmc", "non_stationary", "mcmc", "rstan", "rw7_vb", "non_stationary", "variational_bayes", "rstan", "rw7_pathfinder", "non_stationary", "pathfinder", "cmdstanr", From b9abd3848af56978e648026fb373e77a70bb5e75 Mon Sep 17 00:00:00 2001 From: jamesmbaazam Date: Fri, 8 Nov 2024 08:36:27 +0000 Subject: [PATCH 61/66] Plot timing as points --- vignettes/benchmarks.Rmd.orig | 46 ++++++++++++++++++++++------------- 1 file changed, 29 insertions(+), 17 deletions(-) diff --git a/vignettes/benchmarks.Rmd.orig b/vignettes/benchmarks.Rmd.orig index cb08a7716..992c2e48d 100644 --- a/vignettes/benchmarks.Rmd.orig +++ b/vignettes/benchmarks.Rmd.orig @@ -491,32 +491,44 @@ runtimes_dt_detailed <- merge( snapshot_date_names <- c(growth = "2020-05-15", peak = "2020-06-21", decline = "2020-06-28") # Replace snapshot_date based on the dictionary -runtimes_dt_detailed[, snapshot_date := names(snapshot_date_names)[match(snapshot_date, snapshot_date_names)]] +runtimes_dt_detailed[, epidemic_phase := names(snapshot_date_names)[match(snapshot_date, snapshot_date_names)]] # Move some columns around setcolorder(runtimes_dt_detailed, "timing", after = "package") -# Make the snapshot_date column a factor -runtimes_dt_detailed[, snapshot_date := factor(snapshot_date, levels = c("growth", "peak", "decline"))] +# Make all columns except timing a factor +runtimes_dt_detailed[ + , + (setdiff(names(runtimes_dt_detailed), "timing")) := + lapply(.SD, as.factor), + .SDcols = setdiff(names(runtimes_dt_detailed), "timing") +] + +# Add model descriptions +runtimes_dt_detailed <- merge( + runtimes_dt_detailed, + model_descriptions, + by = "model" +) + # Plot the timing -timing_plot <- ggplot(data = runtimes_dt_detailed) + - geom_col(aes(x = snapshot_date, +timing_plot <- ggplot(data = runtimes_dt_detailed[, fit_type := ifelse(fitting == "mcmc", "mcmc", "approximate")]) + + geom_point(aes(x = factor(epidemic_phase, levels = c("growth", "peak", "decline")), y = timing, - fill = rt_model_gp), - position = position_dodge() - ) + - scale_fill_brewer( - palette = "Dark2", - labels = c("stationary" = "stationary", - "non_stationary" = "non-stationary", - "none" = "non-mechanistic" - ) + color = model_basename, + shape = rt_gp_prior + ), + size = 2.2 ) + + # scale_color_brewer(palette = "Dark2") + labs(x = "Epidemic phase", y = "Runtime (secs)", - fill = "Model type", - caption = "non-stationary Rt model uses Rt-1 * GP; stationary Rt model uses R0 * GP; non-mechanistic model uses no GP." - ) + shape = "Rt model prior", + color = "Base model", + caption = "non-stationary Rt = R(t-1) * GP; stationary Rt = R0 * GP; non-mechanistic Rt = no GP prior." + ) + + theme_minimal() + + facet_wrap(~fit_type, scales = "free_y", nrow = 2) timing_plot ``` From 45381e441fba122730f9c3b1f7280e49320d0725 Mon Sep 17 00:00:00 2001 From: jamesmbaazam Date: Fri, 8 Nov 2024 08:43:41 +0000 Subject: [PATCH 62/66] Plot stationary and non-stationary model results separately --- vignettes/benchmarks.Rmd.orig | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/vignettes/benchmarks.Rmd.orig b/vignettes/benchmarks.Rmd.orig index 992c2e48d..b34d00762 100644 --- a/vignettes/benchmarks.Rmd.orig +++ b/vignettes/benchmarks.Rmd.orig @@ -705,7 +705,7 @@ Let's first see how the models performed over time for the $R_t$ using the CRPS. Let's start by looking at the two broad individual model types (stationary vs non-stationary) ```{r rt_ns_gp_plot,class.source = 'fold-hide'} rt_ns_gp_plot <- ggplot( - data = rt_crps_dt_final[rt_model_gp == "non_stationary"], # this model has extremely large crps + data = rt_crps_dt_final[rt_gp_prior == "non_stationary"], # data = infections_crps_dt_final ) + geom_line( @@ -731,7 +731,7 @@ plot(rt_ns_gp_plot) ```{r rt_st_gp_plot,class.source = 'fold-hide'} rt_st_gp_plot <- ggplot( - data = rt_crps_dt_final[rt_model_gp == "stationary"], + data = rt_crps_dt_final[rt_gp_prior == "stationary"], ) + geom_line( aes(x = date, @@ -829,7 +829,7 @@ Next, let's visualise model performance over time in estimating and forecasting We'll start by looking at the individual model groups (stationary vs non-stationary) ```{r infections_ns_gp_plot,class.source = 'fold-hide'} infections_ns_gp_plot <- ggplot( - data = infections_crps_dt_final[rt_model_gp == "non_stationary"], # this model has extremely large crps + data = infections_crps_dt_final[rt_gp_prior == "non_stationary"], # data = infections_crps_dt_final ) + geom_line( @@ -855,7 +855,7 @@ plot(infections_ns_gp_plot) ```{r infections_st_gp_plot,class.source = 'fold-hide'} infections_st_gp_plot <- ggplot( - data = infections_crps_dt_final[rt_model_gp == "stationary"], + data = infections_crps_dt_final[rt_gp_prior == "stationary"], ) + geom_line( aes(x = date, From 560bab2c6dd694204ef1066d31a84ee8a9533be5 Mon Sep 17 00:00:00 2001 From: jamesmbaazam Date: Fri, 8 Nov 2024 08:45:05 +0000 Subject: [PATCH 63/66] Improve the plots --- vignettes/benchmarks.Rmd.orig | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/vignettes/benchmarks.Rmd.orig b/vignettes/benchmarks.Rmd.orig index b34d00762..bc80ee680 100644 --- a/vignettes/benchmarks.Rmd.orig +++ b/vignettes/benchmarks.Rmd.orig @@ -762,11 +762,11 @@ rt_crps_all_models_plot <- ggplot( geom_line( aes(x = date, y = crps, - color = rt_model_gp, - group = model + linetype = rt_gp_prior, + color = model ) ) + - scale_colour_brewer("Model", palette = "Dark2") + + scale_linetype_manual(values = c(1, 5, 3)) + # scale_y_log10(labels = label_number_auto()) + labs( x = "Time", @@ -776,7 +776,7 @@ rt_crps_all_models_plot <- ggplot( ggplot2::theme_minimal() + guides(color = guide_legend(title = "Model type")) + theme(legend.position = "bottom") + - facet_grid(rows = vars(factor(epidemic_phase, levels = c("growth", "peak", "decline")))) + facet_grid(~factor(epidemic_phase, levels = c("growth", "peak", "decline"))) plot(rt_crps_all_models_plot) ``` @@ -886,11 +886,12 @@ infections_all_models_plot <- ggplot( geom_line( aes(x = date, y = crps, - color = rt_model_gp, - group = model + color = model, + linetype = rt_gp_prior ) ) + - scale_colour_brewer("Model", palette = "Dark2") + + scale_linetype_manual(values = c(1, 5, 3)) + + # scale_colour_brewer("Model", palette = "Blues") + # scale_y_log10(labels = label_number_auto()) + labs( x = "Time", From b06c20196593902ab3d931a8e4a5f9a56df9c7ea Mon Sep 17 00:00:00 2001 From: jamesmbaazam Date: Fri, 8 Nov 2024 08:46:02 +0000 Subject: [PATCH 64/66] Use new column name --- vignettes/benchmarks.Rmd.orig | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/vignettes/benchmarks.Rmd.orig b/vignettes/benchmarks.Rmd.orig index bc80ee680..c90f88201 100644 --- a/vignettes/benchmarks.Rmd.orig +++ b/vignettes/benchmarks.Rmd.orig @@ -789,7 +789,7 @@ rt_total_crps_dt <- rt_crps_dt_final[ , .( total_crps = sum(crps), - rt_model_gp = rt_model_gp[1], + rt_gp_prior = rt_gp_prior[1], fitting = fitting[1], package = package[1] ), @@ -803,7 +803,7 @@ rt_total_crps_plot <- ggplot(data = rt_total_crps_dt) + aes( x = type, y = total_crps, - fill = rt_model_gp + fill = rt_gp_prior ), position = position_dodge() ) + @@ -914,7 +914,7 @@ infections_total_crps_dt <- infections_crps_dt_final[ , .( total_crps = sum(crps), - rt_model_gp = rt_model_gp[1], + rt_gp_prior = rt_gp_prior[1], fitting = fitting[1], package = package[1] ), @@ -928,7 +928,7 @@ infections_total_crps_plot <- ggplot(data = infections_total_crps_dt) + aes( x = type, y = total_crps, - fill = rt_model_gp + fill = rt_gp_prior ), position = position_dodge() ) + From db4bf80793259c35b674177e9f12782e01335057 Mon Sep 17 00:00:00 2001 From: jamesmbaazam Date: Tue, 19 Nov 2024 11:05:50 +0000 Subject: [PATCH 65/66] Fixes to plots --- vignettes/benchmarks.Rmd.orig | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/vignettes/benchmarks.Rmd.orig b/vignettes/benchmarks.Rmd.orig index c90f88201..b1c92ebac 100644 --- a/vignettes/benchmarks.Rmd.orig +++ b/vignettes/benchmarks.Rmd.orig @@ -528,7 +528,7 @@ timing_plot <- ggplot(data = runtimes_dt_detailed[, fit_type := ifelse(fitting = caption = "non-stationary Rt = R(t-1) * GP; stationary Rt = R0 * GP; non-mechanistic Rt = no GP prior." ) + theme_minimal() + - facet_wrap(~fit_type, scales = "free_y", nrow = 2) + facet_wrap(~fit_type, scales = "free_y", nrow = 2, strip.position = "left") timing_plot ``` @@ -705,14 +705,14 @@ Let's first see how the models performed over time for the $R_t$ using the CRPS. Let's start by looking at the two broad individual model types (stationary vs non-stationary) ```{r rt_ns_gp_plot,class.source = 'fold-hide'} rt_ns_gp_plot <- ggplot( - data = rt_crps_dt_final[rt_gp_prior == "non_stationary"], - # data = infections_crps_dt_final + data = rt_crps_dt_final[rt_gp_prior == "non_stationary"] ) + geom_line( - aes(x = date, + aes(x = factor(epidemic_phase, levels = c("growth", "peak", "decline")), y = crps, - color = model - ) + color = model_basename, + linetype = fitting + ) ) + scale_colour_brewer("Model", palette = "Dark2") + # scale_y_log10(labels = label_number_auto()) + From 8f3c566624f090d78332aac1f9abf65f69ff8494 Mon Sep 17 00:00:00 2001 From: jamesmbaazam Date: Tue, 19 Nov 2024 11:06:30 +0000 Subject: [PATCH 66/66] Append the model descriptions to the plot data --- vignettes/benchmarks.Rmd.orig | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/vignettes/benchmarks.Rmd.orig b/vignettes/benchmarks.Rmd.orig index b1c92ebac..77f0fcb16 100644 --- a/vignettes/benchmarks.Rmd.orig +++ b/vignettes/benchmarks.Rmd.orig @@ -605,6 +605,12 @@ rt_crps_full <- merge.data.table( by = "model" ) +# Add model descriptions +rt_crps_full <- merge.data.table( + rt_crps_full, + model_descriptions, + by = "model" +) # Replace the snapshot dates with their description # Replace snapshot_date based on the dictionary rt_crps_full[, epidemic_phase := names(snapshot_date_names)[ @@ -661,6 +667,13 @@ infections_crps_full <- merge.data.table( by = "model" ) +# Add model descriptions +infections_crps_full <- merge.data.table( + infections_crps_full, + model_descriptions, + by = "model" +) + # Replace the snapshot dates with their description # Replace snapshot_date based on the dictionary infections_crps_full[,epidemic_phase := names(snapshot_date_names)[