Module 11 Lab: Structural VARs

From Connected Equations to Identification · Module 11 release · 2026-11-12

How to use this lab

Module 11 release · 2026-11-12. Everything runs as shipped, offline. Your work is to predict, calculate, modify, and explain; no AI is required. Record predictions in your private LEARNING_LOG.md, on paper, or in an R comment before running the corresponding calculation.

With an AI tutor, the prediction pause takes one turn; “just run it” releases it. The tutor asks once per section or experiment. Printed subprompts are optional self-study opportunities, not fresh gates before every chunk.

The algebra has a destination: understand what a fitted VAR coefficient measures. We follow the deck’s sequence from connected equations through both scalar substitutions, then matrices, output, identification, and timing. Sections 2–8 are the main path. Section 9 is optional and outside the assessable surface. The notes’ Core is the factual source and boundary.

This module uses one Concept → Math → computation/interpretation break → Application arc. Deterministic calculations are the spine. One fixed sample from the notes illustrates what regression estimates; there is no Monte Carlo or external-data requirement. The economic application asks you to defend timing assumptions. Neither lecture widget is required to use this lab.

1. Setup

Use a fresh R session and run chunks in order from this folder. In RStudio, choose Session → Set Working Directory → To Source File Location. The calculations use base R; rendering uses installed knitr, rmarkdown, and Quarto. No packages or data are downloaded. Render this file by path, not the folder or the reference notes.

2. Give both outcomes an equation

The pond separates a disturbance’s source from the connections carrying it and the movement you observe. A policy-rate movement could respond to inflation news, reflect an unexpected policy disturbance, or combine the two. An observed movement alone does not distinguish these accounts.

The ARDL already allowed current xx in the yy equation. Give xx its own equation, then allow current yy to enter it:

yt=αy+byxxt+ϕyyyt−1+ϕyxxt−1+ηy,t,xt=αx+bxyyt+ϕxyyt−1+ϕxxxt−1+ηx,t.\begin{aligned} y_t&=\alpha_y+b_{yx}x_t+\phi_{yy}y_{t-1}+\phi_{yx}x_{t-1}+\eta_{y,t},\\ x_t&=\alpha_x+b_{xy}y_t+\phi_{xy}y_{t-1}+\phi_{xx}x_{t-1}+\eta_{x,t}. \end{aligned}

On the mixing board, intercepts and own memory remain, both cross-lags are available, and both outcomes now have an equation. Explicit MA terms and a deterministic trend are off. VAR(1) means one lag of the vector.

The first subscript names the receiving equation; the second names the entering variable. Draw four dated nodes, yt−1,xt−1,yt,xty_{t-1},x_{t-1},y_t,x_t, and put the terms on their arrows. The two current arrows form a directed cycle, so the simultaneous diagram is not a DAG. A lag arrow crosses dates.

Predict: if both current arrows are present, can we find current yy by (a) ignoring the xx equation, (b) accounting for that equation too, or (c) treating current xx as yesterday’s xx? Explain what each time subscript means before doing algebra.

The structural shocks have mean zero and positive constant variances, are independent across dates and of the past, and are uncorrelated with each other. Calling an equation structural does not establish these assumptions. Our supplied simulation uses independent standard normals.

Modify on paper: remove the current y→xy\to x arrow and rewrite the second equation. Identify the lagged feedback arrows that remain.

So what: giving the predictor an equation changes the joint determination problem, even before any new software is used.

3. Follow one coefficient through both substitutions

Our destination is two equations with only known lags on the right, plus innovations. For the forward calculation, structural parameters, the past, and a particular pair of shock realizations are supplied inputs. The analyst does not observe those structural shocks in real data.

Predict: if the direct structural xx-lag coefficient ϕyx\phi_{yx} is zero, must the reduced coefficient be (a) zero, (b) nonzero, or (c) dependent on the other connections? Follow it through the calculation before running R.

Work through the following lines on paper. At each line, name the operation and the term it changes. These are the full substitutions from Core §11.3.

First solve for current y

Start with the yy equation and replace its current xtx_t by the entire right-hand side of the xx equation:

yt=αy+byx(αx+bxyyt+ϕxyyt−1+ϕxxxt−1+ηx,t)+ϕyyyt−1+ϕyxxt−1+ηy,t. \begin{aligned} y_t={}&\alpha_y +b_{yx}\left(\alpha_x+b_{xy}y_t +\phi_{xy}y_{t-1}+\phi_{xx}x_{t-1}+\eta_{x,t}\right)\\ &+\phi_{yy}y_{t-1}+\phi_{yx}x_{t-1}+\eta_{y,t}. \end{aligned}

Distribute byxb_{yx} across every term inside the parentheses:

yt=αy+byxαx+byxbxyyt+byxϕxyyt−1+byxϕxxxt−1+byxηx,t+ϕyyyt−1+ϕyxxt−1+ηy,t. \begin{aligned} y_t={}&\alpha_y+b_{yx}\alpha_x+b_{yx}b_{xy}y_t\\ &+b_{yx}\phi_{xy}y_{t-1}+b_{yx}\phi_{xx}x_{t-1} +b_{yx}\eta_{x,t}\\ &+\phi_{yy}y_{t-1}+\phi_{yx}x_{t-1}+\eta_{y,t}. \end{aligned}

The substitution has brought yty_t back onto its own right-hand side. Move that term to the left, and collect the intercept, the yy lag, the xx lag, and the two disturbances:

(1−byxbxy)yt=αy+byxαx+(ϕyy+byxϕxy)yt−1+(ϕyx+byxϕxx)xt−1+ηy,t+byxηx,t. \begin{aligned} (1-b_{yx}b_{xy})y_t ={}&\alpha_y+b_{yx}\alpha_x\\ &+(\phi_{yy}+b_{yx}\phi_{xy})y_{t-1}\\ &+(\phi_{yx}+b_{yx}\phi_{xx})x_{t-1}\\ &+\eta_{y,t}+b_{yx}\eta_{x,t}. \end{aligned}

