date confirm
<Date> <num>
1: 2020-02-22 14
2: 2020-02-23 62
3: 2020-02-24 53
4: 2020-02-25 97
5: 2020-02-26 93
6: 2020-02-27 78
Estimating and forecasting infectious disease trends with EpiNow2
Research Software Engineer
Epiverse-TRACE Initiative, London School of Hygiene & Tropical Medicine
2025-08-14
Data delays must be accounted for. Source: Gostic et al., 2020
Time-varying reproduction number, \(R_t\): average number of secondary infections caused by each infectious individual at time \(t\) in a population where some individuals may not be susceptible.
\(R_t\) can:
\(R_t\) used to retrospectively and in real-time assess whether an epidemic is increasing, decreasing, or steady.
\[\begin{equation} R_t = \dfrac{I_t}{\sum_{\tau = 1}^{g_\mathrm{max}} g(\tau | \theta_g) I_{t - \tau}} \end{equation}\]
{EpiNow2}
and other packages.{EpiNow2}
under the hoodStan
backends: {rstan}
and {cmdstanr}
stan
’s MCMC algorithmsestimate_infections()
: estimate infections and \(R_t\) from reported casesestimate_secondary()
: estimate secondary outcomes (hospitalisations, deaths, etc) from primary observations (cases)estimate_truncation()
: estimate right truncation distribution from vintage/snapshot data for nowcastingforecast_infections()
: forecast infections trajectoriesforecast_secondary()
: forecast secondary outcomes (hospitalisations, deaths, etc) from primary observations (cases)simulate_infections()
: simulate infections and \(R_t\) from reported casessimulate_secondary()
: simulate secondary outcomes (hospitalisations, deaths, etc) from primary observations (cases)data.frame
of observations. date confirm
<Date> <num>
1: 2020-02-22 14
2: 2020-02-23 62
3: 2020-02-24 53
4: 2020-02-25 97
5: 2020-02-26 93
6: 2020-02-27 78
Most arguments specified as distributions.
With _opts()
function:
generation_time_opts()
/gt_opts()
delay_opts()
obs_opts()
EpiNow2's
bespoke distribution interface:
Normal()
,LogNormal()
,Gamma()
,Fixed()
, andNonParametric()
estimate_infections()
: estimate infections and \(R_t\) from reported cases\[\begin{equation} I_t = R_t \sum_{\tau = 1}^{g_\mathrm{max}} g(\tau | \theta_g) I_{t - \tau} \end{equation}\]
# load the EpiNow2 package
library(EpiNow2)
# Setup cores for parallel processing
options(mc.cores = min(parallel::detectCores(), 4))
# get example case counts
reported_cases <- example_confirmed[1:60]
# set an example generation time. In practice this should use an estimate
# from the literature or be estimated from data
generation_time <- Gamma(
shape = Normal(1.3, 0.3),
rate = Normal(0.37, 0.09),
max = 14
)
# set an example incubation period. In practice this should use an estimate
# from the literature or be estimated from data
incubation_period <- LogNormal(
meanlog = Normal(1.6, 0.06),
sdlog = Normal(0.4, 0.07),
max = 14
)
# set an example reporting delay. In practice this should use an estimate
# from the literature or be estimated from data
reporting_delay <- LogNormal(mean = 2, sd = 1, max = 10)
# set an example prior for the reproduction number
rt_prior <- LogNormal(mean = 2, sd = 0.1)
# for more examples, see the "estimate_infections examples" vignette
estimate_infections_res <- estimate_infections(
reported_cases,
generation_time = gt_opts(generation_time),
delays = delay_opts(incubation_period + reporting_delay),
rt = rt_opts(prior = rt_prior)
)
measure estimate
<char> <char>
1: New infections per day 2219 (1373 -- 3557)
2: Expected change in reports Likely decreasing
3: Effective reproduction no. 0.89 (0.72 -- 1.1)
4: Rate of growth -0.03 (-0.096 -- 0.033)
5: Doubling/halving time (days) -23 (21 -- -7.2)
ggplot2
objects, so can be customisedrt = NULL
gp = NULL
{EpiEstim}
model# load the EpiNow2 package
library(EpiNow2)
# Setup cores for parallel processing
options(mc.cores = min(parallel::detectCores(), 4))
# for more examples, see the "estimate_infections examples" vignette
estimate_infections_nm_res <- estimate_infections(
reported_cases,
generation_time = gt_opts(generation_time),
delays = delay_opts(incubation_period + reporting_delay),
rt = NULL # non-mechanistic/direct infections model
)
measure estimate
<char> <char>
1: New infections per day 2604 (2551 -- 2646)
2: Expected change in reports Decreasing
3: Effective reproduction no. 0.91 (0.87 -- 0.94)
4: Rate of growth -0.024 (-0.025 -- -0.022)
5: Doubling/halving time (days) -29 (-32 -- -28)
{EpiNow2}
can also model temporally aggregated data, e.g., weekly or monthly counts.
Data needs to have an accumulate
column.
fill_missing()
helper function to add accumulate
column.# load the EpiNow2 package
library(EpiNow2)
library(ggplot2)
# Setup cores for parallel processing
options(mc.cores = min(parallel::detectCores(), 4))
# Load example weekly case counts
cases_weekly <- readRDS("data/example_aggregate_data.rds")
# Visualise the data
p <- ggplot(cases_weekly, aes(x = date, y = confirm)) +
geom_col() +
scale_y_continuous(labels = scales::comma) +
scale_x_date(date_labels = "%b-%d", date_breaks = "2 weeks") +
labs(title = "Weekly case counts", x = "Date", y = "Cases")
# Create compatible data for EpiNow2
cases_weekly_complete <- fill_missing(
cases_weekly,
missing_dates = "accumulate",
missing_obs = "accumulate"
)
head(cases_weekly_complete, 10)
Key: <date>
date confirm accumulate
<Date> <num> <lgcl>
1: 2020-02-22 NA TRUE
2: 2020-02-23 NA TRUE
3: 2020-02-24 NA TRUE
4: 2020-02-25 NA TRUE
5: 2020-02-26 NA TRUE
6: 2020-02-27 NA TRUE
7: 2020-02-28 647 FALSE
8: 2020-02-29 NA TRUE
9: 2020-03-01 NA TRUE
10: 2020-03-02 NA TRUE
measure estimate
<char> <char>
1: New infections per day 206 (107 -- 373)
2: Expected change in reports Likely decreasing
3: Effective reproduction no. 0.94 (0.71 -- 1.2)
4: Rate of growth -0.018 (-0.098 -- 0.055)
5: Doubling/halving time (days) -38 (13 -- -7.1)
estimate_secondary()
: estimate secondary observations from primary observations# load packages
library(data.table)
library(EpiNow2)
# Setup cores for parallel processing
options(mc.cores = min(parallel::detectCores(), 4))
#### Incidence data example ####
# Load example secondary incidence data
es_inc_cases <- readRDS("data/example_secondary_incidence_data.rds")
head(es_inc_cases)
date primary secondary scaling meanlog sdlog index scaled conv
<Date> <num> <int> <num> <num> <num> <int> <num> <num>
1: 2020-02-22 14 5 0.4 1.8 0.5 1 5.6 5.600000
2: 2020-02-23 62 5 0.4 1.8 0.5 2 24.8 5.827560
3: 2020-02-24 53 8 0.4 1.8 0.5 3 21.2 8.801043
4: 2020-02-25 97 12 0.4 1.8 0.5 4 38.8 12.938343
5: 2020-02-26 93 16 0.4 1.8 0.5 5 37.2 16.590082
6: 2020-02-27 78 20 0.4 1.8 0.5 6 31.2 20.611478
# load packages
library(data.table)
library(EpiNow2)
# Setup cores for parallel processing
options(mc.cores = min(parallel::detectCores(), 4))
#### Prevalence data example ####
# Load example secondary prevalence data
es_prev_cases <- readRDS("data/example_secondary_prevalence_data.rds")
head(es_prev_cases)
date primary secondary scaling meanlog sdlog index scaled conv
<Date> <num> <int> <num> <num> <num> <int> <num> <num>
1: 2020-02-22 14 4 0.3 1.6 0.8 1 4.2 4.200000
2: 2020-02-23 62 18 0.3 1.6 0.8 2 18.6 6.749663
3: 2020-02-24 53 23 0.3 1.6 0.8 3 15.9 10.939618
4: 2020-02-25 97 38 0.3 1.6 0.8 4 29.1 13.765588
5: 2020-02-26 93 49 0.3 1.6 0.8 5 27.9 17.347381
6: 2020-02-27 78 52 0.3 1.6 0.8 6 23.4 20.088300
{EpiNow2}
provides a flexible and powerful framework for estimating \(R_t\) and forecasting infectious disease trends.
Package supports various customisations:
{EpiNow2}
core developers:
{EpiNow2}
contributorsThank you!
Questions?