Introduction

Structural Vector AutoRegression Models are a multivariate time-series model often used in finance and economics. Their popularity is a function of their ability to understand the relationship between variables and the shocks which they may endure (ie transmission mechanisms). The usage of SVAR models was initiated by Chris Sims in 1980 which would later win him a Nobel Prize along with Thomas Sargent in 2011 “for their empirical research on cause and effect in the macroeconomy”.

The model relies on endogenous variables. T

Personally, I love SVAR models while they may not perform as well as simultaneous equation models with simulations they do perform well with cause and effect delineation. SVAR relies on an orthogonality restriction–which essentially means the error vector of the optimal estimator is statistically independent of any possible estimator. The simplest way to frame SVAR models is they are less concerned with the contemporaneous relationship of variables and focused on dynamic relationships between variables associated with shocks in the variables.

Below is a list of several packages required to under take this example of SVAR.

library(urca)
library(vars)
library(mFilter)
library(tseries)
library(TSstudio)
library(forecast)
library(tidyverse)

Data

This SVAR example will explore an often overlooked fundamental relationship in macroeconomics. The dataset used here leverages quarterly data starting in Q1 1958 to Q2 2022 from FRED. Specifically, it looks at the output gap, inflation, and interest rates. The output gap can be characterized as the real economy’s output in comparison to its potential output. When the gap is to the downside of potential output an economy would experience growth concerns. When the gap is to the upside of potential output this can be viewed as “running hot” and associated with inflation concerns. FRED calculates it as a seasonally adjusted annual percentage change in billions of chained dollars (2012) by

100 x ((Real GDP - Real Potential GDP)/Real Potential GDP)

Inflation is gauged here using CPI ex Food and Energy as a the percentage change from a year ago. This metric is reported monthly so to match up easier to output gap data a quarterly average is used. Interest rates are also a quarterly average of the reverse repo rate (RRP).

This data was selected to investigate and perhaps tell a story. With inflation currently dominating headlines and central banker worries I wanted to focus on this. In theory inflation would be triggered when the output gap is positive indicating an economy running hot. Central banks, in this case the Fed, would then in turn looks to increase interest rates to reduce the output gap and thusly cool inflation. While examining the efficacy of this theory is a goal, so to is investigating the lags present in this potentially causal chain and the scope of responses.

Note the conversion of data to time-series format. While this is needed to properly run most of the functions it also allows for use of ts_plot which gives interactive plots of the data.

#Loading the Dataset
macro <- read_csv("fredgraph-3.csv")
## Rows: 258 Columns: 4
## ── Column specification ────────────────────────────────────────────────────────
## Delimiter: ","
## dbl  (3): Output Gap, CPI, RRP
## date (1): DATE
## 
## ℹ Use `spec()` to retrieve the full column specification for this data.
## ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
head(macro)
## # A tibble: 6 × 4
##   DATE       `Output Gap`   CPI   RRP
##   <date>            <dbl> <dbl> <dbl>
## 1 1958-01-01      -3.37    2.80  1.86
## 2 1958-04-01      -3.59    2.43  0.94
## 3 1958-07-01      -2.25    2.07  1.32
## 4 1958-10-01      -0.894   1.82  2.16
## 5 1959-01-01       0.0631  1.81  2.57
## 6 1959-04-01       1.34    1.92  3.08
#Creating thee Time Series Objectives
y <- ts(macro$`Output Gap`, start = c(1958,1,1), frequency = 4)
pi <- ts(macro$CPI, start = c(1958,1,1), frequency = 4)
r <- ts(macro$RRP, start = c(1958,1,1), frequency = 4)
#Time Series Plots
ts_plot(y, title = "Output Gap", Xtitle = "Time", Ytitle = "Output Gap")
ts_plot(pi, title = "Inflation Rate", Xtitle = "Time", Ytitle = "Inflation Rate")
ts_plot(r, title = "Overnight Reverse Repurchase Rate", Xtitle = "Time", Ytitle = "RRP")

Setting Restrictions

What is essentially a 3x3 identity matrix is created where amat can be thought of as matrix A. Since it is intuitively known the relationships in question do not operate contemporaneously we will restrict this by leaving zeroes in the upper triangle of the matrix. Likewise NA’s are inputted to the bottom triangle to allow us to fill with findings from the model soon to be run.