Define D=1−byxbxyD=1-b_{yx}b_{xy}. Provided D≠0D\neq0, divide every term by DD:

yt=αy+byxαxD+ϕyy+byxϕxyDyt−1+ϕyx+byxϕxxDxt−1+ηy,t+byxηx,tD. \boxed{ \begin{aligned} y_t={}&\frac{\alpha_y+b_{yx}\alpha_x}{D} +\frac{\phi_{yy}+b_{yx}\phi_{xy}}{D}y_{t-1}\\ &+\frac{\phi_{yx}+b_{yx}\phi_{xx}}{D}x_{t-1} +\frac{\eta_{y,t}+b_{yx}\eta_{x,t}}{D}. \end{aligned}}

There is no current xtx_t left on the right. Its influence has been incorporated into the intercept, both lag coefficients, and the innovation.

Pause and interpret. Point to the two pieces in the numerator of the xt−1x_{t-1} coefficient. Which came directly from the original yy equation, and which arrived when we substituted for current xtx_t?

Now solve for current x

Before repeating the calculation, predict whether the denominator should change when we solve for the other outcome. Watch for the reciprocal current-coefficient product as you work.

Start from the other equation and substitute for current yty_t:

xt=αx+bxy(αy+byxxt+ϕyyyt−1+ϕyxxt−1+ηy,t)+ϕxyyt−1+ϕxxxt−1+ηx,t. \begin{aligned} x_t={}&\alpha_x +b_{xy}\left(\alpha_y+b_{yx}x_t +\phi_{yy}y_{t-1}+\phi_{yx}x_{t-1}+\eta_{y,t}\right)\\ &+\phi_{xy}y_{t-1}+\phi_{xx}x_{t-1}+\eta_{x,t}. \end{aligned}

Distribute the contemporaneous coefficient:

xt=αx+bxyαy+bxybyxxt+bxyϕyyyt−1+bxyϕyxxt−1+bxyηy,t+ϕxyyt−1+ϕxxxt−1+ηx,t. \begin{aligned} x_t={}&\alpha_x+b_{xy}\alpha_y+b_{xy}b_{yx}x_t\\ &+b_{xy}\phi_{yy}y_{t-1}+b_{xy}\phi_{yx}x_{t-1} +b_{xy}\eta_{y,t}\\ &+\phi_{xy}y_{t-1}+\phi_{xx}x_{t-1}+\eta_{x,t}. \end{aligned}

Move the current xtx_t term to the left and collect:

Dxt=αx+bxyαy+(ϕxy+bxyϕyy)yt−1+(ϕxx+bxyϕyx)xt−1+ηx,t+bxyηy,t. \begin{aligned} Dx_t ={}&\alpha_x+b_{xy}\alpha_y\\ &+(\phi_{xy}+b_{xy}\phi_{yy})y_{t-1}\\ &+(\phi_{xx}+b_{xy}\phi_{yx})x_{t-1}\\ &+\eta_{x,t}+b_{xy}\eta_{y,t}. \end{aligned}

Dividing by the same DD gives

xt=αx+bxyαyD+ϕxy+bxyϕyyDyt−1+ϕxx+bxyϕyxDxt−1+ηx,t+bxyηy,tD. \boxed{ \begin{aligned} x_t={}&\frac{\alpha_x+b_{xy}\alpha_y}{D} +\frac{\phi_{xy}+b_{xy}\phi_{yy}}{D}y_{t-1}\\ &+\frac{\phi_{xx}+b_{xy}\phi_{yx}}{D}x_{t-1} +\frac{\eta_{x,t}+b_{xy}\eta_{y,t}}{D}. \end{aligned}}

The denominator is not an arbitrary adjustment. It arose because each substitution returned part of the variable to its own equation. If D=0D=0, these steps cannot produce a unique current solution for arbitrary lags and disturbances. We exclude that singular case.

Give the transformed quantities names

The reduced form expresses current outcomes using lagged information and innovations:

yt=ay0+ayyyt−1+ayxxt−1+ϵy,t,xt=ax0+axyyt−1+axxxt−1+ϵx,t. \begin{aligned} y_t&=a_{y0}+a_{yy}y_{t-1}+a_{yx}x_{t-1}+\epsilon_{y,t},\\ x_t&=a_{x0}+a_{xy}y_{t-1}+a_{xx}x_{t-1}+\epsilon_{x,t}. \end{aligned}

These equations are a shorter way to write exactly what we just derived. Every reduced-form object has a structural expression:

Reduced-form object Structural expression
ay0a_{y0} (αy+byxαx)/D(\alpha_y+b_{yx}\alpha_x)/D
ax0a_{x0} (αx+bxyαy)/D(\alpha_x+b_{xy}\alpha_y)/D
ayya_{yy} (ϕyy+byxϕxy)/D(\phi_{yy}+b_{yx}\phi_{xy})/D
ayxa_{yx} (ϕyx+byxϕxx)/D(\phi_{yx}+b_{yx}\phi_{xx})/D
axya_{xy} (ϕxy+bxyϕyy)/D(\phi_{xy}+b_{xy}\phi_{yy})/D
axxa_{xx} (ϕxx+bxyϕyx)/D(\phi_{xx}+b_{xy}\phi_{yx})/D
ϵy,t\epsilon_{y,t} (ηy,t+byxηx,t)/D(\eta_{y,t}+b_{yx}\eta_{x,t})/D
ϵx,t\epsilon_{x,t} (ηx,t+bxyηy,t)/D(\eta_{x,t}+b_{xy}\eta_{y,t})/D

The reduced form preserves the joint solution of the proposed structural system. It has not deleted the contemporaneous connections. It has folded them into new coefficients and mixed disturbances.

Compute the fixed example before using matrices

Use the notes’ one generating model throughout:

