From a457ce822bbdee370c08cdc411212a9b0e3381c3 Mon Sep 17 00:00:00 2001 From: thomassargent30 Date: Mon, 12 Oct 2026 14:11:20 -0400 Subject: [PATCH 1/2] Add induced_priors lecture: induced priors, VARs, and sign restrictions New lecture built on the supply_demand_var laboratory. A structural modeler with uniform priors over the adjustment costs (g_s, g_d) computes, by Monte Carlo, the induced priors over structural impulse responses, autocovariances, the population VAR innovation covariance, and q-first and p-first Cholesky impulse responses. The induced prior generates theory-motivated sign restrictions in the spirit of Uhlig (2005, 2017); because the supply shock is non-fundamental, restrictions that hold with prior probability one yield identified sets that exclude the true response. Further sections widen the prior and compare uniform with log-uniform priors on g_s. Includes four exercises, connections to Leeper, Traum and Walker (2017) and Geweke (2010), new bibliography entries, and a TOC entry after supply_demand_var. Co-Authored-By: Claude Opus 5.5 --- lectures/_static/quant-econ.bib | 60 ++ lectures/_toc.yml | 1 + lectures/induced_priors.md | 1623 +++++++++++++++++++++++++++++++ 3 files changed, 1684 insertions(+) create mode 100644 lectures/induced_priors.md diff --git a/lectures/_static/quant-econ.bib b/lectures/_static/quant-econ.bib index e83d20a9..0dc00740 100644 --- a/lectures/_static/quant-econ.bib +++ b/lectures/_static/quant-econ.bib @@ -3269,3 +3269,63 @@ @article{CoibionGorodnichenko2015 pages = {2644--2678}, doi = {10.1257/aer.20130921} } + +@article{Uhlig2005, + author = {Uhlig, Harald}, + title = {What are the effects of monetary policy on output? Results from an agnostic identification procedure}, + journal = {Journal of Monetary Economics}, + year = {2005}, + volume = {52}, + number = {2}, + pages = {381--419} +} + +@incollection{Uhlig2017, + author = {Uhlig, Harald}, + title = {Shocks, Sign Restrictions, and Identification}, + booktitle = {Advances in Economics and Econometrics: Eleventh World Congress}, + editor = {Honor{\'e}, Bo and Pakes, Ariel and Piazzesi, Monika and Samuelson, Larry}, + volume = {2}, + chapter = {4}, + pages = {95--127}, + publisher = {Cambridge University Press}, + year = {2017} +} + +@article{DelNegroSchorfheide2004, + author = {Del Negro, Marco and Schorfheide, Frank}, + title = {Priors from General Equilibrium Models for {VARs}}, + journal = {International Economic Review}, + year = {2004}, + volume = {45}, + number = {2}, + pages = {643--673} +} + +@article{BaumeisterHamilton2015, + author = {Baumeister, Christiane and Hamilton, James D.}, + title = {Sign Restrictions, Structural Vector Autoregressions, and Useful Prior Information}, + journal = {Econometrica}, + year = {2015}, + volume = {83}, + number = {5}, + pages = {1963--1999} +} + +@article{LeeperTraumWalker2017, + author = {Leeper, Eric M. and Traum, Nora and Walker, Todd B.}, + title = {Clearing Up the Fiscal Multiplier Morass}, + journal = {American Economic Review}, + year = {2017}, + volume = {107}, + number = {8}, + pages = {2409--2454} +} + +@book{Geweke2010, + author = {Geweke, John}, + title = {Complete and Incomplete Econometric Models}, + publisher = {Princeton University Press}, + address = {Princeton, NJ}, + year = {2010} +} diff --git a/lectures/_toc.yml b/lectures/_toc.yml index 91de32ca..7dafb53e 100644 --- a/lectures/_toc.yml +++ b/lectures/_toc.yml @@ -40,6 +40,7 @@ parts: - file: cattle_cycles - file: hs_invertibility_example - file: supply_demand_var + - file: induced_priors - caption: Risk, Model Uncertainty, and Robustness numbered: true chapters: diff --git a/lectures/induced_priors.md b/lectures/induced_priors.md new file mode 100644 index 00000000..29984c1c --- /dev/null +++ b/lectures/induced_priors.md @@ -0,0 +1,1623 @@ +--- +jupytext: + text_representation: + extension: .md + format_name: myst +kernelspec: + display_name: Python 3 + language: python + name: python3 +--- + +(induced_priors)= +```{raw} html + +``` + +```{index} single: python +``` + +# Induced Priors for Impulse Responses + +## Overview + +This lecture uses the market for a single good studied in {doc}`supply_demand_var` as a laboratory. + +We imagine a **structural modeler** who believes that model and knows all of its parameters except two +adjustment-cost parameters, the supplier's $g_s$ and the demander's $g_d$. + +The modeler expresses uncertainty about $(g_s, g_d)$ with a Bayesian prior. + +Because every object that the model implies --- impulse responses, autocovariances, the vector +autoregression that an econometrician would fit to prices and quantities --- is a function of +$(g_s, g_d)$, the prior on $(g_s, g_d)$ **induces** a prior over each of those objects. + +We approximate these induced priors by Monte Carlo. + +In this lecture, we shall + +* compute the induced prior over the impulse responses of price $p_t$ and quantity $q_t$ to the supply + shock $w_{st}$ and the demand shock $w_{dt}$ that hit the agents' information sets, +* compute the induced prior over the autocovariance function of $(q_t, p_t)$ --- everything that an + infinitely long sample could reveal, +* compute the induced prior over the population **vector autoregression** for $(q_t, p_t)$: the + covariance matrix of its innovations and its impulse responses under two standard recursive + (Cholesky) orthogonalizations, one that puts $q_t$ first and one that puts $p_t$ first, +* compare the induced priors over the VAR impulse responses with those over the structural impulse + responses, and +* use the induced prior to construct **sign restrictions motivated by economic theory** in the spirit + of Harald Uhlig {cite}`Uhlig2005,Uhlig2017`, and then ask what those sign restrictions can and cannot + recover from a VAR, and +* widen the prior, and then change its shape from uniform to log-uniform, to see how the induced + priors, the region in which the supply shock is fundamental, and the performance of sign restrictions + change. + +The last step brings us back to the shock-invertibility issue that occupied +{doc}`supply_demand_var` and {doc}`hs_invertibility_example`. + +We shall see that sign restrictions that are *true* --- they hold with prior probability one under the +modeler's theory --- can nonetheless identify a set of VAR impulse responses that *excludes* the true +response to a supply shock. + +In addition to what's in Anaconda, this lecture uses the quantecon library. + +```{code-cell} ipython +--- +tags: [hide-output] +--- +!pip install --upgrade quantecon +``` + +We'll make these imports: + +```{code-cell} python3 +import numpy as np +import quantecon as qe +import matplotlib.pyplot as plt +from quantecon import LQ +from scipy.linalg import solve_discrete_lyapunov +``` + +## Background: sign restrictions + +Uhlig {cite}`Uhlig2005` proposed an "agnostic" way to identify shocks in a vector autoregression. + +Let $u_t$ be the one-step-ahead prediction error of a VAR, with covariance matrix $V = E u_t u_t^\top$. + +A researcher wants to write $u_t = A \epsilon_t$, where $\epsilon_t$ is a vector of mutually +uncorrelated, unit-variance **structural** shocks, so that $V = A A^\top$. + +The covariance matrix $V$ does not pin down $A$. + +If $L$ is the lower-triangular Cholesky factor of $V$, every factorization takes the form + +$$ +A = L R , \qquad R R^\top = I , +$$ + +so that a column $a$ of $A$ --- what Uhlig calls an **impulse vector** --- satisfies $a = L r$ for some +vector $r$ of unit length. + +A Cholesky decomposition picks $R = I$, and with it a recursive "causal ordering" of the variables. + +Uhlig instead imposes inequality restrictions on the *signs* of impulse responses, for example that a +contractionary monetary policy shock does not raise prices for $K$ periods, and treats every rotation +$R$ that satisfies the restrictions as admissible. + +Drawing $r$ uniformly from the unit sphere (the **Haar** measure) and discarding draws that violate the +restrictions gives a sample from a prior that is flat on the admissible set. + +In {cite}`Uhlig2017` he illustrates the logic with a static supply-and-demand example in which +"supply slopes up, demand slopes down" delivers the sign pattern + +$$ +\begin{array}{c|cc} + & \epsilon_S & \epsilon_D \\ \hline +P & - & + \\ +Q & + & + +\end{array} +$$ + +and he summarizes his advice with two principles: *if you know it, impose it*; *if you do not know +it, do not impose it*. + +Where do sign restrictions come from? + +They are supposed to be implications of economic theory that are robust across the range of theories, +or of parameter values, that an audience finds plausible. + +An induced prior makes that idea precise: a sign that holds for *every* parameter vector in the +support of the prior is an implication of the theory that a believer can impose with confidence. + +## The structural model and the prior + +The model is the one in {doc}`supply_demand_var`, which we briefly recall. + +A representative supplier and a representative demander each solve a linear-quadratic problem, +taking the price process as given. + +The supplier faces adjustment costs $\tfrac{g_s}{2}(q_t - q_{t-1})^2$ and a cost shock +$s_t = B_s(L) w_{st}$; the demander values durable services $a(L) q_t$ with adjustment cost +$\tfrac{g_d}{2}(a(L)q_t)^2$ and faces a demand shock $d_t = B_d(L) w_{dt}$. + +The shocks $w_{st}, w_{dt}$ are mutually uncorrelated white noises with variances $\sigma_s^2 = 0.5$ and +$\sigma_d^2 = 4$. + +The rational expectations equilibrium has the state-space representation + +$$ +X_{t+1} = A_F X_t + C \epsilon_{t+1}, \qquad z_t = \begin{bmatrix} q_t \\ p_t \end{bmatrix} = G_z X_t , +$$ + +with $\epsilon_t = (w_{st}, w_{dt})$. + +The modeler knows + +$$ +h_s = h_d = 1, \quad \beta = 1/1.05, \quad a(L) = 1 + .8L + .6L^2 + .4L^3 + .2L^4, +$$ + +$$ +B_d(L) = (1+.6L)(1+.4L)(1+.2L), \qquad B_s(L) = (1-.8L)(1+.4L)(1+.2L), +$$ + +and the two shock variances. + +The modeler does *not* know the adjustment-cost parameters. + +Instead of the values $g_s = 10$ and $g_d = 0.1$ used in {doc}`supply_demand_var`, the modeler has +independent uniform priors + +$$ +g_s \sim U[8, 12], \qquad g_d \sim U[0.05, 0.15] . +$$ + +We continue to call $(g_s, g_d) = (10, 0.1)$ the **true** parameter values. + +```{code-cell} python3 +h_s = h_d = 1.0 +β = 1 / 1.05 +a = np.array([1, .8, .6, .4, .2]) # a(L): coefficients a0..a4 + +# MA polynomials for the shocks (coefficients on L^0..L^3) +B_d = np.convolve(np.convolve([1, .6], [1, .4]), [1, .2]) +B_s = np.convolve(np.convolve([1, -.8], [1, .4]), [1, .2]) + +σ_s2, σ_d2 = 0.5, 4.0 # shock variances +scale = np.diag([np.sqrt(σ_s2), np.sqrt(σ_d2)]) + +g_s_true, g_d_true = 10.0, 0.1 # the "true" values +g_s_lo, g_s_hi = 8.0, 12.0 # prior support for g_s +g_d_lo, g_d_hi = 0.05, 0.15 # prior support for g_d +``` + +We solve for the equilibrium exactly as in {doc}`supply_demand_var`, as the optimal linear regulator of +a fictitious planner. + +```{code-cell} python3 +def companion_shift(n): + "Companion matrix that shifts an n-vector of lagged shocks." + A = np.zeros((n, n)) + A[1:, :-1] = np.eye(n - 1) + return A + +def solve_equilibrium(h_s, h_d, g_s, g_d, β, a, B_s, B_d): + """ + Solve the market equilibrium as a planner's optimal linear regulator. + + State x = [q_{t-1}, ..., q_{t-4}, w_s(t..t-3), w_d(t..t-3)] (dim 12) + Control u = q_t. + + Returns the closed-loop transition A_F, shock loading C, the selector + G_z mapping the state into (q_t, p_t), and the selectors for s_t, d_t. + """ + n_q = 4 + # exogenous companion for the two MA(3) shock processes + A_w = np.zeros((8, 8)) + A_w[:4, :4] = companion_shift(4) + A_w[4:, 4:] = companion_shift(4) + C_w = np.zeros((8, 2)) + C_w[0, 0], C_w[4, 1] = 1.0, 1.0 # inject (w_s, w_d) + + n = n_q + 8 + A = np.zeros((n, n)) + A[1:n_q, 0:n_q - 1] = np.eye(n_q - 1) # q lags shift + A[n_q:, n_q:] = A_w + B = np.zeros((n, 1)); B[0, 0] = 1.0 # new q_{t-1} = q_t + C = np.zeros((n, 2)); C[n_q:, :] = C_w + + def q_lag(k): # selector for q_{t-k} + e = np.zeros(n); e[k - 1] = 1.0; return e + + s_sel = np.zeros(n); s_sel[n_q:n_q + 4] = B_s # s_t = s_sel @ x + d_sel = np.zeros(n); d_sel[n_q + 4:] = B_d + q1 = q_lag(1) + a_vec = sum(a[k] * q_lag(k) for k in range(1, 5)) # lagged part of a(L)q_t + + # one-period loss = -(surplus), written as u'Qu + x'Rx + 2 u'Nx + Q = np.array([[(h_s + h_d) / 2 + g_s / 2 + g_d / 2 * a[0]**2]]) + R = g_d / 2 * np.outer(a_vec, a_vec) + g_s / 2 * np.outer(q1, q1) + R += 1e-5 * np.eye(n) # tiny ridge for the Riccati solver + N = (g_d * a[0] * a_vec / 2 - g_s * q1 / 2 + + (s_sel + d_sel) / 2).reshape(1, n) + + lq = LQ(Q, R, A, B, C, N=N, beta=β) + P, F, _ = lq.stationary_values() + F = np.asarray(F).reshape(1, n) + A_F = A - B @ F + + # price p_t = s_t + h_s q_t + g_s[(1+β) q_t - q_{t-1} - β E_t q_{t+1}] + F_q = -F # q_t = F_q @ x + E_q1 = -F @ A_F # E_t q_{t+1} = E_q1 @ x + G_p = (s_sel + h_s * F_q[0] + g_s * (1 + β) * F_q[0] + - g_s * q1 - g_s * β * E_q1[0]).reshape(1, n) + G_z = np.vstack([F_q, G_p]) # (q_t, p_t) = G_z @ x + return A_F, C, G_z, F, s_sel, d_sel +``` + +### A sign convention + +In {doc}`supply_demand_var`, a positive $w_{st}$ *raises* the supplier's costs and a positive $w_{dt}$ +*lowers* the demander's willingness to pay, so both push quantity down. + +To match the convention in {cite}`Uhlig2017` that a positive shock raises quantity, we work with + +$$ +\tilde w_{st} = -w_{st} \quad \text{(a favorable supply shock)}, \qquad +\tilde w_{dt} = -w_{dt} \quad \text{(a positive demand shock)}, +$$ + +each scaled to one standard deviation. + +From now on "the supply shock" and "the demand shock" refer to $\tilde w_{st}$ and $\tilde w_{dt}$. + +## Induced priors by Monte Carlo + +Let $\theta = (g_s, g_d)$ have prior density $\pi(\theta)$ and let $f(\theta)$ be any object that the +model implies, for example the impulse response of $p_{t+j}$ to $\tilde w_{dt}$. + +The **induced prior** over $f$ is the probability distribution of the random variable $f(\theta)$ +when $\theta \sim \pi$. + +It is the push-forward of $\pi$ through the mapping $f$. + +Typically $f$ is a complicated nonlinear function --- here it requires solving a Riccati equation, and +for the VAR objects a second Riccati equation for a Kalman filter --- so we can't write down the induced +prior in closed form. + +Monte Carlo is the natural tool: + +1. draw $\theta_i \sim \pi$ for $i = 1, \ldots, N$, +2. compute $f(\theta_i)$, +3. treat $\{f(\theta_i)\}_{i=1}^N$ as a sample from the induced prior. + +Geweke {cite}`Geweke2010` calls this **prior predictive analysis**. + +Leeper, Traum, and Walker {cite}`LeeperTraumWalker2017` use it to compute the range of +government-spending multipliers --- ratios of discounted impulse responses --- that alternative DSGE +specifications can produce before they confront data. + +For impulse response functions we summarize the sample by pointwise quantiles, horizon by horizon. + +As Uhlig {cite}`Uhlig2017` (his Principle 17) reminds us, a band built this way describes a +collection of *marginal* distributions, one for each horizon; the curve that joins the pointwise +medians is generally not the impulse response for any single draw $\theta_i$. + +For each draw we compute four objects. + +* **Structural impulse responses** $D_j = G_z A_F^{\,j} \tilde C$, where $\tilde C = -C\, + \mathrm{diag}(\sigma_s, \sigma_d)$ loads the sign-flipped, unit-standard-deviation shocks. +* **Autocovariances** $\Gamma_j = E\, z_{t+j} z_t^\top = G_z A_F^{\,j} \Sigma_X G_z^\top$, where + $\Sigma_X = A_F \Sigma_X A_F^\top + \tilde C \tilde C^\top$ is the stationary covariance of the state. +* **The innovation covariance** $V = E\, a_t a_t^\top$ of the population VAR for $z_t$. +* **The Wold moving-average coefficients** $C_j$ of the population VAR, with $C_0 = I$. + +As in {doc}`supply_demand_var`, the last two come from the stationary Kalman filter, whose innovations +representation is the Wold representation $z_t = \sum_{j \ge 0} C_j a_{t-j}$. + +```{code-cell} python3 +T = 20 # number of horizons j = 0, ..., T-1 + +def model_objects(g_s, g_d, T=T): + "Structural IRFs, autocovariances, and population VAR objects at (g_s, g_d)." + A_F, C, G_z, *_ = solve_equilibrium(h_s, h_d, g_s, g_d, β, a, B_s, B_d) + C_tilde = -C @ scale # favorable supply shock, positive demand shock + + # structural impulse responses D_j = G_z A_F^j C_tilde + D = np.empty((T, 2, 2)) + M = C_tilde.copy() + for j in range(T): + D[j] = G_z @ M + M = A_F @ M + + # autocovariances Γ_j = E z_{t+j} z_t' + Σ_X = solve_discrete_lyapunov(A_F, C_tilde @ C_tilde.T) + Γ = np.empty((T, 2, 2)) + M = Σ_X.copy() + for j in range(T): + Γ[j] = G_z @ M @ G_z.T + M = A_F @ M + + # Wold (innovations) representation from the stationary Kalman filter + lss = qe.LinearStateSpace(A_F, C_tilde, G_z, 1e-8 * np.eye(2)) + kalman = qe.Kalman(lss) + kalman.stationary_values() + V = kalman.stationary_innovation_covar() + C_wold = np.array(kalman.stationary_coefficients(T - 1, 'ma')) + A_var = np.array(kalman.stationary_coefficients(T - 1, 'var')) + + return dict(D=D, Γ=Γ, V=V, C=C_wold, A_var=A_var) +``` + +We take $N = 2{,}000$ draws from the prior and also record the objects at the true parameter values. + +```{code-cell} python3 +rng = np.random.default_rng(1234) +N = 2_000 +g_s_draws = rng.uniform(g_s_lo, g_s_hi, N) +g_d_draws = rng.uniform(g_d_lo, g_d_hi, N) + +draws = [model_objects(gs, gd) for gs, gd in zip(g_s_draws, g_d_draws)] +D_draws = np.array([d['D'] for d in draws]) # (N, T, 2, 2) +Γ_draws = np.array([d['Γ'] for d in draws]) +V_draws = np.array([d['V'] for d in draws]) # (N, 2, 2) +C_draws = np.array([d['C'] for d in draws]) # (N, T, 2, 2) +A_var_draws = np.array([d['A_var'] for d in draws]) + +truth = model_objects(g_s_true, g_d_true) +``` + +In every array of impulse responses, entry `[j, i, k]` is the response at horizon `j` of variable `i` +($0 = q$, $1 = p$) to shock `k`. + +We'll use a helper that draws a band between the 5th and 95th percentiles together with the median. + +```{code-cell} python3 +def fan(ax, paths, color, label, x=None, alpha=0.25): + "Plot the 5-95 percentile band and the median of paths (draws x horizons)." + lo, med, hi = np.percentile(paths, [5, 50, 95], axis=0) + x = np.arange(paths.shape[1]) if x is None else x + ax.fill_between(x, lo, hi, color=color, alpha=alpha, lw=0) + ax.plot(x, med, color=color, lw=2, label=label) + +var_names = ["quantity $q$", "price $p$"] +shock_names = ["supply shock $\\tilde w_s$", "demand shock $\\tilde w_d$"] +``` + +## Object 1: structural impulse responses + +The first figure shows the induced prior over the responses of $(q_t, p_t)$ to the two structural +shocks. + +The dashed black lines are the responses at the true parameter values. + +```{code-cell} python3 +fig, axes = plt.subplots(2, 2, figsize=(12, 7)) +for i in range(2): + for k in range(2): + ax = axes[i, k] + fan(ax, D_draws[:, :, i, k], 'C0', 'induced prior: median, 5-95%') + ax.plot(truth['D'][:, i, k], 'k--', lw=1.5, label='true parameters') + ax.axhline(0, color='k', lw=0.5) + ax.set_title(f"response of {var_names[i]} to {shock_names[k]}") + ax.set_xlabel("periods") +axes[0, 0].legend() +plt.tight_layout() +plt.show() +``` + +Over the ranges spanned by the prior, the qualitative shapes of the structural responses do not +change. + +A positive demand shock raises quantity for many periods and raises price for three periods, after +which the price undershoots. + +A favorable supply shock raises quantity and lowers price on impact, but its effects are small and change +sign after a couple of periods --- an imprint of the moving-average polynomial +$B_s(L) = 1 - .2L - .4L^2 - .064 L^3$, whose negative lag coefficients reverse the cost shock. + +A uniform prior over a parameter generally induces a *non-uniform* prior over the responses. + +The histograms below show the induced priors over the four impact responses. + +```{code-cell} python3 +fig, axes = plt.subplots(2, 2, figsize=(12, 6)) +for i in range(2): + for k in range(2): + ax = axes[i, k] + ax.hist(D_draws[:, 0, i, k], bins=40, density=True, color='C0', alpha=0.7) + ax.axvline(truth['D'][0, i, k], color='k', ls='--') + ax.set_title(f"impact response of {var_names[i]} to {shock_names[k]}") +plt.tight_layout() +plt.show() +``` + +Which uncertain parameter drives which response? + +The correlations of the impact responses with the parameter draws tell us. + +```{code-cell} python3 +for i in range(2): + for k in range(2): + x = D_draws[:, 0, i, k] + print(f"impact of {shock_names[k][:6]} on {var_names[i][:5]:6s}: " + f"corr with g_s = {np.corrcoef(g_s_draws, x)[0, 1]:6.3f}, " + f"corr with g_d = {np.corrcoef(g_d_draws, x)[0, 1]:6.3f}") +``` + +The response to the supply shock is governed almost entirely by the supplier's adjustment cost $g_s$: a +larger $g_s$ makes the supplier less willing to change output, so a given cost reduction moves quantity +less. + +The demander's $g_d$ matters mainly for the response of price to a demand shock. + +### The sign implications of the theory + +For each variable, shock, and horizon, we can compute the induced prior probability that the response +is positive. + +When that probability is one, the sign is an implication of the theory that is robust to the modeler's +uncertainty about $(g_s, g_d)$. + +```{code-cell} python3 +K_max = 5 +prob_pos = (D_draws[:, :K_max + 1] > 0).mean(axis=0) # (K_max+1, 2, 2) +prob_neg = (D_draws[:, :K_max + 1] < 0).mean(axis=0) + +# +1: positive with prior probability one; -1: negative with probability one; 0: '?' +sign_table = np.where(prob_pos == 1, 1, np.where(prob_neg == 1, -1, 0)) + +symbol = {1: ' + ', -1: ' - ', 0: ' ? '} +print("horizon | supply: q p | demand: q p") +for j in range(K_max + 1): + s = [symbol[sign_table[j, i, k]] for k in range(2) for i in range(2)] + print(f" {j} | {s[0]} {s[1]} | {s[2]} {s[3]}") +``` + +On impact, the theory delivers exactly the sign pattern in Uhlig's static example: a favorable supply +shock moves price and quantity in *opposite* directions, while a positive demand shock moves them in the +*same* direction. + +The theory says more than that. + +It also signs most responses at longer horizons: the supply responses *reverse* sign from horizon 2 on, +and the price response to a demand shock turns negative from horizon 3 on. + +At horizon 1 the supply responses are almost exactly zero and their signs depend on $(g_s, g_d)$, so the +theory is silent there. + +```{code-cell} python3 +print("prior probability that the horizon-1 supply responses are positive:") +print(f" q: {prob_pos[1, 0, 0]:.3f}, p: {prob_pos[1, 1, 0]:.3f}") +``` + +Leeper, Traum, and Walker {cite}`LeeperTraumWalker2017` report tables of exactly this kind --- for +example, the prior probability that a consumption multiplier is positive, horizon by horizon and model by +model. + +They put entries of 0 or 1 to the opposite use from Uhlig. + +For Leeper, Traum, and Walker, such an entry is a warning that a model will deliver that sign no matter +what the data say, so they prefer specifications whose induced priors leave signs open. + +For Uhlig, the same entry is a restriction that the theory licenses and that is worth imposing. + +## Object 2: autocovariances + +An econometrician with an infinitely long sample of $(q_t, p_t)$ could learn the autocovariance +function $\{\Gamma_j\}$, and nothing more. + +The induced prior over the autocovariance function is therefore the prior over everything that data +could ever reveal. + +We plot autocorrelations of $q$ and $p$ and the cross-correlation $\mathrm{corr}(q_{t+j}, p_t)$, using +$\Gamma_{-j} = \Gamma_j^\top$ for negative $j$. + +```{code-cell} python3 +sd = np.sqrt(np.stack([Γ_draws[:, 0, 0, 0], Γ_draws[:, 0, 1, 1]], axis=1)) # (N, 2) +ρ_q = Γ_draws[:, :, 0, 0] / sd[:, [0]]**2 +ρ_p = Γ_draws[:, :, 1, 1] / sd[:, [1]]**2 +# corr(q_{t+j}, p_t) for j >= 0 and corr(q_{t-j}, p_t) = corr(p_{t+j}, q_t) for j > 0 +cross_pos = Γ_draws[:, :, 0, 1] / (sd[:, [0]] * sd[:, [1]]) +cross_neg = Γ_draws[:, 1:, 1, 0] / (sd[:, [0]] * sd[:, [1]]) +cross = np.hstack([cross_neg[:, ::-1], cross_pos]) +lags = np.arange(-(T - 1), T) + +fig, axes = plt.subplots(1, 3, figsize=(14, 4)) +fan(axes[0], ρ_q, 'C0', 'induced prior') +axes[0].set_title("autocorrelation of $q$") +fan(axes[1], ρ_p, 'C1', 'induced prior') +axes[1].set_title("autocorrelation of $p$") +fan(axes[2], cross, 'C2', 'induced prior', x=lags) +axes[2].set_title("cross-correlation corr$(q_{t+j}, p_t)$") +for ax in axes: + ax.axhline(0, color='k', lw=0.5) + ax.set_xlabel("$j$") +plt.tight_layout() +plt.show() +``` + +Quantity is much more persistent than price, an outcome of the large supply-side adjustment cost. + +Quantity and price are strongly positively correlated contemporaneously, and price leads quantity. + +## Object 3: the population VAR + +By Wold's theorem, $z_t$ has a vector autoregression + +$$ +z_t = \sum_{j=1}^{\infty} A_j z_{t-j} + a_t, \qquad E a_t a_t^\top = V, +$$ + +with moving-average representation $z_t = \sum_{j \ge 0} C_j a_{t-j}$, $C_0 = I$. + +The VAR coefficients $A_j$, the innovation covariance $V$, and the Wold coefficients $C_j$ are all +functions of the autocovariance function alone. + +So the prior over $(g_s, g_d)$ also induces a prior over the reduced-form VAR, an idea that Del Negro +and Schorfheide {cite}`DelNegroSchorfheide2004` used to construct priors for VARs from equilibrium +models. + +The VAR has infinitely many lags, but its coefficients decay quickly. + +```{code-cell} python3 +size = np.abs(A_var_draws).max(axis=(2, 3)) # largest |entry| of A_j, lag by lag +fig, ax = plt.subplots(figsize=(7, 4)) +fan(ax, np.log10(size), 'C0', 'induced prior', x=np.arange(1, T + 1)) +ax.set_xlabel("lag $j$") +ax.set_ylabel("$\\log_{10} \\max |A_j|$") +ax.set_title("size of the VAR coefficients") +plt.tight_layout() +plt.show() +``` + +### The innovation covariance matrix + +Next we look at the induced prior over the three distinct elements of $V$, expressed as two standard +deviations and a correlation. + +```{code-cell} python3 +sd_a = np.sqrt(np.stack([V_draws[:, 0, 0], V_draws[:, 1, 1]], axis=1)) +corr_a = V_draws[:, 0, 1] / (sd_a[:, 0] * sd_a[:, 1]) +V_true = truth['V'] + +fig, axes = plt.subplots(1, 3, figsize=(14, 3.5)) +axes[0].hist(sd_a[:, 0], bins=40, density=True, color='C0', alpha=0.7) +axes[0].axvline(np.sqrt(V_true[0, 0]), color='k', ls='--') +axes[0].set_title("std. dev. of the $q$ innovation") +axes[1].hist(sd_a[:, 1], bins=40, density=True, color='C1', alpha=0.7) +axes[1].axvline(np.sqrt(V_true[1, 1]), color='k', ls='--') +axes[1].set_title("std. dev. of the $p$ innovation") +axes[2].hist(corr_a, bins=40, density=True, color='C2', alpha=0.7) +axes[2].axvline(V_true[0, 1] / np.sqrt(V_true[0, 0] * V_true[1, 1]), + color='k', ls='--') +axes[2].set_title("correlation of the innovations") +plt.tight_layout() +plt.show() +``` + +The innovations to quantity and price are almost perfectly correlated for every parameter value in the +prior's support. + +```{code-cell} python3 +print(f"range of corr(a_q, a_p) over the prior draws: " + f"[{corr_a.min():.4f}, {corr_a.max():.4f}]") +``` + +Demand shocks have a much larger variance than supply shocks and move $q$ and $p$ in the same +direction, so they dominate the one-step-ahead prediction errors. + +Uhlig {cite}`Uhlig2017` shows that in a static market a nearly perfect positive price–quantity +correlation is the case in which sign restrictions are most informative; we'll return to that point +below. + +How does $V$ compare with the covariance $D_0 D_0^\top$ of the agents' own one-step-ahead surprises +$D_0 \epsilon_t$? + +In {doc}`supply_demand_var` we saw that the econometrician's innovations have the larger covariance, +because the agents' shocks are not fundamental for $(q_t, p_t)$. + +```{code-cell} python3 +Σ_struct = np.einsum('nij,nkj->nik', D_draws[:, 0], D_draws[:, 0]) # D_0 D_0' +gap = V_draws - Σ_struct +print("eigenvalues of V - D_0 D_0' are nonnegative in every draw:", + np.all(np.linalg.eigvalsh(gap) > -1e-10)) +for i, name in enumerate(["q", "p"]): + rel = gap[:, i, i] / V_draws[:, i, i] + print(f"(V - D_0 D_0')_{name}{name} / V_{name}{name}: " + f"median {np.median(rel):.4f}, range [{rel.min():.4f}, {rel.max():.4f}]") +``` + +The gap is small relative to $V$ --- less than one percent of the variance of the quantity innovation. + +But we'll see that the gap is large relative to the contribution of the *supply* shock, and that is what +matters for recovering the supply shock from a VAR. + +### Two Cholesky orthogonalizations + +The most common way to turn VAR innovations into "structural" shocks is a recursive orthogonalization. + +* **$q$ first.** Factor $V = L_q L_q^\top$ with $L_q$ lower triangular. The first orthogonalized shock is + the innovation in $q_t$; the second is the part of the innovation in $p_t$ orthogonal to it, and it does + not move $q_t$ on impact. +* **$p$ first.** Reorder the variables as $(p_t, q_t)$ and factor. The first shock is the innovation in + $p_t$; the second is the part of the innovation in $q_t$ orthogonal to it, and it does not move $p_t$ on + impact. + +In each case the impulse responses are $C_j L$ for the corresponding impact matrix $L$. + +We normalize signs as Uhlig does, so that each shock raises quantity on impact; when a shock does not +move quantity on impact, we normalize it so that it *lowers* price, as a favorable supply shock does. + +```{code-cell} python3 +P_swap = np.array([[0, 1], [1, 0]]) + +def normalize(A): + "Flip columns so that q rises on impact, or, if q does not move, p falls." + A = A.copy() + for k in range(A.shape[1]): + key = A[0, k] if abs(A[0, k]) > 1e-10 else -A[1, k] + A[:, k] *= np.sign(key) + return A + +def cholesky_impact(V, first): + "Impact matrix in (q, p) order; column 0 is the shock ordered first." + if first == 'q': + L = np.linalg.cholesky(V) + else: + L = P_swap @ np.linalg.cholesky(P_swap @ V @ P_swap) + return normalize(L) + +chol_irfs = {} +for first in ['q', 'p']: + L_draws = np.array([cholesky_impact(V, first) for V in V_draws]) + chol_irfs[first] = np.einsum('ntij,njk->ntik', C_draws, L_draws) + +chol_true = {first: truth['C'] @ cholesky_impact(truth['V'], first) + for first in ['q', 'p']} +``` + +Because demand shocks dominate both innovations, the shock ordered *first* in either ordering is mostly +a demand shock, and the shock ordered *second* is the natural candidate for a supply shock. + +We therefore compare the first Cholesky shock with the demand shock and the second Cholesky shock with +the supply shock. + +```{code-cell} python3 +def compare_plot(first): + fig, axes = plt.subplots(2, 2, figsize=(12, 7)) + pairs = [(1, 0), (0, 1)] # (structural shock, Cholesky shock) + other = 'p' if first == 'q' else 'q' + chol_names = [f"innovation in ${first}$ (ordered first)", + f"${other}$ innovation orthogonal to ${first}$ (ordered second)"] + for col, (k_struct, k_chol) in enumerate(pairs): + for i in range(2): + ax = axes[i, col] + fan(ax, D_draws[:, :, i, k_struct], 'gray', + f"structural: {shock_names[k_struct]}") + fan(ax, chol_irfs[first][:, :, i, k_chol], 'C3', + f"VAR, {first} first: {chol_names[k_chol]}") + ax.axhline(0, color='k', lw=0.5) + ax.set_title(f"response of {var_names[i]}") + ax.set_xlabel("periods") + axes[0, col].legend(fontsize=8) + plt.tight_layout() + plt.show() +``` + +First the ordering that puts $q_t$ first. + +```{code-cell} python3 +compare_plot('q') +``` + +Then the ordering that puts $p_t$ first. + +```{code-cell} python3 +compare_plot('p') +``` + +The left columns show that, in both orderings, the first Cholesky shock nearly reproduces the response to +the demand shock. + +The induced prior bands are almost indistinguishable --- the $p$-first ordering does especially well, +because the price innovation is almost entirely a demand surprise. + +The right columns tell a different story. + +Neither ordering produces anything that resembles the response to a supply shock. + +* With $q$ first, the second shock does not move quantity on impact --- by construction --- while a + supply shock raises it; the second shock lowers price on impact by roughly nine times as much as a supply + shock does, and its quantity trough is about twice as deep. +* With $p$ first, the second shock does not move price on impact --- again by construction --- while a + supply shock lowers it; the second shock overstates the impact on quantity, and its quantity response + misses the sign reversal that the structural response displays from horizon 2 on. + +These discrepancies hold for every parameter value in the prior's support: both induced priors are +concentrated, but they are concentrated in different places. + +The numbers at the true parameters confirm this. + +```{code-cell} python3 +np.set_printoptions(precision=4, suppress=True) +print("impact responses (rows q, p) at the true parameters\n") +print("structural (supply, demand):\n", truth['D'][0]) +print("\nq-first Cholesky (first, second):\n", chol_true['q'][0]) +print("\np-first Cholesky (first, second):\n", chol_true['p'][0]) +``` + +## Can any rotation recover the structural shocks? + +A Cholesky factorization is just one rotation. + +Could *some* rotation $R$ of the VAR innovations recover the structural impulse responses? + +Uhlig's Proposition A.1 {cite}`Uhlig2005` says that a vector $d \in \mathbb{R}^2$ is an impulse vector +of the VAR --- a column of some $A$ with $A A^\top = V$ --- if and only if $d = L r$ with $\|r\| = 1$, +that is, if and only if + +$$ +\| L^{-1} d \| = 1 . +$$ + +So we can ask, draw by draw, whether the structural impact vectors --- the columns $d_s, d_d$ of $D_0$ +--- are admissible impulse vectors. + +```{code-cell} python3 +L_q_draws = np.linalg.cholesky(V_draws) +norms = np.linalg.norm(np.linalg.solve(L_q_draws, D_draws[:, 0]), axis=1) # (N, 2) + +fig, axes = plt.subplots(1, 2, figsize=(12, 3.5)) +for k, ax in enumerate(axes): + ax.hist(norms[:, k], bins=40, density=True, color=f'C{k}', alpha=0.7) + ax.set_title(f"$\\|L^{{-1}} d\\|$ for the {shock_names[k]}") +plt.tight_layout() +plt.show() + +for k in range(2): + print(f"{shock_names[k][:6]}: ||L^-1 d|| in " + f"[{norms[:, k].min():.4f}, {norms[:, k].max():.4f}]") +``` + +For the demand shock the norm is one, up to the accuracy of our computations: the demand shock is +(almost) fundamental for $(q_t, p_t)$, and a suitable rotation of the VAR innovations recovers it. + +For the supply shock the norm is about $0.83$ throughout the support of the prior. + +No rotation of the VAR innovations can deliver the structural supply impact vector, because that vector +is "too short". + +The reason is the one identified in {doc}`supply_demand_var`: because quantity adjusts sluggishly, a supply +surprise is revealed to an econometrician only gradually, so the one-step-ahead VAR innovation contains +only part of it, mixed with old news. + +The structural moving average $z_t = \sum_j D_j \epsilon_{t-j}$ and the Wold moving average +$z_t = \sum_j C_j a_{t-j}$ imply the same autocovariances, but $\{D_j\}$ is not of the form $\{C_j L R\}$ +for any orthogonal $R$. + +## Sign restrictions motivated by theory + +Now consider a second econometrician who has an infinite sample from the economy at the true parameter +values, and so knows $V$ and $\{C_j\}$ exactly. + +This econometrician is not willing to impose the whole structural model, but accepts the sign +restrictions that the theory delivers with prior probability one --- the table that we computed above. + +Following Uhlig's Principles 1 and 2, the econometrician imposes what is known and nothing more. + +The econometrician applies Uhlig's draw-and-reject algorithm: + +1. draw $r$ uniformly from the unit circle and form the orthogonal matrix $R = [\,r,\ r^\perp\,]$, where + $r^\perp$ is $r$ rotated by ninety degrees; +2. compute the impact matrix $A = L R$, flipping the sign of each column so that quantity rises on + impact, and the impulse responses + $C_j A$, treating the first column as the supply shock and the second as the demand shock; +3. keep the draw if the responses satisfy every restriction in the table at horizons + $j = 0, \ldots, K$; +4. repeat many times. + +Because $r$ ranges over the whole circle and signs are normalized afterwards, this procedure visits every +orthogonal $R$, including those that swap the roles of the two columns. + +Because the likelihood is flat over the admissible rotations in an infinite sample, the kept draws +form both the identified set and, with the Haar prior, the econometrician's posterior (Uhlig's Principle 9). + +```{code-cell} python3 +def sign_identified(C, V, table, K, n_draws=20_000, seed=0): + """ + Uhlig's draw-and-reject algorithm. + + Returns the impulse responses (draws, T, 2, 2) of the kept rotations, + with shock 0 = supply and shock 1 = demand, and the acceptance rate. + """ + rng = np.random.default_rng(seed) + L = np.linalg.cholesky(V) + x = rng.standard_normal((n_draws, 2)) + r = x / np.linalg.norm(x, axis=1, keepdims=True) # uniform on the circle + R = np.stack([np.stack([r[:, 0], r[:, 1]], 1), + np.stack([-r[:, 1], r[:, 0]], 1)], axis=2) # columns r, r_perp + A = L @ R + A = A * np.sign(A[:, [0], :]) # q rises on impact + irf = np.einsum('tij,njk->ntik', C, A) # (n_draws, T, 2, 2) + + ok = np.all(irf[:, :K + 1] * table[:K + 1] >= 0, axis=(1, 2, 3)) + return irf[ok], ok.mean() +``` + +We impose the restrictions on impact only ($K = 0$) and for six periods ($K = 5$). + +```{code-cell} python3 +sets = {} +for K in [0, K_max]: + sets[K], acc = sign_identified(truth['C'], truth['V'], sign_table, K) + print(f"K = {K}: acceptance rate {acc:.4f}") +``` + +Most draws are rejected. + +That is good news, not bad: as Uhlig's Principle 7 puts it, when a lot of draws are rejected, +identification is sharp. + +We plot the identified sets as the range between the smallest and largest response at each horizon, as in +Figure 2 of {cite}`Uhlig2005`, and compare them with the true structural responses and the $p$-first +Cholesky responses. + +```{code-cell} python3 +fig, axes = plt.subplots(2, 2, figsize=(12, 7)) +p_first_pair = {0: 1, 1: 0} # structural shock -> p-first Cholesky column +for k in range(2): + for i in range(2): + ax = axes[i, k] + for K, color in [(0, 'C0'), (K_max, 'C3')]: + s = sets[K][:, :, i, k] + ax.fill_between(range(T), s.min(0), s.max(0), color=color, + alpha=0.3, lw=0, label=f"sign-identified set, K = {K}") + ax.plot(truth['D'][:, i, k], 'k--', lw=2, label='true structural response') + ax.plot(chol_true['p'][:, i, p_first_pair[k]], 'C2', lw=1.5, + label='p-first Cholesky') + ax.axhline(0, color='k', lw=0.5) + ax.set_title(f"response of {var_names[i]} to {shock_names[k]}") + ax.set_xlabel("periods") + ax.set_xlim(0, 12) +axes[0, 0].legend(fontsize=8) +plt.tight_layout() +plt.show() +``` + +The demand shock is identified almost exactly. + +As Uhlig's analysis of a static market predicts, the near-perfect positive correlation of the price and +quantity innovations leaves very few rotations that move price and quantity in opposite directions for +one shock and in the same direction for the other. + +The supply shock is a different matter. + +With restrictions on impact only, the identified set is wide: on impact the price response can be +anywhere from zero to almost nine times its true value. + +Imposing the restrictions for $K = 5$ periods --- restrictions that are *correct*, since the theory implies +them for every parameter value in the prior's support --- narrows the set dramatically. + +But the narrower set *excludes* the true response: the true response of quantity lies outside it at +every horizon, and the true response of price from horizon 2 on. + +One edge of the $K = 5$ set is the $p$-first Cholesky response, which satisfies the restriction on the +impact response of price only weakly, with a response of exactly zero. + +```{code-cell} python3 +s5 = sets[K_max][:, :, :, 0] +inside = (s5.min(0) <= truth['D'][:, :, 0] + 1e-12) & \ + (truth['D'][:, :, 0] <= s5.max(0) + 1e-12) +print("is the true supply response inside the K = 5 set?") +print("horizon: ", np.arange(8)) +print("quantity: ", inside[:8, 0]) +print("price: ", inside[:8, 1]) +``` + +The explanation is the non-fundamentalness that we diagnosed above. + +The sign restrictions are true statements about the structural responses $D_j$. + +But the econometrician can only search over responses of the form $C_j L r$, and the true supply response +is not among them. + +The sign restrictions pick out the rotations that best mimic the signs of a supply shock, and those rotations +get the magnitudes wrong. + +Sign restrictions are a way to deal with an identification problem *within* the set of rotations of the +VAR innovations; they offer no protection against the possibility that the shocks of interest are not +rotations of the VAR innovations at all. + +Uhlig {cite}`Uhlig2017` explicitly sets aside "the thorny issue of fundamentalness"; our market shows +why it deserves attention. + +### Robustness across the prior + +Is the exclusion of the truth special to $(g_s, g_d) = (10, 0.1)$? + +We repeat the exercise for a few hundred draws from the prior, treating each in turn as the truth, and +record whether the true impact response of quantity to a supply shock lies in the $K = 5$ identified set. + +```{code-cell} python3 +n_check = 300 +covered = np.empty(n_check, dtype=bool) +for n in range(n_check): + kept, _ = sign_identified(C_draws[n], V_draws[n], sign_table, K_max, + n_draws=5_000, seed=n) + lo, hi = kept[:, 0, 0, 0].min(), kept[:, 0, 0, 0].max() + covered[n] = lo <= D_draws[n, 0, 0, 0] <= hi +print(f"fraction of draws in which the K = {K_max} set contains the true " + f"impact response of q to a supply shock: {covered.mean():.3f}") +``` + +Throughout the support of the prior, the theory-motivated sign restrictions identify a set that misses +the true response. + +## Widening the prior + +So far the modeler has been fairly confident: $g_s$ is known to within $\pm 20$ percent and $g_d$ to within +$\pm 50$ percent. + +What happens to the induced priors when the modeler is less sure? + +We compare the baseline prior with two wider uniform priors. + +```{code-cell} python3 +priors = { + 'baseline': ((g_s_lo, g_s_hi), (g_d_lo, g_d_hi)), + 'wider': ((4.0, 20.0), (0.02, 0.3)), + 'widest': ((1.5, 40.0), (0.005, 1.0)), +} + +N_wide = 1_500 +wide = {'baseline': dict(g_s=g_s_draws, g_d=g_d_draws, + D=D_draws, V=V_draws, C=C_draws)} +for name in ['wider', 'widest']: + (gs_lo, gs_hi), (gd_lo, gd_hi) = priors[name] + rng_w = np.random.default_rng(2024) + gs = rng_w.uniform(gs_lo, gs_hi, N_wide) + gd = rng_w.uniform(gd_lo, gd_hi, N_wide) + objs = [model_objects(x, y) for x, y in zip(gs, gd)] + wide[name] = dict(g_s=gs, g_d=gd, + D=np.array([o['D'] for o in objs]), + V=np.array([o['V'] for o in objs]), + C=np.array([o['C'] for o in objs])) +``` + +### Bigger induced priors + +The figure overlays the three induced priors over the structural impulse responses. + +```{code-cell} python3 +colors = {'baseline': 'C0', 'wider': 'C1', 'widest': 'C3'} + +fig, axes = plt.subplots(2, 2, figsize=(12, 7)) +for name in ['widest', 'wider', 'baseline']: + (gs_lo, gs_hi), (gd_lo, gd_hi) = priors[name] + label = f"$g_s \\sim U[{gs_lo:g}, {gs_hi:g}]$, $g_d \\sim U[{gd_lo:g}, {gd_hi:g}]$" + for i in range(2): + for k in range(2): + fan(axes[i, k], wide[name]['D'][:, :, i, k], colors[name], label, + alpha=0.35) +for i in range(2): + for k in range(2): + axes[i, k].axhline(0, color='k', lw=0.5) + axes[i, k].set_title(f"response of {var_names[i]} to {shock_names[k]}") + axes[i, k].set_xlabel("periods") + axes[i, k].set_xlim(0, 12) +axes[0, 0].legend(fontsize=8) +plt.tight_layout() +plt.show() +``` + +The bands fan out as the prior widens, most of all for the impact responses to a supply shock and for +the response of quantity to a demand shock. + +Two features are worth noticing. + +First, the induced prior moves as well as spreads: under the widest prior the median response of +quantity to a demand shock is far below the baseline median. + +A uniform prior on $[1.5, 40]$ puts most of its mass on large values of $g_s$, at which suppliers adjust +output very slowly, so "uniform" is far from "uninformative" about impulse responses (we return to this +point {ref}`below `). + +Second, some signs become uncertain: under the widest prior the response of quantity to a demand shock +can turn negative after a few periods. + +To see how the parameters map into responses, we plot the impact responses for each draw from the +widest prior, coloring each point by its value of $g_s$. + +```{code-cell} python3 +D_w, gs_w, gd_w = wide['widest']['D'], wide['widest']['g_s'], wide['widest']['g_d'] + +fig, axes = plt.subplots(1, 2, figsize=(12, 4.5)) +for k, ax in enumerate(axes): + sc = ax.scatter(D_w[:, 0, 0, k], D_w[:, 0, 1, k], c=gs_w, s=6, cmap='plasma') + ax.set_xlabel("impact response of $q$") + ax.set_ylabel("impact response of $p$") + ax.set_title(f"impact responses to the {shock_names[k]}") +fig.colorbar(sc, ax=axes, label="$g_s$") +plt.show() +``` + +Each point is one economy. + +A larger supply-side adjustment cost $g_s$ shrinks the quantity response to *both* shocks, and the +uniform prior's concentration on large $g_s$ piles the draws up in the upper-left corner of each cloud. + +### Where the supply shock is fundamental + +The same draws let us map where the supply shock can be recovered from a VAR. + +We plot $\|L^{-1} d_s\|$ against $g_s$ and color each point by $g_d$. + +```{code-cell} python3 +V_w = wide['widest']['V'] +norms_w = np.linalg.norm(np.linalg.solve(np.linalg.cholesky(V_w), D_w[:, 0]), axis=1) + +fig, ax = plt.subplots(figsize=(9, 4.5)) +sc = ax.scatter(gs_w, norms_w[:, 0], c=gd_w, s=6, cmap='viridis') +ax.axhline(1, color='k', lw=0.5) +ax.set_xlabel("$g_s$") +ax.set_ylabel("$\\|L^{-1} d_s\\|$") +ax.set_title("can a rotation of the VAR innovations deliver the supply impact vector?") +fig.colorbar(sc, ax=ax, label="$g_d$") +plt.tight_layout() +plt.show() +``` + +For small $g_s$, $\|L^{-1} d_s\| = 1$: quantity adjusts quickly enough that a VAR innovation reveals +the supply surprise at once, and the supply shock is fundamental for $(q_t, p_t)$. + +Beyond a threshold that rises with $g_d$, the norm falls below one and keeps falling as $g_s$ grows. + +The baseline prior $g_s \in [8, 12]$ sits well inside the non-fundamental region. + +### What sign restrictions deliver under a wider prior + +A wider prior has a cost for the sign-restriction approach: fewer signs hold for *every* parameter value, +so the theory licenses fewer restrictions. + +```{code-cell} python3 +def theory_signs(D, K=K_max): + "Signs that hold with prior probability one: +1, -1, or 0 for '?'." + pos = (D[:, :K + 1] > 0).mean(axis=0) + neg = (D[:, :K + 1] < 0).mean(axis=0) + return np.where(pos == 1, 1, np.where(neg == 1, -1, 0)) + +table_w = theory_signs(D_w) +print("signs implied by the widest prior") +print("horizon | supply: q p | demand: q p") +for j in range(K_max + 1): + s = [symbol[table_w[j, i, k]] for k in range(2) for i in range(2)] + print(f" {j} | {s[0]} {s[1]} | {s[2]} {s[3]}") +``` + +Compared with the baseline table, several entries have become "?". + +Among the restrictions that survive is that a favorable supply shock *raises* price two periods later, +the reversal that the cost-shock process $B_s(L)$ builds in. + +We now treat each draw from the widest prior in turn as the truth, give the econometrician its population +VAR, impose the table above for $K = 5$, and record one of three outcomes: + +* the sign-identified set contains the true impact response of quantity to a supply shock, +* the set is nonempty but misses it, or +* no rotation satisfies the restrictions at all. + +```{code-cell} python3 +def classify(C, V, D, table, K=K_max): + """ + For each economy: 0 if no rotation is admissible, 1 if the identified set + misses the true impact response of q to a supply shock, 2 if it contains it. + """ + out = np.empty(len(D), dtype=int) + for n in range(len(D)): + kept, _ = sign_identified(C[n], V[n], table, K, n_draws=10_000, seed=n) + if len(kept) == 0: + out[n] = 0 + else: + lo, hi = kept[:, 0, 0, 0].min(), kept[:, 0, 0, 0].max() + out[n] = 2 if lo <= D[n, 0, 0, 0] <= hi else 1 + return out + +outcome_labels = ["no admissible rotation", "set misses the truth", + "set contains the truth"] +outcome_colors = ['C7', 'C3', 'C2'] + +def outcome_map(ax, g_s, g_d, outcome, title): + for code in range(3): + m = outcome == code + ax.scatter(g_s[m], g_d[m], s=8, color=outcome_colors[code], + label=f"{outcome_labels[code]} ({m.mean():.0%})") + ax.set_xlabel("$g_s$") + ax.set_ylabel("$g_d$") + ax.set_title(title) + ax.legend(loc='upper right', fontsize=9) + +C_w = wide['widest']['C'] +outcome = classify(C_w, V_w, D_w, table_w) + +fig, ax = plt.subplots(figsize=(9, 5)) +outcome_map(ax, gs_w, gd_w, outcome, + "sign restrictions implied by the widest prior, K = 5") +plt.tight_layout() +plt.show() +``` + +The parameter space splits into three regions. + +* For small $g_s$ (green), the restrictions work as intended: the identified set contains the truth. + This region includes the one where the supply shock is fundamental, and extends somewhat beyond it + because the widest prior licenses fewer restrictions, which leaves wider identified sets. +* For intermediate $g_s$ (red), the set is nonempty but, as at our baseline parameters, it misses the + truth. +* For large $g_s$ (gray), *no* rotation of the VAR innovations satisfies the restrictions. + +In the gray region, an econometrician who applies Uhlig's procedure to the population VAR finds that the +sign restrictions are inconsistent with the data and would reject the theory that implied them --- even +though every restriction is a true statement about how the economy responds to its shocks. + +Which restriction cannot be met? + +We drop the horizon-2 restriction on the price response to a supply shock and try again on the gray +draws. + +```{code-cell} python3 +table_drop = table_w.copy() +table_drop[2, 1, 0] = 0 # horizon 2, price, supply shock +gray = np.where(outcome == 0)[0] +nonempty = [len(sign_identified(C_w[n], V_w[n], table_drop, K_max, + n_draws=10_000, seed=n)[0]) > 0 for n in gray] +print(f"gray draws with a nonempty set after dropping that restriction: " + f"{np.mean(nonempty):.0%}") +``` + +The culprit is the delayed rise in price after a favorable supply shock. + +When suppliers adjust slowly, the VAR innovations reveal that part of the supply shock's propagation only +gradually, so no rotation of them can reproduce it while also matching the signs on impact. + +The lesson is that a broader prior can't rescue the sign-restriction approach here. + +Fewer restrictions survive, and for a large part of the prior's support the restrictions that survive +either miss the truth or cannot be satisfied at all. + +(ip_log_uniform)= +### A log-uniform prior on $g_s$ + +The widest prior spans more than an order of magnitude, $g_s \in [1.5, 40]$. + +For a parameter like this --- a positive scale parameter whose plausible values range over several +multiples --- a uniform prior is a strong opinion: it puts 65 percent of its mass on $[15, 40]$ and less +than 7 percent on $[1.5, 4]$, even though each interval spans the same *proportional* range, a factor of +$8/3$. + +A natural alternative is a **log-uniform** prior, under which $\log g_s$ is uniform on +$[\log 1.5, \log 40]$, so that every doubling of $g_s$ receives the same prior probability. + +Its density is proportional to $1/g_s$ on the support. + +We keep the uniform prior on $g_d$ and the same supports, and change only the *shape* of the prior on +$g_s$. + +```{code-cell} python3 +(gs_lo, gs_hi), (gd_lo, gd_hi) = priors['widest'] +rng_log = np.random.default_rng(2024) +gs_log = np.exp(rng_log.uniform(np.log(gs_lo), np.log(gs_hi), N_wide)) +gd_log = rng_log.uniform(gd_lo, gd_hi, N_wide) +objs = [model_objects(x, y) for x, y in zip(gs_log, gd_log)] +wide['log-uniform'] = dict(g_s=gs_log, g_d=gd_log, + D=np.array([o['D'] for o in objs]), + V=np.array([o['V'] for o in objs]), + C=np.array([o['C'] for o in objs])) + +gs_ticks = [2, 5, 10, 20, 40] + +fig, ax = plt.subplots(figsize=(9, 4)) +bins = np.geomspace(gs_lo, gs_hi, 30) # equally spaced in log g_s +w = np.full(N_wide, 1 / N_wide) +ax.hist(gs_w, bins=bins, weights=w, alpha=0.5, color='C3', label='uniform') +ax.hist(gs_log, bins=bins, weights=w, alpha=0.5, color='C4', label='log-uniform') +ax.set_xscale('log') +ax.set_xticks(gs_ticks, labels=[str(t) for t in gs_ticks]) +ax.set_xlabel("$g_s$ (log scale)") +ax.set_ylabel("fraction of draws") +ax.set_title("draws of $g_s$ under the two priors on $[1.5, 40]$") +ax.legend() +plt.tight_layout() +plt.show() + +for name, gs in [('uniform', gs_w), ('log-uniform', gs_log)]: + print(f"{name:12s}: median g_s = {np.median(gs):5.2f}, " + f"P(g_s < 4) = {np.mean(gs < 4):.2f}, P(g_s > 15) = {np.mean(gs > 15):.2f}") +``` + +On a logarithmic axis the log-uniform draws are spread evenly, while the uniform draws pile up at the +right. + +The induced priors over impulse responses change accordingly. + +```{code-cell} python3 +D_log = wide['log-uniform']['D'] +spec_colors = {'uniform': 'C3', 'log-uniform': 'C4'} + +fig, axes = plt.subplots(2, 2, figsize=(12, 7)) +for name, D_ in [('uniform', D_w), ('log-uniform', D_log)]: + for i in range(2): + for k in range(2): + fan(axes[i, k], D_[:, :, i, k], spec_colors[name], + f"{name} prior on $g_s \\in [1.5, 40]$", alpha=0.3) +for i in range(2): + for k in range(2): + axes[i, k].axhline(0, color='k', lw=0.5) + axes[i, k].set_title(f"response of {var_names[i]} to {shock_names[k]}") + axes[i, k].set_xlabel("periods") + axes[i, k].set_xlim(0, 12) +axes[0, 0].legend(fontsize=8) +plt.tight_layout() +plt.show() +``` + +Moving prior mass toward small adjustment costs raises the median quantity responses to both shocks and +widens their bands upward: economies in which suppliers adjust quickly now carry real prior weight. + +What about the sign restrictions? + +A sign holds with prior probability one if and only if it holds at every point of the support, apart +from a set of prior probability zero. + +Because both priors have positive density everywhere on the same support, they imply the *same* sign +restrictions. + +```{code-cell} python3 +table_log = theory_signs(D_log) +print("same sign table under the uniform and log-uniform priors:", + np.array_equal(table_log, table_w)) +``` + +So the econometrician who imposes the theory's sign restrictions does exactly the same thing under the two +priors, and the map from $(g_s, g_d)$ to the three outcomes is the same. + +What changes is how much prior probability falls in each region. + +```{code-cell} python3 +V_log, C_log = wide['log-uniform']['V'], wide['log-uniform']['C'] +outcome_log = classify(C_log, V_log, D_log, table_log) + +fig, ax = plt.subplots(figsize=(9, 5)) +outcome_map(ax, gs_log, gd_log, outcome_log, + "log-uniform prior on $g_s$: sign restrictions, K = 5") +ax.set_xscale('log') +ax.set_xticks(gs_ticks, labels=[str(t) for t in gs_ticks]) +ax.set_xlabel("$g_s$ (log scale)") +plt.tight_layout() +plt.show() + +norms_log = np.linalg.norm( + np.linalg.solve(np.linalg.cholesky(V_log), D_log[:, 0]), axis=1) +print(f"{'':12s} {'P(fundamental)':>15s}" + + ''.join(f"{lab:>25s}" for lab in outcome_labels)) +for name, nrm, out in [('uniform', norms_w, outcome), + ('log-uniform', norms_log, outcome_log)]: + print(f"{name:12s} {np.mean(nrm[:, 0] > 0.99):15.2f}" + + ''.join(f"{np.mean(out == c):25.2f}" for c in range(3))) +``` + +Here $P(\text{fundamental})$ is the prior probability that $\|L^{-1} d_s\| > 0.99$. + +On a logarithmic axis the three regions are laid out evenly, and the green region --- where the +theory-implied sign restrictions recover the true supply response --- is now the largest. + +The same sign restrictions, derived from the same theory with the same parameter support, succeed with +prior probability of roughly one quarter under one prior and about 60 percent under the other. + +This complements a point that Baumeister and Hamilton {cite}`BaumeisterHamilton2015` make about the +Haar prior over rotations: a prior that looks innocuous on one parameterization can be quite informative +about the objects we care about. + +Here the choice is between two equally "flat-looking" priors on a *structural* parameter, and it governs +how likely we think it is that a VAR can recover the supply shock at all. + +## Summary + +A Bayesian structural modeler who is uncertain about two adjustment-cost parameters has, implicitly, a +prior over every object that the model implies. + +Monte Carlo makes those induced priors visible. + +In our market: + +* The induced prior over **structural impulse responses** is concentrated and pins down a rich pattern of + signs, many of which persist beyond the impact period. +* The induced prior over the **population VAR** says that the innovations to price and quantity are almost + perfectly correlated, with a covariance matrix slightly larger than that of the agents' own + surprises. +* Under either **Cholesky ordering**, the first orthogonalized shock looks like the demand shock, but no + orthogonalized shock looks like the supply shock. +* Using the induced prior to generate **sign restrictions** in the spirit of Uhlig works well for the + demand shock, which is essentially fundamental for $(q_t, p_t)$, and fails for the supply shock, which + is not: imposing more correct restrictions shrinks the identified set onto the wrong answer. +* **Widening the prior** produces bigger, and shifted, induced priors and weakens the restrictions the + theory can license. Across the widest prior, sign restrictions recover the supply response only where + suppliers adjust relatively quickly, so that the supply shock is fundamental or close to it; elsewhere + the identified set misses the truth or is empty. +* **Changing the shape of the prior** from uniform to log-uniform on the same support leaves the + theory-implied sign restrictions unchanged --- they depend only on the support --- but moves prior mass + toward fast-adjusting suppliers, and so raises the prior probability that those restrictions recover the + supply response from roughly one quarter to about 60 percent. + +The structural modeler, who believes the whole model, can do better. + +With an infinite sample, the modeler can learn $(g_s, g_d)$ from the autocovariance function (see +{ref}`ip_ex3`) and then compute the structural responses directly from the model, without ever +rotating VAR innovations: *if you know it, impose it*. + +## Exercises + +```{exercise} +:label: ip_ex1 + +Exercise 2 of {doc}`supply_demand_var` showed that the market is (nearly) invertible when $g_s \le 1$: +quantity then adjusts quickly and reveals supply surprises at once. + +Replace the prior on $g_s$ by $g_s \sim U[0.2, 1]$, keeping $g_d \sim U[0.05, 0.15]$. + +1. Recompute the induced prior over $\|L^{-1} d_s\|$. +2. Recompute the sign table implied by the new prior. +3. Take $(g_s, g_d) = (0.6, 0.1)$ as the truth and apply Uhlig's algorithm with the new table and + $K = 5$. Does the identified set now contain the true supply response? +``` + +```{solution-start} ip_ex1 +:class: dropdown +``` + +We repeat the Monte Carlo with the new prior. + +```{code-cell} python3 +rng_ex = np.random.default_rng(42) +N_ex = 1_000 +gs_ex = rng_ex.uniform(0.2, 1.0, N_ex) +gd_ex = rng_ex.uniform(g_d_lo, g_d_hi, N_ex) +draws_ex = [model_objects(gs, gd) for gs, gd in zip(gs_ex, gd_ex)] +D_ex = np.array([d['D'] for d in draws_ex]) +V_ex = np.array([d['V'] for d in draws_ex]) + +norms_ex = np.linalg.norm( + np.linalg.solve(np.linalg.cholesky(V_ex), D_ex[:, 0]), axis=1) +print(f"||L^-1 d_s|| in [{norms_ex[:, 0].min():.4f}, {norms_ex[:, 0].max():.4f}]") + +pos_ex = (D_ex[:, :K_max + 1] > 0).mean(axis=0) +neg_ex = (D_ex[:, :K_max + 1] < 0).mean(axis=0) +table_ex = np.where(pos_ex == 1, 1, np.where(neg_ex == 1, -1, 0)) +print("\nhorizon | supply: q p | demand: q p") +for j in range(K_max + 1): + s = [symbol[table_ex[j, i, k]] for k in range(2) for i in range(2)] + print(f" {j} | {s[0]} {s[1]} | {s[2]} {s[3]}") +``` + +The supply impact vector is now an admissible impulse vector, $\|L^{-1} d_s\| \approx 1$. + +Now we apply the sign restrictions at $(g_s, g_d) = (0.6, 0.1)$. + +```{code-cell} python3 +truth_ex = model_objects(0.6, 0.1) +kept_ex, acc_ex = sign_identified(truth_ex['C'], truth_ex['V'], table_ex, K_max) +print(f"acceptance rate: {acc_ex:.4f}") + +lo, hi = kept_ex.min(0), kept_ex.max(0) +inside_ex = (lo - 1e-10 <= truth_ex['D']) & (truth_ex['D'] <= hi + 1e-10) +print("true supply response inside the set, horizons 0-7:") +print(" quantity:", inside_ex[:8, 0, 0]) +print(" price: ", inside_ex[:8, 1, 0]) + +fig, axes = plt.subplots(1, 2, figsize=(12, 4)) +for i, ax in enumerate(axes): + ax.fill_between(range(T), lo[:, i, 0], hi[:, i, 0], color='C3', alpha=0.3, + label=f'sign-identified set, K = {K_max}') + ax.plot(truth_ex['D'][:, i, 0], 'k--', label='true structural response') + ax.axhline(0, color='k', lw=0.5) + ax.set_title(f"response of {var_names[i]} to the supply shock, $g_s = 0.6$") + ax.set_xlim(0, 12) +axes[0].legend() +plt.tight_layout() +plt.show() +``` + +When the structural shocks are fundamental for $(q_t, p_t)$, the true structural responses are among the +rotations of the VAR innovations, so correct sign restrictions produce an identified set that contains +them. + +```{solution-end} +``` + +```{exercise} +:label: ip_ex2 + +Uhlig's Principle 6 {cite}`Uhlig2017` says that sign-restricting both shocks rather than one "can help: +sometimes a lot, sometimes not at all". + +At the true parameters $(g_s, g_d) = (10, 0.1)$ and with $K = 0$, compare the identified sets for the +impact responses when you impose + +1. only the restrictions on the supply shock, +2. only the restrictions on the demand shock, +3. both. + +Which restriction does the work, and why? +``` + +```{solution-start} ip_ex2 +:class: dropdown +``` + +We zero out the rows of the sign table that we don't want to impose. + +```{code-cell} python3 +cases = {"supply only": [0], "demand only": [1], "both": [0, 1]} +for name, shocks in cases.items(): + tab = np.zeros_like(sign_table) + tab[:, :, shocks] = sign_table[:, :, shocks] + kept, acc = sign_identified(truth['C'], truth['V'], tab, 0) + rng_s = [kept[:, 0, i, k].min() for k in range(2) for i in range(2)], \ + [kept[:, 0, i, k].max() for k in range(2) for i in range(2)] + print(f"{name:12s} acceptance {acc:.3f}") + for (i, k), lo_, hi_ in zip([(0, 0), (1, 0), (0, 1), (1, 1)], *rng_s): + print(f" impact of {shock_names[k][:6]} on {var_names[i][:5]:6s}: " + f"[{lo_:7.4f}, {hi_:7.4f}] truth {truth['D'][0, i, k]:7.4f}") +``` + +The supply restriction does almost all of the work. + +Because the innovations are almost perfectly positively correlated, the data cloud lies close to a line with +positive slope. + +Only rotations that put the "supply" impulse vector nearly orthogonal to that line can make price and +quantity move in opposite directions, and that forces the other impulse vector to lie almost along the +line, which pins down the demand shock. + +The demand restriction alone has little bite: almost every direction in the positive quadrant is consistent +with it. + +This is the case $\mathrm{corr}(P, Q) \to 1$ in Uhlig's Figure 7. + +```{solution-end} +``` + +```{exercise} +:label: ip_ex3 + +The structural modeler believes the whole model. + +Suppose the modeler sees an infinite sample from the economy at $(g_s, g_d) = (10, 0.1)$, and therefore +knows the autocovariances $\Gamma_0, \ldots, \Gamma_{T-1}$. + +On a grid of values $(g_s, g_d)$ covering the support of the prior, compute the distance + +$$ +\sum_{j=0}^{T-1} \| \Gamma_j(g_s, g_d) - \Gamma_j(10, 0.1) \|^2 +$$ + +and show that it has a unique minimum at the true values. + +What does this imply about the modeler's posterior in an infinite sample, and about the posterior over the +structural impulse responses? +``` + +```{solution-start} ip_ex3 +:class: dropdown +``` + +```{code-cell} python3 +gs_grid = np.linspace(g_s_lo, g_s_hi, 21) +gd_grid = np.linspace(g_d_lo, g_d_hi, 21) +Γ_target = truth['Γ'] + +dist = np.empty((len(gs_grid), len(gd_grid))) +for m, gs in enumerate(gs_grid): + for n, gd in enumerate(gd_grid): + dist[m, n] = np.sum((model_objects(gs, gd)['Γ'] - Γ_target)**2) + +m, n = np.unravel_index(dist.argmin(), dist.shape) +print(f"minimizer on the grid: g_s = {gs_grid[m]:.2f}, g_d = {gd_grid[n]:.3f}") + +fig, ax = plt.subplots(figsize=(7, 5)) +cs = ax.contourf(gd_grid, gs_grid, np.log10(np.maximum(dist, 1e-6)), levels=30) +ax.plot(g_d_true, g_s_true, 'w*', ms=14) +ax.set_xlabel("$g_d$") +ax.set_ylabel("$g_s$") +ax.set_title("$\\log_{10}$ distance between autocovariance functions") +fig.colorbar(cs) +plt.tight_layout() +plt.show() +``` + +The autocovariance function identifies $(g_s, g_d)$. + +The elongated valley shows that the autocovariances are less sensitive to movements in which $g_s$ and +$g_d$ rise together than to movements in which they go in opposite directions, so a finite sample would +leave the modeler with a positively correlated posterior. + +In an infinite sample the modeler's likelihood concentrates on the true values, so the posterior over +$(g_s, g_d)$ collapses to a point mass at $(10, 0.1)$, and so does the posterior over the structural +impulse responses, since they are a function of $(g_s, g_d)$. + +The modeler learns the true supply response exactly, including the part that no rotation of VAR +innovations can deliver, because the modeler does not use VAR rotations: the model maps the +parameters directly into the responses. + +This is the payoff from Uhlig's first principle, *if you know it, impose it*. + +The sign-restriction econometrician, who imposes less, learns less, and in this market learns something +wrong about the supply shock. + +```{solution-end} +``` + +```{exercise} +:label: ip_ex4 + +The widest prior on $g_d$, $U[0.005, 1]$, also spans a factor of 200. + +Put log-uniform priors on *both* $g_s \in [1.5, 40]$ and $g_d \in [0.005, 1]$. + +1. Check that the theory-implied sign table is unchanged. +2. Compute the prior probabilities of the three sign-restriction outcomes and compare them with those + under the uniform prior and under the prior that is log-uniform in $g_s$ only. +3. Explain the direction of the change, using the outcome maps above. +``` + +```{solution-start} ip_ex4 +:class: dropdown +``` + +```{code-cell} python3 +rng_ll = np.random.default_rng(99) +gs_ll = np.exp(rng_ll.uniform(np.log(1.5), np.log(40.0), N_wide)) +gd_ll = np.exp(rng_ll.uniform(np.log(0.005), np.log(1.0), N_wide)) +objs = [model_objects(x, y) for x, y in zip(gs_ll, gd_ll)] +D_ll = np.array([o['D'] for o in objs]) +V_ll = np.array([o['V'] for o in objs]) +C_ll = np.array([o['C'] for o in objs]) + +print("same sign table:", np.array_equal(theory_signs(D_ll), table_w)) +outcome_ll = classify(C_ll, V_ll, D_ll, table_w) + +print(f"\n{'':22s}" + ''.join(f"{lab:>25s}" for lab in outcome_labels)) +for name, out in [('uniform, uniform', outcome), + ('log-uniform, uniform', outcome_log), + ('log-uniform, log-unif.', outcome_ll)]: + print(f"{name:22s}" + ''.join(f"{np.mean(out == c):25.2f}" for c in range(3))) + +fig, ax = plt.subplots(figsize=(9, 5)) +outcome_map(ax, gs_ll, gd_ll, outcome_ll, + "log-uniform priors on $g_s$ and $g_d$: sign restrictions, K = 5") +ax.set_xscale('log') +ax.set_yscale('log') +ax.set_xticks(gs_ticks, labels=[str(t) for t in gs_ticks]) +ax.set_xlabel("$g_s$ (log scale)") +ax.set_ylabel("$g_d$ (log scale)") +plt.tight_layout() +plt.show() +``` + +The sign table is unchanged because the support is unchanged. + +A log-uniform prior on $g_d$ shifts mass toward *small* values of $g_d$. + +The outcome maps show that, for a given $g_s$, the green region extends to larger values of $g_s$ when +$g_d$ is large: the boundary between the green and red regions slopes upward. + +Moving prior mass toward small $g_d$ therefore moves mass out of the green region, and the probability that +the sign restrictions recover the supply response falls relative to the prior that is log-uniform in $g_s$ +alone, though it stays well above its value under the uniform prior. + +```{solution-end} +``` From 10ee640182b6b3d77b3c1297471a587b9f284520 Mon Sep 17 00:00:00 2001 From: John Stachurski Date: Tue, 13 Oct 2026 10:41:12 +1100 Subject: [PATCH 2/2] induced_priors: style and typo fixes from first-round review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implements the edits proposed in the review comment on #390: - typos and wording (overview list, truncated "quant" labels in printed output, Exercise 4 wording, missing Figure 7 citation, link to sdvar_ex2) - {cite:t} where the author is part of the sentence - \mathbb{E} / \mathbb{P} notation - mystnb captions and names on main-text figures; single-panel titles moved into captions; default figsize for single-panel figures; lw=2 - minor code tidying (one statement per line, qe.LQ, clearer names) - state the observed range of the supply-shock norm (0.80 to 0.87) - bib: add DOIs, title case for Uhlig2005, Honoré typed directly Co-Authored-By: Claude Opus 5.5 --- lectures/_static/quant-econ.bib | 22 ++-- lectures/induced_priors.md | 210 +++++++++++++++++++++++--------- 2 files changed, 168 insertions(+), 64 deletions(-) diff --git a/lectures/_static/quant-econ.bib b/lectures/_static/quant-econ.bib index 0dc00740..2cb54c37 100644 --- a/lectures/_static/quant-econ.bib +++ b/lectures/_static/quant-econ.bib @@ -3272,24 +3272,26 @@ @article{CoibionGorodnichenko2015 @article{Uhlig2005, author = {Uhlig, Harald}, - title = {What are the effects of monetary policy on output? Results from an agnostic identification procedure}, + title = {What Are the Effects of Monetary Policy on Output? Results from an Agnostic Identification Procedure}, journal = {Journal of Monetary Economics}, year = {2005}, volume = {52}, number = {2}, - pages = {381--419} + pages = {381--419}, + doi = {10.1016/j.jmoneco.2004.05.007} } @incollection{Uhlig2017, author = {Uhlig, Harald}, title = {Shocks, Sign Restrictions, and Identification}, booktitle = {Advances in Economics and Econometrics: Eleventh World Congress}, - editor = {Honor{\'e}, Bo and Pakes, Ariel and Piazzesi, Monika and Samuelson, Larry}, + editor = {Honoré, Bo and Pakes, Ariel and Piazzesi, Monika and Samuelson, Larry}, volume = {2}, chapter = {4}, pages = {95--127}, publisher = {Cambridge University Press}, - year = {2017} + year = {2017}, + doi = {10.1017/9781108227223.004} } @article{DelNegroSchorfheide2004, @@ -3299,7 +3301,8 @@ @article{DelNegroSchorfheide2004 year = {2004}, volume = {45}, number = {2}, - pages = {643--673} + pages = {643--673}, + doi = {10.1111/j.1468-2354.2004.00139.x} } @article{BaumeisterHamilton2015, @@ -3309,7 +3312,8 @@ @article{BaumeisterHamilton2015 year = {2015}, volume = {83}, number = {5}, - pages = {1963--1999} + pages = {1963--1999}, + doi = {10.3982/ECTA12356} } @article{LeeperTraumWalker2017, @@ -3319,7 +3323,8 @@ @article{LeeperTraumWalker2017 year = {2017}, volume = {107}, number = {8}, - pages = {2409--2454} + pages = {2409--2454}, + doi = {10.1257/aer.20111196} } @book{Geweke2010, @@ -3327,5 +3332,6 @@ @book{Geweke2010 title = {Complete and Incomplete Econometric Models}, publisher = {Princeton University Press}, address = {Princeton, NJ}, - year = {2010} + year = {2010}, + doi = {10.1515/9781400835249} } diff --git a/lectures/induced_priors.md b/lectures/induced_priors.md index 29984c1c..80609a73 100644 --- a/lectures/induced_priors.md +++ b/lectures/induced_priors.md @@ -48,9 +48,9 @@ In this lecture, we shall covariance matrix of its innovations and its impulse responses under two standard recursive (Cholesky) orthogonalizations, one that puts $q_t$ first and one that puts $p_t$ first, * compare the induced priors over the VAR impulse responses with those over the structural impulse - responses, and + responses, * use the induced prior to construct **sign restrictions motivated by economic theory** in the spirit - of Harald Uhlig {cite}`Uhlig2005,Uhlig2017`, and then ask what those sign restrictions can and cannot + of {cite:t}`Uhlig2005,Uhlig2017`, and then ask what those sign restrictions can and cannot recover from a VAR, and * widen the prior, and then change its shape from uniform to log-uniform, to see how the induced priors, the region in which the supply shock is fundamental, and the performance of sign restrictions @@ -78,15 +78,14 @@ We'll make these imports: import numpy as np import quantecon as qe import matplotlib.pyplot as plt -from quantecon import LQ from scipy.linalg import solve_discrete_lyapunov ``` ## Background: sign restrictions -Uhlig {cite}`Uhlig2005` proposed an "agnostic" way to identify shocks in a vector autoregression. +{cite:t}`Uhlig2005` proposed an "agnostic" way to identify shocks in a vector autoregression. -Let $u_t$ be the one-step-ahead prediction error of a VAR, with covariance matrix $V = E u_t u_t^\top$. +Let $u_t$ be the one-step-ahead prediction error of a VAR, with covariance matrix $V = \mathbb{E}[u_t u_t^\top]$. A researcher wants to write $u_t = A \epsilon_t$, where $\epsilon_t$ is a vector of mutually uncorrelated, unit-variance **structural** shocks, so that $V = A A^\top$. @@ -111,7 +110,7 @@ $R$ that satisfies the restrictions as admissible. Drawing $r$ uniformly from the unit sphere (the **Haar** measure) and discarding draws that violate the restrictions gives a sample from a prior that is flat on the admissible set. -In {cite}`Uhlig2017` he illustrates the logic with a static supply-and-demand example in which +{cite:t}`Uhlig2017` illustrates the logic with a static supply-and-demand example in which "supply slopes up, demand slopes down" delivers the sign pattern $$ @@ -227,14 +226,20 @@ def solve_equilibrium(h_s, h_d, g_s, g_d, β, a, B_s, B_d): A = np.zeros((n, n)) A[1:n_q, 0:n_q - 1] = np.eye(n_q - 1) # q lags shift A[n_q:, n_q:] = A_w - B = np.zeros((n, 1)); B[0, 0] = 1.0 # new q_{t-1} = q_t - C = np.zeros((n, 2)); C[n_q:, :] = C_w + B = np.zeros((n, 1)) + B[0, 0] = 1.0 # new q_{t-1} = q_t + C = np.zeros((n, 2)) + C[n_q:, :] = C_w def q_lag(k): # selector for q_{t-k} - e = np.zeros(n); e[k - 1] = 1.0; return e - - s_sel = np.zeros(n); s_sel[n_q:n_q + 4] = B_s # s_t = s_sel @ x - d_sel = np.zeros(n); d_sel[n_q + 4:] = B_d + e = np.zeros(n) + e[k - 1] = 1.0 + return e + + s_sel = np.zeros(n) + s_sel[n_q:n_q + 4] = B_s # s_t = s_sel @ x + d_sel = np.zeros(n) + d_sel[n_q + 4:] = B_d q1 = q_lag(1) a_vec = sum(a[k] * q_lag(k) for k in range(1, 5)) # lagged part of a(L)q_t @@ -245,7 +250,7 @@ def solve_equilibrium(h_s, h_d, g_s, g_d, β, a, B_s, B_d): N = (g_d * a[0] * a_vec / 2 - g_s * q1 / 2 + (s_sel + d_sel) / 2).reshape(1, n) - lq = LQ(Q, R, A, B, C, N=N, beta=β) + lq = qe.LQ(Q, R, A, B, C, N=N, beta=β) P, F, _ = lq.stationary_values() F = np.asarray(F).reshape(1, n) A_F = A - B @ F @@ -295,15 +300,15 @@ Monte Carlo is the natural tool: 2. compute $f(\theta_i)$, 3. treat $\{f(\theta_i)\}_{i=1}^N$ as a sample from the induced prior. -Geweke {cite}`Geweke2010` calls this **prior predictive analysis**. +{cite:t}`Geweke2010` calls this **prior predictive analysis**. -Leeper, Traum, and Walker {cite}`LeeperTraumWalker2017` use it to compute the range of +{cite:t}`LeeperTraumWalker2017` use it to compute the range of government-spending multipliers --- ratios of discounted impulse responses --- that alternative DSGE specifications can produce before they confront data. For impulse response functions we summarize the sample by pointwise quantiles, horizon by horizon. -As Uhlig {cite}`Uhlig2017` (his Principle 17) reminds us, a band built this way describes a +As {cite:t}`Uhlig2017` reminds us (his Principle 17), a band built this way describes a collection of *marginal* distributions, one for each horizon; the curve that joins the pointwise medians is generally not the impulse response for any single draw $\theta_i$. @@ -311,9 +316,9 @@ For each draw we compute four objects. * **Structural impulse responses** $D_j = G_z A_F^{\,j} \tilde C$, where $\tilde C = -C\, \mathrm{diag}(\sigma_s, \sigma_d)$ loads the sign-flipped, unit-standard-deviation shocks. -* **Autocovariances** $\Gamma_j = E\, z_{t+j} z_t^\top = G_z A_F^{\,j} \Sigma_X G_z^\top$, where +* **Autocovariances** $\Gamma_j = \mathbb{E}[z_{t+j} z_t^\top] = G_z A_F^{\,j} \Sigma_X G_z^\top$, where $\Sigma_X = A_F \Sigma_X A_F^\top + \tilde C \tilde C^\top$ is the stationary covariance of the state. -* **The innovation covariance** $V = E\, a_t a_t^\top$ of the population VAR for $z_t$. +* **The innovation covariance** $V = \mathbb{E}[a_t a_t^\top]$ of the population VAR for $z_t$. * **The Wold moving-average coefficients** $C_j$ of the population VAR, with $C_0 = I$. As in {doc}`supply_demand_var`, the last two come from the stationary Kalman filter, whose innovations @@ -386,6 +391,7 @@ def fan(ax, paths, color, label, x=None, alpha=0.25): var_names = ["quantity $q$", "price $p$"] shock_names = ["supply shock $\\tilde w_s$", "demand shock $\\tilde w_d$"] +var_short = ["q", "p"] ``` ## Object 1: structural impulse responses @@ -396,12 +402,18 @@ shocks. The dashed black lines are the responses at the true parameter values. ```{code-cell} python3 +--- +mystnb: + figure: + caption: Induced prior over structural impulse responses + name: fig-ip-structural-irfs +--- fig, axes = plt.subplots(2, 2, figsize=(12, 7)) for i in range(2): for k in range(2): ax = axes[i, k] fan(ax, D_draws[:, :, i, k], 'C0', 'induced prior: median, 5-95%') - ax.plot(truth['D'][:, i, k], 'k--', lw=1.5, label='true parameters') + ax.plot(truth['D'][:, i, k], 'k--', lw=2, label='true parameters') ax.axhline(0, color='k', lw=0.5) ax.set_title(f"response of {var_names[i]} to {shock_names[k]}") ax.set_xlabel("periods") @@ -425,6 +437,12 @@ A uniform prior over a parameter generally induces a *non-uniform* prior over th The histograms below show the induced priors over the four impact responses. ```{code-cell} python3 +--- +mystnb: + figure: + caption: Induced prior over impact responses + name: fig-ip-impact-hist +--- fig, axes = plt.subplots(2, 2, figsize=(12, 6)) for i in range(2): for k in range(2): @@ -444,7 +462,7 @@ The correlations of the impact responses with the parameter draws tell us. for i in range(2): for k in range(2): x = D_draws[:, 0, i, k] - print(f"impact of {shock_names[k][:6]} on {var_names[i][:5]:6s}: " + print(f"impact of {shock_names[k][:6]} on {var_short[i]}: " f"corr with g_s = {np.corrcoef(g_s_draws, x)[0, 1]:6.3f}, " f"corr with g_d = {np.corrcoef(g_d_draws, x)[0, 1]:6.3f}") ``` @@ -495,7 +513,7 @@ print("prior probability that the horizon-1 supply responses are positive:") print(f" q: {prob_pos[1, 0, 0]:.3f}, p: {prob_pos[1, 1, 0]:.3f}") ``` -Leeper, Traum, and Walker {cite}`LeeperTraumWalker2017` report tables of exactly this kind --- for +{cite:t}`LeeperTraumWalker2017` report tables of exactly this kind --- for example, the prior probability that a consumption multiplier is positive, horizon by horizon and model by model. @@ -518,6 +536,12 @@ We plot autocorrelations of $q$ and $p$ and the cross-correlation $\mathrm{corr} $\Gamma_{-j} = \Gamma_j^\top$ for negative $j$. ```{code-cell} python3 +--- +mystnb: + figure: + caption: Induced prior over correlations + name: fig-ip-autocorr +--- sd = np.sqrt(np.stack([Γ_draws[:, 0, 0, 0], Γ_draws[:, 0, 1, 1]], axis=1)) # (N, 2) ρ_q = Γ_draws[:, :, 0, 0] / sd[:, [0]]**2 ρ_p = Γ_draws[:, :, 1, 1] / sd[:, [1]]**2 @@ -550,7 +574,7 @@ Quantity and price are strongly positively correlated contemporaneously, and pri By Wold's theorem, $z_t$ has a vector autoregression $$ -z_t = \sum_{j=1}^{\infty} A_j z_{t-j} + a_t, \qquad E a_t a_t^\top = V, +z_t = \sum_{j=1}^{\infty} A_j z_{t-j} + a_t, \qquad \mathbb{E}[a_t a_t^\top] = V, $$ with moving-average representation $z_t = \sum_{j \ge 0} C_j a_{t-j}$, $C_0 = I$. @@ -558,19 +582,24 @@ with moving-average representation $z_t = \sum_{j \ge 0} C_j a_{t-j}$, $C_0 = I$ The VAR coefficients $A_j$, the innovation covariance $V$, and the Wold coefficients $C_j$ are all functions of the autocovariance function alone. -So the prior over $(g_s, g_d)$ also induces a prior over the reduced-form VAR, an idea that Del Negro -and Schorfheide {cite}`DelNegroSchorfheide2004` used to construct priors for VARs from equilibrium +So the prior over $(g_s, g_d)$ also induces a prior over the reduced-form VAR, an idea that +{cite:t}`DelNegroSchorfheide2004` used to construct priors for VARs from equilibrium models. The VAR has infinitely many lags, but its coefficients decay quickly. ```{code-cell} python3 +--- +mystnb: + figure: + caption: Size of the VAR coefficients + name: fig-ip-var-size +--- size = np.abs(A_var_draws).max(axis=(2, 3)) # largest |entry| of A_j, lag by lag -fig, ax = plt.subplots(figsize=(7, 4)) +fig, ax = plt.subplots() fan(ax, np.log10(size), 'C0', 'induced prior', x=np.arange(1, T + 1)) ax.set_xlabel("lag $j$") ax.set_ylabel("$\\log_{10} \\max |A_j|$") -ax.set_title("size of the VAR coefficients") plt.tight_layout() plt.show() ``` @@ -581,6 +610,12 @@ Next we look at the induced prior over the three distinct elements of $V$, expre deviations and a correlation. ```{code-cell} python3 +--- +mystnb: + figure: + caption: Induced prior over innovation moments + name: fig-ip-innov +--- sd_a = np.sqrt(np.stack([V_draws[:, 0, 0], V_draws[:, 1, 1]], axis=1)) corr_a = V_draws[:, 0, 1] / (sd_a[:, 0] * sd_a[:, 1]) V_true = truth['V'] @@ -611,7 +646,7 @@ print(f"range of corr(a_q, a_p) over the prior draws: " Demand shocks have a much larger variance than supply shocks and move $q$ and $p$ in the same direction, so they dominate the one-step-ahead prediction errors. -Uhlig {cite}`Uhlig2017` shows that in a static market a nearly perfect positive price–quantity +{cite:t}`Uhlig2017` shows that in a static market a nearly perfect positive price–quantity correlation is the case in which sign restrictions are most informative; we'll return to that point below. @@ -712,12 +747,24 @@ def compare_plot(first): First the ordering that puts $q_t$ first. ```{code-cell} python3 +--- +mystnb: + figure: + caption: Cholesky ($q$ first) versus structural responses + name: fig-ip-chol-q +--- compare_plot('q') ``` Then the ordering that puts $p_t$ first. ```{code-cell} python3 +--- +mystnb: + figure: + caption: Cholesky ($p$ first) versus structural responses + name: fig-ip-chol-p +--- compare_plot('p') ``` @@ -769,6 +816,12 @@ So we can ask, draw by draw, whether the structural impact vectors --- the colum --- are admissible impulse vectors. ```{code-cell} python3 +--- +mystnb: + figure: + caption: Induced prior over $\|L^{-1} d\|$ + name: fig-ip-impulse-norms +--- L_q_draws = np.linalg.cholesky(V_draws) norms = np.linalg.norm(np.linalg.solve(L_q_draws, D_draws[:, 0]), axis=1) # (N, 2) @@ -787,7 +840,7 @@ for k in range(2): For the demand shock the norm is one, up to the accuracy of our computations: the demand shock is (almost) fundamental for $(q_t, p_t)$, and a suitable rotation of the VAR innovations recovers it. -For the supply shock the norm is about $0.83$ throughout the support of the prior. +For the supply shock the norm lies between $0.80$ and $0.87$ throughout the support of the prior. No rotation of the VAR innovations can deliver the structural supply impact vector, because that vector is "too short". @@ -864,10 +917,16 @@ That is good news, not bad: as Uhlig's Principle 7 puts it, when a lot of draws identification is sharp. We plot the identified sets as the range between the smallest and largest response at each horizon, as in -Figure 2 of {cite}`Uhlig2005`, and compare them with the true structural responses and the $p$-first +Figure 2 of {cite:t}`Uhlig2005`, and compare them with the true structural responses and the $p$-first Cholesky responses. ```{code-cell} python3 +--- +mystnb: + figure: + caption: Sign-identified sets at the true parameters + name: fig-ip-sign-sets +--- fig, axes = plt.subplots(2, 2, figsize=(12, 7)) p_first_pair = {0: 1, 1: 0} # structural shock -> p-first Cholesky column for k in range(2): @@ -878,7 +937,7 @@ for k in range(2): ax.fill_between(range(T), s.min(0), s.max(0), color=color, alpha=0.3, lw=0, label=f"sign-identified set, K = {K}") ax.plot(truth['D'][:, i, k], 'k--', lw=2, label='true structural response') - ax.plot(chol_true['p'][:, i, p_first_pair[k]], 'C2', lw=1.5, + ax.plot(chol_true['p'][:, i, p_first_pair[k]], 'C2', lw=2, label='p-first Cholesky') ax.axhline(0, color='k', lw=0.5) ax.set_title(f"response of {var_names[i]} to {shock_names[k]}") @@ -911,8 +970,8 @@ impact response of price only weakly, with a response of exactly zero. ```{code-cell} python3 s5 = sets[K_max][:, :, :, 0] -inside = (s5.min(0) <= truth['D'][:, :, 0] + 1e-12) & \ - (truth['D'][:, :, 0] <= s5.max(0) + 1e-12) +inside = ((s5.min(0) <= truth['D'][:, :, 0] + 1e-12) + & (truth['D'][:, :, 0] <= s5.max(0) + 1e-12)) print("is the true supply response inside the K = 5 set?") print("horizon: ", np.arange(8)) print("quantity: ", inside[:8, 0]) @@ -933,7 +992,7 @@ Sign restrictions are a way to deal with an identification problem *within* the VAR innovations; they offer no protection against the possibility that the shocks of interest are not rotations of the VAR innovations at all. -Uhlig {cite}`Uhlig2017` explicitly sets aside "the thorny issue of fundamentalness"; our market shows +{cite:t}`Uhlig2017` explicitly sets aside "the thorny issue of fundamentalness"; our market shows why it deserves attention. ### Robustness across the prior @@ -994,6 +1053,12 @@ for name in ['wider', 'widest']: The figure overlays the three induced priors over the structural impulse responses. ```{code-cell} python3 +--- +mystnb: + figure: + caption: Induced priors under wider priors + name: fig-ip-wide-irfs +--- colors = {'baseline': 'C0', 'wider': 'C1', 'widest': 'C3'} fig, axes = plt.subplots(2, 2, figsize=(12, 7)) @@ -1034,6 +1099,12 @@ To see how the parameters map into responses, we plot the impact responses for e widest prior, coloring each point by its value of $g_s$. ```{code-cell} python3 +--- +mystnb: + figure: + caption: Impact responses under the widest prior + name: fig-ip-wide-scatter +--- D_w, gs_w, gd_w = wide['widest']['D'], wide['widest']['g_s'], wide['widest']['g_d'] fig, axes = plt.subplots(1, 2, figsize=(12, 4.5)) @@ -1058,15 +1129,20 @@ The same draws let us map where the supply shock can be recovered from a VAR. We plot $\|L^{-1} d_s\|$ against $g_s$ and color each point by $g_d$. ```{code-cell} python3 +--- +mystnb: + figure: + caption: Where the supply shock is fundamental + name: fig-ip-fundamental +--- V_w = wide['widest']['V'] norms_w = np.linalg.norm(np.linalg.solve(np.linalg.cholesky(V_w), D_w[:, 0]), axis=1) -fig, ax = plt.subplots(figsize=(9, 4.5)) +fig, ax = plt.subplots() sc = ax.scatter(gs_w, norms_w[:, 0], c=gd_w, s=6, cmap='viridis') ax.axhline(1, color='k', lw=0.5) ax.set_xlabel("$g_s$") ax.set_ylabel("$\\|L^{-1} d_s\\|$") -ax.set_title("can a rotation of the VAR innovations deliver the supply impact vector?") fig.colorbar(sc, ax=ax, label="$g_d$") plt.tight_layout() plt.show() @@ -1112,6 +1188,12 @@ VAR, impose the table above for $K = 5$, and record one of three outcomes: * no rotation satisfies the restrictions at all. ```{code-cell} python3 +--- +mystnb: + figure: + caption: Sign-restriction outcomes, uniform prior + name: fig-ip-outcomes-uniform +--- def classify(C, V, D, table, K=K_max): """ For each economy: 0 if no rotation is admissible, 1 if the identified set @@ -1131,22 +1213,22 @@ outcome_labels = ["no admissible rotation", "set misses the truth", "set contains the truth"] outcome_colors = ['C7', 'C3', 'C2'] -def outcome_map(ax, g_s, g_d, outcome, title): +def outcome_map(ax, g_s, g_d, outcome, title=None): for code in range(3): m = outcome == code ax.scatter(g_s[m], g_d[m], s=8, color=outcome_colors[code], label=f"{outcome_labels[code]} ({m.mean():.0%})") ax.set_xlabel("$g_s$") ax.set_ylabel("$g_d$") - ax.set_title(title) + if title is not None: + ax.set_title(title) ax.legend(loc='upper right', fontsize=9) C_w = wide['widest']['C'] outcome = classify(C_w, V_w, D_w, table_w) -fig, ax = plt.subplots(figsize=(9, 5)) -outcome_map(ax, gs_w, gd_w, outcome, - "sign restrictions implied by the widest prior, K = 5") +fig, ax = plt.subplots() +outcome_map(ax, gs_w, gd_w, outcome) plt.tight_layout() plt.show() ``` @@ -1208,6 +1290,12 @@ We keep the uniform prior on $g_d$ and the same supports, and change only the *s $g_s$. ```{code-cell} python3 +--- +mystnb: + figure: + caption: Draws of $g_s$ under two priors + name: fig-ip-gs-draws +--- (gs_lo, gs_hi), (gd_lo, gd_hi) = priors['widest'] rng_log = np.random.default_rng(2024) gs_log = np.exp(rng_log.uniform(np.log(gs_lo), np.log(gs_hi), N_wide)) @@ -1220,7 +1308,7 @@ wide['log-uniform'] = dict(g_s=gs_log, g_d=gd_log, gs_ticks = [2, 5, 10, 20, 40] -fig, ax = plt.subplots(figsize=(9, 4)) +fig, ax = plt.subplots() bins = np.geomspace(gs_lo, gs_hi, 30) # equally spaced in log g_s w = np.full(N_wide, 1 / N_wide) ax.hist(gs_w, bins=bins, weights=w, alpha=0.5, color='C3', label='uniform') @@ -1229,7 +1317,6 @@ ax.set_xscale('log') ax.set_xticks(gs_ticks, labels=[str(t) for t in gs_ticks]) ax.set_xlabel("$g_s$ (log scale)") ax.set_ylabel("fraction of draws") -ax.set_title("draws of $g_s$ under the two priors on $[1.5, 40]$") ax.legend() plt.tight_layout() plt.show() @@ -1245,6 +1332,12 @@ right. The induced priors over impulse responses change accordingly. ```{code-cell} python3 +--- +mystnb: + figure: + caption: Uniform versus log-uniform prior on $g_s$ + name: fig-ip-log-irfs +--- D_log = wide['log-uniform']['D'] spec_colors = {'uniform': 'C3', 'log-uniform': 'C4'} @@ -1288,12 +1381,17 @@ priors, and the map from $(g_s, g_d)$ to the three outcomes is the same. What changes is how much prior probability falls in each region. ```{code-cell} python3 +--- +mystnb: + figure: + caption: Sign-restriction outcomes, log-uniform prior + name: fig-ip-outcomes-log +--- V_log, C_log = wide['log-uniform']['V'], wide['log-uniform']['C'] outcome_log = classify(C_log, V_log, D_log, table_log) -fig, ax = plt.subplots(figsize=(9, 5)) -outcome_map(ax, gs_log, gd_log, outcome_log, - "log-uniform prior on $g_s$: sign restrictions, K = 5") +fig, ax = plt.subplots() +outcome_map(ax, gs_log, gd_log, outcome_log) ax.set_xscale('log') ax.set_xticks(gs_ticks, labels=[str(t) for t in gs_ticks]) ax.set_xlabel("$g_s$ (log scale)") @@ -1310,7 +1408,7 @@ for name, nrm, out in [('uniform', norms_w, outcome), ''.join(f"{np.mean(out == c):25.2f}" for c in range(3))) ``` -Here $P(\text{fundamental})$ is the prior probability that $\|L^{-1} d_s\| > 0.99$. +Here `P(fundamental)` is the prior probability $\mathbb{P}\{\|L^{-1} d_s\| > 0.99\}$. On a logarithmic axis the three regions are laid out evenly, and the green region --- where the theory-implied sign restrictions recover the true supply response --- is now the largest. @@ -1318,7 +1416,7 @@ theory-implied sign restrictions recover the true supply response --- is now the The same sign restrictions, derived from the same theory with the same parameter support, succeed with prior probability of roughly one quarter under one prior and about 60 percent under the other. -This complements a point that Baumeister and Hamilton {cite}`BaumeisterHamilton2015` make about the +This complements a point that {cite:t}`BaumeisterHamilton2015` make about the Haar prior over rotations: a prior that looks innocuous on one parameterization can be quite informative about the objects we care about. @@ -1364,7 +1462,7 @@ rotating VAR innovations: *if you know it, impose it*. ```{exercise} :label: ip_ex1 -Exercise 2 of {doc}`supply_demand_var` showed that the market is (nearly) invertible when $g_s \le 1$: +{ref}`Exercise 2 ` of {doc}`supply_demand_var` showed that the market is (nearly) invertible when $g_s \le 1$: quantity then adjusts quickly and reveals supply surprises at once. Replace the prior on $g_s$ by $g_s \sim U[0.2, 1]$, keeping $g_d \sim U[0.05, 0.15]$. @@ -1422,7 +1520,7 @@ fig, axes = plt.subplots(1, 2, figsize=(12, 4)) for i, ax in enumerate(axes): ax.fill_between(range(T), lo[:, i, 0], hi[:, i, 0], color='C3', alpha=0.3, label=f'sign-identified set, K = {K_max}') - ax.plot(truth_ex['D'][:, i, 0], 'k--', label='true structural response') + ax.plot(truth_ex['D'][:, i, 0], 'k--', lw=2, label='true structural response') ax.axhline(0, color='k', lw=0.5) ax.set_title(f"response of {var_names[i]} to the supply shock, $g_s = 0.6$") ax.set_xlim(0, 12) @@ -1466,11 +1564,11 @@ for name, shocks in cases.items(): tab = np.zeros_like(sign_table) tab[:, :, shocks] = sign_table[:, :, shocks] kept, acc = sign_identified(truth['C'], truth['V'], tab, 0) - rng_s = [kept[:, 0, i, k].min() for k in range(2) for i in range(2)], \ - [kept[:, 0, i, k].max() for k in range(2) for i in range(2)] + bounds = ([kept[:, 0, i, k].min() for k in range(2) for i in range(2)], + [kept[:, 0, i, k].max() for k in range(2) for i in range(2)]) print(f"{name:12s} acceptance {acc:.3f}") - for (i, k), lo_, hi_ in zip([(0, 0), (1, 0), (0, 1), (1, 1)], *rng_s): - print(f" impact of {shock_names[k][:6]} on {var_names[i][:5]:6s}: " + for (i, k), lo_, hi_ in zip([(0, 0), (1, 0), (0, 1), (1, 1)], *bounds): + print(f" impact of {shock_names[k][:6]} on {var_short[i]}: " f"[{lo_:7.4f}, {hi_:7.4f}] truth {truth['D'][0, i, k]:7.4f}") ``` @@ -1486,7 +1584,7 @@ line, which pins down the demand shock. The demand restriction alone has little bite: almost every direction in the positive quadrant is consistent with it. -This is the case $\mathrm{corr}(P, Q) \to 1$ in Uhlig's Figure 7. +This is the case $\mathrm{corr}(P, Q) \to 1$ in Figure 7 of {cite:t}`Uhlig2017`. ```{solution-end} ``` @@ -1564,7 +1662,7 @@ wrong about the supply shock. ```{exercise} :label: ip_ex4 -The widest prior on $g_d$, $U[0.005, 1]$, also spans a factor of 200. +The widest prior on $g_d$, $U[0.005, 1]$, spans an even wider range, a factor of 200. Put log-uniform priors on *both* $g_s \in [1.5, 40]$ and $g_d \in [0.005, 1]$. @@ -1612,7 +1710,7 @@ The sign table is unchanged because the support is unchanged. A log-uniform prior on $g_d$ shifts mass toward *small* values of $g_d$. -The outcome maps show that, for a given $g_s$, the green region extends to larger values of $g_s$ when +The outcome maps show that the green region extends to larger values of $g_s$ when $g_d$ is large: the boundary between the green and red regions slopes upward. Moving prior mass toward small $g_d$ therefore moves mass out of the green region, and the probability that