#Setting the Restrictions
amat <- diag(3)
amat[2,1] <- NA
amat[3,1] <- NA
amat[3,2] <- NA
amat
##      [,1] [,2] [,3]
## [1,]    1    0    0
## [2,]   NA    1    0
## [3,]   NA   NA    1

Lag Order Selection

As with most time-series analysis lag analysis is crucial. Luckily R can tell us the best lag selection for our model. After combining our time-series data, called sv, we can use the VARselect() function from the vars package. The call here uses OLS with arguments (data, maximum lags to consider, type of deterministic regressors, inclusion of seasonal dummy variables, and inclusion of exogenous variables). Another great feature of the vars package is by calling lagselect$selection we can get R’s recommendations for lag selection based on each criteria. Here the recommendation is 5.

I think this is a hugely important. Analysis is indicating the use of a lag of 5 cycles which in this case is 5 quarters or 1.25 years! This is central to understanding monetary policy and macroeconomics. Economies are not F-15 fighter jets, they are Boeing 737’s. Their moves are not quick and agile instead they are slow but ultimately powerful.

sv <- cbind(y, pi, r)
colnames(sv) <- cbind("OutputGap", "Inflation", "RRP")
lagselect <- VARselect(sv, lag.max = 8, type = "both")
lagselect$selection
## AIC(n)  HQ(n)  SC(n) FPE(n) 
##      5      5      5      5
lagselect$criteria
##                1          2          3           4           5           6
## AIC(n) -1.800862 -2.1669065 -2.2141782 -2.34845690 -2.60657321 -2.58684061
## HQ(n)  -1.715825 -2.0308472 -2.0270966 -2.11035302 -2.31744708 -2.24669222
## SC(n)  -1.589575 -1.8288463 -1.7493454 -1.75685147 -1.88819519 -1.74168999
## FPE(n)  0.165159  0.1145389  0.1092619  0.09555006  0.07383351  0.07533483
##                  7           8
## AIC(n) -2.56530116 -2.56683679
## HQ(n)  -2.17413050 -2.12464388
## SC(n)  -1.59337794 -1.46814098
## FPE(n)  0.07701648  0.07695208

Model Construction

Initially a simple VAR model is constructed using the VAR() function with arguments (data, selected lags, inclusion of seasonal variables, inclusion of exogenous variables, type of deterministic regressor). This model is then used as an input using the SVAR() function with arguments (varest object from VAR(), Matrix A, Matrix B, whether hessian matrix, and estimation methods).

Notice the output for SVARMod1. The restriction matrix now has the NA’s filled by coefficients from the model while leaving restrictions unchanged in the upper triangle.

Model1 <- VAR(sv, p = 5, season = NULL, exog = NULL, type = "const")
SVARMod1 <- SVAR(Model1, Amat = amat, Bmat = NULL, hessian = TRUE, estmethod =
                   c("scoring", "direct"))
SVARMod1
## 
## SVAR Estimation Results:
## ======================== 
## 
## 
## Estimated A matrix:
##           OutputGap Inflation RRP
## OutputGap   1.00000    0.0000   0
## Inflation  -0.06745    1.0000   0
## RRP        -0.18256   -0.5187   1

Impulse Response Functions

Now we get to see how shocks or impulse in a variable effect other variables which is really what we are here for. Using the irf() function with arguments (data, impulse, response) we investigate how an increase in the impulse variable effects the response variable. These are then plotted along with confidence intervals.

The first plot of a shock in output gap to output gap shows an as expected immediate increase in the output gap–redundant but intuitive and best way to iniate our example. However, notice the steady decline in the output gap in the coming quarters after the shock. This could be interpreted as the precipitous correction to equilibrium from tightening of supply conditions from the overheating economy. After about 3 years the output gap would normalize from the initial shock or impulse.

The second plot looks at a shock in output gap on inflation. As expected the overproducing economy creates inflationary pressures with inflation increasing rather dramatically in the first year before moderating slightly. However, the impacts seen here are fairly long lasting. This was the most interesting finding for me personally. Unfortunately, I can not figure how to get the IRF plot to extend further on the time horizon. Nonetheless, the stickiness of inflation is quite present.

The third and final IRF plot looks at the response of interest rates on an impulse from inflation. This is a very interesting and somewhat confusing result. While there is an as expected initial increase it is followed by a sharp decent before beginning its ascent. This may be capturing what I like to call the “Burns Effect”. Named so after Arthur Burns the fickle Fed Chairman who was easily pressured by his political ally Nixon. Throughout the inflationary period of the 1970’s Burns had quite the propensity for easing rates to support the economy for political reasons at even the slightest inkling of moderating inflation or threats to growth. In general though there is an increase in interest rates as a response to inflation shocks.