yt=.2+.5xt+.3yt−1+0xt−1+ηy,t,xt=.1+.25yt+.1yt−1+.4xt−1+ηx,t.\begin{aligned} y_t&=.2+.5x_t+.3y_{t-1}+0x_{t-1}+\eta_{y,t},\\ x_t&=.1+.25y_t+.1y_{t-1}+.4x_{t-1}+\eta_{x,t}. \end{aligned}

alpha_y <- 0.2; alpha_x <- 0.1
b_yx <- 0.5; b_xy <- 0.25
phi_yy <- 0.3; phi_yx <- 0; phi_xy <- 0.1; phi_xx <- 0.4
D <- 1 - b_yx * b_xy
A0_scalar <- c(y = (alpha_y + b_yx * alpha_x) / D,
               x = (alpha_x + b_xy * alpha_y) / D)
A1_scalar <- rbind(
  c((phi_yy + b_yx * phi_xy) / D, (phi_yx + b_yx * phi_xx) / D),
  c((phi_xy + b_xy * phi_yy) / D, (phi_xx + b_xy * phi_yx) / D))
dimnames(A1_scalar) <- list(c("y equation", "x equation"), c("y lag", "x lag"))
knitr::kable(cbind(Intercept = A0_scalar, A1_scalar), digits = 5,
             caption = "Population reduced coefficients from scalar substitution")
Population reduced coefficients from scalar substitution
Intercept y lag x lag
y 0.28571 0.4 0.22857
x 0.17143 0.2 0.45714

Here D=7/8D=7/8 and ayx=8/35a_{yx}=8/35. Trace yesterday’s xx into today’s xx and then today’s yy. The denominator accounts for the reciprocal current connection. Reconcile your prediction: the structural zero and reduced nonzero refer to different coefficients.

Modify: on paper set byx=0b_{yx}=0 in the expression for ayxa_{yx}. State which route disappears. To check in R, calculate (phi_yx + 0 * phi_xx) / (1 - 0 * b_xy) without overwriting the shipped parameters.

So what: the coefficient a regression can estimate has already combined structural connections before we draw a single observation.

4. Record the same calculation with matrices

Predict: which operation produces the coefficient on yesterday’s xx in today’s yy equation: (a) row 1 of 𝐁−1\mathbf B^{-1} times column 2 of 𝚪1\mathbf\Gamma_1, (b) row 2 times column 1, or (c) multiplying matching entries only? State the row and column meanings.

Stack everything in order (y,x)(y,x). The vectors 𝐘t\mathbf Y_t, 𝚪0\boldsymbol\Gamma_0 and 𝛈t\boldsymbol\eta_t have two rows and one column. Move the current cross-variable terms to the left:

yt−byxxt=αy+ϕyyyt−1+ϕyxxt−1+ηy,t,−bxyyt+xt=αx+ϕxyyt−1+ϕxxxt−1+ηx,t.\begin{aligned} y_t-b_{yx}x_t&=\alpha_y+\phi_{yy}y_{t-1}+\phi_{yx}x_{t-1}+\eta_{y,t},\\ -b_{xy}y_t+x_t&=\alpha_x+\phi_{xy}y_{t-1}+\phi_{xx}x_{t-1}+\eta_{x,t}. \end{aligned}

𝐁=(1−byx−bxy1),𝚪1=(ϕyyϕyxϕxyϕxx).\mathbf B=\begin{pmatrix}1&-b_{yx}\\-b_{xy}&1\end{pmatrix},\qquad \mathbf\Gamma_1=\begin{pmatrix}\phi_{yy}&\phi_{yx}\\\phi_{xy}&\phi_{xx}\end{pmatrix}.

One row times one column means multiply corresponding entries and add. Thus 𝐁𝐘t\mathbf B\mathbf Y_t reproduces the two left sides and 𝚪1𝐘t−1\mathbf\Gamma_1\mathbf Y_{t-1} reproduces the two lag expressions. The structural vector equation is

𝐁𝐘t=𝚪0+𝚪1𝐘t−1+𝛈t.\mathbf B\mathbf Y_t=\boldsymbol\Gamma_0+\mathbf\Gamma_1\mathbf Y_{t-1} +\boldsymbol\eta_t.

The identity 𝐈\mathbf I leaves a vector unchanged. The inverse undoes 𝐁\mathbf B:

𝐁−1=1D(1byxbxy1),𝐁−1𝐁=𝐈.\mathbf B^{-1}=\frac1D\begin{pmatrix}1&b_{yx}\\b_{xy}&1\end{pmatrix}, \qquad \mathbf B^{-1}\mathbf B=\mathbf I.

Multiply every term on the left by the inverse. This gives 𝐀0=𝐁−1𝚪0\mathbf A_0=\mathbf B^{-1}\boldsymbol\Gamma_0, 𝐀1=𝐁−1𝚪1\mathbf A_1=\mathbf B^{-1}\mathbf\Gamma_1, and 𝛜t=𝐁−1𝛈t\boldsymbol\epsilon_t=\mathbf B^{-1}\boldsymbol\eta_t. The course reserves 𝐀0\mathbf A_0 for the reduced intercept.

B <- matrix(c(1, -0.5, -0.25, 1), nrow = 2, byrow = TRUE)
Gamma0 <- c(y = 0.2, x = 0.1)
Gamma1 <- matrix(c(0.3, 0, 0.1, 0.4), nrow = 2, byrow = TRUE)
Omega <- diag(2)
B_inverse <- solve(B)
A0 <- solve(B, Gamma0)
A1 <- solve(B, Gamma1)
dimnames(A1) <- list(c("y equation", "x equation"), c("y lag", "x lag"))
round(B_inverse %*% B, 10)
     [,1] [,2]
[1,]    1    0
[2,]    0    1
knitr::kable(A1, digits = 5, caption = "The same population lag coefficients")
The same population lag coefficients
y lag x lag
y equation 0.4 0.22857
x equation 0.2 0.45714
c(intercept_difference = max(abs(A0 - A0_scalar)),
  lag_difference = max(abs(A1 - A1_scalar)))
