@cloudglides/nox 2.0.0 → 3.0.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/example/package.json +1 -1
- package/example/src/App.jsx +2 -2
- package/package.json +1 -1
- package/pnpm-workspace.yaml +3 -0
- package/src/core.browser.js +30 -5
- package/src/core.js +5 -0
- package/src/index.browser.js +168 -1
- package/src/index.js +16 -1
- package/src/utils/clustering.js +143 -0
- package/src/utils/diagnostics.js +309 -0
- package/src/utils/logistic.js +91 -0
- package/src/utils/polynomial.js +136 -0
- package/src/utils/pvalues.js +142 -0
- package/example/pnpm-lock.yaml +0 -1006
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
export function residualDiagnostics(model) {
|
|
2
|
+
if (!model.residuals || !model.predictions) {
|
|
3
|
+
throw new TypeError('model must have residuals and predictions');
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
const residuals = model.residuals;
|
|
7
|
+
const predictions = model.predictions;
|
|
8
|
+
const n = residuals.length;
|
|
9
|
+
|
|
10
|
+
const mean = residuals.reduce((a, b) => a + b, 0) / n;
|
|
11
|
+
let variance = 0;
|
|
12
|
+
for (let i = 0; i < n; i++) {
|
|
13
|
+
variance += (residuals[i] - mean) ** 2;
|
|
14
|
+
}
|
|
15
|
+
variance /= (n - 1);
|
|
16
|
+
const stddev = Math.sqrt(variance);
|
|
17
|
+
|
|
18
|
+
const standardized = residuals.map(r => r / stddev);
|
|
19
|
+
|
|
20
|
+
const studentized = residuals.map((r, i) => {
|
|
21
|
+
const leverage = 1 / n;
|
|
22
|
+
const se = stddev * Math.sqrt(1 - leverage);
|
|
23
|
+
return r / se;
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
const qqData = [...standardized].sort((a, b) => a - b);
|
|
27
|
+
const qqTheoretical = new Array(n);
|
|
28
|
+
for (let i = 0; i < n; i++) {
|
|
29
|
+
const p = (i + 0.5) / n;
|
|
30
|
+
qqTheoretical[i] = invNormal(p);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
let correlation = 0;
|
|
34
|
+
let sumX2 = 0, sumY2 = 0;
|
|
35
|
+
for (let i = 0; i < n; i++) {
|
|
36
|
+
correlation += qqData[i] * qqTheoretical[i];
|
|
37
|
+
sumX2 += qqData[i] * qqData[i];
|
|
38
|
+
sumY2 += qqTheoretical[i] * qqTheoretical[i];
|
|
39
|
+
}
|
|
40
|
+
const qqCorr = correlation / Math.sqrt(sumX2 * sumY2);
|
|
41
|
+
|
|
42
|
+
return {
|
|
43
|
+
residuals,
|
|
44
|
+
standardized,
|
|
45
|
+
studentized,
|
|
46
|
+
mean,
|
|
47
|
+
variance,
|
|
48
|
+
stddev,
|
|
49
|
+
qqCorrelation: qqCorr,
|
|
50
|
+
qqData,
|
|
51
|
+
qqTheoretical
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function leverageValues(X) {
|
|
56
|
+
if (!Array.isArray(X) || X.length === 0) {
|
|
57
|
+
throw new TypeError('X must be non-empty array');
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const n = X.length;
|
|
61
|
+
const p = Array.isArray(X[0]) ? X[0].length : 1;
|
|
62
|
+
|
|
63
|
+
const Xmat = X.map(row => Array.isArray(row) ? [1, ...row] : [1, row]);
|
|
64
|
+
const XtX = matmul(transpose(Xmat), Xmat);
|
|
65
|
+
|
|
66
|
+
let invXtX;
|
|
67
|
+
try {
|
|
68
|
+
invXtX = matrixInverse(XtX);
|
|
69
|
+
} catch {
|
|
70
|
+
throw new RangeError('X matrix is singular');
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const leverages = new Array(n);
|
|
74
|
+
for (let i = 0; i < n; i++) {
|
|
75
|
+
let leverage = 0;
|
|
76
|
+
for (let j = 0; j <= p; j++) {
|
|
77
|
+
for (let k = 0; k <= p; k++) {
|
|
78
|
+
leverage += Xmat[i][j] * invXtX[j][k] * Xmat[i][k];
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
leverages[i] = leverage;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return leverages;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function cookDistance(residuals, predictions, leverage, n, p) {
|
|
88
|
+
if (!Array.isArray(residuals) || !Array.isArray(predictions)) {
|
|
89
|
+
throw new TypeError('residuals and predictions must be arrays');
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const sigma2 = residuals.reduce((s, r) => s + r * r, 0) / (n - p - 1);
|
|
93
|
+
const cooks = new Array(n);
|
|
94
|
+
|
|
95
|
+
for (let i = 0; i < n; i++) {
|
|
96
|
+
const lev = leverage[i];
|
|
97
|
+
cooks[i] = (residuals[i] ** 2 / (sigma2 * (p + 1))) * (lev / (1 - lev));
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return cooks;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function durwinWatson(residuals) {
|
|
104
|
+
if (!Array.isArray(residuals) || residuals.length < 2) {
|
|
105
|
+
throw new TypeError('residuals must be array of length >= 2');
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
let numerator = 0;
|
|
109
|
+
let denominator = 0;
|
|
110
|
+
|
|
111
|
+
for (let i = 1; i < residuals.length; i++) {
|
|
112
|
+
const diff = residuals[i] - residuals[i - 1];
|
|
113
|
+
numerator += diff * diff;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
for (let i = 0; i < residuals.length; i++) {
|
|
117
|
+
denominator += residuals[i] * residuals[i];
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return numerator / denominator;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export function vif(X) {
|
|
124
|
+
if (!Array.isArray(X) || X.length === 0) {
|
|
125
|
+
throw new TypeError('X must be non-empty array of features');
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const n = X.length;
|
|
129
|
+
const p = Array.isArray(X[0]) ? X[0].length : 1;
|
|
130
|
+
const vifs = new Array(p);
|
|
131
|
+
|
|
132
|
+
for (let j = 0; j < p; j++) {
|
|
133
|
+
const y = X.map(row => Array.isArray(row) ? row[j] : row);
|
|
134
|
+
const Xj = X.map((row, i) => {
|
|
135
|
+
const features = Array.isArray(row) ? row : [row];
|
|
136
|
+
return features.filter((_, k) => k !== j);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
if (Xj[0].length === 0) {
|
|
140
|
+
vifs[j] = 1;
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const r2 = computeR2(Xj, y);
|
|
145
|
+
vifs[j] = 1 / (1 - r2);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return vifs;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function computeR2(X, y) {
|
|
152
|
+
const n = X.length;
|
|
153
|
+
const yMean = y.reduce((a, b) => a + b, 0) / n;
|
|
154
|
+
|
|
155
|
+
let ssTot = 0;
|
|
156
|
+
for (let i = 0; i < n; i++) {
|
|
157
|
+
ssTot += (y[i] - yMean) ** 2;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const Xmat = X.map(row => Array.isArray(row) ? [1, ...row] : [1, row]);
|
|
161
|
+
const XtX = matmul(transpose(Xmat), Xmat);
|
|
162
|
+
const Xty = matmul(transpose(Xmat), y.map(yi => [yi]));
|
|
163
|
+
|
|
164
|
+
let beta;
|
|
165
|
+
try {
|
|
166
|
+
beta = solve(XtX, Xty).flat();
|
|
167
|
+
} catch {
|
|
168
|
+
return 0;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
let ssRes = 0;
|
|
172
|
+
for (let i = 0; i < n; i++) {
|
|
173
|
+
let pred = beta[0];
|
|
174
|
+
for (let j = 1; j < beta.length; j++) {
|
|
175
|
+
pred += beta[j] * (Array.isArray(X[i]) ? X[i][j - 1] : X[i]);
|
|
176
|
+
}
|
|
177
|
+
ssRes += (y[i] - pred) ** 2;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return 1 - (ssRes / ssTot);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function transpose(matrix) {
|
|
184
|
+
const result = [];
|
|
185
|
+
for (let j = 0; j < matrix[0].length; j++) {
|
|
186
|
+
result[j] = [];
|
|
187
|
+
for (let i = 0; i < matrix.length; i++) {
|
|
188
|
+
result[j].push(matrix[i][j]);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
return result;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function matmul(A, B) {
|
|
195
|
+
const result = [];
|
|
196
|
+
for (let i = 0; i < A.length; i++) {
|
|
197
|
+
result[i] = [];
|
|
198
|
+
for (let j = 0; j < B[0].length; j++) {
|
|
199
|
+
let sum = 0;
|
|
200
|
+
for (let k = 0; k < A[0].length; k++) {
|
|
201
|
+
sum += A[i][k] * B[k][j];
|
|
202
|
+
}
|
|
203
|
+
result[i][j] = sum;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
return result;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function solve(A, b) {
|
|
210
|
+
const n = A.length;
|
|
211
|
+
const aug = A.map((row, i) => [...row, b[i][0]]);
|
|
212
|
+
|
|
213
|
+
for (let i = 0; i < n; i++) {
|
|
214
|
+
let maxRow = i;
|
|
215
|
+
for (let k = i + 1; k < n; k++) {
|
|
216
|
+
if (Math.abs(aug[k][i]) > Math.abs(aug[maxRow][i])) {
|
|
217
|
+
maxRow = k;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
[aug[i], aug[maxRow]] = [aug[maxRow], aug[i]];
|
|
222
|
+
|
|
223
|
+
if (Math.abs(aug[i][i]) < 1e-10) {
|
|
224
|
+
throw new RangeError('matrix is singular');
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
for (let k = i + 1; k < n; k++) {
|
|
228
|
+
const factor = aug[k][i] / aug[i][i];
|
|
229
|
+
for (let j = i; j < n + 1; j++) {
|
|
230
|
+
aug[k][j] -= factor * aug[i][j];
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const x = new Array(n);
|
|
236
|
+
for (let i = n - 1; i >= 0; i--) {
|
|
237
|
+
x[i] = aug[i][n];
|
|
238
|
+
for (let j = i + 1; j < n; j++) {
|
|
239
|
+
x[i] -= aug[i][j] * x[j];
|
|
240
|
+
}
|
|
241
|
+
x[i] /= aug[i][i];
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
return x.map(xi => [xi]);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function matrixInverse(A) {
|
|
248
|
+
const n = A.length;
|
|
249
|
+
const aug = A.map((row, i) => {
|
|
250
|
+
const augRow = [...row];
|
|
251
|
+
for (let j = 0; j < n; j++) {
|
|
252
|
+
augRow.push(i === j ? 1 : 0);
|
|
253
|
+
}
|
|
254
|
+
return augRow;
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
for (let i = 0; i < n; i++) {
|
|
258
|
+
let maxRow = i;
|
|
259
|
+
for (let k = i + 1; k < n; k++) {
|
|
260
|
+
if (Math.abs(aug[k][i]) > Math.abs(aug[maxRow][i])) {
|
|
261
|
+
maxRow = k;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
[aug[i], aug[maxRow]] = [aug[maxRow], aug[i]];
|
|
266
|
+
|
|
267
|
+
const pivot = aug[i][i];
|
|
268
|
+
for (let j = 0; j < 2 * n; j++) {
|
|
269
|
+
aug[i][j] /= pivot;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
for (let k = 0; k < n; k++) {
|
|
273
|
+
if (k !== i) {
|
|
274
|
+
const factor = aug[k][i];
|
|
275
|
+
for (let j = 0; j < 2 * n; j++) {
|
|
276
|
+
aug[k][j] -= factor * aug[i][j];
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
const result = new Array(n);
|
|
283
|
+
for (let i = 0; i < n; i++) {
|
|
284
|
+
result[i] = aug[i].slice(n);
|
|
285
|
+
}
|
|
286
|
+
return result;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
function invNormal(p) {
|
|
290
|
+
if (p <= 0.5) {
|
|
291
|
+
const t = Math.sqrt(-2 * Math.log(p));
|
|
292
|
+
const c0 = 2.515517;
|
|
293
|
+
const c1 = 0.802853;
|
|
294
|
+
const c2 = 0.010328;
|
|
295
|
+
const d1 = 1.432788;
|
|
296
|
+
const d2 = 0.189269;
|
|
297
|
+
const d3 = 0.001308;
|
|
298
|
+
return -(t - (c0 + c1 * t + c2 * t * t) / (1 + d1 * t + d2 * t * t + d3 * t * t * t));
|
|
299
|
+
} else {
|
|
300
|
+
const t = Math.sqrt(-2 * Math.log(1 - p));
|
|
301
|
+
const c0 = 2.515517;
|
|
302
|
+
const c1 = 0.802853;
|
|
303
|
+
const c2 = 0.010328;
|
|
304
|
+
const d1 = 1.432788;
|
|
305
|
+
const d2 = 0.189269;
|
|
306
|
+
const d3 = 0.001308;
|
|
307
|
+
return t - (c0 + c1 * t + c2 * t * t) / (1 + d1 * t + d2 * t * t + d3 * t * t * t);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
export class LogisticRegression {
|
|
2
|
+
constructor(X, y, learningRate = 0.01, iterations = 1000) {
|
|
3
|
+
if (!Array.isArray(X) || !Array.isArray(y)) {
|
|
4
|
+
throw new TypeError('X and y must be arrays');
|
|
5
|
+
}
|
|
6
|
+
if (X.length !== y.length || X.length < 2) {
|
|
7
|
+
throw new RangeError('X and y must have same length >= 2');
|
|
8
|
+
}
|
|
9
|
+
if (!Array.isArray(X[0])) {
|
|
10
|
+
throw new TypeError('X must be array of arrays (features)');
|
|
11
|
+
}
|
|
12
|
+
if (y.some(val => val !== 0 && val !== 1)) {
|
|
13
|
+
throw new RangeError('y must contain only 0 and 1 (binary classification)');
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const n = X.length;
|
|
17
|
+
const p = X[0].length;
|
|
18
|
+
|
|
19
|
+
this.coefficients = new Array(p + 1).fill(0);
|
|
20
|
+
this.learningRate = learningRate;
|
|
21
|
+
this.iterations = iterations;
|
|
22
|
+
|
|
23
|
+
for (let iter = 0; iter < iterations; iter++) {
|
|
24
|
+
const gradients = new Array(p + 1).fill(0);
|
|
25
|
+
|
|
26
|
+
for (let i = 0; i < n; i++) {
|
|
27
|
+
let z = this.coefficients[0];
|
|
28
|
+
for (let j = 0; j < p; j++) {
|
|
29
|
+
z += this.coefficients[j + 1] * X[i][j];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const sigmoid = 1 / (1 + Math.exp(-z));
|
|
33
|
+
const error = sigmoid - y[i];
|
|
34
|
+
|
|
35
|
+
gradients[0] += error;
|
|
36
|
+
for (let j = 0; j < p; j++) {
|
|
37
|
+
gradients[j + 1] += error * X[i][j];
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
for (let j = 0; j < p + 1; j++) {
|
|
42
|
+
this.coefficients[j] -= (learningRate / n) * gradients[j];
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
this.intercept = this.coefficients[0];
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
predict(X) {
|
|
50
|
+
if (typeof X[0] === 'number') {
|
|
51
|
+
let z = this.intercept;
|
|
52
|
+
for (let i = 0; i < X.length; i++) {
|
|
53
|
+
z += this.coefficients[i + 1] * X[i];
|
|
54
|
+
}
|
|
55
|
+
return 1 / (1 + Math.exp(-z));
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return X.map(row => {
|
|
59
|
+
let z = this.intercept;
|
|
60
|
+
for (let i = 0; i < row.length; i++) {
|
|
61
|
+
z += this.coefficients[i + 1] * row[i];
|
|
62
|
+
}
|
|
63
|
+
return 1 / (1 + Math.exp(-z));
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
predictClass(X, threshold = 0.5) {
|
|
68
|
+
const probs = this.predict(X);
|
|
69
|
+
if (Array.isArray(probs)) {
|
|
70
|
+
return probs.map(p => p >= threshold ? 1 : 0);
|
|
71
|
+
}
|
|
72
|
+
return probs >= threshold ? 1 : 0;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
evaluate(X, y) {
|
|
76
|
+
if (X.length !== y.length) {
|
|
77
|
+
throw new RangeError('X and y must have same length');
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const predictions = this.predictClass(X);
|
|
81
|
+
let correct = 0;
|
|
82
|
+
|
|
83
|
+
for (let i = 0; i < y.length; i++) {
|
|
84
|
+
if (predictions[i] === y[i]) {
|
|
85
|
+
correct++;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return correct / y.length;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
export class PolynomialRegression {
|
|
2
|
+
constructor(x, y, degree = 2) {
|
|
3
|
+
if (!Array.isArray(x) || !Array.isArray(y)) {
|
|
4
|
+
throw new TypeError('x and y must be arrays');
|
|
5
|
+
}
|
|
6
|
+
if (x.length !== y.length || x.length < 2) {
|
|
7
|
+
throw new RangeError('x and y must have same length >= 2');
|
|
8
|
+
}
|
|
9
|
+
if (typeof degree !== 'number' || degree < 1) {
|
|
10
|
+
throw new RangeError('degree must be positive integer');
|
|
11
|
+
}
|
|
12
|
+
if (x.length <= degree) {
|
|
13
|
+
throw new RangeError('need at least degree+1 data points');
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const n = x.length;
|
|
17
|
+
this.degree = degree;
|
|
18
|
+
|
|
19
|
+
const X = x.map(xi => {
|
|
20
|
+
const row = [1];
|
|
21
|
+
for (let d = 1; d <= degree; d++) {
|
|
22
|
+
row.push(Math.pow(xi, d));
|
|
23
|
+
}
|
|
24
|
+
return row;
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
const XtX = matmul(transpose(X), X);
|
|
28
|
+
const Xty = matmul(transpose(X), y.map(yi => [yi]));
|
|
29
|
+
|
|
30
|
+
this.coefficients = solve(XtX, Xty).flat();
|
|
31
|
+
|
|
32
|
+
const yPred = x.map(xi => {
|
|
33
|
+
let pred = this.coefficients[0];
|
|
34
|
+
for (let d = 1; d <= degree; d++) {
|
|
35
|
+
pred += this.coefficients[d] * Math.pow(xi, d);
|
|
36
|
+
}
|
|
37
|
+
return pred;
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
const meanY = y.reduce((a, b) => a + b, 0) / n;
|
|
41
|
+
let ssRes = 0, ssTot = 0;
|
|
42
|
+
|
|
43
|
+
for (let i = 0; i < n; i++) {
|
|
44
|
+
ssRes += (y[i] - yPred[i]) ** 2;
|
|
45
|
+
ssTot += (y[i] - meanY) ** 2;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
this.rSquared = 1 - ssRes / ssTot;
|
|
49
|
+
this.adjRSquared = 1 - (1 - this.rSquared) * (n - 1) / (n - degree - 1);
|
|
50
|
+
this.rmse = Math.sqrt(ssRes / n);
|
|
51
|
+
this.predictions = yPred;
|
|
52
|
+
this.residuals = y.map((yi, i) => yi - yPred[i]);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
predict(x) {
|
|
56
|
+
if (typeof x === 'number') {
|
|
57
|
+
let pred = this.coefficients[0];
|
|
58
|
+
for (let d = 1; d <= this.degree; d++) {
|
|
59
|
+
pred += this.coefficients[d] * Math.pow(x, d);
|
|
60
|
+
}
|
|
61
|
+
return pred;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (Array.isArray(x)) {
|
|
65
|
+
return x.map(xi => {
|
|
66
|
+
let pred = this.coefficients[0];
|
|
67
|
+
for (let d = 1; d <= this.degree; d++) {
|
|
68
|
+
pred += this.coefficients[d] * Math.pow(xi, d);
|
|
69
|
+
}
|
|
70
|
+
return pred;
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
throw new TypeError('x must be number or array');
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function transpose(matrix) {
|
|
79
|
+
const result = [];
|
|
80
|
+
for (let j = 0; j < matrix[0].length; j++) {
|
|
81
|
+
result[j] = [];
|
|
82
|
+
for (let i = 0; i < matrix.length; i++) {
|
|
83
|
+
result[j].push(matrix[i][j]);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return result;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function matmul(A, B) {
|
|
90
|
+
const result = [];
|
|
91
|
+
for (let i = 0; i < A.length; i++) {
|
|
92
|
+
result[i] = [];
|
|
93
|
+
for (let j = 0; j < B[0].length; j++) {
|
|
94
|
+
let sum = 0;
|
|
95
|
+
for (let k = 0; k < A[0].length; k++) {
|
|
96
|
+
sum += A[i][k] * B[k][j];
|
|
97
|
+
}
|
|
98
|
+
result[i][j] = sum;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return result;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function solve(A, b) {
|
|
105
|
+
const n = A.length;
|
|
106
|
+
const aug = A.map((row, i) => [...row, b[i][0]]);
|
|
107
|
+
|
|
108
|
+
for (let i = 0; i < n; i++) {
|
|
109
|
+
let maxRow = i;
|
|
110
|
+
for (let k = i + 1; k < n; k++) {
|
|
111
|
+
if (Math.abs(aug[k][i]) > Math.abs(aug[maxRow][i])) {
|
|
112
|
+
maxRow = k;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
[aug[i], aug[maxRow]] = [aug[maxRow], aug[i]];
|
|
117
|
+
|
|
118
|
+
for (let k = i + 1; k < n; k++) {
|
|
119
|
+
const factor = aug[k][i] / aug[i][i];
|
|
120
|
+
for (let j = i; j < n + 1; j++) {
|
|
121
|
+
aug[k][j] -= factor * aug[i][j];
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const x = new Array(n);
|
|
127
|
+
for (let i = n - 1; i >= 0; i--) {
|
|
128
|
+
x[i] = aug[i][n];
|
|
129
|
+
for (let j = i + 1; j < n; j++) {
|
|
130
|
+
x[i] -= aug[i][j] * x[j];
|
|
131
|
+
}
|
|
132
|
+
x[i] /= aug[i][i];
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return x.map(xi => [xi]);
|
|
136
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
export function tTestPValue(t, df, twoTailed = true) {
|
|
2
|
+
if (typeof t !== 'number' || typeof df !== 'number') {
|
|
3
|
+
throw new TypeError('t and df must be numbers');
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
const tAbs = Math.abs(t);
|
|
7
|
+
let p = 2 * (1 - tCDF(tAbs, df));
|
|
8
|
+
|
|
9
|
+
return twoTailed ? p : p / 2;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function zTestPValue(z, twoTailed = true) {
|
|
13
|
+
if (typeof z !== 'number') {
|
|
14
|
+
throw new TypeError('z must be number');
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const zAbs = Math.abs(z);
|
|
18
|
+
const p = 2 * (1 - normalCDF(zAbs));
|
|
19
|
+
|
|
20
|
+
return twoTailed ? p : p / 2;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function fTestPValue(f, df1, df2) {
|
|
24
|
+
if (typeof f !== 'number' || typeof df1 !== 'number' || typeof df2 !== 'number') {
|
|
25
|
+
throw new TypeError('f, df1, df2 must be numbers');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (f < 0) return 1;
|
|
29
|
+
|
|
30
|
+
const alpha = df2 / (df1 * f + df2);
|
|
31
|
+
return 1 - incompleteBeta(alpha, df2 / 2, df1 / 2);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function chi2PValue(chi2, df) {
|
|
35
|
+
if (typeof chi2 !== 'number' || typeof df !== 'number') {
|
|
36
|
+
throw new TypeError('chi2 and df must be numbers');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (chi2 < 0) return 1;
|
|
40
|
+
|
|
41
|
+
return 1 - incompleteBeta(chi2 / (chi2 + 1), 1, df / 2);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function uTestPValue(u, n1, n2) {
|
|
45
|
+
if (typeof u !== 'number' || typeof n1 !== 'number' || typeof n2 !== 'number') {
|
|
46
|
+
throw new TypeError('u, n1, n2 must be numbers');
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const mean = (n1 * n2) / 2;
|
|
50
|
+
const std = Math.sqrt((n1 * n2 * (n1 + n2 + 1)) / 12);
|
|
51
|
+
const z = Math.abs((u - mean) / std);
|
|
52
|
+
|
|
53
|
+
return 2 * (1 - normalCDF(z));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function pearsonPValue(r, n) {
|
|
57
|
+
if (typeof r !== 'number' || typeof n !== 'number') {
|
|
58
|
+
throw new TypeError('r and n must be numbers');
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (Math.abs(r) >= 1) return 0;
|
|
62
|
+
|
|
63
|
+
const t = r * Math.sqrt((n - 2) / (1 - r * r));
|
|
64
|
+
const df = n - 2;
|
|
65
|
+
|
|
66
|
+
return tTestPValue(t, df, true);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function normalCDF(z) {
|
|
70
|
+
return 0.5 * (1 + erf(z / Math.sqrt(2)));
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function tCDF(t, df) {
|
|
74
|
+
const x = df / (df + t * t);
|
|
75
|
+
return 1 - incompleteBeta(x, df / 2, 0.5);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function erf(x) {
|
|
79
|
+
const a1 = 0.254829592;
|
|
80
|
+
const a2 = -0.284496736;
|
|
81
|
+
const a3 = 1.421413741;
|
|
82
|
+
const a4 = -1.453152027;
|
|
83
|
+
const a5 = 1.061405429;
|
|
84
|
+
const p = 0.3275911;
|
|
85
|
+
|
|
86
|
+
const sign = x < 0 ? -1 : 1;
|
|
87
|
+
const absX = Math.abs(x);
|
|
88
|
+
const t = 1 / (1 + p * absX);
|
|
89
|
+
const t2 = t * t;
|
|
90
|
+
const t3 = t2 * t;
|
|
91
|
+
const t4 = t3 * t;
|
|
92
|
+
const t5 = t4 * t;
|
|
93
|
+
|
|
94
|
+
return sign * (1 - (a1 * t + a2 * t2 + a3 * t3 + a4 * t4 + a5 * t5) * Math.exp(-absX * absX));
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function incompleteBeta(x, a, b, maxIter = 100) {
|
|
98
|
+
if (x <= 0) return 0;
|
|
99
|
+
if (x >= 1) return 1;
|
|
100
|
+
|
|
101
|
+
let sum = 1;
|
|
102
|
+
let term = 1;
|
|
103
|
+
|
|
104
|
+
for (let i = 1; i <= maxIter; i++) {
|
|
105
|
+
term *= (a + b - 1) * x / ((a + i - 1) * (1 - x / (a + i)));
|
|
106
|
+
const nextSum = sum + term;
|
|
107
|
+
|
|
108
|
+
if (Math.abs(nextSum - sum) < 1e-14) {
|
|
109
|
+
break;
|
|
110
|
+
}
|
|
111
|
+
sum = nextSum;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return sum * Math.exp(a * Math.log(x) + b * Math.log(1 - x) - logBeta(a, b)) / a;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function logBeta(a, b) {
|
|
118
|
+
return logGamma(a) + logGamma(b) - logGamma(a + b);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function logGamma(x) {
|
|
122
|
+
const coefficients = [
|
|
123
|
+
676.5203681218851,
|
|
124
|
+
-1259.1392167224028,
|
|
125
|
+
771.32342877765313,
|
|
126
|
+
-176.61502916214059,
|
|
127
|
+
12.507343278686905,
|
|
128
|
+
-0.13857109526572012,
|
|
129
|
+
9.9843695780195716e-6,
|
|
130
|
+
1.5056327351493116e-7
|
|
131
|
+
];
|
|
132
|
+
|
|
133
|
+
const g = 7;
|
|
134
|
+
const z = x - 1;
|
|
135
|
+
let sum = 0.99999999999980993;
|
|
136
|
+
|
|
137
|
+
for (let i = 0; i < coefficients.length; i++) {
|
|
138
|
+
sum += coefficients[i] / (z + i + 1);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return 0.5 * Math.log(2 * Math.PI) + (z + 0.5) * Math.log(z + g + 0.5) - (z + g + 0.5) + Math.log(sum);
|
|
142
|
+
}
|