--- title: "Getting started with npfseir" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Getting started with npfseir} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r setup, include=FALSE} knitr::opts_chunk$set( collapse = TRUE, comment = "#>", fig.width = 7, fig.height = 5 ) library(npfseir) set.seed(42) ``` ## Overview `npfseir` implements the online Bayesian inference framework of Feng and Wang (2025) for stochastic SEIR epidemic models with a time-varying transmission rate. The transmission rate $\beta_t$ is modelled as a latent Ornstein--Uhlenbeck (OU) process in log-space, and inference is performed via the nested particle filter (NPF) of Crisan and Miguez (2018). ## 1. Simulate a synthetic epidemic ```{r simulate} fixed <- list( eps = 1/5, # incubation rate (5-day mean) gamma = 1/10, # recovery rate (10-day mean) delta = 1/(70*365), # background mortality b = 1/(70*365), # birth rate q = 0.30, # case detection probability N = 1e6, # reference population size sigma_comp = 0.01 # compartment diffusion coefficient ) x0 <- c(S = 1e6 - 100, E = 50, I = 50, R = 0, Psi = log(0.25)) traj <- seir_simulate(n_steps = 300, kappa = 0.05, sigma_psi = 0.10, mu_psi = log(0.25), x0 = x0, fixed = fixed) plot(traj$day, traj$obs, type = "l", col = "gray40", xlab = "Day", ylab = "Daily confirmed cases", main = "Simulated epidemic (300 days)") lines(traj$day, exp(traj$Psi) * 3000, col = "steelblue", lty = 2) legend("topright", c("Observed counts", "True beta_t (scaled)"), col = c("gray40","steelblue"), lty = c(1,2), bty = "n") ``` ## 2. Run the nested particle filter The `npf_seir()` function takes only the observation vector and the fixed parameter list. By default it uses $K = 100$ outer particles and $M = 200$ inner particles; we use smaller values here for speed. ```{r fit, cache=TRUE} obs <- traj$obs[-1] # observations P_1, ..., P_T (exclude day-0 seed) fit <- npf_seir( observations = obs, fixed = fixed, K = 50, # outer (parameter) particles M = 100, # inner (state) particles seed = 1 ) ``` ## 3. Inspect results ```{r print} print(fit) ``` ```{r summary} summary(fit, burn = 30) ``` ## 4. Plot the R_t trajectory ```{r plot-Rt} plot(fit, burn = 30) abline(h = 1, lty = 2, col = "red") ``` ## 5. Forecast ```{r forecast} fc <- predict(fit, horizon = 14, n_draws = 500) cat("14-day ahead forecast (mean ± 95% PI):\n") print(round(data.frame(day = 1:14, mean = fc$mean, lo = fc$lo, hi = fc$hi), 1)) ``` ## 6. Cori-style R_t comparison (illustrative only) `cori_rt()` implements an in-house Cori-style renewal estimator for **illustrative comparison of estimands** only. It is *not* the R `EpiEstim` package. ```{r cori} ct <- cori_rt(obs, tau = 7, si_mean = 5.5, si_sd = 2.5) plot(ct$t, ct$Rt_mean, type = "l", col = "darkred", ylim = c(0, 4), xlab = "Day", ylab = expression(R[t]), main = "Cori-style Rt (illustrative only)") abline(h = 1, lty = 2) ``` ## References Crisan, D. and Miguez, J. (2018). Nested particle filters for online parameter estimation in discrete-time state-space Markov models. *Bernoulli*, 24(4A):3039--3086. Feng, W. and Wang, W. (2025). Bayesian sequential inference for a stochastic SEIR model with latent transmission dynamics. *Preprint*.