intercept_difference       lag_difference 
        5.551115e-17         0.000000e+00 

byrow = TRUE fills rows. %*% multiplies matrices; * multiplies matching entries. solve(B, Gamma1) solves 𝐁𝐀1=𝚪1\mathbf B\mathbf A_1=\mathbf\Gamma_1. The agreement with substitution is the point of the notation.

# Trace row 1, column 2 by multiplying its two pairs of entries.
entry_terms <- B_inverse[1, ] * Gamma1[, 2]
entry_terms
[1] 0.0000000 0.2285714
c(sum_of_terms = sum(entry_terms), A1_entry = A1[1, 2])
sum_of_terms     A1_entry 
   0.2285714    0.2285714 
# A fixed pair of lags/shocks checks the equations, not a second DGP.
previous <- c(y = 1, x = -1)
eta_now <- c(y = 0.2, x = -0.3)
current <- as.vector(A0 + A1 %*% previous + B_inverse %*% eta_now)
structural_left <- B %*% current
structural_right <- Gamma0 + Gamma1 %*% previous + eta_now
c(y_equation_error = structural_left[1] - structural_right[1],
  x_equation_error = structural_left[2] - structural_right[2])
y_equation_error x_equation_error 
               0                0 

Modify: change the selected entry to row 2, column 1. Write its scalar formula first, then compare the sum with A1[2, 1]. Keep the variable order.

A brief stability check

D≠0D\ne0 makes today’s simultaneous equations solvable for the supplied past and shocks. Stability concerns propagation through time and depends on 𝐀1\mathbf A_1. For this VAR(1), each eigenvalue must have modulus below one. R can do this arithmetic; no companion-matrix workflow is needed here.

eigenvalues <- eigen(A1)$values
data.frame(eigenvalue = eigenvalues, inside_unit_circle = Mod(eigenvalues) < 1)
  eigenvalue inside_unit_circle
1  0.6442810               TRUE
2  0.2128619               TRUE

The values are 0.644 and 0.213. This is equivalent to roots of det(𝐈−𝐀1z)=0\det(\mathbf I-\mathbf A_1z)=0 lying outside the unit circle. Invertibility of 𝐁\mathbf B alone is a different condition.

So what: matrix multiplication locates the same economic and dated connections that scalar substitution revealed.

5. Read the output without changing the target

Predict: uncorrelated structural shocks imply reduced innovations that are (a) always uncorrelated, (b) potentially correlated through mixing, or (c) fitted regression residuals by definition? State which object is random in the population and which becomes observable after estimation.

The innovations are ϵy=(ηy+byxηx)/D\epsilon_y=(\eta_y+b_{yx}\eta_x)/D and ϵx=(bxyηy+ηx)/D\epsilon_x=(b_{xy}\eta_y+\eta_x)/D. With uncorrelated structural shocks,

Σyy=(ωy+byx2ωx)/D2,Σxx=(bxy2ωy+ωx)/D2,Σyx=(bxyωy+byxωx)/D2.\begin{aligned} \Sigma_{yy}&=(\omega_y+b_{yx}^2\omega_x)/D^2,\\ \Sigma_{xx}&=(b_{xy}^2\omega_y+\omega_x)/D^2,\\ \Sigma_{yx}&=(b_{xy}\omega_y+b_{yx}\omega_x)/D^2. \end{aligned}

Sigma <- B_inverse %*% Omega %*% t(B_inverse)
dimnames(Sigma) <- list(c("epsilon_y", "epsilon_x"), c("epsilon_y", "epsilon_x"))
knitr::kable(Sigma, digits = 5, caption = "Population covariance of reduced innovations")
Population covariance of reduced innovations
epsilon_y epsilon_x
epsilon_y 1.63265 0.97959
epsilon_x 0.97959 1.38776

The exact covariance is (80/4948/4948/4968/49)\begin{pmatrix}80/49&48/49\\48/49&68/49\end{pmatrix}. Both mixtures share shocks. This is covariance after removing predictable history, not the covariance of the raw y,xy,x series.

One fixed sample, exactly as in the notes

The next chunk is a short illustration of what regression estimates, not a Monte Carlo exercise. Preserve the seed, draw order, sample size and burn-in. The initialization reduces the stable system’s starting transient.

set.seed(637611)
n <- 2000
burn <- 250
eta <- matrix(rnorm(2 * (n + burn)), ncol = 2)
Y <- matrix(0, nrow = n + burn + 1, ncol = 2)
colnames(Y) <- c("y", "x")
Y[1, ] <- solve(diag(2) - A1, A0)
for (tt in seq_len(nrow(eta))) {
  Y[tt + 1, ] <- A0 + A1 %*% Y[tt, ] + B_inverse %*% eta[tt, ]
}
keep <- (burn + 1):(burn + n)
dat <- data.frame(y = Y[keep + 1, 1], x = Y[keep + 1, 2],
                  y_lag = Y[keep, 1], x_lag = Y[keep, 2])
fit_y <- lm(y ~ y_lag + x_lag, data = dat)
fit_x <- lm(x ~ y_lag + x_lag, data = dat)
comparison <- data.frame(
  Entry = c("y intercept", "y: y lag", "y: x lag", "x intercept", "x: y lag", "x: x lag"),
  Structural = c(Gamma0[1], Gamma1[1, ], Gamma0[2], Gamma1[2, ]),
  Reduced = c(A0[1], A1[1, ], A0[2], A1[2, ]),
  Estimated_reduced = c(coef(fit_y), coef(fit_x)))
knitr::kable(comparison, digits = 4, caption = "Same equation positions, different parameters")
Same equation positions, different parameters
Entry Structural Reduced Estimated_reduced
y intercept 0.2 0.2857 0.2550
y: y lag 0.3 0.4000 0.3756
y: x lag 0.0 0.2286 0.2742
x intercept 0.1 0.1714 0.1577
x: y lag 0.1 0.2000 0.1852
x: x lag 0.4 0.4571 0.4838