SVARog <- irf(SVARMod1, impulse = "OutputGap", response = "OutputGap")
SVARinf <- irf(SVARMod1, impulse = "OutputGap", response = "Inflation")
SVARrrp <- irf(SVARMod1, impulse = "Inflation", response = "RRP")
SVARog
## 
## Impulse response coefficients
## $OutputGap
##       OutputGap
##  [1,] 1.0000000
##  [2,] 0.9078788
##  [3,] 0.8943333
##  [4,] 0.8150217
##  [5,] 0.7124944
##  [6,] 0.6019095
##  [7,] 0.5059486
##  [8,] 0.4108952
##  [9,] 0.3227123
## [10,] 0.2504767
## [11,] 0.1783563
## 
## 
## Lower Band, CI= 0.95 
## $OutputGap
##          OutputGap
##  [1,]  1.000000000
##  [2,]  0.788691151
##  [3,]  0.730426484
##  [4,]  0.611530942
##  [5,]  0.474717386
##  [6,]  0.325088615
##  [7,]  0.215423989
##  [8,]  0.102669502
##  [9,] -0.006581557
## [10,] -0.088622622
## [11,] -0.155726929
## 
## 
## Upper Band, CI= 0.95 
## $OutputGap
##       OutputGap
##  [1,] 1.0000000
##  [2,] 0.9859588
##  [3,] 1.0260769
##  [4,] 1.0300136
##  [5,] 0.9260088
##  [6,] 0.8070772
##  [7,] 0.6875799
##  [8,] 0.5884244
##  [9,] 0.4804997
## [10,] 0.4022815
## [11,] 0.3287160
plot(SVARog)

SVARinf
## 
## Impulse response coefficients
## $OutputGap
##        Inflation
##  [1,] 0.06744802
##  [2,] 0.10129541
##  [3,] 0.15494766
##  [4,] 0.28981676
##  [5,] 0.27055659
##  [6,] 0.30801574
##  [7,] 0.35180180
##  [8,] 0.34173538
##  [9,] 0.37523271
## [10,] 0.38314118
## [11,] 0.36801406
## 
## 
## Lower Band, CI= 0.95 
## $OutputGap
##          Inflation
##  [1,] -0.003427810
##  [2,] -0.010265120
##  [3,]  0.003896355
##  [4,]  0.077857354
##  [5,]  0.033836167
##  [6,]  0.084534596
##  [7,]  0.136795473
##  [8,]  0.129755303
##  [9,]  0.137932497
## [10,]  0.141636820
## [11,]  0.117461866
## 
## 
## Upper Band, CI= 0.95 
## $OutputGap
##       Inflation
##  [1,] 0.1020820
##  [2,] 0.1820196
##  [3,] 0.2663493
##  [4,] 0.4395127
##  [5,] 0.4499602
##  [6,] 0.4988045
##  [7,] 0.5537278
##  [8,] 0.5533894
##  [9,] 0.5987714
## [10,] 0.6119518
## [11,] 0.6114276
plot(SVARinf)

SVARrrp
## 
## Impulse response coefficients
## $Inflation
##               RRP
##  [1,]  0.51867755
##  [2,]  0.02451912
##  [3,] -0.02747991
##  [4,]  0.18609992
##  [5,]  0.03833932
##  [6,]  0.19200334
##  [7,]  0.34421985
##  [8,]  0.38002745
##  [9,]  0.62698895
## [10,]  0.74928290
## [11,]  0.79807826
## 
## 
## Lower Band, CI= 0.95 
## $Inflation
##               RRP
##  [1,]  0.15410601
##  [2,] -0.49040703
##  [3,] -0.54753313
##  [4,] -0.36678225
##  [5,] -0.54665908
##  [6,] -0.49431353
##  [7,] -0.36659882
##  [8,] -0.39598823
##  [9,] -0.20621408
## [10,] -0.06421670
## [11,] -0.01925732
## 
## 
## Upper Band, CI= 0.95 
## $Inflation
##             RRP
##  [1,] 1.0124618
##  [2,] 0.7126711
##  [3,] 0.6171467
##  [4,] 0.8741359
##  [5,] 0.7779577
##  [6,] 0.8933026
##  [7,] 1.0834497
##  [8,] 1.1336416
##  [9,] 1.4051208
## [10,] 1.5543737
## [11,] 1.6224063
plot(SVARrrp)

