| Title: | Bayesian Nonlinear Ornstein-Uhlenbeck Models with Stochastic Volatility |
|---|---|
| Description: | Fits Bayesian nonlinear Ornstein-Uhlenbeck models with cubic drift, stochastic volatility, and Student-t innovations. The package implements hierarchical priors for sector-specific parameters and supports parallel MCMC sampling via 'Stan'. Model comparison is performed using Pareto Smoothed Importance Sampling Leave-One-Out (PSIS-LOO) cross-validation following Vehtari, Gelman, and Gabry (2017) <doi:10.1007/s11222-016-9696-4>. Prior specifications follow recommendations from Gelman (2006) <doi:10.1214/06-BA117A> for scale parameters. |
| Authors: | José Mauricio Gómez Julián [aut, cre] (ORCID: <https://orcid.org/0009-0000-2412-3150>) |
| Maintainer: | José Mauricio Gómez Julián <[email protected]> |
| License: | MIT + file LICENSE |
| Version: | 0.2.0 |
| Built: | 2026-10-05 07:34:48 UTC |
| Source: | https://github.com/IsadoreNabi/bayesianOU |
Creates accounting information for TMG transformations.
build_accounting_block( TMG_raw, zTMG_exo, zTMG_use, mu_tmg, sd_tmg, hard, sigma_delta )build_accounting_block( TMG_raw, zTMG_exo, zTMG_use, mu_tmg, sd_tmg, hard, sigma_delta )
TMG_raw |
Numeric vector. Original TMG series. |
zTMG_exo |
Numeric vector. Exogenous z-scored TMG. |
zTMG_use |
Numeric vector. TMG used in model (possibly orthogonalized). |
mu_tmg |
Numeric. Training mean of TMG. |
sd_tmg |
Numeric. Training SD of TMG. |
hard |
Logical. Whether hard sum-to-zero constraint was used. |
sigma_delta |
Numeric. Prior SD for wedge in original units. |
List with components:
Back-transformed TMG used in model
Back-transformed exogenous TMG
Difference (zero if hard=TRUE)
Prior SD for wedge
Description of constraint type
Constructs the time-varying beta matrix using posterior medians.
build_beta_tmg_table(fit, zTMG_use, summ = NULL)build_beta_tmg_table(fit, zTMG_use, summ = NULL)
fit |
Fitted Stan model object |
zTMG_use |
Numeric vector. Standardized TMG series used in fitting. |
summ |
Optional list from |
This is a deterministic point reconstruction
evaluated at the
posterior medians of beta0_s and beta1. It is NOT a sampled
time-varying coefficient: the time variation comes only from zTMG_t.
List with components:
Matrix (T x S) of beta values
List with description metadata
Compares two fitted models using PSIS-LOO cross-validation.
compare_models_loo(results_new, results_base)compare_models_loo(results_new, results_base)
results_new |
List. Results from new model. |
results_base |
List. Results from base model. |
List with components:
Comparison table from loo::loo_compare
Numeric difference in ELPD
if (requireNamespace("loo", quietly = TRUE)) { # 1. Create mock 'loo' objects manually to avoid computation errors # Structure required by loo_compare: list with 'estimates' and class 'psis_loo' # Mock Model 1: ELPD = -100 est1 <- matrix(c(-100, 5), nrow = 1, dimnames = list("elpd_loo", c("Estimate", "SE"))) # Pointwise data (required for diff calculation). 10 observations. pw1 <- matrix(c(rep(-10, 10)), ncol = 1, dimnames = list(NULL, "elpd_loo")) loo_obj1 <- list(estimates = est1, pointwise = pw1) class(loo_obj1) <- c("psis_loo", "loo") # Mock Model 2: ELPD = -102 (worse) est2 <- matrix(c(-102, 5), nrow = 1, dimnames = list("elpd_loo", c("Estimate", "SE"))) pw2 <- matrix(c(rep(-10.2, 10)), ncol = 1, dimnames = list(NULL, "elpd_loo")) loo_obj2 <- list(estimates = est2, pointwise = pw2) class(loo_obj2) <- c("psis_loo", "loo") # 2. Wrap in the structure expected by your package res_new <- list(diagnostics = list(loo = loo_obj1)) res_base <- list(diagnostics = list(loo = loo_obj2)) # 3. Compare (This will now run cleanly without warnings) cmp <- compare_models_loo(res_new, res_base) print(cmp) }if (requireNamespace("loo", quietly = TRUE)) { # 1. Create mock 'loo' objects manually to avoid computation errors # Structure required by loo_compare: list with 'estimates' and class 'psis_loo' # Mock Model 1: ELPD = -100 est1 <- matrix(c(-100, 5), nrow = 1, dimnames = list("elpd_loo", c("Estimate", "SE"))) # Pointwise data (required for diff calculation). 10 observations. pw1 <- matrix(c(rep(-10, 10)), ncol = 1, dimnames = list(NULL, "elpd_loo")) loo_obj1 <- list(estimates = est1, pointwise = pw1) class(loo_obj1) <- c("psis_loo", "loo") # Mock Model 2: ELPD = -102 (worse) est2 <- matrix(c(-102, 5), nrow = 1, dimnames = list("elpd_loo", c("Estimate", "SE"))) pw2 <- matrix(c(rep(-10.2, 10)), ncol = 1, dimnames = list(NULL, "elpd_loo")) loo_obj2 <- list(estimates = est2, pointwise = pw2) class(loo_obj2) <- c("psis_loo", "loo") # 2. Wrap in the structure expected by your package res_new <- list(diagnostics = list(loo = loo_obj1)) res_base <- list(diagnostics = list(loo = loo_obj2)) # 3. Compare (This will now run cleanly without warnings) cmp <- compare_models_loo(res_new, res_base) print(cmp) }
Extracts the number of divergent transitions from a fitted Stan model.
count_divergences(fit)count_divergences(fit)
fit |
Fitted Stan model object (CmdStanMCMC or stanfit) |
Integer. Number of divergent transitions (post-warmup).
# Create a "mock" CmdStanMCMC object for demonstration # (This simulates a model with 0 divergences) mock_fit <- structure(list( sampler_diagnostics = function() { # Return a 3D array: [iterations, chains, variables] # Variable 1 is usually accept_stat__, let's say var 2 is divergent__ ar <- array(0, dim = c(100, 4, 6)) dimnames(ar)[[3]] <- c("accept_stat__", "divergent__", "energy__", "n_leapfrog__", "stepsize__", "treedepth__") return(ar) } ), class = "CmdStanMCMC") # Now the example can run without errors: n_div <- count_divergences(mock_fit) print(n_div)# Create a "mock" CmdStanMCMC object for demonstration # (This simulates a model with 0 divergences) mock_fit <- structure(list( sampler_diagnostics = function() { # Return a 3D array: [iterations, chains, variables] # Variable 1 is usually accept_stat__, let's say var 2 is divergent__ ar <- array(0, dim = c(100, 4, 6)) dimnames(ar)[[3]] <- c("accept_stat__", "divergent__", "energy__", "n_leapfrog__", "stepsize__", "treedepth__") return(ar) } ), class = "CmdStanMCMC") # Now the example can run without errors: n_div <- count_divergences(mock_fit) print(n_div)
Computes the cubic OU drift function over a grid of centered deviations
. The evaluated function is
, i.e. the drift expressed in the deviation
coordinate, not in the original coordinate. To plot against ,
shift the grid by the corresponding theta_s.
drift_decomposition_grid(fit, summ, z_grid = seq(-2.5, 2.5, length.out = 101))drift_decomposition_grid(fit, summ, z_grid = seq(-2.5, 2.5, length.out = 101))
fit |
Fitted Stan model object (reserved for future use) |
summ |
List. Output from |
z_grid |
Numeric vector. Grid of centered deviation values (Y - theta). |
List with components:
The input z grid
Matrix (length(z) x S) of drift values by sector
Computes RMSE and MAE for multiple forecast horizons.
evaluate_oos( summ, Yz, Xz, zTMG, T_train, COM_ts, K_ts, com_in_mean = FALSE, horizons = c(1, 4, 8) )evaluate_oos( summ, Yz, Xz, zTMG, T_train, COM_ts, K_ts, com_in_mean = FALSE, horizons = c(1, 4, 8) )
summ |
List. Posterior summary from |
Yz |
Numeric matrix. Standardized Y values (T x S) |
Xz |
Numeric matrix. Standardized X values (T x S) |
zTMG |
Numeric vector. Standardized TMG series |
T_train |
Integer. End of training period |
COM_ts |
Numeric matrix. COM values by time and sector (T x S) |
K_ts |
Numeric matrix. Capital values by time and sector (T x S) |
com_in_mean |
Logical. Whether COM is included in mean equation |
horizons |
Integer vector. Forecast horizons to evaluate |
Two caveats matter for interpretation:
Conditional forecast. The recursion uses the realized
future covariates and at each step. It is
therefore a forecast of conditional on the future paths of
and TMG, not an unconditional forecast. If those covariates are
not known ex ante, treat these numbers as conditional/nowcasting metrics.
Plug-in medians. The path is propagated with posterior
medians of the parameters and ignores parameter uncertainty, the
stochastic volatility and the Student-t innovations. It is a point
(mean-equation) forecast, not the full posterior predictive distribution.
For genuinely out-of-sample numbers, fit with fit_window = "train".
Named list with one element per horizon, each a list with
h, RMSE, MAE and n_obs (number of pooled
sector-by-origin errors; 0 / NA when the horizon exceeds the
test window).
# 1. Generate dummy data for testing T_obs <- 20 S <- 2 Yz <- matrix(rnorm(T_obs * S), nrow = T_obs, ncol = S) Xz <- matrix(rnorm(T_obs * S), nrow = T_obs, ncol = S) COM_ts <- matrix(abs(rnorm(T_obs * S)), nrow = T_obs, ncol = S) K_ts <- matrix(abs(rnorm(T_obs * S)) + 1, nrow = T_obs, ncol = S) zTMG <- rnorm(T_obs) # 2. Create a dummy summary list (mimicking extract_posterior_summary) summ <- list( theta_s = runif(S), kappa_s = runif(S), a3_s = runif(S), beta0_s = runif(S), beta1 = 0.5, gamma = 0.1 ) # 3. Run the function metrics <- evaluate_oos(summ, Yz, Xz, zTMG, T_train = 15, COM_ts, K_ts, horizons = c(1, 2)) print(metrics)# 1. Generate dummy data for testing T_obs <- 20 S <- 2 Yz <- matrix(rnorm(T_obs * S), nrow = T_obs, ncol = S) Xz <- matrix(rnorm(T_obs * S), nrow = T_obs, ncol = S) COM_ts <- matrix(abs(rnorm(T_obs * S)), nrow = T_obs, ncol = S) K_ts <- matrix(abs(rnorm(T_obs * S)) + 1, nrow = T_obs, ncol = S) zTMG <- rnorm(T_obs) # 2. Create a dummy summary list (mimicking extract_posterior_summary) summ <- list( theta_s = runif(S), kappa_s = runif(S), a3_s = runif(S), beta0_s = runif(S), beta1 = 0.5, gamma = 0.1 ) # 3. Run the function metrics <- evaluate_oos(summ, Yz, Xz, zTMG, T_train = 15, COM_ts, K_ts, horizons = c(1, 2)) print(metrics)
Out-of-sample analogue of evaluate_oos for the nested model.
The decisive difference from the single-level recursion is the attractor: in
the single-level model the market price reverts to a constant level with a
linear forcing by the exogenous production-price covariate ; in the
nested model the market price reverts to the latent
production price , which is itself propagated forward through its own
Level-2 Ornstein-Uhlenbeck equation toward the -driven mean
. Both states are advanced jointly; the
market never sees the realized future .
evaluate_oos_nested( meds, Yz, Phi_med, Gprime_z, zTMG, T_train, COM_ts, K_ts, kappa_cap, com_in_mean = FALSE, horizons = c(1, 4, 8), V_z = NULL )evaluate_oos_nested( meds, Yz, Phi_med, Gprime_z, zTMG, T_train, COM_ts, K_ts, kappa_cap, com_in_mean = FALSE, horizons = c(1, 4, 8), V_z = NULL )
meds |
List of posterior medians with components |
Yz |
Numeric matrix (T x S). Standardized market price |
Phi_med |
Numeric matrix (T x S). Posterior-median latent production-price
path |
Gprime_z |
Numeric vector (length T). Standardized aggregate profit rate
|
zTMG |
Numeric vector (length T). Standardized TMG series used in fitting. |
T_train |
Integer. End of the training window. |
COM_ts |
Numeric matrix (T x S). Composition of capital by sector. |
K_ts |
Numeric matrix (T x S). Total capital (training weights for the COM standardization, matching the Stan transformed-data block). |
kappa_cap |
Numeric > 0. Stability cap used in fitting. |
com_in_mean |
Logical. Whether the COM term enters the mean. Default FALSE. |
horizons |
Integer vector. Forecast horizons to evaluate. |
V_z |
Numeric matrix (T x S) or NULL. Standardized value anchor (direct
prices |
At each forecast origin the recursion is seeded with the realized
standardized market price and the posterior-median latent
production price , then for steps:
with the bounded gravitation
speed (the same logit link used in fitting). The market step uses
(one-step lag), matching the Stan likelihood.
For the Level-3 value model (n_levels = 3), the Level-2 attractor also
tracks the value index,
(production prices gravitate around values, Capital III ch. IX), recovered by
supplying the standardized value anchor V_z and the coupling
meds$m_v. Without them the recursion is the plain Level-2 mean.
Two caveats, identical in spirit to evaluate_oos:
Conditional forecast. The recursion still consumes the
realized future and (the macro drivers), so it is a
forecast conditional on those aggregate paths, not on the sectoral
production prices (which are now latent and forecast endogenously).
Plug-in medians. States are propagated with posterior medians
and ignore parameter uncertainty, the latent- innovation, the
stochastic volatility and the Student-t tails. It is a point (mean-equation)
forecast, not the full posterior predictive distribution. Fit with
fit_window = "train" for genuinely held-out numbers.
Named list with one element per horizon, each a list with h,
RMSE, MAE and n_obs (pooled sector-by-origin errors;
0 / NA when the horizon exceeds the test window). Errors are in
standardized units, directly comparable with
evaluate_oos.
evaluate_oos (single-level), fit_ou_nested.
Creates an Excel workbook with model comparison results, parameter summaries, and fit information.
export_model_comparison( results_new, results_base, path = file.path(tempdir(), "model_comparison.xlsx"), verbose = FALSE )export_model_comparison( results_new, results_base, path = file.path(tempdir(), "model_comparison.xlsx"), verbose = FALSE )
results_new |
List. Results from new model. |
results_base |
List. Results from base model. |
path |
Character. Output file path. Default "model_comparison.xlsx". |
verbose |
Logical. Print progress messages. Default FALSE. |
Creates three worksheets:
loo_comparison: PSIS-LOO comparison table
param_summary: Sector-specific parameter estimates
fit_info: Model configuration and diagnostics
TRUE invisibly on success.
if (requireNamespace("openxlsx", quietly = TRUE) && requireNamespace("loo", quietly = TRUE)) { # 1. Create mock results objects # Mock Model New res_new <- list( factor_ou = list( kappa_s = c(0.5, 0.6), a3_s = c(-0.1, -0.2), beta0_s = c(1, 2), gamma = 0.05, model = "TestModel", beta1 = 0.3, nu = 4, factor_ou_info = list(T_train = 50, com_in_mean = TRUE) ), diagnostics = list( divergences = 0, # Mock LOO object loo = list( estimates = matrix(c(-100, 2), 1, 2, dimnames=list("elpd_loo", c("Estimate","SE"))), pointwise = matrix(rep(-2, 50), ncol=1) ) ) ) class(res_new$diagnostics$loo) <- c("psis_loo", "loo") # Mock Model Base res_base <- list( diagnostics = list( loo = list( estimates = matrix(c(-110, 2), 1, 2, dimnames=list("elpd_loo", c("Estimate","SE"))), pointwise = matrix(rep(-2.2, 50), ncol=1) ) ) ) class(res_base$diagnostics$loo) <- c("psis_loo", "loo") # 2. Define a safe temporary path out_path <- file.path(tempdir(), "comparison.xlsx") # 3. Run export (This writes to tempdir, allowed by CRAN) try({ export_model_comparison(res_new, res_base, path = out_path) # unlink(out_path) # Cleanup }) }if (requireNamespace("openxlsx", quietly = TRUE) && requireNamespace("loo", quietly = TRUE)) { # 1. Create mock results objects # Mock Model New res_new <- list( factor_ou = list( kappa_s = c(0.5, 0.6), a3_s = c(-0.1, -0.2), beta0_s = c(1, 2), gamma = 0.05, model = "TestModel", beta1 = 0.3, nu = 4, factor_ou_info = list(T_train = 50, com_in_mean = TRUE) ), diagnostics = list( divergences = 0, # Mock LOO object loo = list( estimates = matrix(c(-100, 2), 1, 2, dimnames=list("elpd_loo", c("Estimate","SE"))), pointwise = matrix(rep(-2, 50), ncol=1) ) ) ) class(res_new$diagnostics$loo) <- c("psis_loo", "loo") # Mock Model Base res_base <- list( diagnostics = list( loo = list( estimates = matrix(c(-110, 2), 1, 2, dimnames=list("elpd_loo", c("Estimate","SE"))), pointwise = matrix(rep(-2.2, 50), ncol=1) ) ) ) class(res_base$diagnostics$loo) <- c("psis_loo", "loo") # 2. Define a safe temporary path out_path <- file.path(tempdir(), "comparison.xlsx") # 3. Run export (This writes to tempdir, allowed by CRAN) try({ export_model_comparison(res_new, res_base, path = out_path) # unlink(out_path) # Cleanup }) }
Computes 95 percent credible intervals for each kappa_s (mean
reversion speed) and the posterior probability that every sector reverts.
extract_convergence_evidence(fit_res, verbose = TRUE)extract_convergence_evidence(fit_res, verbose = TRUE)
fit_res |
List returned by |
verbose |
Logical. Print summary to console. Default TRUE. |
List with components:
Matrix (S x 3) with columns q2.5, median, q97.5
Logical indicating if all kappa CIs fall in (0,1)
Deprecated alias of stable (kept for back-compat)
Posterior probability of joint mean reversion
Deprecated alias of prob_stable
Despite the historical name, this function does NOT assess sampler
convergence. It evaluates a dynamic property of the estimated process:
whether the mean-reversion speed lies in a range consistent with a stable,
reverting (gravitating) system. For sampler convergence use R-hat, ESS and
divergences (see validate_ou_fit). The threshold
kappa < 1 is a (conservative) monotone-reversion criterion; under the
Euler discretization the linear map is stable for 0 < kappa < 2.
The alias kappa_stability_evidence is preferred.
# 1. Create a mock fit object with kappa draws # kappa_tilde is log(kappa), so we use log(0.5) roughly -0.69 n_draws <- 100 S <- 2 kappa_tilde_draws <- matrix(rnorm(n_draws * S, mean = -0.7, sd = 0.1), nrow = n_draws, ncol = S) colnames(kappa_tilde_draws) <- c("kappa_tilde[1]", "kappa_tilde[2]") mock_fit <- structure(list( draws = function(vars, format="matrix") { if (vars == "kappa_tilde") return(kappa_tilde_draws) return(NULL) } ), class = "CmdStanMCMC") # 2. Wrap in the results list structure results_mock <- list( factor_ou = list( stan_fit = mock_fit ) ) # 3. Extract evidence conv <- extract_convergence_evidence(results_mock) print(conv$prob_convergence)# 1. Create a mock fit object with kappa draws # kappa_tilde is log(kappa), so we use log(0.5) roughly -0.69 n_draws <- 100 S <- 2 kappa_tilde_draws <- matrix(rnorm(n_draws * S, mean = -0.7, sd = 0.1), nrow = n_draws, ncol = S) colnames(kappa_tilde_draws) <- c("kappa_tilde[1]", "kappa_tilde[2]") mock_fit <- structure(list( draws = function(vars, format="matrix") { if (vars == "kappa_tilde") return(kappa_tilde_draws) return(NULL) } ), class = "CmdStanMCMC") # 2. Wrap in the results list structure results_mock <- list( factor_ou = list( stan_fit = mock_fit ) ) # 3. Extract evidence conv <- extract_convergence_evidence(results_mock) print(conv$prob_convergence)
Reconstructs the time-varying Level-2 mean toward which the latent production
price reverts, , with credible
bands obtained by evaluating the expression over the posterior draws of
mu_const () and m1. This is the structural hook to
the (not-yet-formalized) Level 3 of values: is the slow attractor of
the production price, here driven by the aggregate profit rate .
extract_mu_trajectory(fit, Gprime_z, sector_names = NULL, V_z = NULL)extract_mu_trajectory(fit, Gprime_z, sector_names = NULL, V_z = NULL)
fit |
Fitted Stan object ( |
Gprime_z |
Numeric vector (length T). Standardized aggregate profit rate used in fitting (training statistics). |
sector_names |
Character vector (length S) or NULL. Column names for the returned matrices. |
V_z |
Numeric matrix (T x S) or NULL. Standardized value anchor (direct
prices |
List with median, q2.5, q97.5 (each a T x S
matrix) and Gprime_z (the driver), or NULL if the Level-2
parameters are not present in the fit.
Extracts median point estimates and credible intervals for all model parameters from a fitted Stan model.
extract_posterior_summary(fit)extract_posterior_summary(fit)
fit |
Fitted Stan model object (CmdStanMCMC or stanfit) |
List with components:
Median of global TMG effect
Vector of sector-specific intercepts
Vector of mean reversion speeds
Vector of cubic drift coefficients
Vector of equilibrium levels
Vector of SV persistence parameters
Vector of SV level parameters
Vector of SV volatility parameters
Degrees of freedom for Student-t
COM effect in mean
R-hat convergence diagnostics
Effective sample sizes
# 1. Create a mock CmdStanMCMC object # We simulate a posterior distribution for 2 sectors S <- 2 n_draws <- 100 # Helper to generate random draws mock_draws <- function(name, n_cols=1) { m <- matrix(rnorm(n_draws * n_cols), nrow = n_draws, ncol = n_cols) if (n_cols > 1) { colnames(m) <- paste0(name, "[", 1:n_cols, "]") } else { colnames(m) <- name } as.data.frame(m) } # Combine draws into one data frame df_draws <- cbind( mock_draws("beta1", 1), mock_draws("beta0_s", S), mock_draws("kappa_tilde", S), # Note: function expects log-scale kappa mock_draws("a3_tilde", S), # Note: function expects log-scale a3 mock_draws("theta_s", S), mock_draws("rho_s", S), mock_draws("alpha_s", S), mock_draws("sigma_eta_s", S), mock_draws("nu_tilde", 1), mock_draws("gamma", 1) ) # Mock fit object mock_fit <- structure(list( draws = function(vars, format="df") { # Simple regex matching for the mock if (length(vars) == 1) { # Check if it's a scalar or vector parameter request if (vars %in% names(df_draws)) return(df_draws[vars]) # Pattern match for vectors like "beta0_s" -> "beta0_s[1]", "beta0_s[2]" cols <- grep(paste0("^", vars, "\\["), names(df_draws), value = TRUE) if (length(cols) > 0) return(df_draws[cols]) } return(df_draws) }, summary = function() { data.frame( variable = names(df_draws), rhat = rep(1.0, ncol(df_draws)), ess_bulk = rep(400, ncol(df_draws)) ) } ), class = "CmdStanMCMC") # 2. Run extraction summ <- extract_posterior_summary(mock_fit) print(summ$kappa_s)# 1. Create a mock CmdStanMCMC object # We simulate a posterior distribution for 2 sectors S <- 2 n_draws <- 100 # Helper to generate random draws mock_draws <- function(name, n_cols=1) { m <- matrix(rnorm(n_draws * n_cols), nrow = n_draws, ncol = n_cols) if (n_cols > 1) { colnames(m) <- paste0(name, "[", 1:n_cols, "]") } else { colnames(m) <- name } as.data.frame(m) } # Combine draws into one data frame df_draws <- cbind( mock_draws("beta1", 1), mock_draws("beta0_s", S), mock_draws("kappa_tilde", S), # Note: function expects log-scale kappa mock_draws("a3_tilde", S), # Note: function expects log-scale a3 mock_draws("theta_s", S), mock_draws("rho_s", S), mock_draws("alpha_s", S), mock_draws("sigma_eta_s", S), mock_draws("nu_tilde", 1), mock_draws("gamma", 1) ) # Mock fit object mock_fit <- structure(list( draws = function(vars, format="df") { # Simple regex matching for the mock if (length(vars) == 1) { # Check if it's a scalar or vector parameter request if (vars %in% names(df_draws)) return(df_draws[vars]) # Pattern match for vectors like "beta0_s" -> "beta0_s[1]", "beta0_s[2]" cols <- grep(paste0("^", vars, "\\["), names(df_draws), value = TRUE) if (length(cols) > 0) return(df_draws[cols]) } return(df_draws) }, summary = function() { data.frame( variable = names(df_draws), rhat = rep(1.0, ncol(df_draws)), ess_bulk = rep(400, ncol(df_draws)) ) } ), class = "CmdStanMCMC") # 2. Run extraction summ <- extract_posterior_summary(mock_fit) print(summ$kappa_s)
Single engine behind the package. n_levels = 1 reproduces the legacy
single-level model exactly (market price reverts to a constant level with a
linear forcing by the production price and a TMG interaction);
n_levels = 2 fits the nested model in which the market price
reverts to a latent production price
(Level 1), and has its own Ornstein-Uhlenbeck dynamics reverting
to a -driven mean (Level 2). The constructed production-price index
enters as a noisy measurement (anchor) of the latent .
fit_ou_nested( Y, X, TMG, COM, CAPITAL_TOTAL, n_levels = 1L, Gprime = NULL, k_cost = NULL, V_value = NULL, level_spec = NULL, theta_separation = c("soft", "hard"), k_uncertainty = c("meas", "recon"), sigma_phi_meas_fixed = NULL, kappa_cap = 2, results_robust = list(), priors = list(), com_in_mean = TRUE, train_frac = 0.7, fit_window = c("train", "full"), chains = 6, iter = 12000, warmup = 6000, thin = 1, cores = max(1, parallel::detectCores() - 1), threads_per_chain = 2, hard_sum_zero = TRUE, orthogonalize_tmg = TRUE, factor_from = c("X", "Y"), use_train_loadings = TRUE, adapt_delta = 0.97, max_treedepth = 12, seed = 1234, init = NULL, moment_match = NULL, verbose = FALSE )fit_ou_nested( Y, X, TMG, COM, CAPITAL_TOTAL, n_levels = 1L, Gprime = NULL, k_cost = NULL, V_value = NULL, level_spec = NULL, theta_separation = c("soft", "hard"), k_uncertainty = c("meas", "recon"), sigma_phi_meas_fixed = NULL, kappa_cap = 2, results_robust = list(), priors = list(), com_in_mean = TRUE, train_frac = 0.7, fit_window = c("train", "full"), chains = 6, iter = 12000, warmup = 6000, thin = 1, cores = max(1, parallel::detectCores() - 1), threads_per_chain = 2, hard_sum_zero = TRUE, orthogonalize_tmg = TRUE, factor_from = c("X", "Y"), use_train_loadings = TRUE, adapt_delta = 0.97, max_treedepth = 12, seed = 1234, init = NULL, moment_match = NULL, verbose = FALSE )
Y |
Numeric matrix (T x S). Market prices/values |
X |
Numeric matrix (T x S). Production prices |
TMG |
Numeric vector (length T). Aggregate profit-rate series used as the TMG interaction (modulates the gravitation speed). |
COM |
Numeric matrix (T x S). Composition of capital by sector. |
CAPITAL_TOTAL |
Numeric matrix (T x S). Total capital by sector. |
n_levels |
Integer, 1, 2 or 3. Model depth. |
Gprime |
Numeric vector (length T) or NULL. Aggregate profit rate
|
k_cost |
Numeric matrix (T x S) or NULL. Cost price |
V_value |
Numeric matrix (T x S) or NULL. Value anchor (direct prices
|
level_spec |
List or NULL. Per-level richness toggles, each a list
|
theta_separation |
Character. Time-scale separation |
k_uncertainty |
Character. Treatment of the methodological uncertainty
about the capital |
sigma_phi_meas_fixed |
Numeric > 0 or NULL. Latency dial for the anchor
measurement SD |
kappa_cap |
Numeric > 0. Stability cap for the 2-level reversion speeds:
both the market speed |
results_robust |
List. Previous results object to extend (or empty list). |
priors |
List. Prior overrides (partial; missing entries use robust
defaults). In addition to the single-level names ( |
com_in_mean |
Logical. Include COM effect in the mean. Default TRUE. |
train_frac |
Numeric in (0,1). Training-window fraction. Default 0.70. |
fit_window |
Character. |
chains, iter, warmup, thin
|
Integer MCMC controls. Defaults 6 / 12000 / 6000 / 1. |
cores |
Integer. Cores for parallel chains. |
threads_per_chain |
Integer. Within-chain threads (capped so
|
hard_sum_zero |
Logical. Fix the TMG wedge at zero. Default TRUE. |
orthogonalize_tmg |
Logical. Orthogonalize TMG w.r.t. the common factor. Default TRUE. |
factor_from |
Character. Source for the common factor: |
use_train_loadings |
Logical. Compute factor loadings from training only. Default TRUE. |
adapt_delta |
Numeric in (0,1). NUTS target acceptance. Default 0.97. |
max_treedepth |
Integer. NUTS maximum tree depth. Default 12. |
seed |
Integer. Random seed. |
init |
Numeric, list or function. Initial values (passed to the backend). |
moment_match |
Logical or NULL. Accepted for API parity; not applied to
the array-based PSIS-LOO (see |
verbose |
Logical. Print progress. Default FALSE. |
fit_ou_nonlinear_tmg is a thin backward-compatible wrapper around this
engine with n_levels = 1; this function is the single source of truth.
A list. For n_levels = 1 the structure matches the legacy
single-level fit, with the inner factor_ou carrying the historical S3
class "ou_nonlinear_tmg". For n_levels = 2 the top-level
object carries the S3 class "ou_nested_2level" (so
print,
summary and
plot dispatch on it) and adds
factor_ou$level2 (production reversion speed kappa_p, market
speed at kappa_m_base, mean intercept mu_const,
slope m1, innovation scale sigma_p, measurement scale
sigma_phi_meas, and the Level-2 richness quantities that are present
only when their switch is on: the cubic restoring coefficient a3_p
(l2_cubic), the Student-t degrees of freedom nu_p
(l2_studentt) and the time-varying SV scale path sigma_p_t =
(l2_sv); see decision D-IMPL-10);
phi_latent (the latent trajectory
with credible bands); mu_path (the -driven Level-2 mean
trajectory with bands); a
separation block with the posterior evidence for ; and diagnostics$oos, the 2-level out-of-sample metrics in
which the market reverts to the latent propagated forward (see
evaluate_oos_nested).
fit_ou_nonlinear_tmg for the single-level wrapper and
fit_ou_nested_mi for the multiple-imputation driver that
couples disaggregation draws to the 2-level model via Rubin's rule.
T_obs <- 24; S <- 2 Y <- matrix(rnorm(T_obs * S), T_obs, S) X <- matrix(rnorm(T_obs * S), T_obs, S) TMG <- rnorm(T_obs) COM <- matrix(runif(T_obs * S), T_obs, S) K <- matrix(runif(T_obs * S, 100, 1000), T_obs, S) Gp <- rnorm(T_obs) if (requireNamespace("cmdstanr", quietly = TRUE)) { try(fit_ou_nested(Y, X, TMG, COM, K, n_levels = 2, Gprime = Gp, chains = 1, iter = 100, warmup = 50), silent = TRUE) }T_obs <- 24; S <- 2 Y <- matrix(rnorm(T_obs * S), T_obs, S) X <- matrix(rnorm(T_obs * S), T_obs, S) TMG <- rnorm(T_obs) COM <- matrix(runif(T_obs * S), T_obs, S) K <- matrix(runif(T_obs * S, 100, 1000), T_obs, S) Gp <- rnorm(T_obs) if (requireNamespace("cmdstanr", quietly = TRUE)) { try(fit_ou_nested(Y, X, TMG, COM, K, n_levels = 2, Gprime = Gp, chains = 1, iter = 100, warmup = 50), silent = TRUE) }
Couples the disaggregation posterior to the OU model by multiple imputation
(Rubin's rule). The disaggregation step produces posterior draws of
the sector-level market price (each a T x S matrix); this driver
refits the OU model once per imputation and combines the analyses, propagating
the disaggregation uncertainty into the OU posterior. Two complementary
summaries are returned: Rubin's moment-pooled estimates (within + between
imputation variance) and the mixture posterior over the imputations (the
"complete-draws" pooling used for joint probabilities such as the time-scale
separation).
fit_ou_nested_mi( phi_draws, X, TMG, COM, CAPITAL_TOTAL, Gprime = NULL, V_value = NULL, n_levels = 2L, M = 25, pool_params = NULL, keep_fits = FALSE, keep_kappa_mixture = FALSE, kappa_mixture_draws = 4000L, seed = 1234, verbose = FALSE, checkpoint_dir = NULL, ... )fit_ou_nested_mi( phi_draws, X, TMG, COM, CAPITAL_TOTAL, Gprime = NULL, V_value = NULL, n_levels = 2L, M = 25, pool_params = NULL, keep_fits = FALSE, keep_kappa_mixture = FALSE, kappa_mixture_draws = 4000L, seed = 1234, verbose = FALSE, checkpoint_dir = NULL, ... )
phi_draws |
Imputations of the market price. Either a list of |
X |
Production-price index |
TMG |
Numeric vector (length T). Aggregate profit-rate series. Fixed across imputations (observed aggregate). |
COM |
Composition of capital by sector. A fixed matrix (T x S) or a
per-imputation list/array |
CAPITAL_TOTAL |
Total capital advanced |
Gprime |
Numeric vector (length T) or NULL. Aggregate profit rate driving
the Level-2 mean; required when |
V_value |
Value anchor (direct prices |
n_levels |
Integer, 1, 2 or 3. Passed to |
M |
Integer. Number of imputations to use. Default 25. If fewer draws are
supplied, all are used (with a warning); if more, |
pool_params |
Character vector or NULL. Parameters to Rubin-pool. NULL
selects a sensible set for the chosen |
keep_fits |
Logical. Keep the per-imputation fit objects (memory heavy). Default FALSE (only compact per-imputation summaries are retained). |
keep_kappa_mixture |
Logical. When TRUE (and |
kappa_mixture_draws |
Integer. Target number of thinned mixture draws to
retain when |
seed |
Integer. Base seed; imputation |
verbose |
Logical. Print progress. Default FALSE. |
checkpoint_dir |
Character path or NULL. When NULL (default) the function
behaves exactly as before (no files written). When a directory is given,
each imputation's compact pooling contribution is persisted to
|
... |
Passed to |
Unified external multiple imputation (D-IMPL-13.5). Beyond the market price,
X (the production-price anchor ), V_value (the value
anchor), CAPITAL_TOTAL () and COM may also vary by
imputation. Each of these four accepts either a fixed matrix (the legacy
behaviour: used verbatim for every imputation, bit-identical results) or a
per-imputation set – a list of matrices or a 3-D array
[T, S, D] – paired one-to-one with phi_draws. This lets a single
external imputation generator propagate ALL the construction uncertainty
(disaggregation of the market, the imputed split of and that
feeds and , and the reconstructed ) through the
convergent K-DET / Variant-A engine, pooling by Rubin's rule. The aggregates
TMG and Gprime stay fixed (they are observed, preserved by the
aggregate-preserving renormalization of the generator). The engine is untouched
(R-side only): and the all-fixed path remain bit-identical.
An object of class "ou_nested_mi": a list with
Data frame of Rubin-pooled estimates per parameter (estimate, total SD, 95% CI, Barnard-Rubin df, fraction of missing information).
(n_levels >= 2) Rubin-pooled latent
path: mean, sd, q2.5, q97.5 as
T x S matrices.
(n_levels >= 2) mixture-posterior evidence
for : prob_sep_by_sector (per-sector
, the sectoral distribution to report – at
n_levels = 3 the joint probability typically collapses because
is re-identified once the value term absorbs the production
mean, see D-IMPL-10.1, which is NOT "no separation"), prob_sep_joint,
and the per-sector reversion summaries kappa_m_median /
kappa_p_median and half-lives
(halflife_*_median, halflife_*_q2.5, halflife_*_q97.5)
from the mixture posterior. Since is positive by construction,
is uninformative; the half-life (and whether it exceeds
the observed span – the low-kappa trap) is the substantive per-sector
quantity.
(only when keep_kappa_mixture = TRUE) a thinned
mixture posterior of kappa_m_base and kappa_p (draws x S),
stacked across imputations, for sectoral half-life distributions and
low-kappa-trap probabilities at a chosen horizon.
List of compact per-imputation summaries or full
fits if keep_fits = TRUE. Each summary carries the chain-aware
convergence over the pooled parameters (rhat_max_pooled,
ess_bulk_min_pooled, ess_tail_min_pooled) – the binding
diagnostic for Rubin's pooling – plus the global rhat_max,
rhat_share, ess_bulk_min (over all parameters, latent
states included; reference only) and divergences.
The imputation/pooling configuration.
Rubin DB (1987) Multiple Imputation for Nonresponse in Surveys. Barnard J, Rubin DB (1999) Small-sample degrees of freedom with multiple imputation. Biometrika 86(4):948-955.
Backward-compatible single-level entry point. This is a thin wrapper around
fit_ou_nested with n_levels = 1: the nested engine is the
single source of truth, and n_levels = 1 reproduces the legacy
single-level model exactly (market price reverts to a constant level with a
linear forcing by the production price X and a TMG interaction). The
returned object keeps the historical structure and S3 class
"ou_nonlinear_tmg" so existing extractors, plots and validators work
unchanged.
fit_ou_nonlinear_tmg( results_robust, Y, X, TMG, COM, CAPITAL_TOTAL, model = c("base"), priors = list(), com_in_mean = TRUE, train_frac = 0.7, fit_window = c("train", "full"), chains = 6, iter = 12000, warmup = 6000, thin = 1, cores = max(1, parallel::detectCores() - 1), threads_per_chain = 2, hard_sum_zero = TRUE, orthogonalize_tmg = TRUE, factor_from = c("X", "Y"), use_train_loadings = TRUE, adapt_delta = 0.97, max_treedepth = 12, seed = 1234, init = NULL, moment_match = NULL, verbose = FALSE )fit_ou_nonlinear_tmg( results_robust, Y, X, TMG, COM, CAPITAL_TOTAL, model = c("base"), priors = list(), com_in_mean = TRUE, train_frac = 0.7, fit_window = c("train", "full"), chains = 6, iter = 12000, warmup = 6000, thin = 1, cores = max(1, parallel::detectCores() - 1), threads_per_chain = 2, hard_sum_zero = TRUE, orthogonalize_tmg = TRUE, factor_from = c("X", "Y"), use_train_loadings = TRUE, adapt_delta = 0.97, max_treedepth = 12, seed = 1234, init = NULL, moment_match = NULL, verbose = FALSE )
results_robust |
List. Previous results object to extend (can be empty list). |
Y |
Numeric matrix (T x S). Dependent variable (prices/values by sector). |
X |
Numeric matrix (T x S). Independent variable (production prices). |
TMG |
Numeric vector (length T). Aggregate TMG series. |
COM |
Numeric matrix (T x S). Composition of capital by sector. |
CAPITAL_TOTAL |
Numeric matrix (T x S). Total capital by sector. |
model |
Character. Model type. Currently only "base" supported. |
priors |
List. Prior specifications (partial override allowed; missing
entries fall back to robust defaults). Supported names: |
com_in_mean |
Logical. Include COM effect in mean equation. Default TRUE. |
train_frac |
Numeric in (0,1). Fraction of observations used for the training window. Default 0.70. |
fit_window |
Character. |
chains |
Integer. Number of MCMC chains. Default 6. |
iter |
Integer. Total iterations per chain (must exceed warmup). Default 12000. |
warmup |
Integer. Warmup iterations. Default 6000. |
thin |
Integer. Thinning interval. Default 1 (thinning is statistically wasteful in HMC; increase only to save memory). |
cores |
Integer. Number of cores for parallel chains. |
threads_per_chain |
Integer. Threads per chain for within-chain parallelism. Capped internally so parallel_chains * threads <= cores. |
hard_sum_zero |
Logical. If TRUE, TMG wedge is fixed at zero. Default TRUE. |
orthogonalize_tmg |
Logical. Orthogonalize TMG w.r.t. common factor. Default TRUE. |
factor_from |
Character. Source for common factor: "X" or "Y". Default "X". |
use_train_loadings |
Logical. Compute factor loadings from training only (avoids look-ahead leakage). Default TRUE. |
adapt_delta |
Numeric. Target acceptance rate (0-1). Default 0.97. |
max_treedepth |
Integer. Maximum tree depth for NUTS. Default 12. |
seed |
Integer. Random seed for reproducibility. |
init |
Numeric or function. Initial values for parameters. |
moment_match |
Logical. Use moment matching for LOO. Default NULL. |
verbose |
Logical. Print progress messages. Default FALSE. |
The model uses a non-centered hierarchical (partial-pooling) parameterization
for the sector-specific parameters theta_s, kappa_s,
a3_s and beta0_s: each is built as
hyper_intercept + hyper_slope * COM_s + hyper_sd * z, with
z ~ N(0,1). The training window is floor(T * train_frac).
All data standardization, the COM weighting, and (by default) the common
factor loadings are computed from the training window only. The likelihood
window is controlled by fit_window to keep train/test coherent.
The cubic drift enforces a3 < 0 (a restoring force that strengthens
with the deviation); this is a stability assumption, not an estimated
result, and it precludes detecting locally expansive (self-amplifying)
regimes. The Euler discretization uses dt = 1, so the discrete persistence of
the linear part is 1 - kappa; interpret half-lives accordingly.
List containing factor_ou (model results and parameter
estimates), beta_tmg, sv, nonlinear, accounting
and diagnostics (MCMC diagnostics, PSIS-LOO and OOS metrics). See
fit_ou_nested for the underlying engine and the 2-level model.
# 1. Prepare dummy data T_obs <- 20 S_sectors <- 2 Y <- matrix(rnorm(T_obs * S_sectors), nrow = T_obs, ncol = S_sectors) X <- matrix(rnorm(T_obs * S_sectors), nrow = T_obs, ncol = S_sectors) TMG <- rnorm(T_obs) COM <- matrix(runif(T_obs * S_sectors), nrow = T_obs, ncol = S_sectors) K <- matrix(runif(T_obs * S_sectors, 100, 1000), nrow = T_obs, ncol = S_sectors) # 2. Run model (conditional on Stan backend availability) if (requireNamespace("cmdstanr", quietly = TRUE) || requireNamespace("rstan", quietly = TRUE)) { try({ results <- fit_ou_nonlinear_tmg( results_robust = list(), Y = Y, X = X, TMG = TMG, COM = COM, CAPITAL_TOTAL = K, chains = 1, iter = 100, warmup = 50, verbose = FALSE ) }, silent = TRUE) }# 1. Prepare dummy data T_obs <- 20 S_sectors <- 2 Y <- matrix(rnorm(T_obs * S_sectors), nrow = T_obs, ncol = S_sectors) X <- matrix(rnorm(T_obs * S_sectors), nrow = T_obs, ncol = S_sectors) TMG <- rnorm(T_obs) COM <- matrix(runif(T_obs * S_sectors), nrow = T_obs, ncol = S_sectors) K <- matrix(runif(T_obs * S_sectors, 100, 1000), nrow = T_obs, ncol = S_sectors) # 2. Run model (conditional on Stan backend availability) if (requireNamespace("cmdstanr", quietly = TRUE) || requireNamespace("rstan", quietly = TRUE)) { try({ results <- fit_ou_nonlinear_tmg( results_robust = list(), Y = Y, X = X, TMG = TMG, COM = COM, CAPITAL_TOTAL = K, chains = 1, iter = 100, warmup = 50, verbose = FALSE ) }, silent = TRUE) }
Preferred alias of extract_convergence_evidence. See that
function for details. The name avoids conflating dynamic mean reversion with
MCMC sampler convergence.
kappa_stability_evidence(fit_res, verbose = TRUE)kappa_stability_evidence(fit_res, verbose = TRUE)
fit_res |
List returned by |
verbose |
Logical. Print summary to console. Default TRUE. |
See extract_convergence_evidence.
Decoupled core (the improvement over the gdpar bridge, which required a fitted
object of a specific class and recompiled from its source): takes a
CmdStanModel ALREADY compiled with compile_model_methods = TRUE
together with its Stan data, samples one iteration to expose the standalone
log_prob/grad_log_prob/hessian methods, reads the
unconstrained dimension and a posterior-mean warm-start, and returns an
ou_geom_target plus a reference position. Nothing in the regular
fit path is touched.
ou_geom_bridge( model, stan_data, dim = NULL, reference = NULL, hessian = TRUE, methods_seed = 1L )ou_geom_bridge( model, stan_data, dim = NULL, reference = NULL, hessian = TRUE, methods_seed = 1L )
model |
A |
stan_data |
The Stan data list. |
dim |
Optional unconstrained dimension (derived from the one-iteration
fit when |
reference |
Optional unconstrained warm-start (posterior mean of the
one-iteration fit when |
hessian |
Logical; request the standalone Hessian (best-effort: falls back to gradient-only when higher-order autodiff does not compile). |
methods_seed |
Integer seed forwarded to |
A list with target (an ou_geom_target), dim,
reference, fit (the one-iteration cmdstan fit with methods) and
has_hessian.
Fixed step-size / fixed trajectory-length HMC with a Metropolis correction over a pluggable metric. The default Euclidean metric is textbook HMC; a Riemannian metric reuses the same loop with the generalised implicit leapfrog of Girolami & Calderhead (2011). The metric is a preconditioner only, so the returned draws are exact regardless of which metric is chosen.
ou_geom_hmc( target, metric = NULL, epsilon = 0.1, L = 20L, n_iter = 1000L, n_warmup = 500L, init = NULL, seed = NULL, fp_tol = 1e-09, fp_max = 100L )ou_geom_hmc( target, metric = NULL, epsilon = 0.1, L = 20L, n_iter = 1000L, n_warmup = 500L, init = NULL, seed = NULL, fp_tol = 1e-09, fp_max = 100L )
target |
A |
metric |
A |
epsilon |
Leapfrog step size. |
L |
Leapfrog steps per proposal. |
n_iter |
Retained iterations. |
n_warmup |
Burn-in iterations discarded (no adaptation). |
init |
Optional initial position (defaults to zeros). |
seed |
Optional integer seed (RNG state set and restored). |
fp_tol, fp_max
|
Fixed-point tolerance / max iterations for the implicit leapfrog (position-dependent metric only). |
A list of class ou_geom_hmc with draws, accept_rate,
n_divergent, energy, ebfmi, epsilon, L and
metric_type.
A position-independent mass matrix: the identity (diagonal) by default, or a supplied symmetric positive-definite matrix / positive variance vector (dense preconditioner; the remedy for a straight anisotropic canyon).
ou_geom_metric_euclidean(dim = NULL, M = NULL)ou_geom_metric_euclidean(dim = NULL, M = NULL)
dim |
Integer dimension (required when |
M |
Optional |
A ou_geom_metric (position-independent).
A position-dependent mass adapting the sampler's local
distance to the curvature of the log-posterior – the remedy for a funnel
(variable curvature). curvature = "softabs" maps the eigenvalues of
the Hessian of through
(Betancourt 2013), flooring nearly flat directions; it needs only the Hessian
(from the target or finite-differenced). curvature = "fisher" uses a
supplied expected-Fisher function (positive-definite by construction).
ou_geom_metric_riemannian( target, curvature = c("softabs", "fisher"), fisher = NULL, dfisher = NULL, alpha = 1e+06, floor = 1e-08, fd_step = 1e-04 )ou_geom_metric_riemannian( target, curvature = c("softabs", "fisher"), fisher = NULL, dfisher = NULL, alpha = 1e+06, floor = 1e-08, fd_step = 1e-04 )
target |
|
curvature |
|
fisher, dfisher
|
For |
alpha |
SoftAbs softening (larger tracks curvature more faithfully). |
floor |
Minimum eigenvalue keeping |
fd_step |
Finite-difference step for the Hessian / metric derivative. |
A ou_geom_metric (position-dependent).
Wrap an unconstrained log-density (and its gradient, optionally its Hessian)
into the target object ou_geom_hmc integrates. Accepts either a
cmdstan fit/model compiled with compile_model_methods = TRUE (whose
$log_prob / $grad_log_prob / $hessian act on the
unconstrained scale) or explicit R closures.
ou_geom_target( object = NULL, log_prob = NULL, grad_log_prob = NULL, dim = NULL, hessian = NULL, param_names = NULL, data = NULL )ou_geom_target( object = NULL, log_prob = NULL, grad_log_prob = NULL, dim = NULL, hessian = NULL, param_names = NULL, data = NULL )
object |
Optional cmdstan fit/model (with methods) or a list carrying
|
log_prob, grad_log_prob
|
R closures of the unconstrained parameter vector
(used when |
dim |
Integer unconstrained dimension (required for closures or a cmdstan model). |
hessian |
Optional Hessian closure (used by the SoftAbs Riemannian metric); finite-differenced from the gradient when absent. |
param_names |
Optional parameter names. |
data |
Stan data list (used when |
An object of class ou_geom_target.
Convenience builder for the level_spec argument of
fit_ou_nested. Each level has four richness switches:
cubic (cubic drift), sv (stochastic volatility),
student_t (Student-t innovations) and hierarchy (cross-sector
partial pooling). The switches act only in the 2-level mode.
ou_level_spec( config = c("canonical", "n1_full_n2_lean", "both_full", "both_lean", "n1_lean", "n1_lean_n2_lean") )ou_level_spec( config = c("canonical", "n1_full_n2_lean", "both_full", "both_lean", "n1_lean", "n1_lean_n2_lean") )
config |
Character. One of |
The comparative experiment of the design contrasts these configurations by parameter recovery and out-of-sample / leave-cluster-out predictive performance, reporting which one the data support (no configuration is claimed uniquely correct):
"canonical" (alias "n1_full_n2_lean")Level 1 full (cubic + SV + Student-t + hierarchy), Level 2 lean (linear Gaussian OU with a hierarchical mean). The default.
"both_full"Both levels full: the latent production price also gets cubic restoring, stochastic volatility and Student-t innovations.
"both_lean"Both levels lean: linear Gaussian OU at each level, hierarchy retained.
"n1_lean" (alias "n1_lean_n2_lean")Level 1 lean, Level 2 lean.
The fifth experiment arm, the single-level model, is obtained directly with
fit_ou_nested(..., n_levels = 1) (it takes no level_spec).
A level_spec list with level1 and level2 entries,
each a list of the four logical switches, ready to pass to
fit_ou_nested.
ou_level_spec("both_full") ou_level_spec("both_lean")ou_level_spec("both_full") ou_level_spec("both_lean")
Returns the complete Stan code for the unified Ornstein-Uhlenbeck model: a
single file (inst/stan/ou_nested.stan) covering the single-level mode
(n_levels = 1, cubic drift, stochastic volatility, Student-t
innovations, non-centered hierarchical priors and a TMG interaction) and the
2-level nested mode (n_levels = 2, market price reverting to a latent
production price with its own G'-driven OU). The code is read from the
canonical file (single source of truth); this function does not embed a
duplicate copy.
ou_nested_stan_code()ou_nested_stan_code()
Character string containing the Stan model code.
code <- ou_nested_stan_code() cat(substr(code, 1, 300))code <- ou_nested_stan_code() cat(substr(code, 1, 300))
Creates a line plot showing the evolution of time-varying beta coefficients for selected sectors.
plot_beta_tmg(results, sectors = NULL)plot_beta_tmg(results, sectors = NULL)
results |
List returned by |
sectors |
Character or integer vector. Sectors to plot. If NULL, plots all sectors. |
A ggplot2 object if ggplot2 is available, otherwise NULL with a base R plot produced as side effect.
# 1. Create mock data (T x S matrix) T_obs <- 50 S <- 3 beta_mat <- matrix(rnorm(T_obs * S), nrow = T_obs, ncol = S) colnames(beta_mat) <- paste0("Sector_", 1:S) # 2. Wrap in list structure expected by function results_mock <- list( beta_tmg = list( beta_point = beta_mat ) ) # 3. Plot plot_beta_tmg(results_mock)# 1. Create mock data (T x S matrix) T_obs <- 50 S <- 3 beta_mat <- matrix(rnorm(T_obs * S), nrow = T_obs, ncol = S) colnames(beta_mat) <- paste0("Sector_", 1:S) # 2. Wrap in list structure expected by function results_mock <- list( beta_tmg = list( beta_point = beta_mat ) ) # 3. Plot plot_beta_tmg(results_mock)
Displays the drift function for selected sectors showing mean reversion with cubic nonlinearity.
plot_drift_curves(results, sectors = NULL)plot_drift_curves(results, sectors = NULL)
results |
List returned by |
sectors |
Integer vector. Sector indices to plot. If NULL, plots all sectors. |
NULL invisibly. Produces a base R plot as side effect.
# 1. Create mock data # z: vector of state deviations z_seq <- seq(-3, 3, length.out = 100) # drift: matrix (rows=z, cols=sectors) drift_mat <- cbind( -0.5 * z_seq - 0.1 * z_seq^3, # Sector 1 -0.8 * z_seq - 0.05 * z_seq^3 # Sector 2 ) # 2. Wrap in list structure results_mock <- list( nonlinear = list( drift_decomp = list( z = z_seq, drift = drift_mat ) ) ) # 3. Plot plot_drift_curves(results_mock)# 1. Create mock data # z: vector of state deviations z_seq <- seq(-3, 3, length.out = 100) # drift: matrix (rows=z, cols=sectors) drift_mat <- cbind( -0.5 * z_seq - 0.1 * z_seq^3, # Sector 1 -0.8 * z_seq - 0.05 * z_seq^3 # Sector 2 ) # 2. Wrap in list structure results_mock <- list( nonlinear = list( drift_decomp = list( z = z_seq, drift = drift_mat ) ) ) # 3. Plot plot_drift_curves(results_mock)
Displays the estimated volatility path for a selected sector.
plot_sv_evolution(results, sector = 1)plot_sv_evolution(results, sector = 1)
results |
List returned by |
sector |
Integer. Sector index to plot. Default 1. |
NULL invisibly. Produces a base R plot as side effect.
# 1. Create mock data (Volatility must be positive) T_obs <- 50 sigma_mat <- matrix(exp(rnorm(T_obs * 2)), nrow = T_obs, ncol = 2) # 2. Wrap in list structure results_mock <- list( sv = list( h_summary = list( sigma_t = sigma_mat ) ) ) # 3. Plot sector 1 plot_sv_evolution(results_mock, sector = 1)# 1. Create mock data (Volatility must be positive) T_obs <- 50 sigma_mat <- matrix(exp(rnorm(T_obs * 2)), nrow = T_obs, ncol = 2) # 2. Wrap in list structure results_mock <- list( sv = list( h_summary = list( sigma_t = sigma_mat ) ) ) # 3. Plot sector 1 plot_sv_evolution(results_mock, sector = 1)
Plot a 2-level nested OU fit
## S3 method for class 'ou_nested_2level' plot(x, type = c("phi", "mu", "sv_p", "separation"), sector = 1, ...)## S3 method for class 'ou_nested_2level' plot(x, type = c("phi", "mu", "sv_p", "separation"), sector = 1, ...)
x |
An |
type |
Character. |
sector |
Integer. Sector index for |
... |
Passed to the underlying base graphics call. |
NULL invisibly; draws as a side effect.
Plot a multiple-imputation nested OU fit
## S3 method for class 'ou_nested_mi' plot(x, type = c("phi", "fmi"), sector = 1, ...)## S3 method for class 'ou_nested_mi' plot(x, type = c("phi", "fmi"), sector = 1, ...)
x |
An |
type |
Character. |
sector |
Integer. Sector index for |
... |
Passed to the underlying base graphics call. |
NULL invisibly; draws as a side effect.
Compact one-screen overview: data dimensions, MCMC health, the time-scale
separation evidence , and the median ranges of the
market and production reversion speeds.
## S3 method for class 'ou_nested_2level' print(x, ...)## S3 method for class 'ou_nested_2level' print(x, ...)
x |
An |
... |
Ignored. |
x, invisibly.
Print a multiple-imputation nested OU fit
## S3 method for class 'ou_nested_mi' print(x, ...)## S3 method for class 'ou_nested_mi' print(x, ...)
x |
An |
... |
Ignored. |
x, invisibly.
Extracts median volatility paths from the SV component.
summarize_sv_sigmas(fit)summarize_sv_sigmas(fit)
fit |
Fitted Stan model object |
List with component:
Matrix (T x S) of median volatilities
Assembles the Level-2 parameter table (production reversion speed, market
speed at , mean intercept, innovation scale, measurement scale),
the separation evidence, the MCMC diagnostics and the out-of-sample metrics.
## S3 method for class 'ou_nested_2level' summary(object, ...) ## S3 method for class 'summary.ou_nested_2level' print(x, ...)## S3 method for class 'ou_nested_2level' summary(object, ...) ## S3 method for class 'summary.ou_nested_2level' print(x, ...)
object |
An |
... |
Ignored. |
x |
A |
An object of class summary.ou_nested_2level.
Summarize a multiple-imputation nested OU fit
## S3 method for class 'ou_nested_mi' summary(object, ...) ## S3 method for class 'summary.ou_nested_mi' print(x, ...)## S3 method for class 'ou_nested_mi' summary(object, ...) ## S3 method for class 'summary.ou_nested_mi' print(x, ...)
object |
An |
... |
Ignored. |
x |
A |
An object of class summary.ou_nested_mi.
Prints diagnostic summaries and hypothesis tests for a fitted model.
validate_ou_fit(fit_res, verbose = TRUE)validate_ou_fit(fit_res, verbose = TRUE)
fit_res |
List returned by |
verbose |
Logical. Print detailed output. Default TRUE. |
Invisibly returns the diagnostics list
# Create a dummy results list that mimics the output of fit_ou_nonlinear_tmg dummy_results <- list( diagnostics = list( rhat = c(alpha = 1.01, beta = 1.00), ess = c(alpha = 400, beta = 350), loo = list(estimates = matrix(c(1, 0.1), ncol=2, dimnames=list("elpd_loo", c("Estimate", "SE")))), oos = list(h1 = list(RMSE = 0.5)) ), factor_ou = list(beta1 = 0.3), nonlinear = list(a3 = -0.5), sv = list(rho_s = 0.2) ) # Run validation on the dummy object validate_ou_fit(dummy_results)# Create a dummy results list that mimics the output of fit_ou_nonlinear_tmg dummy_results <- list( diagnostics = list( rhat = c(alpha = 1.01, beta = 1.00), ess = c(alpha = 400, beta = 350), loo = list(estimates = matrix(c(1, 0.1), ncol=2, dimnames=list("elpd_loo", c("Estimate", "SE")))), oos = list(h1 = list(RMSE = 0.5)) ), factor_ou = list(beta1 = 0.3), nonlinear = list(a3 = -0.5), sv = list(rho_s = 0.2) ) # Run validation on the dummy object validate_ou_fit(dummy_results)
Standardizes a matrix using mean and standard deviation computed from the training period only.
zscore_train(M, T_train, eps = 1e-08)zscore_train(M, T_train, eps = 1e-08)
M |
Numeric matrix of dimensions T x S (time by sectors) |
T_train |
Integer. Number of observations in training period |
eps |
Numeric. Minimum standard deviation threshold to avoid division by zero. Default is 1e-8. |
A list with components:
Standardized matrix of same dimensions as M
Vector of training means for each column
Vector of training standard deviations for each column
M <- matrix(rnorm(100), nrow = 20, ncol = 5) result <- zscore_train(M, T_train = 14) str(result)M <- matrix(rnorm(100), nrow = 20, ncol = 5) result <- zscore_train(M, T_train = 14) str(result)