I’m trying to roughly replicate Harvey, Trimbur & van Dijk (2005). See pages 3-4 for their model. Right now I’m only concerned with 1st order cycles.
As it stands right now, autocorrelations are really high - when I look at trace plots, several parameters (especially lambda and rho) look like they’ve just not really moving. Combining this with large Rhat values makes me think that it just hasn’t converged by the time it gets to sampling.
What can I do to improve convergence? It seems like I had a a similar problem when I was working on a local level model that had problems similar to “Neal’s funnel” that made me specify the ratios for the variances instead of the variances themselves. Do I need to find a way to reparametarize lambda and rho? I’m not sure how I would, but based on their particularly low sample sizes and high Rhats, they seem to be causing my fitting issues.
Stan code (ts_trend_cycle.stan)
data {
int<lower=0> N;
real y[N];
# Priors on variances
real <lower=0> sigma_epsilon_prior_mean;
real <lower=0> sigma_epsilon_prior_std;
real <lower=0> signal_noise_prior_mean;
real <lower=0> signal_noise_prior_std;
real <lower=0> cycle_trend_prior_mean;
real <lower=0> cycle_trend_prior_std;
# Priors on cycle process
real rho_alpha;
real rho_beta;
real <lower=0> lambda_alpha;
real <lower=0> lambda_beta;
}
parameters {
# Variances
real <lower=0> sigma_epsilon;
real <lower=0> ratio_signal_noise;
real <lower=0> ratio_cycle_trend;
# Cycle process
real <lower=0, upper=1> lambda;
real <lower=0, upper=1> rho;
# Time series of trend and cycle
vector[N] beta;
matrix[N, 2] psi;
# Initial value of trend (diffuse / no likelihood)
real mu_0_offset;
}
transformed parameters{
real <lower=0> sigma_zeta;
real <lower=0> sigma_kappa;
matrix[2,2] T_psi;
vector[N] mu;
real mu_0;
# Cycle transition equation
T_psi[1,1] = rho * cos(lambda);
T_psi[2,1] = -rho * sin(lambda);
T_psi[1,2] = rho * sin(lambda);
T_psi[2,2] = rho * cos(lambda);
# Defined ratio_signal_noise = sigma_eta / sigma_epsilon
sigma_zeta = sigma_epsilon * ratio_signal_noise;
# Defined ratio_cycle_trend = sigma_kappa / sigma_eta
sigma_kappa = sigma_zeta * ratio_cycle_trend;
# Trend defined from slope
# Fix scale of mu_0:
mu_0 = mu_0_offset + y[1];
mu = cumulative_sum(beta) + mu_0;
}
model {
# Priors
sigma_epsilon ~ normal(sigma_epsilon_prior_mean, sigma_epsilon_prior_std);
ratio_signal_noise ~ normal(signal_noise_prior_mean, signal_noise_prior_std);
ratio_cycle_trend ~ normal(cycle_trend_prior_mean, cycle_trend_prior_std);
rho ~ uniform(rho_alpha, rho_beta);
lambda ~ beta(lambda_alpha, lambda_beta);
# Build trend
# Beta nonstationary, ignore any "error" in the first period.
beta[2:N] ~ normal(beta[1:N-1], sigma_zeta);
# Build cycle
# First period psi variance depends on inverted system since we have a max
# eignevalue less than one here.
psi[1] ~ normal(0, (1 / (1-rho^2)) * sigma_kappa);
# Project psi forward each period
for (i in 2:N) {
psi[i] ~ normal(T_psi * psi[i-1]', sigma_kappa);
}
# Likelihood of data
y ~ normal(mu + psi[,1], sigma_epsilon);
}
R code
# Load libraries
#####################################################################
library(rstan)
library(shinystan)
library(quantmod)
rstan_options(auto_write = TRUE)
options(mc.cores = parallel::detectCores())
# Get data from FRED
#####################################################################
getSymbols("GDPC1", src="FRED")
dat = list(y = as.vector(coredata(log(GDPC1))), N = dim(GDP)[1],
signal_noise_prior_mean = 100, signal_noise_prior_std = 200,
sigma_epsilon_prior_mean = 1, sigma_epsilon_prior_std = 2,
cycle_trend_prior_mean = 0.5, cycle_trend_prior_std = 1,
rho_alpha = 0, rho_beta = 1, lambda_alpha = 4, lambda_beta = 9);
# Fit
#####################################################################
fit = stan(file="ts_trend_cycle.stan", data=dat,
control=list(max_treedepth=15, adapt_delta=0.95),
iter=2000, chains=4)
pairs(fit, pars=list("sigma_zeta", "sigma_epsilon", "sigma_kappa",
"lambda", "rho", "mu_0_offset"))
launch_shinystan(drop_parameters(as.shinystan(fit), pars=c("beta", "mu", "psi")))