@compstats/core 0.2.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/CHANGELOG.md +105 -0
  2. package/README.md +127 -110
  3. package/dist/3d.js +1120 -122
  4. package/dist/3d.js.map +16 -9
  5. package/dist/core/arith.d.ts.map +1 -1
  6. package/dist/core/linalg/cov.d.ts +50 -0
  7. package/dist/core/linalg/cov.d.ts.map +1 -0
  8. package/dist/core/linalg/eigen.d.ts +53 -0
  9. package/dist/core/linalg/eigen.d.ts.map +1 -0
  10. package/dist/core/linalg/lm.d.ts +78 -0
  11. package/dist/core/linalg/lm.d.ts.map +1 -0
  12. package/dist/core/linalg/lu.d.ts +154 -0
  13. package/dist/core/linalg/lu.d.ts.map +1 -0
  14. package/dist/core/linalg/matrix.d.ts +131 -0
  15. package/dist/core/linalg/matrix.d.ts.map +1 -0
  16. package/dist/core/linalg/modelMatrix.d.ts +69 -0
  17. package/dist/core/linalg/modelMatrix.d.ts.map +1 -0
  18. package/dist/core/linalg/namedVector.d.ts +37 -0
  19. package/dist/core/linalg/namedVector.d.ts.map +1 -0
  20. package/dist/core/linalg/ops.d.ts +120 -0
  21. package/dist/core/linalg/ops.d.ts.map +1 -0
  22. package/dist/core/linalg/prcomp.d.ts +66 -0
  23. package/dist/core/linalg/prcomp.d.ts.map +1 -0
  24. package/dist/core/linalg/qr.d.ts +134 -0
  25. package/dist/core/linalg/qr.d.ts.map +1 -0
  26. package/dist/core/linalg/vector.d.ts +68 -0
  27. package/dist/core/linalg/vector.d.ts.map +1 -0
  28. package/dist/core/moderation.d.ts +6 -3
  29. package/dist/core/moderation.d.ts.map +1 -1
  30. package/dist/core/ols.d.ts +4 -7
  31. package/dist/core/ols.d.ts.map +1 -1
  32. package/dist/data/moderationData.d.ts +2 -2
  33. package/dist/data/pcaDegenerate.d.ts +1 -1
  34. package/dist/index.d.ts +1 -1
  35. package/dist/index.d.ts.map +1 -1
  36. package/dist/index.js +1601 -863
  37. package/dist/index.js.map +17 -11
  38. package/dist/linalg.d.ts +36 -0
  39. package/dist/linalg.d.ts.map +1 -0
  40. package/dist/linalg.js +1860 -0
  41. package/dist/linalg.js.map +24 -0
  42. package/dist/plot/moderation3d.d.ts +1 -1
  43. package/dist/plot/sampling.d.ts +45 -0
  44. package/dist/plot/sampling.d.ts.map +1 -1
  45. package/dist/plot/scatter3d.d.ts +1 -1
  46. package/package.json +16 -5
package/CHANGELOG.md CHANGED
@@ -3,6 +3,111 @@
3
3
  All notable changes to `@compstats/core`. The R package this ports keeps its
