svd_intro#
Series: lecture-python.myst
File:
lectures/svd_intro.mdAudit date: 2026-08-26
Corpus snapshot:
e25fdf2345Categories 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 |
|
Math |
9.5/10 |
|
Code |
7.5/10 |
|
JAX |
out of scope |
JAX rules target |
Figures |
6.5/10 |
|
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_svdreads attributes that do not exist: the class storesself.\ud835\udf06(U+1D706 MATHEMATICAL ITALIC SMALL LAMDA) at 872,self.\ud835\udf0e(U+1D70E) at 898 andself.\ud835\udf16(U+1D716) at 881, while 957-958, 975 and 978 readda.\u03bb,da.\u03c3andda.\u03b5- the ordinary Greek code points U+03BB / U+03C3 / U+03B5. Two visually identical but distinct identifiers per letter; the function would raiseAttributeErroron its first line of output, which is consistent with it never having been run. On top of that:Vnames numpy’s third return value at 300, 430 and 492 even thoughnp.linalg.svdreturns \(V^\top\) - the class itself gets this right and calls itVTat 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 writesself.\ud835\udf0e ** 2with spaces around the exponentiation operator, which the rule asks be writtena**b; 431 and 493 leave one space before an inline comment where 430 uses two;import numpy as npis repeated mid-file at 428 and 490 after the import cell at 280-284; anddiag_signis 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 writesrow_space = U[:, :rank]andcol_space = V[:, :rank], exactly reversed; the mislabelling is invisible in the output only because the example matrix at 293-297 is symmetric, soUandVcoincide 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 classDecomAnalysisdefined at 844 is never instantiated either (grepfindsDecomAnalysisonly at 841 and 844,compare_pca_svdonly at 948), so 140 lines of code and both of the lecture’s two figures are dead. All sixqe-fig-003sites and all fourqe-fig-008sites 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) andeq: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.eighwithLA.svdoutput 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.
Recommended actions#
Decide what to do about DMD and make the file say it once: either restore a DMD section or rewrite the seven promises at 27, 59, 115, 357, 403, 425 and 479 to point at
var_dmdthe way 29 and 1041 already do - 403, 425 and 479 say “below” and there is no below.Wire up or delete the dead code at 843-982: nothing instantiates
DecomAnalysisand nothing callscompare_pca_svd, so the lecture renders zero figures. If it is wired up, first fix the identifier mismatch - the class writesself.\ud835\udf06/self.\ud835\udf0e/self.\ud835\udf16(U+1D706 / U+1D70E / U+1D716) at 872, 898 and 881 while the function readsda.\u03bb/da.\u03c3/da.\u03b5(U+03BB / U+03C3 / U+03B5) at 957, 958, 975 and 978, which is anAttributeError.Fix the reversed subspace labels in the worked example: 310-311 assign
row_space = U[:, :rank]andcol_space = V[:, :rank], contradicting 257-260; rename the thirdnp.linalg.svdreturnVTat 300, 430 and 492 as the class already does at 891, and correct the"V.T:"label at 319, which currently prints the untransposed matrix.Add the two figures the prose asks for: a unit-circle-to-ellipse pair illustrating rotate-rescale-rotate for 94-104, and a subspace diagram for the two orthogonal-complement pairs of 268-274.
Turn the five
**Step N: ...:**bold labels (559, 583, 591, 612, 622) into###headings, promote**Road Ahead**(117),**Properties:**(447) and**Remarks:**(472) likewise, and switch pure emphasis from bold to italic (96-97, 102-104, 383, 543, 553, 680), including the two__...__sites at 551 and 553.Repair the display-math typos: 162 writes
U_R = \begin{bmatrix}v_{p+1} & \cdots u_n \end{bmatrix}where the left side should be \(V_R\), and 161-162 both drop the&before \(u_m\) / \(u_n\); 250 leaves a blank line inside thealignedblock; 743 writes \(\tilde\epsilon_2\) where \(\tilde\epsilon_1\) belongs; 735-736 alternates\tilde{\epsilon_j}and\tilde{\epsilon}_j; 566 writes\bar{X_i}for \(\bar X_i\); 696 uses a Unicode\u00d7inside$m\u00d7m$where the other ~40 sites write\times; and 750 and 757 write{V_1}^{T}/{V_k}^{T}where the rest of the file writes^\top.Either cite
eq:Ekart(347) from the three sentences at 349-357 that discuss “this theorem”, or drop the label; and tighten 57, 349, 557, 585 and the doubled “not not” at 991.Sweep the mechanical backlog: the 94 double-space runs, the eight Title-Case H2s (31, 61, 127, 324, 359, 509, 537, 672), the nine capitalised common nouns, and - once the plotting code is reachable - the six embedded titles, the two
figsize=(14, 5)overrides and the fourplot()calls withoutlw=2.