VAR Models
The engine for Dynamic Multivariate Discovery. Vector Autoregression (VAR) audits the internal pulse of a cluster of time series, revealing how multiple variables influence each other's future in a unified temporal system.
What is it?
VAR Models analyzes sequences of data points ordered chronologically over time to extract patterns, model trends, and make forecasts.
The engine for Dynamic Multivariate Discovery. Vector Autoregression (VAR) audits the internal pulse of a cluster of time series, revealing how multiple variables influence each other's future in a unified temporal system.
Goals & Indications
- Multivariate Pulse Audit: Decipher the 'Cross-Variable' signals where every series acts as both a predictor and an outcome.
- Dynamic Feedback Mapping: Identify the recursive loops where Change in X leads to Change in Y, which then feeds back into X.
- Systemic Forecasting: Construct a unified mathematical engine that projects the entire 'Outcome Vector' into the future.
Core Idea Diagram
Claims tested
How it works
- Ensure all multivariate time series are stationary (difference if unit root exists).
- Select lag order p and set up system of linear autoregressive equations.
- Estimate system parameters simultaneously using OLS equation-by-equation.
- Analyze dynamic interactions using Impulse Response Functions and FEVD.
Assumptions
Important Note
VAR models capture dynamic interdependencies among multiple time series. Each variable is modeled as linear function of its own lags plus lags of all other variables in system. Key focus: (1) Granger causality tests: does X help predict Y controlling for Y's past? (2) Impulse Response Functions (IRFs): dynamic effect of shock in one variable on all variables over time. (3) Forecast Error Variance Decomposition (FEVD): proportion of forecast variance in each variable explained by shocks to other variables. (4) Multivariate forecasting: generate joint forecasts accounting for cross-series correlations. VAR(p) uses p lags: y_t = c + A₁y_{t-1} + A₂y_{t-2} + ... + A_py_{t-p} + ε_t where y_t is K×1 vector, A_i are K×K coefficient matrices. Key distinction from univariate ARIMA: VAR explicitly models cross-variable dynamics. Key distinction from structural models: VAR imposes minimal restrictions (reduced-form). Important: Granger causality ≠ true causation (only predictive relationship in temporal sense). All variables must be stationary or use cointegration framework (VECM).
Worked Example
| Equation | Coefficient | Estimate | t-stat | p-value |
|---|---|---|---|---|
| Y1 ~ Y1(t-1) | φ₁₁ | 0.62 | 5.14 | <0.001 |
| Y1 ~ Y2(t-1) | φ₁₂ | 0.28 | 2.31 | 0.021 |
Vector Autoregressive (VAR) Laboratory
VAR models model multi-variable feedback loops. Change coefficients and observe transition eigenvalue stability. If eigenvalues ≥ 1.0, the system becomes explosive.
Hypotheses
Pragmatic null and alternative hypotheses defined in mathematical notation.
H₀: No Granger causality between variables (past values of X don't help predict Y beyond Y's own past values)
Hₐ: Variables exhibit Granger causality (past values of some variables improve prediction of others beyond own history)
VAR models capture dynamic interdependencies among multiple time series. Each variable is modeled as linear function of its own lags plus lags of all other variables in system. Key focus: (1) Granger causality tests: does X help predict Y controlling for Y's past? (2) Impulse Response Functions (IRFs): dynamic effect of shock in one variable on all variables over time. (3) Forecast Error Variance Decomposition (FEVD): proportion of forecast variance in each variable explained by shocks to other variables. (4) Multivariate forecasting: generate joint forecasts accounting for cross-series correlations. VAR(p) uses p lags: y_t = c + A₁y_{t-1} + A₂y_{t-2} + ... + A_py_{t-p} + ε_t where y_t is K×1 vector, A_i are K×K coefficient matrices. Key distinction from univariate ARIMA: VAR explicitly models cross-variable dynamics. Key distinction from structural models: VAR imposes minimal restrictions (reduced-form). Important: Granger causality ≠ true causation (only predictive relationship in temporal sense). All variables must be stationary or use cointegration framework (VECM).
Assumptions
The core mathematical criteria needed to ensure that statistical testing remains unbiased and valid.
Diagnostics
Checking residual plots and indices to examine model deviations and ensure standard error integrity.
- Multivariate Ljung-Box / Portmanteau test for systemic residual autocorrelation.
- Stability audit of the Companion Matrix (eigenvalues must be < 1.0).
- Lag-length selection audit using AIC / BIC / FPE consensus.
- Individual unit root tests (ADF) for every series in the vector.
- Residual covariance matrix check for cross-variable signal bleed.
- Impulse Response Function (IRF) bootstrapping to audit shock-response sensitivity.
- FEVD (Forecast Error Variance Decomposition) stability audit.
- ARCH-LM test on residuals to detect multivariate volatility clustering.
- Granger Causality strikes to verify the directional flow of the system.
- Structural Break audit (Chow Test) to ensure system parameters are constant over time.
Applied Minds
Review concrete study examples, data layout guidelines, and copy executable syntax scripts.
GDP, Inflation, and Interest Rates
3-variable VAR system modeling the dynamic relationships among GDP growth, inflation rate, and interest rate using n=120 quarterly observations (30 years). Demonstrates complete VAR workflow: data generation with realistic interdependencies, stationarity testing for all variables, lag order selection via multiple information criteria (AIC, BIC, HQ), VAR estimation using vars package in R and statsmodels in Python, Granger causality tests to identify predictive relationships, impulse response function analysis with confidence bands to trace shock propagation, forecast error variance decomposition to quantify variable importance, stability diagnostics via companion matrix eigenvalues, multivariate forecasting with prediction intervals, and rigorous out-of-sample validation using rolling windows. The example illustrates bidirectional causality, persistent shock effects, and the superiority of VAR over univariate models for correlated economic indicators.
# ============================================================================
# VAR MODELS: Macroeconomic Forecasting (GDP, Inflation, Interest Rate)
# ============================================================================
# Demonstrates: VAR estimation, Granger causality, IRFs, FEVD, forecasting
# Data: 120 quarters (30 years) of GDP growth, inflation, interest rate
# Model: VAR(p) with p selected via information criteria
# ============================================================================
# Load required packages
library(vars) # VAR estimation, IRF, FEVD, Granger causality
library(urca) # Unit root tests (ADF, KPSS)
library(tseries) # Additional time series tests
library(ggplot2) # Visualization
library(gridExtra) # Multiple plots
library(MASS) # Multivariate normal
set.seed(123)
# ============================================================================
# 1. DATA GENERATION: Three interdependent time series
# ============================================================================
cat("=", rep("=", 78), "\n", sep="")
cat("VAR MODELS: Macroeconomic System(GDP, Inflation, Interest Rate)\n")
cat("=", rep("=", 78), "\n\n", sep="")
# Generate 120 quarterly observations (30 years)
n <- 120
# True VAR(2) parameters for data generation
# Variables: [GDP_growth, Inflation, Interest_rate]
K <- 3 # Number of variables
p_true <- 2 # True lag order
# Coefficient matrices (K x K for each lag)
# A1: lag-1 coefficients
A1 <- matrix(c(
0.5, 0.2, -0.1, # GDP equation: own lag=0.5, inflation=0.2, rate=-0.1
0.3, 0.4, 0.1, # Inflation equation: GDP=0.3, own lag=0.4, rate=0.1
0.4, 0.5, 0.3 # Interest rate equation: GDP=0.4, inflation=0.5, own=0.3
), nrow=K, ncol=K, byrow=TRUE)
# A2: lag-2 coefficients (smaller effects)
A2 <- matrix(c(
0.2, 0.1, 0.0,
0.1, 0.2, 0.0,
0.1, 0.2, 0.2
), nrow=K, ncol=K, byrow=TRUE)
# Constant term
c_vec <- c(2.0, 2.5, 4.0) # Mean levels: ~2% GDP growth, ~2.5% inflation, ~4% rate
# Innovation covariance matrix (contemporaneous correlations)
Sigma <- matrix(c(
1.00, 0.30, 0.20,
0.30, 0.50, 0.40,
0.20, 0.40, 0.60
), nrow=K, ncol=K)
# Initialize series
Y <- matrix(0, nrow=n, ncol=K)
colnames(Y) <- c("GDP_growth", "Inflation", "Interest_rate")
# Set initial values
Y[1,] <- c(2.0, 2.5, 4.0)
Y[2,] <- c(2.2, 2.6, 4.1)
# Generate VAR(2) process
for (t in 3:n) {
epsilon_t <- mvrnorm(n=1, mu=rep(0, K), Sigma=Sigma)
Y[t,] <- c_vec + A1 %*% Y[t-1,] + A2 %*% Y[t-2,] + epsilon_t
}
# Convert to time series object
Y_ts <- ts(Y, start=c(1994, 1), frequency=4)
cat("Data generated: n =", n, "quarterly observations\n")
cat("Variables: K =", K, "(GDP growth, Inflation, Interest rate)\n")
cat("True model: VAR(2) with interdependencies\n\n")
# Summary statistics
cat("Summary statistics:\n")
cat("-----------------------------------\n")
for (i in 1:K) {
cat(sprintf("%-15s: Mean=%.3f, SD=%.3f, Range=[%.2f, %.2f]\n",
colnames(Y)[i], mean(Y[,i]), sd(Y[,i]), min(Y[,i]), max(Y[,i])))
}
cat("\n")
# ============================================================================
# 2. EXPLORATORY DATA ANALYSIS
# ============================================================================
cat("=", rep("=", 78), "\n", sep="")
cat("EXPLORATORY ANALYSIS\n")
cat("=", rep("=", 78), "\n\n", sep="")
# Time series plots
par(mfrow=c(3,1), mar=c(3,4,2,1))
for (i in 1:K) {
plot(Y_ts[,i], main=paste(colnames(Y)[i], "over Time"),
ylab=colnames(Y)[i], xlab="", col="steelblue", lwd=2)
abline(h=mean(Y[,i]), col="red", lty=2)
}
par(mfrow=c(1,1))
# Scatterplot matrix (contemporaneous relationships)
cat("Scatterplot matrix(contemporaneous correlations):\n")
pairs(Y, main="Pairwise Relationships",
col="steelblue", pch=19, cex=0.5)
# Correlation matrix
cat("\nCorrelation matrix:\n")
cor_matrix <- cor(Y)
print(round(cor_matrix, 3))
cat("\n")
# ============================================================================
# 3. STATIONARITY TESTING
# ============================================================================
cat("=", rep("=", 78), "\n", sep="")
cat("STATIONARITY TESTS\n")
cat("=", rep("=", 78), "\n\n", sep="")
cat("Testing each variable for stationarity(required for VAR)\n")
cat("-----------------------------------\n\n")
for (i in 1:K) {
var_name <- colnames(Y)[i]
cat("Variable:", var_name, "\n")
cat("-----------------------------------\n")
# ADF test
adf_result <- ur.df(Y[,i], type="trend", lags=4, selectlags="AIC")
adf_summary <- summary(adf_result)
test_stat <- adf_summary@teststat[1]
crit_5pct <- adf_summary@cval[1,2]
cat("ADF test(H0: unit root):")
cat("\n Test statistic:", round(test_stat, 4))
cat("\n 5% critical value:", round(crit_5pct, 4))
if (test_stat < crit_5pct) {
cat("\n Conclusion: STATIONARY(reject H0)\n")
} else {
cat("\n Conclusion: NON-STATIONARY(fail to reject H0)\n")
}
# KPSS test
kpss_result <- ur.kpss(Y[,i], type="tau", lags="short")
kpss_summary <- summary(kpss_result)
kpss_stat <- kpss_summary@teststat
kpss_crit_5pct <- kpss_summary@cval[2]
cat("\nKPSS test(H0: stationarity):")
cat("\n Test statistic:", round(kpss_stat, 4))
cat("\n 5% critical value:", round(kpss_crit_5pct, 4))
if (kpss_stat < kpss_crit_5pct) {
cat("\n Conclusion: STATIONARY(fail to reject H0)\n")
} else {
cat("\n Conclusion: NON-STATIONARY(reject H0)\n")
}
cat("\n")
}
cat("All variables appear stationary → proceed with VAR estimation\n")
cat("(If any non-stationary: would need differencing or VECM)\n\n")
# ============================================================================
# 4. LAG ORDER SELECTION
# ============================================================================
cat("=", rep("=", 78), "\n", sep="")
cat("LAG ORDER SELECTION\n")
cat("=", rep("=", 78), "\n\n", sep="")
# Test lag orders from 1 to 8
lag_select <- VARselect(Y_ts, lag.max=8, type="const")
cat("Information criteria for different lag orders:\n")
cat("-----------------------------------\n")
print(lag_select$selection)
cat("\n")
cat("Criteria table:\n")
print(lag_select$criteria)
cat("\n")
# Extract recommended lags
aic_lag <- lag_select$selection["AIC(n)"]
bic_lag <- lag_select$selection["SC(n)"] # SC = Schwarz (BIC)
hq_lag <- lag_select$selection["HQ(n)"]
cat("Recommended lag orders:\n")
cat(" AIC:", aic_lag, "(tends to select larger models)\n")
cat(" BIC:", bic_lag, "(penalizes complexity more, parsimonious)\n")
cat(" HQ:", hq_lag, "(intermediate)\n\n")
# Use BIC recommendation (most parsimonious)
p_selected <- bic_lag
cat("Selected lag order: p =", p_selected, "(using BIC)\n\n")
# ============================================================================
# 5. VAR MODEL ESTIMATION
# ============================================================================
cat("=", rep("=", 78), "\n", sep="")
cat("VAR MODEL ESTIMATION\n")
cat("=", rep("=", 78), "\n\n", sep="")
# Estimate VAR(p) with selected lag order
var_model <- VAR(Y_ts, p=p_selected, type="const")
cat("VAR(", p_selected, ") Model Summary:\n", sep="")
cat("-----------------------------------\n")
print(summary(var_model))
# Extract key information
cat("\n\nModel diagnostics:\n")
cat(" Number of observations:", nobs(var_model), "\n")
cat(" Number of parameters per equation:", K*p_selected + 1, "\n")
cat(" Total parameters:", K*(K*p_selected + 1), "\n")
cat(" Degrees of freedom:", nobs(var_model) - K*p_selected - 1, "\n\n")
# Log-likelihood and information criteria
cat("Information criteria:\n")
cat(" AIC:", round(AIC(var_model), 2), "\n")
cat(" BIC:", round(BIC(var_model), 2), "\n\n")
# ============================================================================
# 6. RESIDUAL DIAGNOSTICS
# ============================================================================
cat("=", rep("=", 78), "\n", sep="")
cat("RESIDUAL DIAGNOSTICS\n")
cat("=", rep("=", 78), "\n\n", sep="")
# Portmanteau test for autocorrelation
cat("Portmanteau test(multivariate Ljung-Box):\n")
cat("H0: No autocorrelation up to lag h\n")
cat("-----------------------------------\n")
portmanteau <- serial.test(var_model, lags.pt=16, type="PT.asymptotic")
print(portmanteau)
if (portmanteau$serial$p.value > 0.05) {
cat("\nConclusion: Residuals are WHITE NOISE(no autocorrelation) - GOOD\n")
cat("p-value =", round(portmanteau$serial$p.value, 4), "> 0.05\n\n")
} else {
cat("\nConclusion: Residuals show autocorrelation - consider increasing lag order\n")
cat("p-value =", round(portmanteau$serial$p.value, 4), "< 0.05\n\n")
}
# Test for multivariate normality
cat("\nMultivariate normality test(Jarque-Bera):\n")
cat("H0: Residuals are multivariate normal\n")
cat("-----------------------------------\n")
normality <- normality.test(var_model, multivariate.only=TRUE)
print(normality)
if (normality$jb.mul$JB$p.value > 0.05) {
cat("\nConclusion: Residuals are approximately NORMAL - GOOD\n")
cat("p-value =", round(normality$jb.mul$JB$p.value, 4), "> 0.05\n\n")
} else {
cat("\nConclusion: Residuals deviate from normality\n")
cat("Impact: Point forecasts still valid; use bootstrap for IRF confidence bands\n")
cat("p-value =", round(normality$jb.mul$JB$p.value, 4), "< 0.05\n\n")
}
# Plot residuals
cat("\nGenerating residual plots...\n")
par(mfrow=c(3,2), mar=c(4,4,2,1))
resids <- residuals(var_model)
for (i in 1:K) {
# Residuals over time
plot(resids[,i], type="l", main=paste(colnames(Y)[i], "- Residuals"),
ylab="Residual", xlab="Time", col="darkblue")
abline(h=0, col="red", lty=2)
# Q-Q plot
qqnorm(resids[,i], main=paste(colnames(Y)[i], "- Q-Q Plot"), pch=19, cex=0.5)
qqline(resids[,i], col="red", lwd=2)
}
par(mfrow=c(1,1))
cat("\n")
# ============================================================================
# 7. STABILITY CHECK
# ============================================================================
cat("=", rep("=", 78), "\n", sep="")
cat("STABILITY CHECK\n")
cat("=", rep("=", 78), "\n\n", sep="")
cat("Checking VAR stability via companion matrix eigenvalues\n")
cat("Stability requires all eigenvalues inside unit circle(modulus < 1)\n")
cat("-----------------------------------\n\n")
# Stability check
stability <- stability(var_model, type="OLS-CUSUM")
# Get roots (inverse of eigenvalues)
roots_vals <- roots(var_model)
cat("Inverse roots of characteristic polynomial:\n")
print(round(roots_vals, 4))
if (all(abs(roots_vals) < 1)) {
cat("\nConclusion: VAR is STABLE(all roots inside unit circle)\n")
cat("Maximum root modulus:", round(max(abs(roots_vals)), 4), "< 1\n\n")
} else {
cat("\nWARNING: VAR is UNSTABLE(some roots outside unit circle)\n")
cat("Maximum root modulus:", round(max(abs(roots_vals)), 4), ">= 1\n\n")
}
# Plot roots
plot(var_model, names="GDP_growth")
# ============================================================================
# 8. GRANGER CAUSALITY TESTS
# ============================================================================
cat("=", rep("=", 78), "\n", sep="")
cat("GRANGER CAUSALITY TESTS\n")
cat("=", rep("=", 78), "\n\n", sep="")
cat("Testing whether variable X Granger-causes variable Y\n")
cat("H0: X does not Granger-cause Y(lags of X don't help predict Y)\n")
cat("Ha: X Granger-causes Y(lags of X improve prediction of Y)\n")
cat("-----------------------------------\n\n")
# Test all pairwise Granger causalities
causality_results <- list()
var_names <- colnames(Y)
for (i in 1:K) {
for (j in 1:K) {
if (i != j) {
cause_var <- var_names[j]
effect_var <- var_names[i]
# Granger causality test
gc_test <- causality(var_model, cause=cause_var)
cat("Does", cause_var, "Granger-cause", effect_var, "?\n")
cat(" F-statistic:", round(gc_test$Granger$statistic, 4), "\n")
cat(" p-value:", round(gc_test$Granger$p.value, 4), "\n")
if (gc_test$Granger$p.value < 0.05) {
cat(" Conclusion: YES - Reject H0(Granger causality detected)\n")
causality_results[[paste(cause_var, "->", effect_var)]] <- "YES"
} else {
cat(" Conclusion: NO - Fail to reject H0(no Granger causality)\n")
causality_results[[paste(cause_var, "->", effect_var)]] <- "NO"
}
cat("\n")
}
}
}
cat("\nGranger causality summary:\n")
cat("-----------------------------------\n")
for (name in names(causality_results)) {
cat(sprintf(" %-35s: %s\n", name, causality_results[[name]]))
}
cat("\n")
# ============================================================================
# 9. IMPULSE RESPONSE FUNCTIONS (IRFs)
# ============================================================================
cat("=", rep("=", 78), "\n", sep="")
cat("IMPULSE RESPONSE FUNCTIONS\n")
cat("=", rep("=", 78), "\n\n", sep="")
cat("Computing IRFs: dynamic effect of one-SD shock on all variables\n")
cat("Using orthogonalized IRF(Cholesky decomposition)\n")
cat("Ordering: GDP_growth, Inflation, Interest_rate\n")
cat("95% confidence bands via bootstrap(100 runs)\n")
cat("-----------------------------------\n\n")
# Compute orthogonalized IRFs with bootstrap CI
irf_result <- irf(var_model, impulse=var_names, response=var_names,
n.ahead=20, ortho=TRUE, boot=TRUE, runs=100, ci=0.95)
cat("IRF computed for 20 periods ahead\n\n")
# Plot IRFs
cat("Generating IRF plots...\n")
plot(irf_result, main="Impulse Response Functions(Orthogonalized)")
# Detailed interpretation example
cat("\nExample interpretation:\n")
cat(" - GDP shock → Inflation: Shows how inflation responds over time to GDP surprise\n")
cat(" - Inflation shock → Interest_rate: Central bank response to inflation\n")
cat(" - Interest_rate shock → GDP: Monetary policy transmission mechanism\n")
cat(" - Persistence: How long effects last(return to baseline)\n")
cat(" - Confidence bands: Statistical uncertainty around IRF path\n\n")
# ============================================================================
# 10. FORECAST ERROR VARIANCE DECOMPOSITION (FEVD)
# ============================================================================
cat("=", rep("=", 78), "\n", sep="")
cat("FORECAST ERROR VARIANCE DECOMPOSITION\n")
cat("=", rep("=", 78), "\n\n", sep="")
cat("Decomposing forecast error variance by shock source\n")
cat("Shows relative importance of each variable's shocks\n")
cat("-----------------------------------\n\n")
# Compute FEVD
fevd_result <- fevd(var_model, n.ahead=20)
cat("FEVD Summary:\n\n")
print(fevd_result)
# Plot FEVD
cat("\nGenerating FEVD plots...\n")
plot(fevd_result, main="Forecast Error Variance Decomposition")
cat("\nInterpretation:\n")
cat(" - Each panel shows variance decomposition for one variable\n")
cat(" - Colors represent shocks from different variables\n")
cat(" - Sum to 100% at each horizon\n")
cat(" - Short horizon: own shocks dominant(diagonal elements)\n")
cat(" - Long horizon: cross-variable effects emerge(spillovers)\n\n")
# ============================================================================
# 11. FORECASTING
# ============================================================================
cat("=", rep("=", 78), "\n", sep="")
cat("FORECASTING\n")
cat("=", rep("=", 78), "\n\n", sep="")
# Forecast next 12 quarters (3 years)
h_forecast <- 12
cat("Forecasting horizon: h =", h_forecast, "quarters(3 years)\n\n")
# Generate forecasts
forecast_result <- predict(var_model, n.ahead=h_forecast, ci=0.95)
cat("Forecast summary(first 8 quarters):\n")
cat("-----------------------------------\n\n")
for (i in 1:K) {
cat("Variable:", var_names[i], "\n")
forecast_df <- data.frame(
Forecast = forecast_result$fcst[[i]][1:8, "fcst"],
Lower_95 = forecast_result$fcst[[i]][1:8, "lower"],
Upper_95 = forecast_result$fcst[[i]][1:8, "upper"]
)
print(round(forecast_df, 3))
cat("\n")
}
# Plot forecasts
cat("Generating forecast plots...\n")
par(mfrow=c(3,1), mar=c(4,4,3,1))
for (i in 1:K) {
# Observed data
plot(Y_ts[,i], xlim=c(start(Y_ts)[1], end(Y_ts)[1] + h_forecast/4),
ylim=range(c(Y_ts[,i], forecast_result$fcst[[i]][,"fcst"],
forecast_result$fcst[[i]][,"lower"],
forecast_result$fcst[[i]][,"upper"])),
main=paste(var_names[i], "- Forecast"),
ylab=var_names[i], xlab="Time", col="steelblue", lwd=2)
# Forecast path
forecast_time <- seq(end(Y_ts)[1] + 0.25, by=0.25, length.out=h_forecast)
lines(ts(forecast_result$fcst[[i]][,"fcst"],
start=forecast_time[1], frequency=4),
col="darkred", lwd=2, lty=2)
# Confidence interval
polygon(c(forecast_time, rev(forecast_time)),
c(forecast_result$fcst[[i]][,"lower"], rev(forecast_result$fcst[[i]][,"upper"])),
col=rgb(1,0,0,0.2), border=NA)
legend("topleft", legend=c("Observed", "Forecast", "95% CI"),
col=c("steelblue", "darkred", rgb(1,0,0,0.2)),
lwd=c(2,2,10), lty=c(1,2,1), bty="n")
}
par(mfrow=c(1,1))
cat("\nNote: Prediction intervals widen with horizon(increasing uncertainty)\n\n")
# ============================================================================
# 12. OUT-OF-SAMPLE VALIDATION
# ============================================================================
cat("=", rep("=", 78), "\n", sep="")
cat("OUT-OF-SAMPLE VALIDATION\n")
cat("=", rep("=", 78), "\n\n", sep="")
# Rolling window forecast evaluation
train_size <- 100
test_size <- n - train_size
cat("Rolling window validation:\n")
cat(" Training size:", train_size, "observations\n")
cat(" Test size:", test_size, "observations\n")
cat(" Forecast horizon: 1-step-ahead\n")
cat("-----------------------------------\n\n")
# Initialize forecast storage
forecasts_oos <- matrix(NA, nrow=test_size, ncol=K)
actuals_oos <- matrix(NA, nrow=test_size, ncol=K)
# Rolling forecasts
for (i in 1:test_size) {
# Training data
train_end <- train_size + i - 1
Y_train <- Y_ts[1:train_end, ]
# Fit VAR on training data
var_train <- VAR(Y_train, p=p_selected, type="const")
# 1-step-ahead forecast
forecast_1step <- predict(var_train, n.ahead=1)
# Store forecasts and actuals
for (j in 1:K) {
forecasts_oos[i, j] <- forecast_1step$fcst[[j]][1, "fcst"]
actuals_oos[i, j] <- Y_ts[train_end + 1, j]
}
}
# Calculate forecast errors
errors_oos <- actuals_oos - forecasts_oos
# Compute accuracy metrics
cat("Forecast accuracy metrics(1-step-ahead):\n")
cat("-----------------------------------\n")
for (i in 1:K) {
rmse <- sqrt(mean(errors_oos[, i]^2))
mae <- mean(abs(errors_oos[, i]))
mape <- mean(abs(errors_oos[, i] / actuals_oos[, i])) * 100
cat(sprintf("\n%s:\n", var_names[i]))
cat(sprintf(" RMSE: %.4f\n", rmse))
cat(sprintf(" MAE: %.4f\n", mae))
cat(sprintf(" MAPE: %.2f%%\n", mape))
}
cat("\n")
# Plot forecast errors
par(mfrow=c(3,1), mar=c(4,4,3,1))
for (i in 1:K) {
plot(errors_oos[,i], type="l", main=paste(var_names[i], "- Forecast Errors"),
ylab="Error", xlab="Forecast Period", col="darkgreen", lwd=1.5)
abline(h=0, col="red", lty=2, lwd=2)
abline(h=c(-2*sd(errors_oos[,i]), 2*sd(errors_oos[,i])),
col="orange", lty=2)
}
par(mfrow=c(1,1))
cat("\n")
# ============================================================================
# 13. FINAL SUMMARY
# ============================================================================
cat("=", rep("=", 78), "\n", sep="")
cat("FINAL SUMMARY\n")
cat("=", rep("=", 78), "\n\n", sep="")
cat("Data: n =", n, "quarterly observations\n")
cat("Variables: K =", K, "(GDP growth, Inflation, Interest rate)\n")
cat("Final model: VAR(", p_selected, ")\n\n", sep="")
cat("Model Diagnostics:\n")
cat("-----------------------------------\n")
cat(" Portmanteau test p-value:", round(portmanteau$serial$p.value, 4))
if (portmanteau$serial$p.value > 0.05) cat(" ✓ (white noise residuals)")
cat("\n")
cat(" Normality test p-value:", round(normality$jb.mul$JB$p.value, 4))
if (normality$jb.mul$JB$p.value > 0.05) cat(" ✓ (normal residuals)")
cat("\n")
cat(" Stability: All eigenvalues < 1:", all(abs(roots_vals) < 1))
if (all(abs(roots_vals) < 1)) cat(" ✓ (stable system)")
cat("\n\n")
cat("Granger Causality:\n")
cat("-----------------------------------\n")
for (name in names(causality_results)) {
cat(sprintf(" %-35s: %s\n", name, causality_results[[name]]))
}
cat("\n")
cat("Key Findings:\n")
cat("-----------------------------------\n")
cat(" ✓ All variables stationary(no unit roots)\n")
cat(" ✓ VAR captures dynamic interdependencies\n")
cat(" ✓ Granger causality tests reveal predictive relationships\n")
cat(" ✓ IRFs show shock propagation through system\n")
cat(" ✓ FEVD quantifies relative importance of shocks\n")
cat(" ✓ Out-of-sample validation confirms forecast accuracy\n")
cat(" ✓ Multivariate approach superior to univariate models\n\n")
cat("=", rep("=", 78), "\n", sep="")
cat("VAR ANALYSIS COMPLETE\n")
cat("=", rep("=", 78), "\n\n", sep="")
# ============================================================================
# END OF VAR EXAMPLE
# ============================================================================Alternatives
Structured fallback pathways for choosing alternative tests when normality or slopes requirements fail.
- Lasso-VAR — Apply L1 penalties to select only the most robust cross-variable lags.
- Bayesian VAR — Use informative priors to protect the system from exploding coefficients.
- MGARCH Strike — Model the time-varying covariance matrix to audit risk-bleed across the vector.
- Threshold VAR (TVAR) — Switch models if the system pulse changes after a critical clinical event.
Post-hoc
Group mean comparisons and correction controls (e.g. Tukey HSD, Bonferroni) to protect against Family-Wise Error Rates.
- Vary lag order using AIC/BIC/HQ criteria
- Compare recursive vs structural identification
- Assess stability (eigenvalues inside unit circle)
- Bootstrap confidence intervals for IRF
- Test for cointegration if variables are I(1)
VAR models multivariate time series. Post-hoc involves impulse responses and variance decomposition.
Effect Size
Understanding effect sizes (e.g., Cohen's d, Partial Eta-Squared) and clinical impact benchmarks.
Sample Size
Guidelines for minimum sample requirements and power analysis parameters.
The 'Matrix Buffer' Mandate: A minimum of 100-200 timepoints is required. VAR models are parameter-heavy; a system with 3 variables and 4 lags requires estimating 39+ coefficients, demanding massive temporal depth.
| Effect Size | Parameters | Required n |
|---|---|---|
| Small Effect | Low Coupling (f²=.02) | n ≈ 1000 |
| Medium Effect | Moderate Coupling (f²=.15) | n ≈ 250 |
| Large Effect | Strong Coupling (f²=.35) | n ≈ 100 |
The 'Stability Strike': If your VAR system is near the 'Unit Root' boundary (non-stationary), the power math becomes invalid. Always verify stationarity for every series in the vector before trusting the N.
Reporting
How to compile statistical results into publication prose matching APA and journal style guides.
Manuscript Lab
Copy standard summary tables and forensic reporting grids to outline analysis details.
| Equation | Predictor | Estimate | SE | t | p |
|---|---|---|---|---|---|
| Equation: GDP | GDP.L1 | 0.85 | 0.08 | 10.6 | < .001 |
| Equation: GDP | Interest.L1 | -0.12 | 0.04 | -3.0 | .003 |
| Equation: Interest | GDP.L1 | 0.45 | 0.15 | 3.0 | .003 |
| Equation: Interest | Interest.L1 | 0.62 | 0.10 | 6.2 | < .001 |
The Immediate Past. Represents the value of the variable from the previous time period.
The 'Ecosystem' Logic. In VAR, every variable gets its own regression equation, treating it as a dependent variable predicted by everyone else.
Command Center
Syntax libraries and function parameters for executing calculations in stats packages.
# 1. Select Optimal Lag
lag_select <- vars::VARselect(df_combined, lag.max = 10)
# 2. Fit VAR Model
model <- vars::VAR(df_combined, p = lag_select$selection[1])
summary(model)
# 3. Forecast System Dynamics
plot(predict(model, n.ahead = 12))The VAR model is a 'Black Box' until you run Impulse Response Functions (IRF). Use IRF to visualize how a single 'shock' to one variable (e.g., Oil Price hike) ripples through the other variables over the next 12 months.
# Execute Impulse Response Analysis
plot(vars::irf(model, impulse = 'x', response = 'y'))Common Mistakes
Analytical caveats and corrections to maintain modeling integrity.
References
Scholarly lineage and citation keys grounding the statistical framework.