4
4
  own history in [`NEWS.md`](https://github.com/compstatslib/compstatslib/blob/main/NEWS.md).
5
5
 
6
+ ## 0.4.0
7
+
8
+ ### Added
9
+
10
+ * A linear-algebra entry point, `@compstats/core/linalg`, built as
11
+ `dist/linalg.js` with no dependency and no Plotly. Base R hands the R package
12
+ `matrix()`, `%*%`, `solve()`, `qr()`, `model.matrix()`, `lm()`, `eigen()` and
13
+ `prcomp()` for free; a JavaScript application has none of them, so the port
14
+ writes them in R's vocabulary over a plain column-major `Matrix`
15
+ (`{ nrow, ncol, data: Float64Array, dimnames }`) — plain data, not a class,
16
+ so a matrix serializes, clones and crosses a worker boundary as it is. The
17
+ main entry does not re-export it: a page that draws only the 2D demos never
18
+ loads it. The names:
19
+ * matrices — `matrix`, `fromRows`, `fromColumns`, `fromFrame`, `at`, `row`,
20
+ `column`, `toRows`, `toColumns`;
21
+ * elementary operations — `t` (also `transpose`, for an app whose `t` is its
22
+ translation function), `matmul`, `crossprod`, `tcrossprod`, `cbind`,
23
+ `rbind`, `diag`, `identity`;
24
+ * vectors — `add`, `sub`, `mul`, `div`, `square`, `dot`, `norm`, `cosine`:
25
+ R's operators by name, with a scalar recycled and any other length
26
+ mismatch refused; and the `Vector` type they are written over, which is
27
+ `readonly number[]` and nothing more — a name for the concept in a
28
+ signature, erased at compile time, so a plain array is a vector and
29
+ nothing needs wrapping;
30
+ * QR — `qr` with `qrCoef`, `qrFitted`, `qrResid`, `qrQty`, `qrQy`, `qrQ`,
31
+ `qrR`, which is the LINPACK `dqrdc2` factorization `lm.fit()` runs on,
32
+ promoted out of `leastSquares` (now a wrapper over it, with every existing
33
+ value unchanged);
34
+ * LU — `lu`, `solve`, `det`, `determinant`, `rcond`, `matrixNorm`;
35
+ * models — `modelMatrix`, `lm`, and the `NamedVector` that carries R's named
36
+ coefficients in R's order (`namedVector`, `lookup`);
37
+ * multivariate — `cov`, `cor`, `variance`, `eigenSymmetric`, `isSymmetric`,
38
+ `prcomp`.
39
+
40
+ Every routine is verified against R 4.5.3 in
41
+ `conformance-fixtures/linalg.R` of the R package. The factorizations, the
42
+ solves, the determinant and the fitted values of `lm` pin bit for bit
43
+ against R's reference-BLAS build (which contracts a multiply and an add into
44
+ one rounding, as the port does with `fusedMultiplyAdd`); the summary
45
+ statistics, eigenvalues and standard deviations are verified at a relative
46
+ `1e-12`. Where R warns and recycles, or silently reads only half of a
47
+ matrix, the port refuses, and each such narrowing is stated at the function.
48
+
49
+ ### Changed
50
+
51
+ * `moderationSurface()` fits through the new `lm()` instead of building R's
52
+ model matrix itself. Its fitted values and residuals are now R's exactly —
53
+ all 200 of each, for all three pinned models, against 16 of 200 at 8.6e-15
54
+ before. R's `lm.fit` reports the residuals its factorization computes and
55
+ takes the fitted values as the outcome minus them; the old path re-summed
56
+ `X · β`, which lands a few bits away. Coefficients, grid, surface and `zlim`
57
+ are unchanged. A caller who compared residuals against `y - fitted` with
58
+ `===` will now see a difference in the last bit, as R does on 93 of the 200
59
+ rows.
60
+
61
+ ### Bug fixes
62
+
63
+ * `eigenSymmetric()` refuses what R's `eigen()` refuses, in R's order and R's
64
+ words: a non-square matrix, then a 0 x 0 one, then a missing or infinite
65
+ entry. A NaN used to run the Jacobi sweeps and return a result full of NaN,
66
+ and a 0 x 0 matrix used to return an empty decomposition.
67
+ * `prcomp()` carries the row names of its input onto the scores, as R does.
68
+ The rotation already carried the variable names.
69
+ * `leastSquares()` no longer overflows the call stack on a long design. The
70
+ column norm was `Math.hypot(...column)`, a spread that dies at about a
71
+ million arguments; it is a fold now, and it follows the BLAS `dnrm2` R runs,
72
+ which also moved the Householder vectors of the factorization onto R's
73
+ doubles exactly. No coefficient, fitted value or residual changed.
74
+ * `fusedMultiplyAdd()` keeps the sign of a zero product, as the hardware
75
+ instruction does: `fma(-1, 0, -0)` is `-0`. No pinned value changed.
76
+
77
+ ## 0.3.0
78
+
79
+ ### Added
80
+
81
+ * `plotSampling()` takes a `mark` option and marks the chosen statistic on all
82
+ three panels, in one color. The library computes two of the three numbers:
83
+ the statistic of the pooled sample, and the average of every statistic drawn
84
+ so far. It takes the third from the caller, because the true value in the
85
+ population is a fact about the shape and not about the values in the array. A
86
+ Cauchy population has no mean, and the arithmetic mean of a finite draw from
87
+ it is still a number. `mark.populationValue: null` says the population has no
88
+ such value, and the panel writes `no true <label>` in place of the line. A
89
+ location statistic draws a line, a spread draws a span around a center, and
90
+ the third panel always draws a line, because its axis holds the value of the
91
+ statistic. A mark outside the frozen window draws a caret at that edge, so a
92
+ clipped mark and an absent mark do not look the same. The new `marks` field
93
+ of the result reports the three numbers, and reports null for each one that
94
+ does not exist. A call that passes no `mark` draws what it drew before, and
95
+ gets `marks: null`. `interactiveSampling()` forwards the option, as it
96
+ forwards every other plot option.
97
+
98
+ ### Changed
99
+
100
+ * `plotly.js-dist-min` moves from a runtime dependency to an **optional peer
101
+ dependency**. Installing `@compstats/core` brought in 5.9 MB of 3D engine
102
+ even for a page that draws only 2D. It now installs 1.3 MB, and a consumer
103
+ who uses the `/3d` entry adds `plotly.js-dist-min` to its own dependencies —
104
+ which the two known consumers already did, because a bundler needs the
105
+ package named where it is imported. No source and no bundle changed:
106
+ `dist/3d.js` still reaches Plotly through the same lazy `import()`, and
107
+ passing your own engine in the `plotly` option still keeps that import
108
+ unreached. If Plotly is absent, that load now fails with
109
+ `Cannot find package 'plotly.js-dist-min'` instead of succeeding.
110
+
6
111
  ## 0.2.0
