svd_intro#

  • Series: lecture-python.myst

  • File: lectures/svd_intro.md

  • Audit date: 2026-08-26

  • Corpus snapshot: e25fdf2345

  • Categories audited: writing, math, code, figures, links, admonitions (JAX out of scope)

  • Overall score: 7.8 / 10

  • Priority: HIGH

Score breakdown#

Category

Score

One-line note

Writing

3/10

qe-writing-006 ×8; qe-writing-004 ×9; qe-writing-001 ×7, +5 more.

Math

9.5/10

qe-math-009 ×1.

Code

7.5/10

qe-code-001 ×6.

JAX

out of scope

JAX rules target lecture-jax.

Figures

6.5/10

qe-fig-003 ×6; qe-fig-008 ×4; qe-fig-001 ×2.

References

N/A

no citations in this lecture.

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: 300, 310, 431, 505, 908, 957. Example: compare_pca_svd reads attributes that do not exist: the class stores self.\ud835\udf06 (U+1D706 MATHEMATICAL ITALIC SMALL LAMDA) at 872, self.\ud835\udf0e (U+1D70E) at 898 and self.\ud835\udf16 (U+1D716) at 881, while 957-958, 975 and 978 read da.\u03bb, da.\u03c3 and da.\u03b5 - the ordinary Greek code points U+03BB / U+03C3 / U+03B5. Two visually identical but distinct identifiers per letter; the function would raise AttributeError on its first line of output, which is consistent with it never having been run. On top of that: V names numpy’s third return value at 300, 430 and 492 even though np.linalg.svd returns \(V^\top\) - the class itself gets this right and calls it VT at 891 - so 319 prints the untransposed matrix under the label "V.T:"; np.linalg.svd(A,full_matrices=True) omits the space after the comma at 300, 430, 431, 492 and 493; SShat=np.diag(Shat) at 505 has no spaces around =; 908 writes self.\ud835\udf0e ** 2 with spaces around the exponentiation operator, which the rule asks be written a**b; 431 and 493 leave one space before an inline comment where 430 uses two; import numpy as np is repeated mid-file at 428 and 490 after the import cell at 280-284; and diag_sign is defined at 936, after the class that calls it at 874 and 900, with a single-quoted docstring at 937 where every other docstring in the file is tripled.

  • [qe-fig-003] — No matplotlib embedded titles. Count: 6. Lines: 963, 965, 968, 974, 976, 979. Example: .suptitle.

  • [qe-writing-001] — Use one sentence per paragraph. Count: 7. Lines: 136, 138, 140, 474, 557, 595, 614. Example: 2 sentences in one paragraph.

  • [qe-writing-002] (reviewer) — Keep writing clear, concise, and valuable. Count: 5. Lines: 57, 349, 557, 585, 991. Example: 57 is a 46-word sentence with three nested “in which” clauses before its main clause (“In the \(m > > n\) case in which there are many more attributes \(m\) than individuals \(n\) and when we are in a time-series setting in which \(n\) equals the number of time periods covered in the data set \(X\), we’ll proceed in a different way”); 585 is 36 words joined by a comma splice (“Then because we want to extract the relationships between variables rather than just their magnitude, in other words, we want to know how they can explain each other, we compute the covariance matrix of \(B\)”); 557 says the same thing twice in one sentence - “to help better visualize patterns in data, using statistical tools to capture the most important patterns in data” - and hides the content behind “apply certain steps”; 991 reads “the \( (X^\top X) \) matrix is not not invertible”; and 349 both mis-states the theorem it is summarising and takes 40 words to do it - the Eckart-Young approximant \(\hat X_r\) of {eq}`eq:Ekart` is an \(m \times n\) matrix of rank \(r\), not “a full rank \(p \times p\) matrix”, and “that in not full rank” is a typo for “that is not”.

  • [qe-writing-004] — Avoid unnecessary capitalization in narrative text. Count: 9. Lines: 29, 115, 353, 403, 479, 622, 1041. Example: mid-sentence ‘Vector’.

  • [qe-writing-005] (reviewer) — Use bold for definitions, italic for emphasis. Count: 8. Lines: 96, 102, 117, 447, 472, 551, 559, 680. Example: there is not one italic in the file - a search for single-asterisk emphasis returns nothing - so bold carries definitions and emphasis alike. Definitions are correctly bold (singular value decomposition 18, left singular vectors 80, conjugate-transpose 88, matrix norms 337, economy / reduced 363), but so is pure emphasis: rotates and angles between vectors / lengths of vectors at 96, rescales at 97, rotating / rescaling / rotating at 102-104, tall-skinny / short-fat at 383, data at 543, attributes at 553, pre-processing at 680. 551 and 553 then reach for a third syntax, __times__ and __individuals__, for what is plainly emphasis. And bold is used as a heading eight times where a real heading belongs - **Road Ahead** 117, **Properties:** 447, **Remarks:** 472, and the five **Step N: ...:** labels at 559, 583, 591, 612, 622, which are the structure of the PCA recipe and should be ### headings so they appear in the TOC.

  • [qe-writing-006] — Capitalize lecture titles properly. Count: 8. Lines: 31, 61, 127, 324, 359, 509, 537, 672. Example: H2 Title Case: ‘The Setting’ (Setting).

  • [qe-writing-008] — Remove excessive whitespace between words. Count: 94. Lines: 19, 27, 29, 31, 37, 44, 48, 53, 55, 57, …. Example: 2 spaces.

