R ↔ TypeScript
Same seeds, same numbers.
Each port was written line by line against the original R and its libraries: glmnet's Fortran coordinate descent, LIBSVM's SMO solver as used by e1071, R's Mersenne-Twister with inversion normals and rejection sampling, and the submitted functions themselves. This page reruns them while the site is built and compares the results with the knitted 2023 PDFs.
elnet(), cvGlmnet(), ols() + RRandom(10)
Assignment 1 · Q1 · ridge and lasso (glmnet)
ridge λmin (index)
identical- 2023 (R)
- 300.8959 (76)
- Port (TS)
- 300.8959 (76)
ridge λ1se (index)
identical- 2023 (R)
- 2122.8 (55)
- Port (TS)
- 2122.8 (55)
CV MSE at λmin ± SE
identical- 2023 (R)
- 95596 ± 15574
- Port (TS)
- 95596 ± 15574
ridge test MSE
identical- 2023 (R)
- 143261.3
- Port (TS)
- 143261.3
lasso λmin
identical- 2023 (R)
- 4.468208
- Port (TS)
- 4.468208
lasso test MSE
differs (explained)- 2023 (R)
- 143261.3 (printed)
- Port (TS)
- 141632.7
The notebook printed mse (the ridge value) instead of mse.lasso, so the port reports the lasso's own test error.
coefficient table, 20 × 3
equal to print precision- 2023 (R)
- 8 significant digits
- Port (TS)
- max rel. diff 2.9e-7
predictors zeroed by the lasso
identical- 2023 (R)
- HmRun, Runs, RBI, CAtBat, CHits, NewLeagueN
- Port (TS)
- HmRun, Runs, RBI, CAtBat, CHits, NewLeagueN
| Quantity | 2023 submission (R) | TypeScript port | Verdict |
|---|---|---|---|
| ridge λmin (index) | 300.8959 (76) | 300.8959 (76) | identical |
| ridge λ1se (index) | 2122.8 (55) | 2122.8 (55) | identical |
| CV MSE at λmin ± SE | 95596 ± 15574 | 95596 ± 15574 | identical |
| ridge test MSE | 143261.3 | 143261.3 | identical |
| lasso λmin | 4.468208 | 4.468208 | identical |
| lasso test MSE The notebook printed mse (the ridge value) instead of mse.lasso, so the port reports the lasso's own test error. | 143261.3 (printed) | 141632.7 | differs (explained) |
| coefficient table, 20 × 3 | 8 significant digits | max rel. diff 2.9e-7 | equal to print precision |
| predictors zeroed by the lasso | HmRun, Runs, RBI, CAtBat, CHits, NewLeagueN | HmRun, Runs, RBI, CAtBat, CHits, NewLeagueN | identical |
submittedSamples(), icTable(), simulateAndEvaluateIC(), probOverfitSubmitted()
Assignment 1 · Q2 · AR order selection
Q2.5 IC tables (20 × 3, salary-scale bug included)
equal to print precision- 2023 (R)
- 5 decimals
- Port (TS)
- max |diff| 4.9e-6
Q2.6 M1, n = 100, 1,000 sims
identical- 2023 (R)
- 15 / 204 / 370 / 180 / 78 / 43 / 34 / 29 / 17 / 30 (IC₁ column)
- Port (TS)
- all 30 counts identical
Q2.8 M2, n = 100, 1,000 sims
identical- 2023 (R)
- 30 / 577 / 128 / 82 / 45 / 39 / 21 / 31 / 24 / 23 (IC₁ column)
- Port (TS)
- all 30 counts identical
Q2.7 M1, n = 15
within round-off- 2023 (R)
- p = 8: 921 / 981 / 889
- Port (TS)
- p = 8: 904 / 976 / 870 (max cell diff 22)
Near-singular fits (≤ 7 rows for ≥ 8 coefficients) make the arg-min depend on floating-point round-off; R 4.6 itself differs from the 2023 run by a similar amount. Port counts here come from Node.js at build time; a browser replay can differ by a few counts.
Q2.8 M2, n = 15
within round-off- 2023 (R)
- p = 8: 890 / 964 / 833
- Port (TS)
- p = 8: 864 / 957 / 807 (max cell diff 26)
Same round-off sensitivity as M1. Computed in Node.js at build time; browsers may differ by a few counts.
Q2.11 overfitting probabilities (16 × 3)
equal to print precision- 2023 (R)
- 5 decimals
- Port (TS)
- max |diff| 4.9e-6
| Quantity | 2023 submission (R) | TypeScript port | Verdict |
|---|---|---|---|
| Q2.5 IC tables (20 × 3, salary-scale bug included) | 5 decimals | max |diff| 4.9e-6 | equal to print precision |
| Q2.6 M1, n = 100, 1,000 sims | 15 / 204 / 370 / 180 / 78 / 43 / 34 / 29 / 17 / 30 (IC₁ column) | all 30 counts identical | identical |
| Q2.8 M2, n = 100, 1,000 sims | 30 / 577 / 128 / 82 / 45 / 39 / 21 / 31 / 24 / 23 (IC₁ column) | all 30 counts identical | identical |
| Q2.7 M1, n = 15 Near-singular fits (≤ 7 rows for ≥ 8 coefficients) make the arg-min depend on floating-point round-off; R 4.6 itself differs from the 2023 run by a similar amount. Port counts here come from Node.js at build time; a browser replay can differ by a few counts. | p = 8: 921 / 981 / 889 | p = 8: 904 / 976 / 870 (max cell diff 22) | within round-off |
| Q2.8 M2, n = 15 Same round-off sensitivity as M1. Computed in Node.js at build time; browsers may differ by a few counts. | p = 8: 890 / 964 / 833 | p = 8: 864 / 957 / 807 (max cell diff 26) | within round-off |
| Q2.11 overfitting probabilities (16 × 3) | 5 decimals | max |diff| 4.9e-6 | equal to print precision |
svmTrain(), tuneSvm(), RRandom(50 / 100)
Assignment 3 · Q1 · multiclass SVM (e1071 / LIBSVM)
linear, C = 10: support vectors
identical- 2023 (R)
- 67 ( 31 19 17 )
- Port (TS)
- 67 ( 31 19 17 )
tune(): linear CV errors (7 costs)
equal to print precision- 2023 (R)
- 0.770 0.100 0.107 0.080 0.090 0.090 0.083
- Port (TS)
- max |diff| 3.3e-9
best linear cost, support vectors
identical- 2023 (R)
- C = 1, 81 ( 37 22 22 )
- Port (TS)
- C = 1, 81 ( 37 22 22 )
linear test confusion table
identical- 2023 (R)
- 73 6 3 / 7 91 3 / 6 3 108
- Port (TS)
- 73 6 3 / 7 91 3 / 6 3 108
misclassified (linear)
identical- 2023 (R)
- 28
- Port (TS)
- 28
radial tune: best (cost, γ), CV error
identical- 2023 (R)
- (100, 2), 0.07333333
- Port (TS)
- (100, 2), 0.07333333
best radial: support vectors
identical- 2023 (R)
- 88 ( 32 30 26 )
- Port (TS)
- 88 ( 32 30 26 )
radial test confusion table
identical- 2023 (R)
- 71 5 4 / 9 90 2 / 6 5 108
- Port (TS)
- 71 5 4 / 9 90 2 / 6 5 108
| Quantity | 2023 submission (R) | TypeScript port | Verdict |
|---|---|---|---|
| linear, C = 10: support vectors | 67 ( 31 19 17 ) | 67 ( 31 19 17 ) | identical |
| tune(): linear CV errors (7 costs) | 0.770 0.100 0.107 0.080 0.090 0.090 0.083 | max |diff| 3.3e-9 | equal to print precision |
| best linear cost, support vectors | C = 1, 81 ( 37 22 22 ) | C = 1, 81 ( 37 22 22 ) | identical |
| linear test confusion table | 73 6 3 / 7 91 3 / 6 3 108 | 73 6 3 / 7 91 3 / 6 3 108 | identical |
| misclassified (linear) | 28 | 28 | identical |
| radial tune: best (cost, γ), CV error | (100, 2), 0.07333333 | (100, 2), 0.07333333 | identical |
| best radial: support vectors | 88 ( 32 30 26 ) | 88 ( 32 30 26 ) | identical |
| radial test confusion table | 71 5 4 / 9 90 2 / 6 5 108 | 71 5 4 / 9 90 2 / 6 5 108 | identical |
pnpm test
How parity is tested
scripts/export_artefacts.R reruns the original assignment code in R and writes reference outputs: glmnet paths, CV curves and fold ids, the AR tables, e1071 support vectors, decision values and tuning errors. The Vitest parity suite in web/src/lib compares the ports with those fixtures and with numbers transcribed from the PDFs.
Tolerances are tight: ridge paths agree to about 1e-10 relative, CV curves to 1e-15, SVM tuning errors exactly. The only loose comparisons are the n = 15 Monte Carlo tables, where seven or fewer observations meet eight or more coefficients and the residual sum of squares is pure round-off.
The original R Markdown is preserved unchanged in the repository's coursework/ folder.
# from the repository root (R >= 4.3)
Rscript -e 'install.packages(c("ISLR","glmnet","e1071","tidyr","jsonlite"))'
Rscript scripts/export_artefacts.R
cd web && pnpm install && pnpm testWhy replay R's random numbers?
Statistical similarity would be easy, but it would not show that the port is faithful. Reproducing R's set.seed() stream means the browser draws the same 131 training players, the same CV folds, the same 4,000 simulated series and the same Gaussian clouds as the 2023 run. Matching to the last printed digit is then strong evidence that every algorithm is ported correctly.