@numero-uno-jpn/rstats 0.5.0 → 0.6.1
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 +101 -0
- package/README.ja.md +36 -20
- package/README.md +34 -18
- package/THIRD_PARTY_NOTICES.md +25 -5
- package/dist/chunks/chunk-2JCIB5H2.js +2 -0
- package/dist/chunks/chunk-7JVSDGQQ.js +2 -0
- package/dist/chunks/chunk-7NK4SA6B.js +2 -0
- package/dist/chunks/chunk-AJYQNFXK.js +2 -0
- package/dist/chunks/chunk-DRNMIEYH.js +2 -0
- package/dist/chunks/chunk-NAYDS7CL.js +2 -0
- package/dist/chunks/chunk-NOCEAM44.js +2 -0
- package/dist/chunks/chunk-QNAIK4PY.js +2 -0
- package/dist/chunks/chunk-TPC6MTYC.js +2 -0
- package/dist/chunks/chunk-UGUQYWRQ.js +2 -0
- package/dist/chunks/chunk-XM2TVD63.js +2 -0
- package/dist/chunks/chunk-Y4IZV7XK.js +2 -0
- package/dist/chunks/chunk-ZWJ42QN6.js +2 -0
- package/dist/descriptive/basic.d.ts +73 -0
- package/dist/descriptive/basic.js +256 -0
- package/dist/descriptive/cov.d.ts +39 -0
- package/dist/descriptive/cov.js +105 -0
- package/dist/descriptive/hist.d.ts +64 -0
- package/dist/descriptive/hist.js +304 -0
- package/dist/descriptive/moments.d.ts +63 -0
- package/dist/descriptive/moments.js +158 -0
- package/dist/descriptive/quantile.d.ts +50 -0
- package/dist/descriptive/quantile.js +131 -0
- package/dist/descriptive/rank.d.ts +23 -0
- package/dist/descriptive/rank.js +72 -0
- package/dist/descriptive/table.d.ts +54 -0
- package/dist/descriptive/table.js +160 -0
- package/dist/descriptive.js +2 -0
- package/dist/distributions/arcsine.d.ts +24 -0
- package/dist/distributions/arcsine.js +73 -0
- package/dist/distributions/invgamma.d.ts +29 -0
- package/dist/distributions/invgamma.js +85 -0
- package/dist/distributions/kumaraswamy.d.ts +17 -0
- package/dist/distributions/kumaraswamy.js +96 -0
- package/dist/distributions/laplace.d.ts +23 -0
- package/dist/distributions/laplace.js +69 -0
- package/dist/distributions/multinomial.d.ts +24 -0
- package/dist/distributions/multinomial.js +70 -0
- package/dist/distributions/nbeta.js +28 -10
- package/dist/distributions/nchisq.js +20 -3
- package/dist/distributions/pareto.d.ts +23 -0
- package/dist/distributions/pareto.js +76 -0
- package/dist/distributions/summary.d.ts +25 -0
- package/dist/distributions/summary.js +289 -0
- package/dist/distributions/triangular.d.ts +25 -0
- package/dist/distributions/triangular.js +87 -0
- package/dist/distributions.js +2 -0
- package/dist/entries/descriptive.d.ts +7 -0
- package/dist/entries/descriptive.js +8 -0
- package/dist/entries/distributions.d.ts +41 -0
- package/dist/entries/distributions.js +41 -0
- package/dist/entries/linalg.d.ts +11 -0
- package/dist/entries/linalg.js +11 -0
- package/dist/entries/numerics.d.ts +8 -0
- package/dist/entries/numerics.js +6 -0
- package/dist/entries/special.d.ts +9 -0
- package/dist/entries/special.js +9 -0
- package/dist/entries/tests.d.ts +21 -0
- package/dist/entries/tests.js +20 -0
- package/dist/htest/ansariTest.d.ts +13 -0
- package/dist/htest/ansariTest.js +80 -0
- package/dist/htest/aov.d.ts +85 -0
- package/dist/htest/aov.js +178 -0
- package/dist/htest/binomTest.d.ts +36 -0
- package/dist/htest/binomTest.js +121 -0
- package/dist/htest/cor.d.ts +14 -0
- package/dist/htest/cor.js +4 -3
- package/dist/htest/core/ansari.d.ts +18 -0
- package/dist/htest/core/ansari.js +121 -0
- package/dist/htest/core/kolmogorov.d.ts +26 -0
- package/dist/htest/core/kolmogorov.js +221 -0
- package/dist/htest/core/mantelhaen.d.ts +26 -0
- package/dist/htest/core/mantelhaen.js +145 -0
- package/dist/htest/core/shapiro.d.ts +24 -0
- package/dist/htest/core/shapiro.js +142 -0
- package/dist/htest/core/smirnov.d.ts +20 -0
- package/dist/htest/core/smirnov.js +75 -0
- package/dist/htest/fisherTest.js +134 -120
- package/dist/htest/groups.d.ts +20 -0
- package/dist/htest/groups.js +62 -0
- package/dist/htest/ksTest.d.ts +31 -0
- package/dist/htest/ksTest.js +106 -0
- package/dist/htest/mantelhaenTest.d.ts +26 -0
- package/dist/htest/mantelhaenTest.js +196 -0
- package/dist/htest/mcnemarTest.d.ts +26 -0
- package/dist/htest/mcnemarTest.js +99 -0
- package/dist/htest/onewayTest.d.ts +26 -0
- package/dist/htest/onewayTest.js +97 -0
- package/dist/htest/pairwise.d.ts +64 -0
- package/dist/htest/pairwise.js +152 -0
- package/dist/htest/poissonTest.d.ts +35 -0
- package/dist/htest/poissonTest.js +170 -0
- package/dist/htest/propTest.d.ts +37 -0
- package/dist/htest/propTest.js +164 -0
- package/dist/htest/rankTests.d.ts +52 -0
- package/dist/htest/rankTests.js +234 -0
- package/dist/htest/types.d.ts +2 -0
- package/dist/htest/types.js +6 -0
- package/dist/htest/varTest.d.ts +48 -0
- package/dist/htest/varTest.js +118 -0
- package/dist/htest/wilcoxTest.js +136 -174
- package/dist/index.d.ts +6 -50
- package/dist/index.js +8 -50
- package/dist/integrate/quadpack.d.ts +29 -0
- package/dist/integrate/quadpack.js +525 -0
- package/dist/internal/seq.d.ts +5 -0
- package/dist/internal/seq.js +14 -0
- package/dist/internal/uniroot.js +10 -71
- package/dist/linalg/lu.d.ts +45 -0
- package/dist/linalg/lu.js +88 -0
- package/dist/linalg/ops.d.ts +90 -0
- package/dist/linalg/ops.js +320 -0
- package/dist/linalg.js +2 -0
- package/dist/models/lm.d.ts +98 -0
- package/dist/models/lm.js +187 -0
- package/dist/numerics/deriv.d.ts +37 -0
- package/dist/numerics/deriv.js +150 -0
- package/dist/numerics/integrate.d.ts +91 -0
- package/dist/numerics/integrate.js +264 -0
- package/dist/numerics/interp.d.ts +103 -0
- package/dist/numerics/interp.js +581 -0
- package/dist/numerics/ode.d.ts +23 -0
- package/dist/numerics/ode.js +59 -0
- package/dist/numerics.js +2 -0
- package/dist/optim/localmin.d.ts +16 -0
- package/dist/optim/localmin.js +97 -0
- package/dist/random/beta.js +1 -1
- package/dist/random/binomial.js +1 -1
- package/dist/random/continuous.js +1 -1
- package/dist/random/extra.d.ts +33 -0
- package/dist/random/extra.js +62 -0
- package/dist/random/gamma.js +1 -1
- package/dist/random/hyper.js +1 -1
- package/dist/random/mt.js +0 -1
- package/dist/random/poisson.js +0 -1
- package/dist/random/rank.js +1 -1
- package/dist/random/rng.js +1 -1
- package/dist/random/sample.js +1 -1
- package/dist/rstats.js +2 -2
- package/dist/special/besselIK.d.ts +38 -0
- package/dist/special/besselIK.js +315 -0
- package/dist/special/besselJY.d.ts +32 -0
- package/dist/special/besselJY.js +508 -0
- package/dist/special/besselSeries.d.ts +8 -0
- package/dist/special/besselSeries.js +24 -0
- package/dist/special/beta.js +22 -5
- package/dist/special/choose.d.ts +75 -0
- package/dist/special/choose.js +233 -0
- package/dist/special/erf.d.ts +28 -2
- package/dist/special/erf.js +37 -10
- package/dist/special/extra.d.ts +47 -0
- package/dist/special/extra.js +220 -0
- package/dist/special/igam.d.ts +4 -0
- package/dist/special/igam.js +57 -2
- package/dist/special/incbet.js +77 -22
- package/dist/special/lgammaDiff.d.ts +7 -0
- package/dist/special/lgammaDiff.js +16 -3
- package/dist/special/psigamma.d.ts +48 -0
- package/dist/special/psigamma.js +318 -0
- package/dist/special/rCompatible.d.ts +8 -0
- package/dist/special/rCompatible.js +1 -0
- package/dist/special.js +2 -0
- package/dist/tests.js +2 -0
- package/package.json +26 -1
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
// Linear models with a matrix interface: R's lm (with summary.lm's table), confint and predict.
|
|
2
|
+
//
|
|
3
|
+
// Written for rStats.js (no ported code). The fit is the QR decomposition of the model matrix (a column of ones
|
|
4
|
+
// first unless `intercept` is false, rows scaled by sqrt(weights)) with R's LINPACK pivoting (src/linalg/qr.ts,
|
|
5
|
+
// tolerance 1e-7): columns that are linear combinations of earlier ones are moved to the end and get NaN
|
|
6
|
+
// coefficients ("aliased"), as in R. The summary follows summary.lm: residual variance rss / (n - rank),
|
|
7
|
+
// covariance of the estimates sigma^2 (R'R)^-1 from the triangular factor, t tests on n - rank degrees of freedom,
|
|
8
|
+
// R^2 from the (weighted) mean-corrected model sum of squares when there is an intercept, and the overall F test.
|
|
9
|
+
// Rows of weight 0 are left out of the fit and of the counts of observations, but get fitted values (and residuals)
|
|
10
|
+
// from the coefficients, as in R.
|
|
11
|
+
import { pf1 } from "../distributions/f.js";
|
|
12
|
+
import { pt1, qt1 } from "../distributions/t.js";
|
|
13
|
+
import { chol2inv } from "../linalg/chol.js";
|
|
14
|
+
import { qr, qrCoef, qrResid } from "../linalg/qr.js";
|
|
15
|
+
/**
|
|
16
|
+
* Fit a linear model by least squares. Equivalent to R's `lm(y ~ x)` (or `y ~ x - 1` without intercept, with
|
|
17
|
+
* `weights` for weighted least squares) together with `summary()`; the predictors are given as a matrix instead of a
|
|
18
|
+
* formula. Rows with a missing value in `x`, `y` or the weights are dropped.
|
|
19
|
+
*
|
|
20
|
+
* @param x Predictors: a matrix (array of rows, one column per predictor) or a vector (one predictor).
|
|
21
|
+
* @param y Response.
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* ```ts
|
|
25
|
+
* const fit = lm([1, 2, 3, 4, 5], [2.1, 3.9, 6.2, 7.8, 10.1]);
|
|
26
|
+
* fit.coefficients; // [intercept, slope]
|
|
27
|
+
* fit.rSquared;
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
export function lm(x, y, options = {}) {
|
|
31
|
+
const intercept = options.intercept ?? true;
|
|
32
|
+
const xr = x.length > 0 && typeof x[0] !== "number" ? x.map((r) => Array.from(r)) : Array.from(x, (v) => [v]);
|
|
33
|
+
const ys = Array.from(y);
|
|
34
|
+
if (xr.length !== ys.length)
|
|
35
|
+
throw new RangeError("variable lengths differ (found for 'x')");
|
|
36
|
+
const wsAll = options.weights === undefined ? undefined : Array.from(options.weights);
|
|
37
|
+
if (wsAll !== undefined && wsAll.length !== ys.length)
|
|
38
|
+
throw new RangeError("variable lengths differ (found for '(weights)')");
|
|
39
|
+
if (wsAll?.some((w) => w < 0))
|
|
40
|
+
throw new RangeError("missing or negative weights not allowed");
|
|
41
|
+
const keep = ys.map((v, i) => !Number.isNaN(v) && xr[i].every((t) => !Number.isNaN(t)) && (wsAll === undefined || !Number.isNaN(wsAll[i])));
|
|
42
|
+
const Xall = xr.filter((_, i) => keep[i]).map((r) => (intercept ? [1, ...r] : r));
|
|
43
|
+
const Yall = ys.filter((_, i) => keep[i]);
|
|
44
|
+
const Wall = wsAll?.filter((_, i) => keep[i]);
|
|
45
|
+
if (Yall.length === 0)
|
|
46
|
+
throw new RangeError("0 (non-NA) cases");
|
|
47
|
+
// the fit uses the rows of non-zero weight only
|
|
48
|
+
const fitRow = Yall.map((_, i) => Wall === undefined || Wall[i] !== 0);
|
|
49
|
+
const X = Xall.filter((_, i) => fitRow[i]);
|
|
50
|
+
const Y = Yall.filter((_, i) => fitRow[i]);
|
|
51
|
+
const W = Wall?.filter((_, i) => fitRow[i]);
|
|
52
|
+
const n = Y.length;
|
|
53
|
+
if (n === 0)
|
|
54
|
+
throw new RangeError("all weights are zero");
|
|
55
|
+
const sw = W?.map(Math.sqrt);
|
|
56
|
+
const Xw = sw === undefined ? X : X.map((r, i) => r.map((v) => v * sw[i]));
|
|
57
|
+
const Yw = sw === undefined ? Y : Y.map((v, i) => v * sw[i]);
|
|
58
|
+
const q = qr(Xw);
|
|
59
|
+
const rank = q.rank;
|
|
60
|
+
const coefficients = qrCoef(q, Yw);
|
|
61
|
+
// residuals from Q (the part of Q'y beyond the rank), so a saturated model has residuals of exactly 0, as in R
|
|
62
|
+
const rw = qrResid(q, Yw);
|
|
63
|
+
const fitResiduals = sw === undefined ? rw : rw.map((v, i) => v / sw[i]);
|
|
64
|
+
// rows of weight 0: fitted values x'b (aliased coefficients count as 0), residuals y - fitted
|
|
65
|
+
const residuals = [];
|
|
66
|
+
const fittedValues = [];
|
|
67
|
+
let k = 0;
|
|
68
|
+
Yall.forEach((v, i) => {
|
|
69
|
+
if (fitRow[i]) {
|
|
70
|
+
residuals.push(fitResiduals[k]);
|
|
71
|
+
fittedValues.push(v - fitResiduals[k]);
|
|
72
|
+
k++;
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
const fv = Xall[i].reduce((s, t, j) => (Number.isNaN(coefficients[j]) ? s : s + t * coefficients[j]), 0);
|
|
76
|
+
fittedValues.push(fv);
|
|
77
|
+
residuals.push(v - fv);
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
const fitted = fittedValues.filter((_, i) => fitRow[i]);
|
|
81
|
+
const dfResidual = n - rank;
|
|
82
|
+
const w = W ?? new Array(n).fill(1);
|
|
83
|
+
let rss = 0;
|
|
84
|
+
for (let i = 0; i < n; i++)
|
|
85
|
+
rss += w[i] * fitResiduals[i] ** 2;
|
|
86
|
+
const resvar = rss / dfResidual;
|
|
87
|
+
const sigma = Math.sqrt(resvar);
|
|
88
|
+
// (R'R)^-1 of the leading rank x rank block, in pivot order
|
|
89
|
+
const Rr = q.qr.slice(0, rank).map((row) => row.slice(0, rank));
|
|
90
|
+
const covUnscaled = rank > 0 ? chol2inv(Rr) : [];
|
|
91
|
+
const piv = q.pivot.slice(0, rank);
|
|
92
|
+
const order = piv.map((col, k) => [col, k]).sort((a, b) => a[0] - b[0]);
|
|
93
|
+
const estimate = order.map(([col]) => coefficients[col]);
|
|
94
|
+
const stdError = order.map(([, k]) => Math.sqrt(covUnscaled[k][k] * resvar));
|
|
95
|
+
const tValue = estimate.map((e, i) => e / stdError[i]);
|
|
96
|
+
const pValue = tValue.map((t) => 2 * pt1(-Math.abs(t), dfResidual, true, false));
|
|
97
|
+
const result = {
|
|
98
|
+
coefficients,
|
|
99
|
+
residuals,
|
|
100
|
+
fittedValues,
|
|
101
|
+
rank,
|
|
102
|
+
dfResidual,
|
|
103
|
+
coefTable: { estimate, stdError, tValue, pValue },
|
|
104
|
+
sigma,
|
|
105
|
+
rSquared: 0,
|
|
106
|
+
adjRSquared: 0,
|
|
107
|
+
intercept,
|
|
108
|
+
qr: q,
|
|
109
|
+
};
|
|
110
|
+
const dfInt = intercept ? 1 : 0;
|
|
111
|
+
const numdf = rank - dfInt;
|
|
112
|
+
// nothing beyond the intercept: R^2 = 0 and no F test, as in summary.lm
|
|
113
|
+
if (numdf === 0)
|
|
114
|
+
return result;
|
|
115
|
+
let mss = 0;
|
|
116
|
+
if (intercept) {
|
|
117
|
+
const sumw = w.reduce((a, b) => a + b, 0);
|
|
118
|
+
let m = 0;
|
|
119
|
+
for (let i = 0; i < n; i++)
|
|
120
|
+
m += (w[i] * fitted[i]) / sumw;
|
|
121
|
+
for (let i = 0; i < n; i++)
|
|
122
|
+
mss += w[i] * (fitted[i] - m) ** 2;
|
|
123
|
+
}
|
|
124
|
+
else
|
|
125
|
+
for (let i = 0; i < n; i++)
|
|
126
|
+
mss += w[i] * fitted[i] ** 2;
|
|
127
|
+
result.rSquared = mss / (mss + rss);
|
|
128
|
+
result.adjRSquared = 1 - (1 - result.rSquared) * ((n - dfInt) / dfResidual);
|
|
129
|
+
const fValue = mss / numdf / resvar;
|
|
130
|
+
result.fStatistic = { value: fValue, numdf, dendf: dfResidual };
|
|
131
|
+
result.fPValue = pf1(fValue, numdf, dfResidual, false, false);
|
|
132
|
+
return result;
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Confidence intervals for the coefficients of a linear model (NaN for aliased columns). Equivalent to R's
|
|
136
|
+
* `confint()` for an `lm` fit.
|
|
137
|
+
*/
|
|
138
|
+
export function confint(fit, options = {}) {
|
|
139
|
+
const level = options.level ?? 0.95;
|
|
140
|
+
const t = qt1((1 + level) / 2, fit.dfResidual, true, false);
|
|
141
|
+
const { estimate, stdError } = fit.coefTable;
|
|
142
|
+
const lower = fit.coefficients.map(() => NaN);
|
|
143
|
+
const upper = fit.coefficients.map(() => NaN);
|
|
144
|
+
let k = 0;
|
|
145
|
+
fit.coefficients.forEach((c, j) => {
|
|
146
|
+
if (Number.isNaN(c))
|
|
147
|
+
return;
|
|
148
|
+
lower[j] = estimate[k] - t * stdError[k];
|
|
149
|
+
upper[j] = estimate[k] + t * stdError[k];
|
|
150
|
+
k++;
|
|
151
|
+
});
|
|
152
|
+
return { lower, upper };
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Predictions of a linear model at new values of the predictors, with standard errors and optionally intervals.
|
|
156
|
+
* Equivalent to R's `predict()` for an `lm` fit (`se.fit = TRUE`). Aliased columns are left out, as R does.
|
|
157
|
+
*
|
|
158
|
+
* @param newdata New predictor values, with the columns of `x` (no column of ones).
|
|
159
|
+
*/
|
|
160
|
+
export function predict(fit, newdata, options = {}) {
|
|
161
|
+
const rows = newdata.length > 0 && typeof newdata[0] !== "number"
|
|
162
|
+
? newdata.map((r) => Array.from(r))
|
|
163
|
+
: Array.from(newdata, (v) => [v]);
|
|
164
|
+
const X = rows.map((r) => (fit.intercept ? [1, ...r] : r));
|
|
165
|
+
const rank = fit.rank;
|
|
166
|
+
const piv = fit.qr.pivot.slice(0, rank);
|
|
167
|
+
const Rr = fit.qr.qr.slice(0, rank).map((row) => row.slice(0, rank));
|
|
168
|
+
const resvar = fit.sigma ** 2;
|
|
169
|
+
const values = X.map((r) => r.reduce((s, v, j) => (Number.isNaN(fit.coefficients[j]) ? s : s + v * fit.coefficients[j]), 0));
|
|
170
|
+
// se: || R^-T x_piv || sigma, by forward substitution with R^T
|
|
171
|
+
const seFit = X.map((r) => {
|
|
172
|
+
const z = new Array(rank).fill(0);
|
|
173
|
+
for (let i = 0; i < rank; i++) {
|
|
174
|
+
let s = r[piv[i]];
|
|
175
|
+
for (let k = 0; k < i; k++)
|
|
176
|
+
s -= Rr[k][i] * z[k];
|
|
177
|
+
z[i] = s / Rr[i][i];
|
|
178
|
+
}
|
|
179
|
+
return Math.sqrt(z.reduce((a, v) => a + v * v, 0) * resvar);
|
|
180
|
+
});
|
|
181
|
+
const interval = options.interval ?? "none";
|
|
182
|
+
if (interval === "none")
|
|
183
|
+
return { fit: values, seFit };
|
|
184
|
+
const t = qt1((1 + (options.level ?? 0.95)) / 2, fit.dfResidual, true, false);
|
|
185
|
+
const half = seFit.map((s) => t * Math.sqrt(s * s + (interval === "prediction" ? resvar : 0)));
|
|
186
|
+
return { fit: values, seFit, lwr: values.map((v, i) => v - half[i]), upr: values.map((v, i) => v + half[i]) };
|
|
187
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/** Options for {@link grad}, {@link jacobian} and {@link hessian}. */
|
|
2
|
+
export interface DerivOptions {
|
|
3
|
+
/** `"Richardson"` (default) or `"simple"` (forward difference; not for `hessian`). */
|
|
4
|
+
method?: "Richardson" | "simple";
|
|
5
|
+
/** Step for "simple", and for coordinates near 0 in "Richardson". Default `1e-4`. */
|
|
6
|
+
eps?: number;
|
|
7
|
+
/** Relative initial step of "Richardson". Default `1e-4` (`0.1` for `hessian`). */
|
|
8
|
+
d?: number;
|
|
9
|
+
/** Coordinates with |x| below this get the step `eps`. Default `sqrt(2.220446e-16 / 7e-7)`. */
|
|
10
|
+
zeroTol?: number;
|
|
11
|
+
/** Number of halvings of the step (Richardson iterations). Default `4`. */
|
|
12
|
+
r?: number;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Gradient of a scalar function of a vector. Equivalent to `numDeriv::grad(func, x, method)`.
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```ts
|
|
19
|
+
* grad((x) => x[0]! ** 2 + Math.sin(x[1]!), [1, 0]); // ≈ [2, 1]
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
export declare function grad(func: (x: number[]) => number, x: readonly number[], options?: DerivOptions): number[];
|
|
23
|
+
/**
|
|
24
|
+
* Jacobian of a vector function of a vector: row i holds the derivatives of the i-th value. Equivalent to
|
|
25
|
+
* `numDeriv::jacobian(func, x, method)`.
|
|
26
|
+
*/
|
|
27
|
+
export declare function jacobian(func: (x: number[]) => readonly number[], x: readonly number[], options?: DerivOptions): number[][];
|
|
28
|
+
/**
|
|
29
|
+
* Hessian matrix of a scalar function of a vector, by Richardson extrapolation (Bates and Watts). Equivalent to
|
|
30
|
+
* `numDeriv::hessian(func, x)`.
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* ```ts
|
|
34
|
+
* hessian((x) => x[0]! ** 2 * x[1]!, [1, 2]); // ≈ [[4, 2], [2, 0]]
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
37
|
+
export declare function hessian(func: (x: number[]) => number, x: readonly number[], options?: DerivOptions): number[][];
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
// Numerical derivatives: numDeriv's grad, jacobian and hessian (methods "Richardson" and "simple").
|
|
2
|
+
//
|
|
3
|
+
// Written for rStats.js from the textbook methods (no ported code); the defaults and the choice of steps follow
|
|
4
|
+
// numDeriv's documentation so the results agree with it. Richardson: central differences with steps h, h/2, ...,
|
|
5
|
+
// h/2^(r-1), h = d |x_i| (eps where |x_i| is below zeroTol), combined by Richardson extrapolation
|
|
6
|
+
// A <- (4^m A(h/2) - A(h)) / (4^m - 1) for m = 1, ..., r - 1. The Hessian is that of Bates and Watts (1988): the
|
|
7
|
+
// diagonal from second central differences, the off-diagonal entries from differences along e_i + e_j less the
|
|
8
|
+
// diagonal terms, each extrapolated the same way (with d = 0.1). "simple" is the forward difference with step eps.
|
|
9
|
+
// numDeriv's "complex" (complex-step) method needs a function of a complex argument and is not offered.
|
|
10
|
+
function steps(x, options, d0) {
|
|
11
|
+
const eps = options.eps ?? 1e-4;
|
|
12
|
+
const d = options.d ?? d0;
|
|
13
|
+
const zeroTol = options.zeroTol ?? Math.sqrt(Number.EPSILON / 7e-7);
|
|
14
|
+
const r = options.r ?? 4;
|
|
15
|
+
if (!(Number.isInteger(r) && r >= 1))
|
|
16
|
+
throw new RangeError("'r' must be a positive integer");
|
|
17
|
+
return { eps, r, h0: x.map((v) => Math.abs(d * v) + (Math.abs(v) < zeroTol ? eps : 0)) };
|
|
18
|
+
}
|
|
19
|
+
/** Richardson extrapolation of approximations at steps h, h/2, h/4, ... (errors in even powers of h) */
|
|
20
|
+
function extrapolate(a) {
|
|
21
|
+
const t = [...a];
|
|
22
|
+
for (let m = 1; m < t.length; m++) {
|
|
23
|
+
const p = 4 ** m;
|
|
24
|
+
for (let k = 0; k < t.length - m; k++)
|
|
25
|
+
t[k] = (t[k + 1] * p - t[k]) / (p - 1);
|
|
26
|
+
}
|
|
27
|
+
return t[0];
|
|
28
|
+
}
|
|
29
|
+
function shifted(x, i, h) {
|
|
30
|
+
const y = [...x];
|
|
31
|
+
y[i] = y[i] + h;
|
|
32
|
+
return y;
|
|
33
|
+
}
|
|
34
|
+
function method(options) {
|
|
35
|
+
const m = options.method ?? "Richardson";
|
|
36
|
+
if (m !== "Richardson" && m !== "simple")
|
|
37
|
+
throw new RangeError(`unknown method "${String(m)}" (expected "Richardson" or "simple")`);
|
|
38
|
+
return m;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Gradient of a scalar function of a vector. Equivalent to `numDeriv::grad(func, x, method)`.
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* ```ts
|
|
45
|
+
* grad((x) => x[0]! ** 2 + Math.sin(x[1]!), [1, 0]); // ≈ [2, 1]
|
|
46
|
+
* ```
|
|
47
|
+
*/
|
|
48
|
+
export function grad(func, x, options = {}) {
|
|
49
|
+
const xs = [...x];
|
|
50
|
+
const n = xs.length;
|
|
51
|
+
if (method(options) === "simple") {
|
|
52
|
+
const eps = options.eps ?? 1e-4;
|
|
53
|
+
const f0 = func([...xs]);
|
|
54
|
+
return xs.map((_, i) => (func(shifted(xs, i, eps)) - f0) / eps);
|
|
55
|
+
}
|
|
56
|
+
const { r, h0 } = steps(xs, options, 1e-4);
|
|
57
|
+
const out = [];
|
|
58
|
+
for (let i = 0; i < n; i++) {
|
|
59
|
+
const a = [];
|
|
60
|
+
let h = h0[i];
|
|
61
|
+
for (let k = 0; k < r; k++) {
|
|
62
|
+
// a central difference below 1e-20 is taken as 0 at the smaller steps (functions unstable near 0), as numDeriv does
|
|
63
|
+
if (k > 0 && Math.abs(a[k - 1]) < 1e-20)
|
|
64
|
+
a.push(0);
|
|
65
|
+
else
|
|
66
|
+
a.push((func(shifted(xs, i, h)) - func(shifted(xs, i, -h))) / (2 * h));
|
|
67
|
+
if (Number.isNaN(a[k]))
|
|
68
|
+
throw new RangeError(`func returned NaN at step ${h} from x`);
|
|
69
|
+
h /= 2;
|
|
70
|
+
}
|
|
71
|
+
out.push(extrapolate(a));
|
|
72
|
+
}
|
|
73
|
+
return out;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Jacobian of a vector function of a vector: row i holds the derivatives of the i-th value. Equivalent to
|
|
77
|
+
* `numDeriv::jacobian(func, x, method)`.
|
|
78
|
+
*/
|
|
79
|
+
export function jacobian(func, x, options = {}) {
|
|
80
|
+
const xs = [...x];
|
|
81
|
+
const n = xs.length;
|
|
82
|
+
const f0 = Array.from(func([...xs]));
|
|
83
|
+
const J = f0.map(() => new Array(n).fill(NaN));
|
|
84
|
+
if (method(options) === "simple") {
|
|
85
|
+
const eps = options.eps ?? 1e-4;
|
|
86
|
+
for (let j = 0; j < n; j++) {
|
|
87
|
+
const f1 = func(shifted(xs, j, eps));
|
|
88
|
+
f0.forEach((v, i) => (J[i][j] = (f1[i] - v) / eps));
|
|
89
|
+
}
|
|
90
|
+
return J;
|
|
91
|
+
}
|
|
92
|
+
const { r, h0 } = steps(xs, options, 1e-4);
|
|
93
|
+
for (let j = 0; j < n; j++) {
|
|
94
|
+
const a = f0.map(() => []);
|
|
95
|
+
let h = h0[j];
|
|
96
|
+
for (let k = 0; k < r; k++) {
|
|
97
|
+
const fp = func(shifted(xs, j, h));
|
|
98
|
+
const fm = func(shifted(xs, j, -h));
|
|
99
|
+
f0.forEach((_, i) => a[i].push((fp[i] - fm[i]) / (2 * h)));
|
|
100
|
+
h /= 2;
|
|
101
|
+
}
|
|
102
|
+
f0.forEach((_, i) => (J[i][j] = extrapolate(a[i])));
|
|
103
|
+
}
|
|
104
|
+
return J;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Hessian matrix of a scalar function of a vector, by Richardson extrapolation (Bates and Watts). Equivalent to
|
|
108
|
+
* `numDeriv::hessian(func, x)`.
|
|
109
|
+
*
|
|
110
|
+
* @example
|
|
111
|
+
* ```ts
|
|
112
|
+
* hessian((x) => x[0]! ** 2 * x[1]!, [1, 2]); // ≈ [[4, 2], [2, 0]]
|
|
113
|
+
* ```
|
|
114
|
+
*/
|
|
115
|
+
export function hessian(func, x, options = {}) {
|
|
116
|
+
if ((options.method ?? "Richardson") !== "Richardson")
|
|
117
|
+
throw new RangeError('hessian supports only method "Richardson"');
|
|
118
|
+
const xs = [...x];
|
|
119
|
+
const n = xs.length;
|
|
120
|
+
const { r, h0 } = steps(xs, options, 0.1);
|
|
121
|
+
const f0 = func([...xs]);
|
|
122
|
+
const diag = [];
|
|
123
|
+
for (let i = 0; i < n; i++) {
|
|
124
|
+
const a = [];
|
|
125
|
+
let h = h0[i];
|
|
126
|
+
for (let k = 0; k < r; k++) {
|
|
127
|
+
a.push((func(shifted(xs, i, h)) - 2 * f0 + func(shifted(xs, i, -h))) / h ** 2);
|
|
128
|
+
h /= 2;
|
|
129
|
+
}
|
|
130
|
+
diag.push(extrapolate(a));
|
|
131
|
+
}
|
|
132
|
+
const H = xs.map(() => new Array(n).fill(NaN));
|
|
133
|
+
for (let i = 0; i < n; i++) {
|
|
134
|
+
H[i][i] = diag[i];
|
|
135
|
+
for (let j = 0; j < i; j++) {
|
|
136
|
+
const a = [];
|
|
137
|
+
let hi = h0[i];
|
|
138
|
+
let hj = h0[j];
|
|
139
|
+
for (let k = 0; k < r; k++) {
|
|
140
|
+
const up = shifted(shifted(xs, i, hi), j, hj);
|
|
141
|
+
const down = shifted(shifted(xs, i, -hi), j, -hj);
|
|
142
|
+
a.push((func(up) - 2 * f0 + func(down) - diag[i] * hi ** 2 - diag[j] * hj ** 2) / (2 * hi * hj));
|
|
143
|
+
hi /= 2;
|
|
144
|
+
hj /= 2;
|
|
145
|
+
}
|
|
146
|
+
H[i][j] = H[j][i] = extrapolate(a);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return H;
|
|
150
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/** Options for {@link integrate}. */
|
|
2
|
+
export interface IntegrateOptions {
|
|
3
|
+
/** Maximum number of subintervals. Default `100`. */
|
|
4
|
+
subdivisions?: number;
|
|
5
|
+
/** Relative accuracy requested. Default `eps^0.25` (about 1.2e-4). */
|
|
6
|
+
relTol?: number;
|
|
7
|
+
/** Absolute accuracy requested. Default `relTol`. */
|
|
8
|
+
absTol?: number;
|
|
9
|
+
/** Throw when QUADPACK reports a problem (default `true`); otherwise return the result with its code. */
|
|
10
|
+
stopOnError?: boolean;
|
|
11
|
+
}
|
|
12
|
+
/** Result of {@link integrate}. */
|
|
13
|
+
export interface IntegrateResult {
|
|
14
|
+
value: number;
|
|
15
|
+
/** Estimate of the absolute error. */
|
|
16
|
+
absError: number;
|
|
17
|
+
/** Number of subintervals used. */
|
|
18
|
+
subdivisions: number;
|
|
19
|
+
/** QUADPACK's code: 0 OK, 1 maximum number of subdivisions reached, 2 roundoff error, 3 bad integrand
|
|
20
|
+
* behaviour, 4 roundoff error in the extrapolation table, 5 probably divergent, 6 invalid input. */
|
|
21
|
+
ier: number;
|
|
22
|
+
/** R's message for `ier`. */
|
|
23
|
+
message: string;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Integral of a function of one variable over a finite or infinite range. Equivalent to R's `integrate()`.
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* ```ts
|
|
30
|
+
* integrate((x) => Math.exp(-x * x / 2), -Infinity, Infinity).value; // 2.5066282746310002 (sqrt(2 pi))
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
export declare function integrate(f: (x: number) => number, lower: number, upper: number, options?: IntegrateOptions): IntegrateResult;
|
|
34
|
+
/** Options for {@link optimize}. */
|
|
35
|
+
export interface OptimizeOptions {
|
|
36
|
+
/** Maximise instead of minimise. Default `false`. */
|
|
37
|
+
maximum?: boolean;
|
|
38
|
+
/** Accuracy. Default `eps^0.25`. */
|
|
39
|
+
tol?: number;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Minimum (or maximum) of a function of one variable on an interval, by Brent's golden-section and parabolic
|
|
43
|
+
* search. Equivalent to R's `optimize()`: `optimum` is the location (R's `minimum`/`maximum`) and `objective` the
|
|
44
|
+
* function there. `lower` must be less than `upper`, as in R.
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* ```ts
|
|
48
|
+
* optimize((x) => (x - 1 / 3) ** 2, 0, 1).optimum; // 0.3333333333333333
|
|
49
|
+
* ```
|
|
50
|
+
*/
|
|
51
|
+
export declare function optimize(f: (x: number) => number, lower: number, upper: number, options?: OptimizeOptions): {
|
|
52
|
+
optimum: number;
|
|
53
|
+
objective: number;
|
|
54
|
+
};
|
|
55
|
+
/** Options for {@link uniroot}. */
|
|
56
|
+
export interface UnirootOptions {
|
|
57
|
+
/** Accuracy. Default `eps^0.25`. */
|
|
58
|
+
tol?: number;
|
|
59
|
+
/** Maximum number of iterations. Default `1000`. */
|
|
60
|
+
maxiter?: number;
|
|
61
|
+
/**
|
|
62
|
+
* Extend the interval when f has the same sign at both ends: `"yes"` (both ways), `"downX"` (f decreasing),
|
|
63
|
+
* `"upX"` (f increasing) or `"no"` (default).
|
|
64
|
+
*/
|
|
65
|
+
extendInt?: "no" | "yes" | "downX" | "upX";
|
|
66
|
+
/** f(lower), when already known. */
|
|
67
|
+
fLower?: number;
|
|
68
|
+
/** f(upper), when already known. */
|
|
69
|
+
fUpper?: number;
|
|
70
|
+
}
|
|
71
|
+
/** Result of {@link uniroot}. */
|
|
72
|
+
export interface UnirootResult {
|
|
73
|
+
root: number;
|
|
74
|
+
/** f at the root. */
|
|
75
|
+
fRoot: number;
|
|
76
|
+
/** Iterations used (including those extending the interval). */
|
|
77
|
+
iter: number;
|
|
78
|
+
/** Iterations used to extend the interval (NaN when it was not extended). */
|
|
79
|
+
initIt: number;
|
|
80
|
+
/** Brent's estimate of the precision of the root. */
|
|
81
|
+
estimPrec: number;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* A root of a function of one variable in an interval, by Brent's zeroin. Equivalent to R's `uniroot()`.
|
|
85
|
+
*
|
|
86
|
+
* @example
|
|
87
|
+
* ```ts
|
|
88
|
+
* uniroot((x) => x * x - 2, 0, 2, { tol: 1e-12 }).root; // 1.4142135623731364
|
|
89
|
+
* ```
|
|
90
|
+
*/
|
|
91
|
+
export declare function uniroot(f: (x: number) => number, lower: number, upper: number, options?: UnirootOptions): UnirootResult;
|