@compstats/core 0.3.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.
- package/CHANGELOG.md +71 -0
- package/README.md +117 -116
- package/dist/3d.js +1120 -122
- package/dist/3d.js.map +16 -9
- package/dist/core/arith.d.ts.map +1 -1
- package/dist/core/linalg/cov.d.ts +50 -0
- package/dist/core/linalg/cov.d.ts.map +1 -0
- package/dist/core/linalg/eigen.d.ts +53 -0
- package/dist/core/linalg/eigen.d.ts.map +1 -0
- package/dist/core/linalg/lm.d.ts +78 -0
- package/dist/core/linalg/lm.d.ts.map +1 -0
- package/dist/core/linalg/lu.d.ts +154 -0
- package/dist/core/linalg/lu.d.ts.map +1 -0
- package/dist/core/linalg/matrix.d.ts +131 -0
- package/dist/core/linalg/matrix.d.ts.map +1 -0
- package/dist/core/linalg/modelMatrix.d.ts +69 -0
- package/dist/core/linalg/modelMatrix.d.ts.map +1 -0
- package/dist/core/linalg/namedVector.d.ts +37 -0
- package/dist/core/linalg/namedVector.d.ts.map +1 -0
- package/dist/core/linalg/ops.d.ts +120 -0
- package/dist/core/linalg/ops.d.ts.map +1 -0
- package/dist/core/linalg/prcomp.d.ts +66 -0
- package/dist/core/linalg/prcomp.d.ts.map +1 -0
- package/dist/core/linalg/qr.d.ts +134 -0
- package/dist/core/linalg/qr.d.ts.map +1 -0
- package/dist/core/linalg/vector.d.ts +68 -0
- package/dist/core/linalg/vector.d.ts.map +1 -0
- package/dist/core/moderation.d.ts +6 -3
- package/dist/core/moderation.d.ts.map +1 -1
- package/dist/core/ols.d.ts +4 -7
- package/dist/core/ols.d.ts.map +1 -1
- package/dist/data/moderationData.d.ts +2 -2
- package/dist/data/pcaDegenerate.d.ts +1 -1
- package/dist/index.js +1455 -863
- package/dist/index.js.map +16 -10
- package/dist/linalg.d.ts +36 -0
- package/dist/linalg.d.ts.map +1 -0
- package/dist/linalg.js +1860 -0
- package/dist/linalg.js.map +24 -0
- package/dist/plot/moderation3d.d.ts +1 -1
- package/dist/plot/scatter3d.d.ts +1 -1
- package/package.json +7 -2
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,77 @@
|
|
|
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
|
+
|
|
6
77
|
## 0.3.0
|
|
7
78
|
|
|
8
79
|
### Added
|
package/README.md
CHANGED
|
@@ -2,21 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://github.com/compstatslib/compstatslib-ts/actions/workflows/ci.yml)
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
package [
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
**
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
The
|
|
19
|
-
|
|
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,11 +27,9 @@ npm install @compstats/core
|
|
|
26
27
|
bun add @compstats/core
|
|
27
28
|
```
|
|
28
29
|
|
|
29
|
-
|
|
30
|
-
**optional peer dependency**: the package declares it, but no package manager
|
|
31
|
-
installs it for you. A page that draws only 2D never downloads it.
|
|
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.
|
|
32
31
|
|
|
33
|
-
Add
|
|
32
|
+
Add Plotly yourself when you use the 3D functions:
|
|
34
33
|
|
|
35
34
|
```bash
|
|
36
35
|
npm install plotly.js-dist-min
|
|
@@ -42,8 +41,38 @@ See [3D and Plotly](#3d-and-plotly) below.
|
|
|
42
41
|
|
|
43
42
|
## Quick start
|
|
44
43
|
|
|
45
|
-
|
|
46
|
-
|
|
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:
|
|
47
76
|
|
|
48
77
|
```html
|
|
49
78
|
<canvas id="plot" width="640" height="480"></canvas>
|
|
@@ -62,8 +91,7 @@ every click.
|
|
|
62
91
|
</script>
|
|
63
92
|
```
|
|
64
93
|
|
|
65
|
-
The plot functions draw the same picture from data you already hold, and
|
|
66
|
-
return what they computed:
|
|
94
|
+
The plot functions draw the same picture from data you already hold, and return what they computed:
|
|
67
95
|
|
|
68
96
|
```js
|
|
69
97
|
import { plotRegression } from "@compstats/core";
|
|
@@ -77,20 +105,9 @@ const fit = plotRegression(canvas, [
|
|
|
77
105
|
console.log(fit.slope, fit.rSquared);
|
|
78
106
|
```
|
|
79
107
|
|
|
80
|
-
The statistics are separate from the drawing. Import them alone when you want
|
|
81
|
-
the numbers and not the picture:
|
|
82
|
-
|
|
83
|
-
```js
|
|
84
|
-
import { linearRegression, principalComponents, tTestStats } from "@compstats/core";
|
|
85
|
-
```
|
|
86
|
-
|
|
87
|
-
Nothing in the `core/` layer touches the DOM, so it also runs under Node or
|
|
88
|
-
Bun.
|
|
89
|
-
|
|
90
108
|
## Use from a CDN
|
|
91
109
|
|
|
92
|
-
The main bundle is self-contained browser ESM with no imports of its own. A
|
|
93
|
-
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:
|
|
94
111
|
|
|
95
112
|
```html
|
|
96
113
|
<script type="module">
|
|
@@ -114,9 +131,7 @@ The 3D functions live behind the `@compstats/core/3d` entry point:
|
|
|
114
131
|
import { interactiveScatter3d, moderationData } from "@compstats/core/3d";
|
|
115
132
|
```
|
|
116
133
|
|
|
117
|
-
That entry does not load Plotly either. It reaches the library through a
|
|
118
|
-
dynamic import the first time it draws. A caller that passes its own engine in
|
|
119
|
-
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:
|
|
120
135
|
|
|
121
136
|
```html
|
|
122
137
|
<script src="https://cdn.jsdelivr.net/npm/plotly.js-dist-min"></script>
|
|
@@ -133,14 +148,9 @@ the `plotly` option never triggers that import:
|
|
|
133
148
|
</script>
|
|
134
149
|
```
|
|
135
150
|
|
|
136
|
-
Pass `plotly` when you load the 3D entry from a raw file CDN such as jsDelivr,
|
|
137
|
-
because nothing there resolves the bare `plotly.js-dist-min` specifier. esm.sh
|
|
138
|
-
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.
|
|
139
152
|
|
|
140
|
-
Under a bundler, the dynamic import needs Plotly in your own dependencies. If
|
|
141
|
-
it is absent, the load fails with `Cannot find package 'plotly.js-dist-min'`.
|
|
142
|
-
The two cures are the same two paths: install the optional peer, or pass your
|
|
143
|
-
own engine in the `plotly` option and let the dynamic import stay unreached.
|
|
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.
|
|
144
154
|
|
|
145
155
|
## How the functions are organized
|
|
146
156
|
|
|
@@ -148,16 +158,13 @@ Every family has three parts, and you can use any one of them alone:
|
|
|
148
158
|
|
|
149
159
|
- a **core** function that computes the statistics and touches no DOM,
|
|
150
160
|
- a **plot** function that draws it on a target you give it,
|
|
151
|
-
- an **interactive** component that owns the input and hands each draw to the
|
|
152
|
-
plot function.
|
|
161
|
+
- an **interactive** component that owns the input and hands each draw to the plot function.
|
|
153
162
|
|
|
154
|
-
The families are grouped below by what they are *for*, because that varies
|
|
155
|
-
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.
|
|
156
164
|
|
|
157
165
|
### Data sets in 3D
|
|
158
166
|
|
|
159
|
-
These accept any data frame, with control over axes, color mapping, aspect
|
|
160
|
-
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`.
|
|
161
168
|
|
|
162
169
|
| Function | What it does |
|
|
163
170
|
| --- | --- |
|
|
@@ -169,11 +176,7 @@ ratio, and camera. They come from `@compstats/core/3d`.
|
|
|
169
176
|
|
|
170
177
|
### 2D relationships
|
|
171
178
|
|
|
172
|
-
These plot x / y points you supply, together with a fitted model. They are
|
|
173
|
-
sized for small data — points clicked in by hand, or a modest table — and not
|
|
174
|
-
for arbitrary data. `plotRegression` draws in a window of -5 to 50 unless you
|
|
175
|
-
give it `xlim` and `ylim`, and the PCA functions expect points with an `x` and
|
|
176
|
-
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`.
|
|
177
180
|
|
|
178
181
|
| Function | What it does |
|
|
179
182
|
| --- | --- |
|
|
@@ -187,8 +190,7 @@ a `y`.
|
|
|
187
190
|
|
|
188
191
|
### Simulations and concept demonstrations
|
|
189
192
|
|
|
190
|
-
These do not plot your data. They simulate a process, or draw a geometric
|
|
191
|
-
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.
|
|
192
194
|
|
|
193
195
|
| Function | What it does |
|
|
194
196
|
| --- | --- |
|
|
@@ -203,11 +205,7 @@ object, so that a concept can be watched instead of described.
|
|
|
203
205
|
|
|
204
206
|
### Statistics without a picture
|
|
205
207
|
|
|
206
|
-
The
|
|
207
|
-
`rnorm()`, `density()`, `hist()`, `pretty()`, `solve()` and `lm.fit()` are all
|
|
208
|
-
in base R, and its functions call them. JavaScript has no statistics standard
|
|
209
|
-
library, so the port wrote them — and exports them, because an application
|
|
210
|
-
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.
|
|
211
209
|
|
|
212
210
|
| Group | Functions |
|
|
213
211
|
| --- | --- |
|
|
@@ -216,12 +214,9 @@ built on this package needs them for the same reason the package did.
|
|
|
216
214
|
| Seeded random draws | `seededRng`, `runif`, `rnorm`, `rt`, `rlnorm`, `rcauchy`, `sampleWithoutReplacement` |
|
|
217
215
|
| Binning and density | `histogram`, `nclassSturges`, `kernelDensity`, `bwNrd0` |
|
|
218
216
|
| Axis ticks | `rPretty`, `prettyTicks` |
|
|
219
|
-
|
|
|
217
|
+
| Fitting and 2x2 matrices | `leastSquares`, `determinant`, `invertMatrix` |
|
|
220
218
|
|
|
221
|
-
|
|
222
|
-
`quantile` is type 7, `nclassSturges` is Sturges' rule, `bwNrd0` is R's
|
|
223
|
-
`nrd0` bandwidth, `rPretty` is `pretty()`, and the samplers take R's own
|
|
224
|
-
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.
|
|
225
220
|
|
|
226
221
|
The draws take a generator you pass in, so a demonstration repeats exactly:
|
|
227
222
|
|
|
@@ -233,25 +228,53 @@ const draws = rnorm(rng, 1000, { mean: 100, sd: 15 });
|
|
|
233
228
|
quantile(draws, 0.975);
|
|
234
229
|
```
|
|
235
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
|
+
|
|
236
267
|
### Bundled data
|
|
237
268
|
|
|
238
|
-
`moderationData` (200 rows of `y`, `x`, `z`, `w`) and `pcaDegenerate` (16 rows
|
|
239
|
-
of `x`, `y`) are the same tables as in the R package, exported from R rather
|
|
240
|
-
than regenerated. Both are defaults, so a call with no data still gives a
|
|
241
|
-
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.
|
|
242
270
|
|
|
243
271
|
### Targets
|
|
244
272
|
|
|
245
|
-
The click-to-add-points components take a `<canvas>`. The components that own
|
|
246
|
-
sliders or menus take a container element and build their controls inside it.
|
|
247
|
-
Each one also accepts an explicit `{ surface, element }` pair when you want to
|
|
248
|
-
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.
|
|
249
274
|
|
|
250
275
|
## Reproducing an interactive session
|
|
251
276
|
|
|
252
|
-
R's gadgets block until you click Done, and then print the `plot_*()` call
|
|
253
|
-
that reproduces the screen. Nothing blocks in a browser. Each component
|
|
254
|
-
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:
|
|
255
278
|
|
|
256
279
|
```js
|
|
257
280
|
const handle = interactiveTTest(container);
|
|
@@ -262,40 +285,22 @@ handle.done(); // hand the state to the onDone callback
|
|
|
262
285
|
handle.destroy(); // stop listening and remove what was built
|
|
263
286
|
```
|
|
264
287
|
|
|
265
|
-
`getValues()` returns the options that draw the same picture again. Pass them
|
|
266
|
-
straight back to the matching plot function, or to the component itself. State
|
|
267
|
-
you would not retype has its own accessor: `getFit()` for PCA, `getState()`
|
|
268
|
-
for the accumulated sampling draws, `getSpec()` for the traces and layout of a
|
|
269
|
-
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.
|
|
270
289
|
|
|
271
290
|
## Differences from the R package
|
|
272
291
|
|
|
273
|
-
The two packages compute the same statistics and draw the same pictures. The
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
- **
|
|
277
|
-
|
|
278
|
-
- **
|
|
279
|
-
|
|
280
|
-
- **
|
|
281
|
-
|
|
282
|
-
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
repeats exactly. The stream does not match R's Mersenne Twister, and it is
|
|
286
|
-
not meant to.
|
|
287
|
-
- **Devices.** Every plot function takes an explicit target. There is no
|
|
288
|
-
current device.
|
|
289
|
-
|
|
290
|
-
One thing the port adds. It exports the statistical primitives that base R
|
|
291
|
-
hands the R package for free — descriptives, the t distribution, seeded
|
|
292
|
-
samplers, binning, density, axis ticks and small linear algebra, listed under
|
|
293
|
-
[Statistics without a picture](#statistics-without-a-picture). This is an
|
|
294
|
-
addition, not a divergence: each one follows its R counterpart's rule and is
|
|
295
|
-
tested against R's output.
|
|
296
|
-
|
|
297
|
-
The core statistics are asserted against values computed in R. The fixtures
|
|
298
|
-
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/`.
|
|
299
304
|
|
|
300
305
|
## Development
|
|
301
306
|
|
|
@@ -307,17 +312,13 @@ bun run build
|
|
|
307
312
|
bun run dev # demo site on http://localhost:3000
|
|
308
313
|
```
|
|
309
314
|
|
|
310
|
-
Bun is the toolchain: runtime, package manager, test runner, and bundler. The
|
|
311
|
-
demo site runs one page per function family and is the fastest way to see a
|
|
312
|
-
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.
|
|
313
316
|
|
|
314
317
|
## Contributors
|
|
315
318
|
|
|
316
319
|
`@compstats/core` and `compstatslib` are maintained by Soumya Ray.
|
|
317
320
|
|
|
318
|
-
Daniele Melotti is a co-author of the R package. Several of the plotting and
|
|
319
|
-
interactive functions grew out of work he did as a student under Soumya Ray's
|
|
320
|
-
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.
|
|
321
322
|
|
|
322
323
|
Issues and pull requests are welcome.
|
|
323
324
|
|