@compstats/core 0.2.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 +110 -0
- package/LICENSE +21 -0
- package/README.md +310 -0
- package/dist/3d.d.ts +33 -0
- package/dist/3d.d.ts.map +1 -0
- package/dist/3d.js +1984 -0
- package/dist/3d.js.map +22 -0
- package/dist/core/arith.d.ts +133 -0
- package/dist/core/arith.d.ts.map +1 -0
- package/dist/core/frame.d.ts +98 -0
- package/dist/core/frame.d.ts.map +1 -0
- package/dist/core/histogram.d.ts +68 -0
- package/dist/core/histogram.d.ts.map +1 -0
- package/dist/core/kde.d.ts +106 -0
- package/dist/core/kde.d.ts.map +1 -0
- package/dist/core/logit.d.ts +109 -0
- package/dist/core/logit.d.ts.map +1 -0
- package/dist/core/matrix.d.ts +135 -0
- package/dist/core/matrix.d.ts.map +1 -0
- package/dist/core/moderation.d.ts +127 -0
- package/dist/core/moderation.d.ts.map +1 -0
- package/dist/core/ols.d.ts +76 -0
- package/dist/core/ols.d.ts.map +1 -0
- package/dist/core/pca.d.ts +99 -0
- package/dist/core/pca.d.ts.map +1 -0
- package/dist/core/precision.d.ts +10 -0
- package/dist/core/precision.d.ts.map +1 -0
- package/dist/core/pretty.d.ts +51 -0
- package/dist/core/pretty.d.ts.map +1 -0
- package/dist/core/regression.d.ts +68 -0
- package/dist/core/regression.d.ts.map +1 -0
- package/dist/core/rng.d.ts +206 -0
- package/dist/core/rng.d.ts.map +1 -0
- package/dist/core/sampling.d.ts +157 -0
- package/dist/core/sampling.d.ts.map +1 -0
- package/dist/core/special.d.ts +92 -0
- package/dist/core/special.d.ts.map +1 -0
- package/dist/core/tdist.d.ts +53 -0
- package/dist/core/tdist.d.ts.map +1 -0
- package/dist/core/ttest.d.ts +146 -0
- package/dist/core/ttest.d.ts.map +1 -0
- package/dist/data/moderationData.d.ts +40 -0
- package/dist/data/moderationData.d.ts.map +1 -0
- package/dist/data/pcaDegenerate.d.ts +18 -0
- package/dist/data/pcaDegenerate.d.ts.map +1 -0
- package/dist/index.d.ts +66 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4195 -0
- package/dist/index.js.map +47 -0
- package/dist/interactive/controls.d.ts +97 -0
- package/dist/interactive/controls.d.ts.map +1 -0
- package/dist/interactive/logit.d.ts +57 -0
- package/dist/interactive/logit.d.ts.map +1 -0
- package/dist/interactive/matrixInverse.d.ts +86 -0
- package/dist/interactive/matrixInverse.d.ts.map +1 -0
- package/dist/interactive/moderation3d.d.ts +103 -0
- package/dist/interactive/moderation3d.d.ts.map +1 -0
- package/dist/interactive/pca.d.ts +90 -0
- package/dist/interactive/pca.d.ts.map +1 -0
- package/dist/interactive/regression.d.ts +57 -0
- package/dist/interactive/regression.d.ts.map +1 -0
- package/dist/interactive/sampling.d.ts +78 -0
- package/dist/interactive/sampling.d.ts.map +1 -0
- package/dist/interactive/scatter3d.d.ts +124 -0
- package/dist/interactive/scatter3d.d.ts.map +1 -0
- package/dist/interactive/tTest.d.ts +69 -0
- package/dist/interactive/tTest.d.ts.map +1 -0
- package/dist/interactive/target.d.ts +132 -0
- package/dist/interactive/target.d.ts.map +1 -0
- package/dist/plot/axes.d.ts +128 -0
- package/dist/plot/axes.d.ts.map +1 -0
- package/dist/plot/draw.d.ts +46 -0
- package/dist/plot/draw.d.ts.map +1 -0
- package/dist/plot/format.d.ts +33 -0
- package/dist/plot/format.d.ts.map +1 -0
- package/dist/plot/logit.d.ts +68 -0
- package/dist/plot/logit.d.ts.map +1 -0
- package/dist/plot/matrixInverse.d.ts +62 -0
- package/dist/plot/matrixInverse.d.ts.map +1 -0
- package/dist/plot/moderation3d.d.ts +135 -0
- package/dist/plot/moderation3d.d.ts.map +1 -0
- package/dist/plot/pca.d.ts +79 -0
- package/dist/plot/pca.d.ts.map +1 -0
- package/dist/plot/plotly.d.ts +209 -0
- package/dist/plot/plotly.d.ts.map +1 -0
- package/dist/plot/regression.d.ts +52 -0
- package/dist/plot/regression.d.ts.map +1 -0
- package/dist/plot/sampleCi.d.ts +82 -0
- package/dist/plot/sampleCi.d.ts.map +1 -0
- package/dist/plot/sampling.d.ts +159 -0
- package/dist/plot/sampling.d.ts.map +1 -0
- package/dist/plot/scatter3d.d.ts +164 -0
- package/dist/plot/scatter3d.d.ts.map +1 -0
- package/dist/plot/tTest.d.ts +49 -0
- package/dist/plot/tTest.d.ts.map +1 -0
- package/dist/plot/target.d.ts +55 -0
- package/dist/plot/target.d.ts.map +1 -0
- package/package.json +71 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,4195 @@
|
|
|
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/precision.ts
|
|
10
|
+
function machinePrecision() {
|
|
11
|
+
return Number.EPSILON;
|
|
12
|
+
}
|
|
13
|
+
// src/core/arith.ts
|
|
14
|
+
function sum(values) {
|
|
15
|
+
return values.reduce((total, value) => total + value, 0);
|
|
16
|
+
}
|
|
17
|
+
function mean(values) {
|
|
18
|
+
return sum(values) / values.length;
|
|
19
|
+
}
|
|
20
|
+
function extent(values) {
|
|
21
|
+
return values.reduce(([low, high], value) => [
|
|
22
|
+
value < low ? value : low,
|
|
23
|
+
value > high ? value : high
|
|
24
|
+
], [Number.POSITIVE_INFINITY, Number.NEGATIVE_INFINITY]);
|
|
25
|
+
}
|
|
26
|
+
function withoutNegativeZero(value) {
|
|
27
|
+
return value === 0 ? 0 : value;
|
|
28
|
+
}
|
|
29
|
+
function requireCount(value, name) {
|
|
30
|
+
if (!Number.isInteger(value) || value < 0) {
|
|
31
|
+
throw new RangeError(`${name} must be a non-negative integer, got ${value}`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
function zipWith(as, bs, combine) {
|
|
35
|
+
const length = Math.min(as.length, bs.length);
|
|
36
|
+
return as.slice(0, length).map((a, index) => combine(a, bs[index]));
|
|
37
|
+
}
|
|
38
|
+
function sd(values) {
|
|
39
|
+
if (values.length < 2) {
|
|
40
|
+
return Number.NaN;
|
|
41
|
+
}
|
|
42
|
+
const center = mean(values);
|
|
43
|
+
const squares = values.map((value) => (value - center) * (value - center));
|
|
44
|
+
return Math.sqrt(sum(squares) / (values.length - 1));
|
|
45
|
+
}
|
|
46
|
+
function meanAbsoluteDeviation(values) {
|
|
47
|
+
const center = mean(values);
|
|
48
|
+
return mean(values.map((value) => Math.abs(value - center)));
|
|
49
|
+
}
|
|
50
|
+
function fusedMultiplyAdd(a, b, c) {
|
|
51
|
+
const [product, productError] = twoProduct(a, b);
|
|
52
|
+
const [sum2, sumError] = twoSum(c, product);
|
|
53
|
+
const rounded = sum2 + (sumError + productError);
|
|
54
|
+
return Number.isFinite(rounded) ? rounded : a * b + c;
|
|
55
|
+
}
|
|
56
|
+
function split(value) {
|
|
57
|
+
const scaled = 134217729 * value;
|
|
58
|
+
const high = scaled - (scaled - value);
|
|
59
|
+
return [high, value - high];
|
|
60
|
+
}
|
|
61
|
+
function twoProduct(a, b) {
|
|
62
|
+
const product = a * b;
|
|
63
|
+
const [aHigh, aLow] = split(a);
|
|
64
|
+
const [bHigh, bLow] = split(b);
|
|
65
|
+
const error = aLow * bLow - (product - aHigh * bHigh - aLow * bHigh - aHigh * bLow);
|
|
66
|
+
return [product, error];
|
|
67
|
+
}
|
|
68
|
+
function twoSum(a, b) {
|
|
69
|
+
const sum2 = a + b;
|
|
70
|
+
const carried = sum2 - a;
|
|
71
|
+
return [sum2, a - (sum2 - carried) + (b - carried)];
|
|
72
|
+
}
|
|
73
|
+
function quantile(values, p) {
|
|
74
|
+
return quantiles(values, [p])[0];
|
|
75
|
+
}
|
|
76
|
+
function quantiles(values, probs) {
|
|
77
|
+
if (probs.some((p) => !(p >= 0 && p <= 1))) {
|
|
78
|
+
throw new RangeError(`every probability must be in [0, 1], got ${probs}`);
|
|
79
|
+
}
|
|
80
|
+
if (values.length === 0) {
|
|
81
|
+
return probs.map(() => Number.NaN);
|
|
82
|
+
}
|
|
83
|
+
const sorted = Float64Array.from(values).sort();
|
|
84
|
+
return probs.map((p) => type7(sorted, p));
|
|
85
|
+
}
|
|
86
|
+
function median(values) {
|
|
87
|
+
return quantile(values, 0.5);
|
|
88
|
+
}
|
|
89
|
+
function type7(sorted, p) {
|
|
90
|
+
const position = 1 + (sorted.length - 1) * p;
|
|
91
|
+
const below = Math.floor(position);
|
|
92
|
+
const above = Math.ceil(position);
|
|
93
|
+
const low = sorted[below - 1];
|
|
94
|
+
const high = sorted[above - 1];
|
|
95
|
+
if (position > below && high !== low) {
|
|
96
|
+
const h = position - below;
|
|
97
|
+
return (1 - h) * low + h * high;
|
|
98
|
+
}
|
|
99
|
+
return low;
|
|
100
|
+
}
|
|
101
|
+
// src/core/kde.ts
|
|
102
|
+
var GRID_SIZE = 512;
|
|
103
|
+
var FFT_SIZE = 2 * GRID_SIZE;
|
|
104
|
+
var CUT = 3;
|
|
105
|
+
var EXT = 4;
|
|
106
|
+
var INV_SQRT_2PI = 0.3989422804014327;
|
|
107
|
+
function bwNrd0(values) {
|
|
108
|
+
if (values.length < 2) {
|
|
109
|
+
throw new RangeError(`need at least 2 data points, got ${values.length}`);
|
|
110
|
+
}
|
|
111
|
+
const spread = sd(values);
|
|
112
|
+
const [lowerQuartile, upperQuartile] = quantiles(values, [0.25, 0.75]);
|
|
113
|
+
const iqrSpread = (upperQuartile - lowerQuartile) / 1.34;
|
|
114
|
+
const scale = fallbackScale(spread, iqrSpread, values[0]);
|
|
115
|
+
return 0.9 * scale * Math.pow(values.length, -0.2);
|
|
116
|
+
}
|
|
117
|
+
function fallbackScale(spread, iqrSpread, first) {
|
|
118
|
+
const smaller = Math.min(spread, iqrSpread);
|
|
119
|
+
if (smaller !== 0) {
|
|
120
|
+
return smaller;
|
|
121
|
+
}
|
|
122
|
+
if (spread !== 0) {
|
|
123
|
+
return spread;
|
|
124
|
+
}
|
|
125
|
+
return Math.abs(first) !== 0 ? Math.abs(first) : 1;
|
|
126
|
+
}
|
|
127
|
+
function kernelDensity(values, options = {}) {
|
|
128
|
+
const finite = finiteValuesOf(values);
|
|
129
|
+
const bw = options.bw ?? bwNrd0(finite);
|
|
130
|
+
if (!Number.isFinite(bw) || bw <= 0) {
|
|
131
|
+
throw new RangeError(`bw must be positive and finite, got ${bw}`);
|
|
132
|
+
}
|
|
133
|
+
if (finite.length === 0) {
|
|
134
|
+
throw new RangeError("need at least 1 finite value, got none");
|
|
135
|
+
}
|
|
136
|
+
if (options.from !== undefined && !Number.isFinite(options.from)) {
|
|
137
|
+
throw new RangeError(`non-finite 'from': ${options.from}`);
|
|
138
|
+
}
|
|
139
|
+
if (options.to !== undefined && !Number.isFinite(options.to)) {
|
|
140
|
+
throw new RangeError(`non-finite 'to': ${options.to}`);
|
|
141
|
+
}
|
|
142
|
+
const [lowest, highest] = extent(finite);
|
|
143
|
+
const from = options.from ?? lowest - CUT * bw;
|
|
144
|
+
const to = options.to ?? highest + CUT * bw;
|
|
145
|
+
const lo = from - EXT * bw;
|
|
146
|
+
const up = to + EXT * bw;
|
|
147
|
+
const binned = binDist(finite, lo, up, finite.length / values.length);
|
|
148
|
+
const kernel = gaussianKernel(lo, up, bw);
|
|
149
|
+
const density = convolve(binned, kernel);
|
|
150
|
+
const working = gridOf(lo, up);
|
|
151
|
+
const x = gridOf(from, to);
|
|
152
|
+
const y = x.map((point) => interpolate(working, density, point));
|
|
153
|
+
return { x, y, bw, n: values.length };
|
|
154
|
+
}
|
|
155
|
+
function finiteValuesOf(values) {
|
|
156
|
+
if (values.every((value) => Number.isFinite(value))) {
|
|
157
|
+
return values;
|
|
158
|
+
}
|
|
159
|
+
if (values.some((value) => Number.isNaN(value))) {
|
|
160
|
+
throw new RangeError("values contain a missing value");
|
|
161
|
+
}
|
|
162
|
+
return values.filter((value) => Number.isFinite(value));
|
|
163
|
+
}
|
|
164
|
+
function binDist(values, lo, up, keptShare) {
|
|
165
|
+
const bins = new Float64Array(FFT_SIZE);
|
|
166
|
+
const step = (up - lo) / (GRID_SIZE - 1);
|
|
167
|
+
const weight = keptShare / values.length;
|
|
168
|
+
const lastPair = GRID_SIZE - 2;
|
|
169
|
+
for (let i = 0;i < values.length; i += 1) {
|
|
170
|
+
const position = (values[i] - lo) / step;
|
|
171
|
+
const behind = Math.floor(position);
|
|
172
|
+
const ahead = position - behind;
|
|
173
|
+
if (behind >= 0 && behind <= lastPair) {
|
|
174
|
+
bins[behind] += (1 - ahead) * weight;
|
|
175
|
+
bins[behind + 1] += ahead * weight;
|
|
176
|
+
} else if (behind === -1) {
|
|
177
|
+
bins[0] += ahead * weight;
|
|
178
|
+
} else if (behind === lastPair + 1) {
|
|
179
|
+
bins[behind] += (1 - ahead) * weight;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
return bins;
|
|
183
|
+
}
|
|
184
|
+
function gaussianKernel(lo, up, bw) {
|
|
185
|
+
const span = (FFT_SIZE - 1) / (GRID_SIZE - 1) * (up - lo);
|
|
186
|
+
const step = span / (FFT_SIZE - 1);
|
|
187
|
+
const lags = Array.from({ length: FFT_SIZE }, (_, index) => index * step);
|
|
188
|
+
const folded = lags.map((lag, index) => index > GRID_SIZE ? -lags[FFT_SIZE - index] : lag);
|
|
189
|
+
return Float64Array.from(folded, (lag) => dnorm(lag, bw));
|
|
190
|
+
}
|
|
191
|
+
function dnorm(at, sd2) {
|
|
192
|
+
const z = Math.abs(at) / sd2;
|
|
193
|
+
if (z < 5) {
|
|
194
|
+
return INV_SQRT_2PI * Math.exp(-0.5 * z * z) / sd2;
|
|
195
|
+
}
|
|
196
|
+
if (z > Math.sqrt(-2 * Math.LN2 * (-1021 + 1 - 53))) {
|
|
197
|
+
return 0;
|
|
198
|
+
}
|
|
199
|
+
const head = Math.round(z * 65536) / 65536;
|
|
200
|
+
const tail = z - head;
|
|
201
|
+
return INV_SQRT_2PI / sd2 * (Math.exp(-0.5 * head * head) * Math.exp((-0.5 * tail - head) * tail));
|
|
202
|
+
}
|
|
203
|
+
function convolve(binned, kernel) {
|
|
204
|
+
const massReal = Float64Array.from(binned);
|
|
205
|
+
const massImaginary = new Float64Array(FFT_SIZE);
|
|
206
|
+
const kernelReal = Float64Array.from(kernel);
|
|
207
|
+
const kernelImaginary = new Float64Array(FFT_SIZE);
|
|
208
|
+
fftInPlace(massReal, massImaginary, false);
|
|
209
|
+
fftInPlace(kernelReal, kernelImaginary, false);
|
|
210
|
+
for (let i = 0;i < FFT_SIZE; i += 1) {
|
|
211
|
+
const real = massReal[i] * kernelReal[i] + massImaginary[i] * kernelImaginary[i];
|
|
212
|
+
const imaginary = massImaginary[i] * kernelReal[i] - massReal[i] * kernelImaginary[i];
|
|
213
|
+
massReal[i] = real;
|
|
214
|
+
massImaginary[i] = imaginary;
|
|
215
|
+
}
|
|
216
|
+
fftInPlace(massReal, massImaginary, true);
|
|
217
|
+
return Float64Array.from(massReal.subarray(0, GRID_SIZE), (value) => Math.max(0, value / FFT_SIZE));
|
|
218
|
+
}
|
|
219
|
+
function fftInPlace(real, imaginary, inverse) {
|
|
220
|
+
const size = real.length;
|
|
221
|
+
const half = size >> 1;
|
|
222
|
+
for (let i = 1, j = 0;i < size; i += 1) {
|
|
223
|
+
let bit = half;
|
|
224
|
+
for (;(j & bit) !== 0; bit >>= 1) {
|
|
225
|
+
j ^= bit;
|
|
226
|
+
}
|
|
227
|
+
j ^= bit;
|
|
228
|
+
if (i < j) {
|
|
229
|
+
const swappedReal = real[i];
|
|
230
|
+
const swappedImaginary = imaginary[i];
|
|
231
|
+
real[i] = real[j];
|
|
232
|
+
imaginary[i] = imaginary[j];
|
|
233
|
+
real[j] = swappedReal;
|
|
234
|
+
imaginary[j] = swappedImaginary;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
const direction = inverse ? 1 : -1;
|
|
238
|
+
const cosines = new Float64Array(half);
|
|
239
|
+
const sines = new Float64Array(half);
|
|
240
|
+
for (let k = 0;k < half; k += 1) {
|
|
241
|
+
const angle = direction * 2 * Math.PI * k / size;
|
|
242
|
+
cosines[k] = Math.cos(angle);
|
|
243
|
+
sines[k] = Math.sin(angle);
|
|
244
|
+
}
|
|
245
|
+
for (let span = 1;span < size; span <<= 1) {
|
|
246
|
+
const stride = half / span;
|
|
247
|
+
for (let start = 0;start < size; start += span << 1) {
|
|
248
|
+
for (let k = 0;k < span; k += 1) {
|
|
249
|
+
const even = start + k;
|
|
250
|
+
const odd = even + span;
|
|
251
|
+
const cosine = cosines[k * stride];
|
|
252
|
+
const sine = sines[k * stride];
|
|
253
|
+
const oddReal = real[odd] * cosine - imaginary[odd] * sine;
|
|
254
|
+
const oddImaginary = real[odd] * sine + imaginary[odd] * cosine;
|
|
255
|
+
real[odd] = real[even] - oddReal;
|
|
256
|
+
imaginary[odd] = imaginary[even] - oddImaginary;
|
|
257
|
+
real[even] = real[even] + oddReal;
|
|
258
|
+
imaginary[even] = imaginary[even] + oddImaginary;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
function gridOf(from, to) {
|
|
264
|
+
const step = (to - from) / (GRID_SIZE - 1);
|
|
265
|
+
const points = Array.from({ length: GRID_SIZE }, (_, index) => from + index * step);
|
|
266
|
+
points[GRID_SIZE - 1] = to;
|
|
267
|
+
return points;
|
|
268
|
+
}
|
|
269
|
+
function interpolate(grid, values, at) {
|
|
270
|
+
let low = 0;
|
|
271
|
+
let high = grid.length - 1;
|
|
272
|
+
if (at < grid[low]) {
|
|
273
|
+
return values[low];
|
|
274
|
+
}
|
|
275
|
+
if (at > grid[high]) {
|
|
276
|
+
return values[high];
|
|
277
|
+
}
|
|
278
|
+
while (low < high - 1) {
|
|
279
|
+
const middle = low + high >> 1;
|
|
280
|
+
if (at < grid[middle]) {
|
|
281
|
+
high = middle;
|
|
282
|
+
} else {
|
|
283
|
+
low = middle;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
if (at === grid[high]) {
|
|
287
|
+
return values[high];
|
|
288
|
+
}
|
|
289
|
+
if (at === grid[low]) {
|
|
290
|
+
return values[low];
|
|
291
|
+
}
|
|
292
|
+
const lowValue = values[low];
|
|
293
|
+
const highValue = values[high];
|
|
294
|
+
const lowPoint = grid[low];
|
|
295
|
+
const highPoint = grid[high];
|
|
296
|
+
return lowValue + (highValue - lowValue) * ((at - lowPoint) / (highPoint - lowPoint));
|
|
297
|
+
}
|
|
298
|
+
// src/core/pretty.ts
|
|
299
|
+
var ROUNDING_EPS = 0.0000000001;
|
|
300
|
+
var SHRINK_SMALL = 0.75;
|
|
301
|
+
var HIGH_U_BIAS = 1.5;
|
|
302
|
+
var U5_BIAS = 0.5 + 1.5 * HIGH_U_BIAS;
|
|
303
|
+
function rPretty(lo, up, options = {}) {
|
|
304
|
+
const { n = 5, minN = Math.floor(n / 3) } = options;
|
|
305
|
+
if (!Number.isFinite(lo) || !Number.isFinite(up)) {
|
|
306
|
+
throw new RangeError(`lo and up must be finite, got ${lo} and ${up}`);
|
|
307
|
+
}
|
|
308
|
+
if (up < lo) {
|
|
309
|
+
throw new RangeError(`up must not be below lo, got ${lo} and ${up}`);
|
|
310
|
+
}
|
|
311
|
+
requireCount(n, "n");
|
|
312
|
+
requireCount(minN, "minN");
|
|
313
|
+
if (minN > n) {
|
|
314
|
+
throw new RangeError(`minN must not be above n, got ${minN} and ${n}`);
|
|
315
|
+
}
|
|
316
|
+
const { low, high, cells } = prettyBounds(lo, up, n, minN);
|
|
317
|
+
if (cells === 0) {
|
|
318
|
+
return [low];
|
|
319
|
+
}
|
|
320
|
+
const step = (high - low) / cells;
|
|
321
|
+
const edges = Array.from({ length: cells + 1 }, (_, index) => fusedMultiplyAdd(index, step, low));
|
|
322
|
+
edges[cells] = high;
|
|
323
|
+
return edges.map((edge) => Math.abs(edge) < 0.00000000000001 * step ? 0 : edge);
|
|
324
|
+
}
|
|
325
|
+
function prettyBounds(lo, up, n, minN) {
|
|
326
|
+
const unit = cellUnit(lo, up, n, minN);
|
|
327
|
+
let steps = Math.floor(lo / unit + ROUNDING_EPS);
|
|
328
|
+
let stepsUp = Math.ceil(up / unit - ROUNDING_EPS);
|
|
329
|
+
while (steps * unit > lo + ROUNDING_EPS * unit) {
|
|
330
|
+
steps -= 1;
|
|
331
|
+
}
|
|
332
|
+
while (stepsUp * unit < up - ROUNDING_EPS * unit) {
|
|
333
|
+
stepsUp += 1;
|
|
334
|
+
}
|
|
335
|
+
let cells = Math.floor(0.5 + stepsUp - steps);
|
|
336
|
+
if (cells < minN) {
|
|
337
|
+
const missing = minN - cells;
|
|
338
|
+
if (lo === 0 && up > 0) {
|
|
339
|
+
stepsUp += missing;
|
|
340
|
+
} else if (up === 0 && lo < 0) {
|
|
341
|
+
steps -= missing;
|
|
342
|
+
} else if (steps >= 0) {
|
|
343
|
+
stepsUp += Math.floor(missing / 2);
|
|
344
|
+
steps -= Math.floor(missing / 2) + missing % 2;
|
|
345
|
+
} else {
|
|
346
|
+
steps -= Math.floor(missing / 2);
|
|
347
|
+
stepsUp += Math.floor(missing / 2) + missing % 2;
|
|
348
|
+
}
|
|
349
|
+
cells = minN;
|
|
350
|
+
}
|
|
351
|
+
return {
|
|
352
|
+
low: steps * unit < lo ? steps * unit : lo,
|
|
353
|
+
high: stepsUp * unit > up ? stepsUp * unit : up,
|
|
354
|
+
cells
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
function cellUnit(lo, up, n, minN) {
|
|
358
|
+
const width = up - lo;
|
|
359
|
+
let cell;
|
|
360
|
+
let noWidth;
|
|
361
|
+
if (width === 0 && up === 0) {
|
|
362
|
+
cell = 1;
|
|
363
|
+
noWidth = true;
|
|
364
|
+
} else {
|
|
365
|
+
cell = Math.max(Math.abs(lo), Math.abs(up));
|
|
366
|
+
const bound = 1 + (U5_BIAS >= 1.5 * HIGH_U_BIAS + 0.5 ? 1 / (1 + HIGH_U_BIAS) : 1.5 / (1 + U5_BIAS));
|
|
367
|
+
noWidth = width < cell * bound * Math.max(1, n) * Number.EPSILON * 3;
|
|
368
|
+
}
|
|
369
|
+
if (noWidth) {
|
|
370
|
+
if (cell > 10) {
|
|
371
|
+
cell = 9 + cell / 10;
|
|
372
|
+
}
|
|
373
|
+
cell *= SHRINK_SMALL;
|
|
374
|
+
if (minN > 1) {
|
|
375
|
+
cell /= minN;
|
|
376
|
+
}
|
|
377
|
+
} else {
|
|
378
|
+
cell = width;
|
|
379
|
+
if (n > 1) {
|
|
380
|
+
cell /= n;
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
const base = powerOfTen(Math.floor(Math.log10(cell)));
|
|
384
|
+
let unit = base;
|
|
385
|
+
if (2 * base - cell < HIGH_U_BIAS * (cell - unit)) {
|
|
386
|
+
unit = 2 * base;
|
|
387
|
+
if (5 * base - cell < U5_BIAS * (cell - unit)) {
|
|
388
|
+
unit = 5 * base;
|
|
389
|
+
if (10 * base - cell < HIGH_U_BIAS * (cell - unit)) {
|
|
390
|
+
unit = 10 * base;
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
return unit;
|
|
395
|
+
}
|
|
396
|
+
function powerOfTen(exponent) {
|
|
397
|
+
const parsed = Number(`1e${exponent}`);
|
|
398
|
+
return Number.isFinite(parsed) && parsed !== 0 ? parsed : Math.pow(10, exponent);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
// src/core/histogram.ts
|
|
402
|
+
var FUZZ = 0.0000001;
|
|
403
|
+
function nclassSturges(values) {
|
|
404
|
+
return Math.ceil(Math.log2(values.length) + 1);
|
|
405
|
+
}
|
|
406
|
+
function histogram(values, options = {}) {
|
|
407
|
+
const finite = values.filter((value) => Number.isFinite(value));
|
|
408
|
+
if (finite.length === 0) {
|
|
409
|
+
throw new RangeError("need at least 1 finite value, got none");
|
|
410
|
+
}
|
|
411
|
+
const breaks = resolveBreaks(finite, options.breaks);
|
|
412
|
+
const widths = zipWith(breaks.slice(1), breaks.slice(0, -1), (up, low) => up - low);
|
|
413
|
+
const counts = binCount(finite, fuzzyBreaks(breaks, widths, finite));
|
|
414
|
+
if (sum(counts) < finite.length) {
|
|
415
|
+
throw new RangeError("some values were not counted; the breaks may not span their range");
|
|
416
|
+
}
|
|
417
|
+
return {
|
|
418
|
+
breaks,
|
|
419
|
+
counts,
|
|
420
|
+
mids: zipWith(breaks.slice(1), breaks.slice(0, -1), (up, low) => 0.5 * (up + low))
|
|
421
|
+
};
|
|
422
|
+
}
|
|
423
|
+
function resolveBreaks(finite, requested) {
|
|
424
|
+
if (requested !== undefined && typeof requested !== "number") {
|
|
425
|
+
if (requested.length < 2) {
|
|
426
|
+
throw new RangeError(`breaks given as edges need at least 2 of them, got ${requested.length}`);
|
|
427
|
+
}
|
|
428
|
+
return [...requested].sort((a, b) => a - b);
|
|
429
|
+
}
|
|
430
|
+
const cells = requested ?? nclassSturges(finite);
|
|
431
|
+
if (!Number.isInteger(cells) || cells < 1) {
|
|
432
|
+
throw new RangeError(`invalid number of breaks: ${cells}`);
|
|
433
|
+
}
|
|
434
|
+
return rPretty(...extent(finite), { n: cells, minN: 1 });
|
|
435
|
+
}
|
|
436
|
+
function fuzzyBreaks(breaks, widths, finite) {
|
|
437
|
+
const positive = widths.filter((width) => width > 0);
|
|
438
|
+
const [lowest, highest] = extent(finite);
|
|
439
|
+
const scale = breaks.length > 5 ? quantile(widths, 0.5) : breaks.length <= 3 ? highest - lowest : Math.min(...positive);
|
|
440
|
+
const nudge = FUZZ * scale;
|
|
441
|
+
return breaks.map((edge, index) => index === 0 ? edge - nudge : edge + nudge);
|
|
442
|
+
}
|
|
443
|
+
function binCount(values, breaks) {
|
|
444
|
+
const counts = new Array(breaks.length - 1).fill(0);
|
|
445
|
+
const last = breaks.length - 1;
|
|
446
|
+
for (let i = 0;i < values.length; i += 1) {
|
|
447
|
+
const value = values[i];
|
|
448
|
+
if (value < breaks[0] || value > breaks[last]) {
|
|
449
|
+
continue;
|
|
450
|
+
}
|
|
451
|
+
let low = 0;
|
|
452
|
+
let high = last;
|
|
453
|
+
while (high - low >= 2) {
|
|
454
|
+
const middle = high + low >> 1;
|
|
455
|
+
if (value > breaks[middle]) {
|
|
456
|
+
low = middle;
|
|
457
|
+
} else {
|
|
458
|
+
high = middle;
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
counts[low] += 1;
|
|
462
|
+
}
|
|
463
|
+
return counts;
|
|
464
|
+
}
|
|
465
|
+
// src/core/rng.ts
|
|
466
|
+
function seededRng(seed) {
|
|
467
|
+
if (!Number.isFinite(seed)) {
|
|
468
|
+
throw new RangeError(`seed must be finite, got ${seed}`);
|
|
469
|
+
}
|
|
470
|
+
let state = Math.trunc(seed) | 0;
|
|
471
|
+
return () => {
|
|
472
|
+
state = state + 1831565813 | 0;
|
|
473
|
+
let mixed = Math.imul(state ^ state >>> 15, 1 | state);
|
|
474
|
+
mixed = mixed + Math.imul(mixed ^ mixed >>> 7, 61 | mixed) ^ mixed;
|
|
475
|
+
return ((mixed ^ mixed >>> 14) >>> 0) / 4294967296;
|
|
476
|
+
};
|
|
477
|
+
}
|
|
478
|
+
function runif(rng, n, options = {}) {
|
|
479
|
+
requireCount(n, "n");
|
|
480
|
+
const { min = 0, max = 1 } = options;
|
|
481
|
+
if (!Number.isFinite(min) || !Number.isFinite(max) || max < min) {
|
|
482
|
+
return new Array(n).fill(Number.NaN);
|
|
483
|
+
}
|
|
484
|
+
return Array.from({ length: n }, () => min + (max - min) * rng());
|
|
485
|
+
}
|
|
486
|
+
function rnorm(rng, n, options = {}) {
|
|
487
|
+
requireCount(n, "n");
|
|
488
|
+
const { mean: mean2 = 0, sd: sd2 = 1 } = options;
|
|
489
|
+
if (!Number.isFinite(mean2) || !Number.isFinite(sd2) || sd2 < 0) {
|
|
490
|
+
return new Array(n).fill(Number.NaN);
|
|
491
|
+
}
|
|
492
|
+
const pairs = Array.from({ length: Math.ceil(n / 2) }, () => standardNormalPair(rng));
|
|
493
|
+
return pairs.flat().slice(0, n).map((z) => mean2 + sd2 * z);
|
|
494
|
+
}
|
|
495
|
+
function rt(rng, n, df) {
|
|
496
|
+
requireCount(n, "n");
|
|
497
|
+
if (!Number.isInteger(df) || df <= 0) {
|
|
498
|
+
return new Array(n).fill(Number.NaN);
|
|
499
|
+
}
|
|
500
|
+
const z = rnorm(rng, n);
|
|
501
|
+
const components = Array.from({ length: df }, () => rnorm(rng, n));
|
|
502
|
+
return z.map((numerator, index) => {
|
|
503
|
+
const chiSquare = sum(components.map((draws) => draws[index] ** 2));
|
|
504
|
+
return numerator / Math.sqrt(chiSquare / df);
|
|
505
|
+
});
|
|
506
|
+
}
|
|
507
|
+
function rlnorm(rng, n, options = {}) {
|
|
508
|
+
const { meanlog = 0, sdlog = 1 } = options;
|
|
509
|
+
return rnorm(rng, n, { mean: meanlog, sd: sdlog }).map(Math.exp);
|
|
510
|
+
}
|
|
511
|
+
function rcauchy(rng, n, options = {}) {
|
|
512
|
+
requireCount(n, "n");
|
|
513
|
+
const { location = 0, scale = 1 } = options;
|
|
514
|
+
if (!Number.isFinite(location) || !Number.isFinite(scale) || scale < 0) {
|
|
515
|
+
return new Array(n).fill(Number.NaN);
|
|
516
|
+
}
|
|
517
|
+
return runif(rng, n).map((u) => location + scale * Math.tan(Math.PI * (u - 0.5)));
|
|
518
|
+
}
|
|
519
|
+
function standardNormalPair(rng) {
|
|
520
|
+
const radius = Math.sqrt(-2 * Math.log(1 - rng()));
|
|
521
|
+
const angle = 2 * Math.PI * rng();
|
|
522
|
+
return [radius * Math.cos(angle), radius * Math.sin(angle)];
|
|
523
|
+
}
|
|
524
|
+
function sampleWithoutReplacement(rng, values, k) {
|
|
525
|
+
requireCount(k, "k");
|
|
526
|
+
if (k > values.length) {
|
|
527
|
+
throw new RangeError(`cannot take a sample of ${k} from a population of ${values.length}`);
|
|
528
|
+
}
|
|
529
|
+
const pool = values.slice();
|
|
530
|
+
for (let i = 0;i < k; i += 1) {
|
|
531
|
+
const chosen = i + Math.floor(rng() * (pool.length - i));
|
|
532
|
+
const held = pool[chosen];
|
|
533
|
+
pool[chosen] = pool[i];
|
|
534
|
+
pool[i] = held;
|
|
535
|
+
}
|
|
536
|
+
return pool.slice(0, k);
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
// src/core/sampling.ts
|
|
540
|
+
var CI95_MULTIPLIER = 1.96;
|
|
541
|
+
var CI99_MULTIPLIER = 2.58;
|
|
542
|
+
var DEFAULT_POP_SIZE = 1e4;
|
|
543
|
+
var DEFAULT_NUM_SAMPLES = 100;
|
|
544
|
+
var DEFAULT_SAMPLE_SIZE = 100;
|
|
545
|
+
function drawSamples(rng, population, options) {
|
|
546
|
+
const { sampleSize, reps = 1, theta = mean } = options;
|
|
547
|
+
requireCount(reps, "reps");
|
|
548
|
+
const samples = Array.from({ length: reps }, () => sampleWithoutReplacement(rng, population, sampleSize));
|
|
549
|
+
return { samples, thetas: samples.map((sample) => theta(sample)) };
|
|
550
|
+
}
|
|
551
|
+
function sampleConfidenceIntervals(samples, populationMean) {
|
|
552
|
+
return samples.map((sample) => {
|
|
553
|
+
const center = mean(sample);
|
|
554
|
+
const spread = sd(sample);
|
|
555
|
+
const standardError = spread / Math.sqrt(sample.length);
|
|
556
|
+
const ci95 = spreadAround(center, standardError, CI95_MULTIPLIER);
|
|
557
|
+
const ci99 = spreadAround(center, standardError, CI99_MULTIPLIER);
|
|
558
|
+
return {
|
|
559
|
+
mean: center,
|
|
560
|
+
sd: spread,
|
|
561
|
+
standardError,
|
|
562
|
+
ci95,
|
|
563
|
+
ci99,
|
|
564
|
+
excludesPopulationMean: ci95.low > populationMean || ci95.high < populationMean || ci99.low > populationMean || ci99.high < populationMean
|
|
565
|
+
};
|
|
566
|
+
});
|
|
567
|
+
}
|
|
568
|
+
function spreadAround(center, standardError, multiplier) {
|
|
569
|
+
return {
|
|
570
|
+
low: center - standardError * multiplier,
|
|
571
|
+
high: center + standardError * multiplier
|
|
572
|
+
};
|
|
573
|
+
}
|
|
574
|
+
function simulateSampleCi(rng, options = {}) {
|
|
575
|
+
const {
|
|
576
|
+
numSamples = DEFAULT_NUM_SAMPLES,
|
|
577
|
+
sampleSize = DEFAULT_SAMPLE_SIZE,
|
|
578
|
+
popSize = DEFAULT_POP_SIZE,
|
|
579
|
+
distribution = standardNormal
|
|
580
|
+
} = options;
|
|
581
|
+
requireCount(numSamples, "numSamples");
|
|
582
|
+
requireCount(popSize, "popSize");
|
|
583
|
+
const population = distribution(rng, popSize);
|
|
584
|
+
const populationMean = mean(population);
|
|
585
|
+
const { samples } = drawSamples(rng, population, {
|
|
586
|
+
sampleSize,
|
|
587
|
+
reps: numSamples
|
|
588
|
+
});
|
|
589
|
+
return {
|
|
590
|
+
populationMean,
|
|
591
|
+
populationSd: sd(population),
|
|
592
|
+
intervals: sampleConfidenceIntervals(samples, populationMean)
|
|
593
|
+
};
|
|
594
|
+
}
|
|
595
|
+
function standardNormal(rng, n) {
|
|
596
|
+
return rnorm(rng, n);
|
|
597
|
+
}
|
|
598
|
+
// src/core/frame.ts
|
|
599
|
+
function isNumericColumn(column) {
|
|
600
|
+
return column.length > 0 && column.every((value) => typeof value === "number");
|
|
601
|
+
}
|
|
602
|
+
function numericColumns(data) {
|
|
603
|
+
return Object.keys(data).filter((name) => isNumericColumn(data[name]));
|
|
604
|
+
}
|
|
605
|
+
function requireThreeNumericColumns(numeric, caller) {
|
|
606
|
+
if (numeric.length < 3) {
|
|
607
|
+
throw new RangeError(`${caller}() needs at least 3 numeric columns; got ${numeric.length}. ` + "Supply x/y/z explicitly or add numeric columns.");
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
function frameRows(data) {
|
|
611
|
+
const names = Object.keys(data);
|
|
612
|
+
const first = names[0];
|
|
613
|
+
if (first === undefined) {
|
|
614
|
+
return 0;
|
|
615
|
+
}
|
|
616
|
+
const rows = data[first].length;
|
|
617
|
+
const ragged = names.find((name) => data[name].length !== rows);
|
|
618
|
+
if (ragged !== undefined) {
|
|
619
|
+
throw new RangeError(`every column needs the same number of rows: "${first}" has ${rows} ` + `but "${ragged}" has ${data[ragged].length}`);
|
|
620
|
+
}
|
|
621
|
+
return rows;
|
|
622
|
+
}
|
|
623
|
+
function requireNumericColumn(data, name, role) {
|
|
624
|
+
const column = data[name];
|
|
625
|
+
if (column === undefined) {
|
|
626
|
+
throw new RangeError(`Column "${name}" (passed as \`${role}\`) is not in the data.`);
|
|
627
|
+
}
|
|
628
|
+
if (!isNumericColumn(column)) {
|
|
629
|
+
throw new RangeError(`Column "${name}" (passed as \`${role}\`) is not numeric; ` + "only numeric columns can carry the statistics.");
|
|
630
|
+
}
|
|
631
|
+
return column;
|
|
632
|
+
}
|
|
633
|
+
// src/core/ols.ts
|
|
634
|
+
var DEFAULT_LEAST_SQUARES_TOLERANCE = 0.0000001;
|
|
635
|
+
function leastSquares(design, y, options = {}) {
|
|
636
|
+
const { weights, tolerance = DEFAULT_LEAST_SQUARES_TOLERANCE } = options;
|
|
637
|
+
const rows = design.length;
|
|
638
|
+
if (rows === 0) {
|
|
639
|
+
throw new RangeError("least squares needs at least one row");
|
|
640
|
+
}
|
|
641
|
+
const width = design[0].length;
|
|
642
|
+
if (design.some((row) => row.length !== width)) {
|
|
643
|
+
throw new RangeError("every design row needs the same number of columns");
|
|
644
|
+
}
|
|
645
|
+
if (y.length !== rows) {
|
|
646
|
+
throw new RangeError(`the response has ${y.length} values but the design has ${rows} rows`);
|
|
647
|
+
}
|
|
648
|
+
if (weights !== undefined) {
|
|
649
|
+
if (weights.length !== rows) {
|
|
650
|
+
throw new RangeError(`there are ${weights.length} weights but ${rows} rows`);
|
|
651
|
+
}
|
|
652
|
+
if (weights.some((weight) => !(weight >= 0))) {
|
|
653
|
+
throw new RangeError("weights cannot be negative or missing");
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
const scale = weights?.map((weight) => Math.sqrt(weight));
|
|
657
|
+
const scaled = (value, row) => scale === undefined ? value : value * scale[row];
|
|
658
|
+
const columns = Array.from({ length: width }, (_, column) => design.map((row, index) => scaled(row[column], index)));
|
|
659
|
+
const projected = y.map(scaled);
|
|
660
|
+
const { householders, pivot, rank } = decompose(columns, tolerance, rows);
|
|
661
|
+
applyHouseholders(columns, householders, projected, Math.min(rank, rows - 1));
|
|
662
|
+
const solved = backSubstitute(columns, projected, rank);
|
|
663
|
+
const coefficients = new Array(width).fill(null);
|
|
664
|
+
pivot.slice(0, rank).forEach((column, position) => {
|
|
665
|
+
coefficients[column] = solved[position];
|
|
666
|
+
});
|
|
667
|
+
const fitted = design.map((row) => sum(zipWith(row, coefficients, (value, coefficient) => coefficient === null ? 0 : value * coefficient)));
|
|
668
|
+
const residuals = zipWith(y, fitted, (value, fit) => value - fit);
|
|
669
|
+
return { coefficients, fitted, residuals, rank };
|
|
670
|
+
}
|
|
671
|
+
function decompose(columns, tolerance, rows) {
|
|
672
|
+
const width = columns.length;
|
|
673
|
+
const pivot = columns.map((_, column) => column);
|
|
674
|
+
const originalNorms = columns.map((column) => norm(column, 0) || 1);
|
|
675
|
+
const householders = new Array(width).fill(0);
|
|
676
|
+
let live = width;
|
|
677
|
+
for (let step = 0;step < Math.min(rows, width); step++) {
|
|
678
|
+
while (step < live && norm(columns[step], step) < originalNorms[step] * tolerance) {
|
|
679
|
+
cycleToEnd(columns, pivot, originalNorms, step);
|
|
680
|
+
live -= 1;
|
|
681
|
+
}
|
|
682
|
+
if (step === rows - 1) {
|
|
683
|
+
continue;
|
|
684
|
+
}
|
|
685
|
+
householders[step] = reflect(columns, step, rows);
|
|
686
|
+
}
|
|
687
|
+
return { householders, pivot, rank: Math.min(live, rows) };
|
|
688
|
+
}
|
|
689
|
+
function reflect(columns, step, rows) {
|
|
690
|
+
const column = columns[step];
|
|
691
|
+
const length = norm(column, step);
|
|
692
|
+
if (length === 0) {
|
|
693
|
+
return 0;
|
|
694
|
+
}
|
|
695
|
+
const pivotNorm = column[step] < 0 ? -length : length;
|
|
696
|
+
for (let row = step;row < rows; row++) {
|
|
697
|
+
column[row] = column[row] / pivotNorm;
|
|
698
|
+
}
|
|
699
|
+
const leading = 1 + column[step];
|
|
700
|
+
column[step] = leading;
|
|
701
|
+
for (let index = step + 1;index < columns.length; index++) {
|
|
702
|
+
const other = columns[index];
|
|
703
|
+
let inner = 0;
|
|
704
|
+
for (let row = step;row < rows; row++) {
|
|
705
|
+
inner += column[row] * other[row];
|
|
706
|
+
}
|
|
707
|
+
const factor = -inner / leading;
|
|
708
|
+
for (let row = step;row < rows; row++) {
|
|
709
|
+
other[row] = other[row] + factor * column[row];
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
column[step] = -pivotNorm;
|
|
713
|
+
return leading;
|
|
714
|
+
}
|
|
715
|
+
function applyHouseholders(columns, householders, response, count) {
|
|
716
|
+
const rows = response.length;
|
|
717
|
+
for (let step = 0;step < count; step++) {
|
|
718
|
+
const leading = householders[step];
|
|
719
|
+
if (leading === 0) {
|
|
720
|
+
continue;
|
|
721
|
+
}
|
|
722
|
+
const column = columns[step];
|
|
723
|
+
let inner = leading * response[step];
|
|
724
|
+
for (let row = step + 1;row < rows; row++) {
|
|
725
|
+
inner += column[row] * response[row];
|
|
726
|
+
}
|
|
727
|
+
const factor = -inner / leading;
|
|
728
|
+
response[step] = response[step] + factor * leading;
|
|
729
|
+
for (let row = step + 1;row < rows; row++) {
|
|
730
|
+
response[row] = response[row] + factor * column[row];
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
function backSubstitute(columns, response, rank) {
|
|
735
|
+
const solved = new Array(rank).fill(0);
|
|
736
|
+
for (let row = rank - 1;row >= 0; row--) {
|
|
737
|
+
let value = response[row];
|
|
738
|
+
for (let column = row + 1;column < rank; column++) {
|
|
739
|
+
value -= columns[column][row] * solved[column];
|
|
740
|
+
}
|
|
741
|
+
solved[row] = value / columns[row][row];
|
|
742
|
+
}
|
|
743
|
+
return solved;
|
|
744
|
+
}
|
|
745
|
+
function cycleToEnd(columns, pivot, originalNorms, step) {
|
|
746
|
+
moveToEnd(columns, step);
|
|
747
|
+
moveToEnd(pivot, step);
|
|
748
|
+
moveToEnd(originalNorms, step);
|
|
749
|
+
}
|
|
750
|
+
function moveToEnd(track, from) {
|
|
751
|
+
const [moved] = track.splice(from, 1);
|
|
752
|
+
track.push(moved);
|
|
753
|
+
}
|
|
754
|
+
function norm(column, from) {
|
|
755
|
+
return Math.hypot(...column.slice(from));
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
// src/core/moderation.ts
|
|
759
|
+
var GRID_STEPS = 15;
|
|
760
|
+
function moderationSurface(data, options) {
|
|
761
|
+
const { outcome, iv, mod, interaction = true, controls = [] } = options;
|
|
762
|
+
if (iv === mod) {
|
|
763
|
+
throw new RangeError(`\`iv\` and \`mod\` must name different columns, both name "${iv}"`);
|
|
764
|
+
}
|
|
765
|
+
const named = new Set([outcome, iv, mod]);
|
|
766
|
+
controls.forEach((control) => {
|
|
767
|
+
if (named.has(control)) {
|
|
768
|
+
throw new RangeError(`control "${control}" already names the outcome, the iv, the mod, ` + "or another control");
|
|
769
|
+
}
|
|
770
|
+
named.add(control);
|
|
771
|
+
});
|
|
772
|
+
const rows = frameRows(data);
|
|
773
|
+
const y = requireNumericColumn(data, outcome, "outcome");
|
|
774
|
+
const ivColumn = requireNumericColumn(data, iv, "iv");
|
|
775
|
+
const modColumn = requireNumericColumn(data, mod, "mod");
|
|
776
|
+
const controlColumns = controls.map((control) => requireNumericColumn(data, control, "controls"));
|
|
777
|
+
const modelColumns = [y, ivColumn, modColumn, ...controlColumns];
|
|
778
|
+
const completeRows = y.map((_, row) => row).filter((row) => modelColumns.every((column) => Number.isFinite(column[row])));
|
|
779
|
+
if (completeRows.length === 0) {
|
|
780
|
+
throw new RangeError("the model has no complete rows: every row is missing a value in " + "the outcome, the IV, the moderator, or a control");
|
|
781
|
+
}
|
|
782
|
+
const designColumns = [
|
|
783
|
+
{ name: "(Intercept)", values: new Array(rows).fill(1) },
|
|
784
|
+
{ name: iv, values: ivColumn },
|
|
785
|
+
{ name: mod, values: modColumn },
|
|
786
|
+
...controls.map((control, index) => ({
|
|
787
|
+
name: control,
|
|
788
|
+
values: controlColumns[index]
|
|
789
|
+
})),
|
|
790
|
+
...interaction ? [
|
|
791
|
+
{
|
|
792
|
+
name: `${iv}:${mod}`,
|
|
793
|
+
values: zipWith(ivColumn, modColumn, (a, b) => a * b)
|
|
794
|
+
}
|
|
795
|
+
] : []
|
|
796
|
+
];
|
|
797
|
+
const design = completeRows.map((row) => designColumns.map((column) => column.values[row]));
|
|
798
|
+
const fit = leastSquares(design, completeRows.map((row) => y[row]));
|
|
799
|
+
const coefficients = designColumns.map((column, index) => ({
|
|
800
|
+
name: column.name,
|
|
801
|
+
value: fit.coefficients[index] ?? null
|
|
802
|
+
}));
|
|
803
|
+
const fitted = new Array(rows).fill(Number.NaN);
|
|
804
|
+
const residuals = new Array(rows).fill(Number.NaN);
|
|
805
|
+
completeRows.forEach((row, survivor) => {
|
|
806
|
+
fitted[row] = fit.fitted[survivor];
|
|
807
|
+
residuals[row] = fit.residuals[survivor];
|
|
808
|
+
});
|
|
809
|
+
const ivValues = rSeq(...extent(ivColumn), GRID_STEPS);
|
|
810
|
+
const modValues = rSeq(...extent(modColumn), GRID_STEPS);
|
|
811
|
+
const holds = Object.fromEntries(controls.map((control, index) => [
|
|
812
|
+
control,
|
|
813
|
+
mean(controlColumns[index].filter(Number.isFinite))
|
|
814
|
+
]));
|
|
815
|
+
const weights = coefficients.map((term) => term.value ?? 0);
|
|
816
|
+
const predictions = modValues.flatMap((modValue) => ivValues.map((ivValue) => {
|
|
817
|
+
const gridRow = [
|
|
818
|
+
1,
|
|
819
|
+
ivValue,
|
|
820
|
+
modValue,
|
|
821
|
+
...controls.map((control) => holds[control]),
|
|
822
|
+
...interaction ? [ivValue * modValue] : []
|
|
823
|
+
];
|
|
824
|
+
return sum(zipWith(gridRow, weights, (value, weight) => value * weight));
|
|
825
|
+
}));
|
|
826
|
+
const [dataLow, dataHigh] = extent(y);
|
|
827
|
+
const [surfaceLow, surfaceHigh] = extent(predictions);
|
|
828
|
+
return {
|
|
829
|
+
coefficients,
|
|
830
|
+
fitted,
|
|
831
|
+
residuals,
|
|
832
|
+
ivValues,
|
|
833
|
+
modValues,
|
|
834
|
+
predictions,
|
|
835
|
+
zlim: [Math.min(dataLow, surfaceLow), Math.max(dataHigh, surfaceHigh)],
|
|
836
|
+
holds
|
|
837
|
+
};
|
|
838
|
+
}
|
|
839
|
+
function rSeq(from, to, length) {
|
|
840
|
+
if (from === to) {
|
|
841
|
+
return new Array(length).fill(from);
|
|
842
|
+
}
|
|
843
|
+
const by = (to - from) / (length - 1);
|
|
844
|
+
return Array.from({ length }, (_, index) => index === 0 ? from : index === length - 1 ? to : from + index * by);
|
|
845
|
+
}
|
|
846
|
+
// src/core/regression.ts
|
|
847
|
+
function completePointRows(points) {
|
|
848
|
+
return points.flatMap((point, row) => Number.isFinite(point.x) && Number.isFinite(point.y) ? [row] : []);
|
|
849
|
+
}
|
|
850
|
+
function linearRegression(points) {
|
|
851
|
+
const rows = completePointRows(points);
|
|
852
|
+
if (rows.length === 0) {
|
|
853
|
+
return null;
|
|
854
|
+
}
|
|
855
|
+
const xs = rows.map((row) => points[row].x);
|
|
856
|
+
const ys = rows.map((row) => points[row].y);
|
|
857
|
+
const meanX = mean(xs);
|
|
858
|
+
const meanY = mean(ys);
|
|
859
|
+
const devX = xs.map((x) => x - meanX);
|
|
860
|
+
const devY = ys.map((y) => y - meanY);
|
|
861
|
+
const sumSquaresX = sum(devX.map((d) => d * d));
|
|
862
|
+
const sumSquaresY = sum(devY.map((d) => d * d));
|
|
863
|
+
const sumProducts = sum(zipWith(devX, devY, (dx, dy) => dx * dy));
|
|
864
|
+
const slope = sumSquaresX === 0 ? null : sumProducts / sumSquaresX;
|
|
865
|
+
const intercept = slope === null ? meanY : meanY - slope * meanX;
|
|
866
|
+
const correlation = sumSquaresX === 0 || sumSquaresY === 0 ? null : sumProducts / Math.sqrt(sumSquaresX * sumSquaresY);
|
|
867
|
+
const fittedComplete = xs.map((x) => slope === null ? intercept : intercept + slope * x);
|
|
868
|
+
const ssr = sum(fittedComplete.map((f) => (f - meanY) * (f - meanY)));
|
|
869
|
+
const sse = sum(zipWith(ys, fittedComplete, (y, f) => (y - f) * (y - f)));
|
|
870
|
+
const sst = sumSquaresY;
|
|
871
|
+
const rSquared = sst === 0 ? null : ssr / sst;
|
|
872
|
+
const fitted = new Array(points.length).fill(Number.NaN);
|
|
873
|
+
rows.forEach((row, survivor) => {
|
|
874
|
+
fitted[row] = fittedComplete[survivor];
|
|
875
|
+
});
|
|
876
|
+
return {
|
|
877
|
+
intercept,
|
|
878
|
+
slope,
|
|
879
|
+
correlation,
|
|
880
|
+
ssr,
|
|
881
|
+
sse,
|
|
882
|
+
sst,
|
|
883
|
+
rSquared,
|
|
884
|
+
fitted
|
|
885
|
+
};
|
|
886
|
+
}
|
|
887
|
+
// src/core/matrix.ts
|
|
888
|
+
var SMALLEST_NORMAL = 0.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000022250738585072014;
|
|
889
|
+
function determinant(matrix) {
|
|
890
|
+
return determinantOf(factorize(matrix));
|
|
891
|
+
}
|
|
892
|
+
function invertMatrix(matrix) {
|
|
893
|
+
const factorization = factorize(matrix);
|
|
894
|
+
const determinant2 = determinantOf(factorization);
|
|
895
|
+
const zeroPivot = zeroPivotOf(factorization);
|
|
896
|
+
if (zeroPivot !== null) {
|
|
897
|
+
return {
|
|
898
|
+
determinant: determinant2,
|
|
899
|
+
inverse: null,
|
|
900
|
+
singularity: "exact",
|
|
901
|
+
rcond: 0,
|
|
902
|
+
zeroPivot
|
|
903
|
+
};
|
|
904
|
+
}
|
|
905
|
+
const inverse = solveForIdentity(factorization);
|
|
906
|
+
const rcond = 1 / (oneNorm(matrix) * oneNorm(inverse));
|
|
907
|
+
if (rcond < Number.EPSILON) {
|
|
908
|
+
return {
|
|
909
|
+
determinant: determinant2,
|
|
910
|
+
inverse: null,
|
|
911
|
+
singularity: "computational",
|
|
912
|
+
rcond,
|
|
913
|
+
zeroPivot: null
|
|
914
|
+
};
|
|
915
|
+
}
|
|
916
|
+
return {
|
|
917
|
+
determinant: determinant2,
|
|
918
|
+
inverse,
|
|
919
|
+
singularity: null,
|
|
920
|
+
rcond,
|
|
921
|
+
zeroPivot: null
|
|
922
|
+
};
|
|
923
|
+
}
|
|
924
|
+
function factorize(matrix) {
|
|
925
|
+
const interchanged = Math.abs(matrix.y1) > Math.abs(matrix.x1);
|
|
926
|
+
const leading = interchanged ? matrix.y1 : matrix.x1;
|
|
927
|
+
const upperRight = interchanged ? matrix.y2 : matrix.x2;
|
|
928
|
+
const below = interchanged ? matrix.x1 : matrix.y1;
|
|
929
|
+
const belowRight = interchanged ? matrix.x2 : matrix.y2;
|
|
930
|
+
const multiplier = Math.abs(leading) >= SMALLEST_NORMAL ? below * (1 / leading) : below / leading;
|
|
931
|
+
const trailing = fusedMultiplyAdd(-multiplier, upperRight, belowRight);
|
|
932
|
+
return {
|
|
933
|
+
interchanged,
|
|
934
|
+
multiplier,
|
|
935
|
+
pivots: [leading, trailing],
|
|
936
|
+
upperRight
|
|
937
|
+
};
|
|
938
|
+
}
|
|
939
|
+
function determinantOf(factorization) {
|
|
940
|
+
const { interchanged, pivots } = factorization;
|
|
941
|
+
if (zeroPivotOf(factorization) !== null) {
|
|
942
|
+
return 0;
|
|
943
|
+
}
|
|
944
|
+
const modulus = sum(pivots.map((pivot) => Math.log(Math.abs(pivot))));
|
|
945
|
+
const sign = pivots.reduce((carried, pivot) => pivot < 0 ? -carried : carried, interchanged ? -1 : 1);
|
|
946
|
+
return sign * Math.exp(modulus);
|
|
947
|
+
}
|
|
948
|
+
function zeroPivotOf(factorization) {
|
|
949
|
+
const [leading, trailing] = factorization.pivots;
|
|
950
|
+
if (leading === 0) {
|
|
951
|
+
return 1;
|
|
952
|
+
}
|
|
953
|
+
return trailing === 0 ? 2 : null;
|
|
954
|
+
}
|
|
955
|
+
function solveForIdentity(factorization) {
|
|
956
|
+
const { interchanged } = factorization;
|
|
957
|
+
const [firstTop, firstBottom] = interchanged ? [0, 1] : [1, 0];
|
|
958
|
+
const [secondTop, secondBottom] = interchanged ? [1, 0] : [0, 1];
|
|
959
|
+
const [x1, y1] = solveColumn(factorization, firstTop, firstBottom);
|
|
960
|
+
const [x2, y2] = solveColumn(factorization, secondTop, secondBottom);
|
|
961
|
+
return {
|
|
962
|
+
x1: withoutNegativeZero(x1),
|
|
963
|
+
y1: withoutNegativeZero(y1),
|
|
964
|
+
x2: withoutNegativeZero(x2),
|
|
965
|
+
y2: withoutNegativeZero(y2)
|
|
966
|
+
};
|
|
967
|
+
}
|
|
968
|
+
function solveColumn(factorization, top, bottom) {
|
|
969
|
+
const { multiplier, pivots, upperRight } = factorization;
|
|
970
|
+
const lower = fusedMultiplyAdd(-multiplier, top, bottom) / pivots[1];
|
|
971
|
+
const upper = fusedMultiplyAdd(-upperRight, lower, top) / pivots[0];
|
|
972
|
+
return [upper, lower];
|
|
973
|
+
}
|
|
974
|
+
function oneNorm(matrix) {
|
|
975
|
+
return Math.max(Math.abs(matrix.x1) + Math.abs(matrix.y1), Math.abs(matrix.x2) + Math.abs(matrix.y2));
|
|
976
|
+
}
|
|
977
|
+
// src/core/pca.ts
|
|
978
|
+
function principalComponents(points) {
|
|
979
|
+
const rows = completePointRows(points);
|
|
980
|
+
if (rows.length === 0) {
|
|
981
|
+
return null;
|
|
982
|
+
}
|
|
983
|
+
const complete = rows.map((row) => points[row]);
|
|
984
|
+
const centerX = mean(complete.map((point) => point.x));
|
|
985
|
+
const centerY = mean(complete.map((point) => point.y));
|
|
986
|
+
const devX = complete.map((point) => point.x - centerX);
|
|
987
|
+
const devY = complete.map((point) => point.y - centerY);
|
|
988
|
+
const divisor = Math.max(1, complete.length - 1);
|
|
989
|
+
const varX = sum(devX.map((d) => d * d)) / divisor;
|
|
990
|
+
const varY = sum(devY.map((d) => d * d)) / divisor;
|
|
991
|
+
const covariance = sum(zipWith(devX, devY, (dx, dy) => dx * dy)) / divisor;
|
|
992
|
+
const [first, second] = componentsOf(varX, varY, covariance);
|
|
993
|
+
const scores = points.map(() => ({ x: Number.NaN, y: Number.NaN }));
|
|
994
|
+
rows.forEach((row, survivor) => {
|
|
995
|
+
const dx = devX[survivor];
|
|
996
|
+
const dy = devY[survivor];
|
|
997
|
+
scores[row] = {
|
|
998
|
+
x: dx * first.loadings[0] + dy * first.loadings[1],
|
|
999
|
+
y: dx * second.loadings[0] + dy * second.loadings[1]
|
|
1000
|
+
};
|
|
1001
|
+
});
|
|
1002
|
+
return {
|
|
1003
|
+
sdev: [sdevOf(first.variance), sdevOf(second.variance)],
|
|
1004
|
+
rotation: [first.loadings, second.loadings],
|
|
1005
|
+
center: { x: centerX, y: centerY },
|
|
1006
|
+
scores
|
|
1007
|
+
};
|
|
1008
|
+
}
|
|
1009
|
+
function componentsOf(varX, varY, covariance) {
|
|
1010
|
+
if (covariance === 0) {
|
|
1011
|
+
const alongX = { variance: varX, loadings: [1, 0] };
|
|
1012
|
+
const alongY = { variance: varY, loadings: [0, 1] };
|
|
1013
|
+
return varX >= varY ? [alongX, alongY] : [alongY, alongX];
|
|
1014
|
+
}
|
|
1015
|
+
const middle = (varX + varY) / 2;
|
|
1016
|
+
const spread = Math.hypot((varX - varY) / 2, covariance);
|
|
1017
|
+
const larger = middle + spread;
|
|
1018
|
+
const smaller = middle - spread;
|
|
1019
|
+
const fromRowX = larger - varX;
|
|
1020
|
+
const fromRowY = larger - varY;
|
|
1021
|
+
const direction = fromRowX >= fromRowY ? [covariance, fromRowX] : [fromRowY, covariance];
|
|
1022
|
+
const leading = signed(unit(direction));
|
|
1023
|
+
const trailing = signed([negated(leading[1]), leading[0]]);
|
|
1024
|
+
return [
|
|
1025
|
+
{ variance: larger, loadings: leading },
|
|
1026
|
+
{ variance: smaller, loadings: trailing }
|
|
1027
|
+
];
|
|
1028
|
+
}
|
|
1029
|
+
function unit(vector) {
|
|
1030
|
+
const length = Math.hypot(vector[0], vector[1]);
|
|
1031
|
+
return [vector[0] / length, vector[1] / length];
|
|
1032
|
+
}
|
|
1033
|
+
function signed(vector) {
|
|
1034
|
+
const dominant = Math.abs(vector[0]) >= Math.abs(vector[1]) ? vector[0] : vector[1];
|
|
1035
|
+
return dominant >= 0 ? vector : [negated(vector[0]), negated(vector[1])];
|
|
1036
|
+
}
|
|
1037
|
+
function negated(value) {
|
|
1038
|
+
return withoutNegativeZero(-value);
|
|
1039
|
+
}
|
|
1040
|
+
function sdevOf(variance) {
|
|
1041
|
+
return Math.sqrt(Math.max(0, variance));
|
|
1042
|
+
}
|
|
1043
|
+
// src/core/logit.ts
|
|
1044
|
+
var DEFAULT_LOGIT_EPSILON = 0.00000001;
|
|
1045
|
+
var DEFAULT_LOGIT_MAX_ITERATIONS = 25;
|
|
1046
|
+
var LINK_THRESHOLD = 30;
|
|
1047
|
+
var BOUND = 10 * Number.EPSILON;
|
|
1048
|
+
function logisticRegression(points, options = {}) {
|
|
1049
|
+
const {
|
|
1050
|
+
epsilon = DEFAULT_LOGIT_EPSILON,
|
|
1051
|
+
maxIterations = DEFAULT_LOGIT_MAX_ITERATIONS
|
|
1052
|
+
} = options;
|
|
1053
|
+
const rows = completePointRows(points);
|
|
1054
|
+
if (rows.length === 0) {
|
|
1055
|
+
return null;
|
|
1056
|
+
}
|
|
1057
|
+
const complete = rows.map((row) => points[row]);
|
|
1058
|
+
if (complete.some((point) => point.y !== 0 && point.y !== 1)) {
|
|
1059
|
+
throw new RangeError("every outcome must be 0 or 1");
|
|
1060
|
+
}
|
|
1061
|
+
const outcomes = complete.map((point) => point.y);
|
|
1062
|
+
const design = complete.map((point) => [1, point.x]);
|
|
1063
|
+
let fitted = outcomes.map((outcome) => (outcome + 0.5) / 2);
|
|
1064
|
+
let predictors = fitted.map(logit);
|
|
1065
|
+
let previousDeviance = totalDeviance(outcomes, fitted);
|
|
1066
|
+
let deviance = previousDeviance;
|
|
1067
|
+
let coefficients = [Number.NaN, null];
|
|
1068
|
+
let rank = 0;
|
|
1069
|
+
let iterations = 0;
|
|
1070
|
+
let converged = false;
|
|
1071
|
+
for (let iteration = 1;iteration <= maxIterations; iteration++) {
|
|
1072
|
+
iterations = iteration;
|
|
1073
|
+
const working = predictors.map((predictor, row) => {
|
|
1074
|
+
const probability = fitted[row];
|
|
1075
|
+
const slope = linkSlope(predictor);
|
|
1076
|
+
return {
|
|
1077
|
+
weight: slope * slope / (probability * (1 - probability)),
|
|
1078
|
+
response: predictor + (outcomes[row] - probability) / slope
|
|
1079
|
+
};
|
|
1080
|
+
});
|
|
1081
|
+
const step = leastSquares(design, working.map((row) => row.response), {
|
|
1082
|
+
weights: working.map((row) => row.weight),
|
|
1083
|
+
tolerance: Math.min(0.0000001, epsilon / 1000)
|
|
1084
|
+
});
|
|
1085
|
+
if (step.coefficients.some((coefficient) => coefficient !== null && !Number.isFinite(coefficient))) {
|
|
1086
|
+
break;
|
|
1087
|
+
}
|
|
1088
|
+
coefficients = step.coefficients;
|
|
1089
|
+
rank = step.rank;
|
|
1090
|
+
predictors = design.map((row) => sum(zipWith(row, coefficients, (value, coefficient) => coefficient === null ? 0 : value * coefficient)));
|
|
1091
|
+
fitted = predictors.map(linkInverse);
|
|
1092
|
+
deviance = totalDeviance(outcomes, fitted);
|
|
1093
|
+
if (Math.abs(deviance - previousDeviance) / (0.1 + Math.abs(deviance)) < epsilon) {
|
|
1094
|
+
converged = true;
|
|
1095
|
+
break;
|
|
1096
|
+
}
|
|
1097
|
+
previousDeviance = deviance;
|
|
1098
|
+
}
|
|
1099
|
+
const wholeMean = mean(outcomes);
|
|
1100
|
+
const nullDeviance = totalDeviance(outcomes, outcomes.map(() => wholeMean));
|
|
1101
|
+
const paddedFitted = new Array(points.length).fill(Number.NaN);
|
|
1102
|
+
const paddedPredictors = new Array(points.length).fill(Number.NaN);
|
|
1103
|
+
rows.forEach((row, survivor) => {
|
|
1104
|
+
paddedFitted[row] = fitted[survivor];
|
|
1105
|
+
paddedPredictors[row] = predictors[survivor];
|
|
1106
|
+
});
|
|
1107
|
+
return {
|
|
1108
|
+
intercept: coefficients[0] ?? Number.NaN,
|
|
1109
|
+
slope: coefficients[1] ?? null,
|
|
1110
|
+
fitted: paddedFitted,
|
|
1111
|
+
linearPredictors: paddedPredictors,
|
|
1112
|
+
deviance,
|
|
1113
|
+
nullDeviance,
|
|
1114
|
+
aic: deviance + 2 * rank,
|
|
1115
|
+
rank,
|
|
1116
|
+
iterations,
|
|
1117
|
+
converged,
|
|
1118
|
+
saturated: fitted.some((probability) => probability > 1 - BOUND || probability < BOUND)
|
|
1119
|
+
};
|
|
1120
|
+
}
|
|
1121
|
+
function predictLogit(fit, x) {
|
|
1122
|
+
return linkInverse(fit.intercept + (fit.slope ?? 0) * x);
|
|
1123
|
+
}
|
|
1124
|
+
function logit(probability) {
|
|
1125
|
+
return Math.log(probability / (1 - probability));
|
|
1126
|
+
}
|
|
1127
|
+
function linkInverse(predictor) {
|
|
1128
|
+
const odds = predictor < -LINK_THRESHOLD ? Number.EPSILON : predictor > LINK_THRESHOLD ? 1 / Number.EPSILON : Math.exp(predictor);
|
|
1129
|
+
return odds / (1 + odds);
|
|
1130
|
+
}
|
|
1131
|
+
function linkSlope(predictor) {
|
|
1132
|
+
if (predictor > LINK_THRESHOLD || predictor < -LINK_THRESHOLD) {
|
|
1133
|
+
return Number.EPSILON;
|
|
1134
|
+
}
|
|
1135
|
+
const odds = Math.exp(predictor);
|
|
1136
|
+
return odds / ((1 + odds) * (1 + odds));
|
|
1137
|
+
}
|
|
1138
|
+
function totalDeviance(outcomes, probabilities) {
|
|
1139
|
+
return sum(zipWith(outcomes, probabilities, (outcome, probability) => 2 * (surprise(outcome, probability) + surprise(1 - outcome, 1 - probability))));
|
|
1140
|
+
}
|
|
1141
|
+
function surprise(outcome, probability) {
|
|
1142
|
+
return outcome !== 0 ? outcome * Math.log(outcome / probability) : 0;
|
|
1143
|
+
}
|
|
1144
|
+
// src/core/special.ts
|
|
1145
|
+
var LANCZOS_G = 607 / 128;
|
|
1146
|
+
var LANCZOS_LEAD = 0.9999999999999971;
|
|
1147
|
+
var LANCZOS_TAIL = [
|
|
1148
|
+
57.15623566586292,
|
|
1149
|
+
-59.59796035547549,
|
|
1150
|
+
14.136097974741746,
|
|
1151
|
+
-0.4919138160976202,
|
|
1152
|
+
0.00003399464998481189,
|
|
1153
|
+
0.00004652362892704858,
|
|
1154
|
+
-0.00009837447530487956,
|
|
1155
|
+
0.0001580887032249125,
|
|
1156
|
+
-0.00021026444172410488,
|
|
1157
|
+
0.00021743961811521265,
|
|
1158
|
+
-0.0001643181065367639,
|
|
1159
|
+
0.00008441822398385275,
|
|
1160
|
+
-0.000026190838401581408,
|
|
1161
|
+
0.0000036899182659531625
|
|
1162
|
+
];
|
|
1163
|
+
var LOG_SQRT_TWO_PI = 0.5 * Math.log(2 * Math.PI);
|
|
1164
|
+
function lanczosSeries(x) {
|
|
1165
|
+
return LANCZOS_LEAD + sum(LANCZOS_TAIL.map((coefficient, index) => coefficient / (x + index)));
|
|
1166
|
+
}
|
|
1167
|
+
function logGamma(x) {
|
|
1168
|
+
if (!(x > 0)) {
|
|
1169
|
+
return Number.NaN;
|
|
1170
|
+
}
|
|
1171
|
+
const shifted = x + LANCZOS_G - 0.5;
|
|
1172
|
+
return LOG_SQRT_TWO_PI + (x - 0.5) * Math.log(shifted) - shifted + Math.log(lanczosSeries(x));
|
|
1173
|
+
}
|
|
1174
|
+
function logBeta(a, b) {
|
|
1175
|
+
if (!(a > 0) || !(b > 0)) {
|
|
1176
|
+
return Number.NaN;
|
|
1177
|
+
}
|
|
1178
|
+
const shiftedSum = a + b + LANCZOS_G - 0.5;
|
|
1179
|
+
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);
|
|
1180
|
+
}
|
|
1181
|
+
var FRACTION_MAX_STEPS = 400;
|
|
1182
|
+
var FRACTION_EPSILON = 0.0000000000000003;
|
|
1183
|
+
var FRACTION_FLOOR = 0.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001;
|
|
1184
|
+
function betaContinuedFraction(x, a, b) {
|
|
1185
|
+
const total = a + b;
|
|
1186
|
+
const aPlus = a + 1;
|
|
1187
|
+
const aMinus = a - 1;
|
|
1188
|
+
let c = 1;
|
|
1189
|
+
let d = 1 - total * x / aPlus;
|
|
1190
|
+
if (Math.abs(d) < FRACTION_FLOOR) {
|
|
1191
|
+
d = FRACTION_FLOOR;
|
|
1192
|
+
}
|
|
1193
|
+
d = 1 / d;
|
|
1194
|
+
let value = d;
|
|
1195
|
+
for (let step = 1;step <= FRACTION_MAX_STEPS; step += 1) {
|
|
1196
|
+
const twice = 2 * step;
|
|
1197
|
+
const even = step * (b - step) * x / ((aMinus + twice) * (a + twice));
|
|
1198
|
+
d = 1 + even * d;
|
|
1199
|
+
if (Math.abs(d) < FRACTION_FLOOR) {
|
|
1200
|
+
d = FRACTION_FLOOR;
|
|
1201
|
+
}
|
|
1202
|
+
c = 1 + even / c;
|
|
1203
|
+
if (Math.abs(c) < FRACTION_FLOOR) {
|
|
1204
|
+
c = FRACTION_FLOOR;
|
|
1205
|
+
}
|
|
1206
|
+
d = 1 / d;
|
|
1207
|
+
value *= d * c;
|
|
1208
|
+
const odd = -(a + step) * (total + step) * x / ((a + twice) * (aPlus + twice));
|
|
1209
|
+
d = 1 + odd * d;
|
|
1210
|
+
if (Math.abs(d) < FRACTION_FLOOR) {
|
|
1211
|
+
d = FRACTION_FLOOR;
|
|
1212
|
+
}
|
|
1213
|
+
c = 1 + odd / c;
|
|
1214
|
+
if (Math.abs(c) < FRACTION_FLOOR) {
|
|
1215
|
+
c = FRACTION_FLOOR;
|
|
1216
|
+
}
|
|
1217
|
+
d = 1 / d;
|
|
1218
|
+
const delta = d * c;
|
|
1219
|
+
value *= delta;
|
|
1220
|
+
if (Math.abs(delta - 1) < FRACTION_EPSILON) {
|
|
1221
|
+
break;
|
|
1222
|
+
}
|
|
1223
|
+
}
|
|
1224
|
+
return value;
|
|
1225
|
+
}
|
|
1226
|
+
function incompleteBeta(x, a, b) {
|
|
1227
|
+
if (Number.isNaN(x)) {
|
|
1228
|
+
return Number.NaN;
|
|
1229
|
+
}
|
|
1230
|
+
if (x <= 0) {
|
|
1231
|
+
return 0;
|
|
1232
|
+
}
|
|
1233
|
+
if (x >= 1) {
|
|
1234
|
+
return 1;
|
|
1235
|
+
}
|
|
1236
|
+
return incompleteBetaSplit(x, 1 - x, a, b);
|
|
1237
|
+
}
|
|
1238
|
+
function incompleteBetaSplit(x, complement, a, b) {
|
|
1239
|
+
if (Number.isNaN(x) || Number.isNaN(complement) || !(a > 0) || !(b > 0)) {
|
|
1240
|
+
return Number.NaN;
|
|
1241
|
+
}
|
|
1242
|
+
if (x <= 0) {
|
|
1243
|
+
return 0;
|
|
1244
|
+
}
|
|
1245
|
+
if (complement <= 0) {
|
|
1246
|
+
return 1;
|
|
1247
|
+
}
|
|
1248
|
+
const logX = complement < 0.5 ? Math.log1p(-complement) : Math.log(x);
|
|
1249
|
+
const logComplement = x < 0.5 ? Math.log1p(-x) : Math.log(complement);
|
|
1250
|
+
const front = Math.exp(a * logX + b * logComplement - logBeta(a, b));
|
|
1251
|
+
if (x < (a + 1) / (a + b + 2)) {
|
|
1252
|
+
return front * betaContinuedFraction(x, a, b) / a;
|
|
1253
|
+
}
|
|
1254
|
+
return 1 - front * betaContinuedFraction(complement, b, a) / b;
|
|
1255
|
+
}
|
|
1256
|
+
var GAMMA_MAX_STEPS = 1000;
|
|
1257
|
+
var GAMMA_EPSILON = 0.0000000000000003;
|
|
1258
|
+
function lowerGammaSeries(a, x) {
|
|
1259
|
+
let term = 1 / a;
|
|
1260
|
+
let total = term;
|
|
1261
|
+
for (let step = 1;step <= GAMMA_MAX_STEPS; step += 1) {
|
|
1262
|
+
term *= x / (a + step);
|
|
1263
|
+
total += term;
|
|
1264
|
+
if (Math.abs(term) < Math.abs(total) * GAMMA_EPSILON) {
|
|
1265
|
+
break;
|
|
1266
|
+
}
|
|
1267
|
+
}
|
|
1268
|
+
return total * Math.exp(-x + a * Math.log(x) - logGamma(a));
|
|
1269
|
+
}
|
|
1270
|
+
function upperGammaFraction(a, x) {
|
|
1271
|
+
let b = x + 1 - a;
|
|
1272
|
+
let c = 1 / FRACTION_FLOOR;
|
|
1273
|
+
let d = 1 / b;
|
|
1274
|
+
let value = d;
|
|
1275
|
+
for (let step = 1;step <= GAMMA_MAX_STEPS; step += 1) {
|
|
1276
|
+
const numerator = -step * (step - a);
|
|
1277
|
+
b += 2;
|
|
1278
|
+
d = numerator * d + b;
|
|
1279
|
+
if (Math.abs(d) < FRACTION_FLOOR) {
|
|
1280
|
+
d = FRACTION_FLOOR;
|
|
1281
|
+
}
|
|
1282
|
+
c = b + numerator / c;
|
|
1283
|
+
if (Math.abs(c) < FRACTION_FLOOR) {
|
|
1284
|
+
c = FRACTION_FLOOR;
|
|
1285
|
+
}
|
|
1286
|
+
d = 1 / d;
|
|
1287
|
+
const delta = d * c;
|
|
1288
|
+
value *= delta;
|
|
1289
|
+
if (Math.abs(delta - 1) < GAMMA_EPSILON) {
|
|
1290
|
+
break;
|
|
1291
|
+
}
|
|
1292
|
+
}
|
|
1293
|
+
return value * Math.exp(-x + a * Math.log(x) - logGamma(a));
|
|
1294
|
+
}
|
|
1295
|
+
function upperGamma(a, x) {
|
|
1296
|
+
if (x <= 0) {
|
|
1297
|
+
return 1;
|
|
1298
|
+
}
|
|
1299
|
+
if (!Number.isFinite(x)) {
|
|
1300
|
+
return 0;
|
|
1301
|
+
}
|
|
1302
|
+
return x < a + 1 ? 1 - lowerGammaSeries(a, x) : upperGammaFraction(a, x);
|
|
1303
|
+
}
|
|
1304
|
+
function normalCdf(z) {
|
|
1305
|
+
if (Number.isNaN(z)) {
|
|
1306
|
+
return Number.NaN;
|
|
1307
|
+
}
|
|
1308
|
+
const lower = 0.5 * upperGamma(0.5, 0.5 * z * z);
|
|
1309
|
+
return z > 0 ? 1 - lower : lower;
|
|
1310
|
+
}
|
|
1311
|
+
var BELOW_ONE = 1 - Number.EPSILON / 2;
|
|
1312
|
+
var INVERSE_MAX_STEPS = 200;
|
|
1313
|
+
function inverseGuess(p, a, b) {
|
|
1314
|
+
if (a >= 1 && b >= 1) {
|
|
1315
|
+
const tail = p < 0.5 ? p : 1 - p;
|
|
1316
|
+
const t = Math.sqrt(-2 * Math.log(tail));
|
|
1317
|
+
const normal = (p < 0.5 ? -1 : 1) * ((2.30753 + t * 0.27061) / (1 + t * (0.99229 + t * 0.04481)) - t);
|
|
1318
|
+
const scale = (normal * normal - 3) / 6;
|
|
1319
|
+
const harmonic = 2 / (1 / (2 * a - 1) + 1 / (2 * b - 1));
|
|
1320
|
+
const w = normal * Math.sqrt(scale + harmonic) / harmonic - (1 / (2 * b - 1) - 1 / (2 * a - 1)) * (scale + 5 / 6 - 2 / (3 * harmonic));
|
|
1321
|
+
return a / (a + b * Math.exp(2 * w));
|
|
1322
|
+
}
|
|
1323
|
+
const lower = Math.exp(a * Math.log(a / (a + b))) / a;
|
|
1324
|
+
const upper = Math.exp(b * Math.log(b / (a + b))) / b;
|
|
1325
|
+
const total = lower + upper;
|
|
1326
|
+
if (p < lower / total) {
|
|
1327
|
+
return Math.pow(a * total * p, 1 / a);
|
|
1328
|
+
}
|
|
1329
|
+
return 1 - Math.pow(b * total * (1 - p), 1 / b);
|
|
1330
|
+
}
|
|
1331
|
+
function inverseIncompleteBeta(p, a, b) {
|
|
1332
|
+
if (Number.isNaN(p) || !(a > 0) || !(b > 0)) {
|
|
1333
|
+
return Number.NaN;
|
|
1334
|
+
}
|
|
1335
|
+
if (p <= 0) {
|
|
1336
|
+
return 0;
|
|
1337
|
+
}
|
|
1338
|
+
if (p >= 1) {
|
|
1339
|
+
return 1;
|
|
1340
|
+
}
|
|
1341
|
+
const logBetaValue = logBeta(a, b);
|
|
1342
|
+
let lower = 0;
|
|
1343
|
+
let upper = 1;
|
|
1344
|
+
let x = inverseGuess(p, a, b);
|
|
1345
|
+
if (!(x > 0) || !(x < 1)) {
|
|
1346
|
+
x = 0.5;
|
|
1347
|
+
}
|
|
1348
|
+
for (let step = 0;step < INVERSE_MAX_STEPS; step += 1) {
|
|
1349
|
+
const residual = incompleteBeta(x, a, b) - p;
|
|
1350
|
+
if (residual < 0) {
|
|
1351
|
+
lower = x;
|
|
1352
|
+
} else {
|
|
1353
|
+
upper = x;
|
|
1354
|
+
}
|
|
1355
|
+
const density = Math.exp((a - 1) * Math.log(x) + (b - 1) * Math.log1p(-x) - logBetaValue);
|
|
1356
|
+
let next = density > 0 && Number.isFinite(density) ? x - residual / density : Number.NaN;
|
|
1357
|
+
if (!(next > lower) || !(next < upper)) {
|
|
1358
|
+
next = 0.5 * (lower + upper);
|
|
1359
|
+
}
|
|
1360
|
+
if (next === x) {
|
|
1361
|
+
break;
|
|
1362
|
+
}
|
|
1363
|
+
const moved = Math.abs(next - x);
|
|
1364
|
+
x = next;
|
|
1365
|
+
if (moved <= Number.EPSILON * x) {
|
|
1366
|
+
break;
|
|
1367
|
+
}
|
|
1368
|
+
}
|
|
1369
|
+
return Math.min(x, BELOW_ONE);
|
|
1370
|
+
}
|
|
1371
|
+
|
|
1372
|
+
// src/core/tdist.ts
|
|
1373
|
+
function isNonCentral(ncp) {
|
|
1374
|
+
return ncp !== undefined && ncp !== 0;
|
|
1375
|
+
}
|
|
1376
|
+
function isBadArgument(value, df, ncp) {
|
|
1377
|
+
return Number.isNaN(value) || !(df > 0) || ncp !== undefined && Number.isNaN(ncp);
|
|
1378
|
+
}
|
|
1379
|
+
function dt(x, df, ncp) {
|
|
1380
|
+
if (isBadArgument(x, df, ncp)) {
|
|
1381
|
+
return Number.NaN;
|
|
1382
|
+
}
|
|
1383
|
+
return isNonCentral(ncp) ? nonCentralDensity(x, df, ncp) : centralDensity(x, df);
|
|
1384
|
+
}
|
|
1385
|
+
function pt(x, df, ncp) {
|
|
1386
|
+
if (isBadArgument(x, df, ncp)) {
|
|
1387
|
+
return Number.NaN;
|
|
1388
|
+
}
|
|
1389
|
+
return isNonCentral(ncp) ? nonCentralProbability(x, df, ncp) : centralProbability(x, df);
|
|
1390
|
+
}
|
|
1391
|
+
function qt(p, df, ncp) {
|
|
1392
|
+
if (isBadArgument(p, df, ncp) || p < 0 || p > 1) {
|
|
1393
|
+
return Number.NaN;
|
|
1394
|
+
}
|
|
1395
|
+
return isNonCentral(ncp) ? nonCentralQuantile(p, df, ncp) : centralQuantile(p, df);
|
|
1396
|
+
}
|
|
1397
|
+
function centralDensity(x, df) {
|
|
1398
|
+
if (!Number.isFinite(x)) {
|
|
1399
|
+
return 0;
|
|
1400
|
+
}
|
|
1401
|
+
const logDensity = -0.5 * Math.log(df) - logBeta(0.5, df / 2) - (df + 1) / 2 * Math.log1p(x * x / df);
|
|
1402
|
+
return Math.exp(logDensity);
|
|
1403
|
+
}
|
|
1404
|
+
function centralProbability(x, df) {
|
|
1405
|
+
if (x === 0) {
|
|
1406
|
+
return 0.5;
|
|
1407
|
+
}
|
|
1408
|
+
if (x === Number.POSITIVE_INFINITY) {
|
|
1409
|
+
return 1;
|
|
1410
|
+
}
|
|
1411
|
+
if (x === Number.NEGATIVE_INFINITY) {
|
|
1412
|
+
return 0;
|
|
1413
|
+
}
|
|
1414
|
+
const tail = upperTail(Math.abs(x), df);
|
|
1415
|
+
return x < 0 ? tail : 1 - tail;
|
|
1416
|
+
}
|
|
1417
|
+
function upperTail(t, df) {
|
|
1418
|
+
const squared = t * t;
|
|
1419
|
+
if (!Number.isFinite(squared)) {
|
|
1420
|
+
return 0;
|
|
1421
|
+
}
|
|
1422
|
+
const total = df + squared;
|
|
1423
|
+
return 0.5 * incompleteBetaSplit(df / total, squared / total, df / 2, 0.5);
|
|
1424
|
+
}
|
|
1425
|
+
var POLISH_MAX_STEPS = 4;
|
|
1426
|
+
function centralQuantile(p, df) {
|
|
1427
|
+
if (p === 0.5) {
|
|
1428
|
+
return 0;
|
|
1429
|
+
}
|
|
1430
|
+
if (p <= 0) {
|
|
1431
|
+
return Number.NEGATIVE_INFINITY;
|
|
1432
|
+
}
|
|
1433
|
+
if (p >= 1) {
|
|
1434
|
+
return Number.POSITIVE_INFINITY;
|
|
1435
|
+
}
|
|
1436
|
+
const tail = p < 0.5 ? p : 1 - p;
|
|
1437
|
+
const sign = p < 0.5 ? -1 : 1;
|
|
1438
|
+
const twoSided = 2 * tail;
|
|
1439
|
+
let squared;
|
|
1440
|
+
if (twoSided > 0.5) {
|
|
1441
|
+
const near = inverseIncompleteBeta(1 - twoSided, 0.5, df / 2);
|
|
1442
|
+
squared = df * near / (1 - near);
|
|
1443
|
+
} else {
|
|
1444
|
+
const far = inverseIncompleteBeta(twoSided, df / 2, 0.5);
|
|
1445
|
+
squared = df * (1 - far) / far;
|
|
1446
|
+
}
|
|
1447
|
+
return sign * polish(Math.sqrt(squared), tail, df);
|
|
1448
|
+
}
|
|
1449
|
+
function polish(start, tail, df) {
|
|
1450
|
+
let t = start;
|
|
1451
|
+
for (let step = 0;step < POLISH_MAX_STEPS; step += 1) {
|
|
1452
|
+
const density = centralDensity(t, df);
|
|
1453
|
+
if (!(density > 0) || !Number.isFinite(t)) {
|
|
1454
|
+
break;
|
|
1455
|
+
}
|
|
1456
|
+
const move = (upperTail(t, df) - tail) / density;
|
|
1457
|
+
const next = t + move;
|
|
1458
|
+
if (!(next > 0) || !Number.isFinite(next) || Math.abs(move) > 0.25 * t) {
|
|
1459
|
+
break;
|
|
1460
|
+
}
|
|
1461
|
+
if (next === t) {
|
|
1462
|
+
break;
|
|
1463
|
+
}
|
|
1464
|
+
t = next;
|
|
1465
|
+
if (Math.abs(move) <= Number.EPSILON * t) {
|
|
1466
|
+
break;
|
|
1467
|
+
}
|
|
1468
|
+
}
|
|
1469
|
+
return t;
|
|
1470
|
+
}
|
|
1471
|
+
var SERIES_MAX_STEPS = 1000;
|
|
1472
|
+
var SERIES_ERROR_MAX = 0.000000000001;
|
|
1473
|
+
var SERIES_NCP_LIMIT_SQUARED = 2 * Math.LN2 * 1022;
|
|
1474
|
+
var SERIES_DF_LIMIT = 400000;
|
|
1475
|
+
var SQRT_TWO_OVER_PI = Math.sqrt(2 / Math.PI);
|
|
1476
|
+
function nonCentralProbability(x, df, ncp) {
|
|
1477
|
+
if (x === Number.POSITIVE_INFINITY) {
|
|
1478
|
+
return 1;
|
|
1479
|
+
}
|
|
1480
|
+
if (x === Number.NEGATIVE_INFINITY) {
|
|
1481
|
+
return 0;
|
|
1482
|
+
}
|
|
1483
|
+
const reflected = x < 0;
|
|
1484
|
+
const t = reflected ? -x : x;
|
|
1485
|
+
const delta = reflected ? -ncp : ncp;
|
|
1486
|
+
const lower = df > SERIES_DF_LIMIT || delta * delta > SERIES_NCP_LIMIT_SQUARED ? normalApproximation(t, df, delta) : lenthSeries(t, df, delta);
|
|
1487
|
+
return reflected ? 1 - lower : lower;
|
|
1488
|
+
}
|
|
1489
|
+
function normalApproximation(t, df, delta) {
|
|
1490
|
+
const shrink = 1 / (4 * df);
|
|
1491
|
+
const spread = Math.sqrt(1 + t * t * 2 * shrink);
|
|
1492
|
+
return normalCdf((t * (1 - shrink) - delta) / spread);
|
|
1493
|
+
}
|
|
1494
|
+
function lenthSeries(t, df, delta) {
|
|
1495
|
+
const squared = t * t;
|
|
1496
|
+
const total = df + squared;
|
|
1497
|
+
const x = squared / total;
|
|
1498
|
+
const complement = df / total;
|
|
1499
|
+
let sum2 = 0;
|
|
1500
|
+
if (x > 0) {
|
|
1501
|
+
const lambda = delta * delta;
|
|
1502
|
+
let oddWeight = 0.5 * Math.exp(-0.5 * lambda);
|
|
1503
|
+
let evenWeight = SQRT_TWO_OVER_PI * oddWeight * delta;
|
|
1504
|
+
let remaining = 0.5 - oddWeight;
|
|
1505
|
+
if (remaining < 0.0000001) {
|
|
1506
|
+
remaining = -0.5 * Math.expm1(-0.5 * lambda);
|
|
1507
|
+
}
|
|
1508
|
+
let a = 0.5;
|
|
1509
|
+
const b = 0.5 * df;
|
|
1510
|
+
const powered = Math.pow(complement, b);
|
|
1511
|
+
const logBetaValue = logBeta(0.5, b);
|
|
1512
|
+
let oddTerm = incompleteBetaSplit(x, complement, a, b);
|
|
1513
|
+
let oddStep = 2 * powered * Math.exp(a * Math.log(x) - logBetaValue);
|
|
1514
|
+
let evenTerm = 1 - powered;
|
|
1515
|
+
let evenStep = b * x * powered;
|
|
1516
|
+
sum2 = oddWeight * oddTerm + evenWeight * evenTerm;
|
|
1517
|
+
for (let step = 1;step <= SERIES_MAX_STEPS; step += 1) {
|
|
1518
|
+
a += 1;
|
|
1519
|
+
oddTerm -= oddStep;
|
|
1520
|
+
evenTerm -= evenStep;
|
|
1521
|
+
oddStep *= x * (a + b - 1) / a;
|
|
1522
|
+
evenStep *= x * (a + b - 0.5) / (a + 0.5);
|
|
1523
|
+
oddWeight *= lambda / (2 * step);
|
|
1524
|
+
evenWeight *= lambda / (2 * step + 1);
|
|
1525
|
+
remaining -= oddWeight;
|
|
1526
|
+
if (remaining <= 0) {
|
|
1527
|
+
break;
|
|
1528
|
+
}
|
|
1529
|
+
sum2 += oddWeight * oddTerm + evenWeight * evenTerm;
|
|
1530
|
+
if (Math.abs(2 * remaining * (oddTerm - oddStep)) < SERIES_ERROR_MAX) {
|
|
1531
|
+
break;
|
|
1532
|
+
}
|
|
1533
|
+
}
|
|
1534
|
+
}
|
|
1535
|
+
return Math.min(Math.max(sum2 + normalCdf(-delta), 0), 1);
|
|
1536
|
+
}
|
|
1537
|
+
function nonCentralDensity(x, df, ncp) {
|
|
1538
|
+
if (!Number.isFinite(x)) {
|
|
1539
|
+
return 0;
|
|
1540
|
+
}
|
|
1541
|
+
if (Math.abs(x) > Math.sqrt(df * Number.EPSILON)) {
|
|
1542
|
+
const stepped = x * Math.sqrt((df + 2) / df);
|
|
1543
|
+
const difference = nonCentralProbability(stepped, df + 2, ncp) - nonCentralProbability(x, df, ncp);
|
|
1544
|
+
return df / Math.abs(x) * Math.abs(difference);
|
|
1545
|
+
}
|
|
1546
|
+
return Math.exp(-0.5 * Math.log(df) - logBeta(0.5, df / 2) - 0.5 * ncp * ncp);
|
|
1547
|
+
}
|
|
1548
|
+
var QUANTILE_MAX_STEPS = 200;
|
|
1549
|
+
function nonCentralQuantile(p, df, ncp) {
|
|
1550
|
+
if (p <= 0) {
|
|
1551
|
+
return Number.NEGATIVE_INFINITY;
|
|
1552
|
+
}
|
|
1553
|
+
if (p >= 1) {
|
|
1554
|
+
return Number.POSITIVE_INFINITY;
|
|
1555
|
+
}
|
|
1556
|
+
let upper = Math.max(1, ncp);
|
|
1557
|
+
while (Number.isFinite(upper) && nonCentralProbability(upper, df, ncp) < p) {
|
|
1558
|
+
upper *= 2;
|
|
1559
|
+
}
|
|
1560
|
+
let lower = Math.min(-1, -ncp);
|
|
1561
|
+
while (Number.isFinite(lower) && nonCentralProbability(lower, df, ncp) > p) {
|
|
1562
|
+
lower *= 2;
|
|
1563
|
+
}
|
|
1564
|
+
let t = 0.5 * (lower + upper);
|
|
1565
|
+
for (let step = 0;step < QUANTILE_MAX_STEPS; step += 1) {
|
|
1566
|
+
const residual = nonCentralProbability(t, df, ncp) - p;
|
|
1567
|
+
if (residual < 0) {
|
|
1568
|
+
lower = t;
|
|
1569
|
+
} else {
|
|
1570
|
+
upper = t;
|
|
1571
|
+
}
|
|
1572
|
+
const density = nonCentralDensity(t, df, ncp);
|
|
1573
|
+
let next = density > 0 && Number.isFinite(density) ? t - residual / density : Number.NaN;
|
|
1574
|
+
if (!(next > lower) || !(next < upper)) {
|
|
1575
|
+
next = 0.5 * (lower + upper);
|
|
1576
|
+
}
|
|
1577
|
+
if (next === t) {
|
|
1578
|
+
break;
|
|
1579
|
+
}
|
|
1580
|
+
const moved = Math.abs(next - t);
|
|
1581
|
+
t = next;
|
|
1582
|
+
if (moved <= Number.EPSILON * Math.abs(t)) {
|
|
1583
|
+
break;
|
|
1584
|
+
}
|
|
1585
|
+
}
|
|
1586
|
+
return t;
|
|
1587
|
+
}
|
|
1588
|
+
// src/core/ttest.ts
|
|
1589
|
+
var DEFAULT_T_TEST_OPTIONS = {
|
|
1590
|
+
diff: 0.5,
|
|
1591
|
+
sd: 4,
|
|
1592
|
+
n: 100,
|
|
1593
|
+
alpha: 0.05
|
|
1594
|
+
};
|
|
1595
|
+
var FILL_UPPER_QUANTILE = 0.999;
|
|
1596
|
+
function tTestStats(options = {}) {
|
|
1597
|
+
const { diff, sd: sd2, n, alpha } = { ...DEFAULT_T_TEST_OPTIONS, ...options };
|
|
1598
|
+
const df = n - 1;
|
|
1599
|
+
const t = diff / (sd2 / Math.sqrt(n));
|
|
1600
|
+
const criticalValue = qt(1 - alpha, df);
|
|
1601
|
+
const beta = pt(criticalValue, df, t);
|
|
1602
|
+
const power = 1 - beta;
|
|
1603
|
+
const altMedian = qt(0.5, df, t);
|
|
1604
|
+
const altFillFrom = qt(beta, df, t);
|
|
1605
|
+
return {
|
|
1606
|
+
diff,
|
|
1607
|
+
sd: sd2,
|
|
1608
|
+
n,
|
|
1609
|
+
alpha,
|
|
1610
|
+
df,
|
|
1611
|
+
t,
|
|
1612
|
+
criticalValue,
|
|
1613
|
+
beta,
|
|
1614
|
+
power,
|
|
1615
|
+
altMedian,
|
|
1616
|
+
altMedianDensity: dt(altMedian, df, t),
|
|
1617
|
+
altFill: {
|
|
1618
|
+
from: altFillFrom,
|
|
1619
|
+
to: qt(FILL_UPPER_QUANTILE, df, t)
|
|
1620
|
+
},
|
|
1621
|
+
nullFill: {
|
|
1622
|
+
from: criticalValue,
|
|
1623
|
+
to: qt(FILL_UPPER_QUANTILE, df)
|
|
1624
|
+
},
|
|
1625
|
+
errorMatrix: {
|
|
1626
|
+
typeOne: alpha,
|
|
1627
|
+
correctReject: power,
|
|
1628
|
+
correctFailToReject: 1 - alpha,
|
|
1629
|
+
typeTwo: beta,
|
|
1630
|
+
highlightTopRow: altFillFrom < altMedian
|
|
1631
|
+
}
|
|
1632
|
+
};
|
|
1633
|
+
}
|
|
1634
|
+
// src/data/moderationData.ts
|
|
1635
|
+
var moderationData = {
|
|
1636
|
+
y: [
|
|
1637
|
+
-7.272897200071196,
|
|
1638
|
+
-1.8368504105536234,
|
|
1639
|
+
2.4825030269210964,
|
|
1640
|
+
6.0885502472158235,
|
|
1641
|
+
-2.7813932586215913,
|
|
1642
|
+
-1.404547743484975,
|
|
1643
|
+
-2.328370225641195,
|
|
1644
|
+
0.24770227680217238,
|
|
1645
|
+
-1.0630104614416778,
|
|
1646
|
+
-2.0458910688111343,
|
|
1647
|
+
-5.134105295915988,
|
|
1648
|
+
18.10246414109479,
|
|
1649
|
+
-3.4663420296290077,
|
|
1650
|
+
-1.0047522093312424,
|
|
1651
|
+
-0.8247466640897548,
|
|
1652
|
+
0.011971257899126564,
|
|
1653
|
+
-2.4321918294924254,
|
|
1654
|
+
-14.110628278307653,
|
|
1655
|
+
-1.5077118301445713,
|
|
1656
|
+
-3.3519580636316557,
|
|
1657
|
+
0.3422839785409655,
|
|
1658
|
+
-1.4420566151665186,
|
|
1659
|
+
-0.09954101599791107,
|
|
1660
|
+
-4.171757229061835,
|
|
1661
|
+
5.638370200339164,
|
|
1662
|
+
-0.24209932633994577,
|
|
1663
|
+
-0.9294220298667977,
|
|
1664
|
+
-0.1430677581344491,
|
|
1665
|
+
-0.7519187319967391,
|
|
1666
|
+
-2.6149345603765624,
|
|
1667
|
+
1.9162030995224288,
|
|
1668
|
+
2.882308922335612,
|
|
1669
|
+
-4.1366719661768885,
|
|
1670
|
+
-1.5262757249783787,
|
|
1671
|
+
-0.13241561132928859,
|
|
1672
|
+
1.0868995782488633,
|
|
1673
|
+
-5.43223982082246,
|
|
1674
|
+
-1.0526454319438507,
|
|
1675
|
+
-5.336080703693647,
|
|
1676
|
+
0.8037058943922428,
|
|
1677
|
+
0.6456670109243666,
|
|
1678
|
+
-0.580865310517441,
|
|
1679
|
+
5.995820813731591,
|
|
1680
|
+
-3.748881331929856,
|
|
1681
|
+
4.799106863920568,
|
|
1682
|
+
3.917014743258661,
|
|
1683
|
+
-2.52057971625805,
|
|
1684
|
+
1.0052100508228277,
|
|
1685
|
+
-1.2592451123609627,
|
|
1686
|
+
-1.4191054380364576,
|
|
1687
|
+
-2.7688068774363885,
|
|
1688
|
+
-1.1283556266006958,
|
|
1689
|
+
-5.015571911735988,
|
|
1690
|
+
0.7443650892965741,
|
|
1691
|
+
1.3457889732609731,
|
|
1692
|
+
-1.5775936354323048,
|
|
1693
|
+
0.24940175331052905,
|
|
1694
|
+
0.8453453656305834,
|
|
1695
|
+
9.108639550485199,
|
|
1696
|
+
-1.089576439866974,
|
|
1697
|
+
-0.0548347508354996,
|
|
1698
|
+
3.4396743692990768,
|
|
1699
|
+
1.82777129315956,
|
|
1700
|
+
-9.08768179039613,
|
|
1701
|
+
-1.6403441299936004,
|
|
1702
|
+
6.344112012159234,
|
|
1703
|
+
3.0068415808329494,
|
|
1704
|
+
1.0402916701960967,
|
|
1705
|
+
-10.792485667257104,
|
|
1706
|
+
0.19774249672127087,
|
|
1707
|
+
-2.6230964327485333,
|
|
1708
|
+
-1.5340564759379902,
|
|
1709
|
+
1.72657398132359,
|
|
1710
|
+
-4.472744132233102,
|
|
1711
|
+
0.051358701883551766,
|
|
1712
|
+
3.4112581498484817,
|
|
1713
|
+
1.7155585583877218,
|
|
1714
|
+
-2.4096746494084527,
|
|
1715
|
+
-3.658089954388166,
|
|
1716
|
+
-4.219629941316109,
|
|
1717
|
+
5.770611553647898,
|
|
1718
|
+
-4.03685548370302,
|
|
1719
|
+
-1.8534915424952012,
|
|
1720
|
+
-1.4551065653571458,
|
|
1721
|
+
-0.5733313061368043,
|
|
1722
|
+
1.2587331393918402,
|
|
1723
|
+
-0.1807495531690153,
|
|
1724
|
+
-1.7541060455623132,
|
|
1725
|
+
-7.181673967259479,
|
|
1726
|
+
2.268171576280935,
|
|
1727
|
+
-2.2618552849857134,
|
|
1728
|
+
-1.0033432551835315,
|
|
1729
|
+
-1.5998101004488254,
|
|
1730
|
+
13.776336779706227,
|
|
1731
|
+
-1.360193467035638,
|
|
1732
|
+
0.8437705359604352,
|
|
1733
|
+
0.26923062515768637,
|
|
1734
|
+
-6.158720480354257,
|
|
1735
|
+
0.2845594239106771,
|
|
1736
|
+
-3.7313390977173673,
|
|
1737
|
+
2.209575611277963,
|
|
1738
|
+
4.957315515246748,
|
|
1739
|
+
-1.1074416328100625,
|
|
1740
|
+
6.773590899689121,
|
|
1741
|
+
-1.1622855609602678,
|
|
1742
|
+
-0.1468882418607233,
|
|
1743
|
+
-1.8138297100034615,
|
|
1744
|
+
-0.8822977247856844,
|
|
1745
|
+
-2.531058796320521,
|
|
1746
|
+
0.9225068493015128,
|
|
1747
|
+
1.0529999297345707,
|
|
1748
|
+
-1.0226841183124429,
|
|
1749
|
+
-0.6282619133683303,
|
|
1750
|
+
0.047095104558446066,
|
|
1751
|
+
-2.1862971987559754,
|
|
1752
|
+
1.9185432668727294,
|
|
1753
|
+
-1.4157898788687031,
|
|
1754
|
+
3.8559400012867275,
|
|
1755
|
+
4.8196910254616006,
|
|
1756
|
+
1.1859049624611124,
|
|
1757
|
+
2.1292032810255828,
|
|
1758
|
+
-2.580469493916582,
|
|
1759
|
+
0.7806460650847441,
|
|
1760
|
+
-0.7717747448671646,
|
|
1761
|
+
4.302323128742706,
|
|
1762
|
+
1.1280497997890846,
|
|
1763
|
+
-3.3823450205179935,
|
|
1764
|
+
-2.8141768087650334,
|
|
1765
|
+
1.9176969652481777,
|
|
1766
|
+
-1.8022438527307034,
|
|
1767
|
+
0.5676992260859575,
|
|
1768
|
+
-1.1988798479187797,
|
|
1769
|
+
-0.8112714289530125,
|
|
1770
|
+
3.0023694027723717,
|
|
1771
|
+
6.313814189291469,
|
|
1772
|
+
-0.7209131724573175,
|
|
1773
|
+
-0.4934212971184535,
|
|
1774
|
+
-5.311645069538028,
|
|
1775
|
+
1.5864321371634191,
|
|
1776
|
+
-1.3767448543412033,
|
|
1777
|
+
-0.32149297043332475,
|
|
1778
|
+
-2.7378068772633672,
|
|
1779
|
+
0.44512957644982276,
|
|
1780
|
+
1.014415590908376,
|
|
1781
|
+
-0.3398299559551271,
|
|
1782
|
+
3.6424411700938926,
|
|
1783
|
+
-0.804849649687885,
|
|
1784
|
+
0.26491344714041165,
|
|
1785
|
+
1.0255159271802998,
|
|
1786
|
+
-3.4431481857611512,
|
|
1787
|
+
-1.261772843081508,
|
|
1788
|
+
2.4072718264535986,
|
|
1789
|
+
4.18584814419661,
|
|
1790
|
+
0.8444989650012846,
|
|
1791
|
+
0.6890664424805704,
|
|
1792
|
+
-4.930173029208773,
|
|
1793
|
+
1.3181686730513686,
|
|
1794
|
+
-3.950474574106549,
|
|
1795
|
+
0.6996653997568072,
|
|
1796
|
+
-7.733171334446728,
|
|
1797
|
+
0.37966613312994063,
|
|
1798
|
+
2.098048515776042,
|
|
1799
|
+
0.5869676229216956,
|
|
1800
|
+
-0.09533520375390134,
|
|
1801
|
+
-1.3602473690753563,
|
|
1802
|
+
-1.8363341184552933,
|
|
1803
|
+
-5.327197109758741,
|
|
1804
|
+
-2.4667878836756927,
|
|
1805
|
+
-0.8477118043370667,
|
|
1806
|
+
1.794347325600735,
|
|
1807
|
+
-1.1215052328640052,
|
|
1808
|
+
0.9693808927847424,
|
|
1809
|
+
-8.852509568972764,
|
|
1810
|
+
-10.171192748327044,
|
|
1811
|
+
5.515479360644947,
|
|
1812
|
+
0.8133474344349123,
|
|
1813
|
+
1.5442257490953633,
|
|
1814
|
+
-2.3028072866769134,
|
|
1815
|
+
0.6898369821689965,
|
|
1816
|
+
1.9915198521027817,
|
|
1817
|
+
2.832247846170376,
|
|
1818
|
+
0.34764862749885805,
|
|
1819
|
+
-0.5034271262990748,
|
|
1820
|
+
1.1733004427545144,
|
|
1821
|
+
0.18449984894824215,
|
|
1822
|
+
-1.9938893833160367,
|
|
1823
|
+
0.8829064829548916,
|
|
1824
|
+
0.6266797217305222,
|
|
1825
|
+
-3.9311936779928676,
|
|
1826
|
+
-4.693057322327316,
|
|
1827
|
+
0.5942425542793619,
|
|
1828
|
+
-0.04091322810348444,
|
|
1829
|
+
1.5529759191506414,
|
|
1830
|
+
0.5255897517761228,
|
|
1831
|
+
-0.5181252414019946,
|
|
1832
|
+
5.713823828860155,
|
|
1833
|
+
0.8064569522257248,
|
|
1834
|
+
2.7208395005315182,
|
|
1835
|
+
-1.9931388422548042,
|
|
1836
|
+
2.0982441518090775
|
|
1837
|
+
],
|
|
1838
|
+
x: [
|
|
1839
|
+
2.741916894293337,
|
|
1840
|
+
-1.1293963427921776,
|
|
1841
|
+
0.7262568226746783,
|
|
1842
|
+
1.265725209922081,
|
|
1843
|
+
0.8085366462819982,
|
|
1844
|
+
-0.21224903218296798,
|
|
1845
|
+
3.023043994877878,
|
|
1846
|
+
-0.18931807682619511,
|
|
1847
|
+
4.036847427754083,
|
|
1848
|
+
-0.125428198104842,
|
|
1849
|
+
2.6097393084469704,
|
|
1850
|
+
4.573290785402214,
|
|
1851
|
+
-2.7777214022246786,
|
|
1852
|
+
-0.5575775336347428,
|
|
1853
|
+
-0.266642672787316,
|
|
1854
|
+
1.271900796140149,
|
|
1855
|
+
-0.5685058428321448,
|
|
1856
|
+
-5.312910841809552,
|
|
1857
|
+
-4.880933857151038,
|
|
1858
|
+
2.640226691460384,
|
|
1859
|
+
-0.6132771881569492,
|
|
1860
|
+
-3.562616867960001,
|
|
1861
|
+
-0.3438347115192427,
|
|
1862
|
+
2.429349398345198,
|
|
1863
|
+
3.7903869225299305,
|
|
1864
|
+
-0.8609382632123992,
|
|
1865
|
+
-0.5145387655378593,
|
|
1866
|
+
-3.5263261703895603,
|
|
1867
|
+
0.9201947096625428,
|
|
1868
|
+
-1.2799897519202386,
|
|
1869
|
+
0.9109002464824387,
|
|
1870
|
+
1.4096746744576383,
|
|
1871
|
+
2.0702070439398446,
|
|
1872
|
+
-1.2178527508144228,
|
|
1873
|
+
1.0099102465959404,
|
|
1874
|
+
-3.434017358146686,
|
|
1875
|
+
-1.5689180167589927,
|
|
1876
|
+
-1.7018151883530368,
|
|
1877
|
+
-4.828415299893265,
|
|
1878
|
+
0.07224521378451125,
|
|
1879
|
+
0.41199720040050775,
|
|
1880
|
+
-0.7221145970973324,
|
|
1881
|
+
1.516326471399034,
|
|
1882
|
+
-1.4534096541531503,
|
|
1883
|
+
-2.736562088838589,
|
|
1884
|
+
0.8656360517774341,
|
|
1885
|
+
-1.6227863523733432,
|
|
1886
|
+
2.8882025234425055,
|
|
1887
|
+
-0.8628924052266909,
|
|
1888
|
+
1.3112957668044132,
|
|
1889
|
+
0.6438505304078931,
|
|
1890
|
+
-1.5676778817607508,
|
|
1891
|
+
3.1514550395839547,
|
|
1892
|
+
1.285798611434633,
|
|
1893
|
+
0.17952129319921128,
|
|
1894
|
+
0.5531014945829259,
|
|
1895
|
+
1.3585776321105416,
|
|
1896
|
+
0.17966577315816343,
|
|
1897
|
+
-5.98618016630587,
|
|
1898
|
+
0.5697659070613188,
|
|
1899
|
+
-0.7344692854819507,
|
|
1900
|
+
0.3704611297312187,
|
|
1901
|
+
1.1636474547310138,
|
|
1902
|
+
2.799473654585356,
|
|
1903
|
+
-1.45458411894893,
|
|
1904
|
+
2.605085264088287,
|
|
1905
|
+
0.6716962395041488,
|
|
1906
|
+
2.0770121973952422,
|
|
1907
|
+
1.8414571365812926,
|
|
1908
|
+
1.4417563257337251,
|
|
1909
|
+
-2.08623787713571,
|
|
1910
|
+
-0.18037277322141337,
|
|
1911
|
+
1.2470363239990871,
|
|
1912
|
+
-1.907046715544688,
|
|
1913
|
+
-1.0856576291477136,
|
|
1914
|
+
1.1619929953633648,
|
|
1915
|
+
1.536357475669182,
|
|
1916
|
+
0.9275351770803344,
|
|
1917
|
+
-1.771552594819359,
|
|
1918
|
+
-2.199561797295711,
|
|
1919
|
+
3.025414019609856,
|
|
1920
|
+
0.5158428750640617,
|
|
1921
|
+
0.17688045831917268,
|
|
1922
|
+
-0.24179307507817885,
|
|
1923
|
+
-2.3886577903210564,
|
|
1924
|
+
1.2239937960807739,
|
|
1925
|
+
-0.4342796914930417,
|
|
1926
|
+
-0.3655134126638434,
|
|
1927
|
+
1.8666926571423201,
|
|
1928
|
+
1.643546221016498,
|
|
1929
|
+
2.784232751868542,
|
|
1930
|
+
-0.9523478461093488,
|
|
1931
|
+
1.3006971214526097,
|
|
1932
|
+
2.7822209127800015,
|
|
1933
|
+
-2.2215777588957977,
|
|
1934
|
+
-1.7215851737556847,
|
|
1935
|
+
-2.26347736170754,
|
|
1936
|
+
-2.9184279990047917,
|
|
1937
|
+
0.15996510648232234,
|
|
1938
|
+
1.30640867929838,
|
|
1939
|
+
2.4019307511969883,
|
|
1940
|
+
2.0895021743354505,
|
|
1941
|
+
-2.006417293679696,
|
|
1942
|
+
3.696963803345493,
|
|
1943
|
+
-1.3335468175156342,
|
|
1944
|
+
0.21102762491213886,
|
|
1945
|
+
-0.8445117637377121,
|
|
1946
|
+
-0.24470034390994258,
|
|
1947
|
+
0.37638606900299576,
|
|
1948
|
+
0.23832191599401276,
|
|
1949
|
+
-0.050185101734805754,
|
|
1950
|
+
0.2161454558840652,
|
|
1951
|
+
-0.9708704716933354,
|
|
1952
|
+
-1.0084342613758068,
|
|
1953
|
+
-3.322198159829624,
|
|
1954
|
+
-0.7646674537476362,
|
|
1955
|
+
-1.025300515755601,
|
|
1956
|
+
5.403782000689595,
|
|
1957
|
+
-2.7242324623794376,
|
|
1958
|
+
0.2745124371172138,
|
|
1959
|
+
-2.987250134632581,
|
|
1960
|
+
-2.940871482873593,
|
|
1961
|
+
0.2494047723940136,
|
|
1962
|
+
-1.9932782697680742,
|
|
1963
|
+
-0.0036452286094163897,
|
|
1964
|
+
-0.8565177628516306,
|
|
1965
|
+
-1.2273432128989903,
|
|
1966
|
+
-4.049355690838215,
|
|
1967
|
+
-2.449495900719972,
|
|
1968
|
+
0.3590328822358758,
|
|
1969
|
+
1.1352411888470701,
|
|
1970
|
+
-0.9857547071069496,
|
|
1971
|
+
0.00012576813070224816,
|
|
1972
|
+
2.245779286759933,
|
|
1973
|
+
2.87971148595238,
|
|
1974
|
+
-2.1942275368116433,
|
|
1975
|
+
-0.23463912050035363,
|
|
1976
|
+
2.4029968018394023,
|
|
1977
|
+
-0.9394591611326017,
|
|
1978
|
+
-0.10493896987799263,
|
|
1979
|
+
-0.17221459647417905,
|
|
1980
|
+
-1.775358035812863,
|
|
1981
|
+
-0.8893680097694762,
|
|
1982
|
+
-0.05888975817647629,
|
|
1983
|
+
-0.8277376981158471,
|
|
1984
|
+
2.2267720467364067,
|
|
1985
|
+
-0.9619856833079639,
|
|
1986
|
+
-0.8663380652014581,
|
|
1987
|
+
1.3937251531042052,
|
|
1988
|
+
-2.1127368263418163,
|
|
1989
|
+
-0.08139695030242985,
|
|
1990
|
+
-3.1030896446951792,
|
|
1991
|
+
2.334339098471364,
|
|
1992
|
+
-0.5472914027481613,
|
|
1993
|
+
-0.9356906493445072,
|
|
1994
|
+
-2.4765046559724295,
|
|
1995
|
+
-0.015524067554653256,
|
|
1996
|
+
-1.600564355903319,
|
|
1997
|
+
-1.066984659900873,
|
|
1998
|
+
2.5753504911691776,
|
|
1999
|
+
-0.35105174048425475,
|
|
2000
|
+
-2.143564768301352,
|
|
2001
|
+
0.3264137649347646,
|
|
2002
|
+
-0.7254768312558996,
|
|
2003
|
+
1.1800270959746773,
|
|
2004
|
+
2.864843855461979,
|
|
2005
|
+
-1.9853850222189862,
|
|
2006
|
+
0.9093005951605652,
|
|
2007
|
+
0.16979611735697459,
|
|
2008
|
+
1.7911311645290895,
|
|
2009
|
+
-0.45955627789253306,
|
|
2010
|
+
1.6732381369212268,
|
|
2011
|
+
-3.4901117226733867,
|
|
2012
|
+
3.378917842626747,
|
|
2013
|
+
1.729555957037156,
|
|
2014
|
+
-0.3015519777714956,
|
|
2015
|
+
-2.898014260278335,
|
|
2016
|
+
1.2860174000839635,
|
|
2017
|
+
0.9663877276295352,
|
|
2018
|
+
-0.012711252842777425,
|
|
2019
|
+
0.30291178572484884,
|
|
2020
|
+
-1.1682179406996087,
|
|
2021
|
+
0.7376134652604842,
|
|
2022
|
+
0.5893086794390312,
|
|
2023
|
+
-0.5585187466851501,
|
|
2024
|
+
-2.672473309786308,
|
|
2025
|
+
1.4014976368800685,
|
|
2026
|
+
1.108393244548067,
|
|
2027
|
+
-1.67261318560283,
|
|
2028
|
+
-3.189176324012486,
|
|
2029
|
+
0.40991716117526766,
|
|
2030
|
+
-0.6901759559457797,
|
|
2031
|
+
0.5052234067289093,
|
|
2032
|
+
-2.5880049309691064,
|
|
2033
|
+
-1.918340888760726,
|
|
2034
|
+
2.1715497073598016,
|
|
2035
|
+
0.8075498094314278,
|
|
2036
|
+
1.1729750734385955,
|
|
2037
|
+
3.630456892307906,
|
|
2038
|
+
0.2576428572047662
|
|
2039
|
+
],
|
|
2040
|
+
z: [
|
|
2041
|
+
-4.001858475463022,
|
|
2042
|
+
0.6675543948671404,
|
|
2043
|
+
2.342650254717589,
|
|
2044
|
+
4.119078484598608,
|
|
2045
|
+
-2.7537231964810425,
|
|
2046
|
+
-2.301711131254211,
|
|
2047
|
+
-1.4116427895202421,
|
|
2048
|
+
-2.108111564154383,
|
|
2049
|
+
-1.2914874462849828,
|
|
2050
|
+
-0.3707559353530068,
|
|
2051
|
+
-2.4024441014799702,
|
|
2052
|
+
4.0739443339663,
|
|
2053
|
+
0.2155494897710934,
|
|
2054
|
+
-0.16821620101116128,
|
|
2055
|
+
0.9912392832091879,
|
|
2056
|
+
0.07483037223593068,
|
|
2057
|
+
-0.2641760739118198,
|
|
2058
|
+
2.9535748471041936,
|
|
2059
|
+
-0.43406042018420693,
|
|
2060
|
+
-2.5672044081844505,
|
|
2061
|
+
0.7713357808868047,
|
|
2062
|
+
-0.7030257470581849,
|
|
2063
|
+
-1.0435921867125384,
|
|
2064
|
+
-2.1362624013743408,
|
|
2065
|
+
0.856731806533385,
|
|
2066
|
+
-0.3480364688539898,
|
|
2067
|
+
1.0313354572960587,
|
|
2068
|
+
-0.468730554611843,
|
|
2069
|
+
-1.3170068516435418,
|
|
2070
|
+
2.500473208157433,
|
|
2071
|
+
-0.5435274302227936,
|
|
2072
|
+
1.8959039917503921,
|
|
2073
|
+
-2.4031648602178755,
|
|
2074
|
+
-0.9322321927510044,
|
|
2075
|
+
-0.5387027903063599,
|
|
2076
|
+
-0.7819308162617215,
|
|
2077
|
+
2.697414023983427,
|
|
2078
|
+
-0.04552940259682519,
|
|
2079
|
+
0.4884517022069007,
|
|
2080
|
+
-1.8847434157278455,
|
|
2081
|
+
-1.4584345530191491,
|
|
2082
|
+
1.9961378171096964,
|
|
2083
|
+
2.5169633291911206,
|
|
2084
|
+
2.497727377620192,
|
|
2085
|
+
-2.7612740990506603,
|
|
2086
|
+
4.099921387272788,
|
|
2087
|
+
2.0337456595990835,
|
|
2088
|
+
-0.053434928276515886,
|
|
2089
|
+
1.407215557596521,
|
|
2090
|
+
-1.9427704583037273,
|
|
2091
|
+
-2.1923124831177145,
|
|
2092
|
+
0.09810090187519412,
|
|
2093
|
+
-2.39699171311556,
|
|
2094
|
+
0.380037997119496,
|
|
2095
|
+
2.5954117992093324,
|
|
2096
|
+
-2.067747445929502,
|
|
2097
|
+
-1.4768815084261278,
|
|
2098
|
+
0.0931278789017267,
|
|
2099
|
+
-2.0351922396522117,
|
|
2100
|
+
-0.7665679197725563,
|
|
2101
|
+
1.745510823342797,
|
|
2102
|
+
1.939090027940234,
|
|
2103
|
+
0.7676933300541888,
|
|
2104
|
+
-3.703111326134929,
|
|
2105
|
+
-0.10799347365233136,
|
|
2106
|
+
2.1295464286765675,
|
|
2107
|
+
1.62639007482321,
|
|
2108
|
+
-0.38163294820856347,
|
|
2109
|
+
-5.399859617127405,
|
|
2110
|
+
0.1219332776022331,
|
|
2111
|
+
1.1475033949671558,
|
|
2112
|
+
0.09160715945019773,
|
|
2113
|
+
0.3148250803517509,
|
|
2114
|
+
0.8631307457375416,
|
|
2115
|
+
-0.7930994721004244,
|
|
2116
|
+
2.6199564515030986,
|
|
2117
|
+
0.940786799747886,
|
|
2118
|
+
-2.4853405411492027,
|
|
2119
|
+
2.763150912771066,
|
|
2120
|
+
2.4089178740196484,
|
|
2121
|
+
1.648147927363762,
|
|
2122
|
+
-3.3252588043711886,
|
|
2123
|
+
-1.1386126872109616,
|
|
2124
|
+
1.2710276345790525,
|
|
2125
|
+
0.08744401515571584,
|
|
2126
|
+
0.6960246073226142,
|
|
2127
|
+
4.91918709774083,
|
|
2128
|
+
-1.636760648799339,
|
|
2129
|
+
-4.226400229858274,
|
|
2130
|
+
0.5473905448608293,
|
|
2131
|
+
-1.3751936824868236,
|
|
2132
|
+
0.8920821059237314,
|
|
2133
|
+
-1.6247694475811947,
|
|
2134
|
+
4.424110960670272,
|
|
2135
|
+
-0.24741194315485965,
|
|
2136
|
+
-0.9546710120601046,
|
|
2137
|
+
-0.3325229829732164,
|
|
2138
|
+
1.7251267672528618,
|
|
2139
|
+
0.19468097040563787,
|
|
2140
|
+
-3.2512334784225763,
|
|
2141
|
+
-0.00924153565291559,
|
|
2142
|
+
1.5204843353241098,
|
|
2143
|
+
0.07798182574512524,
|
|
2144
|
+
1.4701442832288814,
|
|
2145
|
+
-0.2929452539977395,
|
|
2146
|
+
-0.11577467076256517,
|
|
2147
|
+
0.9647389322090653,
|
|
2148
|
+
1.9858872735514685,
|
|
2149
|
+
-2.4927909960640844,
|
|
2150
|
+
-0.06697504969121937,
|
|
2151
|
+
-0.1419243624999926,
|
|
2152
|
+
-1.517841307461901,
|
|
2153
|
+
-2.0687187218783056,
|
|
2154
|
+
-1.2614639079416392,
|
|
2155
|
+
1.1736154400259904,
|
|
2156
|
+
-0.8326453123673038,
|
|
2157
|
+
-1.5697756190106404,
|
|
2158
|
+
0.32683263897219417,
|
|
2159
|
+
-2.4734284702186993,
|
|
2160
|
+
2.091747552473343,
|
|
2161
|
+
-0.9691908324374187,
|
|
2162
|
+
0.37825762336133084,
|
|
2163
|
+
0.1020126631499464,
|
|
2164
|
+
-0.0004813377728839536,
|
|
2165
|
+
3.618764084885732,
|
|
2166
|
+
-1.6506559142198896,
|
|
2167
|
+
2.2909409044559705,
|
|
2168
|
+
0.06314637510648614,
|
|
2169
|
+
-1.6704116106134392,
|
|
2170
|
+
-0.13752729803302502,
|
|
2171
|
+
1.4935433740249338,
|
|
2172
|
+
-0.8510374687892082,
|
|
2173
|
+
-1.5441644703869135,
|
|
2174
|
+
0.30552821349578935,
|
|
2175
|
+
1.977193690357598,
|
|
2176
|
+
-0.14691666934434808,
|
|
2177
|
+
-2.774053107119484,
|
|
2178
|
+
-2.6133518087507257,
|
|
2179
|
+
-1.5367906501558772,
|
|
2180
|
+
-1.0542162507489161,
|
|
2181
|
+
-0.04285413008324925,
|
|
2182
|
+
1.3409961419925638,
|
|
2183
|
+
-0.869234077144813,
|
|
2184
|
+
-2.2277595666256937,
|
|
2185
|
+
1.2142119897569996,
|
|
2186
|
+
0.5509139374791826,
|
|
2187
|
+
2.3146941388294735,
|
|
2188
|
+
-3.3649617189565113,
|
|
2189
|
+
0.17463817778974872,
|
|
2190
|
+
2.706723787885337,
|
|
2191
|
+
1.4483476013667382,
|
|
2192
|
+
-1.665105651528423,
|
|
2193
|
+
1.4650569735368288,
|
|
2194
|
+
-1.7438537399944254,
|
|
2195
|
+
-0.9067950224595854,
|
|
2196
|
+
2.3750685572581554,
|
|
2197
|
+
-0.5802906235801585,
|
|
2198
|
+
1.6570922899308729,
|
|
2199
|
+
-0.5824554175583375,
|
|
2200
|
+
-3.1527248094958122,
|
|
2201
|
+
-1.6976313938978438,
|
|
2202
|
+
-2.1770397239143895,
|
|
2203
|
+
-0.968581142234347,
|
|
2204
|
+
-0.6726224180191456,
|
|
2205
|
+
-0.3067157813232876,
|
|
2206
|
+
-0.4864944571496919,
|
|
2207
|
+
3.784404083200223,
|
|
2208
|
+
-2.7719966745708398,
|
|
2209
|
+
-0.8296486014155607,
|
|
2210
|
+
0.6981630562136623,
|
|
2211
|
+
3.256884531630813,
|
|
2212
|
+
0.17704379142723264,
|
|
2213
|
+
2.478301416801126,
|
|
2214
|
+
-3.2891110715074134,
|
|
2215
|
+
2.892713050756959,
|
|
2216
|
+
-1.3811203433303656,
|
|
2217
|
+
-0.5528621709077041,
|
|
2218
|
+
-2.2188375197988743,
|
|
2219
|
+
0.26773863285890354,
|
|
2220
|
+
3.570678103463104,
|
|
2221
|
+
4.8443267105031795,
|
|
2222
|
+
-2.1536578042368233,
|
|
2223
|
+
0.971882220806198,
|
|
2224
|
+
2.7770434774885837,
|
|
2225
|
+
-0.3913136345690878,
|
|
2226
|
+
-0.43634959541242196,
|
|
2227
|
+
-0.6095559090928905,
|
|
2228
|
+
1.195665448222886,
|
|
2229
|
+
2.7948588216315193,
|
|
2230
|
+
1.3752395224799268,
|
|
2231
|
+
0.6403760285939373,
|
|
2232
|
+
-0.6037398515545216,
|
|
2233
|
+
0.9966973711158991,
|
|
2234
|
+
-1.0990738358335126,
|
|
2235
|
+
-0.5585130072744854,
|
|
2236
|
+
2.1930268862234645,
|
|
2237
|
+
0.8840261763576225,
|
|
2238
|
+
0.48203258805222476,
|
|
2239
|
+
-0.5112153107110367,
|
|
2240
|
+
1.8620658029697648
|
|
2241
|
+
],
|
|
2242
|
+
w: [
|
|
2243
|
+
-0.49696586637986573,
|
|
2244
|
+
0.8446407716647866,
|
|
2245
|
+
1.9753065872793472,
|
|
2246
|
+
1.6711363440700464,
|
|
2247
|
+
-1.321043718091411,
|
|
2248
|
+
3.1281389865836706,
|
|
2249
|
+
-3.245951870583094,
|
|
2250
|
+
1.727792746957976,
|
|
2251
|
+
-1.0232055468724002,
|
|
2252
|
+
-3.8347300508908027,
|
|
2253
|
+
-3.731627697239897,
|
|
2254
|
+
0.4903581268793773,
|
|
2255
|
+
4.447068603816132,
|
|
2256
|
+
0.5467521282889305,
|
|
2257
|
+
2.261569589056565,
|
|
2258
|
+
1.6773387481201982,
|
|
2259
|
+
-1.3092305557564106,
|
|
2260
|
+
1.907921956958791,
|
|
2261
|
+
0.7059029309786451,
|
|
2262
|
+
0.4131985852992269,
|
|
2263
|
+
2.002226468620497,
|
|
2264
|
+
1.4949039832805529,
|
|
2265
|
+
-1.2531496734462773,
|
|
2266
|
+
0.7904473718734281,
|
|
2267
|
+
-1.7843359080911552,
|
|
2268
|
+
1.2616368201835455,
|
|
2269
|
+
-0.865410366569135,
|
|
2270
|
+
0.9042772884370336,
|
|
2271
|
+
0.7359980903926909,
|
|
2272
|
+
-0.5407749793238182,
|
|
2273
|
+
0.9310252354259575,
|
|
2274
|
+
1.1487124512539382,
|
|
2275
|
+
-0.4613999803148218,
|
|
2276
|
+
2.3444850166049025,
|
|
2277
|
+
2.7854054134662434,
|
|
2278
|
+
-1.3239825114191694,
|
|
2279
|
+
-1.5547384134500284,
|
|
2280
|
+
1.0270771578175601,
|
|
2281
|
+
-1.8266244760825443,
|
|
2282
|
+
-0.8988476105006012,
|
|
2283
|
+
1.6058653982072366,
|
|
2284
|
+
-1.1469537019622078,
|
|
2285
|
+
-3.8562503356966524,
|
|
2286
|
+
1.3287816676442965,
|
|
2287
|
+
-3.205080447588506,
|
|
2288
|
+
-2.709200514907699,
|
|
2289
|
+
-6.0358653588357924,
|
|
2290
|
+
1.6624756444601259,
|
|
2291
|
+
0.502194177694688,
|
|
2292
|
+
0.9245869318826591,
|
|
2293
|
+
1.6895844464464735,
|
|
2294
|
+
-0.0839430486431242,
|
|
2295
|
+
-2.2111518114000552,
|
|
2296
|
+
1.1275513046617478,
|
|
2297
|
+
2.606729511639341,
|
|
2298
|
+
-3.0004418755893147,
|
|
2299
|
+
-1.2139784702380723,
|
|
2300
|
+
-0.5844901313131545,
|
|
2301
|
+
-2.579366686647206,
|
|
2302
|
+
1.3882116975493977,
|
|
2303
|
+
-1.198363805794238,
|
|
2304
|
+
2.5138132879432744,
|
|
2305
|
+
0.10701602725472605,
|
|
2306
|
+
1.456185035343705,
|
|
2307
|
+
3.1221961364212145,
|
|
2308
|
+
0.5312495071943797,
|
|
2309
|
+
2.1534525184145346,
|
|
2310
|
+
0.4213958633584909,
|
|
2311
|
+
-3.0233470516447363,
|
|
2312
|
+
0.044804519827250026,
|
|
2313
|
+
1.4362724120842063,
|
|
2314
|
+
0.9789140356845396,
|
|
2315
|
+
-0.34777669175653925,
|
|
2316
|
+
-2.435398977336801,
|
|
2317
|
+
1.292796737418924,
|
|
2318
|
+
-1.8329120566957797,
|
|
2319
|
+
-2.5036468848511326,
|
|
2320
|
+
1.1898554368110026,
|
|
2321
|
+
-2.465621330379653,
|
|
2322
|
+
0.48872882981227517,
|
|
2323
|
+
0.005544370687190118,
|
|
2324
|
+
-2.65641937205113,
|
|
2325
|
+
2.35939282366555,
|
|
2326
|
+
-1.1856099982978574,
|
|
2327
|
+
2.399956632673344,
|
|
2328
|
+
-0.9500673592242697,
|
|
2329
|
+
-1.1501141923016327,
|
|
2330
|
+
-0.06245204921699806,
|
|
2331
|
+
-0.7161139919569112,
|
|
2332
|
+
-0.7132021552954626,
|
|
2333
|
+
-1.7553287668674522,
|
|
2334
|
+
-2.4257942503559256,
|
|
2335
|
+
1.2265732373740676,
|
|
2336
|
+
-1.6124066829249548,
|
|
2337
|
+
-2.7529138598177902,
|
|
2338
|
+
-1.0156957983270942,
|
|
2339
|
+
-1.6018709734746754,
|
|
2340
|
+
-4.385571371340135,
|
|
2341
|
+
-0.5818742985353497,
|
|
2342
|
+
0.3343482423210354,
|
|
2343
|
+
0.5893847129713754,
|
|
2344
|
+
0.7854825308414517,
|
|
2345
|
+
-2.0016874260016304,
|
|
2346
|
+
-0.6514542396468678,
|
|
2347
|
+
-2.016697610857685,
|
|
2348
|
+
-1.2708629640675322,
|
|
2349
|
+
-2.419681375974685,
|
|
2350
|
+
-2.2329276013053807,
|
|
2351
|
+
1.2597623251518704,
|
|
2352
|
+
-0.5450431403572512,
|
|
2353
|
+
-0.5176823367573421,
|
|
2354
|
+
3.459116359782987,
|
|
2355
|
+
-0.11678433081474329,
|
|
2356
|
+
-1.0741275692178371,
|
|
2357
|
+
1.4945733923314797,
|
|
2358
|
+
-0.9745156707794659,
|
|
2359
|
+
2.7458155629590104,
|
|
2360
|
+
-0.7553447212924415,
|
|
2361
|
+
-1.2323052282367883,
|
|
2362
|
+
-2.3362501021493713,
|
|
2363
|
+
0.6572807171821716,
|
|
2364
|
+
2.93302125488033,
|
|
2365
|
+
-0.7120190901778252,
|
|
2366
|
+
0.5229352837548622,
|
|
2367
|
+
0.6666577103514623,
|
|
2368
|
+
2.8443864806190855,
|
|
2369
|
+
1.3277532013323454,
|
|
2370
|
+
-2.147310311390621,
|
|
2371
|
+
-1.3938035493334802,
|
|
2372
|
+
-1.4922609138691223,
|
|
2373
|
+
0.2831457442644387,
|
|
2374
|
+
-0.007894441461306145,
|
|
2375
|
+
0.7358750021835311,
|
|
2376
|
+
-1.314685829394494,
|
|
2377
|
+
-0.7526933403221041,
|
|
2378
|
+
1.4827201208501692,
|
|
2379
|
+
-0.19921351289694036,
|
|
2380
|
+
-1.3085797572141897,
|
|
2381
|
+
1.942328748122542,
|
|
2382
|
+
0.02699262313677293,
|
|
2383
|
+
-1.83306933404309,
|
|
2384
|
+
3.4193771225232736,
|
|
2385
|
+
-2.3362019798933114,
|
|
2386
|
+
-3.562072520305047,
|
|
2387
|
+
-4.506264897136434,
|
|
2388
|
+
1.3022519390933986,
|
|
2389
|
+
-1.0656652512055347,
|
|
2390
|
+
-0.5511187139445664,
|
|
2391
|
+
0.5792539452580913,
|
|
2392
|
+
-0.9329681235815281,
|
|
2393
|
+
-3.216120349889375,
|
|
2394
|
+
-3.899567592083976,
|
|
2395
|
+
-0.6813998364619616,
|
|
2396
|
+
0.3494510546626397,
|
|
2397
|
+
-4.555555121554458,
|
|
2398
|
+
0.5810489618941678,
|
|
2399
|
+
0.8446127149472286,
|
|
2400
|
+
2.5894747264134295,
|
|
2401
|
+
0.32942803714790875,
|
|
2402
|
+
0.4099076816004704,
|
|
2403
|
+
1.2092072534516118,
|
|
2404
|
+
-0.03828153833786284,
|
|
2405
|
+
-0.15942869475761728,
|
|
2406
|
+
0.2319689288056091,
|
|
2407
|
+
1.4883467280967757,
|
|
2408
|
+
-0.8625812070553853,
|
|
2409
|
+
-0.9990579038224688,
|
|
2410
|
+
-1.7303241535114227,
|
|
2411
|
+
-1.915514566543747,
|
|
2412
|
+
0.6535999426772754,
|
|
2413
|
+
3.09474445137035,
|
|
2414
|
+
-1.9377191377288612,
|
|
2415
|
+
-0.37688085251892567,
|
|
2416
|
+
-2.060002322911837,
|
|
2417
|
+
1.816172883720992,
|
|
2418
|
+
-0.634763729416257,
|
|
2419
|
+
0.35800793308084394,
|
|
2420
|
+
0.6960563754583543,
|
|
2421
|
+
-2.1085580425488804,
|
|
2422
|
+
-0.2094885762214029,
|
|
2423
|
+
-0.45668523418905876,
|
|
2424
|
+
1.3507111248550778,
|
|
2425
|
+
-2.4664893870126186,
|
|
2426
|
+
-2.3999230888455583,
|
|
2427
|
+
1.5317331532322156,
|
|
2428
|
+
-1.1761957225659538,
|
|
2429
|
+
-1.3205916599511518,
|
|
2430
|
+
0.22602704439468746,
|
|
2431
|
+
-0.6407975993720775,
|
|
2432
|
+
3.7327629938775755,
|
|
2433
|
+
0.5190629115355975,
|
|
2434
|
+
0.32312014183613985,
|
|
2435
|
+
1.8621498186707084,
|
|
2436
|
+
-0.11989349851924225,
|
|
2437
|
+
0.09747940015490032,
|
|
2438
|
+
-2.1457508014679263,
|
|
2439
|
+
-4.585942860961345,
|
|
2440
|
+
-2.414413700128684,
|
|
2441
|
+
0.22821885959438232,
|
|
2442
|
+
-2.066594158210754
|
|
2443
|
+
]
|
|
2444
|
+
};
|
|
2445
|
+
// src/data/pcaDegenerate.ts
|
|
2446
|
+
var pcaDegenerate = [
|
|
2447
|
+
{ x: 0.0548817670573706, y: 34.740158547327 },
|
|
2448
|
+
{ x: 0.0548817670573706, y: 24.6785012534724 },
|
|
2449
|
+
{ x: 0.0548817670573706, y: 10.0433633715021 },
|
|
2450
|
+
{ x: -0.493935903516516, y: -0.567111592926364 },
|
|
2451
|
+
{ x: -0.128057456467252, y: -10.0799512162071 },
|
|
2452
|
+
{ x: 1.88427400230367, y: -16.6657632630937 },
|
|
2453
|
+
{ x: -40.7405650789349, y: -17.7633986042415 },
|
|
2454
|
+
{ x: -22.8125211735212, y: -17.2145809336676 },
|
|
2455
|
+
{ x: -7.4456263974524, y: -16.8487024866183 },
|
|
2456
|
+
{ x: 7.73832915509179, y: -16.4828240395691 },
|
|
2457
|
+
{ x: 22.3734670370621, y: -15.9340063689952 },
|
|
2458
|
+
{ x: 38.4721187072295, y: -15.7510671454706 },
|
|
2459
|
+
{ x: 14.8729588725523, y: -16.2998848160445 },
|
|
2460
|
+
{ x: 0.237820990582013, y: 18.6415068771597 },
|
|
2461
|
+
{ x: -1.0427535740904, y: -18.3122162748154 },
|
|
2462
|
+
{ x: -12.0191069855681, y: -16.8487024866183 }
|
|
2463
|
+
];
|
|
2464
|
+
// src/plot/target.ts
|
|
2465
|
+
function resolveTarget(target) {
|
|
2466
|
+
if ("ctx" in target) {
|
|
2467
|
+
return target;
|
|
2468
|
+
}
|
|
2469
|
+
const ctx = target.getContext("2d");
|
|
2470
|
+
if (ctx === null) {
|
|
2471
|
+
throw new Error("plot target: the canvas gave no 2D context. In tests, pass " + "{ ctx, width, height } instead of a canvas element.");
|
|
2472
|
+
}
|
|
2473
|
+
return { ctx, width: target.width, height: target.height };
|
|
2474
|
+
}
|
|
2475
|
+
// src/plot/axes.ts
|
|
2476
|
+
var DEFAULT_MARGINS = {
|
|
2477
|
+
top: 20,
|
|
2478
|
+
right: 20,
|
|
2479
|
+
bottom: 44,
|
|
2480
|
+
left: 52
|
|
2481
|
+
};
|
|
2482
|
+
var DEFAULT_TICK_COUNT = 5;
|
|
2483
|
+
var TICK_LENGTH = 5;
|
|
2484
|
+
var LABEL_GAP = 4;
|
|
2485
|
+
var TITLE_GAP = 26;
|
|
2486
|
+
var AXIS_COLOR = "#000000";
|
|
2487
|
+
var AXIS_FONT = "12px sans-serif";
|
|
2488
|
+
function extentOf(limits) {
|
|
2489
|
+
return { min: Math.min(...limits), max: Math.max(...limits) };
|
|
2490
|
+
}
|
|
2491
|
+
function createScale(options) {
|
|
2492
|
+
const margins = options.margins ?? DEFAULT_MARGINS;
|
|
2493
|
+
const left = margins.left;
|
|
2494
|
+
const top = margins.top;
|
|
2495
|
+
const right = options.width - margins.right;
|
|
2496
|
+
const bottom = options.height - margins.bottom;
|
|
2497
|
+
const area = {
|
|
2498
|
+
left,
|
|
2499
|
+
right,
|
|
2500
|
+
top,
|
|
2501
|
+
bottom,
|
|
2502
|
+
width: right - left,
|
|
2503
|
+
height: bottom - top
|
|
2504
|
+
};
|
|
2505
|
+
const world = { x: options.x, y: options.y };
|
|
2506
|
+
const spanX = options.x.max - options.x.min;
|
|
2507
|
+
const spanY = options.y.max - options.y.min;
|
|
2508
|
+
return {
|
|
2509
|
+
area,
|
|
2510
|
+
world,
|
|
2511
|
+
toPixelX(x) {
|
|
2512
|
+
if (spanX === 0) {
|
|
2513
|
+
return left + area.width / 2;
|
|
2514
|
+
}
|
|
2515
|
+
return left + (x - options.x.min) / spanX * area.width;
|
|
2516
|
+
},
|
|
2517
|
+
toPixelY(y) {
|
|
2518
|
+
if (spanY === 0) {
|
|
2519
|
+
return top + area.height / 2;
|
|
2520
|
+
}
|
|
2521
|
+
return bottom - (y - options.y.min) / spanY * area.height;
|
|
2522
|
+
},
|
|
2523
|
+
toWorldX(px) {
|
|
2524
|
+
if (area.width === 0) {
|
|
2525
|
+
return options.x.min;
|
|
2526
|
+
}
|
|
2527
|
+
return options.x.min + (px - left) / area.width * spanX;
|
|
2528
|
+
},
|
|
2529
|
+
toWorldY(py) {
|
|
2530
|
+
if (area.height === 0) {
|
|
2531
|
+
return options.y.min;
|
|
2532
|
+
}
|
|
2533
|
+
return options.y.min + (bottom - py) / area.height * spanY;
|
|
2534
|
+
}
|
|
2535
|
+
};
|
|
2536
|
+
}
|
|
2537
|
+
function pixelInArea(area, pixel) {
|
|
2538
|
+
return pixel.x >= area.left && pixel.x <= area.right && pixel.y >= area.top && pixel.y <= area.bottom;
|
|
2539
|
+
}
|
|
2540
|
+
function prettyTicks(extent2, count = DEFAULT_TICK_COUNT) {
|
|
2541
|
+
const min = Math.min(extent2.min, extent2.max);
|
|
2542
|
+
const max = Math.max(extent2.min, extent2.max);
|
|
2543
|
+
if (min === max) {
|
|
2544
|
+
return [min];
|
|
2545
|
+
}
|
|
2546
|
+
const step = niceStep((max - min) / Math.max(1, count));
|
|
2547
|
+
const decimals = decimalsFor(step);
|
|
2548
|
+
const guard = step * 0.000000001;
|
|
2549
|
+
const first = Math.ceil((min - guard) / step);
|
|
2550
|
+
const last = Math.floor((max + guard) / step);
|
|
2551
|
+
return Array.from({ length: last - first + 1 }, (_unused, index) => roundTo((first + index) * step, decimals));
|
|
2552
|
+
}
|
|
2553
|
+
function drawAxes(ctx, scale, options = {}) {
|
|
2554
|
+
const { area } = scale;
|
|
2555
|
+
const showYAxis = options.yAxis ?? true;
|
|
2556
|
+
const xTicks = prettyTicks(scale.world.x, options.tickCount);
|
|
2557
|
+
const yTicks = showYAxis ? prettyTicks(scale.world.y, options.tickCount) : [];
|
|
2558
|
+
ctx.save();
|
|
2559
|
+
ctx.setLineDash([]);
|
|
2560
|
+
ctx.strokeStyle = AXIS_COLOR;
|
|
2561
|
+
ctx.fillStyle = AXIS_COLOR;
|
|
2562
|
+
ctx.lineWidth = 1;
|
|
2563
|
+
ctx.font = AXIS_FONT;
|
|
2564
|
+
ctx.beginPath();
|
|
2565
|
+
if (options.frame ?? true) {
|
|
2566
|
+
ctx.rect(area.left, area.top, area.width, area.height);
|
|
2567
|
+
} else if (showYAxis) {
|
|
2568
|
+
ctx.moveTo(area.left, area.top);
|
|
2569
|
+
ctx.lineTo(area.left, area.bottom);
|
|
2570
|
+
ctx.lineTo(area.right, area.bottom);
|
|
2571
|
+
} else {
|
|
2572
|
+
ctx.moveTo(area.left, area.bottom);
|
|
2573
|
+
ctx.lineTo(area.right, area.bottom);
|
|
2574
|
+
}
|
|
2575
|
+
for (const tick of xTicks) {
|
|
2576
|
+
const px = scale.toPixelX(tick);
|
|
2577
|
+
ctx.moveTo(px, area.bottom);
|
|
2578
|
+
ctx.lineTo(px, area.bottom + TICK_LENGTH);
|
|
2579
|
+
}
|
|
2580
|
+
for (const tick of yTicks) {
|
|
2581
|
+
const py = scale.toPixelY(tick);
|
|
2582
|
+
ctx.moveTo(area.left, py);
|
|
2583
|
+
ctx.lineTo(area.left - TICK_LENGTH, py);
|
|
2584
|
+
}
|
|
2585
|
+
ctx.stroke();
|
|
2586
|
+
ctx.textAlign = "center";
|
|
2587
|
+
ctx.textBaseline = "top";
|
|
2588
|
+
for (const tick of xTicks) {
|
|
2589
|
+
ctx.fillText(String(tick), scale.toPixelX(tick), area.bottom + TICK_LENGTH + LABEL_GAP);
|
|
2590
|
+
}
|
|
2591
|
+
ctx.textAlign = "right";
|
|
2592
|
+
ctx.textBaseline = "middle";
|
|
2593
|
+
for (const tick of yTicks) {
|
|
2594
|
+
ctx.fillText(String(tick), area.left - TICK_LENGTH - LABEL_GAP, scale.toPixelY(tick));
|
|
2595
|
+
}
|
|
2596
|
+
if (options.xLabel !== undefined) {
|
|
2597
|
+
ctx.textAlign = "center";
|
|
2598
|
+
ctx.textBaseline = "bottom";
|
|
2599
|
+
ctx.fillText(options.xLabel, (area.left + area.right) / 2, area.bottom + TICK_LENGTH + LABEL_GAP + TITLE_GAP);
|
|
2600
|
+
}
|
|
2601
|
+
if (options.yLabel !== undefined) {
|
|
2602
|
+
ctx.save();
|
|
2603
|
+
ctx.translate(area.left - TICK_LENGTH - LABEL_GAP - TITLE_GAP, (area.top + area.bottom) / 2);
|
|
2604
|
+
ctx.rotate(-Math.PI / 2);
|
|
2605
|
+
ctx.textAlign = "center";
|
|
2606
|
+
ctx.textBaseline = "middle";
|
|
2607
|
+
ctx.fillText(options.yLabel, 0, 0);
|
|
2608
|
+
ctx.restore();
|
|
2609
|
+
}
|
|
2610
|
+
ctx.restore();
|
|
2611
|
+
}
|
|
2612
|
+
function niceStep(rawStep) {
|
|
2613
|
+
const magnitude = 10 ** Math.floor(Math.log10(rawStep));
|
|
2614
|
+
const normalized = rawStep / magnitude;
|
|
2615
|
+
if (normalized < 1.5) {
|
|
2616
|
+
return magnitude;
|
|
2617
|
+
}
|
|
2618
|
+
if (normalized < 3) {
|
|
2619
|
+
return 2 * magnitude;
|
|
2620
|
+
}
|
|
2621
|
+
if (normalized < 7) {
|
|
2622
|
+
return 5 * magnitude;
|
|
2623
|
+
}
|
|
2624
|
+
return 10 * magnitude;
|
|
2625
|
+
}
|
|
2626
|
+
function decimalsFor(step) {
|
|
2627
|
+
return Math.min(20, Math.max(0, -Math.floor(Math.log10(step))));
|
|
2628
|
+
}
|
|
2629
|
+
function roundTo(value, decimals) {
|
|
2630
|
+
return Number(value.toFixed(decimals));
|
|
2631
|
+
}
|
|
2632
|
+
// src/plot/draw.ts
|
|
2633
|
+
var DOTTED = [1, 3];
|
|
2634
|
+
var POINT_COLOR = "#bebebe";
|
|
2635
|
+
var POINT_RADIUS = 6;
|
|
2636
|
+
var ARROW_COLOR = "#000000";
|
|
2637
|
+
var ARROW_WIDTH = 1;
|
|
2638
|
+
var HEAD_ANGLE = Math.PI / 6;
|
|
2639
|
+
var MIN_ARROW_PIXELS = 96 / 1000;
|
|
2640
|
+
function clearSurface(ctx, width, height) {
|
|
2641
|
+
ctx.setLineDash([]);
|
|
2642
|
+
ctx.fillStyle = "#ffffff";
|
|
2643
|
+
ctx.fillRect(0, 0, width, height);
|
|
2644
|
+
}
|
|
2645
|
+
function clipToArea(ctx, area) {
|
|
2646
|
+
ctx.beginPath();
|
|
2647
|
+
ctx.rect(area.left, area.top, area.width, area.height);
|
|
2648
|
+
ctx.clip();
|
|
2649
|
+
}
|
|
2650
|
+
function drawDots(ctx, scale, points) {
|
|
2651
|
+
ctx.save();
|
|
2652
|
+
ctx.setLineDash([]);
|
|
2653
|
+
ctx.fillStyle = POINT_COLOR;
|
|
2654
|
+
for (const point of points) {
|
|
2655
|
+
ctx.beginPath();
|
|
2656
|
+
ctx.arc(scale.toPixelX(point.x), scale.toPixelY(point.y), POINT_RADIUS, 0, 2 * Math.PI);
|
|
2657
|
+
ctx.fill();
|
|
2658
|
+
}
|
|
2659
|
+
ctx.restore();
|
|
2660
|
+
}
|
|
2661
|
+
function drawArrow(ctx, tail, tip, options) {
|
|
2662
|
+
const {
|
|
2663
|
+
headLength,
|
|
2664
|
+
dash = [],
|
|
2665
|
+
color = ARROW_COLOR,
|
|
2666
|
+
lineWidth = ARROW_WIDTH
|
|
2667
|
+
} = options;
|
|
2668
|
+
const length = Math.hypot(tip[0] - tail[0], tip[1] - tail[1]);
|
|
2669
|
+
if (!Number.isFinite(length) || length < MIN_ARROW_PIXELS) {
|
|
2670
|
+
return;
|
|
2671
|
+
}
|
|
2672
|
+
const back = Math.atan2(tail[1] - tip[1], tail[0] - tip[0]);
|
|
2673
|
+
const edge = (turn) => [
|
|
2674
|
+
tip[0] + headLength * Math.cos(back + turn),
|
|
2675
|
+
tip[1] + headLength * Math.sin(back + turn)
|
|
2676
|
+
];
|
|
2677
|
+
ctx.save();
|
|
2678
|
+
ctx.strokeStyle = color;
|
|
2679
|
+
ctx.lineWidth = lineWidth;
|
|
2680
|
+
ctx.setLineDash([...dash]);
|
|
2681
|
+
ctx.beginPath();
|
|
2682
|
+
ctx.moveTo(tail[0], tail[1]);
|
|
2683
|
+
ctx.lineTo(tip[0], tip[1]);
|
|
2684
|
+
const [firstX, firstY] = edge(HEAD_ANGLE);
|
|
2685
|
+
const [secondX, secondY] = edge(-HEAD_ANGLE);
|
|
2686
|
+
ctx.moveTo(firstX, firstY);
|
|
2687
|
+
ctx.lineTo(tip[0], tip[1]);
|
|
2688
|
+
ctx.lineTo(secondX, secondY);
|
|
2689
|
+
ctx.stroke();
|
|
2690
|
+
ctx.restore();
|
|
2691
|
+
}
|
|
2692
|
+
|
|
2693
|
+
// src/plot/matrixInverse.ts
|
|
2694
|
+
var WORLD = { min: -3, max: 3 };
|
|
2695
|
+
var MATRIX_FILL = "#FF00001A";
|
|
2696
|
+
var INVERSE_FILL = "#0000FF1A";
|
|
2697
|
+
var BORDER_COLOR = "#0000004D";
|
|
2698
|
+
var ARROW_COLOR2 = "#000000";
|
|
2699
|
+
var LINE_WIDTH = 1;
|
|
2700
|
+
var HEAD_LENGTH = 24;
|
|
2701
|
+
function matrixInverseScale(width, height) {
|
|
2702
|
+
return createScale({ width, height, x: WORLD, y: WORLD });
|
|
2703
|
+
}
|
|
2704
|
+
function plotMatrixInverse(target, matrix) {
|
|
2705
|
+
const inversion = invertMatrix(matrix);
|
|
2706
|
+
const { ctx, width, height } = resolveTarget(target);
|
|
2707
|
+
if (inversion.inverse === null) {
|
|
2708
|
+
return inversion;
|
|
2709
|
+
}
|
|
2710
|
+
const scale = matrixInverseScale(width, height);
|
|
2711
|
+
clearSurface(ctx, width, height);
|
|
2712
|
+
drawAxes(ctx, scale, { frame: false, xLabel: "x", yLabel: "y" });
|
|
2713
|
+
const { area } = scale;
|
|
2714
|
+
ctx.save();
|
|
2715
|
+
clipToArea(ctx, area);
|
|
2716
|
+
drawMatrix(ctx, scale, matrix, MATRIX_FILL);
|
|
2717
|
+
drawMatrix(ctx, scale, inversion.inverse, INVERSE_FILL);
|
|
2718
|
+
ctx.restore();
|
|
2719
|
+
return inversion;
|
|
2720
|
+
}
|
|
2721
|
+
function drawMatrix(ctx, scale, matrix, fill) {
|
|
2722
|
+
drawParallelogram(ctx, scale, matrix, fill);
|
|
2723
|
+
drawColumnArrow(ctx, scale, matrix.x1, matrix.y1);
|
|
2724
|
+
drawColumnArrow(ctx, scale, matrix.x2, matrix.y2);
|
|
2725
|
+
}
|
|
2726
|
+
function drawParallelogram(ctx, scale, matrix, fill) {
|
|
2727
|
+
const corners = [
|
|
2728
|
+
[0, 0],
|
|
2729
|
+
[matrix.x1, matrix.y1],
|
|
2730
|
+
[matrix.x1 + matrix.x2, matrix.y1 + matrix.y2],
|
|
2731
|
+
[matrix.x2, matrix.y2],
|
|
2732
|
+
[0, 0]
|
|
2733
|
+
];
|
|
2734
|
+
ctx.save();
|
|
2735
|
+
ctx.setLineDash([]);
|
|
2736
|
+
ctx.fillStyle = fill;
|
|
2737
|
+
ctx.strokeStyle = BORDER_COLOR;
|
|
2738
|
+
ctx.lineWidth = LINE_WIDTH;
|
|
2739
|
+
ctx.beginPath();
|
|
2740
|
+
corners.forEach(([x, y], index) => {
|
|
2741
|
+
const px = scale.toPixelX(x);
|
|
2742
|
+
const py = scale.toPixelY(y);
|
|
2743
|
+
if (index === 0) {
|
|
2744
|
+
ctx.moveTo(px, py);
|
|
2745
|
+
} else {
|
|
2746
|
+
ctx.lineTo(px, py);
|
|
2747
|
+
}
|
|
2748
|
+
});
|
|
2749
|
+
ctx.fill();
|
|
2750
|
+
ctx.stroke();
|
|
2751
|
+
ctx.restore();
|
|
2752
|
+
}
|
|
2753
|
+
function drawColumnArrow(ctx, scale, x, y) {
|
|
2754
|
+
const tail = [scale.toPixelX(0), scale.toPixelY(0)];
|
|
2755
|
+
const tip = [scale.toPixelX(x), scale.toPixelY(y)];
|
|
2756
|
+
drawArrow(ctx, tail, tip, {
|
|
2757
|
+
headLength: HEAD_LENGTH,
|
|
2758
|
+
color: ARROW_COLOR2,
|
|
2759
|
+
lineWidth: LINE_WIDTH
|
|
2760
|
+
});
|
|
2761
|
+
}
|
|
2762
|
+
// src/plot/format.ts
|
|
2763
|
+
function formatStat(value) {
|
|
2764
|
+
if (value === null || !Number.isFinite(value)) {
|
|
2765
|
+
return "NA";
|
|
2766
|
+
}
|
|
2767
|
+
return String(Math.round(value * 100) / 100);
|
|
2768
|
+
}
|
|
2769
|
+
var R_SIGNIFICANT_DIGITS = 15;
|
|
2770
|
+
function formatNumber(value) {
|
|
2771
|
+
if (!Number.isFinite(value)) {
|
|
2772
|
+
return "NA";
|
|
2773
|
+
}
|
|
2774
|
+
return String(Number(value.toPrecision(R_SIGNIFICANT_DIGITS)));
|
|
2775
|
+
}
|
|
2776
|
+
|
|
2777
|
+
// src/plot/logit.ts
|
|
2778
|
+
var WORLD_Y = { min: 0, max: 1 };
|
|
2779
|
+
var DEFAULT_MIN_X = 0;
|
|
2780
|
+
var DEFAULT_MAX_X = 1;
|
|
2781
|
+
var HALF_LINE_COLOR = "lightgray";
|
|
2782
|
+
var CURVE_COLOR = "cornflowerblue";
|
|
2783
|
+
var TEXT_COLOR = "#000000";
|
|
2784
|
+
var CURVE_WIDTH = 2;
|
|
2785
|
+
var CURVE_SAMPLES = 500;
|
|
2786
|
+
var HALF = 0.5;
|
|
2787
|
+
var STATS_FONT = "12px monospace";
|
|
2788
|
+
var STATS_LINE_HEIGHT = 15;
|
|
2789
|
+
var STATS_PADDING = 8;
|
|
2790
|
+
var STATS_LABEL_WIDTH = 11;
|
|
2791
|
+
function logitScale(width, height, points, options = {}) {
|
|
2792
|
+
const finite = points.filter((point) => Number.isFinite(point.x));
|
|
2793
|
+
const min = finite.reduce((low, point) => Math.min(low, point.x), options.minX ?? DEFAULT_MIN_X);
|
|
2794
|
+
const max = finite.reduce((high, point) => Math.max(high, point.x), options.maxX ?? DEFAULT_MAX_X);
|
|
2795
|
+
return createScale({ width, height, x: { min, max }, y: WORLD_Y });
|
|
2796
|
+
}
|
|
2797
|
+
function plotLogit(target, points, options = {}) {
|
|
2798
|
+
const { ctx, width, height } = resolveTarget(target);
|
|
2799
|
+
const showRegression = options.regression ?? true;
|
|
2800
|
+
const showStats = options.stats ?? true;
|
|
2801
|
+
const legendLocation = options.legendLoc ?? "topleft";
|
|
2802
|
+
const scale = logitScale(width, height, points, options);
|
|
2803
|
+
clearSurface(ctx, width, height);
|
|
2804
|
+
drawAxes(ctx, scale, { xLabel: "x", yLabel: "y" });
|
|
2805
|
+
drawDots(ctx, scale, points);
|
|
2806
|
+
drawHalfLine(ctx, scale);
|
|
2807
|
+
if (points.length < 2 || !showRegression) {
|
|
2808
|
+
return null;
|
|
2809
|
+
}
|
|
2810
|
+
const fit = logisticRegression(points);
|
|
2811
|
+
if (fit === null) {
|
|
2812
|
+
return null;
|
|
2813
|
+
}
|
|
2814
|
+
drawCurve(ctx, scale, fit);
|
|
2815
|
+
if (showStats) {
|
|
2816
|
+
drawStats(ctx, scale, fit, legendLocation);
|
|
2817
|
+
}
|
|
2818
|
+
return fit;
|
|
2819
|
+
}
|
|
2820
|
+
function drawHalfLine(ctx, scale) {
|
|
2821
|
+
const { area } = scale;
|
|
2822
|
+
ctx.save();
|
|
2823
|
+
ctx.strokeStyle = HALF_LINE_COLOR;
|
|
2824
|
+
ctx.lineWidth = 1;
|
|
2825
|
+
ctx.setLineDash(DOTTED);
|
|
2826
|
+
ctx.beginPath();
|
|
2827
|
+
ctx.moveTo(area.left, scale.toPixelY(HALF));
|
|
2828
|
+
ctx.lineTo(area.right, scale.toPixelY(HALF));
|
|
2829
|
+
ctx.stroke();
|
|
2830
|
+
ctx.restore();
|
|
2831
|
+
}
|
|
2832
|
+
function drawCurve(ctx, scale, fit) {
|
|
2833
|
+
const { area, world } = scale;
|
|
2834
|
+
const span = world.x.max - world.x.min;
|
|
2835
|
+
const samples = Array.from({ length: CURVE_SAMPLES }, (_unused, index) => {
|
|
2836
|
+
const x = world.x.min + span * index / (CURVE_SAMPLES - 1);
|
|
2837
|
+
return {
|
|
2838
|
+
px: scale.toPixelX(x),
|
|
2839
|
+
py: scale.toPixelY(predictLogit(fit, x))
|
|
2840
|
+
};
|
|
2841
|
+
});
|
|
2842
|
+
ctx.save();
|
|
2843
|
+
clipToArea(ctx, area);
|
|
2844
|
+
ctx.strokeStyle = CURVE_COLOR;
|
|
2845
|
+
ctx.lineWidth = CURVE_WIDTH;
|
|
2846
|
+
ctx.setLineDash([]);
|
|
2847
|
+
ctx.beginPath();
|
|
2848
|
+
for (const [index, { px, py }] of samples.entries()) {
|
|
2849
|
+
if (index === 0) {
|
|
2850
|
+
ctx.moveTo(px, py);
|
|
2851
|
+
} else {
|
|
2852
|
+
ctx.lineTo(px, py);
|
|
2853
|
+
}
|
|
2854
|
+
}
|
|
2855
|
+
ctx.stroke();
|
|
2856
|
+
ctx.restore();
|
|
2857
|
+
}
|
|
2858
|
+
function drawStats(ctx, scale, fit, location) {
|
|
2859
|
+
const rows = [
|
|
2860
|
+
["Intercept", fit.intercept],
|
|
2861
|
+
["Coefficient", fit.slope],
|
|
2862
|
+
["AIC", fit.aic]
|
|
2863
|
+
];
|
|
2864
|
+
const { area } = scale;
|
|
2865
|
+
const onRight = location === "topright" || location === "bottomright";
|
|
2866
|
+
const atBottom = location === "bottomleft" || location === "bottomright";
|
|
2867
|
+
const x = onRight ? area.right - STATS_PADDING : area.left + STATS_PADDING;
|
|
2868
|
+
const top = atBottom ? Math.max(area.top + STATS_PADDING, area.bottom - STATS_PADDING - rows.length * STATS_LINE_HEIGHT) : area.top + STATS_PADDING;
|
|
2869
|
+
ctx.save();
|
|
2870
|
+
ctx.setLineDash([]);
|
|
2871
|
+
ctx.fillStyle = TEXT_COLOR;
|
|
2872
|
+
ctx.font = STATS_FONT;
|
|
2873
|
+
ctx.textAlign = onRight ? "right" : "left";
|
|
2874
|
+
ctx.textBaseline = "top";
|
|
2875
|
+
for (const [index, [label, value]] of rows.entries()) {
|
|
2876
|
+
ctx.fillText(`${label.padEnd(STATS_LABEL_WIDTH)}: ${formatStat(value)}`, x, top + index * STATS_LINE_HEIGHT);
|
|
2877
|
+
}
|
|
2878
|
+
ctx.restore();
|
|
2879
|
+
}
|
|
2880
|
+
// src/plot/sampleCi.ts
|
|
2881
|
+
var GOOD_COLORS = {
|
|
2882
|
+
span99: "#87cefa",
|
|
2883
|
+
span95: "#6ca6cd",
|
|
2884
|
+
point: "#4a708b"
|
|
2885
|
+
};
|
|
2886
|
+
var BAD_COLORS = {
|
|
2887
|
+
span99: "#f08080",
|
|
2888
|
+
span95: "#cd5b45",
|
|
2889
|
+
point: "#8b3e2f"
|
|
2890
|
+
};
|
|
2891
|
+
var MEAN_LINE_COLOR = "#000000";
|
|
2892
|
+
var SPAN_WIDTH = 3;
|
|
2893
|
+
var POINT_RADIUS2 = 2.5;
|
|
2894
|
+
function sampleCiScale(width, height, simulation) {
|
|
2895
|
+
const center = Number.isFinite(simulation.populationMean) ? simulation.populationMean : 0;
|
|
2896
|
+
const half = Number.isFinite(simulation.populationSd) ? simulation.populationSd / 2 : 0.5;
|
|
2897
|
+
return createScale({
|
|
2898
|
+
width,
|
|
2899
|
+
height,
|
|
2900
|
+
x: { min: center - half, max: center + half },
|
|
2901
|
+
y: { min: 1, max: Math.max(1, simulation.intervals.length) }
|
|
2902
|
+
});
|
|
2903
|
+
}
|
|
2904
|
+
function plotSampleCi(target, options = {}) {
|
|
2905
|
+
const { ctx, width, height } = resolveTarget(target);
|
|
2906
|
+
const {
|
|
2907
|
+
rng = seededRng(Math.floor(Math.random() * 4294967296)),
|
|
2908
|
+
...simulationOptions
|
|
2909
|
+
} = options;
|
|
2910
|
+
const simulation = simulateSampleCi(rng, simulationOptions);
|
|
2911
|
+
const scale = sampleCiScale(width, height, simulation);
|
|
2912
|
+
clearSurface(ctx, width, height);
|
|
2913
|
+
drawAxes(ctx, scale, {
|
|
2914
|
+
xLabel: "Confidence Intervals",
|
|
2915
|
+
yLabel: "Samples"
|
|
2916
|
+
});
|
|
2917
|
+
const { area } = scale;
|
|
2918
|
+
ctx.save();
|
|
2919
|
+
clipToArea(ctx, area);
|
|
2920
|
+
const rows = simulation.intervals.map((interval, index) => ({ interval, row: index + 1 })).filter(({ interval }) => isDrawable(interval));
|
|
2921
|
+
drawRows(ctx, scale, rows, GOOD_COLORS);
|
|
2922
|
+
drawRows(ctx, scale, rows.filter(({ interval }) => interval.excludesPopulationMean), BAD_COLORS);
|
|
2923
|
+
drawPopulationMean(ctx, scale, simulation.populationMean);
|
|
2924
|
+
ctx.restore();
|
|
2925
|
+
return simulation;
|
|
2926
|
+
}
|
|
2927
|
+
function isDrawable(interval) {
|
|
2928
|
+
return Number.isFinite(interval.mean) && Number.isFinite(interval.ci95.low) && Number.isFinite(interval.ci95.high) && Number.isFinite(interval.ci99.low) && Number.isFinite(interval.ci99.high);
|
|
2929
|
+
}
|
|
2930
|
+
function drawRows(ctx, scale, rows, colors) {
|
|
2931
|
+
ctx.save();
|
|
2932
|
+
ctx.setLineDash([]);
|
|
2933
|
+
ctx.lineWidth = SPAN_WIDTH;
|
|
2934
|
+
ctx.strokeStyle = colors.span99;
|
|
2935
|
+
for (const { interval, row } of rows) {
|
|
2936
|
+
drawSpan(ctx, scale, interval.ci99.low, interval.ci99.high, row);
|
|
2937
|
+
}
|
|
2938
|
+
ctx.strokeStyle = colors.span95;
|
|
2939
|
+
for (const { interval, row } of rows) {
|
|
2940
|
+
drawSpan(ctx, scale, interval.ci95.low, interval.ci95.high, row);
|
|
2941
|
+
}
|
|
2942
|
+
ctx.fillStyle = colors.point;
|
|
2943
|
+
for (const { interval, row } of rows) {
|
|
2944
|
+
drawDiamond(ctx, scale, interval.mean, row);
|
|
2945
|
+
}
|
|
2946
|
+
ctx.restore();
|
|
2947
|
+
}
|
|
2948
|
+
function drawSpan(ctx, scale, low, high, row) {
|
|
2949
|
+
const y = scale.toPixelY(row);
|
|
2950
|
+
ctx.beginPath();
|
|
2951
|
+
ctx.moveTo(scale.toPixelX(low), y);
|
|
2952
|
+
ctx.lineTo(scale.toPixelX(high), y);
|
|
2953
|
+
ctx.stroke();
|
|
2954
|
+
}
|
|
2955
|
+
function drawDiamond(ctx, scale, at, row) {
|
|
2956
|
+
const x = scale.toPixelX(at);
|
|
2957
|
+
const y = scale.toPixelY(row);
|
|
2958
|
+
ctx.beginPath();
|
|
2959
|
+
ctx.moveTo(x, y - POINT_RADIUS2);
|
|
2960
|
+
ctx.lineTo(x + POINT_RADIUS2, y);
|
|
2961
|
+
ctx.lineTo(x, y + POINT_RADIUS2);
|
|
2962
|
+
ctx.lineTo(x - POINT_RADIUS2, y);
|
|
2963
|
+
ctx.fill();
|
|
2964
|
+
}
|
|
2965
|
+
function drawPopulationMean(ctx, scale, at) {
|
|
2966
|
+
if (!Number.isFinite(at)) {
|
|
2967
|
+
return;
|
|
2968
|
+
}
|
|
2969
|
+
const { area } = scale;
|
|
2970
|
+
const x = scale.toPixelX(at);
|
|
2971
|
+
ctx.save();
|
|
2972
|
+
ctx.setLineDash([]);
|
|
2973
|
+
ctx.strokeStyle = MEAN_LINE_COLOR;
|
|
2974
|
+
ctx.lineWidth = 1;
|
|
2975
|
+
ctx.beginPath();
|
|
2976
|
+
ctx.moveTo(x, area.top);
|
|
2977
|
+
ctx.lineTo(x, area.bottom);
|
|
2978
|
+
ctx.stroke();
|
|
2979
|
+
ctx.restore();
|
|
2980
|
+
}
|
|
2981
|
+
// src/plot/sampling.ts
|
|
2982
|
+
var DEFAULT_SAMPLE_SIZE2 = 10;
|
|
2983
|
+
var DEFAULT_REPS = 1;
|
|
2984
|
+
var PANELS = ["population", "samples", "statistic"];
|
|
2985
|
+
var PANEL_MARGINS = { top: 8, right: 24, bottom: 28, left: 24 };
|
|
2986
|
+
var CURVE_COLOR2 = "#000000";
|
|
2987
|
+
var TEXT_COLOR2 = "#000000";
|
|
2988
|
+
var SAMPLE_CURVE_COLOR = "rgba(179, 179, 179, 0.5)";
|
|
2989
|
+
var BAR_COLOR = "lightgray";
|
|
2990
|
+
var CURVE_WIDTH2 = 2;
|
|
2991
|
+
var SAMPLE_CURVE_WIDTH = 1;
|
|
2992
|
+
var LABEL_FONT = "bold 11px sans-serif";
|
|
2993
|
+
var COUNT_FONT = "11px sans-serif";
|
|
2994
|
+
var LABEL_LINE_HEIGHT = 13;
|
|
2995
|
+
function samplingScale(width, height, panel, window, yMax) {
|
|
2996
|
+
const band = height / PANELS.length;
|
|
2997
|
+
const index = PANELS.indexOf(panel);
|
|
2998
|
+
const top = index * band;
|
|
2999
|
+
return createScale({
|
|
3000
|
+
width,
|
|
3001
|
+
height,
|
|
3002
|
+
x: window,
|
|
3003
|
+
y: { min: 0, max: yMax },
|
|
3004
|
+
margins: {
|
|
3005
|
+
top: top + PANEL_MARGINS.top,
|
|
3006
|
+
bottom: height - (top + band) + PANEL_MARGINS.bottom,
|
|
3007
|
+
left: PANEL_MARGINS.left,
|
|
3008
|
+
right: PANEL_MARGINS.right
|
|
3009
|
+
}
|
|
3010
|
+
});
|
|
3011
|
+
}
|
|
3012
|
+
function plotSampling(target, population, options = {}) {
|
|
3013
|
+
const { ctx, width, height } = resolveTarget(target);
|
|
3014
|
+
const {
|
|
3015
|
+
sampleSize = DEFAULT_SAMPLE_SIZE2,
|
|
3016
|
+
reps = DEFAULT_REPS,
|
|
3017
|
+
theta = mean,
|
|
3018
|
+
rng = seededRng(Math.floor(Math.random() * 4294967296)),
|
|
3019
|
+
state = null,
|
|
3020
|
+
densityWindow = "data"
|
|
3021
|
+
} = options;
|
|
3022
|
+
const window = frozenWindow(population, state);
|
|
3023
|
+
const grid = densityWindow === "frozen" ? { from: window.min, to: window.max } : {};
|
|
3024
|
+
const drawable = population.length >= 2;
|
|
3025
|
+
const draw = drawable ? drawSamples(rng, population, { sampleSize, reps, theta }) : { samples: [], thetas: [] };
|
|
3026
|
+
const sampleTheta = [...state?.sampleTheta ?? [], ...draw.thetas];
|
|
3027
|
+
clearSurface(ctx, width, height);
|
|
3028
|
+
drawDensityPanel(ctx, width, height, "population", window, drawable ? kernelDensity(population, grid) : null, [], "Population Distribution");
|
|
3029
|
+
const pooled = draw.samples.flat();
|
|
3030
|
+
drawDensityPanel(ctx, width, height, "samples", window, pooled.length >= 2 ? kernelDensity(pooled, grid) : null, draw.samples.filter((sample) => sample.length >= 2).map((sample) => kernelDensity(sample, grid)), "Sample Distribution");
|
|
3031
|
+
const countable = sampleTheta.filter((theta2) => theta2 >= window.min && theta2 <= window.max);
|
|
3032
|
+
const counted = countable.length > 0 ? histogram(countable) : null;
|
|
3033
|
+
drawStatisticPanel(ctx, width, height, window, counted, sampleTheta.length);
|
|
3034
|
+
return {
|
|
3035
|
+
state: { xMin: window.min, xMax: window.max, sampleTheta },
|
|
3036
|
+
samples: draw.samples,
|
|
3037
|
+
thetas: draw.thetas,
|
|
3038
|
+
histogram: counted
|
|
3039
|
+
};
|
|
3040
|
+
}
|
|
3041
|
+
function frozenWindow(population, state) {
|
|
3042
|
+
if (state !== null) {
|
|
3043
|
+
return { min: state.xMin, max: state.xMax };
|
|
3044
|
+
}
|
|
3045
|
+
if (population.length === 0) {
|
|
3046
|
+
return { min: 0, max: 1 };
|
|
3047
|
+
}
|
|
3048
|
+
const [min, max] = extent(population);
|
|
3049
|
+
return { min, max };
|
|
3050
|
+
}
|
|
3051
|
+
function drawDensityPanel(ctx, width, height, panel, window, main, perSample, label) {
|
|
3052
|
+
const peak = main === null ? 1 : highestOf(main.y);
|
|
3053
|
+
const scale = samplingScale(width, height, panel, window, peak);
|
|
3054
|
+
drawAxes(ctx, scale, { frame: false, yAxis: false });
|
|
3055
|
+
drawLabel(ctx, scale, [label], peak / 2, LABEL_FONT);
|
|
3056
|
+
if (main === null) {
|
|
3057
|
+
return;
|
|
3058
|
+
}
|
|
3059
|
+
drawCurve2(ctx, scale, main, CURVE_COLOR2, CURVE_WIDTH2, panel === "population");
|
|
3060
|
+
for (const estimate of perSample) {
|
|
3061
|
+
drawCurve2(ctx, scale, estimate, SAMPLE_CURVE_COLOR, SAMPLE_CURVE_WIDTH, false);
|
|
3062
|
+
}
|
|
3063
|
+
if (panel === "samples") {
|
|
3064
|
+
drawCurve2(ctx, scale, main, CURVE_COLOR2, CURVE_WIDTH2, false);
|
|
3065
|
+
}
|
|
3066
|
+
}
|
|
3067
|
+
function drawStatisticPanel(ctx, width, height, window, counted, drawnSoFar) {
|
|
3068
|
+
const tallest = counted === null ? 1 : highestOf(counted.counts);
|
|
3069
|
+
const scale = samplingScale(width, height, "statistic", window, tallest);
|
|
3070
|
+
drawAxes(ctx, scale, { frame: false, yAxis: false });
|
|
3071
|
+
if (counted !== null) {
|
|
3072
|
+
drawBars(ctx, scale, counted);
|
|
3073
|
+
}
|
|
3074
|
+
drawLabel(ctx, scale, ["Sampling Statistic ", `( ${drawnSoFar} )`], tallest / 2, COUNT_FONT);
|
|
3075
|
+
}
|
|
3076
|
+
function drawCurve2(ctx, scale, estimate, color, lineWidth, dotted) {
|
|
3077
|
+
const { area } = scale;
|
|
3078
|
+
ctx.save();
|
|
3079
|
+
clipToArea(ctx, area);
|
|
3080
|
+
ctx.strokeStyle = color;
|
|
3081
|
+
ctx.lineWidth = lineWidth;
|
|
3082
|
+
ctx.setLineDash(dotted ? DOTTED : []);
|
|
3083
|
+
ctx.beginPath();
|
|
3084
|
+
estimate.x.forEach((x, index) => {
|
|
3085
|
+
const px = scale.toPixelX(x);
|
|
3086
|
+
const py = scale.toPixelY(estimate.y[index]);
|
|
3087
|
+
if (index === 0) {
|
|
3088
|
+
ctx.moveTo(px, py);
|
|
3089
|
+
} else {
|
|
3090
|
+
ctx.lineTo(px, py);
|
|
3091
|
+
}
|
|
3092
|
+
});
|
|
3093
|
+
ctx.stroke();
|
|
3094
|
+
ctx.restore();
|
|
3095
|
+
}
|
|
3096
|
+
function drawBars(ctx, scale, counted) {
|
|
3097
|
+
const { area } = scale;
|
|
3098
|
+
const foot = scale.toPixelY(0);
|
|
3099
|
+
ctx.save();
|
|
3100
|
+
clipToArea(ctx, area);
|
|
3101
|
+
ctx.setLineDash([]);
|
|
3102
|
+
ctx.fillStyle = BAR_COLOR;
|
|
3103
|
+
counted.counts.forEach((count, index) => {
|
|
3104
|
+
const left = scale.toPixelX(counted.breaks[index]);
|
|
3105
|
+
const right = scale.toPixelX(counted.breaks[index + 1]);
|
|
3106
|
+
const top = scale.toPixelY(count);
|
|
3107
|
+
ctx.fillRect(left, top, right - left, foot - top);
|
|
3108
|
+
});
|
|
3109
|
+
ctx.restore();
|
|
3110
|
+
}
|
|
3111
|
+
function drawLabel(ctx, scale, lines, at, font) {
|
|
3112
|
+
ctx.save();
|
|
3113
|
+
ctx.setLineDash([]);
|
|
3114
|
+
ctx.fillStyle = TEXT_COLOR2;
|
|
3115
|
+
ctx.font = font;
|
|
3116
|
+
ctx.textAlign = "left";
|
|
3117
|
+
ctx.textBaseline = "middle";
|
|
3118
|
+
lines.forEach((line, index) => {
|
|
3119
|
+
ctx.fillText(line, scale.area.left, scale.toPixelY(at) + index * LABEL_LINE_HEIGHT);
|
|
3120
|
+
});
|
|
3121
|
+
ctx.restore();
|
|
3122
|
+
}
|
|
3123
|
+
function highestOf(values) {
|
|
3124
|
+
const highest = values.reduce((high, value) => value > high ? value : high, 0);
|
|
3125
|
+
return highest > 0 ? highest : 1;
|
|
3126
|
+
}
|
|
3127
|
+
// src/plot/pca.ts
|
|
3128
|
+
var DEFAULT_LIMITS = [-50, 50];
|
|
3129
|
+
var HEAD_LENGTH2 = 10;
|
|
3130
|
+
function pcaScale(width, height, options = {}) {
|
|
3131
|
+
const x = extentOf(options.xlim ?? DEFAULT_LIMITS);
|
|
3132
|
+
const y = extentOf(options.ylim ?? DEFAULT_LIMITS);
|
|
3133
|
+
const areaWidth = width - DEFAULT_MARGINS.left - DEFAULT_MARGINS.right;
|
|
3134
|
+
const areaHeight = height - DEFAULT_MARGINS.top - DEFAULT_MARGINS.bottom;
|
|
3135
|
+
const perPixel = Math.max((x.max - x.min) / areaWidth, (y.max - y.min) / areaHeight);
|
|
3136
|
+
if (!(areaWidth > 0 && areaHeight > 0 && perPixel > 0)) {
|
|
3137
|
+
return createScale({ width, height, x, y });
|
|
3138
|
+
}
|
|
3139
|
+
return createScale({
|
|
3140
|
+
width,
|
|
3141
|
+
height,
|
|
3142
|
+
x: widenedTo(x, perPixel * areaWidth),
|
|
3143
|
+
y: widenedTo(y, perPixel * areaHeight)
|
|
3144
|
+
});
|
|
3145
|
+
}
|
|
3146
|
+
function plotPca(target, points, options = {}) {
|
|
3147
|
+
const { ctx, width, height } = resolveTarget(target);
|
|
3148
|
+
const scale = pcaScale(width, height, options);
|
|
3149
|
+
clearSurface(ctx, width, height);
|
|
3150
|
+
drawAxes(ctx, scale, { xLabel: "x", yLabel: "y" });
|
|
3151
|
+
if (points.length === 0) {
|
|
3152
|
+
return null;
|
|
3153
|
+
}
|
|
3154
|
+
const { area } = scale;
|
|
3155
|
+
ctx.save();
|
|
3156
|
+
clipToArea(ctx, area);
|
|
3157
|
+
drawDots(ctx, scale, points);
|
|
3158
|
+
const result = points.length >= 3 ? principalComponents(points) : null;
|
|
3159
|
+
if (result !== null) {
|
|
3160
|
+
const anchor = options.meancenter ?? true ? result.center : ORIGIN;
|
|
3161
|
+
drawComponent(ctx, scale, result, anchor, 0, []);
|
|
3162
|
+
drawComponent(ctx, scale, result, anchor, 1, DOTTED);
|
|
3163
|
+
}
|
|
3164
|
+
ctx.restore();
|
|
3165
|
+
return result;
|
|
3166
|
+
}
|
|
3167
|
+
var ORIGIN = { x: 0, y: 0 };
|
|
3168
|
+
function widenedTo(extent2, span) {
|
|
3169
|
+
const middle = (extent2.min + extent2.max) / 2;
|
|
3170
|
+
return { min: middle - span / 2, max: middle + span / 2 };
|
|
3171
|
+
}
|
|
3172
|
+
function drawComponent(ctx, scale, result, anchor, component, dash) {
|
|
3173
|
+
const loadings = result.rotation[component];
|
|
3174
|
+
const sdev = result.sdev[component];
|
|
3175
|
+
const tail = [
|
|
3176
|
+
scale.toPixelX(anchor.x - loadings[0] * sdev),
|
|
3177
|
+
scale.toPixelY(anchor.y - loadings[1] * sdev)
|
|
3178
|
+
];
|
|
3179
|
+
const tip = [
|
|
3180
|
+
scale.toPixelX(anchor.x + loadings[0] * sdev),
|
|
3181
|
+
scale.toPixelY(anchor.y + loadings[1] * sdev)
|
|
3182
|
+
];
|
|
3183
|
+
drawArrow(ctx, tail, tip, { headLength: HEAD_LENGTH2, dash });
|
|
3184
|
+
}
|
|
3185
|
+
// src/plot/regression.ts
|
|
3186
|
+
var WORLD2 = { min: -5, max: 50 };
|
|
3187
|
+
var CROSSHAIR_COLOR = "lightgray";
|
|
3188
|
+
var LINE_COLOR = "cornflowerblue";
|
|
3189
|
+
var TEXT_COLOR3 = "#000000";
|
|
3190
|
+
var STATS_FONT2 = "12px monospace";
|
|
3191
|
+
var STATS_LINE_HEIGHT2 = 15;
|
|
3192
|
+
var STATS_PADDING2 = 8;
|
|
3193
|
+
var STATS_LABEL_WIDTH2 = 13;
|
|
3194
|
+
function regressionScale(width, height, options = {}) {
|
|
3195
|
+
return createScale({
|
|
3196
|
+
width,
|
|
3197
|
+
height,
|
|
3198
|
+
x: options.xlim ? extentOf(options.xlim) : WORLD2,
|
|
3199
|
+
y: options.ylim ? extentOf(options.ylim) : WORLD2
|
|
3200
|
+
});
|
|
3201
|
+
}
|
|
3202
|
+
function plotRegression(target, points, options = {}) {
|
|
3203
|
+
const { ctx, width, height } = resolveTarget(target);
|
|
3204
|
+
const showRegression = options.regression ?? true;
|
|
3205
|
+
const showStats = options.stats ?? true;
|
|
3206
|
+
const scale = regressionScale(width, height, options);
|
|
3207
|
+
clearSurface(ctx, width, height);
|
|
3208
|
+
drawAxes(ctx, scale, { xLabel: "x", yLabel: "y" });
|
|
3209
|
+
const fit = linearRegression(points);
|
|
3210
|
+
if (fit === null) {
|
|
3211
|
+
return null;
|
|
3212
|
+
}
|
|
3213
|
+
drawDots(ctx, scale, points);
|
|
3214
|
+
if (points.length < 2 || !showRegression) {
|
|
3215
|
+
return fit;
|
|
3216
|
+
}
|
|
3217
|
+
drawMeanCrosshair(ctx, scale, points);
|
|
3218
|
+
if (fit.slope !== null) {
|
|
3219
|
+
drawFittedLine(ctx, scale, fit.intercept, fit.slope);
|
|
3220
|
+
}
|
|
3221
|
+
if (showStats) {
|
|
3222
|
+
drawStats2(ctx, scale, fit);
|
|
3223
|
+
}
|
|
3224
|
+
return fit;
|
|
3225
|
+
}
|
|
3226
|
+
function drawMeanCrosshair(ctx, scale, points) {
|
|
3227
|
+
const meanX = mean(points.map((point) => point.x));
|
|
3228
|
+
const meanY = mean(points.map((point) => point.y));
|
|
3229
|
+
const { x, y } = scale.world;
|
|
3230
|
+
const originX = Math.min(Math.max(0, x.min), x.max);
|
|
3231
|
+
const originY = Math.min(Math.max(0, y.min), y.max);
|
|
3232
|
+
ctx.save();
|
|
3233
|
+
ctx.strokeStyle = CROSSHAIR_COLOR;
|
|
3234
|
+
ctx.lineWidth = 1;
|
|
3235
|
+
ctx.setLineDash(DOTTED);
|
|
3236
|
+
ctx.beginPath();
|
|
3237
|
+
ctx.moveTo(scale.toPixelX(originX), scale.toPixelY(meanY));
|
|
3238
|
+
ctx.lineTo(scale.toPixelX(x.max), scale.toPixelY(meanY));
|
|
3239
|
+
ctx.moveTo(scale.toPixelX(meanX), scale.toPixelY(originY));
|
|
3240
|
+
ctx.lineTo(scale.toPixelX(meanX), scale.toPixelY(meanY));
|
|
3241
|
+
ctx.stroke();
|
|
3242
|
+
ctx.restore();
|
|
3243
|
+
}
|
|
3244
|
+
function drawFittedLine(ctx, scale, intercept, slope) {
|
|
3245
|
+
const { area, world } = scale;
|
|
3246
|
+
ctx.save();
|
|
3247
|
+
clipToArea(ctx, area);
|
|
3248
|
+
ctx.strokeStyle = LINE_COLOR;
|
|
3249
|
+
ctx.lineWidth = 2;
|
|
3250
|
+
ctx.setLineDash([]);
|
|
3251
|
+
ctx.beginPath();
|
|
3252
|
+
ctx.moveTo(scale.toPixelX(world.x.min), scale.toPixelY(intercept + slope * world.x.min));
|
|
3253
|
+
ctx.lineTo(scale.toPixelX(world.x.max), scale.toPixelY(intercept + slope * world.x.max));
|
|
3254
|
+
ctx.stroke();
|
|
3255
|
+
ctx.restore();
|
|
3256
|
+
}
|
|
3257
|
+
function drawStats2(ctx, scale, fit) {
|
|
3258
|
+
const rows = [
|
|
3259
|
+
["Raw intercept", fit.intercept],
|
|
3260
|
+
["Raw slope", fit.slope],
|
|
3261
|
+
["Correlation", fit.correlation],
|
|
3262
|
+
["SSR", fit.ssr],
|
|
3263
|
+
["SSE", fit.sse],
|
|
3264
|
+
["SST", fit.sst],
|
|
3265
|
+
["R-squared", fit.rSquared]
|
|
3266
|
+
];
|
|
3267
|
+
ctx.save();
|
|
3268
|
+
ctx.setLineDash([]);
|
|
3269
|
+
ctx.fillStyle = TEXT_COLOR3;
|
|
3270
|
+
ctx.font = STATS_FONT2;
|
|
3271
|
+
ctx.textAlign = "left";
|
|
3272
|
+
ctx.textBaseline = "top";
|
|
3273
|
+
for (const [index, [label, value]] of rows.entries()) {
|
|
3274
|
+
ctx.fillText(`${label.padEnd(STATS_LABEL_WIDTH2)}: ${formatStat(value)}`, scale.area.left + STATS_PADDING2, scale.area.top + STATS_PADDING2 + index * STATS_LINE_HEIGHT2);
|
|
3275
|
+
}
|
|
3276
|
+
ctx.restore();
|
|
3277
|
+
}
|
|
3278
|
+
// src/plot/tTest.ts
|
|
3279
|
+
var WORLD_X = { min: -6, max: 6 };
|
|
3280
|
+
var NULL_COLOR = "#bf1a1a";
|
|
3281
|
+
var NULL_FILL = "#ff8080";
|
|
3282
|
+
var ALT_COLOR = "#1a1abf";
|
|
3283
|
+
var ALT_FILL = "rgba(102, 102, 255, 0.3)";
|
|
3284
|
+
var CORRECT_FILL = "rgba(77, 128, 191, 0.5)";
|
|
3285
|
+
var TEXT_COLOR4 = "#000000";
|
|
3286
|
+
var LINE_WIDTH2 = 2;
|
|
3287
|
+
var DASHED = [4, 4];
|
|
3288
|
+
var HIGHLIGHT_WIDTH = 4;
|
|
3289
|
+
var CURVE_SAMPLES2 = 1000;
|
|
3290
|
+
var CURVE_HALF_WIDTH = 6;
|
|
3291
|
+
var MAX_FILL_SAMPLES = 2000;
|
|
3292
|
+
var MATRIX = {
|
|
3293
|
+
left: -5.5,
|
|
3294
|
+
middle: -4,
|
|
3295
|
+
right: -2.5,
|
|
3296
|
+
bottom: 0.125,
|
|
3297
|
+
center: 0.25,
|
|
3298
|
+
top: 0.375
|
|
3299
|
+
};
|
|
3300
|
+
var MATRIX_HEADROOM = 0.045;
|
|
3301
|
+
var CAPTION_FONT_SIZE = 9;
|
|
3302
|
+
var TITLE_FONT_SIZE = 10;
|
|
3303
|
+
var VALUE_FONT_SIZE = 13;
|
|
3304
|
+
var CAPTION_LINE_HEIGHT = 12;
|
|
3305
|
+
var MIN_CAPTION_FONT_SIZE = 6;
|
|
3306
|
+
function tTestScale(width, height, options = {}) {
|
|
3307
|
+
const { errorMatrix = false, ...statOptions } = options;
|
|
3308
|
+
return buildScale(width, height, tTestStats(statOptions), errorMatrix);
|
|
3309
|
+
}
|
|
3310
|
+
function buildScale(width, height, stats, showErrorMatrix) {
|
|
3311
|
+
const peak = Math.max(dt(0, stats.df), stats.altMedianDensity);
|
|
3312
|
+
const tallest = Number.isFinite(peak) && peak > 0 ? peak : 1;
|
|
3313
|
+
const top = showErrorMatrix ? Math.max(1.04 * tallest, MATRIX.top + MATRIX_HEADROOM) : 1.04 * tallest;
|
|
3314
|
+
return createScale({
|
|
3315
|
+
width,
|
|
3316
|
+
height,
|
|
3317
|
+
x: WORLD_X,
|
|
3318
|
+
y: { min: -0.04 * tallest, max: top }
|
|
3319
|
+
});
|
|
3320
|
+
}
|
|
3321
|
+
function plotTTest(target, options = {}) {
|
|
3322
|
+
const { ctx, width, height } = resolveTarget(target);
|
|
3323
|
+
const { errorMatrix: showErrorMatrix = false, ...statOptions } = options;
|
|
3324
|
+
const stats = tTestStats(statOptions);
|
|
3325
|
+
const scale = buildScale(width, height, stats, showErrorMatrix);
|
|
3326
|
+
clearSurface(ctx, width, height);
|
|
3327
|
+
drawAxes(ctx, scale, { frame: false });
|
|
3328
|
+
drawFill(ctx, scale, stats.nullFill, stats.df, 0, NULL_FILL);
|
|
3329
|
+
drawCurve3(ctx, scale, stats.df, 0, NULL_COLOR, []);
|
|
3330
|
+
drawFill(ctx, scale, stats.altFill, stats.df, stats.t, ALT_FILL);
|
|
3331
|
+
drawMedianSegment(ctx, scale, stats);
|
|
3332
|
+
drawCurve3(ctx, scale, stats.df, stats.t, ALT_COLOR, DASHED);
|
|
3333
|
+
if (showErrorMatrix) {
|
|
3334
|
+
drawErrorMatrix(ctx, scale, stats);
|
|
3335
|
+
}
|
|
3336
|
+
return stats;
|
|
3337
|
+
}
|
|
3338
|
+
function drawCurve3(ctx, scale, df, ncp, color, dash) {
|
|
3339
|
+
ctx.save();
|
|
3340
|
+
clipToArea(ctx, scale.area);
|
|
3341
|
+
ctx.strokeStyle = color;
|
|
3342
|
+
ctx.lineWidth = LINE_WIDTH2;
|
|
3343
|
+
ctx.setLineDash([...dash]);
|
|
3344
|
+
ctx.beginPath();
|
|
3345
|
+
const from = ncp - CURVE_HALF_WIDTH;
|
|
3346
|
+
const step = 2 * CURVE_HALF_WIDTH / (CURVE_SAMPLES2 - 1);
|
|
3347
|
+
for (let index = 0;index < CURVE_SAMPLES2; index += 1) {
|
|
3348
|
+
const x = from + index * step;
|
|
3349
|
+
const px = scale.toPixelX(x);
|
|
3350
|
+
const py = scale.toPixelY(dt(x, df, ncp));
|
|
3351
|
+
if (index === 0) {
|
|
3352
|
+
ctx.moveTo(px, py);
|
|
3353
|
+
} else {
|
|
3354
|
+
ctx.lineTo(px, py);
|
|
3355
|
+
}
|
|
3356
|
+
}
|
|
3357
|
+
ctx.stroke();
|
|
3358
|
+
ctx.restore();
|
|
3359
|
+
}
|
|
3360
|
+
function drawFill(ctx, scale, range, df, ncp, color) {
|
|
3361
|
+
const from = Math.max(range.from, WORLD_X.min);
|
|
3362
|
+
const to = Math.min(range.to, WORLD_X.max);
|
|
3363
|
+
if (!(to > from)) {
|
|
3364
|
+
return;
|
|
3365
|
+
}
|
|
3366
|
+
const fromPixel = scale.toPixelX(from);
|
|
3367
|
+
const toPixel = scale.toPixelX(to);
|
|
3368
|
+
const samples = Math.max(2, Math.min(MAX_FILL_SAMPLES, Math.ceil(toPixel - fromPixel)));
|
|
3369
|
+
const step = (to - from) / (samples - 1);
|
|
3370
|
+
const baseline = scale.toPixelY(0);
|
|
3371
|
+
ctx.save();
|
|
3372
|
+
clipToArea(ctx, scale.area);
|
|
3373
|
+
ctx.fillStyle = color;
|
|
3374
|
+
ctx.setLineDash([]);
|
|
3375
|
+
ctx.beginPath();
|
|
3376
|
+
ctx.moveTo(fromPixel, baseline);
|
|
3377
|
+
for (let index = 0;index < samples; index += 1) {
|
|
3378
|
+
const x = from + index * step;
|
|
3379
|
+
ctx.lineTo(scale.toPixelX(x), scale.toPixelY(dt(x, df, ncp)));
|
|
3380
|
+
}
|
|
3381
|
+
ctx.lineTo(toPixel, baseline);
|
|
3382
|
+
ctx.fill();
|
|
3383
|
+
ctx.restore();
|
|
3384
|
+
}
|
|
3385
|
+
function drawMedianSegment(ctx, scale, stats) {
|
|
3386
|
+
if (!Number.isFinite(stats.altMedian)) {
|
|
3387
|
+
return;
|
|
3388
|
+
}
|
|
3389
|
+
ctx.save();
|
|
3390
|
+
clipToArea(ctx, scale.area);
|
|
3391
|
+
ctx.strokeStyle = ALT_FILL;
|
|
3392
|
+
ctx.lineWidth = LINE_WIDTH2;
|
|
3393
|
+
ctx.setLineDash([]);
|
|
3394
|
+
ctx.beginPath();
|
|
3395
|
+
ctx.moveTo(scale.toPixelX(stats.altMedian), scale.toPixelY(0));
|
|
3396
|
+
ctx.lineTo(scale.toPixelX(stats.altMedian), scale.toPixelY(stats.altMedianDensity));
|
|
3397
|
+
ctx.stroke();
|
|
3398
|
+
ctx.restore();
|
|
3399
|
+
}
|
|
3400
|
+
function drawErrorMatrix(ctx, scale, stats) {
|
|
3401
|
+
const { errorMatrix: cells, alpha } = stats;
|
|
3402
|
+
ctx.save();
|
|
3403
|
+
ctx.setLineDash([]);
|
|
3404
|
+
drawCell(ctx, scale, {
|
|
3405
|
+
left: MATRIX.left,
|
|
3406
|
+
bottom: MATRIX.center,
|
|
3407
|
+
right: MATRIX.middle,
|
|
3408
|
+
top: MATRIX.top,
|
|
3409
|
+
fill: NULL_FILL,
|
|
3410
|
+
title: "Type I error",
|
|
3411
|
+
value: formatNumber(alpha),
|
|
3412
|
+
rowCaption: ["If evidence says", "REJECT", "null hypothesis"],
|
|
3413
|
+
columnCaption: ["If null is", "really TRUE"]
|
|
3414
|
+
});
|
|
3415
|
+
drawCell(ctx, scale, {
|
|
3416
|
+
left: MATRIX.middle,
|
|
3417
|
+
bottom: MATRIX.center,
|
|
3418
|
+
right: MATRIX.right,
|
|
3419
|
+
top: MATRIX.top,
|
|
3420
|
+
fill: CORRECT_FILL,
|
|
3421
|
+
title: "Correct!",
|
|
3422
|
+
value: formatStat(cells.correctReject),
|
|
3423
|
+
columnCaption: ["If null is", "really FALSE"]
|
|
3424
|
+
});
|
|
3425
|
+
drawCell(ctx, scale, {
|
|
3426
|
+
left: MATRIX.left,
|
|
3427
|
+
bottom: MATRIX.bottom,
|
|
3428
|
+
right: MATRIX.middle,
|
|
3429
|
+
top: MATRIX.center,
|
|
3430
|
+
fill: CORRECT_FILL,
|
|
3431
|
+
title: "Correct!",
|
|
3432
|
+
value: formatNumber(cells.correctFailToReject),
|
|
3433
|
+
rowCaption: ["If evidence says", "CANNOT REJECT", "null hypothesis"]
|
|
3434
|
+
});
|
|
3435
|
+
drawCell(ctx, scale, {
|
|
3436
|
+
left: MATRIX.middle,
|
|
3437
|
+
bottom: MATRIX.bottom,
|
|
3438
|
+
right: MATRIX.right,
|
|
3439
|
+
top: MATRIX.center,
|
|
3440
|
+
fill: NULL_FILL,
|
|
3441
|
+
title: "Type II error",
|
|
3442
|
+
value: formatStat(cells.typeTwo)
|
|
3443
|
+
});
|
|
3444
|
+
const ringBottom = cells.highlightTopRow ? MATRIX.center : MATRIX.bottom;
|
|
3445
|
+
const ringTop = cells.highlightTopRow ? MATRIX.top : MATRIX.center;
|
|
3446
|
+
ctx.strokeStyle = ALT_COLOR;
|
|
3447
|
+
ctx.lineWidth = HIGHLIGHT_WIDTH;
|
|
3448
|
+
ctx.beginPath();
|
|
3449
|
+
ctx.rect(...worldRect(scale, MATRIX.left, ringBottom, MATRIX.right, ringTop));
|
|
3450
|
+
ctx.stroke();
|
|
3451
|
+
ctx.restore();
|
|
3452
|
+
}
|
|
3453
|
+
function drawCell(ctx, scale, cell) {
|
|
3454
|
+
const centerX = (cell.left + cell.right) / 2;
|
|
3455
|
+
const centerY = (cell.bottom + cell.top) / 2;
|
|
3456
|
+
ctx.fillStyle = cell.fill;
|
|
3457
|
+
ctx.beginPath();
|
|
3458
|
+
ctx.rect(...worldRect(scale, cell.left, cell.bottom, cell.right, cell.top));
|
|
3459
|
+
ctx.fill();
|
|
3460
|
+
ctx.fillStyle = TEXT_COLOR4;
|
|
3461
|
+
ctx.textAlign = "center";
|
|
3462
|
+
ctx.font = `${TITLE_FONT_SIZE}px sans-serif`;
|
|
3463
|
+
ctx.textBaseline = "top";
|
|
3464
|
+
ctx.fillText(cell.title, scale.toPixelX(centerX), scale.toPixelY(cell.top - 0.015));
|
|
3465
|
+
ctx.font = `${VALUE_FONT_SIZE}px sans-serif`;
|
|
3466
|
+
ctx.textBaseline = "middle";
|
|
3467
|
+
ctx.fillText(cell.value, scale.toPixelX(centerX), scale.toPixelY(centerY));
|
|
3468
|
+
ctx.font = `${CAPTION_FONT_SIZE}px sans-serif`;
|
|
3469
|
+
if (cell.columnCaption !== undefined) {
|
|
3470
|
+
ctx.textBaseline = "bottom";
|
|
3471
|
+
const blockHeight = (cell.columnCaption.length - 1) * CAPTION_LINE_HEIGHT;
|
|
3472
|
+
const anchor = Math.max(scale.toPixelY(cell.top + 0.02), scale.area.top + blockHeight + CAPTION_FONT_SIZE);
|
|
3473
|
+
drawLines(ctx, cell.columnCaption, scale.toPixelX(centerX), anchor - blockHeight, CAPTION_LINE_HEIGHT);
|
|
3474
|
+
}
|
|
3475
|
+
if (cell.rowCaption !== undefined) {
|
|
3476
|
+
const gapLeft = scale.toPixelX(WORLD_X.min);
|
|
3477
|
+
const layout = fitCaption(scale.toPixelX(cell.left) - gapLeft, cell.rowCaption.length);
|
|
3478
|
+
ctx.save();
|
|
3479
|
+
ctx.font = `${layout.fontSize}px sans-serif`;
|
|
3480
|
+
ctx.translate(gapLeft + layout.start, scale.toPixelY(centerY));
|
|
3481
|
+
ctx.rotate(-Math.PI / 2);
|
|
3482
|
+
ctx.textAlign = "center";
|
|
3483
|
+
ctx.textBaseline = "middle";
|
|
3484
|
+
drawLines(ctx, cell.rowCaption, 0, 0, layout.advance);
|
|
3485
|
+
ctx.restore();
|
|
3486
|
+
}
|
|
3487
|
+
}
|
|
3488
|
+
function fitCaption(gap, lineCount) {
|
|
3489
|
+
const advance = Math.min(CAPTION_LINE_HEIGHT, gap / lineCount);
|
|
3490
|
+
const fontSize = Math.max(MIN_CAPTION_FONT_SIZE, Math.min(CAPTION_FONT_SIZE, advance - 1));
|
|
3491
|
+
return { advance, fontSize, start: (gap - (lineCount - 1) * advance) / 2 };
|
|
3492
|
+
}
|
|
3493
|
+
function drawLines(ctx, lines, x, y, advance) {
|
|
3494
|
+
for (const [index, line] of lines.entries()) {
|
|
3495
|
+
ctx.fillText(line, x, y + index * advance);
|
|
3496
|
+
}
|
|
3497
|
+
}
|
|
3498
|
+
function worldRect(scale, left, bottom, right, top) {
|
|
3499
|
+
const x = scale.toPixelX(left);
|
|
3500
|
+
const y = scale.toPixelY(top);
|
|
3501
|
+
return [x, y, scale.toPixelX(right) - x, scale.toPixelY(bottom) - y];
|
|
3502
|
+
}
|
|
3503
|
+
// src/interactive/target.ts
|
|
3504
|
+
function resolveInteractiveTarget(target) {
|
|
3505
|
+
if ("surface" in target) {
|
|
3506
|
+
return target;
|
|
3507
|
+
}
|
|
3508
|
+
return { surface: resolveTarget(target), element: target };
|
|
3509
|
+
}
|
|
3510
|
+
function eventPixel(element, surface, event) {
|
|
3511
|
+
const rect = element.getBoundingClientRect();
|
|
3512
|
+
const scaleX = rect.width === 0 ? 1 : surface.width / rect.width;
|
|
3513
|
+
const scaleY = rect.height === 0 ? 1 : surface.height / rect.height;
|
|
3514
|
+
return {
|
|
3515
|
+
x: (event.clientX - rect.left) * scaleX,
|
|
3516
|
+
y: (event.clientY - rect.top) * scaleY
|
|
3517
|
+
};
|
|
3518
|
+
}
|
|
3519
|
+
var CONTROL_COLUMN_WIDTH = 140;
|
|
3520
|
+
var FALLBACK_WIDTH = 640;
|
|
3521
|
+
var FALLBACK_HEIGHT = 400;
|
|
3522
|
+
function resolveControlTarget(target) {
|
|
3523
|
+
if ("surface" in target) {
|
|
3524
|
+
return {
|
|
3525
|
+
surface: target.surface,
|
|
3526
|
+
controls: target.controls,
|
|
3527
|
+
release: () => {
|
|
3528
|
+
return;
|
|
3529
|
+
}
|
|
3530
|
+
};
|
|
3531
|
+
}
|
|
3532
|
+
if (target.tagName === "CANVAS") {
|
|
3533
|
+
throw new Error("interactive target: pass a container element to build the panel in, " + "not a canvas. A canvas has no room for the controls.");
|
|
3534
|
+
}
|
|
3535
|
+
const owner = target.ownerDocument;
|
|
3536
|
+
const controls = owner.createElement("div");
|
|
3537
|
+
controls.style.width = `${CONTROL_COLUMN_WIDTH}px`;
|
|
3538
|
+
controls.style.flexShrink = "0";
|
|
3539
|
+
controls.style.padding = "4px 6px";
|
|
3540
|
+
controls.style.overflowY = "auto";
|
|
3541
|
+
const width = target.clientWidth > CONTROL_COLUMN_WIDTH ? target.clientWidth - CONTROL_COLUMN_WIDTH : FALLBACK_WIDTH;
|
|
3542
|
+
const height = target.clientHeight > 0 ? target.clientHeight : FALLBACK_HEIGHT;
|
|
3543
|
+
const ratio = pixelRatio();
|
|
3544
|
+
const canvas = owner.createElement("canvas");
|
|
3545
|
+
canvas.width = Math.round(width * ratio);
|
|
3546
|
+
canvas.height = Math.round(height * ratio);
|
|
3547
|
+
canvas.style.width = `${width}px`;
|
|
3548
|
+
canvas.style.height = `${height}px`;
|
|
3549
|
+
canvas.style.minWidth = "0";
|
|
3550
|
+
target.style.display = "flex";
|
|
3551
|
+
target.appendChild(controls);
|
|
3552
|
+
target.appendChild(canvas);
|
|
3553
|
+
const remove = () => {
|
|
3554
|
+
controls.remove();
|
|
3555
|
+
canvas.remove();
|
|
3556
|
+
};
|
|
3557
|
+
const context = canvas.getContext("2d");
|
|
3558
|
+
if (context === null) {
|
|
3559
|
+
remove();
|
|
3560
|
+
throw new Error("interactive target: the canvas gave no 2D context. In tests, pass " + "{ surface: { ctx, width, height }, controls } instead of a container.");
|
|
3561
|
+
}
|
|
3562
|
+
if (ratio !== 1) {
|
|
3563
|
+
context.scale(ratio, ratio);
|
|
3564
|
+
}
|
|
3565
|
+
return { surface: { ctx: context, width, height }, controls, release: remove };
|
|
3566
|
+
}
|
|
3567
|
+
var FALLBACK_PLOT_HEIGHT = 480;
|
|
3568
|
+
function resolvePlot3dTarget(target) {
|
|
3569
|
+
if ("plot" in target) {
|
|
3570
|
+
return {
|
|
3571
|
+
plot: target.plot,
|
|
3572
|
+
controls: target.controls,
|
|
3573
|
+
release: () => {
|
|
3574
|
+
return;
|
|
3575
|
+
}
|
|
3576
|
+
};
|
|
3577
|
+
}
|
|
3578
|
+
if (target.tagName === "CANVAS") {
|
|
3579
|
+
throw new Error("interactive target: pass a container element to build the panel in, " + "not a canvas. Plotly draws into an element of its own.");
|
|
3580
|
+
}
|
|
3581
|
+
const owner = target.ownerDocument;
|
|
3582
|
+
const controls = owner.createElement("div");
|
|
3583
|
+
controls.style.display = "flex";
|
|
3584
|
+
controls.style.flexWrap = "wrap";
|
|
3585
|
+
controls.style.gap = "12px";
|
|
3586
|
+
controls.style.padding = "8px";
|
|
3587
|
+
controls.style.flexShrink = "0";
|
|
3588
|
+
const plot = owner.createElement("div");
|
|
3589
|
+
plot.style.flex = "1 1 auto";
|
|
3590
|
+
plot.style.minHeight = target.clientHeight > 0 ? "0" : `${FALLBACK_PLOT_HEIGHT}px`;
|
|
3591
|
+
target.style.display = "flex";
|
|
3592
|
+
target.style.flexDirection = "column";
|
|
3593
|
+
target.appendChild(controls);
|
|
3594
|
+
target.appendChild(plot);
|
|
3595
|
+
return {
|
|
3596
|
+
plot,
|
|
3597
|
+
controls,
|
|
3598
|
+
release: () => {
|
|
3599
|
+
controls.remove();
|
|
3600
|
+
plot.remove();
|
|
3601
|
+
}
|
|
3602
|
+
};
|
|
3603
|
+
}
|
|
3604
|
+
function pixelRatio() {
|
|
3605
|
+
const ratio = globalThis.devicePixelRatio;
|
|
3606
|
+
return typeof ratio === "number" && ratio > 0 ? ratio : 1;
|
|
3607
|
+
}
|
|
3608
|
+
// src/interactive/controls.ts
|
|
3609
|
+
function startingSliderValue(given, fallback, range) {
|
|
3610
|
+
if (given === undefined) {
|
|
3611
|
+
return fallback;
|
|
3612
|
+
}
|
|
3613
|
+
if (!Number.isFinite(given)) {
|
|
3614
|
+
return range.min;
|
|
3615
|
+
}
|
|
3616
|
+
const clamped = Math.min(range.max, Math.max(range.min, given));
|
|
3617
|
+
const decimals = decimalsOf(range.step);
|
|
3618
|
+
const factor = 10 ** decimals;
|
|
3619
|
+
const steps = Math.round((clamped - range.min) * factor / (range.step * factor));
|
|
3620
|
+
const snapped = Number((range.min + steps * range.step).toFixed(decimals));
|
|
3621
|
+
return Math.min(range.max, snapped);
|
|
3622
|
+
}
|
|
3623
|
+
function decimalsOf(step) {
|
|
3624
|
+
return String(step).split(".")[1]?.length ?? 0;
|
|
3625
|
+
}
|
|
3626
|
+
function buildSlider(owner, name, label, range, value) {
|
|
3627
|
+
const wrapper = owner.createElement("label");
|
|
3628
|
+
wrapper.style.display = "block";
|
|
3629
|
+
const caption = owner.createElement("span");
|
|
3630
|
+
caption.textContent = `${label} `;
|
|
3631
|
+
const readout = owner.createElement("output");
|
|
3632
|
+
readout.textContent = String(value);
|
|
3633
|
+
const input = owner.createElement("input");
|
|
3634
|
+
input.type = "range";
|
|
3635
|
+
input.name = name;
|
|
3636
|
+
input.min = String(range.min);
|
|
3637
|
+
input.max = String(range.max);
|
|
3638
|
+
input.step = String(range.step);
|
|
3639
|
+
input.value = String(value);
|
|
3640
|
+
input.style.width = "100%";
|
|
3641
|
+
wrapper.appendChild(caption);
|
|
3642
|
+
wrapper.appendChild(readout);
|
|
3643
|
+
wrapper.appendChild(input);
|
|
3644
|
+
return { wrapper, input, readout };
|
|
3645
|
+
}
|
|
3646
|
+
function buildSelect(owner, name, label, choices, selected) {
|
|
3647
|
+
const wrapper = owner.createElement("label");
|
|
3648
|
+
wrapper.style.display = "block";
|
|
3649
|
+
const caption = owner.createElement("span");
|
|
3650
|
+
caption.textContent = `${label} `;
|
|
3651
|
+
const input = owner.createElement("select");
|
|
3652
|
+
input.name = name;
|
|
3653
|
+
input.style.width = "100%";
|
|
3654
|
+
for (const choice of choices) {
|
|
3655
|
+
const option = owner.createElement("option");
|
|
3656
|
+
option.value = String(choice);
|
|
3657
|
+
option.textContent = String(choice);
|
|
3658
|
+
input.appendChild(option);
|
|
3659
|
+
}
|
|
3660
|
+
input.value = String(selected);
|
|
3661
|
+
wrapper.appendChild(caption);
|
|
3662
|
+
wrapper.appendChild(input);
|
|
3663
|
+
return { wrapper, input };
|
|
3664
|
+
}
|
|
3665
|
+
function buildNote(owner) {
|
|
3666
|
+
const element = owner.createElement("p");
|
|
3667
|
+
element.style.margin = "4px 0 0";
|
|
3668
|
+
element.style.fontSize = "12px";
|
|
3669
|
+
element.textContent = "";
|
|
3670
|
+
element.hidden = true;
|
|
3671
|
+
return {
|
|
3672
|
+
element,
|
|
3673
|
+
show(text) {
|
|
3674
|
+
element.textContent = text ?? "";
|
|
3675
|
+
element.hidden = text === null;
|
|
3676
|
+
}
|
|
3677
|
+
};
|
|
3678
|
+
}
|
|
3679
|
+
|
|
3680
|
+
// src/interactive/matrixInverse.ts
|
|
3681
|
+
var DEFAULT_MATRIX_INVERSE_VALUES = {
|
|
3682
|
+
x1: 1,
|
|
3683
|
+
y1: 2,
|
|
3684
|
+
x2: 2,
|
|
3685
|
+
y2: 1
|
|
3686
|
+
};
|
|
3687
|
+
var ENTRIES = ["x1", "y1", "x2", "y2"];
|
|
3688
|
+
var SLIDER_MIN = -2;
|
|
3689
|
+
var SLIDER_MAX = 2;
|
|
3690
|
+
var SLIDER_STEP = 0.1;
|
|
3691
|
+
var SLIDER_RANGE = {
|
|
3692
|
+
min: SLIDER_MIN,
|
|
3693
|
+
max: SLIDER_MAX,
|
|
3694
|
+
step: SLIDER_STEP
|
|
3695
|
+
};
|
|
3696
|
+
var NOTICE_BACKGROUND = "#ffffff";
|
|
3697
|
+
var NOTICE_COLOR = "#b22222";
|
|
3698
|
+
var NOTICE_FONT = "12px monospace";
|
|
3699
|
+
var NOTICE_MARGIN = 12;
|
|
3700
|
+
var SIGNIFICANT_DIGITS = 6;
|
|
3701
|
+
function interactiveMatrixInverse(target, options = {}) {
|
|
3702
|
+
const { onDone } = options;
|
|
3703
|
+
const panel = resolveControlTarget(target);
|
|
3704
|
+
const startingValue = (name) => startingSliderValue(options[name], DEFAULT_MATRIX_INVERSE_VALUES[name], SLIDER_RANGE);
|
|
3705
|
+
let values = {
|
|
3706
|
+
x1: startingValue("x1"),
|
|
3707
|
+
y1: startingValue("y1"),
|
|
3708
|
+
x2: startingValue("x2"),
|
|
3709
|
+
y2: startingValue("y2")
|
|
3710
|
+
};
|
|
3711
|
+
const owner = panel.controls.ownerDocument;
|
|
3712
|
+
const built = [];
|
|
3713
|
+
const inputs = [];
|
|
3714
|
+
const readouts = new Map;
|
|
3715
|
+
let destroyed = false;
|
|
3716
|
+
function draw() {
|
|
3717
|
+
const drawn = plotMatrixInverse(panel.surface, values);
|
|
3718
|
+
if (drawn.singularity !== null) {
|
|
3719
|
+
showNotice(panel.surface, drawn);
|
|
3720
|
+
}
|
|
3721
|
+
return drawn;
|
|
3722
|
+
}
|
|
3723
|
+
function handleInput(event) {
|
|
3724
|
+
const input = event.target;
|
|
3725
|
+
const name = input.name;
|
|
3726
|
+
if (!(name in values)) {
|
|
3727
|
+
return;
|
|
3728
|
+
}
|
|
3729
|
+
values = { ...values, [name]: Number(input.value) };
|
|
3730
|
+
const readout = readouts.get(name);
|
|
3731
|
+
if (readout !== undefined) {
|
|
3732
|
+
readout.textContent = input.value;
|
|
3733
|
+
}
|
|
3734
|
+
result = draw();
|
|
3735
|
+
}
|
|
3736
|
+
for (const name of ENTRIES) {
|
|
3737
|
+
const { wrapper, input, readout } = buildSlider(owner, name, name, SLIDER_RANGE, values[name]);
|
|
3738
|
+
readouts.set(name, readout);
|
|
3739
|
+
panel.controls.appendChild(wrapper);
|
|
3740
|
+
built.push(wrapper);
|
|
3741
|
+
inputs.push(input);
|
|
3742
|
+
input.addEventListener("input", handleInput);
|
|
3743
|
+
}
|
|
3744
|
+
let result = draw();
|
|
3745
|
+
return {
|
|
3746
|
+
getValues: () => ({ ...values }),
|
|
3747
|
+
getResult: () => result,
|
|
3748
|
+
done() {
|
|
3749
|
+
onDone?.({ ...values });
|
|
3750
|
+
},
|
|
3751
|
+
destroy() {
|
|
3752
|
+
if (destroyed) {
|
|
3753
|
+
return;
|
|
3754
|
+
}
|
|
3755
|
+
destroyed = true;
|
|
3756
|
+
for (const input of inputs) {
|
|
3757
|
+
input.removeEventListener("input", handleInput);
|
|
3758
|
+
}
|
|
3759
|
+
for (const node of built) {
|
|
3760
|
+
node.remove();
|
|
3761
|
+
}
|
|
3762
|
+
panel.release();
|
|
3763
|
+
}
|
|
3764
|
+
};
|
|
3765
|
+
}
|
|
3766
|
+
function showNotice(surface, report) {
|
|
3767
|
+
const { ctx, width, height } = surface;
|
|
3768
|
+
ctx.save();
|
|
3769
|
+
ctx.setLineDash([]);
|
|
3770
|
+
ctx.fillStyle = NOTICE_BACKGROUND;
|
|
3771
|
+
ctx.fillRect(0, 0, width, height);
|
|
3772
|
+
ctx.fillStyle = NOTICE_COLOR;
|
|
3773
|
+
ctx.font = NOTICE_FONT;
|
|
3774
|
+
ctx.textAlign = "left";
|
|
3775
|
+
ctx.textBaseline = "top";
|
|
3776
|
+
ctx.fillText(messageFor(report), NOTICE_MARGIN, NOTICE_MARGIN);
|
|
3777
|
+
ctx.restore();
|
|
3778
|
+
}
|
|
3779
|
+
function messageFor(report) {
|
|
3780
|
+
if (report.singularity === "exact") {
|
|
3781
|
+
const pivot = report.zeroPivot ?? 1;
|
|
3782
|
+
return `Lapack routine dgesv: system is exactly singular: U[${pivot},${pivot}] = 0`;
|
|
3783
|
+
}
|
|
3784
|
+
return "system is computationally singular: reciprocal condition number = " + formatConditionNumber(report.rcond);
|
|
3785
|
+
}
|
|
3786
|
+
function formatConditionNumber(value) {
|
|
3787
|
+
const [mantissa, exponentText] = value.toExponential(SIGNIFICANT_DIGITS - 1).split("e");
|
|
3788
|
+
const exponent = Number(exponentText);
|
|
3789
|
+
const sign = exponent < 0 ? "-" : "+";
|
|
3790
|
+
const digits = String(Math.abs(exponent)).padStart(2, "0");
|
|
3791
|
+
return `${withoutTrailingZeros(String(mantissa))}e${sign}${digits}`;
|
|
3792
|
+
}
|
|
3793
|
+
function withoutTrailingZeros(text) {
|
|
3794
|
+
if (!text.includes(".")) {
|
|
3795
|
+
return text;
|
|
3796
|
+
}
|
|
3797
|
+
return text.replace(/0+$/, "").replace(/\.$/, "");
|
|
3798
|
+
}
|
|
3799
|
+
// src/interactive/sampling.ts
|
|
3800
|
+
var SAMPLE_SIZES = [10, 100, 500, 1000, 5000, 1e4];
|
|
3801
|
+
var REPETITIONS = [1, 5, 10, 50, 100];
|
|
3802
|
+
var SAMPLE_SIZE_NAME = "sampleSize";
|
|
3803
|
+
var SAMPLE_SIZE_LABEL = "Sample size";
|
|
3804
|
+
var REPS_NAME = "reps";
|
|
3805
|
+
var REPS_LABEL = "Repetitions";
|
|
3806
|
+
var BUTTON_LABEL = "Sample";
|
|
3807
|
+
function interactiveSampling(target, population, options = {}) {
|
|
3808
|
+
const {
|
|
3809
|
+
onDone,
|
|
3810
|
+
sampleSize,
|
|
3811
|
+
reps,
|
|
3812
|
+
rng: given,
|
|
3813
|
+
state: initial,
|
|
3814
|
+
...forwarded
|
|
3815
|
+
} = options;
|
|
3816
|
+
const panel = resolveControlTarget(target);
|
|
3817
|
+
let values = {
|
|
3818
|
+
sampleSize: chooseFrom(sampleSize, SAMPLE_SIZES),
|
|
3819
|
+
reps: chooseFrom(reps, REPETITIONS)
|
|
3820
|
+
};
|
|
3821
|
+
const rng = given ?? seededRng(Math.floor(Math.random() * 4294967296));
|
|
3822
|
+
let state = initial ?? null;
|
|
3823
|
+
let last;
|
|
3824
|
+
const owner = panel.controls.ownerDocument;
|
|
3825
|
+
const built = [];
|
|
3826
|
+
let destroyed = false;
|
|
3827
|
+
function draw(repetitions) {
|
|
3828
|
+
last = plotSampling(panel.surface, population, {
|
|
3829
|
+
...forwarded,
|
|
3830
|
+
sampleSize: values.sampleSize,
|
|
3831
|
+
reps: repetitions,
|
|
3832
|
+
rng,
|
|
3833
|
+
state
|
|
3834
|
+
});
|
|
3835
|
+
state = last.state;
|
|
3836
|
+
}
|
|
3837
|
+
function handleChange(event) {
|
|
3838
|
+
const input = event.target;
|
|
3839
|
+
const numeric = Number(input.value);
|
|
3840
|
+
values = input.name === SAMPLE_SIZE_NAME ? { ...values, sampleSize: numeric } : { ...values, reps: numeric };
|
|
3841
|
+
}
|
|
3842
|
+
function handlePress() {
|
|
3843
|
+
draw(values.reps);
|
|
3844
|
+
}
|
|
3845
|
+
const sizeSelect = buildSelect(owner, SAMPLE_SIZE_NAME, SAMPLE_SIZE_LABEL, SAMPLE_SIZES, values.sampleSize);
|
|
3846
|
+
const repsSelect = buildSelect(owner, REPS_NAME, REPS_LABEL, REPETITIONS, values.reps);
|
|
3847
|
+
const button = owner.createElement("button");
|
|
3848
|
+
button.type = "button";
|
|
3849
|
+
button.textContent = BUTTON_LABEL;
|
|
3850
|
+
for (const wrapper of [sizeSelect.wrapper, repsSelect.wrapper]) {
|
|
3851
|
+
panel.controls.appendChild(wrapper);
|
|
3852
|
+
built.push(wrapper);
|
|
3853
|
+
}
|
|
3854
|
+
panel.controls.appendChild(button);
|
|
3855
|
+
built.push(button);
|
|
3856
|
+
sizeSelect.input.addEventListener("change", handleChange);
|
|
3857
|
+
repsSelect.input.addEventListener("change", handleChange);
|
|
3858
|
+
button.addEventListener("click", handlePress);
|
|
3859
|
+
draw(values.reps);
|
|
3860
|
+
return {
|
|
3861
|
+
getValues: () => ({ ...values }),
|
|
3862
|
+
getState: () => last.state,
|
|
3863
|
+
reset() {
|
|
3864
|
+
state = null;
|
|
3865
|
+
draw(0);
|
|
3866
|
+
},
|
|
3867
|
+
done() {
|
|
3868
|
+
onDone?.(last.state);
|
|
3869
|
+
},
|
|
3870
|
+
destroy() {
|
|
3871
|
+
if (destroyed) {
|
|
3872
|
+
return;
|
|
3873
|
+
}
|
|
3874
|
+
destroyed = true;
|
|
3875
|
+
sizeSelect.input.removeEventListener("change", handleChange);
|
|
3876
|
+
repsSelect.input.removeEventListener("change", handleChange);
|
|
3877
|
+
button.removeEventListener("click", handlePress);
|
|
3878
|
+
for (const node of built) {
|
|
3879
|
+
node.remove();
|
|
3880
|
+
}
|
|
3881
|
+
panel.release();
|
|
3882
|
+
}
|
|
3883
|
+
};
|
|
3884
|
+
}
|
|
3885
|
+
function chooseFrom(asked, choices) {
|
|
3886
|
+
const first = choices[0];
|
|
3887
|
+
return asked !== undefined && choices.includes(asked) ? asked : first;
|
|
3888
|
+
}
|
|
3889
|
+
// src/interactive/tTest.ts
|
|
3890
|
+
var SLIDERS = [
|
|
3891
|
+
{ name: "diff", label: "Difference", min: 0, max: 4, step: 0.1 },
|
|
3892
|
+
{ name: "sd", label: "Std Dev", min: 1, max: 5, step: 0.1 },
|
|
3893
|
+
{ name: "n", label: "Sample Size", min: 2, max: 500, step: 1 },
|
|
3894
|
+
{ name: "alpha", label: "Alpha", min: 0.01, max: 0.1, step: 0.01 }
|
|
3895
|
+
];
|
|
3896
|
+
var ERROR_MATRIX_NAME = "errorMatrix";
|
|
3897
|
+
var ERROR_MATRIX_LABEL = "Error Matrix";
|
|
3898
|
+
function interactiveTTest(target, options = {}) {
|
|
3899
|
+
const { onDone, diff, sd: sd2, n, alpha, errorMatrix, ...forwarded } = options;
|
|
3900
|
+
const panel = resolveControlTarget(target);
|
|
3901
|
+
let values = {
|
|
3902
|
+
diff: diff ?? DEFAULT_T_TEST_OPTIONS.diff,
|
|
3903
|
+
sd: sd2 ?? DEFAULT_T_TEST_OPTIONS.sd,
|
|
3904
|
+
n: n ?? DEFAULT_T_TEST_OPTIONS.n,
|
|
3905
|
+
alpha: alpha ?? DEFAULT_T_TEST_OPTIONS.alpha,
|
|
3906
|
+
errorMatrix: errorMatrix ?? false
|
|
3907
|
+
};
|
|
3908
|
+
const owner = panel.controls.ownerDocument;
|
|
3909
|
+
const built = [];
|
|
3910
|
+
const inputs = [];
|
|
3911
|
+
const readouts = new Map;
|
|
3912
|
+
let destroyed = false;
|
|
3913
|
+
function draw() {
|
|
3914
|
+
return plotTTest(panel.surface, { ...forwarded, ...values });
|
|
3915
|
+
}
|
|
3916
|
+
function withControl(input) {
|
|
3917
|
+
const numeric = Number(input.value);
|
|
3918
|
+
switch (input.name) {
|
|
3919
|
+
case "diff":
|
|
3920
|
+
return { ...values, diff: numeric };
|
|
3921
|
+
case "sd":
|
|
3922
|
+
return { ...values, sd: numeric };
|
|
3923
|
+
case "n":
|
|
3924
|
+
return { ...values, n: numeric };
|
|
3925
|
+
case "alpha":
|
|
3926
|
+
return { ...values, alpha: numeric };
|
|
3927
|
+
case ERROR_MATRIX_NAME:
|
|
3928
|
+
return { ...values, errorMatrix: input.checked };
|
|
3929
|
+
default:
|
|
3930
|
+
return values;
|
|
3931
|
+
}
|
|
3932
|
+
}
|
|
3933
|
+
function handleInput(event) {
|
|
3934
|
+
const input = event.target;
|
|
3935
|
+
values = withControl(input);
|
|
3936
|
+
const readout = readouts.get(input.name);
|
|
3937
|
+
if (readout !== undefined) {
|
|
3938
|
+
readout.textContent = input.value;
|
|
3939
|
+
}
|
|
3940
|
+
stats = draw();
|
|
3941
|
+
}
|
|
3942
|
+
function mount(wrapper, input) {
|
|
3943
|
+
panel.controls.appendChild(wrapper);
|
|
3944
|
+
built.push(wrapper);
|
|
3945
|
+
inputs.push(input);
|
|
3946
|
+
input.addEventListener("input", handleInput);
|
|
3947
|
+
}
|
|
3948
|
+
for (const spec of SLIDERS) {
|
|
3949
|
+
const { wrapper, input, readout } = buildSlider(owner, spec.name, spec.label, spec, values[spec.name]);
|
|
3950
|
+
readouts.set(spec.name, readout);
|
|
3951
|
+
mount(wrapper, input);
|
|
3952
|
+
}
|
|
3953
|
+
const checkboxWrapper = owner.createElement("label");
|
|
3954
|
+
checkboxWrapper.style.display = "block";
|
|
3955
|
+
const checkbox = owner.createElement("input");
|
|
3956
|
+
checkbox.type = "checkbox";
|
|
3957
|
+
checkbox.name = ERROR_MATRIX_NAME;
|
|
3958
|
+
checkbox.checked = values.errorMatrix;
|
|
3959
|
+
checkboxWrapper.appendChild(checkbox);
|
|
3960
|
+
checkboxWrapper.appendChild(owner.createTextNode(` ${ERROR_MATRIX_LABEL}`));
|
|
3961
|
+
mount(checkboxWrapper, checkbox);
|
|
3962
|
+
let stats = draw();
|
|
3963
|
+
return {
|
|
3964
|
+
getValues: () => ({ ...values }),
|
|
3965
|
+
getStats: () => stats,
|
|
3966
|
+
done() {
|
|
3967
|
+
onDone?.({ ...values });
|
|
3968
|
+
},
|
|
3969
|
+
destroy() {
|
|
3970
|
+
if (destroyed) {
|
|
3971
|
+
return;
|
|
3972
|
+
}
|
|
3973
|
+
destroyed = true;
|
|
3974
|
+
for (const input of inputs) {
|
|
3975
|
+
input.removeEventListener("input", handleInput);
|
|
3976
|
+
}
|
|
3977
|
+
for (const node of built) {
|
|
3978
|
+
node.remove();
|
|
3979
|
+
}
|
|
3980
|
+
panel.release();
|
|
3981
|
+
}
|
|
3982
|
+
};
|
|
3983
|
+
}
|
|
3984
|
+
// src/interactive/logit.ts
|
|
3985
|
+
var DEFAULT_MIN_X2 = 0;
|
|
3986
|
+
var DEFAULT_MAX_X2 = 50;
|
|
3987
|
+
function interactiveLogit(target, options = {}) {
|
|
3988
|
+
const {
|
|
3989
|
+
initialPoints,
|
|
3990
|
+
onDone,
|
|
3991
|
+
minX = DEFAULT_MIN_X2,
|
|
3992
|
+
maxX = DEFAULT_MAX_X2,
|
|
3993
|
+
...plotOptions
|
|
3994
|
+
} = options;
|
|
3995
|
+
const { surface, element } = resolveInteractiveTarget(target);
|
|
3996
|
+
let points = initialPoints ?? [];
|
|
3997
|
+
const rightEdge = points.reduce((edge, point) => Math.max(edge, point.x), maxX);
|
|
3998
|
+
function draw() {
|
|
3999
|
+
plotLogit(surface, points, { ...plotOptions, minX, maxX: rightEdge });
|
|
4000
|
+
}
|
|
4001
|
+
function handleClick(event) {
|
|
4002
|
+
const scale = logitScale(surface.width, surface.height, points, {
|
|
4003
|
+
minX,
|
|
4004
|
+
maxX: rightEdge
|
|
4005
|
+
});
|
|
4006
|
+
const pixel = eventPixel(element, surface, event);
|
|
4007
|
+
if (!pixelInArea(scale.area, pixel)) {
|
|
4008
|
+
return;
|
|
4009
|
+
}
|
|
4010
|
+
points = [
|
|
4011
|
+
...points,
|
|
4012
|
+
{
|
|
4013
|
+
x: clamp(scale.toWorldX(pixel.x), 0, rightEdge),
|
|
4014
|
+
y: Math.round(clamp(scale.toWorldY(pixel.y), 0, 1))
|
|
4015
|
+
}
|
|
4016
|
+
];
|
|
4017
|
+
draw();
|
|
4018
|
+
}
|
|
4019
|
+
draw();
|
|
4020
|
+
element.addEventListener("click", handleClick);
|
|
4021
|
+
return {
|
|
4022
|
+
getPoints: () => [...points],
|
|
4023
|
+
reset() {
|
|
4024
|
+
points = [];
|
|
4025
|
+
draw();
|
|
4026
|
+
},
|
|
4027
|
+
done() {
|
|
4028
|
+
onDone?.([...points]);
|
|
4029
|
+
},
|
|
4030
|
+
destroy() {
|
|
4031
|
+
element.removeEventListener("click", handleClick);
|
|
4032
|
+
}
|
|
4033
|
+
};
|
|
4034
|
+
}
|
|
4035
|
+
function clamp(value, low, high) {
|
|
4036
|
+
return Math.min(high, Math.max(low, value));
|
|
4037
|
+
}
|
|
4038
|
+
// src/interactive/pca.ts
|
|
4039
|
+
function interactivePca(target, options = {}) {
|
|
4040
|
+
const { initialPoints, onDone, ...plotOptions } = options;
|
|
4041
|
+
const { surface, element } = resolveInteractiveTarget(target);
|
|
4042
|
+
let points = initialPoints ?? [];
|
|
4043
|
+
let fit = null;
|
|
4044
|
+
function draw() {
|
|
4045
|
+
fit = plotPca(surface, points, plotOptions);
|
|
4046
|
+
}
|
|
4047
|
+
function handleClick(event) {
|
|
4048
|
+
const scale = pcaScale(surface.width, surface.height, plotOptions);
|
|
4049
|
+
const pixel = eventPixel(element, surface, event);
|
|
4050
|
+
points = [
|
|
4051
|
+
...points,
|
|
4052
|
+
{ x: scale.toWorldX(pixel.x), y: scale.toWorldY(pixel.y) }
|
|
4053
|
+
];
|
|
4054
|
+
draw();
|
|
4055
|
+
}
|
|
4056
|
+
draw();
|
|
4057
|
+
element.addEventListener("click", handleClick);
|
|
4058
|
+
return {
|
|
4059
|
+
getPoints: () => [...points],
|
|
4060
|
+
getFit: () => fit,
|
|
4061
|
+
reset() {
|
|
4062
|
+
points = [];
|
|
4063
|
+
draw();
|
|
4064
|
+
},
|
|
4065
|
+
done() {
|
|
4066
|
+
onDone?.({ points: [...points], fit });
|
|
4067
|
+
},
|
|
4068
|
+
destroy() {
|
|
4069
|
+
element.removeEventListener("click", handleClick);
|
|
4070
|
+
}
|
|
4071
|
+
};
|
|
4072
|
+
}
|
|
4073
|
+
// src/interactive/regression.ts
|
|
4074
|
+
function interactiveRegression(target, options = {}) {
|
|
4075
|
+
const { initialPoints, onDone, ...plotOptions } = options;
|
|
4076
|
+
const { surface, element } = resolveInteractiveTarget(target);
|
|
4077
|
+
const scale = regressionScale(surface.width, surface.height);
|
|
4078
|
+
let points = initialPoints ?? [];
|
|
4079
|
+
function draw() {
|
|
4080
|
+
plotRegression(surface, points, plotOptions);
|
|
4081
|
+
}
|
|
4082
|
+
function handleClick(event) {
|
|
4083
|
+
const pixel = eventPixel(element, surface, event);
|
|
4084
|
+
if (!pixelInArea(scale.area, pixel)) {
|
|
4085
|
+
return;
|
|
4086
|
+
}
|
|
4087
|
+
points = [
|
|
4088
|
+
...points,
|
|
4089
|
+
{ x: scale.toWorldX(pixel.x), y: scale.toWorldY(pixel.y) }
|
|
4090
|
+
];
|
|
4091
|
+
draw();
|
|
4092
|
+
}
|
|
4093
|
+
draw();
|
|
4094
|
+
element.addEventListener("click", handleClick);
|
|
4095
|
+
return {
|
|
4096
|
+
getPoints: () => [...points],
|
|
4097
|
+
reset() {
|
|
4098
|
+
points = [];
|
|
4099
|
+
draw();
|
|
4100
|
+
},
|
|
4101
|
+
done() {
|
|
4102
|
+
onDone?.([...points]);
|
|
4103
|
+
},
|
|
4104
|
+
destroy() {
|
|
4105
|
+
element.removeEventListener("click", handleClick);
|
|
4106
|
+
}
|
|
4107
|
+
};
|
|
4108
|
+
}
|
|
4109
|
+
export {
|
|
4110
|
+
tTestStats,
|
|
4111
|
+
tTestScale,
|
|
4112
|
+
simulateSampleCi,
|
|
4113
|
+
seededRng,
|
|
4114
|
+
sd,
|
|
4115
|
+
samplingScale,
|
|
4116
|
+
sampleWithoutReplacement,
|
|
4117
|
+
sampleConfidenceIntervals,
|
|
4118
|
+
sampleCiScale,
|
|
4119
|
+
runif,
|
|
4120
|
+
rt,
|
|
4121
|
+
rnorm,
|
|
4122
|
+
rlnorm,
|
|
4123
|
+
resolveTarget,
|
|
4124
|
+
resolveInteractiveTarget,
|
|
4125
|
+
resolveControlTarget,
|
|
4126
|
+
requireNumericColumn,
|
|
4127
|
+
regressionScale,
|
|
4128
|
+
rcauchy,
|
|
4129
|
+
rPretty,
|
|
4130
|
+
quantiles,
|
|
4131
|
+
quantile,
|
|
4132
|
+
qt,
|
|
4133
|
+
pt,
|
|
4134
|
+
principalComponents,
|
|
4135
|
+
prettyTicks,
|
|
4136
|
+
predictLogit,
|
|
4137
|
+
plotTTest,
|
|
4138
|
+
plotSampling,
|
|
4139
|
+
plotSampleCi,
|
|
4140
|
+
plotRegression,
|
|
4141
|
+
plotPca,
|
|
4142
|
+
plotMatrixInverse,
|
|
4143
|
+
plotLogit,
|
|
4144
|
+
pixelInArea,
|
|
4145
|
+
pcaScale,
|
|
4146
|
+
pcaDegenerate,
|
|
4147
|
+
numericColumns,
|
|
4148
|
+
normalCdf,
|
|
4149
|
+
nclassSturges,
|
|
4150
|
+
moderationSurface,
|
|
4151
|
+
moderationData,
|
|
4152
|
+
median,
|
|
4153
|
+
meanAbsoluteDeviation,
|
|
4154
|
+
mean,
|
|
4155
|
+
matrixInverseScale,
|
|
4156
|
+
machinePrecision,
|
|
4157
|
+
logitScale,
|
|
4158
|
+
logisticRegression,
|
|
4159
|
+
logGamma,
|
|
4160
|
+
logBeta,
|
|
4161
|
+
linearRegression,
|
|
4162
|
+
leastSquares,
|
|
4163
|
+
kernelDensity,
|
|
4164
|
+
isNumericColumn,
|
|
4165
|
+
invertMatrix,
|
|
4166
|
+
inverseIncompleteBeta,
|
|
4167
|
+
interactiveTTest,
|
|
4168
|
+
interactiveSampling,
|
|
4169
|
+
interactiveRegression,
|
|
4170
|
+
interactivePca,
|
|
4171
|
+
interactiveMatrixInverse,
|
|
4172
|
+
interactiveLogit,
|
|
4173
|
+
incompleteBetaSplit,
|
|
4174
|
+
incompleteBeta,
|
|
4175
|
+
histogram,
|
|
4176
|
+
frameRows,
|
|
4177
|
+
formatStat,
|
|
4178
|
+
formatNumber,
|
|
4179
|
+
eventPixel,
|
|
4180
|
+
dt,
|
|
4181
|
+
drawSamples,
|
|
4182
|
+
drawAxes,
|
|
4183
|
+
determinant,
|
|
4184
|
+
createScale,
|
|
4185
|
+
bwNrd0,
|
|
4186
|
+
DEFAULT_T_TEST_OPTIONS,
|
|
4187
|
+
DEFAULT_MATRIX_INVERSE_VALUES,
|
|
4188
|
+
DEFAULT_MARGINS,
|
|
4189
|
+
DEFAULT_LOGIT_MAX_ITERATIONS,
|
|
4190
|
+
DEFAULT_LOGIT_EPSILON,
|
|
4191
|
+
DEFAULT_LEAST_SQUARES_TOLERANCE
|
|
4192
|
+
};
|
|
4193
|
+
|
|
4194
|
+
//# debugId=2FC10F4594685E1B64756E2164756E21
|
|
4195
|
+
//# sourceMappingURL=index.js.map
|