@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.
- package/CHANGELOG.md +105 -0
- package/README.md +127 -110
- 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.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1601 -863
- package/dist/index.js.map +17 -11
- 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/sampling.d.ts +45 -0
- package/dist/plot/sampling.d.ts.map +1 -1
- package/dist/plot/scatter3d.d.ts +1 -1
- package/package.json +16 -5
package/dist/linalg.js
ADDED
|
@@ -0,0 +1,1860 @@
|
|
|
1
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
2
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
3
|
+
}) : x)(function(x) {
|
|
4
|
+
if (typeof require !== "undefined")
|
|
5
|
+
return require.apply(this, arguments);
|
|
6
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
// src/core/frame.ts
|
|
10
|
+
function isNumericColumn(column) {
|
|
11
|
+
return column.length > 0 && column.every((value) => typeof value === "number");
|
|
12
|
+
}
|
|
13
|
+
function numericColumns(data) {
|
|
14
|
+
return Object.keys(data).filter((name) => isNumericColumn(data[name]));
|
|
15
|
+
}
|
|
16
|
+
function requireThreeNumericColumns(numeric, caller) {
|
|
17
|
+
if (numeric.length < 3) {
|
|
18
|
+
throw new RangeError(`${caller}() needs at least 3 numeric columns; got ${numeric.length}. ` + "Supply x/y/z explicitly or add numeric columns.");
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
function frameRows(data) {
|
|
22
|
+
const names = Object.keys(data);
|
|
23
|
+
const first = names[0];
|
|
24
|
+
if (first === undefined) {
|
|
25
|
+
return 0;
|
|
26
|
+
}
|
|
27
|
+
const rows = data[first].length;
|
|
28
|
+
const ragged = names.find((name) => data[name].length !== rows);
|
|
29
|
+
if (ragged !== undefined) {
|
|
30
|
+
throw new RangeError(`every column needs the same number of rows: "${first}" has ${rows} ` + `but "${ragged}" has ${data[ragged].length}`);
|
|
31
|
+
}
|
|
32
|
+
return rows;
|
|
33
|
+
}
|
|
34
|
+
function requireNumericColumn(data, name, role) {
|
|
35
|
+
const column = data[name];
|
|
36
|
+
if (column === undefined) {
|
|
37
|
+
throw new RangeError(`Column "${name}" (passed as \`${role}\`) is not in the data.`);
|
|
38
|
+
}
|
|
39
|
+
if (!isNumericColumn(column)) {
|
|
40
|
+
throw new RangeError(`Column "${name}" (passed as \`${role}\`) is not numeric; ` + "only numeric columns can carry the statistics.");
|
|
41
|
+
}
|
|
42
|
+
return column;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// src/core/linalg/matrix.ts
|
|
46
|
+
function matrix(values, options) {
|
|
47
|
+
const { byrow = false, dimnames } = options;
|
|
48
|
+
const [nrow, ncol] = extents(values.length, options);
|
|
49
|
+
const dense = Float64Array.from(values);
|
|
50
|
+
const data = new Float64Array(nrow * ncol);
|
|
51
|
+
if (dense.length === 1 && data.length > 1) {
|
|
52
|
+
data.fill(dense[0]);
|
|
53
|
+
} else if (byrow) {
|
|
54
|
+
dense.forEach((value, index) => {
|
|
55
|
+
const i = Math.floor(index / ncol);
|
|
56
|
+
const j = index % ncol;
|
|
57
|
+
data[j * nrow + i] = value;
|
|
58
|
+
});
|
|
59
|
+
} else {
|
|
60
|
+
data.set(dense);
|
|
61
|
+
}
|
|
62
|
+
return make(nrow, ncol, data, dimnames ?? null);
|
|
63
|
+
}
|
|
64
|
+
function extents(length, { nrow, ncol }) {
|
|
65
|
+
if (nrow === undefined && ncol === undefined) {
|
|
66
|
+
throw new RangeError("matrix() needs nrow or ncol");
|
|
67
|
+
}
|
|
68
|
+
if (nrow !== undefined) {
|
|
69
|
+
requireExtent(nrow, "nrow");
|
|
70
|
+
}
|
|
71
|
+
if (ncol !== undefined) {
|
|
72
|
+
requireExtent(ncol, "ncol");
|
|
73
|
+
}
|
|
74
|
+
const scalar = length === 1;
|
|
75
|
+
if (nrow !== undefined && ncol !== undefined) {
|
|
76
|
+
if (!scalar && nrow * ncol !== length) {
|
|
77
|
+
throw new RangeError(length > nrow * ncol ? "data is too long" : `data length [${length}] is not nrow * ncol [${nrow} * ${ncol}]`);
|
|
78
|
+
}
|
|
79
|
+
return [nrow, ncol];
|
|
80
|
+
}
|
|
81
|
+
const given = nrow ?? ncol;
|
|
82
|
+
const name = nrow !== undefined ? "rows" : "columns";
|
|
83
|
+
if (given === 0) {
|
|
84
|
+
if (length !== 0) {
|
|
85
|
+
throw new RangeError("data is too long");
|
|
86
|
+
}
|
|
87
|
+
return [0, 0];
|
|
88
|
+
}
|
|
89
|
+
const other = scalar ? 1 : length / given;
|
|
90
|
+
if (!scalar && length % given !== 0) {
|
|
91
|
+
throw new RangeError(`data length [${length}] is not a multiple of the number of ${name} [${given}]`);
|
|
92
|
+
}
|
|
93
|
+
return nrow !== undefined ? [nrow, other] : [other, given];
|
|
94
|
+
}
|
|
95
|
+
function requireExtent(value, name) {
|
|
96
|
+
if (!Number.isSafeInteger(value) || value < 0) {
|
|
97
|
+
throw new RangeError(`${name} must be a non-negative integer, got ${value}`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
function make(nrow, ncol, data, dimnames) {
|
|
101
|
+
if (data.length !== nrow * ncol) {
|
|
102
|
+
throw new RangeError(`data length [${data.length}] is not nrow * ncol [${nrow} * ${ncol}]`);
|
|
103
|
+
}
|
|
104
|
+
if (dimnames !== null) {
|
|
105
|
+
const [rows, columns] = dimnames;
|
|
106
|
+
if (rows !== null && rows.length !== nrow) {
|
|
107
|
+
throw new RangeError(`length of dimnames [1] (${rows.length}) not equal to array extent (${nrow})`);
|
|
108
|
+
}
|
|
109
|
+
if (columns !== null && columns.length !== ncol) {
|
|
110
|
+
throw new RangeError(`length of dimnames [2] (${columns.length}) not equal to array extent (${ncol})`);
|
|
111
|
+
}
|
|
112
|
+
dimnames = rows === null && columns === null ? null : [rows === null ? null : [...rows], columns === null ? null : [...columns]];
|
|
113
|
+
}
|
|
114
|
+
return { nrow, ncol, data, dimnames };
|
|
115
|
+
}
|
|
116
|
+
function fromRows(rows) {
|
|
117
|
+
const nrow = rows.length;
|
|
118
|
+
const first = rows[0];
|
|
119
|
+
if (first === undefined || first.length === 0) {
|
|
120
|
+
throw new RangeError("fromRows() needs at least one row with one value");
|
|
121
|
+
}
|
|
122
|
+
const ncol = first.length;
|
|
123
|
+
const ragged = rows.findIndex((row) => row.length !== ncol);
|
|
124
|
+
if (ragged !== -1) {
|
|
125
|
+
throw new RangeError(`every row needs ${ncol} values; row ${ragged} has ${rows[ragged].length}`);
|
|
126
|
+
}
|
|
127
|
+
const data = new Float64Array(nrow * ncol);
|
|
128
|
+
rows.forEach((row, i) => {
|
|
129
|
+
Float64Array.from(row).forEach((value, j) => {
|
|
130
|
+
data[j * nrow + i] = value;
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
return make(nrow, ncol, data, null);
|
|
134
|
+
}
|
|
135
|
+
function fromColumns(columns) {
|
|
136
|
+
const ncol = columns.length;
|
|
137
|
+
const first = columns[0];
|
|
138
|
+
if (first === undefined || first.length === 0) {
|
|
139
|
+
throw new RangeError("fromColumns() needs at least one column with one value");
|
|
140
|
+
}
|
|
141
|
+
const nrow = first.length;
|
|
142
|
+
const ragged = columns.findIndex((column) => column.length !== nrow);
|
|
143
|
+
if (ragged !== -1) {
|
|
144
|
+
throw new RangeError(`every column needs ${nrow} values; column ${ragged} has ${columns[ragged].length}`);
|
|
145
|
+
}
|
|
146
|
+
const data = new Float64Array(nrow * ncol);
|
|
147
|
+
columns.forEach((column, j) => {
|
|
148
|
+
data.set(column, j * nrow);
|
|
149
|
+
});
|
|
150
|
+
return make(nrow, ncol, data, null);
|
|
151
|
+
}
|
|
152
|
+
function at(m, i, j) {
|
|
153
|
+
if (!Number.isInteger(i) || i < 0 || i >= m.nrow) {
|
|
154
|
+
throw new RangeError(`row index ${i} is outside 0..${m.nrow - 1}`);
|
|
155
|
+
}
|
|
156
|
+
if (!Number.isInteger(j) || j < 0 || j >= m.ncol) {
|
|
157
|
+
throw new RangeError(`column index ${j} is outside 0..${m.ncol - 1}`);
|
|
158
|
+
}
|
|
159
|
+
return m.data[j * m.nrow + i];
|
|
160
|
+
}
|
|
161
|
+
function row(m, i) {
|
|
162
|
+
if (!Number.isInteger(i) || i < 0 || i >= m.nrow) {
|
|
163
|
+
throw new RangeError(`row index ${i} is outside 0..${m.nrow - 1}`);
|
|
164
|
+
}
|
|
165
|
+
return Array.from({ length: m.ncol }, (_, j) => m.data[j * m.nrow + i]);
|
|
166
|
+
}
|
|
167
|
+
function column(m, j) {
|
|
168
|
+
if (!Number.isInteger(j) || j < 0 || j >= m.ncol) {
|
|
169
|
+
throw new RangeError(`column index ${j} is outside 0..${m.ncol - 1}`);
|
|
170
|
+
}
|
|
171
|
+
return Array.from(m.data.subarray(j * m.nrow, (j + 1) * m.nrow));
|
|
172
|
+
}
|
|
173
|
+
function toRows(m) {
|
|
174
|
+
return Array.from({ length: m.nrow }, (_, i) => row(m, i));
|
|
175
|
+
}
|
|
176
|
+
function toColumns(m) {
|
|
177
|
+
return Array.from({ length: m.ncol }, (_, j) => column(m, j));
|
|
178
|
+
}
|
|
179
|
+
function fromFrame(data, columns) {
|
|
180
|
+
const nrow = frameRows(data);
|
|
181
|
+
const names = columns ?? numericColumns(data);
|
|
182
|
+
const buffer = new Float64Array(nrow * names.length);
|
|
183
|
+
names.forEach((name, j) => {
|
|
184
|
+
buffer.set(requireNumericColumn(data, name, "columns"), j * nrow);
|
|
185
|
+
});
|
|
186
|
+
return make(nrow, names.length, buffer, names.length === 0 ? null : [null, [...names]]);
|
|
187
|
+
}
|
|
188
|
+
// src/core/arith.ts
|
|
189
|
+
function sum(values) {
|
|
190
|
+
return values.reduce((total, value) => total + value, 0);
|
|
191
|
+
}
|
|
192
|
+
function mean(values) {
|
|
193
|
+
return sum(values) / values.length;
|
|
194
|
+
}
|
|
195
|
+
function extent(values) {
|
|
196
|
+
return values.reduce(([low, high], value) => [
|
|
197
|
+
value < low ? value : low,
|
|
198
|
+
value > high ? value : high
|
|
199
|
+
], [Number.POSITIVE_INFINITY, Number.NEGATIVE_INFINITY]);
|
|
200
|
+
}
|
|
201
|
+
function withoutNegativeZero(value) {
|
|
202
|
+
return value === 0 ? 0 : value;
|
|
203
|
+
}
|
|
204
|
+
function requireCount(value, name) {
|
|
205
|
+
if (!Number.isInteger(value) || value < 0) {
|
|
206
|
+
throw new RangeError(`${name} must be a non-negative integer, got ${value}`);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
function zipWith(as, bs, combine) {
|
|
210
|
+
const length = Math.min(as.length, bs.length);
|
|
211
|
+
return as.slice(0, length).map((a, index) => combine(a, bs[index]));
|
|
212
|
+
}
|
|
213
|
+
function sd(values) {
|
|
214
|
+
if (values.length < 2) {
|
|
215
|
+
return Number.NaN;
|
|
216
|
+
}
|
|
217
|
+
const center = mean(values);
|
|
218
|
+
const squares = values.map((value) => (value - center) * (value - center));
|
|
219
|
+
return Math.sqrt(sum(squares) / (values.length - 1));
|
|
220
|
+
}
|
|
221
|
+
function meanAbsoluteDeviation(values) {
|
|
222
|
+
const center = mean(values);
|
|
223
|
+
return mean(values.map((value) => Math.abs(value - center)));
|
|
224
|
+
}
|
|
225
|
+
function fusedMultiplyAdd(a, b, c) {
|
|
226
|
+
if (a * b === 0) {
|
|
227
|
+
return c + a * b;
|
|
228
|
+
}
|
|
229
|
+
const [product, productError] = twoProduct(a, b);
|
|
230
|
+
const [sum2, sumError] = twoSum(c, product);
|
|
231
|
+
const rounded = sum2 + (sumError + productError);
|
|
232
|
+
return Number.isFinite(rounded) ? rounded : a * b + c;
|
|
233
|
+
}
|
|
234
|
+
function split(value) {
|
|
235
|
+
const scaled = 134217729 * value;
|
|
236
|
+
const high = scaled - (scaled - value);
|
|
237
|
+
return [high, value - high];
|
|
238
|
+
}
|
|
239
|
+
function twoProduct(a, b) {
|
|
240
|
+
const product = a * b;
|
|
241
|
+
const [aHigh, aLow] = split(a);
|
|
242
|
+
const [bHigh, bLow] = split(b);
|
|
243
|
+
const error = aLow * bLow - (product - aHigh * bHigh - aLow * bHigh - aHigh * bLow);
|
|
244
|
+
return [product, error];
|
|
245
|
+
}
|
|
246
|
+
function twoSum(a, b) {
|
|
247
|
+
const sum2 = a + b;
|
|
248
|
+
const carried = sum2 - a;
|
|
249
|
+
return [sum2, a - (sum2 - carried) + (b - carried)];
|
|
250
|
+
}
|
|
251
|
+
function quantile(values, p) {
|
|
252
|
+
return quantiles(values, [p])[0];
|
|
253
|
+
}
|
|
254
|
+
function quantiles(values, probs) {
|
|
255
|
+
if (probs.some((p) => !(p >= 0 && p <= 1))) {
|
|
256
|
+
throw new RangeError(`every probability must be in [0, 1], got ${probs}`);
|
|
257
|
+
}
|
|
258
|
+
if (values.length === 0) {
|
|
259
|
+
return probs.map(() => Number.NaN);
|
|
260
|
+
}
|
|
261
|
+
const sorted = Float64Array.from(values).sort();
|
|
262
|
+
return probs.map((p) => type7(sorted, p));
|
|
263
|
+
}
|
|
264
|
+
function median(values) {
|
|
265
|
+
return quantile(values, 0.5);
|
|
266
|
+
}
|
|
267
|
+
function type7(sorted, p) {
|
|
268
|
+
const position = 1 + (sorted.length - 1) * p;
|
|
269
|
+
const below = Math.floor(position);
|
|
270
|
+
const above = Math.ceil(position);
|
|
271
|
+
const low = sorted[below - 1];
|
|
272
|
+
const high = sorted[above - 1];
|
|
273
|
+
if (position > below && high !== low) {
|
|
274
|
+
const h = position - below;
|
|
275
|
+
return (1 - h) * low + h * high;
|
|
276
|
+
}
|
|
277
|
+
return low;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// src/core/linalg/ops.ts
|
|
281
|
+
function t(m) {
|
|
282
|
+
const { nrow, ncol } = m;
|
|
283
|
+
const data = new Float64Array(nrow * ncol);
|
|
284
|
+
for (let j = 0;j < ncol; j++) {
|
|
285
|
+
for (let i = 0;i < nrow; i++) {
|
|
286
|
+
data[i * ncol + j] = m.data[j * nrow + i];
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
const dimnames = m.dimnames === null ? null : [m.dimnames[1], m.dimnames[0]];
|
|
290
|
+
return make(ncol, nrow, data, dimnames);
|
|
291
|
+
}
|
|
292
|
+
var transpose = t;
|
|
293
|
+
function matmul(x, y) {
|
|
294
|
+
const [left, right] = conformProduct(x, y);
|
|
295
|
+
if (left.ncol !== right.nrow) {
|
|
296
|
+
throw new RangeError(`non-conformable arguments: ${left.nrow} x ${left.ncol} %*% ${right.nrow} x ${right.ncol}`);
|
|
297
|
+
}
|
|
298
|
+
const data = product(left, right);
|
|
299
|
+
return make(left.nrow, right.ncol, data, productDimnames(left, right));
|
|
300
|
+
}
|
|
301
|
+
function conformProduct(x, y) {
|
|
302
|
+
if (isMatrix(x)) {
|
|
303
|
+
if (isMatrix(y)) {
|
|
304
|
+
return [x, y];
|
|
305
|
+
}
|
|
306
|
+
return [x, y.length === x.ncol ? asColumn(y) : asRow(y)];
|
|
307
|
+
}
|
|
308
|
+
if (isMatrix(y)) {
|
|
309
|
+
return [x.length === y.nrow ? asRow(x) : asColumn(x), y];
|
|
310
|
+
}
|
|
311
|
+
return [asRow(x), x.length === 1 ? asRow(y) : asColumn(y)];
|
|
312
|
+
}
|
|
313
|
+
function crossprod(x, y = x) {
|
|
314
|
+
const left = asColumn(x);
|
|
315
|
+
const right = isMatrix(y) ? y : y.length === left.nrow ? asColumn(y) : asRow(y);
|
|
316
|
+
if (left.nrow !== right.nrow) {
|
|
317
|
+
throw new RangeError(`non-conformable arguments: crossprod of ${left.nrow} x ${left.ncol} and ${right.nrow} x ${right.ncol}`);
|
|
318
|
+
}
|
|
319
|
+
return matmul(t(left), right);
|
|
320
|
+
}
|
|
321
|
+
function tcrossprod(x, y = x) {
|
|
322
|
+
const bothVectors = !isMatrix(x) && !isMatrix(y);
|
|
323
|
+
const left = isMatrix(x) ? x : isMatrix(y) && y.ncol === x.length ? asRow(x) : asColumn(x);
|
|
324
|
+
const right = isMatrix(y) ? y : !bothVectors && left.nrow === 1 ? asRow(y) : asColumn(y);
|
|
325
|
+
if (left.ncol !== right.ncol) {
|
|
326
|
+
throw new RangeError(`non-conformable arguments: tcrossprod of ${left.nrow} x ${left.ncol} and ${right.nrow} x ${right.ncol}`);
|
|
327
|
+
}
|
|
328
|
+
return matmul(left, t(right));
|
|
329
|
+
}
|
|
330
|
+
function product(x, y) {
|
|
331
|
+
const { nrow, ncol: inner } = x;
|
|
332
|
+
const { ncol } = y;
|
|
333
|
+
const data = new Float64Array(nrow * ncol);
|
|
334
|
+
for (let j = 0;j < ncol; j++) {
|
|
335
|
+
for (let k = 0;k < inner; k++) {
|
|
336
|
+
const factor = y.data[j * y.nrow + k];
|
|
337
|
+
for (let i = 0;i < nrow; i++) {
|
|
338
|
+
data[j * nrow + i] = fusedMultiplyAdd(x.data[k * nrow + i], factor, data[j * nrow + i]);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
return data;
|
|
343
|
+
}
|
|
344
|
+
function productDimnames(x, y) {
|
|
345
|
+
const rows = x.dimnames?.[0] ?? null;
|
|
346
|
+
const columns = y.dimnames?.[1] ?? null;
|
|
347
|
+
return rows === null && columns === null ? null : [rows, columns];
|
|
348
|
+
}
|
|
349
|
+
function asColumn(value) {
|
|
350
|
+
if (isMatrix(value)) {
|
|
351
|
+
return value;
|
|
352
|
+
}
|
|
353
|
+
return make(value.length, 1, Float64Array.from(value), null);
|
|
354
|
+
}
|
|
355
|
+
function asRow(value) {
|
|
356
|
+
return make(1, value.length, Float64Array.from(value), null);
|
|
357
|
+
}
|
|
358
|
+
function isMatrix(value) {
|
|
359
|
+
if (Array.isArray(value)) {
|
|
360
|
+
return false;
|
|
361
|
+
}
|
|
362
|
+
if (typeof value === "object" && value !== null && "nrow" in value && "ncol" in value && "data" in value) {
|
|
363
|
+
return true;
|
|
364
|
+
}
|
|
365
|
+
throw new TypeError("expected a Matrix or an array of numbers");
|
|
366
|
+
}
|
|
367
|
+
function cbind(...parts) {
|
|
368
|
+
const matrices = parts.map(asColumn);
|
|
369
|
+
const first = matrices[0];
|
|
370
|
+
if (first === undefined) {
|
|
371
|
+
throw new RangeError("cbind() needs at least one argument");
|
|
372
|
+
}
|
|
373
|
+
const nrow = first.nrow;
|
|
374
|
+
matrices.forEach((m, index) => {
|
|
375
|
+
if (m.nrow !== nrow) {
|
|
376
|
+
throw new RangeError(isMatrix(parts[index]) ? `number of rows of matrices must match (see arg ${index + 1})` : `number of rows of result is not a multiple of vector length (arg ${index + 1})`);
|
|
377
|
+
}
|
|
378
|
+
});
|
|
379
|
+
const ncol = matrices.reduce((total, m) => total + m.ncol, 0);
|
|
380
|
+
const data = new Float64Array(nrow * ncol);
|
|
381
|
+
let offset = 0;
|
|
382
|
+
matrices.forEach((m) => {
|
|
383
|
+
data.set(m.data, offset);
|
|
384
|
+
offset += m.data.length;
|
|
385
|
+
});
|
|
386
|
+
const rows = matrices.find((m) => m.dimnames?.[0])?.dimnames?.[0] ?? null;
|
|
387
|
+
const columns = boundNames(matrices.map((m) => ({ names: m.dimnames?.[1] ?? null, count: m.ncol })));
|
|
388
|
+
return make(nrow, ncol, data, rows === null && columns === null ? null : [rows, columns]);
|
|
389
|
+
}
|
|
390
|
+
function rbind(...parts) {
|
|
391
|
+
if (parts.length === 0) {
|
|
392
|
+
throw new RangeError("rbind() needs at least one argument");
|
|
393
|
+
}
|
|
394
|
+
const transposed = parts.map((part) => isMatrix(part) ? t(part) : part);
|
|
395
|
+
try {
|
|
396
|
+
return t(cbind(...transposed));
|
|
397
|
+
} catch (error) {
|
|
398
|
+
if (error instanceof RangeError) {
|
|
399
|
+
throw new RangeError(error.message.replace("number of rows", "number of columns"));
|
|
400
|
+
}
|
|
401
|
+
throw error;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
function boundNames(parts) {
|
|
405
|
+
if (parts.every((part) => part.names === null)) {
|
|
406
|
+
return null;
|
|
407
|
+
}
|
|
408
|
+
return parts.flatMap((part) => part.names ?? new Array(part.count).fill(""));
|
|
409
|
+
}
|
|
410
|
+
function diag(arg) {
|
|
411
|
+
if (typeof arg === "number") {
|
|
412
|
+
return identity(arg);
|
|
413
|
+
}
|
|
414
|
+
if (Array.isArray(arg)) {
|
|
415
|
+
const values = arg;
|
|
416
|
+
const n2 = values.length;
|
|
417
|
+
const data = new Float64Array(n2 * n2);
|
|
418
|
+
values.forEach((value, i) => {
|
|
419
|
+
data[i * n2 + i] = value;
|
|
420
|
+
});
|
|
421
|
+
return make(n2, n2, data, null);
|
|
422
|
+
}
|
|
423
|
+
const m = arg;
|
|
424
|
+
const n = Math.min(m.nrow, m.ncol);
|
|
425
|
+
return Array.from({ length: n }, (_, i) => m.data[i * m.nrow + i]);
|
|
426
|
+
}
|
|
427
|
+
function identity(order) {
|
|
428
|
+
if (!Number.isInteger(order) || order < 0) {
|
|
429
|
+
throw new RangeError(`order must be a non-negative integer, got ${order}`);
|
|
430
|
+
}
|
|
431
|
+
const data = new Float64Array(order * order);
|
|
432
|
+
for (let i = 0;i < order; i++) {
|
|
433
|
+
data[i * order + i] = 1;
|
|
434
|
+
}
|
|
435
|
+
return make(order, order, data, null);
|
|
436
|
+
}
|
|
437
|
+
// src/core/linalg/qr.ts
|
|
438
|
+
var DEFAULT_QR_TOLERANCE = 0.0000001;
|
|
439
|
+
function qr(x, options = {}) {
|
|
440
|
+
const { tolerance = DEFAULT_QR_TOLERANCE } = options;
|
|
441
|
+
if (!(tolerance >= 0)) {
|
|
442
|
+
throw new RangeError(`tolerance must be a non-negative number, got ${tolerance}`);
|
|
443
|
+
}
|
|
444
|
+
if (!isMatrix(x)) {
|
|
445
|
+
throw new TypeError("expected a Matrix");
|
|
446
|
+
}
|
|
447
|
+
if (!x.data.every(Number.isFinite)) {
|
|
448
|
+
throw new RangeError("NA/NaN/Inf in foreign function call (arg 1)");
|
|
449
|
+
}
|
|
450
|
+
const { nrow, ncol } = x;
|
|
451
|
+
const columns = Array.from({ length: ncol }, (_, j) => Array.from(x.data.subarray(j * nrow, (j + 1) * nrow)));
|
|
452
|
+
const { householders, pivot, rank } = decompose(columns, tolerance, nrow);
|
|
453
|
+
const data = new Float64Array(nrow * ncol);
|
|
454
|
+
columns.forEach((column2, j) => {
|
|
455
|
+
data.set(column2, j * nrow);
|
|
456
|
+
});
|
|
457
|
+
const rows = x.dimnames?.[0] ?? null;
|
|
458
|
+
const names = x.dimnames?.[1] ?? null;
|
|
459
|
+
const dimnames = rows === null && names === null ? null : [rows, names === null ? null : pivot.map((from) => names[from])];
|
|
460
|
+
return { qr: make(nrow, ncol, data, dimnames), qraux: householders, pivot, rank };
|
|
461
|
+
}
|
|
462
|
+
function qrCoef(q, y) {
|
|
463
|
+
if (isMatrix(y)) {
|
|
464
|
+
requireRows(q, y.nrow);
|
|
465
|
+
const width = q.qr.ncol;
|
|
466
|
+
const data = new Float64Array(width * y.ncol);
|
|
467
|
+
for (let j = 0;j < y.ncol; j++) {
|
|
468
|
+
const solved = coefficientsOf(q, Array.from(y.data.subarray(j * y.nrow, (j + 1) * y.nrow)));
|
|
469
|
+
solved.forEach((value, i) => {
|
|
470
|
+
data[j * width + i] = value ?? Number.NaN;
|
|
471
|
+
});
|
|
472
|
+
}
|
|
473
|
+
return make(width, y.ncol, data, readerDimnames(originalColumnNames(q), y));
|
|
474
|
+
}
|
|
475
|
+
requireRows(q, y.length);
|
|
476
|
+
return coefficientsOf(q, y);
|
|
477
|
+
}
|
|
478
|
+
function coefficientsOf(q, y) {
|
|
479
|
+
const qty = transformed(q, y, true);
|
|
480
|
+
const solved = backSubstitute(q.qr, qty, q.rank);
|
|
481
|
+
const coefficients = new Array(q.qr.ncol).fill(null);
|
|
482
|
+
q.pivot.slice(0, q.rank).forEach((column2, position) => {
|
|
483
|
+
coefficients[column2] = solved[position];
|
|
484
|
+
});
|
|
485
|
+
return coefficients;
|
|
486
|
+
}
|
|
487
|
+
function originalColumnNames(q) {
|
|
488
|
+
const pivoted = q.qr.dimnames?.[1] ?? null;
|
|
489
|
+
if (pivoted === null) {
|
|
490
|
+
return null;
|
|
491
|
+
}
|
|
492
|
+
const names = new Array(pivoted.length);
|
|
493
|
+
q.pivot.forEach((original, position) => {
|
|
494
|
+
names[original] = pivoted[position];
|
|
495
|
+
});
|
|
496
|
+
return names;
|
|
497
|
+
}
|
|
498
|
+
function qrFitted(q, y) {
|
|
499
|
+
return perColumn(q, y, (column2) => transformed(q, transformed(q, column2, true).map((value, index) => index < q.rank ? value : 0), false));
|
|
500
|
+
}
|
|
501
|
+
function qrResid(q, y) {
|
|
502
|
+
return perColumn(q, y, (column2) => transformed(q, transformed(q, column2, true).map((value, index) => index < q.rank ? 0 : value), false));
|
|
503
|
+
}
|
|
504
|
+
function qrQty(q, y) {
|
|
505
|
+
return perColumn(q, y, (column2) => transformed(q, column2, true));
|
|
506
|
+
}
|
|
507
|
+
function qrQy(q, y) {
|
|
508
|
+
return perColumn(q, y, (column2) => transformed(q, column2, false));
|
|
509
|
+
}
|
|
510
|
+
function perColumn(q, y, read) {
|
|
511
|
+
if (isMatrix(y)) {
|
|
512
|
+
requireRows(q, y.nrow);
|
|
513
|
+
const data = new Float64Array(y.nrow * y.ncol);
|
|
514
|
+
for (let j = 0;j < y.ncol; j++) {
|
|
515
|
+
data.set(read(Array.from(y.data.subarray(j * y.nrow, (j + 1) * y.nrow))), j * y.nrow);
|
|
516
|
+
}
|
|
517
|
+
return make(y.nrow, y.ncol, data, readerDimnames(null, y));
|
|
518
|
+
}
|
|
519
|
+
requireRows(q, y.length);
|
|
520
|
+
return read(y);
|
|
521
|
+
}
|
|
522
|
+
function readerDimnames(rows, y) {
|
|
523
|
+
const columns = y.dimnames?.[1] ?? null;
|
|
524
|
+
return rows === null && columns === null ? null : [rows, columns];
|
|
525
|
+
}
|
|
526
|
+
function transformed(q, y, transpose2) {
|
|
527
|
+
const result = [...y];
|
|
528
|
+
const count = reflectorCount(q);
|
|
529
|
+
if (transpose2) {
|
|
530
|
+
for (let step = 0;step < count; step++) {
|
|
531
|
+
applyReflector(q, step, result);
|
|
532
|
+
}
|
|
533
|
+
} else {
|
|
534
|
+
for (let step = count - 1;step >= 0; step--) {
|
|
535
|
+
applyReflector(q, step, result);
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
return result;
|
|
539
|
+
}
|
|
540
|
+
function qrQ(q) {
|
|
541
|
+
const { nrow, ncol } = q.qr;
|
|
542
|
+
const width = Math.min(nrow, ncol);
|
|
543
|
+
const data = new Float64Array(nrow * width);
|
|
544
|
+
for (let j = 0;j < width; j++) {
|
|
545
|
+
const unit = new Array(nrow).fill(0);
|
|
546
|
+
unit[j] = 1;
|
|
547
|
+
data.set(transformed(q, unit, false), j * nrow);
|
|
548
|
+
}
|
|
549
|
+
return make(nrow, width, data, null);
|
|
550
|
+
}
|
|
551
|
+
function qrR(q) {
|
|
552
|
+
const { nrow, ncol } = q.qr;
|
|
553
|
+
const height = Math.min(nrow, ncol);
|
|
554
|
+
const data = new Float64Array(height * ncol);
|
|
555
|
+
for (let j = 0;j < ncol; j++) {
|
|
556
|
+
for (let i = 0;i <= Math.min(j, height - 1); i++) {
|
|
557
|
+
data[j * height + i] = q.qr.data[j * nrow + i];
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
const rows = q.qr.dimnames?.[0]?.slice(0, height) ?? null;
|
|
561
|
+
const columns = q.qr.dimnames?.[1] ?? null;
|
|
562
|
+
return make(height, ncol, data, rows === null && columns === null ? null : [rows, columns]);
|
|
563
|
+
}
|
|
564
|
+
function requireRows(q, rows) {
|
|
565
|
+
if (rows !== q.qr.nrow) {
|
|
566
|
+
throw new RangeError("'qr' and 'y' must have the same number of rows");
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
function reflectorCount(q) {
|
|
570
|
+
return Math.min(q.rank, q.qr.nrow - 1);
|
|
571
|
+
}
|
|
572
|
+
function applyReflector(q, step, vector) {
|
|
573
|
+
const leading = q.qraux[step];
|
|
574
|
+
if (leading === 0) {
|
|
575
|
+
return;
|
|
576
|
+
}
|
|
577
|
+
const { nrow } = q.qr;
|
|
578
|
+
const column2 = q.qr.data.subarray(step * nrow, (step + 1) * nrow);
|
|
579
|
+
let inner = leading * vector[step];
|
|
580
|
+
for (let row2 = step + 1;row2 < nrow; row2++) {
|
|
581
|
+
inner = fusedMultiplyAdd(column2[row2], vector[row2], inner);
|
|
582
|
+
}
|
|
583
|
+
const factor = -inner / leading;
|
|
584
|
+
vector[step] = fusedMultiplyAdd(factor, leading, vector[step]);
|
|
585
|
+
for (let row2 = step + 1;row2 < nrow; row2++) {
|
|
586
|
+
vector[row2] = fusedMultiplyAdd(factor, column2[row2], vector[row2]);
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
function decompose(columns, tolerance, rows) {
|
|
590
|
+
const width = columns.length;
|
|
591
|
+
const pivot = columns.map((_, column2) => column2);
|
|
592
|
+
const qraux = columns.map((column2) => norm(column2, 0));
|
|
593
|
+
const lastNorms = [...qraux];
|
|
594
|
+
const originalNorms = qraux.map((value) => value || 1);
|
|
595
|
+
let k = width + 1;
|
|
596
|
+
for (let step = 0;step < Math.min(rows, width); step++) {
|
|
597
|
+
while (step + 1 < k && qraux[step] < originalNorms[step] * tolerance) {
|
|
598
|
+
moveToEnd(columns, step);
|
|
599
|
+
moveToEnd(pivot, step);
|
|
600
|
+
moveToEnd(qraux, step);
|
|
601
|
+
moveToEnd(lastNorms, step);
|
|
602
|
+
moveToEnd(originalNorms, step);
|
|
603
|
+
k -= 1;
|
|
604
|
+
}
|
|
605
|
+
if (step === rows - 1) {
|
|
606
|
+
continue;
|
|
607
|
+
}
|
|
608
|
+
reflect(columns, qraux, lastNorms, step, rows);
|
|
609
|
+
}
|
|
610
|
+
return { householders: qraux, pivot, rank: Math.min(k - 1, rows) };
|
|
611
|
+
}
|
|
612
|
+
function reflect(columns, qraux, lastNorms, step, rows) {
|
|
613
|
+
const column2 = columns[step];
|
|
614
|
+
const length = norm(column2, step);
|
|
615
|
+
if (length === 0) {
|
|
616
|
+
return;
|
|
617
|
+
}
|
|
618
|
+
const pivotNorm = column2[step] < 0 ? -length : length;
|
|
619
|
+
const reciprocal = 1 / pivotNorm;
|
|
620
|
+
for (let row2 = step;row2 < rows; row2++) {
|
|
621
|
+
column2[row2] = column2[row2] * reciprocal;
|
|
622
|
+
}
|
|
623
|
+
const leading = 1 + column2[step];
|
|
624
|
+
column2[step] = leading;
|
|
625
|
+
for (let index = step + 1;index < columns.length; index++) {
|
|
626
|
+
const other = columns[index];
|
|
627
|
+
let inner = 0;
|
|
628
|
+
for (let row2 = step;row2 < rows; row2++) {
|
|
629
|
+
inner = fusedMultiplyAdd(column2[row2], other[row2], inner);
|
|
630
|
+
}
|
|
631
|
+
const factor = -inner / leading;
|
|
632
|
+
for (let row2 = step;row2 < rows; row2++) {
|
|
633
|
+
other[row2] = fusedMultiplyAdd(factor, column2[row2], other[row2]);
|
|
634
|
+
}
|
|
635
|
+
const running = qraux[index];
|
|
636
|
+
if (running !== 0) {
|
|
637
|
+
const ratio = Math.abs(other[step]) / running;
|
|
638
|
+
const remaining = Math.max(1 - ratio * ratio, 0);
|
|
639
|
+
if (Math.abs(remaining) < 0.000001) {
|
|
640
|
+
qraux[index] = norm(other, step + 1);
|
|
641
|
+
lastNorms[index] = qraux[index];
|
|
642
|
+
} else {
|
|
643
|
+
qraux[index] = running * Math.sqrt(remaining);
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
qraux[step] = leading;
|
|
648
|
+
column2[step] = -pivotNorm;
|
|
649
|
+
}
|
|
650
|
+
function backSubstitute(compact, response, rank) {
|
|
651
|
+
const { nrow } = compact;
|
|
652
|
+
const entry = (i, j) => compact.data[j * nrow + i];
|
|
653
|
+
const solved = response.slice(0, rank);
|
|
654
|
+
for (let column2 = rank - 1;column2 >= 0; column2--) {
|
|
655
|
+
const value = solved[column2] / entry(column2, column2);
|
|
656
|
+
solved[column2] = value;
|
|
657
|
+
for (let row2 = 0;row2 < column2; row2++) {
|
|
658
|
+
solved[row2] = fusedMultiplyAdd(-value, entry(row2, column2), solved[row2]);
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
return solved;
|
|
662
|
+
}
|
|
663
|
+
function moveToEnd(track, from) {
|
|
664
|
+
const [moved] = track.splice(from, 1);
|
|
665
|
+
track.push(moved);
|
|
666
|
+
}
|
|
667
|
+
function norm(column2, from) {
|
|
668
|
+
let squares = 0;
|
|
669
|
+
for (let row2 = from;row2 < column2.length; row2++) {
|
|
670
|
+
const value = column2[row2];
|
|
671
|
+
squares = fusedMultiplyAdd(value, value, squares);
|
|
672
|
+
}
|
|
673
|
+
return Math.sqrt(squares);
|
|
674
|
+
}
|
|
675
|
+
// src/core/linalg/lu.ts
|
|
676
|
+
var SMALLEST_NORMAL = 0.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000022250738585072014;
|
|
677
|
+
function lu(a) {
|
|
678
|
+
requireSquare(a, "a");
|
|
679
|
+
const n = a.nrow;
|
|
680
|
+
const data = Float64Array.from(a.data);
|
|
681
|
+
const pivots = new Array(n).fill(0);
|
|
682
|
+
let zeroPivot = null;
|
|
683
|
+
const entry = (i, j) => data[j * n + i];
|
|
684
|
+
for (let j = 0;j < n; j++) {
|
|
685
|
+
let p = j;
|
|
686
|
+
for (let i = j + 1;i < n; i++) {
|
|
687
|
+
if (Math.abs(entry(i, j)) > Math.abs(entry(p, j))) {
|
|
688
|
+
p = i;
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
pivots[j] = p;
|
|
692
|
+
if (entry(p, j) !== 0) {
|
|
693
|
+
if (p !== j) {
|
|
694
|
+
swapRows(data, n, j, p);
|
|
695
|
+
}
|
|
696
|
+
const pivot = entry(j, j);
|
|
697
|
+
if (Math.abs(pivot) >= SMALLEST_NORMAL) {
|
|
698
|
+
const reciprocal = 1 / pivot;
|
|
699
|
+
for (let i = j + 1;i < n; i++) {
|
|
700
|
+
data[j * n + i] = entry(i, j) * reciprocal;
|
|
701
|
+
}
|
|
702
|
+
} else {
|
|
703
|
+
for (let i = j + 1;i < n; i++) {
|
|
704
|
+
data[j * n + i] = entry(i, j) / pivot;
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
} else if (zeroPivot === null) {
|
|
708
|
+
zeroPivot = j;
|
|
709
|
+
}
|
|
710
|
+
for (let jj = j + 1;jj < n; jj++) {
|
|
711
|
+
const factor = -entry(j, jj);
|
|
712
|
+
for (let i = j + 1;i < n; i++) {
|
|
713
|
+
data[jj * n + i] = fusedMultiplyAdd(entry(i, j), factor, entry(i, jj));
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
return { lu: make(n, n, data, null), pivots, zeroPivot };
|
|
718
|
+
}
|
|
719
|
+
function swapRows(data, n, i, p) {
|
|
720
|
+
for (let j = 0;j < n; j++) {
|
|
721
|
+
const held = data[j * n + i];
|
|
722
|
+
data[j * n + i] = data[j * n + p];
|
|
723
|
+
data[j * n + p] = held;
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
var DEFAULT_SOLVE_TOLERANCE = Number.EPSILON;
|
|
727
|
+
function solve(a, second, third = {}) {
|
|
728
|
+
const [b, options] = splitArguments(second, third);
|
|
729
|
+
const { tolerance = DEFAULT_SOLVE_TOLERANCE } = options;
|
|
730
|
+
if (!(tolerance >= 0)) {
|
|
731
|
+
throw new RangeError(`tolerance must be a non-negative number, got ${tolerance}`);
|
|
732
|
+
}
|
|
733
|
+
requireSquare(a, "a");
|
|
734
|
+
const n = a.nrow;
|
|
735
|
+
if (n === 0) {
|
|
736
|
+
throw new RangeError("'a' is 0-diml");
|
|
737
|
+
}
|
|
738
|
+
const rhs = b === undefined ? identityData(n) : isMatrix(b) ? b : make(b.length, 1, Float64Array.from(b), null);
|
|
739
|
+
if (rhs.ncol === 0) {
|
|
740
|
+
throw new RangeError("no right-hand side in 'b'");
|
|
741
|
+
}
|
|
742
|
+
if (rhs.nrow !== n) {
|
|
743
|
+
throw new RangeError(`'b' (${rhs.nrow} x ${rhs.ncol}) must be compatible with 'a' (${n} x ${n})`);
|
|
744
|
+
}
|
|
745
|
+
const factored = lu(a);
|
|
746
|
+
if (factored.zeroPivot !== null) {
|
|
747
|
+
const i = factored.zeroPivot + 1;
|
|
748
|
+
throw new RangeError(`Lapack routine dgesv: system is exactly singular: U[${i},${i}] = 0`);
|
|
749
|
+
}
|
|
750
|
+
const solution = substitute(factored, rhs);
|
|
751
|
+
const anorm = oneNorm(a.data, n, n);
|
|
752
|
+
if (tolerance > 0 && Number.isFinite(anorm)) {
|
|
753
|
+
const inverse = b === undefined ? solution : substitute(factored, identityData(n));
|
|
754
|
+
const reciprocal = 1 / (anorm * oneNorm(inverse, n, n));
|
|
755
|
+
if (reciprocal < tolerance) {
|
|
756
|
+
throw new RangeError(`system is computationally singular: reciprocal condition number = ${formatG(reciprocal)}`);
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
if (b !== undefined && !isMatrix(b)) {
|
|
760
|
+
return Array.from(solution);
|
|
761
|
+
}
|
|
762
|
+
const aColumns = a.dimnames?.[1] ?? null;
|
|
763
|
+
const dimnames = b === undefined ? a.dimnames === null ? null : [aColumns, a.dimnames[0]] : aColumns === null && (rhs.dimnames?.[1] ?? null) === null ? null : [aColumns, rhs.dimnames?.[1] ?? null];
|
|
764
|
+
return make(n, rhs.ncol, solution, dimnames);
|
|
765
|
+
}
|
|
766
|
+
function splitArguments(second, third) {
|
|
767
|
+
if (second === undefined) {
|
|
768
|
+
return [undefined, third];
|
|
769
|
+
}
|
|
770
|
+
if (Array.isArray(second) || typeof second === "object" && "data" in second && "nrow" in second) {
|
|
771
|
+
return [second, third];
|
|
772
|
+
}
|
|
773
|
+
if (typeof second === "object" && second !== null && !ArrayBuffer.isView(second)) {
|
|
774
|
+
return [undefined, second];
|
|
775
|
+
}
|
|
776
|
+
throw new TypeError("expected a Matrix, an array of numbers, or the options");
|
|
777
|
+
}
|
|
778
|
+
function identityData(n) {
|
|
779
|
+
const data = new Float64Array(n * n);
|
|
780
|
+
for (let i = 0;i < n; i++) {
|
|
781
|
+
data[i * n + i] = 1;
|
|
782
|
+
}
|
|
783
|
+
return make(n, n, data, null);
|
|
784
|
+
}
|
|
785
|
+
function substitute(factored, rhs) {
|
|
786
|
+
const { lu: compact, pivots } = factored;
|
|
787
|
+
const n = compact.nrow;
|
|
788
|
+
const entry = (i, j) => compact.data[j * n + i];
|
|
789
|
+
const data = Float64Array.from(rhs.data);
|
|
790
|
+
const width = rhs.ncol;
|
|
791
|
+
pivots.forEach((p, i) => {
|
|
792
|
+
if (p !== i) {
|
|
793
|
+
for (let j = 0;j < width; j++) {
|
|
794
|
+
const held = data[j * n + i];
|
|
795
|
+
data[j * n + i] = data[j * n + p];
|
|
796
|
+
data[j * n + p] = held;
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
});
|
|
800
|
+
for (let j = 0;j < width; j++) {
|
|
801
|
+
const at2 = (i) => data[j * n + i];
|
|
802
|
+
for (let k = 0;k < n; k++) {
|
|
803
|
+
if (at2(k) !== 0) {
|
|
804
|
+
for (let i = k + 1;i < n; i++) {
|
|
805
|
+
data[j * n + i] = fusedMultiplyAdd(-at2(k), entry(i, k), at2(i));
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
for (let k = n - 1;k >= 0; k--) {
|
|
810
|
+
if (at2(k) !== 0) {
|
|
811
|
+
data[j * n + k] = at2(k) / entry(k, k);
|
|
812
|
+
for (let i = 0;i < k; i++) {
|
|
813
|
+
data[j * n + i] = fusedMultiplyAdd(-at2(k), entry(i, k), at2(i));
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
return data;
|
|
819
|
+
}
|
|
820
|
+
function det(a) {
|
|
821
|
+
const { modulus, sign } = determinant(a);
|
|
822
|
+
return sign * Math.exp(modulus);
|
|
823
|
+
}
|
|
824
|
+
function determinant(a) {
|
|
825
|
+
requireSquare(a, "x");
|
|
826
|
+
const factored = lu(a);
|
|
827
|
+
if (factored.zeroPivot !== null) {
|
|
828
|
+
return { modulus: Number.NEGATIVE_INFINITY, sign: 1 };
|
|
829
|
+
}
|
|
830
|
+
const n = a.nrow;
|
|
831
|
+
let modulus = 0;
|
|
832
|
+
let sign = 1;
|
|
833
|
+
for (let i = 0;i < n; i++) {
|
|
834
|
+
const pivot = factored.lu.data[i * n + i];
|
|
835
|
+
if (factored.pivots[i] !== i) {
|
|
836
|
+
sign = -sign;
|
|
837
|
+
}
|
|
838
|
+
if (pivot < 0) {
|
|
839
|
+
sign = -sign;
|
|
840
|
+
}
|
|
841
|
+
modulus += Math.log(Math.abs(pivot));
|
|
842
|
+
}
|
|
843
|
+
return { modulus, sign };
|
|
844
|
+
}
|
|
845
|
+
function rcond(a) {
|
|
846
|
+
if (!isMatrix(a)) {
|
|
847
|
+
throw new TypeError("expected a Matrix");
|
|
848
|
+
}
|
|
849
|
+
if (a.nrow !== a.ncol) {
|
|
850
|
+
return rcond(qrR(qr(a.nrow < a.ncol ? t(a) : a)));
|
|
851
|
+
}
|
|
852
|
+
const n = a.nrow;
|
|
853
|
+
if (n === 0) {
|
|
854
|
+
return Number.POSITIVE_INFINITY;
|
|
855
|
+
}
|
|
856
|
+
const anorm = oneNorm(a.data, n, n);
|
|
857
|
+
if (!Number.isFinite(anorm)) {
|
|
858
|
+
throw new RangeError("error code -5 from Lapack routine 'dgecon()'");
|
|
859
|
+
}
|
|
860
|
+
const factored = lu(a);
|
|
861
|
+
if (factored.zeroPivot !== null) {
|
|
862
|
+
return 0;
|
|
863
|
+
}
|
|
864
|
+
return 1 / (anorm * oneNorm(substitute(factored, identityData(n)), n, n));
|
|
865
|
+
}
|
|
866
|
+
function matrixNorm(a, type = "O") {
|
|
867
|
+
if (!isMatrix(a)) {
|
|
868
|
+
throw new TypeError("expected a Matrix");
|
|
869
|
+
}
|
|
870
|
+
const { nrow, ncol, data } = a;
|
|
871
|
+
switch (type.toUpperCase()) {
|
|
872
|
+
case "O":
|
|
873
|
+
case "1":
|
|
874
|
+
return oneNorm(data, nrow, ncol);
|
|
875
|
+
case "I": {
|
|
876
|
+
let largest = 0;
|
|
877
|
+
for (let i = 0;i < nrow; i++) {
|
|
878
|
+
let total = 0;
|
|
879
|
+
for (let j = 0;j < ncol; j++) {
|
|
880
|
+
total += Math.abs(data[j * nrow + i]);
|
|
881
|
+
}
|
|
882
|
+
largest = Math.max(largest, total);
|
|
883
|
+
}
|
|
884
|
+
return largest;
|
|
885
|
+
}
|
|
886
|
+
case "F":
|
|
887
|
+
case "E": {
|
|
888
|
+
let squares = 0;
|
|
889
|
+
data.forEach((value) => {
|
|
890
|
+
squares += value * value;
|
|
891
|
+
});
|
|
892
|
+
return Math.sqrt(squares);
|
|
893
|
+
}
|
|
894
|
+
case "M":
|
|
895
|
+
return data.reduce((largest, value) => Math.max(largest, Math.abs(value)), 0);
|
|
896
|
+
default:
|
|
897
|
+
throw new RangeError(`argument type[1]='${type}' must be one of 'M','1','O','I','F' or 'E'`);
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
function oneNorm(data, nrow, ncol) {
|
|
901
|
+
let largest = 0;
|
|
902
|
+
for (let j = 0;j < ncol; j++) {
|
|
903
|
+
let total = 0;
|
|
904
|
+
for (let i = 0;i < nrow; i++) {
|
|
905
|
+
total += Math.abs(data[j * nrow + i]);
|
|
906
|
+
}
|
|
907
|
+
largest = Math.max(largest, total);
|
|
908
|
+
}
|
|
909
|
+
return largest;
|
|
910
|
+
}
|
|
911
|
+
function requireSquare(a, name) {
|
|
912
|
+
if (!isMatrix(a)) {
|
|
913
|
+
throw new TypeError("expected a Matrix");
|
|
914
|
+
}
|
|
915
|
+
if (a.nrow !== a.ncol) {
|
|
916
|
+
throw new RangeError(name === "a" ? `'a' (${a.nrow} x ${a.ncol}) must be square` : "'x' must be a square matrix");
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
function formatG(value) {
|
|
920
|
+
if (value === 0 || !Number.isFinite(value)) {
|
|
921
|
+
return String(value);
|
|
922
|
+
}
|
|
923
|
+
const [mantissa, power] = value.toExponential(5).split("e");
|
|
924
|
+
const exponent = Number(power);
|
|
925
|
+
const trim = (digits) => digits.includes(".") ? digits.replace(/0+$/, "").replace(/\.$/, "") : digits;
|
|
926
|
+
if (exponent < -4 || exponent >= 6) {
|
|
927
|
+
const sign = exponent < 0 ? "-" : "+";
|
|
928
|
+
return `${trim(mantissa)}e${sign}${String(Math.abs(exponent)).padStart(2, "0")}`;
|
|
929
|
+
}
|
|
930
|
+
return trim(value.toFixed(Math.max(0, 5 - exponent)));
|
|
931
|
+
}
|
|
932
|
+
// src/core/linalg/namedVector.ts
|
|
933
|
+
function namedVector(names, values) {
|
|
934
|
+
if (names.length !== values.length) {
|
|
935
|
+
throw new RangeError(`a named vector needs one name per value: ${names.length} names, ${values.length} values`);
|
|
936
|
+
}
|
|
937
|
+
return { names: [...names], values: [...values] };
|
|
938
|
+
}
|
|
939
|
+
function lookup(v, name) {
|
|
940
|
+
const index = v.names.indexOf(name);
|
|
941
|
+
return index === -1 ? undefined : v.values[index];
|
|
942
|
+
}
|
|
943
|
+
// src/core/linalg/modelMatrix.ts
|
|
944
|
+
function modelMatrix(data, spec) {
|
|
945
|
+
const { outcome, intercept = true } = spec;
|
|
946
|
+
const rowCount = frameRows(data);
|
|
947
|
+
const terms = orderTerms(spec.terms);
|
|
948
|
+
const columns = new Map;
|
|
949
|
+
const read = (name, role) => {
|
|
950
|
+
const held = columns.get(name);
|
|
951
|
+
if (held !== undefined) {
|
|
952
|
+
return held;
|
|
953
|
+
}
|
|
954
|
+
const column2 = requireNumericColumn(data, name, role);
|
|
955
|
+
columns.set(name, column2);
|
|
956
|
+
return column2;
|
|
957
|
+
};
|
|
958
|
+
if (outcome !== undefined) {
|
|
959
|
+
read(outcome, "outcome");
|
|
960
|
+
}
|
|
961
|
+
terms.forEach((factors) => {
|
|
962
|
+
factors.forEach((name) => read(name, "terms"));
|
|
963
|
+
});
|
|
964
|
+
const involved = [...columns.values()];
|
|
965
|
+
const rows = Array.from({ length: rowCount }, (_, row2) => row2).filter((row2) => involved.every((column2) => Number.isFinite(column2[row2])));
|
|
966
|
+
const termColumns = terms.map((factors) => rows.map((row2) => factors.reduce((product2, name) => product2 * columns.get(name)[row2], 1)));
|
|
967
|
+
const design = intercept ? [rows.map(() => 1), ...termColumns] : termColumns;
|
|
968
|
+
const names = [
|
|
969
|
+
...intercept ? ["(Intercept)"] : [],
|
|
970
|
+
...terms.map((factors) => factors.join(":"))
|
|
971
|
+
];
|
|
972
|
+
const assign = [
|
|
973
|
+
...intercept ? [0] : [],
|
|
974
|
+
...terms.map((_, index) => index + 1)
|
|
975
|
+
];
|
|
976
|
+
const n = rows.length;
|
|
977
|
+
const p = design.length;
|
|
978
|
+
const buffer = new Float64Array(n * p);
|
|
979
|
+
design.forEach((column2, j) => {
|
|
980
|
+
buffer.set(column2, j * n);
|
|
981
|
+
});
|
|
982
|
+
return {
|
|
983
|
+
matrix: make(n, p, buffer, p === 0 ? null : [null, names]),
|
|
984
|
+
rows,
|
|
985
|
+
assign,
|
|
986
|
+
termLabels: names.slice(intercept ? 1 : 0)
|
|
987
|
+
};
|
|
988
|
+
}
|
|
989
|
+
function orderTerms(terms) {
|
|
990
|
+
const seen = new Set;
|
|
991
|
+
const unique = [];
|
|
992
|
+
terms.forEach((term) => {
|
|
993
|
+
const factors = typeof term === "string" ? [term] : term;
|
|
994
|
+
if (factors.length === 0) {
|
|
995
|
+
throw new RangeError("an interaction term needs at least one column name");
|
|
996
|
+
}
|
|
997
|
+
const key = [...factors].sort().join("\x00");
|
|
998
|
+
if (!seen.has(key)) {
|
|
999
|
+
seen.add(key);
|
|
1000
|
+
unique.push(factors);
|
|
1001
|
+
}
|
|
1002
|
+
});
|
|
1003
|
+
return unique.map((factors, index) => ({ factors, index })).sort((a, b) => a.factors.length - b.factors.length || a.index - b.index).map(({ factors }) => factors);
|
|
1004
|
+
}
|
|
1005
|
+
// src/core/special.ts
|
|
1006
|
+
var LANCZOS_G = 607 / 128;
|
|
1007
|
+
var LANCZOS_LEAD = 0.9999999999999971;
|
|
1008
|
+
var LANCZOS_TAIL = [
|
|
1009
|
+
57.15623566586292,
|
|
1010
|
+
-59.59796035547549,
|
|
1011
|
+
14.136097974741746,
|
|
1012
|
+
-0.4919138160976202,
|
|
1013
|
+
0.00003399464998481189,
|
|
1014
|
+
0.00004652362892704858,
|
|
1015
|
+
-0.00009837447530487956,
|
|
1016
|
+
0.0001580887032249125,
|
|
1017
|
+
-0.00021026444172410488,
|
|
1018
|
+
0.00021743961811521265,
|
|
1019
|
+
-0.0001643181065367639,
|
|
1020
|
+
0.00008441822398385275,
|
|
1021
|
+
-0.000026190838401581408,
|
|
1022
|
+
0.0000036899182659531625
|
|
1023
|
+
];
|
|
1024
|
+
var LOG_SQRT_TWO_PI = 0.5 * Math.log(2 * Math.PI);
|
|
1025
|
+
function lanczosSeries(x) {
|
|
1026
|
+
return LANCZOS_LEAD + sum(LANCZOS_TAIL.map((coefficient, index) => coefficient / (x + index)));
|
|
1027
|
+
}
|
|
1028
|
+
function logGamma(x) {
|
|
1029
|
+
if (!(x > 0)) {
|
|
1030
|
+
return Number.NaN;
|
|
1031
|
+
}
|
|
1032
|
+
const shifted = x + LANCZOS_G - 0.5;
|
|
1033
|
+
return LOG_SQRT_TWO_PI + (x - 0.5) * Math.log(shifted) - shifted + Math.log(lanczosSeries(x));
|
|
1034
|
+
}
|
|
1035
|
+
function logBeta(a, b) {
|
|
1036
|
+
if (!(a > 0) || !(b > 0)) {
|
|
1037
|
+
return Number.NaN;
|
|
1038
|
+
}
|
|
1039
|
+
const shiftedSum = a + b + LANCZOS_G - 0.5;
|
|
1040
|
+
return LOG_SQRT_TWO_PI - (LANCZOS_G - 0.5) + Math.log(lanczosSeries(a)) + Math.log(lanczosSeries(b)) - Math.log(lanczosSeries(a + b)) + (a - 0.5) * Math.log1p(-b / shiftedSum) + (b - 0.5) * Math.log1p(-a / shiftedSum) - 0.5 * Math.log(shiftedSum);
|
|
1041
|
+
}
|
|
1042
|
+
var FRACTION_MAX_STEPS = 400;
|
|
1043
|
+
var FRACTION_EPSILON = 0.0000000000000003;
|
|
1044
|
+
var FRACTION_FLOOR = 0.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001;
|
|
1045
|
+
function betaContinuedFraction(x, a, b) {
|
|
1046
|
+
const total = a + b;
|
|
1047
|
+
const aPlus = a + 1;
|
|
1048
|
+
const aMinus = a - 1;
|
|
1049
|
+
let c = 1;
|
|
1050
|
+
let d = 1 - total * x / aPlus;
|
|
1051
|
+
if (Math.abs(d) < FRACTION_FLOOR) {
|
|
1052
|
+
d = FRACTION_FLOOR;
|
|
1053
|
+
}
|
|
1054
|
+
d = 1 / d;
|
|
1055
|
+
let value = d;
|
|
1056
|
+
for (let step = 1;step <= FRACTION_MAX_STEPS; step += 1) {
|
|
1057
|
+
const twice = 2 * step;
|
|
1058
|
+
const even = step * (b - step) * x / ((aMinus + twice) * (a + twice));
|
|
1059
|
+
d = 1 + even * d;
|
|
1060
|
+
if (Math.abs(d) < FRACTION_FLOOR) {
|
|
1061
|
+
d = FRACTION_FLOOR;
|
|
1062
|
+
}
|
|
1063
|
+
c = 1 + even / c;
|
|
1064
|
+
if (Math.abs(c) < FRACTION_FLOOR) {
|
|
1065
|
+
c = FRACTION_FLOOR;
|
|
1066
|
+
}
|
|
1067
|
+
d = 1 / d;
|
|
1068
|
+
value *= d * c;
|
|
1069
|
+
const odd = -(a + step) * (total + step) * x / ((a + twice) * (aPlus + twice));
|
|
1070
|
+
d = 1 + odd * d;
|
|
1071
|
+
if (Math.abs(d) < FRACTION_FLOOR) {
|
|
1072
|
+
d = FRACTION_FLOOR;
|
|
1073
|
+
}
|
|
1074
|
+
c = 1 + odd / c;
|
|
1075
|
+
if (Math.abs(c) < FRACTION_FLOOR) {
|
|
1076
|
+
c = FRACTION_FLOOR;
|
|
1077
|
+
}
|
|
1078
|
+
d = 1 / d;
|
|
1079
|
+
const delta = d * c;
|
|
1080
|
+
value *= delta;
|
|
1081
|
+
if (Math.abs(delta - 1) < FRACTION_EPSILON) {
|
|
1082
|
+
break;
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
1085
|
+
return value;
|
|
1086
|
+
}
|
|
1087
|
+
function incompleteBeta(x, a, b) {
|
|
1088
|
+
if (Number.isNaN(x)) {
|
|
1089
|
+
return Number.NaN;
|
|
1090
|
+
}
|
|
1091
|
+
if (x <= 0) {
|
|
1092
|
+
return 0;
|
|
1093
|
+
}
|
|
1094
|
+
if (x >= 1) {
|
|
1095
|
+
return 1;
|
|
1096
|
+
}
|
|
1097
|
+
return incompleteBetaSplit(x, 1 - x, a, b);
|
|
1098
|
+
}
|
|
1099
|
+
function incompleteBetaSplit(x, complement, a, b) {
|
|
1100
|
+
if (Number.isNaN(x) || Number.isNaN(complement) || !(a > 0) || !(b > 0)) {
|
|
1101
|
+
return Number.NaN;
|
|
1102
|
+
}
|
|
1103
|
+
if (x <= 0) {
|
|
1104
|
+
return 0;
|
|
1105
|
+
}
|
|
1106
|
+
if (complement <= 0) {
|
|
1107
|
+
return 1;
|
|
1108
|
+
}
|
|
1109
|
+
const logX = complement < 0.5 ? Math.log1p(-complement) : Math.log(x);
|
|
1110
|
+
const logComplement = x < 0.5 ? Math.log1p(-x) : Math.log(complement);
|
|
1111
|
+
const front = Math.exp(a * logX + b * logComplement - logBeta(a, b));
|
|
1112
|
+
if (x < (a + 1) / (a + b + 2)) {
|
|
1113
|
+
return front * betaContinuedFraction(x, a, b) / a;
|
|
1114
|
+
}
|
|
1115
|
+
return 1 - front * betaContinuedFraction(complement, b, a) / b;
|
|
1116
|
+
}
|
|
1117
|
+
var GAMMA_MAX_STEPS = 1000;
|
|
1118
|
+
var GAMMA_EPSILON = 0.0000000000000003;
|
|
1119
|
+
function lowerGammaSeries(a, x) {
|
|
1120
|
+
let term = 1 / a;
|
|
1121
|
+
let total = term;
|
|
1122
|
+
for (let step = 1;step <= GAMMA_MAX_STEPS; step += 1) {
|
|
1123
|
+
term *= x / (a + step);
|
|
1124
|
+
total += term;
|
|
1125
|
+
if (Math.abs(term) < Math.abs(total) * GAMMA_EPSILON) {
|
|
1126
|
+
break;
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1129
|
+
return total * Math.exp(-x + a * Math.log(x) - logGamma(a));
|
|
1130
|
+
}
|
|
1131
|
+
function upperGammaFraction(a, x) {
|
|
1132
|
+
let b = x + 1 - a;
|
|
1133
|
+
let c = 1 / FRACTION_FLOOR;
|
|
1134
|
+
let d = 1 / b;
|
|
1135
|
+
let value = d;
|
|
1136
|
+
for (let step = 1;step <= GAMMA_MAX_STEPS; step += 1) {
|
|
1137
|
+
const numerator = -step * (step - a);
|
|
1138
|
+
b += 2;
|
|
1139
|
+
d = numerator * d + b;
|
|
1140
|
+
if (Math.abs(d) < FRACTION_FLOOR) {
|
|
1141
|
+
d = FRACTION_FLOOR;
|
|
1142
|
+
}
|
|
1143
|
+
c = b + numerator / c;
|
|
1144
|
+
if (Math.abs(c) < FRACTION_FLOOR) {
|
|
1145
|
+
c = FRACTION_FLOOR;
|
|
1146
|
+
}
|
|
1147
|
+
d = 1 / d;
|
|
1148
|
+
const delta = d * c;
|
|
1149
|
+
value *= delta;
|
|
1150
|
+
if (Math.abs(delta - 1) < GAMMA_EPSILON) {
|
|
1151
|
+
break;
|
|
1152
|
+
}
|
|
1153
|
+
}
|
|
1154
|
+
return value * Math.exp(-x + a * Math.log(x) - logGamma(a));
|
|
1155
|
+
}
|
|
1156
|
+
function upperGamma(a, x) {
|
|
1157
|
+
if (x <= 0) {
|
|
1158
|
+
return 1;
|
|
1159
|
+
}
|
|
1160
|
+
if (!Number.isFinite(x)) {
|
|
1161
|
+
return 0;
|
|
1162
|
+
}
|
|
1163
|
+
return x < a + 1 ? 1 - lowerGammaSeries(a, x) : upperGammaFraction(a, x);
|
|
1164
|
+
}
|
|
1165
|
+
function normalCdf(z) {
|
|
1166
|
+
if (Number.isNaN(z)) {
|
|
1167
|
+
return Number.NaN;
|
|
1168
|
+
}
|
|
1169
|
+
const lower = 0.5 * upperGamma(0.5, 0.5 * z * z);
|
|
1170
|
+
return z > 0 ? 1 - lower : lower;
|
|
1171
|
+
}
|
|
1172
|
+
var BELOW_ONE = 1 - Number.EPSILON / 2;
|
|
1173
|
+
var INVERSE_MAX_STEPS = 200;
|
|
1174
|
+
function inverseGuess(p, a, b) {
|
|
1175
|
+
if (a >= 1 && b >= 1) {
|
|
1176
|
+
const tail = p < 0.5 ? p : 1 - p;
|
|
1177
|
+
const t2 = Math.sqrt(-2 * Math.log(tail));
|
|
1178
|
+
const normal = (p < 0.5 ? -1 : 1) * ((2.30753 + t2 * 0.27061) / (1 + t2 * (0.99229 + t2 * 0.04481)) - t2);
|
|
1179
|
+
const scale = (normal * normal - 3) / 6;
|
|
1180
|
+
const harmonic = 2 / (1 / (2 * a - 1) + 1 / (2 * b - 1));
|
|
1181
|
+
const w = normal * Math.sqrt(scale + harmonic) / harmonic - (1 / (2 * b - 1) - 1 / (2 * a - 1)) * (scale + 5 / 6 - 2 / (3 * harmonic));
|
|
1182
|
+
return a / (a + b * Math.exp(2 * w));
|
|
1183
|
+
}
|
|
1184
|
+
const lower = Math.exp(a * Math.log(a / (a + b))) / a;
|
|
1185
|
+
const upper = Math.exp(b * Math.log(b / (a + b))) / b;
|
|
1186
|
+
const total = lower + upper;
|
|
1187
|
+
if (p < lower / total) {
|
|
1188
|
+
return Math.pow(a * total * p, 1 / a);
|
|
1189
|
+
}
|
|
1190
|
+
return 1 - Math.pow(b * total * (1 - p), 1 / b);
|
|
1191
|
+
}
|
|
1192
|
+
function inverseIncompleteBeta(p, a, b) {
|
|
1193
|
+
if (Number.isNaN(p) || !(a > 0) || !(b > 0)) {
|
|
1194
|
+
return Number.NaN;
|
|
1195
|
+
}
|
|
1196
|
+
if (p <= 0) {
|
|
1197
|
+
return 0;
|
|
1198
|
+
}
|
|
1199
|
+
if (p >= 1) {
|
|
1200
|
+
return 1;
|
|
1201
|
+
}
|
|
1202
|
+
const logBetaValue = logBeta(a, b);
|
|
1203
|
+
let lower = 0;
|
|
1204
|
+
let upper = 1;
|
|
1205
|
+
let x = inverseGuess(p, a, b);
|
|
1206
|
+
if (!(x > 0) || !(x < 1)) {
|
|
1207
|
+
x = 0.5;
|
|
1208
|
+
}
|
|
1209
|
+
for (let step = 0;step < INVERSE_MAX_STEPS; step += 1) {
|
|
1210
|
+
const residual = incompleteBeta(x, a, b) - p;
|
|
1211
|
+
if (residual < 0) {
|
|
1212
|
+
lower = x;
|
|
1213
|
+
} else {
|
|
1214
|
+
upper = x;
|
|
1215
|
+
}
|
|
1216
|
+
const density = Math.exp((a - 1) * Math.log(x) + (b - 1) * Math.log1p(-x) - logBetaValue);
|
|
1217
|
+
let next = density > 0 && Number.isFinite(density) ? x - residual / density : Number.NaN;
|
|
1218
|
+
if (!(next > lower) || !(next < upper)) {
|
|
1219
|
+
next = 0.5 * (lower + upper);
|
|
1220
|
+
}
|
|
1221
|
+
if (next === x) {
|
|
1222
|
+
break;
|
|
1223
|
+
}
|
|
1224
|
+
const moved = Math.abs(next - x);
|
|
1225
|
+
x = next;
|
|
1226
|
+
if (moved <= Number.EPSILON * x) {
|
|
1227
|
+
break;
|
|
1228
|
+
}
|
|
1229
|
+
}
|
|
1230
|
+
return Math.min(x, BELOW_ONE);
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1233
|
+
// src/core/tdist.ts
|
|
1234
|
+
function isNonCentral(ncp) {
|
|
1235
|
+
return ncp !== undefined && ncp !== 0;
|
|
1236
|
+
}
|
|
1237
|
+
function isBadArgument(value, df, ncp) {
|
|
1238
|
+
return Number.isNaN(value) || !(df > 0) || ncp !== undefined && Number.isNaN(ncp);
|
|
1239
|
+
}
|
|
1240
|
+
function dt(x, df, ncp) {
|
|
1241
|
+
if (isBadArgument(x, df, ncp)) {
|
|
1242
|
+
return Number.NaN;
|
|
1243
|
+
}
|
|
1244
|
+
return isNonCentral(ncp) ? nonCentralDensity(x, df, ncp) : centralDensity(x, df);
|
|
1245
|
+
}
|
|
1246
|
+
function pt(x, df, ncp) {
|
|
1247
|
+
if (isBadArgument(x, df, ncp)) {
|
|
1248
|
+
return Number.NaN;
|
|
1249
|
+
}
|
|
1250
|
+
return isNonCentral(ncp) ? nonCentralProbability(x, df, ncp) : centralProbability(x, df);
|
|
1251
|
+
}
|
|
1252
|
+
function qt(p, df, ncp) {
|
|
1253
|
+
if (isBadArgument(p, df, ncp) || p < 0 || p > 1) {
|
|
1254
|
+
return Number.NaN;
|
|
1255
|
+
}
|
|
1256
|
+
return isNonCentral(ncp) ? nonCentralQuantile(p, df, ncp) : centralQuantile(p, df);
|
|
1257
|
+
}
|
|
1258
|
+
function centralDensity(x, df) {
|
|
1259
|
+
if (!Number.isFinite(x)) {
|
|
1260
|
+
return 0;
|
|
1261
|
+
}
|
|
1262
|
+
const logDensity = -0.5 * Math.log(df) - logBeta(0.5, df / 2) - (df + 1) / 2 * Math.log1p(x * x / df);
|
|
1263
|
+
return Math.exp(logDensity);
|
|
1264
|
+
}
|
|
1265
|
+
function centralProbability(x, df) {
|
|
1266
|
+
if (x === 0) {
|
|
1267
|
+
return 0.5;
|
|
1268
|
+
}
|
|
1269
|
+
if (x === Number.POSITIVE_INFINITY) {
|
|
1270
|
+
return 1;
|
|
1271
|
+
}
|
|
1272
|
+
if (x === Number.NEGATIVE_INFINITY) {
|
|
1273
|
+
return 0;
|
|
1274
|
+
}
|
|
1275
|
+
const tail = upperTail(Math.abs(x), df);
|
|
1276
|
+
return x < 0 ? tail : 1 - tail;
|
|
1277
|
+
}
|
|
1278
|
+
function upperTail(t2, df) {
|
|
1279
|
+
const squared = t2 * t2;
|
|
1280
|
+
if (!Number.isFinite(squared)) {
|
|
1281
|
+
return 0;
|
|
1282
|
+
}
|
|
1283
|
+
const total = df + squared;
|
|
1284
|
+
return 0.5 * incompleteBetaSplit(df / total, squared / total, df / 2, 0.5);
|
|
1285
|
+
}
|
|
1286
|
+
var POLISH_MAX_STEPS = 4;
|
|
1287
|
+
function centralQuantile(p, df) {
|
|
1288
|
+
if (p === 0.5) {
|
|
1289
|
+
return 0;
|
|
1290
|
+
}
|
|
1291
|
+
if (p <= 0) {
|
|
1292
|
+
return Number.NEGATIVE_INFINITY;
|
|
1293
|
+
}
|
|
1294
|
+
if (p >= 1) {
|
|
1295
|
+
return Number.POSITIVE_INFINITY;
|
|
1296
|
+
}
|
|
1297
|
+
const tail = p < 0.5 ? p : 1 - p;
|
|
1298
|
+
const sign = p < 0.5 ? -1 : 1;
|
|
1299
|
+
const twoSided = 2 * tail;
|
|
1300
|
+
let squared;
|
|
1301
|
+
if (twoSided > 0.5) {
|
|
1302
|
+
const near = inverseIncompleteBeta(1 - twoSided, 0.5, df / 2);
|
|
1303
|
+
squared = df * near / (1 - near);
|
|
1304
|
+
} else {
|
|
1305
|
+
const far = inverseIncompleteBeta(twoSided, df / 2, 0.5);
|
|
1306
|
+
squared = df * (1 - far) / far;
|
|
1307
|
+
}
|
|
1308
|
+
return sign * polish(Math.sqrt(squared), tail, df);
|
|
1309
|
+
}
|
|
1310
|
+
function polish(start, tail, df) {
|
|
1311
|
+
let t2 = start;
|
|
1312
|
+
for (let step = 0;step < POLISH_MAX_STEPS; step += 1) {
|
|
1313
|
+
const density = centralDensity(t2, df);
|
|
1314
|
+
if (!(density > 0) || !Number.isFinite(t2)) {
|
|
1315
|
+
break;
|
|
1316
|
+
}
|
|
1317
|
+
const move = (upperTail(t2, df) - tail) / density;
|
|
1318
|
+
const next = t2 + move;
|
|
1319
|
+
if (!(next > 0) || !Number.isFinite(next) || Math.abs(move) > 0.25 * t2) {
|
|
1320
|
+
break;
|
|
1321
|
+
}
|
|
1322
|
+
if (next === t2) {
|
|
1323
|
+
break;
|
|
1324
|
+
}
|
|
1325
|
+
t2 = next;
|
|
1326
|
+
if (Math.abs(move) <= Number.EPSILON * t2) {
|
|
1327
|
+
break;
|
|
1328
|
+
}
|
|
1329
|
+
}
|
|
1330
|
+
return t2;
|
|
1331
|
+
}
|
|
1332
|
+
var SERIES_MAX_STEPS = 1000;
|
|
1333
|
+
var SERIES_ERROR_MAX = 0.000000000001;
|
|
1334
|
+
var SERIES_NCP_LIMIT_SQUARED = 2 * Math.LN2 * 1022;
|
|
1335
|
+
var SERIES_DF_LIMIT = 400000;
|
|
1336
|
+
var SQRT_TWO_OVER_PI = Math.sqrt(2 / Math.PI);
|
|
1337
|
+
function nonCentralProbability(x, df, ncp) {
|
|
1338
|
+
if (x === Number.POSITIVE_INFINITY) {
|
|
1339
|
+
return 1;
|
|
1340
|
+
}
|
|
1341
|
+
if (x === Number.NEGATIVE_INFINITY) {
|
|
1342
|
+
return 0;
|
|
1343
|
+
}
|
|
1344
|
+
const reflected = x < 0;
|
|
1345
|
+
const t2 = reflected ? -x : x;
|
|
1346
|
+
const delta = reflected ? -ncp : ncp;
|
|
1347
|
+
const lower = df > SERIES_DF_LIMIT || delta * delta > SERIES_NCP_LIMIT_SQUARED ? normalApproximation(t2, df, delta) : lenthSeries(t2, df, delta);
|
|
1348
|
+
return reflected ? 1 - lower : lower;
|
|
1349
|
+
}
|
|
1350
|
+
function normalApproximation(t2, df, delta) {
|
|
1351
|
+
const shrink = 1 / (4 * df);
|
|
1352
|
+
const spread = Math.sqrt(1 + t2 * t2 * 2 * shrink);
|
|
1353
|
+
return normalCdf((t2 * (1 - shrink) - delta) / spread);
|
|
1354
|
+
}
|
|
1355
|
+
function lenthSeries(t2, df, delta) {
|
|
1356
|
+
const squared = t2 * t2;
|
|
1357
|
+
const total = df + squared;
|
|
1358
|
+
const x = squared / total;
|
|
1359
|
+
const complement = df / total;
|
|
1360
|
+
let sum2 = 0;
|
|
1361
|
+
if (x > 0) {
|
|
1362
|
+
const lambda = delta * delta;
|
|
1363
|
+
let oddWeight = 0.5 * Math.exp(-0.5 * lambda);
|
|
1364
|
+
let evenWeight = SQRT_TWO_OVER_PI * oddWeight * delta;
|
|
1365
|
+
let remaining = 0.5 - oddWeight;
|
|
1366
|
+
if (remaining < 0.0000001) {
|
|
1367
|
+
remaining = -0.5 * Math.expm1(-0.5 * lambda);
|
|
1368
|
+
}
|
|
1369
|
+
let a = 0.5;
|
|
1370
|
+
const b = 0.5 * df;
|
|
1371
|
+
const powered = Math.pow(complement, b);
|
|
1372
|
+
const logBetaValue = logBeta(0.5, b);
|
|
1373
|
+
let oddTerm = incompleteBetaSplit(x, complement, a, b);
|
|
1374
|
+
let oddStep = 2 * powered * Math.exp(a * Math.log(x) - logBetaValue);
|
|
1375
|
+
let evenTerm = 1 - powered;
|
|
1376
|
+
let evenStep = b * x * powered;
|
|
1377
|
+
sum2 = oddWeight * oddTerm + evenWeight * evenTerm;
|
|
1378
|
+
for (let step = 1;step <= SERIES_MAX_STEPS; step += 1) {
|
|
1379
|
+
a += 1;
|
|
1380
|
+
oddTerm -= oddStep;
|
|
1381
|
+
evenTerm -= evenStep;
|
|
1382
|
+
oddStep *= x * (a + b - 1) / a;
|
|
1383
|
+
evenStep *= x * (a + b - 0.5) / (a + 0.5);
|
|
1384
|
+
oddWeight *= lambda / (2 * step);
|
|
1385
|
+
evenWeight *= lambda / (2 * step + 1);
|
|
1386
|
+
remaining -= oddWeight;
|
|
1387
|
+
if (remaining <= 0) {
|
|
1388
|
+
break;
|
|
1389
|
+
}
|
|
1390
|
+
sum2 += oddWeight * oddTerm + evenWeight * evenTerm;
|
|
1391
|
+
if (Math.abs(2 * remaining * (oddTerm - oddStep)) < SERIES_ERROR_MAX) {
|
|
1392
|
+
break;
|
|
1393
|
+
}
|
|
1394
|
+
}
|
|
1395
|
+
}
|
|
1396
|
+
return Math.min(Math.max(sum2 + normalCdf(-delta), 0), 1);
|
|
1397
|
+
}
|
|
1398
|
+
function nonCentralDensity(x, df, ncp) {
|
|
1399
|
+
if (!Number.isFinite(x)) {
|
|
1400
|
+
return 0;
|
|
1401
|
+
}
|
|
1402
|
+
if (Math.abs(x) > Math.sqrt(df * Number.EPSILON)) {
|
|
1403
|
+
const stepped = x * Math.sqrt((df + 2) / df);
|
|
1404
|
+
const difference = nonCentralProbability(stepped, df + 2, ncp) - nonCentralProbability(x, df, ncp);
|
|
1405
|
+
return df / Math.abs(x) * Math.abs(difference);
|
|
1406
|
+
}
|
|
1407
|
+
return Math.exp(-0.5 * Math.log(df) - logBeta(0.5, df / 2) - 0.5 * ncp * ncp);
|
|
1408
|
+
}
|
|
1409
|
+
var QUANTILE_MAX_STEPS = 200;
|
|
1410
|
+
function nonCentralQuantile(p, df, ncp) {
|
|
1411
|
+
if (p <= 0) {
|
|
1412
|
+
return Number.NEGATIVE_INFINITY;
|
|
1413
|
+
}
|
|
1414
|
+
if (p >= 1) {
|
|
1415
|
+
return Number.POSITIVE_INFINITY;
|
|
1416
|
+
}
|
|
1417
|
+
let upper = Math.max(1, ncp);
|
|
1418
|
+
while (Number.isFinite(upper) && nonCentralProbability(upper, df, ncp) < p) {
|
|
1419
|
+
upper *= 2;
|
|
1420
|
+
}
|
|
1421
|
+
let lower = Math.min(-1, -ncp);
|
|
1422
|
+
while (Number.isFinite(lower) && nonCentralProbability(lower, df, ncp) > p) {
|
|
1423
|
+
lower *= 2;
|
|
1424
|
+
}
|
|
1425
|
+
let t2 = 0.5 * (lower + upper);
|
|
1426
|
+
for (let step = 0;step < QUANTILE_MAX_STEPS; step += 1) {
|
|
1427
|
+
const residual = nonCentralProbability(t2, df, ncp) - p;
|
|
1428
|
+
if (residual < 0) {
|
|
1429
|
+
lower = t2;
|
|
1430
|
+
} else {
|
|
1431
|
+
upper = t2;
|
|
1432
|
+
}
|
|
1433
|
+
const density = nonCentralDensity(t2, df, ncp);
|
|
1434
|
+
let next = density > 0 && Number.isFinite(density) ? t2 - residual / density : Number.NaN;
|
|
1435
|
+
if (!(next > lower) || !(next < upper)) {
|
|
1436
|
+
next = 0.5 * (lower + upper);
|
|
1437
|
+
}
|
|
1438
|
+
if (next === t2) {
|
|
1439
|
+
break;
|
|
1440
|
+
}
|
|
1441
|
+
const moved = Math.abs(next - t2);
|
|
1442
|
+
t2 = next;
|
|
1443
|
+
if (moved <= Number.EPSILON * Math.abs(t2)) {
|
|
1444
|
+
break;
|
|
1445
|
+
}
|
|
1446
|
+
}
|
|
1447
|
+
return t2;
|
|
1448
|
+
}
|
|
1449
|
+
|
|
1450
|
+
// src/core/linalg/lm.ts
|
|
1451
|
+
function lm(data, options) {
|
|
1452
|
+
const { outcome, intercept = true, tolerance = DEFAULT_QR_TOLERANCE } = options;
|
|
1453
|
+
const design = modelMatrix(data, options);
|
|
1454
|
+
const { rows } = design;
|
|
1455
|
+
const n = rows.length;
|
|
1456
|
+
if (n === 0) {
|
|
1457
|
+
throw new RangeError("0 (non-NA) cases");
|
|
1458
|
+
}
|
|
1459
|
+
const outcomeColumn = data[outcome];
|
|
1460
|
+
const y = rows.map((row2) => outcomeColumn[row2]);
|
|
1461
|
+
const factored = qr(design.matrix, { tolerance });
|
|
1462
|
+
const coefficients = qrCoef(factored, y);
|
|
1463
|
+
const residuals = qrResid(factored, y);
|
|
1464
|
+
const fitted = zipWith(y, residuals, (value, residual) => value - residual);
|
|
1465
|
+
const names = design.matrix.dimnames?.[1] ?? [];
|
|
1466
|
+
const { rank } = factored;
|
|
1467
|
+
const dfResidual = n - rank;
|
|
1468
|
+
const interceptCount = intercept ? 1 : 0;
|
|
1469
|
+
const rss = sum(residuals.map((r) => r * r));
|
|
1470
|
+
const centered = intercept ? mean(fitted) : 0;
|
|
1471
|
+
const mss = sum(fitted.map((f) => (f - centered) * (f - centered)));
|
|
1472
|
+
const resvar = rss / dfResidual;
|
|
1473
|
+
const numdf = rank - interceptCount;
|
|
1474
|
+
const rSquared = numdf > 0 ? mss / (mss + rss) : 0;
|
|
1475
|
+
const adjRSquared = numdf > 0 ? 1 - (1 - rSquared) * ((n - interceptCount) / dfResidual) : 0;
|
|
1476
|
+
const standardErrors = standardErrorsOf(factored, resvar, names.length);
|
|
1477
|
+
const tValues = zipWith(coefficients, standardErrors, (b, se) => b === null || se === null ? null : b / se);
|
|
1478
|
+
const pValues = tValues.map((tv) => tv === null ? null : 2 * pt(-Math.abs(tv), dfResidual));
|
|
1479
|
+
return {
|
|
1480
|
+
coefficients: namedVector(names, coefficients),
|
|
1481
|
+
standardErrors: namedVector(names, standardErrors),
|
|
1482
|
+
tValues: namedVector(names, tValues),
|
|
1483
|
+
pValues: namedVector(names, pValues),
|
|
1484
|
+
fitted: padded(fitted, rows, outcomeColumn.length),
|
|
1485
|
+
residuals: padded(residuals, rows, outcomeColumn.length),
|
|
1486
|
+
rank,
|
|
1487
|
+
dfResidual,
|
|
1488
|
+
rSquared,
|
|
1489
|
+
adjRSquared,
|
|
1490
|
+
sigma: Math.sqrt(resvar),
|
|
1491
|
+
fStatistic: numdf > 0 ? { value: mss / numdf / resvar, numdf, dendf: dfResidual } : null,
|
|
1492
|
+
rows,
|
|
1493
|
+
termLabels: design.termLabels
|
|
1494
|
+
};
|
|
1495
|
+
}
|
|
1496
|
+
function standardErrorsOf(factored, resvar, width) {
|
|
1497
|
+
const { rank, pivot } = factored;
|
|
1498
|
+
const { nrow } = factored.qr;
|
|
1499
|
+
const r = (i, j) => factored.qr.data[j * nrow + i];
|
|
1500
|
+
const inverse = Array.from({ length: rank }, (_, k) => {
|
|
1501
|
+
const x = new Array(rank).fill(0);
|
|
1502
|
+
x[k] = 1 / r(k, k);
|
|
1503
|
+
for (let i = k - 1;i >= 0; i--) {
|
|
1504
|
+
let total = 0;
|
|
1505
|
+
for (let j = i + 1;j <= k; j++) {
|
|
1506
|
+
total += r(i, j) * x[j];
|
|
1507
|
+
}
|
|
1508
|
+
x[i] = -total / r(i, i);
|
|
1509
|
+
}
|
|
1510
|
+
return x;
|
|
1511
|
+
});
|
|
1512
|
+
const diagonal = Array.from({ length: rank }, (_, i) => sum(inverse.map((columnOfInverse) => columnOfInverse[i] ** 2)));
|
|
1513
|
+
const errors = new Array(width).fill(null);
|
|
1514
|
+
pivot.slice(0, rank).forEach((original, position) => {
|
|
1515
|
+
errors[original] = Math.sqrt(diagonal[position] * resvar);
|
|
1516
|
+
});
|
|
1517
|
+
return errors;
|
|
1518
|
+
}
|
|
1519
|
+
function padded(values, rows, length) {
|
|
1520
|
+
const out = new Array(length).fill(Number.NaN);
|
|
1521
|
+
rows.forEach((row2, index) => {
|
|
1522
|
+
out[row2] = values[index];
|
|
1523
|
+
});
|
|
1524
|
+
return out;
|
|
1525
|
+
}
|
|
1526
|
+
// src/core/linalg/cov.ts
|
|
1527
|
+
function refinedMean(values) {
|
|
1528
|
+
const n = values.length;
|
|
1529
|
+
const first = sum(values) / n;
|
|
1530
|
+
if (!Number.isFinite(first)) {
|
|
1531
|
+
return first;
|
|
1532
|
+
}
|
|
1533
|
+
return first + sum(values.map((value) => value - first)) / n;
|
|
1534
|
+
}
|
|
1535
|
+
function covariance(a, meanA, b, meanB) {
|
|
1536
|
+
const n = a.length;
|
|
1537
|
+
if (n < 2) {
|
|
1538
|
+
return Number.NaN;
|
|
1539
|
+
}
|
|
1540
|
+
let total = 0;
|
|
1541
|
+
for (let i = 0;i < n; i++) {
|
|
1542
|
+
total += (a[i] - meanA) * (b[i] - meanB);
|
|
1543
|
+
}
|
|
1544
|
+
return total / (n - 1);
|
|
1545
|
+
}
|
|
1546
|
+
function requireSameLength(a, b) {
|
|
1547
|
+
if (a.length !== b.length) {
|
|
1548
|
+
throw new RangeError("incompatible dimensions");
|
|
1549
|
+
}
|
|
1550
|
+
}
|
|
1551
|
+
function variance(a) {
|
|
1552
|
+
const center = refinedMean(a);
|
|
1553
|
+
return covariance(a, center, a, center);
|
|
1554
|
+
}
|
|
1555
|
+
function cov(x, y) {
|
|
1556
|
+
if (isMatrix(x)) {
|
|
1557
|
+
return pairwise(x, false);
|
|
1558
|
+
}
|
|
1559
|
+
if (y === undefined) {
|
|
1560
|
+
throw new RangeError("cov() of a vector needs a second vector");
|
|
1561
|
+
}
|
|
1562
|
+
requireSameLength(x, y);
|
|
1563
|
+
return covariance(x, refinedMean(x), y, refinedMean(y));
|
|
1564
|
+
}
|
|
1565
|
+
function cor(x, y) {
|
|
1566
|
+
if (isMatrix(x)) {
|
|
1567
|
+
return pairwise(x, true);
|
|
1568
|
+
}
|
|
1569
|
+
if (y === undefined) {
|
|
1570
|
+
throw new RangeError("cor() of a vector needs a second vector");
|
|
1571
|
+
}
|
|
1572
|
+
requireSameLength(x, y);
|
|
1573
|
+
const meanX = refinedMean(x);
|
|
1574
|
+
const meanY = refinedMean(y);
|
|
1575
|
+
const spread = Math.sqrt(covariance(x, meanX, x, meanX) * covariance(y, meanY, y, meanY));
|
|
1576
|
+
return spread === 0 ? Number.NaN : clamp(covariance(x, meanX, y, meanY) / spread);
|
|
1577
|
+
}
|
|
1578
|
+
function pairwise(m, correlation) {
|
|
1579
|
+
const { nrow, ncol } = m;
|
|
1580
|
+
const columns = Array.from({ length: ncol }, (_, j) => Array.from(m.data.subarray(j * nrow, (j + 1) * nrow)));
|
|
1581
|
+
const means = columns.map(refinedMean);
|
|
1582
|
+
const data = new Float64Array(ncol * ncol);
|
|
1583
|
+
for (let i = 0;i < ncol; i++) {
|
|
1584
|
+
for (let j = 0;j <= i; j++) {
|
|
1585
|
+
const value = covariance(columns[i], means[i], columns[j], means[j]);
|
|
1586
|
+
data[j * ncol + i] = value;
|
|
1587
|
+
data[i * ncol + j] = value;
|
|
1588
|
+
}
|
|
1589
|
+
}
|
|
1590
|
+
if (correlation) {
|
|
1591
|
+
const spreads = Array.from({ length: ncol }, (_, i) => Math.sqrt(data[i * ncol + i]));
|
|
1592
|
+
for (let i = 0;i < ncol; i++) {
|
|
1593
|
+
for (let j = 0;j <= i; j++) {
|
|
1594
|
+
const divisor = spreads[i] * spreads[j];
|
|
1595
|
+
const value = i === j ? 1 : divisor === 0 ? Number.NaN : clamp(data[j * ncol + i] / divisor);
|
|
1596
|
+
data[j * ncol + i] = value;
|
|
1597
|
+
data[i * ncol + j] = value;
|
|
1598
|
+
}
|
|
1599
|
+
}
|
|
1600
|
+
}
|
|
1601
|
+
const names = m.dimnames?.[1] ?? null;
|
|
1602
|
+
const dimnames = names === null ? null : [names, names];
|
|
1603
|
+
return make(ncol, ncol, data, dimnames);
|
|
1604
|
+
}
|
|
1605
|
+
function clamp(value) {
|
|
1606
|
+
return value > 1 ? 1 : value < -1 ? -1 : value;
|
|
1607
|
+
}
|
|
1608
|
+
// src/core/linalg/eigen.ts
|
|
1609
|
+
function isSymmetric(m, tolerance = 100 * Number.EPSILON) {
|
|
1610
|
+
const { nrow, ncol, data } = m;
|
|
1611
|
+
if (nrow !== ncol) {
|
|
1612
|
+
return false;
|
|
1613
|
+
}
|
|
1614
|
+
for (let j = 0;j < ncol; j++) {
|
|
1615
|
+
for (let i = 0;i < j; i++) {
|
|
1616
|
+
const a = data[j * nrow + i];
|
|
1617
|
+
const b = data[i * nrow + j];
|
|
1618
|
+
if (Math.abs(a - b) > tolerance * Math.max(Math.abs(a), Math.abs(b))) {
|
|
1619
|
+
return false;
|
|
1620
|
+
}
|
|
1621
|
+
}
|
|
1622
|
+
}
|
|
1623
|
+
return true;
|
|
1624
|
+
}
|
|
1625
|
+
function eigenSymmetric(m) {
|
|
1626
|
+
if (m.nrow !== m.ncol) {
|
|
1627
|
+
throw new RangeError("non-square matrix in 'eigen'");
|
|
1628
|
+
}
|
|
1629
|
+
if (m.nrow === 0) {
|
|
1630
|
+
throw new RangeError("0 x 0 matrix");
|
|
1631
|
+
}
|
|
1632
|
+
if (!m.data.every(Number.isFinite)) {
|
|
1633
|
+
throw new RangeError("infinite or missing values in 'x'");
|
|
1634
|
+
}
|
|
1635
|
+
if (!isSymmetric(m)) {
|
|
1636
|
+
throw new RangeError("'x' must be symmetric");
|
|
1637
|
+
}
|
|
1638
|
+
const n = m.nrow;
|
|
1639
|
+
const a = Float64Array.from(m.data);
|
|
1640
|
+
const v = new Float64Array(n * n);
|
|
1641
|
+
for (let i = 0;i < n; i++) {
|
|
1642
|
+
v[i * n + i] = 1;
|
|
1643
|
+
}
|
|
1644
|
+
jacobi(a, v, n);
|
|
1645
|
+
const order = Array.from({ length: n }, (_, i) => i).sort((i, j) => a[j * n + j] - a[i * n + i] || i - j);
|
|
1646
|
+
const values = order.map((i) => a[i * n + i]);
|
|
1647
|
+
const vectors = new Float64Array(n * n);
|
|
1648
|
+
order.forEach((from, k) => {
|
|
1649
|
+
const columnVector = v.subarray(from * n, (from + 1) * n);
|
|
1650
|
+
let largest = 0;
|
|
1651
|
+
columnVector.forEach((entry) => {
|
|
1652
|
+
if (Math.abs(entry) > Math.abs(largest)) {
|
|
1653
|
+
largest = entry;
|
|
1654
|
+
}
|
|
1655
|
+
});
|
|
1656
|
+
const sign = largest < 0 ? -1 : 1;
|
|
1657
|
+
columnVector.forEach((entry, i) => {
|
|
1658
|
+
vectors[k * n + i] = sign * entry + 0;
|
|
1659
|
+
});
|
|
1660
|
+
});
|
|
1661
|
+
const rows = m.dimnames?.[0] ?? null;
|
|
1662
|
+
return { values, vectors: make(n, n, vectors, rows === null ? null : [rows, null]) };
|
|
1663
|
+
}
|
|
1664
|
+
function jacobi(a, v, n) {
|
|
1665
|
+
const at2 = (i, j) => a[j * n + i];
|
|
1666
|
+
const set = (i, j, value) => {
|
|
1667
|
+
a[j * n + i] = value;
|
|
1668
|
+
};
|
|
1669
|
+
for (let sweep = 0;sweep < 100; sweep++) {
|
|
1670
|
+
let off = 0;
|
|
1671
|
+
for (let p = 0;p < n; p++) {
|
|
1672
|
+
for (let q = p + 1;q < n; q++) {
|
|
1673
|
+
off += at2(p, q) * at2(p, q);
|
|
1674
|
+
}
|
|
1675
|
+
}
|
|
1676
|
+
if (off === 0) {
|
|
1677
|
+
return;
|
|
1678
|
+
}
|
|
1679
|
+
let diagonal = 0;
|
|
1680
|
+
for (let p = 0;p < n; p++) {
|
|
1681
|
+
diagonal += at2(p, p) * at2(p, p);
|
|
1682
|
+
}
|
|
1683
|
+
if (off <= Number.EPSILON * Number.EPSILON * diagonal) {
|
|
1684
|
+
return;
|
|
1685
|
+
}
|
|
1686
|
+
for (let p = 0;p < n; p++) {
|
|
1687
|
+
for (let q = p + 1;q < n; q++) {
|
|
1688
|
+
const apq = at2(p, q);
|
|
1689
|
+
if (apq === 0) {
|
|
1690
|
+
continue;
|
|
1691
|
+
}
|
|
1692
|
+
const theta = (at2(q, q) - at2(p, p)) / (2 * apq);
|
|
1693
|
+
const t2 = (theta >= 0 ? 1 : -1) / (Math.abs(theta) + Math.sqrt(theta * theta + 1));
|
|
1694
|
+
const c = 1 / Math.sqrt(t2 * t2 + 1);
|
|
1695
|
+
const s = t2 * c;
|
|
1696
|
+
for (let k = 0;k < n; k++) {
|
|
1697
|
+
const akp = at2(k, p);
|
|
1698
|
+
const akq = at2(k, q);
|
|
1699
|
+
set(k, p, c * akp - s * akq);
|
|
1700
|
+
set(k, q, s * akp + c * akq);
|
|
1701
|
+
}
|
|
1702
|
+
for (let k = 0;k < n; k++) {
|
|
1703
|
+
const apk = at2(p, k);
|
|
1704
|
+
const aqk = at2(q, k);
|
|
1705
|
+
set(p, k, c * apk - s * aqk);
|
|
1706
|
+
set(q, k, s * apk + c * aqk);
|
|
1707
|
+
}
|
|
1708
|
+
for (let k = 0;k < n; k++) {
|
|
1709
|
+
const vkp = v[p * n + k];
|
|
1710
|
+
const vkq = v[q * n + k];
|
|
1711
|
+
v[p * n + k] = c * vkp - s * vkq;
|
|
1712
|
+
v[q * n + k] = s * vkp + c * vkq;
|
|
1713
|
+
}
|
|
1714
|
+
}
|
|
1715
|
+
}
|
|
1716
|
+
}
|
|
1717
|
+
}
|
|
1718
|
+
// src/core/linalg/prcomp.ts
|
|
1719
|
+
function prcomp(input, options = {}) {
|
|
1720
|
+
const { center = true, scale = false } = options;
|
|
1721
|
+
const m = isMatrixLike(input) ? input : fromFrame(input);
|
|
1722
|
+
const { nrow: n, ncol: p } = m;
|
|
1723
|
+
if (!m.data.every(Number.isFinite)) {
|
|
1724
|
+
throw new RangeError("infinite or missing values in 'x'");
|
|
1725
|
+
}
|
|
1726
|
+
const columns = Array.from({ length: p }, (_, j) => Array.from(m.data.subarray(j * n, (j + 1) * n)));
|
|
1727
|
+
const centers = columns.map((column2) => center ? sum(column2) / n : 0);
|
|
1728
|
+
const centered = columns.map((column2, j) => column2.map((value) => value - centers[j]));
|
|
1729
|
+
const scales = scale ? centered.map((column2) => Math.sqrt(sum(column2.map((value) => value * value)) / Math.max(1, n - 1))) : null;
|
|
1730
|
+
if (scales !== null && scales.some((value) => value === 0)) {
|
|
1731
|
+
throw new RangeError("cannot rescale a constant/zero column to unit variance");
|
|
1732
|
+
}
|
|
1733
|
+
const prepared = centered.map((column2, j) => scales === null ? column2 : column2.map((value) => value / scales[j]));
|
|
1734
|
+
const divisor = Math.max(1, n - 1);
|
|
1735
|
+
const covariance2 = new Float64Array(p * p);
|
|
1736
|
+
for (let i = 0;i < p; i++) {
|
|
1737
|
+
for (let j = 0;j <= i; j++) {
|
|
1738
|
+
let total = 0;
|
|
1739
|
+
for (let k = 0;k < n; k++) {
|
|
1740
|
+
total += prepared[i][k] * prepared[j][k];
|
|
1741
|
+
}
|
|
1742
|
+
covariance2[j * p + i] = total / divisor;
|
|
1743
|
+
covariance2[i * p + j] = total / divisor;
|
|
1744
|
+
}
|
|
1745
|
+
}
|
|
1746
|
+
const variables = m.dimnames?.[1] ?? null;
|
|
1747
|
+
const eigen = eigenSymmetric(make(p, p, covariance2, variables === null ? null : [variables, variables]));
|
|
1748
|
+
const components = Array.from({ length: p }, (_, k) => `PC${k + 1}`);
|
|
1749
|
+
const rotation = make(p, p, Float64Array.from(eigen.vectors.data), [variables, components]);
|
|
1750
|
+
const data = new Float64Array(n * p);
|
|
1751
|
+
prepared.forEach((column2, j) => {
|
|
1752
|
+
data.set(column2, j * n);
|
|
1753
|
+
});
|
|
1754
|
+
const scores = matmul(make(n, p, data, null), rotation);
|
|
1755
|
+
return {
|
|
1756
|
+
sdev: eigen.values.map((value) => Math.sqrt(Math.max(value, 0))),
|
|
1757
|
+
rotation,
|
|
1758
|
+
center: centers,
|
|
1759
|
+
scale: scales,
|
|
1760
|
+
x: make(n, p, scores.data, [m.dimnames?.[0] ?? null, components])
|
|
1761
|
+
};
|
|
1762
|
+
}
|
|
1763
|
+
function isMatrixLike(input) {
|
|
1764
|
+
return "data" in input && input.data instanceof Float64Array && typeof input.nrow === "number";
|
|
1765
|
+
}
|
|
1766
|
+
// src/core/linalg/vector.ts
|
|
1767
|
+
function elementwise(a, b, combine) {
|
|
1768
|
+
if (typeof b === "number") {
|
|
1769
|
+
return a.map((x) => combine(x, b));
|
|
1770
|
+
}
|
|
1771
|
+
requireSameLength2(a, b);
|
|
1772
|
+
return zipWith(a, b, combine);
|
|
1773
|
+
}
|
|
1774
|
+
function requireSameLength2(a, b) {
|
|
1775
|
+
if (a.length !== b.length) {
|
|
1776
|
+
throw new RangeError(`vector lengths differ: ${a.length} and ${b.length}`);
|
|
1777
|
+
}
|
|
1778
|
+
}
|
|
1779
|
+
function add(a, b) {
|
|
1780
|
+
return elementwise(a, b, (x, y) => x + y);
|
|
1781
|
+
}
|
|
1782
|
+
function sub(a, b) {
|
|
1783
|
+
return elementwise(a, b, (x, y) => x - y);
|
|
1784
|
+
}
|
|
1785
|
+
function mul(a, b) {
|
|
1786
|
+
return elementwise(a, b, (x, y) => x * y);
|
|
1787
|
+
}
|
|
1788
|
+
function div(a, b) {
|
|
1789
|
+
return elementwise(a, b, (x, y) => x / y);
|
|
1790
|
+
}
|
|
1791
|
+
function square(a) {
|
|
1792
|
+
return a.map((x) => x * x);
|
|
1793
|
+
}
|
|
1794
|
+
function dot(a, b) {
|
|
1795
|
+
requireSameLength2(a, b);
|
|
1796
|
+
return sum(mul(a, b));
|
|
1797
|
+
}
|
|
1798
|
+
function norm2(a) {
|
|
1799
|
+
return Math.sqrt(sum(square(a)));
|
|
1800
|
+
}
|
|
1801
|
+
function cosine(a, b) {
|
|
1802
|
+
return dot(a, b) / (norm2(a) * norm2(b));
|
|
1803
|
+
}
|
|
1804
|
+
export {
|
|
1805
|
+
variance,
|
|
1806
|
+
transpose,
|
|
1807
|
+
toRows,
|
|
1808
|
+
toColumns,
|
|
1809
|
+
tcrossprod,
|
|
1810
|
+
t,
|
|
1811
|
+
sub,
|
|
1812
|
+
square,
|
|
1813
|
+
solve,
|
|
1814
|
+
row,
|
|
1815
|
+
rcond,
|
|
1816
|
+
rbind,
|
|
1817
|
+
qrResid,
|
|
1818
|
+
qrR,
|
|
1819
|
+
qrQy,
|
|
1820
|
+
qrQty,
|
|
1821
|
+
qrQ,
|
|
1822
|
+
qrFitted,
|
|
1823
|
+
qrCoef,
|
|
1824
|
+
qr,
|
|
1825
|
+
prcomp,
|
|
1826
|
+
norm2 as norm,
|
|
1827
|
+
namedVector,
|
|
1828
|
+
mul,
|
|
1829
|
+
modelMatrix,
|
|
1830
|
+
matrixNorm,
|
|
1831
|
+
matrix,
|
|
1832
|
+
matmul,
|
|
1833
|
+
lu,
|
|
1834
|
+
lookup,
|
|
1835
|
+
lm,
|
|
1836
|
+
isSymmetric,
|
|
1837
|
+
identity,
|
|
1838
|
+
fromRows,
|
|
1839
|
+
fromFrame,
|
|
1840
|
+
fromColumns,
|
|
1841
|
+
eigenSymmetric,
|
|
1842
|
+
dot,
|
|
1843
|
+
div,
|
|
1844
|
+
diag,
|
|
1845
|
+
determinant,
|
|
1846
|
+
det,
|
|
1847
|
+
crossprod,
|
|
1848
|
+
cov,
|
|
1849
|
+
cosine,
|
|
1850
|
+
cor,
|
|
1851
|
+
column,
|
|
1852
|
+
cbind,
|
|
1853
|
+
at,
|
|
1854
|
+
add,
|
|
1855
|
+
DEFAULT_SOLVE_TOLERANCE,
|
|
1856
|
+
DEFAULT_QR_TOLERANCE
|
|
1857
|
+
};
|
|
1858
|
+
|
|
1859
|
+
//# debugId=3595AB073D7D3A5A64756E2164756E21
|
|
1860
|
+
//# sourceMappingURL=linalg.js.map
|