The estimated xx-lag coefficient is 0.274; its population target is 8/358/35, while the structural lag is zero. Differences between reduced truth and its estimate are sampling variation. They do not change the algebra or justify reseeding for a closer-looking table.

The estimated equation is yt=ây0+âyyyt−1+âyxxt−1+ey,ty_t=\hat a_{y0}+\hat a_{yy}y_{t-1}+\hat a_{yx}x_{t-1}+e_{y,t}. The hat marks an estimate and ee is a residual.

coefficient_table <- as.data.frame(coef(summary(fit_y)))
coefficient_table[["Pr(>|t|)"]] <- format.pval(coefficient_table[["Pr(>|t|)"]],
                                              digits = 3, eps = 0.001)
knitr::kable(coefficient_table, digits = 4, caption = "Reduced y regression; very small p-values are not zero")
Reduced y regression; very small p-values are not zero
Estimate Std. Error t value Pr(>|t|)
(Intercept) 0.2550 0.0301 8.4784 <0.001
y_lag 0.3756 0.0282 13.3415 <0.001
x_lag 0.2742 0.0287 9.5477 <0.001
E <- cbind(y = residuals(fit_y), x = residuals(fit_x))
Sigma_hat <- crossprod(E) / nrow(E)
knitr::kable(Sigma_hat, digits = 4, caption = "Estimated innovation covariance, residual divisor n")
Estimated innovation covariance, residual divisor n
y x
y 1.5423 0.9473
x 0.9473 1.3848

The current reduced innovation has conditional mean zero given the included history, under this DGP. Across-equation innovation correlation therefore does not invalidate that regressor condition. A reduced xx-lag coefficient describes conditional prediction holding the other included history fixed. Testing ayx=0a_{yx}=0 tests ϕyx+byxϕxx=0\phi_{yx}+b_{yx}\phi_{xx}=0 for D≠0D\ne0; it is not generally a test of ϕyx=0\phi_{yx}=0.

Break the interpretation using the same observations

Putting current xx back into the regression makes it look structural, but current xx contains part of ηy\eta_y. Conditional on history, Cov(xt,ηy,t)=bxyωy/D≠0\operatorname{Cov}(x_t,\eta_{y,t})=b_{xy}\omega_y/D\ne0 here. The structural OLS orthogonality assumption fails.

naive_y <- lm(y ~ x + y_lag + x_lag, data = dat)
naive_x <- lm(x ~ y + y_lag + x_lag, data = dat)
naive_comparison <- data.frame(
  Current_connection = c("x into y", "y into x"),
  True_structural = c(0.5, 0.25),
  Population_association = c(Sigma[1, 2] / Sigma[2, 2], Sigma[2, 1] / Sigma[1, 1]),
  Naive_OLS = c(coef(naive_y)["x"], coef(naive_x)["y"]))
knitr::kable(naive_comparison, digits = 4,
             caption = "Structural-looking OLS estimates an association between mixtures")
Structural-looking OLS estimates an association between mixtures
Current_connection True_structural Population_association Naive_OLS
x x into y 0.50 0.7059 0.6841
y y into x 0.25 0.6000 0.6143

The population association slopes are 12/1712/17 and .6.6, not .5.5 and .25.25. A larger sample does not fix the failed orthogonality. Each is a separate conditional association; the two cannot simply be combined into an identified simultaneous system with uncorrelated shocks.

Reconcile: distinguish a structural coefficient, its transformed reduced counterpart, and an estimated coefficient. Explain which target a reported t-test addresses. The label “structural” does not change a regression’s target.

So what: a useful conditional predictor can be an incorrect answer to a question about a primitive structural connection.

6. Work backward under an explicit restriction

Cover the generating coefficients, including the known zero ϕyx\phi_{yx}. For this exercise the hypothetical analyst receives only 𝐀0,𝐀1,𝚺\mathbf A_0,\mathbf A_1,\boldsymbol\Sigma, treated as known population objects so sampling noise cannot explain ambiguity.

Predict: can (a) only one, (b) more than one, or (c) no structural system reproduce these reduced moments? State what information the analyst has.

Unit diagonal 𝐁\mathbf B and diagonal positive 𝛀\boldsymbol\Omega leave four current/shock unknowns against three distinct covariance entries. The mirrored covariance adds no information. Unrestricted structural lags and intercepts can adapt through 𝚪0=𝐁𝐀0\boldsymbol\Gamma_0=\mathbf B\mathbf A_0 and 𝚪1=𝐁𝐀1\mathbf\Gamma_1=\mathbf B\mathbf A_1.

Derive a recovery, not just a count

Impose byx=0b_{yx}=0, meaning y first. Then

ϵy=ηy,ϵx=bxyηy+ηx,\epsilon_y=\eta_y,\quad\epsilon_x=b_{xy}\eta_y+\eta_x, Σyy=ωy,Σyx=bxyωy,Σxx=bxy2ωy+ωx.\Sigma_{yy}=\omega_y,\quad\Sigma_{yx}=b_{xy}\omega_y,\quad \Sigma_{xx}=b_{xy}^2\omega_y+\omega_x.

Recover variance → current coefficient → remaining variance:

ωy=Σyy,bxy=ΣyxΣyy,ωx=Σxx−Σyx2Σyy.\omega_y=\Sigma_{yy},\qquad b_{xy}=\frac{\Sigma_{yx}}{\Sigma_{yy}},\qquad \omega_x=\Sigma_{xx}-\frac{\Sigma_{yx}^2}{\Sigma_{yy}}.

Positive-definite 𝚺\boldsymbol\Sigma gives positive variances. Explain why the last subtraction removes the first shock’s contribution. Then reverse the exclusion: with bxy=0b_{xy}=0, xx is first, giving byx=Σyx/Σxxb_{yx}=\Sigma_{yx}/\Sigma_{xx}, ωx=Σxx\omega_x=\Sigma_{xx}, and ωy=Σyy−Σyx2/Σxx\omega_y=\Sigma_{yy}-\Sigma_{yx}^2/\Sigma_{xx}.