Medium severity#

  • [qe-fig-001] — Do not set figure size unless necessary. Count: 2. Lines: 962, 973. Example: figsize=.

  • [qe-fig-008] — Use lw=2 for line charts. Count: 4. Lines: 964, 967, 975, 978. Example: plot() without lw=.

  • [qe-writing-003] (reviewer) — Maintain logical flow. Count: 4. Lines: 24, 115, 310, 665. Example: four breaks. (a) Seven passages promise dynamic mode decomposition in this lecture - 27 uses “DMD” before it is defined at all, 59 says “We’ll again use a singular value decomposition, but now to construct a dynamic mode decomposition (DMD)”, 115 “later in this lecture when we study Dynamic Mode Decomposition”, 357, 403 “When we study Dynamic Mode Decomposition below”, 425 “that will concern us when we study Dynamic Mode Decompositions below”, 479 “When we study Dynamic Mode Decompositions below” - but there is no DMD section anywhere in the file, and 29 and 1041 say the opposite, that DMD is a sequel lecture var_dmd. A reader following “below” four times finds nothing. (b) The roadmap at 23-25 promises three things and delivers two: “under-determined and over-determined least squares regressions” never gets a section, only the pseudoinverse exercise at 989-995. © 257-260 states that \(U_L\) is a basis for the column space of \(X\) and \(V_L\) for the row space, and {eq}`eq:fourspaceSVD` at 247-250 says it in algebra - then the worked example at 310-311 writes row_space = U[:, :rank] and col_space = V[:, :rank], exactly reversed; the mislabelling is invisible in the output only because the example matrix at 293-297 is symmetric, so U and V coincide up to sign. (d) 665-668 loses the index: 665 says “for each \(k=1, \ldots, n\), the object \(\lbrace V_{kj} \rbrace_{j=1}^n\)”, then 667 writes the loadings vector as \(U_j\) on the left while every entry inside it and the range that follows are indexed by \(k\).

  • [qe-writing-007] (reviewer) — Use visual elements to enhance understanding. Count: 3. Lines: 94, 129, 948. Example: the lecture publishes no figures at all. compare_pca_svd (948-982) is the only cell that plots, and it is never called - the class DecomAnalysis defined at 844 is never instantiated either (grep finds DecomAnalysis only at 841 and 844, compare_pca_svd only at 948), so 140 lines of code and both of the lecture’s two figures are dead. All six qe-fig-003 sites and all four qe-fig-008 sites the scanner reports live inside that unreachable function. Separately, the two most visual passages are left as prose: 94-104 describes multiplying by \(X\) as rotate (\(V^\top y\)) then rescale (\(\Sigma V^\top y\)) then rotate (\(U \Sigma V^\top y\)), with 96-97 stating explicitly that angles and lengths are preserved or rescaled - a two-panel unit-circle-to-ellipse plot is the standard illustration and would take five lines of matplotlib; and 129-274 builds two pairs of mutually orthogonal subspaces and their complements (268-274) entirely in symbols, where the reference example the rule cites uses a diagram for exactly this kind of structure.