7
112
 
8
113
  The first npm release. It follows the R package's own 0.8.0 release, which
package/README.md CHANGED
@@ -2,21 +2,22 @@
2
2
 
3
3
  [![CI](https://github.com/compstatslib/compstatslib-ts/actions/workflows/ci.yml/badge.svg)](https://github.com/compstatslib/compstatslib-ts/actions/workflows/ci.yml)
4
4
 
5
- Interactive gadgets and plotting functions for data sets and statistical
6
- concepts, in two and three dimensions. This is the browser port of the R
7
- package [`compstatslib`](https://github.com/compstatslib/compstatslib).
8
-
9
- Some of it works on **your own data**. Explore any data frame as a rotatable
10
- 3D point cloud. Fit a moderated (interaction) regression and rotate its
11
- surface to see how the interaction twists it away from a plane. The rest
12
- **simulates a concept** instead of plotting your data sampling
13
- distributions, confidence intervals, t-statistics, matrix inversion. That part
14
- is built for class demonstrations, homework, and self-study.
15
-
16
- The R original runs in RStudio. This port runs in a browser, with no R
17
- installation and no server. The 2D plots draw on a plain Canvas 2D context.
18
- The 3D plots draw through Plotly, behind a separate entry point, so a page
19
- that shows only 2D never loads it.
5
+ **Statistical routines for TypeScript, checked against R.**
6
+
7
+ The package brings statistical primitives and visualization tools to the TypeScript/JavaScript ecosystem. In both sets of features, this package seeks to attain reasonable parity with equivalent functions from the [R platform](https://www.r-project.org) where statistical routines are well vetted. The routines are largely ported from R implementations and the test suite compares each routine against equivalent procedures run in R on the same input and pins the answer to R's output, given a tolerance. The long-term vision of this package is to bring more of R's statistical judgment and rigor into TypeScript/JavaScript.
8
+
9
+ **For statistical tasks**, this package: (1) provides types for vector, dataframe, and matrix representations; and (2) fits linear models, factors matrices, finds principal components, and evaluates distributions. The statistical routines can run browser-side or server-side, and can be used in both TypeScript and JavaScript.
10
+
11
+ - **Matrices and linear algebra.** A plain matrix type with multiplication, transpose, inverse, determinant, condition number, and the two workhorse factorizations (QR and LU) that every solver and model fit is built on, plus covariance and correlation matrices, symmetric eigendecomposition and principal components.
12
+ - **Models.** Fit a linear model from a table of columns and a list of terms, interactions included, and read back coefficients, standard errors, t and p values, R², the F statistic, fitted values and residuals. Fit logistic regression using data with a binary column.
13
+ - **Distributions and random draws.** The Student t distribution — density, cumulative probability and quantiles — and reproducible draws from the uniform, normal, t, log-normal and Cauchy distributions, all from a seeded generator you pass in, so a demonstration or a test repeats exactly.
14
+ - **Summaries and shaping.** Means, medians, standard deviations and quantiles that follow the same definitions R uses; kernel density estimation with its bandwidth rule; histogram binning with its bin-count rule; and the algorithm that picks readable axis tick marks.
15
+
16
+ **For visualization tasks** this package ports functions from its R sibling [`compstatslib`](https://github.com/compstatslib/compstatslib). Explore any table as a rotatable 3D point cloud. Fit a moderated (interaction) regression and turn its surface to watch the interaction twist it away from a plane. Click points onto a canvas and see a regression line, a logistic curve or a principal-component axis follow your mouse. Other components simulate a concept rather than your data — sampling distributions, confidence intervals, t-tests, matrix inversion — for class demonstrations, homework and self-study. Those demonstrations are also what the statistics were built for, and they are the standing proof that the statistics work.
17
+
18
+ The 2D plots draw on a plain Canvas 2D context. The 3D plots draw through Plotly.
19
+
20
+ **Vision.** This package aims to bring more of R's core algorithms to TypeScript/JavaScript. The statistical routines currently support the visualization tools and demonstrations. As of now, there is no general framework for generalized linear models beyond logistic regression, and no singular value decomposition. But such features will likely be added in the future based on demand.
20
21
 
21
22
  ## Install
22
23
 
@@ -26,13 +27,52 @@ npm install @compstats/core
26
27
  bun add @compstats/core
27
28
  ```
28
29
 
29
- Plotly is only needed for the 3D functions. See
30
- [3D and Plotly](#3d-and-plotly) below.
30
+ The core install is small, because it has no large dependencies. Plotly is an **optional peer dependency**: the package declares it, but no package manager installs it for you. A page that only computes statistics, or only plots in 2D, never downloads it.
31
+
32
+ Add Plotly yourself when you use the 3D functions:
33
+
34
+ ```bash
35
+ npm install plotly.js-dist-min
36
+ # or
37
+ bun add plotly.js-dist-min
38
+ ```
39
+
40
+ See [3D and Plotly](#3d-and-plotly) below.
31
41
 
32
42
  ## Quick start
33
43
 
34
- Give an interactive component a canvas. It draws at once, and it redraws on
35
- every click.
44
+ Fit a model. Your data is a table of columns; the model is the outcome and a list of terms, where an array of names is an interaction:
45
+
46
+ ```js
47
+ import { lm } from "@compstats/core/linalg";
48
+ import { moderationData } from "@compstats/core";
49
+
50
+ const fit = lm(moderationData, { outcome: "y", terms: ["x", "z", ["x", "z"]] });
51
+
52
+ fit.coefficients.names; // ["(Intercept)", "x", "z", "x:z"]
53
+ fit.coefficients.values; // [-0.0452945…, 0.4720139…, 0.3136202…, 0.8481828…]
54
+ fit.standardErrors.values[3]; // 0.019869806719131564
55
+ fit.pValues.values[3]; // 3.4056449407081656e-101
56
+ fit.rSquared; // 0.9203966770597701
57
+ fit.sigma; // 1.0301490130678368
58
+ ```
59
+
60
+ Summaries, correlations and reproducible random draws come the same way. The generator is passed in, never taken from `Math.random`, so the same seed gives the same numbers on every run and in every browser:
61
+
62
+ ```js
63
+ import { cor } from "@compstats/core/linalg";
64
+ import { sd, quantile, seededRng, rnorm } from "@compstats/core";
65
+
66
+ cor(moderationData.x, moderationData.z); // -0.08036538677894722
67
+ sd(moderationData.y); // 3.6235641115369814
68
+
69
+ const draws = rnorm(seededRng(42), 1000, { mean: 100, sd: 15 });
70
+ quantile(draws, 0.975); // 129.24422658630283
71
+ ```
72
+
73
+ Every number above is produced by the package and matched against the value R returns for the same input.
74
+
75
+ None of that touches the DOM, so it runs under Node and Bun as readily as in a browser. When you do want a picture, hand a component a canvas — it draws at once and redraws on every click:
36
76
 
37
77
  ```html
38
78
  <canvas id="plot" width="640" height="480"></canvas>
@@ -51,8 +91,7 @@ every click.
51
91
  </script>
52
92
  ```
53
93
 
54
- The plot functions draw the same picture from data you already hold, and
55
- return what they computed:
94
+ The plot functions draw the same picture from data you already hold, and return what they computed:
56
95
 
57
96
  ```js
58
97
  import { plotRegression } from "@compstats/core";
@@ -66,20 +105,9 @@ const fit = plotRegression(canvas, [
66
105
  console.log(fit.slope, fit.rSquared);
67
106
  ```
68
107
 
69
- The statistics are separate from the drawing. Import them alone when you want
70
- the numbers and not the picture:
71
-
72
- ```js
73
- import { linearRegression, principalComponents, tTestStats } from "@compstats/core";
74
- ```
75
-
76
- Nothing in the `core/` layer touches the DOM, so it also runs under Node or
77
- Bun.
78
-
79
108
  ## Use from a CDN
80
109
 
81
- The main bundle is self-contained browser ESM with no imports of its own. A
82
- page can load it directly, with no build step:
110
+ The main bundle is self-contained browser ESM with no imports of its own. A page can load it directly, with no build step:
83
111
 
84
112
  ```html
85
113
  <script type="module">
@@ -103,9 +131,7 @@ The 3D functions live behind the `@compstats/core/3d` entry point:
103
131
  import { interactiveScatter3d, moderationData } from "@compstats/core/3d";
104
132
  ```
105
133
 
106
- That entry does not load Plotly either. It reaches the library through a
107
- dynamic import the first time it draws. A caller that passes its own engine in
108
- the `plotly` option never triggers that import:
134
+ That entry does not load Plotly either. It reaches the library through a dynamic import the first time it draws. A caller that passes its own engine in the `plotly` option never triggers that import:
109
135
 
110
136
  ```html
111
137
  <script src="https://cdn.jsdelivr.net/npm/plotly.js-dist-min"></script>
@@ -122,9 +148,9 @@ the `plotly` option never triggers that import:
122
148
  </script>
123
149
  ```
124
150
 
125
- Pass `plotly` when you load the 3D entry from a raw file CDN such as jsDelivr,
126
- because nothing there resolves the bare `plotly.js-dist-min` specifier. esm.sh
127
- rewrites bare specifiers, so on esm.sh both ways work.
151
+ Pass `plotly` when you load the 3D entry from a raw file CDN such as jsDelivr, because nothing there resolves the bare `plotly.js-dist-min` specifier. esm.sh rewrites bare specifiers, so on esm.sh both ways work.
152
+
153
+ Under a bundler, the dynamic import needs Plotly in your own dependencies. If it is absent, the load fails with `Cannot find package 'plotly.js-dist-min'`. The two cures are the same two paths: install the optional peer, or pass your own engine in the `plotly` option and let the dynamic import stay unreached.
128
154
 
129
155
  ## How the functions are organized
130
156
 
@@ -132,16 +158,13 @@ Every family has three parts, and you can use any one of them alone:
132
158
 
133
159
  - a **core** function that computes the statistics and touches no DOM,
134
160
  - a **plot** function that draws it on a target you give it,
135
- - an **interactive** component that owns the input and hands each draw to the
136
- plot function.
161
+ - an **interactive** component that owns the input and hands each draw to the plot function.
137
162
 
138
- The families are grouped below by what they are *for*, because that varies
139
- more than the interaction style does.
163
+ The families are grouped below by what they are *for*, because that varies more than the interaction style does.
140
164
 
141
165
  ### Data sets in 3D
142
166
 
143
- These accept any data frame, with control over axes, color mapping, aspect
144
- ratio, and camera. They come from `@compstats/core/3d`.
167
+ These accept any data frame, with control over axes, color mapping, aspect ratio, and camera. They come from `@compstats/core/3d`.
145
168
 
146
169
  | Function | What it does |
147
170
  | --- | --- |
@@ -153,11 +176,7 @@ ratio, and camera. They come from `@compstats/core/3d`.
153
176
 
154
177
  ### 2D relationships
155
178
 
156
- These plot x / y points you supply, together with a fitted model. They are
157
- sized for small data — points clicked in by hand, or a modest table — and not
158
- for arbitrary data. `plotRegression` draws in a window of -5 to 50 unless you
159
- give it `xlim` and `ylim`, and the PCA functions expect points with an `x` and
160
- a `y`.
179
+ These plot x / y points you supply, together with a fitted model. They are sized for small data — points clicked in by hand, or a modest table — and not for arbitrary data. `plotRegression` draws in a window of -5 to 50 unless you give it `xlim` and `ylim`, and the PCA functions expect points with an `x` and a `y`.
161
180
 
162
181
  | Function | What it does |
163
182
  | --- | --- |
@@ -171,8 +190,7 @@ a `y`.
171
190
 
172
191
  ### Simulations and concept demonstrations
173
192
 
174
- These do not plot your data. They simulate a process, or draw a geometric
175
- object, so that a concept can be watched instead of described.
193
+ These do not plot your data. They simulate a process, or draw a geometric object, so that a concept can be watched instead of described.
176
194
 
177
195
  | Function | What it does |
178
196
  | --- | --- |
@@ -187,11 +205,7 @@ object, so that a concept can be watched instead of described.
187
205
 
188
206
  ### Statistics without a picture
189
207
 
190
- The R package never had to ship these. `mean()`, `sd()`, `quantile()`, `dt()`,
191
- `rnorm()`, `density()`, `hist()`, `pretty()`, `solve()` and `lm.fit()` are all
192
- in base R, and its functions call them. JavaScript has no statistics standard
193
- library, so the port wrote them — and exports them, because an application
194
- built on this package needs them for the same reason the package did.
208
+ The routines the plots are built on are exported in their own right, because an application built on this package needs them for the same reason the plots did. Each follows the definition R uses — there is more than one reasonable definition of a quantile, of a histogram's bin count, of a kernel bandwidth, and picking a different one silently changes results — and the test suite pins each to the value R returns.
195
209
 
196
210
  | Group | Functions |
197
211
  | --- | --- |
@@ -200,12 +214,9 @@ built on this package needs them for the same reason the package did.
200
214
  | Seeded random draws | `seededRng`, `runif`, `rnorm`, `rt`, `rlnorm`, `rcauchy`, `sampleWithoutReplacement` |
201
215
  | Binning and density | `histogram`, `nclassSturges`, `kernelDensity`, `bwNrd0` |
202
216
  | Axis ticks | `rPretty`, `prettyTicks` |
203
- | Linear algebra | `leastSquares`, `determinant`, `invertMatrix` |
217
+ | Fitting and 2x2 matrices | `leastSquares`, `determinant`, `invertMatrix` |
204
218
 
205
- Each one follows its R counterpart, down to the rule and the argument names:
206
- `quantile` is type 7, `nclassSturges` is Sturges' rule, `bwNrd0` is R's
207
- `nrd0` bandwidth, `rPretty` is `pretty()`, and the samplers take R's own
208
- parameters. The test suite pins them to values computed in R.
219
+ The names say which rule was followed, for anyone checking: `quantile` is type 7, `nclassSturges` is Sturges' rule, `bwNrd0` is the `nrd0` bandwidth, `rPretty` is R's `pretty()`, and the samplers take R's own parameters. The general matrix routines — solving, factorizing, model fitting, principal components — live under [Linear algebra](#linear-algebra) below.
209
220
 
210
221
  The draws take a generator you pass in, so a demonstration repeats exactly:
211
222
 
@@ -217,25 +228,53 @@ const draws = rnorm(rng, 1000, { mean: 100, sd: 15 });
217
228
  quantile(draws, 0.975);
218
229
  ```
219
230
 
231
+ ### Linear algebra
232
+
233
+ Matrix arithmetic, the factorizations that solvers and model fits are built on, and the multivariate routines that sit on top of them. They have their own entry point, so a page that only draws never loads them, and they keep R's names — an application that needs a QR decomposition is usually being written by someone who can already read one:
234
+
235
+ ```js
236
+ import { matrix, matmul, solve, lm, prcomp } from "@compstats/core/linalg";
237
+ ```
238
+
239
+ A matrix is plain data, laid out as R lays it out — **column-major**, with `nrow`, `ncol`, a `Float64Array` of the entries column by column, and optional `dimnames`. `matrix(values, { nrow })` fills column by column as R's `matrix()` does, and `byrow: true` fills by rows. Operations are functions that take matrices and return new ones; nothing modifies its input. Indices are zero-based. A vector is a plain array of numbers: the exported `Vector` type is a name for `readonly number[]` and nothing more, so a JavaScript caller passes an array as it always did.
240
+
241
+ | Group | Functions |
242
+ | --- | --- |
243
+ | Building | `matrix`, `fromRows`, `fromColumns`, `fromFrame`, `at`, `row`, `column`, `toRows`, `toColumns` |
244
+ | Elementary operations | `t` (or `transpose`), `matmul`, `crossprod`, `tcrossprod`, `cbind`, `rbind`, `diag`, `identity` |
245
+ | Vectors | `add`, `sub`, `mul`, `div`, `square`, `dot`, `norm`, `cosine` |
246
+ | QR | `qr`, `qrCoef`, `qrFitted`, `qrResid`, `qrQty`, `qrQy`, `qrQ`, `qrR` |
247
+ | LU | `lu`, `solve`, `det`, `determinant`, `rcond`, `matrixNorm` |
248
+ | Models | `modelMatrix`, `lm`, `namedVector`, `lookup` |
249
+ | Multivariate | `cov`, `cor`, `variance`, `eigenSymmetric`, `isSymmetric`, `prcomp` |
250
+
251
+ A model is a term list rather than a formula — R's `y ~ x * z + w` is `{ outcome: "y", terms: ["x", "z", "w", ["x", "z"]] }` — and `lm` returns the coefficients as a named vector in R's order, with `null` where R prints `NA`:
252
+
253
+ ```js
254
+ import { lm, solve, matrix } from "@compstats/core/linalg";
255
+ import { moderationData } from "@compstats/core";
256
+
257
+ const fit = lm(moderationData, { outcome: "y", terms: ["x", "z", "w", ["x", "z"]] });
258
+ fit.coefficients.names; // ["(Intercept)", "x", "z", "w", "x:z"]
259
+ fit.rSquared; // 0.9204001958847745
260
+
261
+ const a = matrix([2, 1, -1, 1, 3, 2, 1, -1, 4], { nrow: 3 });
262
+ solve(a, [1, 2, 3]); // [-0.06666666666666665, 0.8, 0.3333333333333333]
263
+ ```
264
+
265
+ Each routine follows R down to the arithmetic of its LAPACK and LINPACK calls, so the factorizations, the solves and the fitted values match R's doubles exactly, and the rest is verified at a stated tolerance. Where R warns and recycles a mismatched length, or silently reads only the lower triangle of a matrix, this entry refuses and says so at the function.
266
+
220
267
  ### Bundled data
221
268
 
222
- `moderationData` (200 rows of `y`, `x`, `z`, `w`) and `pcaDegenerate` (16 rows
223
- of `x`, `y`) are the same tables as in the R package, exported from R rather
224
- than regenerated. Both are defaults, so a call with no data still gives a
225
- working demonstration.
269
+ `moderationData` (200 rows of `y`, `x`, `z`, `w`) and `pcaDegenerate` (16 rows of `x`, `y`) are the same tables as in the R package, exported from R rather than regenerated. Both are defaults, so a call with no data still gives a working demonstration.
226
270
 
227
271
  ### Targets
228
272
 
229
- The click-to-add-points components take a `<canvas>`. The components that own
230
- sliders or menus take a container element and build their controls inside it.
231
- Each one also accepts an explicit `{ surface, element }` pair when you want to
232
- place the drawing surface and the controls yourself.
273
+ The click-to-add-points components take a `<canvas>`. The components that own sliders or menus take a container element and build their controls inside it. Each one also accepts an explicit `{ surface, element }` pair when you want to place the drawing surface and the controls yourself.
233
274
 
234
275
  ## Reproducing an interactive session
235
276
 
236
- R's gadgets block until you click Done, and then print the `plot_*()` call
237
- that reproduces the screen. Nothing blocks in a browser. Each component
238
- returns a handle at once, and the handle carries the same state:
277
+ R's gadgets block until you click Done, and then print the `plot_*()` call that reproduces the screen. Nothing blocks in a browser. Each component returns a handle at once, and the handle carries the same state:
239
278
 
240
279
  ```js
241
280
  const handle = interactiveTTest(container);
@@ -246,40 +285,22 @@ handle.done(); // hand the state to the onDone callback
246
285
  handle.destroy(); // stop listening and remove what was built
247
286
  ```
248
287
 
249
- `getValues()` returns the options that draw the same picture again. Pass them
250
- straight back to the matching plot function, or to the component itself. State
251
- you would not retype has its own accessor: `getFit()` for PCA, `getState()`
252
- for the accumulated sampling draws, `getSpec()` for the traces and layout of a
253
- 3D draw.
288
+ `getValues()` returns the options that draw the same picture again. Pass them straight back to the matching plot function, or to the component itself. State you would not retype has its own accessor: `getFit()` for PCA, `getState()` for the accumulated sampling draws, `getSpec()` for the traces and layout of a 3D draw.
254
289
 
255
290
  ## Differences from the R package
256
291
 
257
- The two packages compute the same statistics and draw the same pictures. The
258
- R idioms that a browser has no answer for are handled like this:
259
-
260
- - **Names.** `snake_case` becomes `camelCase`. `plot_regr()` is
261
- `plotRegression()`, as it is in R since 0.8.0.
262
- - **Signatures.** R's positional arguments and `...` become a data argument
263
- and one options object.
264
- - **Formulas.** `y ~ x * z` has no TypeScript counterpart. Name the columns
265
- instead: `{ outcome: "y", iv: "x", mod: "z" }`.
266
- - **Data frames.** Point sets are arrays of records. Bundled tables are
267
- objects of columns.
268
- - **Random numbers.** Draws take an injectable seeded generator, so a demo
269
- repeats exactly. The stream does not match R's Mersenne Twister, and it is
270
- not meant to.
271
- - **Devices.** Every plot function takes an explicit target. There is no
272
- current device.
273
-
274
- One thing the port adds. It exports the statistical primitives that base R
275
- hands the R package for free — descriptives, the t distribution, seeded
276
- samplers, binning, density, axis ticks and small linear algebra, listed under
277
- [Statistics without a picture](#statistics-without-a-picture). This is an
278
- addition, not a divergence: each one follows its R counterpart's rule and is
279
- tested against R's output.
280
-
281
- The core statistics are asserted against values computed in R. The fixtures
282
- live in the R package under `conformance-fixtures/`.
292
+ The two packages compute the same statistics and draw the same pictures. The R idioms that a browser has no answer for are handled like this:
293
+
294
+ - **Names.** `snake_case` becomes `camelCase`. `plot_regr()` is `plotRegression()`, as it is in R since 0.8.0.
295
+ - **Signatures.** R's positional arguments and `...` become a data argument and one options object.
296
+ - **Formulas.** `y ~ x * z` has no TypeScript counterpart. Name the columns instead: `{ outcome: "y", iv: "x", mod: "z" }`.
297
+ - **Data frames.** Point sets are arrays of records. Bundled tables are objects of columns.
298
+ - **Random numbers.** Draws take an injectable seeded generator, so a demo repeats exactly. The stream does not match R's Mersenne Twister, and it is not meant to.
299
+ - **Devices.** Every plot function takes an explicit target. There is no current device.
300
+
301
+ Two things the port adds. It exports the statistical primitives that base R hands the R package for free — descriptives, the t distribution, seeded samplers, binning, density, axis ticks and small linear algebra, listed under [Statistics without a picture](#statistics-without-a-picture). And it carries a general [linear-algebra entry](#linear-algebra) — a column-major matrix, `solve`, `qr`, `lm`, `prcomp` and the rest — for the same reason. Both are additions, not divergences: each routine follows its R counterpart's rule and is tested against R's output.
302
+
303
+ The core statistics are asserted against values computed in R. The fixtures live in the R package under `conformance-fixtures/`.
283
304
 
284
305
  ## Development
285
306
 
@@ -291,17 +312,13 @@ bun run build
291
312
  bun run dev # demo site on http://localhost:3000
292
313
  ```
293
314
 
294
- Bun is the toolchain: runtime, package manager, test runner, and bundler. The
295
- demo site runs one page per function family and is the fastest way to see a
296
- change.
315
+ Bun is the toolchain: runtime, package manager, test runner, and bundler. The demo site runs one page per function family and is the fastest way to see a change.
297
316
 
298
317
  ## Contributors
299
318
 
300
319
  `@compstats/core` and `compstatslib` are maintained by Soumya Ray.
301
320
 
302
- Daniele Melotti is a co-author of the R package. Several of the plotting and
303
- interactive functions grew out of work he did as a student under Soumya Ray's
304
- supervision, and were then folded back into the package.
321
+ Daniele Melotti is a co-author of the R package. Several of the plotting and interactive functions grew out of work he did as a student under Soumya Ray's supervision, and were then folded back into the package.
305
322
 
306
323
  Issues and pull requests are welcome.
307
324