| Title: | Bayesian Dynamic Models for Poisson and Binomial Time Series |
|---|---|
| Description: | Fits Bayesian state-space models for non-Gaussian time series using a latent log-rate (Poisson) or latent logit (binomial) formulation. The latent trajectory follows a first-order random walk or a stationary AR(1) process, sampled by Metropolis-within-Gibbs using the implied Gaussian Markov random field (GMRF) full conditionals. Four innovation structures are supported for the latent increments: constant-variance Gaussian, Student-t, a finite scale mixture of normals, and stochastic volatility. Both families support time-constant zero inflation. The package provides simulation, fitting, forecasting, summary and plotting tools. It implements and extends the methodology of Zens and Bijak (2026) <doi:10.1214/26-AOAS2171>. |
| Authors: | Gregor Zens [aut, cre] |
| Maintainer: | Gregor Zens <[email protected]> |
| License: | MIT + file LICENSE |
| Version: | 0.1.0 |
| Built: | 2026-07-14 20:02:01 UTC |
| Source: | https://github.com/cran/DynCount |
Builds the prior hyperparameters used by fit_dynamic_model(). Called with
no arguments it returns weakly informative defaults.
dynamic_prior( var_shape = 2.5, var_rate = 0.5, df_min = 3, df_mean_excess = 6, mix_components = 2, mix_concentration = 1, sv_prior = NULL, zi_open_a = 1, zi_open_b = 1, ar_rho_mean = 0, ar_rho_sd = 1, mu_mean = 0, mu_sd = 1, init_mean = 0, init_var = 100 )dynamic_prior( var_shape = 2.5, var_rate = 0.5, df_min = 3, df_mean_excess = 6, mix_components = 2, mix_concentration = 1, sv_prior = NULL, zi_open_a = 1, zi_open_b = 1, ar_rho_mean = 0, ar_rho_sd = 1, mu_mean = 0, mu_sd = 1, init_mean = 0, init_var = 100 )
var_shape |
Shape of the inverse-gamma prior on the innovation
variance/scale. Default |
var_rate |
Rate of the inverse-gamma prior on the innovation
variance/scale. Default |
df_min |
Lower bound for the Student-t degrees of freedom. Default |
df_mean_excess |
Prior mean of |
mix_components |
Number of components in the scale-mixture innovation
structure. Default |
mix_concentration |
Symmetric Dirichlet concentration for the mixture
weights. Default |
sv_prior |
Optional stochvol prior specification for the
stochastic-volatility innovation structure. Default |
zi_open_a, zi_open_b
|
Beta prior parameters for the gate-open
probability in zero-inflated models. Default |
ar_rho_mean, ar_rho_sd
|
Mean and standard deviation of the Gaussian
prior on the AR(1) coefficient |
mu_mean, mu_sd
|
Mean and standard deviation of the Gaussian prior on the
drift/intercept |
init_mean, init_var
|
Mean and variance of the Gaussian prior on
the initial latent state, used under both random-walk and AR(1) dynamics.
Defaults |
The model places a GMRF latent process on the series (a log-rate
for the Poisson family, a logit for the binomial family): either a first-order
random walk (latent_dynamics = "rw") or an AR(1) process
(latent_dynamics = "ar1"). The increments
(with for the
random walk and unless a drift/intercept is included) are given
one of four innovation distributions, each governed by some of the priors
below.
Innovation variance (all structures). The baseline increment
variance (or, for the "t"/"mixture" structures, the
overall scale) has an inverse-gamma prior
Smaller var_rate gives a smoother (more strongly shrunk) latent path.
Student-t degrees of freedom (innovations = "t"). The degrees of
freedom are modelled as , where
. Thus
and its prior mean is
. Large approaches
the Gaussian case.
Scale mixture (innovations = "mixture"). The mixture uses
mix_components variance components, each with an
prior, and
symmetric Dirichlet weights with concentration mix_concentration.
Stochastic volatility (innovations = "sv"). The log-variance
AR(1) priors are delegated to stochvol. Pass a prior specification
created with stochvol::specify_priors() via sv_prior, or leave it NULL
to use the stochvol defaults.
Zero inflation (zeros = "inflated"). The probability that the
observation "gate" is open (i.e. that a zero is an ordinary sampling zero
rather than a structural zero) has a
prior. The
default Beta(1, 1) is uniform.
AR(1) coefficient (latent_dynamics = "ar1"). When the latent
state follows , the
coefficient is given a Gaussian prior
,
truncated to the stationary region , and is sampled
jointly with by an exact conjugate Gibbs draw (see
DynCount-package). AR(1) always carries an intercept
(include_mu = TRUE). Under latent_dynamics = "rw" the coefficient is
fixed at and this prior is unused.
Drift / intercept (include_mu = TRUE). A scalar in the
state equation : a
drift under the random walk () and an intercept
under AR(1) (where it is always enabled). It is given a Gaussian prior
. With
include_mu = FALSE (random walk only), and is not sampled.
Initial state. A proper, fixed
prior (default
) anchors the otherwise-improper GMRF on the first latent
state, under both "rw" and "ar1" dynamics; keeping it free of
is what makes their update conjugate (see
DynCount-package). With the diffuse default the initial state is
effectively determined by the data.
An object of class "dynamic_prior": a named list of hyperparameters.
# Defaults dynamic_prior() # Smoother latent path and heavier-tailed t innovations dynamic_prior(var_rate = 0.1, df_min = 2, df_mean_excess = 3)# Defaults dynamic_prior() # Smoother latent path and heavier-tailed t innovations dynamic_prior(var_rate = 0.1, df_min = 2, df_mean_excess = 3)
Fits a GMRF state-space model in which a latent trajectory evolves
as either a first-order random walk (latent_dynamics = "rw", the default)
or a stationary AR(1) process (latent_dynamics = "ar1"), and the
observations are linked to it through a Poisson (log link) or binomial
(logit link) observation model. See DynCount-package for an overview.
fit_dynamic_model( y, family = c("poisson", "binomial"), trials = NULL, innovations = c("gaussian", "t", "mixture", "sv"), latent_dynamics = c("rw", "ar1"), include_mu = FALSE, zeros = c("none", "inflated", "missing"), zero_inflation = FALSE, prior = dynamic_prior(), nsave = 4000, nburn = 1000, thin = 1, horizon = 0L, forecast_trials = NULL, forecast_offset = NULL, offset = NULL, verbose = FALSE, seed = NULL )fit_dynamic_model( y, family = c("poisson", "binomial"), trials = NULL, innovations = c("gaussian", "t", "mixture", "sv"), latent_dynamics = c("rw", "ar1"), include_mu = FALSE, zeros = c("none", "inflated", "missing"), zero_inflation = FALSE, prior = dynamic_prior(), nsave = 4000, nburn = 1000, thin = 1, horizon = 0L, forecast_trials = NULL, forecast_offset = NULL, offset = NULL, verbose = FALSE, seed = NULL )
y |
Numeric vector of non-negative integer observations. For the Poisson family these are counts; for the binomial family these are the numbers of successes. |
family |
Observation model, |
trials |
For |
innovations |
Distribution of the latent increments, one of
|
latent_dynamics |
Latent state evolution: |
include_mu |
Logical; include a scalar |
zeros |
Zero handling for both families: |
zero_inflation |
Logical convenience flag. If |
prior |
A |
nsave |
Number of posterior draws to keep. Default |
nburn |
Number of burn-in iterations. Default |
thin |
Thinning interval: one draw is kept every |
horizon |
Forecast horizon |
forecast_trials |
For the binomial family, the number of trials in the
forecast period (length 1, recycled, or length |
forecast_offset |
Known per-period offset over the forecast horizon
(length 1, recycled, or length |
offset |
Optional known per-observation offset on the linear-predictor
scale (length 1 or |
verbose |
Logical; print a progress bar. Default |
seed |
Optional integer random seed for reproducibility. |
Zero handling. Under zeros = "inflated" a latent gate decides,
for each observed zero, whether it is structural (gate closed) or an
ordinary sampling zero produced by the Poisson/binomial process (gate
open); the gate-open probability is a single time-constant parameter. See
structural_zero_prob().
Forecasting. Forecasts are produced during sampling: set
horizon = H (a positive integer) and H extra latent states are appended
to the state vector and sampled as missing values inside the MCMC. A response is
drawn from the observation model at each sampled forecast state. The stored
forecast draws are retrieved with forecast.dynamic_fit(). With the default
horizon = 0 no forecast is produced.
An object of class "dynamic_fit": a list with three elements, each
holding a distinct kind of content.
drawsPosterior draws only – a list of matrices/vectors with one row (or element) per stored draw:
z, sig2
Latent states and increment variances.
fitted, yrep
Unconditional fitted means and
posterior predictive replicates; under zeros = "inflated" they
include the zero-inflation gate.
fitted_open, yrep_open
Their conditional-on-gate-open counterparts: the latent-implied mean, and a replicate drawn straight from the observation model.
gate, pi_open
Zero-inflation gate indicators and
gate-open probability. For fits without zero inflation these are
stored as placeholders: gate is the constant indicator
y > 0 and pi_open is 1.
rho, mu, nu, innov_var
AR(1) coefficient,
drift/intercept, Student-t degrees of freedom, and a
representative innovation variance whose definition depends on
innovations: the estimated constant increment variance
for "gaussian"; the marginal increment variance
for "t" () and "mixture"
(); and the average of
the per-increment variances over the series for
"sv". For "t", if a draw has (possible only
when df_min <= 2), the undefined variance factor is replaced by the
convention 3.
forecast_z, forecast_y
Latent and response forecasts
when horizon >= 1 (NULL otherwise); forecast_y is
unconditional, i.e. includes the gate.
Use yrep, not yrep_open, for posterior predictive checks; without
zero inflation the conditional and unconditional pairs are identical.
See predict.dynamic_fit().
dataThe observed inputs: y, trials, the series length n,
and the resolved per-observation offset.
specThe model and MCMC specification: family, innovations,
latent_dynamics, include_mu, zeros, prior, nsave, nburn,
thin, horizon, and the fixed forecast-period inputs
forecast_offset / forecast_trials (NULL when not applicable).
dynamic_prior(), forecast.dynamic_fit(), plot_fitted(),
structural_zero_prob()
set.seed(1) sim <- simulate_dynamic_poisson(n = 60, sigma = 0.2, log_rate0 = 2) fit <- fit_dynamic_model(sim$y, family = "poisson", nsave = 300, nburn = 200) summary(fit)set.seed(1) sim <- simulate_dynamic_poisson(n = 60, sigma = 0.2, log_rate0 = 2) fit <- fit_dynamic_model(sim$y, family = "poisson", nsave = 300, nburn = 200) summary(fit)
Generic for extracting forecasts from a fitted model. See
forecast.dynamic_fit() for the method for "dynamic_fit" objects.
forecast(object, ...)forecast(object, ...)
object |
A fitted model object. |
... |
Passed to methods. |
The value returned by the method dispatched on object; for
"dynamic_fit" objects a "dynamic_forecast" object (see
forecast.dynamic_fit()).
Returns the forecast draws that were produced during MCMC sampling.
Forecasts are generated inside the sampler when the model is fitted with
horizon = H >= 1. This method extracts and summarises those stored
draws.
## S3 method for class 'dynamic_fit' forecast(object, probs = c(0.025, 0.5, 0.975), ...)## S3 method for class 'dynamic_fit' forecast(object, probs = c(0.025, 0.5, 0.975), ...)
object |
A |
probs |
Quantile probabilities for the summary. Default
|
... |
Unused. |
Zero inflation. For fits with zeros = "inflated" the response
forecast is unconditional: each draw includes the zero-inflation
gate, so structural zeros are reproduced and the draws are directly
comparable to future observations (like the in-sample yrep, unlike
yrep_open). The latent forecast summary in $latent is gate-free by
construction.
An object of class "dynamic_forecast": a list with summary (one
row per horizon , on the response scale), latent
(summary of the forecast latent path), draws (the predictive draws,
draws x H), final (the single-row summary of the final H-step-ahead
forecast), final_draws (the predictive draws at horizon H), and the
forecast horizon horizon.
sim <- simulate_dynamic_poisson(60, 0.2, 2, seed = 1) fit <- fit_dynamic_model(sim$y, nsave = 300, nburn = 200, horizon = 8) fc <- forecast(fit) fc$summarysim <- simulate_dynamic_poisson(60, 0.2, 2, seed = 1) fit <- fit_dynamic_model(sim$y, nsave = 300, nburn = 200, horizon = 8) fc <- forecast(fit) fc$summary
A longer weekly count series of irregular sea crossings on the Mediterranean route, beginning in autumn 2015. The counts are larger in magnitude with fewer zeros than uk_weekly, which makes the series useful for the plain Poisson model and the robust (Student-t) and stochastic-volatility innovation structures. The series is used in the irregular-migration application of Zens and Bijak (2026).
med_weeklymed_weekly
A data frame with 494 rows and 2 variables:
ISO week label, e.g. "2015-W40".
Non-negative integer count of weekly crossings.
Weekly aggregates of detected irregular Mediterranean crossings, compiled from operational/agency records as described in Zens and Bijak (2026), doi:10.1214/26-AOAS2171.
Zens, G. and Bijak, J. (2026). Dynamic Count Models with Flexible Innovation Processes for Irregular Maritime Migration. The Annals of Applied Statistics. doi:10.1214/26-AOAS2171.
data(med_weekly) summary(med_weekly$count)data(med_weekly) summary(med_weekly$count)
Observed series with the posterior median fitted mean and a credible band on the response scale.
plot_fitted(object, level = 0.95, ...)plot_fitted(object, level = 0.95, ...)
object |
A |
level |
Credible level. Default |
... |
Passed to |
Invisibly, the fitted summary.
sim <- simulate_dynamic_poisson(60, 0.2, 2, seed = 1) fit <- fit_dynamic_model(sim$y, nsave = 300, nburn = 200) plot_fitted(fit)sim <- simulate_dynamic_poisson(60, 0.2, 2, seed = 1) fit <- fit_dynamic_model(sim$y, nsave = 300, nburn = 200) plot_fitted(fit)
Shows the observed series together with the in-sample fitted mean (median and
credible band) and the forecast (median and credible band) over the forecast
horizon. The forecast draws are those produced during sampling, so the model
must have been fitted with horizon >= 1 (see forecast.dynamic_fit()).
plot_forecast(object, level = 0.95, ...)plot_forecast(object, level = 0.95, ...)
object |
A |
level |
Credible level. Default |
... |
Passed to |
Invisibly, the "dynamic_forecast" object.
sim <- simulate_dynamic_poisson(60, 0.2, 2, seed = 1) fit <- fit_dynamic_model(sim$y, nsave = 300, nburn = 200, horizon = 12) plot_forecast(fit)sim <- simulate_dynamic_poisson(60, 0.2, 2, seed = 1) fit <- fit_dynamic_model(sim$y, nsave = 300, nburn = 200, horizon = 12) plot_forecast(fit)
Shows the posterior median of the latent state (log-rate for the Poisson family, logit for the binomial family) with a credible band.
plot_latent(object, level = 0.95, ...)plot_latent(object, level = 0.95, ...)
object |
A |
level |
Credible level for the band. Default |
... |
Passed to |
Invisibly, the summary data frame used for plotting.
sim <- simulate_dynamic_poisson(60, 0.2, 2, seed = 1) fit <- fit_dynamic_model(sim$y, nsave = 300, nburn = 200) plot_latent(fit)sim <- simulate_dynamic_poisson(60, 0.2, 2, seed = 1) fit <- fit_dynamic_model(sim$y, nsave = 300, nburn = 200) plot_latent(fit)
Bar chart of the posterior probability that each observed zero is a
structural zero. Requires a model fitted with zeros = "inflated".
plot_zero_inflation(object, ...)plot_zero_inflation(object, ...)
object |
A |
... |
Passed to |
Invisibly, the structural_zero_prob() data frame.
sim <- simulate_dynamic_poisson(80, 0.2, 2, zero_inflation = 0.3, seed = 1) fit <- fit_dynamic_model(sim$y, zero_inflation = TRUE, nsave = 300, nburn = 200) plot_zero_inflation(fit)sim <- simulate_dynamic_poisson(80, 0.2, 2, zero_inflation = 0.3, seed = 1) fit <- fit_dynamic_model(sim$y, zero_inflation = TRUE, nsave = 300, nburn = 200) plot_zero_inflation(fit)
A convenience wrapper that dispatches to the dedicated plotting functions.
## S3 method for class 'dynamic_fit' plot(x, which = c("fitted", "latent", "forecast", "zeros"), ...)## S3 method for class 'dynamic_fit' plot(x, which = c("fitted", "latent", "forecast", "zeros"), ...)
x |
A |
which |
One of |
... |
Passed to the underlying plotting function. |
Invisibly, the result of the underlying plotting function.
In-sample fitted values and posterior predictive replicates
## S3 method for class 'dynamic_fit' predict( object, type = c("mean", "response"), conditional = FALSE, probs = c(0.025, 0.5, 0.975), ... )## S3 method for class 'dynamic_fit' predict( object, type = c("mean", "response"), conditional = FALSE, probs = c(0.025, 0.5, 0.975), ... )
object |
A |
type |
|
conditional |
Logical. Leave |
probs |
Quantile probabilities for the summary. Default
|
... |
Unused. |
A list with summary (a data frame, one row per observation) and
draws (the underlying draws matrix, draws x time).
sim <- simulate_dynamic_poisson(40, 0.2, 2, seed = 1) fit <- fit_dynamic_model(sim$y, nsave = 200, nburn = 100) head(predict(fit)$summary)sim <- simulate_dynamic_poisson(40, 0.2, 2, seed = 1) fit <- fit_dynamic_model(sim$y, nsave = 200, nburn = 100) head(predict(fit)$summary)
Generates a latent logit process (random walk or AR(1)) and binomial counts, optionally with structural (zero-inflation) zeros.
simulate_dynamic_binomial( n, sigma, trials, logit0 = 0, zero_inflation = 0, rho = 1, mu = 0, offset = 0, seed = NULL )simulate_dynamic_binomial( n, sigma, trials, logit0 = 0, zero_inflation = 0, rho = 1, mu = 0, offset = 0, seed = NULL )
n |
Number of observations. |
sigma |
Standard deviation of the latent increments (Gaussian). |
trials |
Number of trials: a single number (recycled) or a length- |
logit0 |
Initial logit |
zero_inflation |
Structural-zero probability. With probability
|
rho |
AR(1) coefficient of the latent process
|
mu |
Drift (random walk) / intercept (AR(1)) of the latent process.
Default |
offset |
Known offset on the logit scale (length 1 or |
seed |
Optional random seed. |
A list with components y (successes), trials, logit (latent
path ), prob (plogis(offset + logit)), offset, and
structural (logical, TRUE for structural zeros).
sim <- simulate_dynamic_binomial(n = 50, sigma = 0.15, trials = 40, seed = 1) head(sim$y) # with structural zeros: zi <- simulate_dynamic_binomial(50, 0.15, trials = 40, zero_inflation = 0.2, seed = 1) mean(zi$structural)sim <- simulate_dynamic_binomial(n = 50, sigma = 0.15, trials = 40, seed = 1) head(sim$y) # with structural zeros: zi <- simulate_dynamic_binomial(50, 0.15, trials = 40, zero_inflation = 0.2, seed = 1) mean(zi$structural)
Generates a latent log-rate process (random walk or AR(1)) and Poisson counts, optionally with zero inflation.
simulate_dynamic_poisson( n, sigma, log_rate0 = 1, zero_inflation = 0, rho = 1, mu = 0, offset = 0, seed = NULL )simulate_dynamic_poisson( n, sigma, log_rate0 = 1, zero_inflation = 0, rho = 1, mu = 0, offset = 0, seed = NULL )
n |
Number of observations. |
sigma |
Standard deviation of the latent increments (Gaussian). |
log_rate0 |
Initial log-rate |
zero_inflation |
Probability that the gate is closed (i.e. the
probability of a structural zero) at each time point. |
rho |
AR(1) coefficient of the latent process
|
mu |
Drift (random walk) / intercept (AR(1)) of the latent process.
Default |
offset |
Known log-exposure offset (length 1 or |
seed |
Optional random seed. |
A list with components y (observed counts), log_rate (the latent
log-rate path ), rate (the mean exp(offset + log_rate)),
offset, and structural (logical, TRUE where a structural zero was
forced).
sim <- simulate_dynamic_poisson(n = 50, sigma = 0.2, log_rate0 = 2, zero_inflation = 0.2, seed = 1) table(sim$y == 0, sim$structural)sim <- simulate_dynamic_poisson(n = 50, sigma = 0.2, log_rate0 = 2, zero_inflation = 0.2, seed = 1) table(sim$y == 0, sim$structural)
For a zero-inflated fit (Poisson or binomial), returns for every observed zero the posterior probability that it is a structural zero (gate closed) rather than an ordinary sampling zero generated by the observation process. The zero inflation is time-constant: a single gate-open probability governs all observations (it does not vary over time or with covariates).
structural_zero_prob(object, zeros_only = TRUE)structural_zero_prob(object, zeros_only = TRUE)
object |
A |
zeros_only |
If |
The model introduces a latent gate indicator : when
the gate is open () the count comes from the observation model
(Poisson or binomial); when closed () the count is a structural
zero. The sampler stores for each draw, so the structural-zero
probability is
the posterior mean of the gate being closed. Non-zero observations are
always sampling observations and have structural probability 0.
A data frame with columns time, observed, p_structural
(posterior probability the zero is structural) and p_sampling
(1 - p_structural).
sim <- simulate_dynamic_poisson(60, 0.2, 2, zero_inflation = 0.25, seed = 1) fit <- fit_dynamic_model(sim$y, zero_inflation = TRUE, nsave = 300, nburn = 200) structural_zero_prob(fit)sim <- simulate_dynamic_poisson(60, 0.2, 2, zero_inflation = 0.25, seed = 1) fit <- fit_dynamic_model(sim$y, zero_inflation = TRUE, nsave = 300, nburn = 200) structural_zero_prob(fit)
Summarise a fitted dynamic model
## S3 method for class 'dynamic_fit' summary(object, probs = c(0.025, 0.5, 0.975), ...)## S3 method for class 'dynamic_fit' summary(object, probs = c(0.025, 0.5, 0.975), ...)
object |
A |
probs |
Quantile probabilities. Default |
... |
Unused. |
An object of class "summary.dynamic_fit" containing posterior
summaries of the global parameters (the innovation standard deviation
innov_sd – the estimated SD for "gaussian" innovations, the marginal
SD for "t" and "mixture", and the series-average SD for
"sv" – and where relevant the t degrees of freedom, the AR(1)
coefficient, the drift/intercept, and the zero-inflation gate-open
probability) and the fitted-value summary.
sim <- simulate_dynamic_poisson(50, 0.2, 2, seed = 1) fit <- fit_dynamic_model(sim$y, nsave = 200, nburn = 100) summary(fit)sim <- simulate_dynamic_poisson(50, 0.2, 2, seed = 1) fit <- fit_dynamic_model(sim$y, nsave = 200, nburn = 100) summary(fit)
A weekly count series of irregular small-boat crossings of the English Channel towards the United Kingdom, beginning in early 2018. The series has frequent zeros in its early weeks, which makes it a useful example for zero inflation with the Poisson model. The series is used in the irregular-migration application of Zens and Bijak (2026).
uk_weeklyuk_weekly
A data frame with 376 rows and 2 variables:
ISO week label, e.g. "2018-W01".
Non-negative integer count of weekly crossings.
Weekly aggregates of detected irregular English Channel crossings, compiled from operational/agency records as described in Zens and Bijak (2026), doi:10.1214/26-AOAS2171.
Zens, G. and Bijak, J. (2026). Dynamic Count Models with Flexible Innovation Processes for Irregular Maritime Migration. The Annals of Applied Statistics. doi:10.1214/26-AOAS2171.
data(uk_weekly) plot(uk_weekly$count, type = "h") mean(uk_weekly$count == 0)data(uk_weekly) plot(uk_weekly$count, type = "h") mean(uk_weekly$count == 0)