Forecast Error Variance Decomposition

Unlike other models which use predict() SVAR models rely on forecast error variance decomposition as their forecasting medium using fevd(). The forecast is not a fan chart showing the predicted path of the variable like in other models, rather it shows the expected effect of other variables on the variable being charted going out on the time horizon. For example the output gap becomes increasingly responsive to inflation especially after two quarters. This makes sense as an overproducing, running hot economy eventually runs into high prices and also higher interest rates with the top sliver increasing ever so slightly as well. Looking at the FEVD for interest rates is quite interesting as well. Inflation looks to have a quick impact on rates while the output gap effect is much slower.

SVARfevd <- fevd(SVARMod1, n.ahead = 10)
SVARfevd
## $OutputGap
##       OutputGap   Inflation        RRP
##  [1,] 1.0000000 0.000000000 0.00000000
##  [2,] 0.9682939 0.009059151 0.02264691
##  [3,] 0.8814411 0.104197363 0.01436149
##  [4,] 0.8055637 0.183843835 0.01059250
##  [5,] 0.7359414 0.252193979 0.01186464
##  [6,] 0.6823954 0.301145040 0.01645957
##  [7,] 0.6525956 0.324651211 0.02275322
##  [8,] 0.6347861 0.337608194 0.02760573
##  [9,] 0.6217731 0.346422992 0.03180387
## [10,] 0.6125350 0.352489658 0.03497531
## 
## $Inflation
##         OutputGap Inflation        RRP
##  [1,] 0.004528634 0.9954714 0.00000000
##  [2,] 0.004706439 0.9835079 0.01178563
##  [3,] 0.006634159 0.9728997 0.02046612
##  [4,] 0.012569260 0.9523915 0.03503923
##  [5,] 0.015391567 0.9362612 0.04834725
##  [6,] 0.019511097 0.9277340 0.05275495
##  [7,] 0.024597088 0.9198632 0.05553974
##  [8,] 0.029269693 0.9141232 0.05660706
##  [9,] 0.034736505 0.9100170 0.05524649
## [10,] 0.039737518 0.9070763 0.05318615
## 
## $RRP
##        OutputGap  Inflation       RRP
##  [1,] 0.03595311 0.20437248 0.7596744
##  [2,] 0.07741588 0.08438574 0.8381984
##  [3,] 0.12382999 0.05946266 0.8167074
##  [4,] 0.16343811 0.05258150 0.7839804
##  [5,] 0.19159704 0.04294336 0.7654596
##  [6,] 0.22436258 0.03969554 0.7359419
##  [7,] 0.24689816 0.04570982 0.7073920
##  [8,] 0.26272982 0.05404622 0.6832240
##  [9,] 0.27157354 0.07989006 0.6485364
## [10,] 0.27282321 0.11205314 0.6151237
plot(SVARfevd)

Findings and Comparison

Everything matched intuition except the IFR for interest rates where I floated the idea of the Burns Effect. Naturally curious I reran everything but only included data from 1982 onward, four years after Burns and well into Volcker’s stand on inflation. The differences were noticeable as seen below. The initial drop in rates is no longer present. Notice too it takes longer for rates to begin to increase in response to the inflation shock. Using the 1958-2022 data rates were raised after the third quarter while using the 1982 to 2022 data the rate response isn’t seen until the seventh quarter. This could be due to the Fed understanding structural shifts in inflationary pressures after 1982 including globalization and technological advancements.

Another interesting takeaway from this analysis was the output gap shocks effect on inflation. Using the full dataset show inflation to be rather sticky. With the impulse response remaining elevated for sometime. However, the using the subset of data post-Volcker inflation’s response moderates quicker. It would be interesting to see if this behavior in inflation is more a function of changes in the aforementioned inflationary structure or an increase in central bank credibility from Volcker. Furthermore, it will be interesting to feed data from coming quarters. The reopening of the economy from the pandemic saw the output gap have its most dramatic increase on record. It should come as little surprise then inflation too spiked and rates spiked as well. The spike in rates and inflation is not really captured in the data given its quarterly formatting and only using data through Q2. It will be interesting to run this same code with more data in coming years.