Comparing Copula VAR Models

Overview

The dcvar package provides three copula VAR models:

  1. DC-VAR (dcvar()): Time-varying correlation via a random walk on the Fisher-z scale. Best for smooth, gradual changes.

  2. HMM Copula (dcvar_hmm()): Discrete regime-switching with K hidden states. Best for abrupt changes between distinct coupling regimes.

  3. Constant Copula (dcvar_constant()): Single time-invariant correlation. Serves as a null/baseline model.

Mathematical Background

For normal-margin fits, all three models share a common decomposition of the bivariate likelihood into marginal and copula components. The package also supports exponential, gamma, and skew-normal margins for these three models, but the copula structure below is the common part. All three single-level models additionally support per-variable margins, where each variable can use a different marginal family (see the mixed-margins section below).

VAR(1) Marginals

Each variable follows a VAR(1) process with innovations:

\[y_t = \mu + \Phi \, (y_{t-1} - \mu) + \varepsilon_t, \qquad \varepsilon_t \sim N(0, \text{diag}(\sigma_{\varepsilon}^2))\]

The normal marginal densities provide the per-variable likelihoods in this special case. For non-normal margins, dcvar uses the same VAR recursion and Gaussian copula but swaps in the corresponding marginal likelihood and transform.

Gaussian Copula

The dependence between variables is captured by a Gaussian copula with time-varying correlation \(\rho_t\). The log-likelihood contribution of the copula at time \(t\) is:

\[\log c(u_{1t}, u_{2t}; \rho_t) = -\frac{1}{2}\log(1 - \rho_t^2) - \frac{\rho_t^2 \, (z_{1t}^2 + z_{2t}^2) - 2\rho_t \, z_{1t} z_{2t}}{2(1 - \rho_t^2)}\]

where \(z_{it} = \Phi^{-1}(u_{it})\) and \(u_{it} = \Phi(\varepsilon_{it} / \sigma_{\varepsilon,i})\).

How the Models Differ

  • DC-VAR: The correlation evolves as a random walk on the Fisher-z scale: \(z_t = z_{t-1} + \omega_t\) with \(\omega_t \sim N(0, \sigma_\omega^2)\), and \(\rho_t = \tanh(z_t)\).

  • HMM Copula: The correlation is state-dependent: \(\rho_t = \rho_{s_t}\), where \(s_t \in \{1, \ldots, K\}\) follows a Markov chain with transition matrix \(A\). State-specific correlations use an ordered constraint (\(\rho_1 < \rho_2 < \ldots\)) to prevent label switching.

  • Constant Copula: \(\rho_t = \rho\) for all \(t\). A special case of both the DC-VAR (\(\sigma_\omega = 0\)) and HMM (\(K = 1\)).

Per-variable (mixed) margins

A copula separates the marginal distributions from the dependence structure, so each variable is free to follow its own marginal family. dcvar_constant(), dcvar(), and dcvar_hmm() expose this by accepting a length-2 margins vector. For example, a roughly-symmetric first series paired with a positively-skewed second series can be modelled as normal and exponential margins under a single Gaussian copula:

library(dcvar)

sim_mix <- simulate_dcvar(
  n_time = 200,
  rho_trajectory = rho_constant(200, rho = 0.6),
  margins = c("normal", "exponential"),
  skew_direction = c(1, 1),
  seed = 11
)

fit_mix <- dcvar_constant(
  sim_mix$Y_df,
  vars = c("y1", "y2"),
  margins = c("normal", "exponential"),
  skew_direction = c(1, 1),
  seed = 11
)

# Each dimension is reported under its own family: sigma_eps for the normal
# variable, sigma_exp for the exponential variable.
coef(fit_mix)[c("sigma_eps", "sigma_exp")]

skew_direction is required whenever any dimension uses an exponential or gamma margin, and only those dimensions consult it. A single margins string still applies the same family to both variables, and an all-identical vector such as c("normal", "normal") is treated exactly like the scalar form (it reuses the specialised single-family model), so existing code is unaffected. Mixed margins currently require the Gaussian copula and are available for the three single-level models (dcvar_constant(), dcvar(), dcvar_hmm()).

Time-varying coefficients and scales

