measurement_models#
Series: lecture-python.myst
File:
lectures/measurement_models.mdAudit date: 2026-08-26
Corpus snapshot:
e25fdf2345Categories audited: writing, math, code, figures, references, links, admonitions (JAX out of scope)
Overall score: 7.5 / 10
Priority: HIGH
Score breakdown#
Category |
Score |
One-line note |
|---|---|---|
Writing |
3/10 |
|
Math |
4/10 |
|
Code |
6/10 |
|
JAX |
out of scope |
JAX rules target |
Figures |
9.5/10 |
|
References |
10/10 |
no mechanical violations detected. |
Links |
10/10 |
no mechanical violations detected. |
Admonitions |
10/10 |
no mechanical violations detected. |
Issues#
Critical#
None found.
High severity#
[qe-code-001] (reviewer) — Follow PEP8 unless closer to mathematical notation. Count: 6. Lines: 422, 492, 717, 742, 1109, 1296. Example:
steady_state_kalman(492-517) hand-iterates the Riccati equation up to 200,000 times to a tolerance of 1e-13 on a two-by-two state, and exits the loop silently whenmax_iteris exhausted (505-513) - whilescipy.linalgis already imported at 83 and exposessolve_discrete_are, which appears nowhere in the file.Tis bound to a matrix at 742 (T = psi[j] @ P) in a file where.Tis the transpose attribute on almost every line and where \(T\) is the sample length in the likelihood at 762 and 1071. The observation dimension is hardcoded seven times rather than derived from the arguments -np.zeros((2, 3))at 717,np.eye(3)at 720, 725, 1106 and 1110,np.zeros((3, 3, n_horizons))at 738,np.zeros((3, 3))at 740,np.eye(2)at 1111 - someasured_wold_coeffsandfev_contributionssilently only work for this model.filtered_wold_coeffs(A, C, K, ...)at 1109 shadows the globalsAandCwith its own parameters.displayis called at 422, 480-482, 836, 854, 904, 1139, 1156 and 1181 but never imported (84 brings in onlyLatex), so every one of those cells relies on the IPython builtin.t = np.arange(1, 81)at 1296 hardcodes 81 against theT=80passed at 1278 and shadows the loop variabletused insidesimulate_series. Andfev_contributionsrecomputespsi[j] @ Pinside the horizon loop (741-744), doing O(H^2) work where an accumulator does O(H).[qe-code-002] — Use Unicode symbols for Greek letters in code. Count: 8. Lines: 725, 728, 730, 733, 742, 1110, 1113, 1115. Example: spelled-out
psi.[qe-math-010 (proposed)] — Blackboard \mathbb{P}, \mathbb{E}, \mathbb{V} with braces. Count: 10. Lines: 141, 628, 629, 630, 631, 640, 954, 1025, 1026, 1027. Example: bare expectation
E \sum.[qe-writing-003] (reviewer) — Maintain logical flow. Count: 5. Lines: 236, 343, 599, 1028, 1404. Example: five breaks, four of them checkable line by line. (a) The state equation is written with the shock dated one period too early: 343 and 590 give \(x_{t+1} = A x_t + \varepsilon_t\) and 348 defines \(\varepsilon_t = [0, \theta_t]^\top\), so with \(A\)’s second row equal to \([0,0]\) the system says \(\theta_{t+1} = \theta_t\) - a constant, contradicting assumption 1 at 198 that \(\theta_t\) is white noise. The code has it right in both places: the docstring at 495 writes
x_{t+1} = A x_t + w_{t+1}, andtable2_irf(411-419) advances the state withx = A @ xand no shock after \(t=0\). (b) 599-601 writes \(E \begin{bmatrix}\varepsilon_t\end{bmatrix}\begin{bmatrix}\varepsilon_t^\top & \bar\nu_t^\top\end{bmatrix}\) and equates it to a two-by-two block matrix: \(\bar\nu_t\) is missing from the left column vector, so the dimensions on the two sides do not match. © 1028 defines \([K_2, S_2] = \text{kalmanfilter}(A, G, Q_2, R_2, 0)\), a routine that appears nowhere; the implementation issteady_state_kalman(492) and its signature is(A, C_obs, Q, R, W). (d) 1404-1407 and 1435 disagree about the same result: “Independent measurement errors break this accounting identity … The Kalman filter approximately restores it” against “the residual for the filtered data is numerically 0”. It is exact, not approximate, and for a reason the lecture never gives - all three filtered series are rows of \(C\hat x_t\) for one \(\hat x_t\), and rows 2 and 3 of \(C\) (357-361) sum to row 1, so the identity is an algebraic property of \(C\) rather than an artefact of good filtering. (e) 236 and 293 are the identical display carrying two different labels,income_processandincome_process_ma; the second could cite the first.[qe-writing-004] — Avoid unnecessary capitalization in narrative text. Count: 12. Lines: 1053, 1059, 1142, 1149, 1151, 1184, 1197, 1218, 1398, 1443, …. Example: mid-sentence ‘Model’.
[qe-writing-008] — Remove excessive whitespace between words. Count: 20. Lines: 63, 64, 69, 75, 134, 137, 159, 164, 277, 326, …. Example: 2 spaces.
Medium severity#
[qe-fig-001] — Do not set figure size unless necessary. Count: 2. Lines: 1286, 1419. Example: figsize=.
[qe-math-009] (reviewer) — Choose simplicity in mathematical notation. Count: 3. Lines: 951, 988, 335. Example: \(\tilde z_t\) names two different objects and both stay live. 578 defines it as the quasi-difference of the measured data, \(\tilde z_t = \bar z_{t+1} - D\bar z_t\), and it is used that way at 591, 619, 630-631, 775-776 and 780; then 951 reassigns it to the agency’s reported filtered data, \(\tilde z_t = G\hat x_t\), used that way at 962, 984, 1016, 1047, 1060 and 1066-1067 - so the Model 1 recursion at 775 and the Model 2 recursion at 1066 apply the same symbol to unrelated series. \(\eta_t\) does the same: 437-441 makes it the innovation to the measurement-error AR(1) with covariance \(\Sigma_\eta\), used at 579, and 984-989 makes it a second, unrelated white noise (“type 2 … “typos””) with covariance \(R_2\). And the same number and the same object each get two names: \(\beta\), \(f^{-1}\) and \(1/f\) all denote the discount factor (218, 226, 231, 287, 376, 406), and the third element of \(z_t\) is \(\Delta k_t\) at 335 and 418 but \(\bar k_{t+1} - \bar k_t\) at 276 and 286, though 360’s row of \(C\) makes it \(k_{t+1}-k_t\).
[qe-math-011 (proposed)] — Distribution names in plain letters, not \mathcal / \mathbb. Count: 1. Lines: 328. Example: decorated distribution
\mathcal{N}.[qe-writing-001] — Use one sentence per paragraph. Count: 2. Lines: 45, 276. Example: 2 sentences in one paragraph.
[qe-writing-002] (reviewer) — Keep writing clear, concise, and valuable. Count: 4. Lines: 44, 52, 535, 1435. Example: the
{note}at 44-47 puts its content on the directive line -```{note} This is why econometrics estimation is often called an ''inverse'' problem, ...- so MyST reads that first sentence as the admonition’s argument, i.e. its title, and renders a note titled with a 25-word sentence; the block also contains three sentences in one paragraph and uses''...''(two apostrophes) for quotation marks four times. 52-55 is a 40-word sentence whose core is hard to recover (“a model of the data reporting agency, one that is workable enough that we can determine the mapping induced jointly by the dynamic economic model and the measurement process to the probability law for the measured data”). 535-558 then promises the same thing four separate times before constructing anything - “it is convenient to obtain its population vector autoregression” (535), “carries insights about how to interpret estimated vector autoregressions” (543-546), “is also useful as an intermediate step” (548), “also proves useful as an intermediate step” (553) - six one-sentence paragraphs of anticipation for one construction. And 1435 says the filtered residual “is numerically 0” three paragraphs after 1407 said the filter “approximately restores” the identity.[qe-writing-005] (reviewer) — Use bold for definitions, italic for emphasis. Count: 4. Lines: 532, 843, 929, 934. Example: the two definitions in the Model 2 section are italic where the rule asks for bold: 929 “- seasonal adjustment: if the components of \(v_t\) have strong seasonals …” and 934 “- data revisions: if \(z_t\) contains current and lagged values …”, each introducing a term the paragraph then explains. 532 does the same for the lecture’s central label - “We refer to this as Model 1” - while the same label appears unemphasised at 66, 1204 and 1450 and its counterpart “Model 2” is never emphasised at all (68, 1142, 1197, 1206, 1455), so one name gets three treatments. In the other direction, Granger causality is bolded at 843 and again at 1450, after being used plainly eight times from 209 onward (263, 265, 296-300, 318, 462, 844), so the bold arrives 630 lines after the term’s first use and then repeats. Only 613 (innovations representation) is a definition bolded at first use, and the four emphases - not at 969, cannot at 1198 and 1456 - are correctly italic.
[qe-writing-007] (reviewer) — Use visual elements to enhance understanding. Count: 3. Lines: 836, 863, 1188. Example: the lecture’s central results are delivered as six LaTeX tables and never plotted. Each
display(Latex(...))at 836, 904, 1139 and 1181 renders three side-by-side panels of a 20-by-3 or 14-by-3 array - roughly 180 numbers per table - and the reader is then asked to compare them across pages: 1142-1149 compares Model 2’s decomposition with Model 1’s, and 1188-1191 says “Readers can compare the left table with the table in the{ref}`true-impulse-responses`section above. / The numbers are essentially the same.” Comparing two 14-by-3 tables 770 lines apart by eye is exactly what a two-panel line plot removes, and the lecture already has matplotlib configured and seven well-captioned figures in its simulation section (1301-1396), so the tooling is in hand. The same applies to the eigenvalue claims: 866-867 (“The first eigenvalue is much larger than the others”) and 1126-1127 (“\(V_2\) is close to singular with one dominant eigenvalue”) each rest on a bareprintof three numbers at 863 and 1161, where one small bar chart of the two spectra side by side would carry the whole Model 1 versus Model 2 comparison that the lecture is about.
Low severity#
None found.
Strengths#
The two measurement models are built as a matched pair and the comparison is made possible by construction: the same \([A, C, Q]\) (373-393) and the same \([D, \Sigma_\eta, R]\) (467-476) feed both,
steady_state_kalmanis called once for each (673, 1106), and the two Wold representations, decompositions and impulse responses are then rendered with the same three helper functions (fev_table,wold_response_table,df_to_latex_array) - so any difference the reader sees is a difference in the reporting scheme and nothing else.The quasi-differencing trick is motivated before it is used: 562-564 says why the textbook Kalman filter cannot be applied (\(v_t\) is serially correlated), 566-568 names the alternative (augment the state, with a pointer to Appendix B of the source), and 570-572 says which route this lecture takes - so the transformation at 576-581 arrives as a choice with a reason rather than an algebraic surprise.
The economics is reduced to two named results before any state-space algebra appears:
{eq}`friedman_consumption`at 226 is identified as Friedman’s geometric-distributed-lag consumption function with the decay coefficient equal to \(\beta\) (239-241),{eq}`mm_accelerator`at 231 as the distributed-lag accelerator (243-245), and 247-248 ties the second to{cite:t}`Chow1968`and the companion lecture - so the reader knows what the model is for.The puzzle that motivates the whole lecture is stated as a falsifiable contrast: 258-263 shows that with accurate data the spectral density matrix has rank one and no variable Granger-causes any other, 265-266 states the contrary empirical fact in one sentence, and 268-269 names the resolution - after which 299-321 explains the mechanism (better-measured series carry more information about the common index) before the numbers arrive, so 839-847 confirms a prediction instead of describing an output.
The error-free impulse response is computed first and labelled for later reference (
(true-impulse-responses)=at 395), and its economics is read off explicitly at 404-408 - “the full impact on net income \(y_n\) occurs at lag 0, while consumption adjusts by only \(1 - f^{-1} \approx 0.048\) and investment absorbs the remainder” - which is what makes the Model 2 comparison at 1184-1191 meaningful.Every one of the seven simulation figures carries full
mystnbmetadata - caption,name, and animage: alt:description (1301-1396) - which is whyqe-fig-005measures zero for a file with eight figures, and the naming is consistent (fig-true-measured-consumption,fig-true-filtered-capital, and so on).The simulation is designed to test the population claims rather than to illustrate them: it is seeded (1221), it reports the three measured series against the truth in the order the measurement-error variances predict (1340-1342, investment worst, income best), and it closes with the accounting-identity residual (1409-1433), which is a check the population tables cannot give.
Citations are consistently textual where the sentence names the author and the report measures zero
qe-ref-001violations across thirteen{cite:t}uses (61, 74, 194, 247, 253, 268, 462, 521, 568, 571, 1164, 1439, 1452), and the two{doc}cross-references at 62-64 point at the companion lectures that carry the accelerator evidence.
Recommended actions#
Fix the timing in the state equation: 343 and 590 should read \(x_{t+1} = A x_t + \varepsilon_{t+1}\), or 348 should define \(\varepsilon_t = [0, \theta_{t+1}]^\top\) - as written the system implies \(\theta_{t+1} = \theta_t\), contradicting 198, and the code’s own docstring at 495 already uses the correct convention. The same shift shows up in
simulate_series, where 1258 computes \(\bar z_t - D\bar z_{t-1}\) under the namez_bar_twhile 578 defines \(\tilde z_t = \bar z_{t+1} - D\bar z_t\).Rename one of the two \(\tilde z_t\) (578 versus 951) and one of the two \(\eta_t\) (437 versus 988) - both pairs are simultaneously live, and the Model 1 and Model 2 recursions at 775 and 1066 use the same symbol for different series.
Repair
{eq}`model1_covs`at 599-601: the left factor should be \(\begin{bmatrix}\varepsilon_t \\ \bar\nu_t\end{bmatrix}\), otherwise a one-by-one matrix is equated to a two-by-two block matrix.Replace
\text{kalmanfilter}(A, G, Q_2, R_2, 0)at 1028 with the function the lecture actually defines,steady_state_kalman(A, G, Q_2, R_2, 0)(492), so the display and the code agree.Plot the results instead of only tabulating them: two line plots of the Model 1 and Model 2 forecast-error-variance shares against the horizon would replace the four 180-number LaTeX tables at 836, 904, 1139 and 1181, and would let 1142-1149 and 1188-1191 point at a picture instead of asking the reader to compare tables 770 lines apart.
Say why the accounting identity holds exactly for the filtered data - rows 2 and 3 of \(C\) sum to row 1 (357-361), so any \(C\hat x_t\) satisfies it - and reconcile 1407 (“approximately restores”) with 1435 (“numerically 0”).
Use
scipy.linalg.solve_discrete_are(already available through the import at 83) instead of the 200,000-iteration loop at 492-517, or at minimum raise whenmax_iteris exhausted; derive the observation dimension from the arguments rather than hardcoding 3 and 2 at 717, 720, 725, 738, 740, 1110 and 1111; rename the matrixTat 742; and importdisplayalongsideLatexat 84.Sweep the presentation items: move the
{note}content off the directive line at 44 (it is currently rendered as the note’s title) and replace the four''...''quote pairs, bold the definitions at 532, 929 and 934 and move Granger causality to its first use at 209, renamepsito\u03c8in the six placesqe-code-002flags so the code matches \(\psi_j\) at 713 and 1050, add braces to the nineE[sites (628-631, 640, 954, 1025-1027), lower-case the twelve capitalised common nouns, clear the twenty double-space runs, and give the residual figure at 1412-1417 theimage: alt:metadata that the other seven figures all carry.