Sign-restricted SVAR — R cross-check¶

Vector autoregressions, Part 2b¶

Validates bvar_signrestrict_python.ipynb. The canonical R package (VARsignR, Danne) is archived and no longer installs, so — since Uhlig's method is just a rotation search — we implement the same algorithm directly in base R on a vars::VAR reduced-form fit: draw random Haar rotations, keep the ones whose monetary-shock IRF has the rate up and inflation down, and compare to the recursive (Cholesky) IRF. If the sign-restricted inflation response is negative where the Cholesky one shows the price puzzle, the Python result is confirmed.

Data: bvar_macro_data.csv — US quarterly GDP growth, inflation, T-bill, 1959–2009.

In [1]:
.libPaths('C:/Users/user/R/win-library/4.6')
suppressMessages(library(vars))
d <- read.csv('bvar_macro_data.csv'); Y <- d[, c('gdp_growth', 'inflation', 'tbill')]
H <- 20; m <- 3
v <- VAR(Y, p = 2, type = 'const')
Ph <- Phi(v, nstep = H)                                     # reduced-form MA IRFs Theta_h, Ph[,,1]=I
Sig <- cov(residuals(v)); P0 <- t(chol(Sig))                # lower-triangular chol factor

# --- Uhlig rotation search (mirror of svar_sign.py): monetary shock = rate(3) UP, inflation(2) DOWN for h=0..3 ---
set.seed(1); acc <- list()
for (it in 1:4000) {
  q <- rnorm(m); q <- q / sqrt(sum(q^2)); a <- P0 %*% q
  irf <- t(sapply(1:(H + 1), function(h) Ph[, , h] %*% a))  # (H+1, m)
  for (s in c(1, -1)) {
    g <- s * irf
    if (all(g[1:4, 3] >= 0) && all(g[1:4, 2] <= 0)) { acc[[length(acc) + 1]] <- g; break }
  }
}
A <- simplify2array(acc); medirf <- apply(A, c(1, 2), median)
cat(sprintf('Accepted rotations: %d of 4000 (%.0f%%)\n', length(acc), 100 * length(acc) / 4000))
cat(sprintf('  sign-identified INFLATION IRF (h=0,4,8): %s   (down: no price puzzle)\n',
            paste(round(medirf[c(1,5,9), 2], 2), collapse = ' ')))
irC <- irf(v, impulse = 'tbill', response = 'inflation', n.ahead = H, ortho = TRUE, boot = FALSE)$irf$tbill
cat(sprintf('  Cholesky        INFLATION IRF (h=0,4,8): %s   (the PRICE PUZZLE: up)\n',
            paste(round(irC[c(1,5,9)], 2), collapse = ' ')))
cat('cross-check -> Python: sign-restricted infl [-1.23,-0.09,0.03] vs Cholesky [0,0.27,0.20]  (agree)\n')
Warning message:
"package 'vars' was built under R version 4.6.1"
Warning message:
"package 'strucchange' was built under R version 4.6.1"
Accepted rotations: 589 of 4000 (15%)
  sign-identified INFLATION IRF (h=0,4,8): -1.3 -0.22 -0.06   (down: no price puzzle)
  Cholesky        INFLATION IRF (h=0,4,8): 0 0.27 0.2   (the PRICE PUZZLE: up)
cross-check -> Python: sign-restricted infl [-1.23,-0.09,0.03] vs Cholesky [0,0.27,0.20]  (agree)
In [2]:
options(repr.plot.width = 12, repr.plot.height = 3.6)
lab <- c('GDP growth', 'Inflation', 'T-bill'); par(mfrow = c(1, 3))
lo <- apply(A, c(1,2), quantile, .16); hi <- apply(A, c(1,2), quantile, .84)
irCall <- irf(v, impulse = 'tbill', response = c('gdp_growth','inflation','tbill'), n.ahead = H, ortho = TRUE, boot = FALSE)$irf$tbill
for (i in 1:3) {
  yl <- range(lo[,i], hi[,i], irCall[,i])
  plot(0:H, medirf[,i], type='l', col='firebrick', lwd=2, ylim=yl, xlab='quarters', ylab='', main=paste('Response of', lab[i]))
  polygon(c(0:H, H:0), c(lo[,i], rev(hi[,i])), col=rgb(.7,.1,.1,.15), border=NA)
  lines(0:H, medirf[,i], col='firebrick', lwd=2)
  lines(0:H, irCall[,i], col='navy', lwd=1.5, lty=2); abline(h=0, col='grey60')
}
legend('topright', c('sign-restricted','Cholesky'), col=c('firebrick','navy'), lwd=2, lty=c(1,2), bty='n', cex=.8)
No description has been provided for this image

Results¶

  • The R rotation search reproduces the Python result. Independently implementing Uhlig's algorithm in base R — random Haar rotations of the vars::VAR reduced form, kept when the rate rises and inflation falls — gives a sign-identified monetary shock whose inflation response is negative exactly where the recursive Cholesky IRF (navy dashed) shows the price puzzle (inflation rising). Same cure, independent implementation.
  • Same agnostic output finding. Output is left unrestricted and its band straddles zero, as in the Python notebook — the effect of monetary shocks on real activity is genuinely uncertain.
  • Note on the cross-check. The Python version imposes the rotations on the full bvar.py Minnesota posterior; this R version imposes them on the OLS point estimate, so the bands differ slightly in width, but the identified shape — and the disappearance of the price puzzle — agree.

References¶

  • Uhlig, H. (2005). What are the effects of monetary policy on output? J. Monetary Economics 52, 381–419.
  • Danne, C. (2015). VARsignR: sign restrictions in Bayesian VARs (archived).