In dcvar() the copula correlation always evolves over time. The autoregressive dynamics and the residual scales can be allowed to evolve as well, each as a random walk around a constant baseline. Two flags turn these on:

  • tv_phi lets the VAR(1) coefficients drift. tv_phi = TRUE varies all four (the autoregressive coefficients phi11, phi22 and the cross-lagged coefficients phi12, phi21); a selector varies only a subset. Use "ar" to let only the autoregressive coefficients drift (for example to capture changing inertia), "cross" for only the cross-lagged coefficients, or pass specific names such as c("phi11", "phi22").
  • tv_sigma lets the residual scales drift. Normal and skew-normal dimensions scale multiplicatively on the log scale. Exponential and gamma dimensions use a soft-barrier transform so their scale can vary even though the shifted margin has a hard support boundary; tv_sigma_k controls how closely it approximates the exact margin.

With both flags off, dcvar() is the usual dynamic copula model with constant coefficients and scales. Smaller models are nested in larger ones, which suggests fitting a short ladder and comparing the fits.

library(dcvar)

# A series with declining coupling to illustrate the time-varying fits
sim_tv <- simulate_dcvar(
  n_time = 150,
  rho_trajectory = rho_decreasing(150, rho_start = 0.7, rho_end = 0.3),
  seed = 3
)

# Only the autoregressive coefficients evolve over time
fit_ar <- dcvar(
  sim_tv$Y_df,
  vars = c("y1", "y2"),
  tv_phi = "ar",
  seed = 4
)

# Time-varying coefficients and time-varying scales together, with a
# right-skewed second margin (the exponential dimension uses the soft barrier)
fit_full <- dcvar(
  sim_tv$Y_df,
  vars = c("y1", "y2"),
  margins = c("normal", "exponential"),
  skew_direction = c(1, 1),
  tv_phi = TRUE,
  tv_sigma = TRUE,
  seed = 5
)

The coefficient and scale paths come back as posterior summaries, in the same shape as the correlation trajectory, and have their own plotting helpers.

# Posterior summaries of phi11(t), phi12(t), phi21(t), phi22(t)
head(phi_trajectory(fit_ar))

# Per-variable residual-scale paths
head(sigma_trajectory(fit_full))

# Facetted trajectory plots
plot_phi_trajectory(fit_ar)
plot_sigma_trajectory(fit_full)

Fitting All Three Models

library(dcvar)

# Simulate data with a step-function trajectory
sim <- simulate_dcvar(
  n_time = 200,
  rho_trajectory = rho_step(200, rho_before = 0.7, rho_after = 0.3),
  seed = 42
)

# Fit all three models on the same data
fit_dc  <- dcvar(sim$Y_df, vars = c("y1", "y2"), seed = 1)
fit_hmm <- dcvar_hmm(sim$Y_df, vars = c("y1", "y2"), K = 2, seed = 2)
fit_con <- dcvar_constant(sim$Y_df, vars = c("y1", "y2"), seed = 3)

LOO-CV Comparison

dcvar_compare(dcvar = fit_dc, hmm = fit_hmm, constant = fit_con)

Extracting Tidy Summaries

All fit objects support as.data.frame() for exporting tidy parameter summaries. All five fit families also support fitted()/predict() for one-step-ahead values (for SEM and multilevel fits as well). Prediction intervals are restricted to normal margins for single-level, multilevel, and naive-SEM fits; indicator-SEM predictions are available for all supported SEM margins:

# Full parameter summary as a data frame
param_df <- as.data.frame(fit_dc)
head(param_df)

# Predictions with marginal intervals
pred_df <- predict(fit_hmm)
head(pred_df)

HMM-Specific Outputs

The HMM model provides additional outputs related to state inference:

# State posteriors, Viterbi path, transition matrix
states <- hmm_states(fit_hmm)

# State-specific rho values
states$rho_state

# Transition matrix
states$A

# Visualise state posteriors
plot(fit_hmm, type = "states")

# Transition matrix heatmap
plot(fit_hmm, type = "transition")

Clinical Interpretation

interpret_rho_trajectory() provides a model-aware narrative:

interpret_rho_trajectory(fit_dc)
interpret_rho_trajectory(fit_hmm)
interpret_rho_trajectory(fit_con)

When to Use Which Model

  • Smooth dynamics (therapy effects, developmental trends): Use DC-VAR
  • Abrupt regime switches (treatment onset, relapse): Use HMM Copula
  • No time variation expected: Use Constant Copula as baseline
  • Uncertain: Fit all three and compare with LOO-CV