B_y_first <- matrix(c(1, 0, -Sigma[2, 1] / Sigma[1, 1], 1), 2, byrow = TRUE)
Omega_y_first <- diag(c(Sigma[1, 1], Sigma[2, 2] - Sigma[2, 1]^2 / Sigma[1, 1]))
Gamma0_y_first <- B_y_first %*% A0
Gamma1_y_first <- B_y_first %*% A1
B_x_first <- matrix(c(1, -Sigma[1, 2] / Sigma[2, 2], 0, 1), 2, byrow = TRUE)
Omega_x_first <- diag(c(Sigma[1, 1] - Sigma[1, 2]^2 / Sigma[2, 2], Sigma[2, 2]))
Gamma0_x_first <- B_x_first %*% A0
Gamma1_x_first <- B_x_first %*% A1
structures <- data.frame(
  Structure = c("Generating simultaneous", "y first", "x first"),
  b_yx = c(-B[1, 2], -B_y_first[1, 2], -B_x_first[1, 2]),
  b_xy = c(-B[2, 1], -B_y_first[2, 1], -B_x_first[2, 1]),
  phi_yx = c(Gamma1[1, 2], Gamma1_y_first[1, 2], Gamma1_x_first[1, 2]),
  omega_y = c(Omega[1, 1], Omega_y_first[1, 1], Omega_x_first[1, 1]),
  omega_x = c(Omega[2, 2], Omega_y_first[2, 2], Omega_x_first[2, 2]))
knitr::kable(structures, digits = 5, caption = "Reveal the generating structure only after recovering alternatives")
Reveal the generating structure only after recovering alternatives
Structure b_yx b_xy phi_yx omega_y omega_x
Generating simultaneous 0.50000 0.25 0.00000 1.00000 1.00000
y first 0.00000 0.60 0.22857 1.63265 0.80000
x first 0.70588 0.00 -0.09412 0.94118 1.38776

The assumed zero is false for the original generator in both recursive alternatives. We recovered the structure conditional on each restriction. The fixed matrix order stays (y,x)(y,x); “x first” names economic timing, not a silent row swap.

Sigma_y_first <- solve(B_y_first) %*% Omega_y_first %*% t(solve(B_y_first))
Sigma_x_first <- solve(B_x_first) %*% Omega_x_first %*% t(solve(B_x_first))
reconstruction <- data.frame(
  Ordering = c("y first", "x first"),
  Intercept_error = c(max(abs(solve(B_y_first, Gamma0_y_first) - A0)),
                      max(abs(solve(B_x_first, Gamma0_x_first) - A0))),
  Lag_error = c(max(abs(solve(B_y_first, Gamma1_y_first) - A1)),
                max(abs(solve(B_x_first, Gamma1_x_first) - A1))),
  Covariance_error = c(max(abs(Sigma_y_first - Sigma)), max(abs(Sigma_x_first - Sigma))))
knitr::kable(reconstruction, digits = 12, caption = "Maximum absolute reconstruction discrepancies")
Maximum absolute reconstruction discrepancies
Ordering Intercept_error Lag_error Covariance_error
y first 0 0 0
x first 0 0 0

All are zero to numerical precision. More data can estimate reduced moments more precisely without choosing the economic restriction.

For estimated quantities, retain hats and the condition:

b_xy_hat_y_first <- Sigma_hat[2, 1] / Sigma_hat[1, 1]
B_hat_y_first <- matrix(c(1, 0, -b_xy_hat_y_first, 1), 2, byrow = TRUE)
A0_hat <- c(coef(fit_y)[1], coef(fit_x)[1])
A1_hat <- rbind(coef(fit_y)[2:3], coef(fit_x)[2:3])
Gamma1_hat_y_first <- B_hat_y_first %*% A1_hat
data.frame(Estimate_under_y_first = b_xy_hat_y_first,
           Population_under_y_first = -B_y_first[2, 1],
           Original_generator = b_xy)
  Estimate_under_y_first Population_under_y_first Original_generator
1              0.6142564                      0.6               0.25

Modify: change the assumed current exclusion while holding reduced objects fixed. Explain why structural lag coefficients change too.

So what: exact fit of reduced moments leaves room for distinct structural explanations, each conditional on its identifying assumptions.

7. Enlarge the system without losing the argument

Three quarterly outcomes

For this separate quarterly illustration, order outcomes as unemployment uu, inflation π\pi, and federal funds rate ii. Here uu is unemployment, not an error. Write qj,t−1q_{j,t-1} for the intercept and one lag of all three outcomes in equation jj; those terms remain unrestricted.

ut=qu,t−1+buππt+buiit+ηu,t,πt=qπ,t−1+bπuut+bπiit+ηπ,t,it=qi,t−1+biuut+biππt+ηi,t.\begin{aligned} u_t&=q_{u,t-1}+b_{u\pi}\pi_t+b_{ui}i_t+\eta_{u,t},\\ \pi_t&=q_{\pi,t-1}+b_{\pi u}u_t+b_{\pi i}i_t+\eta_{\pi,t},\\ i_t&=q_{i,t-1}+b_{iu}u_t+b_{i\pi}\pi_t+\eta_{i,t}. \end{aligned}

Predict: under unit diagonal 𝐁3\mathbf B_3 and diagonal positive 𝛀3\boldsymbol\Omega_3, does the whole current/shock structure need (a) one, (b) three, or (c) nine additional restrictions by the count? List current coefficients, shock variances, and distinct innovation moments.

Six current cross-coefficients plus three shock variances face three reduced innovation variances plus three covariances: 9−6=39-6=3. These are innovation covariances after conditioning on the past, not raw macroseries covariances.

Choose buπ=bui=bπi=0b_{u\pi}=b_{ui}=b_{\pi i}=0, giving