Low severity#

  • [qe-math-009] (reviewer) — Choose simplicity in mathematical notation. Count: 1. Lines: 659. Example: {eq}`eq:PCA2` at 658-660 expands the rank-one sum of {eq}`eq:PCA1` (640) into three explicit block columns whose entries are \(U_{11}V_1^\top, U_{21}V_1^\top, \ldots\) - each entry a scalar times a row vector, so an \(m \times n\) matrix is written as an \(m \times 1\) stack without saying so, and each block carries a stray trailing \\. The only use made of it is 662-670, which interprets \(V_{kj}\), \(U_{jk}\) and \(\sigma_k\) - all three of which are already visible in {eq}`eq:PCA1`. The simpler display is the one already written at 640.

Strengths#

  • The four-subspace result is derived rather than asserted: 150-155 partitions the full SVD, 167-189 multiplies it out one way, 203-226 transposes and multiplies it out the other, and 242-253 collects the four identities - then 263 tells the reader exactly what the proof was, “simply by performing the multiplications called for by the right side of {eq}`eq:fullSVDpartition` and reading them”.

  • Eleven of the twelve labelled equations are genuinely cited later, several of them more than once: eq:SVD101 (67) at 99, eq:fullSVDpartition (155) at 167, 203 and 263, eq:Xfour1a (180) at 193 and 242, eq:Xfour1b (217) at 231 and 242, eq:orthoortho1 (189) at 191, eq:orthoortho2 (226) at 228, eq:fourspaceSVD (253) at 255, 263 and 265, eq:PCA1 (641) at 653, eq:PCA2 (660) at 662, eq:XXo (722) and eq:XXcompare (784) at 786.

  • The full-versus-reduced distinction is handled precisely, by stating which of the four orthogonality identities survives in each shape regime - 374-379 for the full SVD, 387-392 for tall-skinny, 396-401 for short-fat - and then checking it in code on a \(5 \times 2\) matrix (427-467) and a \(2 \times 5\) matrix (489-507), with np.allclose(X, Uhat@SShat@Vhat) at 506 confirming the reduced form still reproduces \(X\).

  • The decomposition is given a reading before it is given an application: 94-104 turns \(U \Sigma V^\top\) into rotate-rescale-rotate and 106-113 into an encoder / operator / decoder triple, so the algebra of 63-91 arrives already interpreted.

  • PCA is reached twice by different routes and the two are reconciled rather than left side by side - via the SVD at 633-670, via the eigendecomposition of \(XX^\top\) at 672-745, then equated term by term at 747-757 with \(U_j = P_j\) and \(V_k^\top = \tilde\epsilon_k\) - and 759-764 names the two practical ambiguities this leaves (eigenvector sign and ordering) and how to resolve them, which is the trap someone comparing LA.eigh with LA.svd output actually hits.

  • Notation conventions are stated where they could be guessed wrong: 129 declares \({\mathcal C}\), \({\mathcal N}\), \({\mathcal R}\) for column, null and row space before using them, and 88-91 states that \(U^\top\) means the conjugate transpose for complex matrices instead of leaving it implicit.

  • Every matrix display uses bmatrix (153-162, 207, 546-549, 573, 646-651, 1018-1024) and every transpose in the narrative math is ^\top, so the two very-high-weight math conventions hold across roughly 60 displays.