𝐁3rec=(100−bπu10−biu−biπ1).\mathbf B_3^{\mathrm{rec}}=\begin{pmatrix} 1&0&0\\-b_{\pi u}&1&0\\-b_{iu}&-b_{i\pi}&1 \end{pmatrix}.

The allowed current arrows are u→πu\to\pi, u→iu\to i, and π→i\pi\to i. Unemployment cannot respond to current inflation or policy within the quarter; inflation cannot respond to current policy. All lagged connections remain. Those are economic restrictions to defend, not findings about a policy rule.

Recover the third equation

Subtract conditional means given history:

ϵu=ηu,ϵπ=bπuϵu+ηπ,ϵi=biuϵu+biπϵπ+ηi.\epsilon_u=\eta_u,\quad \epsilon_\pi=b_{\pi u}\epsilon_u+\eta_\pi, \quad\epsilon_i=b_{iu}\epsilon_u+b_{i\pi}\epsilon_\pi+\eta_i.

The first two repeat the bivariate calculation:

ωu=Σuu,bπu=ΣuπΣuu,ωπ=Σππ−Σuπ2Σuu.\omega_u=\Sigma_{uu},\quad b_{\pi u}=\frac{\Sigma_{u\pi}}{\Sigma_{uu}}, \quad\omega_\pi=\Sigma_{\pi\pi}-\frac{\Sigma_{u\pi}^2}{\Sigma_{uu}}.

For policy, take covariance with each earlier innovation:

Σui=biuΣuu+biπΣuπ,Σπi=biuΣuπ+biπΣππ.\begin{aligned} \Sigma_{ui}&=b_{iu}\Sigma_{uu}+b_{i\pi}\Sigma_{u\pi},\\ \Sigma_{\pi i}&=b_{iu}\Sigma_{u\pi}+b_{i\pi}\Sigma_{\pi\pi}. \end{aligned}

This is the familiar row-by-column operation, now used to solve two coefficients together:

(ΣuuΣuπΣuπΣππ)(biubiπ)=(ΣuiΣπi),ωi=Σii−biuΣui−biπΣπi.\begin{pmatrix}\Sigma_{uu}&\Sigma_{u\pi}\\\Sigma_{u\pi}&\Sigma_{\pi\pi}\end{pmatrix} \begin{pmatrix}b_{iu}\\b_{i\pi}\end{pmatrix} =\begin{pmatrix}\Sigma_{ui}\\\Sigma_{\pi i}\end{pmatrix},\qquad \omega_i=\Sigma_{ii}-b_{iu}\Sigma_{ui}-b_{i\pi}\Sigma_{\pi i}.

Explain why Σui/Σuu\Sigma_{ui}/\Sigma_{uu} alone combines the direct connection with the route through inflation. Positive-definite 𝚺3\boldsymbol\Sigma_3 makes the block invertible and leaves positive shock variances.

The following arithmetic fixture verifies the formula, with no empirical macro interpretation and no new simulated DGP. Its numbers are chosen only to make a positive-definite covariance calculation easy to inspect.

Sigma3_check <- matrix(c(4, 2, 1, 2, 3, 2, 1, 2, 5), 3, byrow = TRUE)
b21_check <- Sigma3_check[1, 2] / Sigma3_check[1, 1]
b3_check <- solve(Sigma3_check[1:2, 1:2], Sigma3_check[1:2, 3])
omega3_check <- c(Sigma3_check[1, 1],
                  Sigma3_check[2, 2] - Sigma3_check[1, 2]^2 / Sigma3_check[1, 1],
                  Sigma3_check[3, 3] - sum(b3_check * Sigma3_check[1:2, 3]))
B3_check <- diag(3)
B3_check[2, 1] <- -b21_check
B3_check[3, 1:2] <- -b3_check
recovered_covariance3 <- solve(B3_check) %*% diag(omega3_check) %*% t(solve(B3_check))
data.frame(quantity = c("first variance", "second variance", "third variance",
                        "largest covariance reconstruction error"),
           value = c(omega3_check, max(abs(recovered_covariance3 - Sigma3_check))))
                                 quantity value
1                          first variance 4.000
2                         second variance 2.000
3                          third variance 3.625
4 largest covariance reconstruction error 0.000

Modify on paper: reverse the first two outcomes’ ordering. State the new zeros and their timing meanings before doing any arithmetic. Merely changing variable names without permuting the covariance’s rows and columns would mix incompatible orders.

Add GDP growth first

Let gt=100Δlog(realGDPt)g_t=100\Delta\log(\mathrm{real\ GDP}_t) and use quarterly order (g,u,π,i)(g,u,\pi,i). Keep the original three outcomes’ relative order:

𝐁4rec=(1000−bug100−bπg−bπu10−big−biu−biπ1).\mathbf B_4^{\mathrm{rec}}=\begin{pmatrix} 1&0&0&0\\ -b_{ug}&1&0&0\\ -b_{\pi g}&-b_{\pi u}&1&0\\ -b_{ig}&-b_{iu}&-b_{i\pi}&1 \end{pmatrix}.

The three new zeros are bgu=bgπ=bgi=0b_{gu}=b_{g\pi}=b_{gi}=0: current unemployment, inflation and policy do not enter the GDP-growth equation. Later equations may respond to current GDP growth. All four lags may enter every equation. GDP-first is an added timing assumption, not a consequence of the word GDP. Growth and rates do not automatically establish stationarity.

Twelve current coefficients plus four shock variances face ten distinct covariance entries: 16−10=616-10=6. The original three exclusions plus the three new variable-pair exclusions supply six recursive zeros.

Count the general m-equation structure

Maintain unit-diagonal 𝐁\mathbf B, diagonal positive 𝛀\boldsymbol\Omega, and unrestricted structural intercepts and lags. Do not count those baseline conditions a second time.

m(m−1)+m−m(m+1)2=m(m−1)2.m(m-1)+m-\frac{m(m+1)}2=\frac{m(m-1)}2.

m <- 2:5
count_table <- data.frame(
  Equations = m, Current_coefficients = m * (m - 1), Shock_variances = m,
  Distinct_covariance_entries = m * (m + 1) / 2,
  Additional_restrictions = m * (m - 1) / 2)
knitr::kable(count_table, caption = "Whole-system count under the stated baseline assumptions")
Whole-system count under the stated baseline assumptions
Equations Current_coefficients Shock_variances Distinct_covariance_entries Additional_restrictions
2 2 2 3 1
3 6 3 6 3
4 12 4 10 6
5 20 5 15 10

This is a necessary order count for the whole current/shock structure, not a per-equation requirement. An arbitrary collection of the same number of restrictions need not identify the system. A fixed recursive ordering puts (m−1)+(m−2)+⋯+1(m-1)+(m-2)+\cdots+1 zeros above the diagonal. With a positive-definite reduced covariance, successive recovery works and has positive shock variances. Unrestricted structural lags still adapt through 𝚪1=𝐁𝐀1\mathbf\Gamma_1=\mathbf B\mathbf A_1.

Recognize the software factorization

Return to the original two-variable example:

P_y_first <- t(chol(Sigma))
P_from_recovered <- solve(B_y_first) %*% diag(sqrt(diag(Omega_y_first)))
c(covariance_error = max(abs(P_y_first %*% t(P_y_first) - Sigma)),
  recovered_factor_error = max(abs(P_y_first - P_from_recovered)))
      covariance_error recovered_factor_error 
          2.220446e-16           0.000000e+00 

R’s chol() returns an upper factor; transpose it for the lower factor. The recovered y-first 𝐁,𝛀\mathbf B,\boldsymbol\Omega produce this factor, not the original simultaneous matrices. Cholesky implements the chosen ordering; it cannot decide whether the economic timing assumption is true.

So what: the number of missing restrictions grows with the system, but the distinction between counting, recovering, and defending remains the same.

8. Defend the timing, then ask the next system question

Return to monthly inflation yy and policy rate xx. This application is distinct from the preceding quarterly three/four-variable illustration.

Predict and defend: which assumption is more plausible for a stated monthly measurement: (a) inflation first, (b) policy first, or (c) neither without more information? Name the economic reasoning your choice requires.

Proposed ordering Current restriction Timing claim
Inflation first byx=0b_{yx}=0 Measured inflation does not respond to an unexpected policy disturbance within the month; policy may respond to current inflation news
Policy first bxy=0b_{xy}=0 Policy does not respond to current inflation within the month; inflation may respond to current policy

State one supporting argument and one challenge involving publication dates, information sets, aggregation, or frequency. Discuss how moving to quarterly data could affect the argument. The pack has not estimated a monetary-policy effect. Covariance fit and significant lagged Granger predictability do not choose the current ordering.

Return to the original coefficient: a structural zero became a nonzero reduced coefficient. The coefficient table has conditional predictive meaning, but it does not by itself identify a primitive effect. Even after identifying shocks, one coefficient describes only one part of the connected system.

Next question Later tool
How does each outcome respond over time after a specified identified disturbance? IRF, Module 12
At a given horizon, which identified disturbances account for forecast-error variance? FEVD, Module 13

Both retain identification assumptions. FEVD is not regression R2R^2 or a share of every observed movement. Their formulas and inference come later. The cointegrated walkers also return through VECMs in Module 13; they are not the stationary baseline simulated here.

So what: software can implement a restriction, while the economic argument must defend the interpretation it creates.

9. Explorations — optional, outside the assessable surface

These seeds go from guided to open. They are not additional Core requirements. Keep the shipped baseline intact; put variants in new objects and record what changed. On request, an AI tutor can build later machinery as a clearly labelled preview and connect it back to Core.

  1. Follow another entry. Derive axya_{xy} by substitution, identify its row and column, and verify the matrix calculation. Explain its two routes.
  2. Remove one current connection. Create B_variant with one off-diagonal entry zero, keeping the original structural lag matrix. Predict which reduced coefficients and covariances change, then compute them. This changes the DGP; distinguish it from holding reduced moments fixed while identifying.
  3. Solvable but unstable. Leave B fixed and vary a copy of Gamma1 until a reduced eigenvalue reaches or crosses one. Check the current determinant separately. No simulation is needed to see the distinction.
  4. Reverse the arithmetic fixture’s ordering. Permute its rows and columns consistently, recover the recursive coefficients, then reconstruct the original covariance. Explain which timing claims changed and which moments did not.
  5. Build a timing argument. Choose two plausible measurement frequencies for an economic question and compare the implied current exclusions. State what institutional evidence would strengthen or weaken each argument.

10. Run the shared referee and close your log

The checks validate specific arithmetic and implementation claims. They do not certify an economic ordering, arbitrary student modifications, or causal identification from observational data. See CHECKS.md for their scope.

source("R/checks.R", local = new.env())
Module 11 checks
----------------
PASS 1. Scalar substitution and matrix map agree for every coefficient  [largest discrepancy=2.2e-16; direct lag=0; reduced lag=0.228571]
PASS 2. Shock mixing, positive covariance and distinct stability condition  [covariance=0.979592; baseline eigenvalues=0.644281/0.212862]
PASS 3. Fixed draw, dated structural equations and OLS projection targets  [rows=2000; equation error=9.4e-16; estimated reduced x-lag=0.274247]
PASS 4. Both false recursive exclusions reproduce all reduced moments  [largest reconstruction error=2.2e-16]
PASS 5. Three-equation joint recovery and positive remaining variances  [last-row coefficients=-0.125/0.750; reconstruction error=0.0e+00]
PASS 6. Whole-system counts, recursive zeros and Cholesky orientation  [counts=1/3/6/10; lower-factor error=2.2e-16]
----------------
6/6 checks passed.

Record the prediction you actually made, the calculation you ran, and what you observed. Your revised reasoning and remaining uncertainty belong in your own words. The log is private, never collected and never graded.