@cloudglides/nox 1.1.6 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -9
- package/example/package.json +1 -1
- package/example/src/App.css +38 -7
- package/example/src/App.jsx +328 -25
- package/package.json +7 -6
- package/pnpm-workspace.yaml +3 -0
- package/src/core.browser.js +37 -4
- package/src/core.js +37 -4
- package/src/generators/logistic.js +30 -25
- package/src/generators/mixer.js +7 -7
- package/src/generators/mt19937.js +10 -7
- package/src/generators/pcg64.js +23 -12
- package/src/generators/splitmix64.js +12 -6
- package/src/generators/tent.js +12 -7
- package/src/generators/xorshift64.js +6 -3
- package/src/index.browser.js +168 -1
- package/src/index.d.ts +68 -4
- package/src/index.js +169 -1
- package/src/rng.browser.js +5 -6
- package/src/rng.js +95 -94
- package/src/utils/arrays.js +149 -0
- package/src/utils/bits.js +146 -21
- package/src/utils/categorical.js +68 -31
- package/src/utils/clustering.js +143 -0
- package/src/utils/combinatorics.js +113 -69
- package/src/utils/confidence.js +145 -0
- package/src/utils/decomposition.js +204 -0
- package/src/utils/diagnostics.js +309 -0
- package/src/utils/distributions-advanced.js +122 -0
- package/src/utils/distributions-extra.js +102 -11
- package/src/utils/distributions-special.js +77 -20
- package/src/utils/distributions.js +99 -35
- package/src/utils/effects.js +172 -0
- package/src/utils/entropy.browser.js +29 -26
- package/src/utils/entropy.js +18 -8
- package/src/utils/helpers.js +64 -0
- package/src/utils/hypothesis.js +167 -0
- package/src/utils/integration.js +137 -0
- package/src/utils/interpolation.js +221 -0
- package/src/utils/logistic.js +91 -0
- package/src/utils/matrix.js +242 -0
- package/src/utils/noise.js +36 -22
- package/src/utils/odesolvers.js +176 -0
- package/src/utils/optimization.js +215 -0
- package/src/utils/polynomial.js +136 -0
- package/src/utils/precomputed.js +166 -0
- package/src/utils/probability.js +199 -0
- package/src/utils/pvalues.js +142 -0
- package/src/utils/regression.js +170 -0
- package/src/utils/resampling.js +112 -0
- package/src/utils/rootfinding.js +158 -0
- package/src/utils/sampling.js +86 -77
- package/src/utils/seed.js +10 -4
- package/src/utils/seeding.js +24 -12
- package/src/utils/sequence.js +116 -32
- package/src/utils/state.js +48 -36
- package/src/utils/statistics.js +64 -2
- package/src/utils/stochastic.js +91 -31
- package/src/utils/stratified.js +108 -0
- package/src/utils/timeseries.js +166 -0
- package/src/utils/transforms.js +146 -0
- package/test/comprehensive.js +4 -3
- package/test/new-features.js +52 -0
- package/IMPROVEMENTS.md +0 -58
- package/PERFORMANCE.md +0 -69
- package/example/pnpm-lock.yaml +0 -1006
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { gamma } from './distributions-extra.js';
|
|
2
|
+
|
|
3
|
+
export function dirichlet(r, alpha) {
|
|
4
|
+
if (!Array.isArray(alpha) || alpha.length === 0) {
|
|
5
|
+
throw new TypeError('alpha must be a non-empty array');
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const k = alpha.length;
|
|
9
|
+
const samples = new Array(k);
|
|
10
|
+
let sum = 0;
|
|
11
|
+
|
|
12
|
+
for (let i = 0; i < k; i++) {
|
|
13
|
+
if (typeof alpha[i] !== 'number' || alpha[i] <= 0) {
|
|
14
|
+
throw new RangeError('all alpha values must be positive');
|
|
15
|
+
}
|
|
16
|
+
samples[i] = gamma(r, alpha[i], 1);
|
|
17
|
+
sum += samples[i];
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
for (let i = 0; i < k; i++) {
|
|
21
|
+
samples[i] /= sum;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return samples;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function dirichlets(r, alpha, n) {
|
|
28
|
+
if (typeof n !== 'number' || n < 1) {
|
|
29
|
+
throw new RangeError('n must be a positive integer');
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const samples = new Array(n);
|
|
33
|
+
for (let i = 0; i < n; i++) {
|
|
34
|
+
samples[i] = dirichlet(r, alpha);
|
|
35
|
+
}
|
|
36
|
+
return samples;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function mixture(r, distributions, weights) {
|
|
40
|
+
if (!Array.isArray(distributions) || distributions.length === 0) {
|
|
41
|
+
throw new TypeError('distributions must be a non-empty array');
|
|
42
|
+
}
|
|
43
|
+
if (!Array.isArray(weights) || weights.length !== distributions.length) {
|
|
44
|
+
throw new TypeError('weights length must match distributions');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
let sum = weights.reduce((a, b) => a + b, 0);
|
|
48
|
+
if (sum <= 0) {
|
|
49
|
+
throw new RangeError('weights must sum to positive value');
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const normalized = weights.map(w => w / sum);
|
|
53
|
+
let u = r.nextFloat();
|
|
54
|
+
let cumsum = 0;
|
|
55
|
+
|
|
56
|
+
for (let i = 0; i < normalized.length; i++) {
|
|
57
|
+
cumsum += normalized[i];
|
|
58
|
+
if (u < cumsum) {
|
|
59
|
+
return distributions[i](r);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return distributions[distributions.length - 1](r);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function mixtures(r, distributions, weights, n) {
|
|
67
|
+
if (typeof n !== 'number' || n < 1) {
|
|
68
|
+
throw new RangeError('n must be a positive integer');
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const samples = new Array(n);
|
|
72
|
+
for (let i = 0; i < n; i++) {
|
|
73
|
+
samples[i] = mixture(r, distributions, weights);
|
|
74
|
+
}
|
|
75
|
+
return samples;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function studentT(r, df) {
|
|
79
|
+
if (typeof df !== 'number' || df <= 0) {
|
|
80
|
+
throw new RangeError('degrees of freedom must be positive');
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const z = r.normal();
|
|
84
|
+
const u = r.nextFloat();
|
|
85
|
+
const chi2Val = -2 * Math.log(u) + (df - 1) * Math.log(1 - 2 * u / df);
|
|
86
|
+
const v = Math.sqrt((df * chi2Val) / (df + z * z));
|
|
87
|
+
|
|
88
|
+
return z / v;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function studentTs(r, df, n) {
|
|
92
|
+
if (typeof n !== 'number' || n < 1) {
|
|
93
|
+
throw new RangeError('n must be a positive integer');
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const samples = new Array(n);
|
|
97
|
+
for (let i = 0; i < n; i++) {
|
|
98
|
+
samples[i] = studentT(r, df);
|
|
99
|
+
}
|
|
100
|
+
return samples;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function betaBinomial(r, n, alpha, beta) {
|
|
104
|
+
if (typeof n !== 'number' || n < 0 || n !== Math.floor(n)) {
|
|
105
|
+
throw new RangeError('n must be a non-negative integer');
|
|
106
|
+
}
|
|
107
|
+
if (typeof alpha !== 'number' || alpha <= 0) {
|
|
108
|
+
throw new RangeError('alpha must be positive');
|
|
109
|
+
}
|
|
110
|
+
if (typeof beta !== 'number' || beta <= 0) {
|
|
111
|
+
throw new RangeError('beta must be positive');
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const p = gamma(r, alpha, 1) / (gamma(r, alpha, 1) + gamma(r, beta, 1));
|
|
115
|
+
let count = 0;
|
|
116
|
+
|
|
117
|
+
for (let i = 0; i < n; i++) {
|
|
118
|
+
if (r.nextFloat() < p) count++;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return count;
|
|
122
|
+
}
|
|
@@ -1,21 +1,59 @@
|
|
|
1
1
|
export const beta = (rng, alpha, beta) => {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
2
|
+
if (!rng || typeof rng.nextFloat !== 'function') {
|
|
3
|
+
throw new TypeError('First argument must be RNG instance');
|
|
4
|
+
}
|
|
5
|
+
if (typeof alpha !== 'number') {
|
|
6
|
+
throw new TypeError('alpha must be a number');
|
|
7
|
+
}
|
|
8
|
+
if (alpha <= 0) {
|
|
9
|
+
throw new RangeError('alpha must be positive');
|
|
10
|
+
}
|
|
11
|
+
if (typeof beta !== 'number') {
|
|
12
|
+
throw new TypeError('beta must be a number');
|
|
13
|
+
}
|
|
14
|
+
if (beta <= 0) {
|
|
15
|
+
throw new RangeError('beta must be positive');
|
|
16
|
+
}
|
|
17
|
+
let u = rng.nextFloat();
|
|
18
|
+
while (u === 0 || u === 1) {
|
|
19
|
+
u = rng.nextFloat();
|
|
20
|
+
}
|
|
21
|
+
let v = rng.nextFloat();
|
|
22
|
+
while (v === 0 || v === 1) {
|
|
23
|
+
v = rng.nextFloat();
|
|
24
|
+
}
|
|
25
|
+
const uVal = -Math.log(u) / alpha;
|
|
26
|
+
const vVal = -Math.log(v) / beta;
|
|
27
|
+
return uVal / (uVal + vVal);
|
|
28
|
+
};
|
|
6
29
|
|
|
7
30
|
export const gamma = (rng, shape, scale = 1) => {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
31
|
+
if (!rng || typeof rng.nextFloat !== 'function') {
|
|
32
|
+
throw new TypeError('First argument must be RNG instance');
|
|
33
|
+
}
|
|
34
|
+
if (typeof shape !== 'number') {
|
|
35
|
+
throw new TypeError('shape must be a number');
|
|
36
|
+
}
|
|
37
|
+
if (shape <= 0) {
|
|
38
|
+
throw new RangeError('shape must be positive');
|
|
39
|
+
}
|
|
40
|
+
if (typeof scale !== 'number') {
|
|
41
|
+
throw new TypeError('scale must be a number');
|
|
42
|
+
}
|
|
43
|
+
if (scale <= 0) {
|
|
44
|
+
throw new RangeError('scale must be positive');
|
|
45
|
+
}
|
|
46
|
+
if (shape < 1) {
|
|
47
|
+
return gamma(rng, shape + 1, scale) * Math.pow(rng.nextFloat(), 1 / shape);
|
|
48
|
+
}
|
|
11
49
|
|
|
12
50
|
const d = shape - 1 / 3;
|
|
13
51
|
const c = 1 / Math.sqrt(9 * d);
|
|
14
52
|
|
|
15
|
-
let z, v, u;
|
|
53
|
+
let z, v, u, x;
|
|
16
54
|
do {
|
|
17
55
|
do {
|
|
18
|
-
|
|
56
|
+
x = (rng.nextFloat() * 2 - 1);
|
|
19
57
|
v = 1 + c * x;
|
|
20
58
|
} while (v <= 0);
|
|
21
59
|
|
|
@@ -28,5 +66,58 @@ export const gamma = (rng, shape, scale = 1) => {
|
|
|
28
66
|
};
|
|
29
67
|
|
|
30
68
|
export const chi2 = (rng, k) => {
|
|
31
|
-
|
|
32
|
-
|
|
69
|
+
if (!rng || typeof rng.nextFloat !== 'function') {
|
|
70
|
+
throw new TypeError('First argument must be RNG instance');
|
|
71
|
+
}
|
|
72
|
+
if (typeof k !== 'number') {
|
|
73
|
+
throw new TypeError('k must be a number');
|
|
74
|
+
}
|
|
75
|
+
if (k <= 0) {
|
|
76
|
+
throw new RangeError('k must be positive');
|
|
77
|
+
}
|
|
78
|
+
return gamma(rng, k / 2, 2);
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
export const binomial = (rng, n, p) => {
|
|
82
|
+
if (!rng || typeof rng.nextFloat !== 'function') {
|
|
83
|
+
throw new TypeError('First argument must be RNG instance');
|
|
84
|
+
}
|
|
85
|
+
if (typeof n !== 'number' || !Number.isInteger(n)) {
|
|
86
|
+
throw new TypeError('n must be an integer');
|
|
87
|
+
}
|
|
88
|
+
if (n <= 0) {
|
|
89
|
+
throw new RangeError('n must be positive');
|
|
90
|
+
}
|
|
91
|
+
if (typeof p !== 'number') {
|
|
92
|
+
throw new TypeError('p must be a number');
|
|
93
|
+
}
|
|
94
|
+
if (p < 0 || p > 1) {
|
|
95
|
+
throw new RangeError('p must be between 0 and 1');
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (p === 0) return 0;
|
|
99
|
+
if (p === 1) return n;
|
|
100
|
+
if (n > 30 && p > 0.1 && p < 0.9) {
|
|
101
|
+
return Math.round(n * p + Math.sqrt(n * p * (1 - p)) * (rng.nextFloat() * 2 - 1));
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
let count = 0;
|
|
105
|
+
for (let i = 0; i < n; i++) {
|
|
106
|
+
if (rng.nextFloat() < p) count++;
|
|
107
|
+
}
|
|
108
|
+
return count;
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
export const geometric = (rng, p) => {
|
|
112
|
+
if (!rng || typeof rng.nextFloat !== 'function') {
|
|
113
|
+
throw new TypeError('First argument must be RNG instance');
|
|
114
|
+
}
|
|
115
|
+
if (typeof p !== 'number') {
|
|
116
|
+
throw new TypeError('p must be a number');
|
|
117
|
+
}
|
|
118
|
+
if (p <= 0 || p > 1) {
|
|
119
|
+
throw new RangeError('p must be between 0 and 1 (exclusive 0)');
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return Math.floor(Math.log(rng.nextFloat()) / Math.log(1 - p)) + 1;
|
|
123
|
+
};
|
|
@@ -1,21 +1,78 @@
|
|
|
1
1
|
export const weibull = (rng, shape, scale = 1) => {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
}
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
2
|
+
if (!rng || typeof rng.nextFloat !== 'function') {
|
|
3
|
+
throw new TypeError('First argument must be RNG instance');
|
|
4
|
+
}
|
|
5
|
+
if (typeof shape !== 'number') {
|
|
6
|
+
throw new TypeError('shape must be a number');
|
|
7
|
+
}
|
|
8
|
+
if (shape <= 0) {
|
|
9
|
+
throw new RangeError('shape must be positive');
|
|
10
|
+
}
|
|
11
|
+
if (typeof scale !== 'number') {
|
|
12
|
+
throw new TypeError('scale must be a number');
|
|
13
|
+
}
|
|
14
|
+
if (scale <= 0) {
|
|
15
|
+
throw new RangeError('scale must be positive');
|
|
16
|
+
}
|
|
17
|
+
let u = rng.nextFloat();
|
|
18
|
+
while (u === 0 || u === 1) {
|
|
19
|
+
u = rng.nextFloat();
|
|
20
|
+
}
|
|
21
|
+
return scale * Math.pow(-Math.log(1 - u), 1 / shape);
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export const lognormal = (rng, mu, sigma) => {
|
|
25
|
+
if (!rng || typeof rng.nextFloat !== 'function') {
|
|
26
|
+
throw new TypeError('First argument must be RNG instance');
|
|
27
|
+
}
|
|
28
|
+
if (typeof mu !== 'number') {
|
|
29
|
+
throw new TypeError('mu must be a number');
|
|
30
|
+
}
|
|
31
|
+
if (typeof sigma !== 'number') {
|
|
32
|
+
throw new TypeError('sigma must be a number');
|
|
33
|
+
}
|
|
34
|
+
if (sigma <= 0) {
|
|
35
|
+
throw new RangeError('sigma must be positive');
|
|
36
|
+
}
|
|
37
|
+
let u1 = rng.nextFloat();
|
|
38
|
+
while (u1 === 0 || u1 === 1) {
|
|
39
|
+
u1 = rng.nextFloat();
|
|
40
|
+
}
|
|
41
|
+
const u2 = rng.nextFloat();
|
|
42
|
+
const z = Math.sqrt(-2 * Math.log(u1)) * Math.cos(2 * Math.PI * u2);
|
|
43
|
+
return Math.exp(mu + sigma * z);
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export const rayleigh = (rng, sigma = 1) => {
|
|
47
|
+
if (!rng || typeof rng.nextFloat !== 'function') {
|
|
48
|
+
throw new TypeError('First argument must be RNG instance');
|
|
49
|
+
}
|
|
50
|
+
if (typeof sigma !== 'number') {
|
|
51
|
+
throw new TypeError('sigma must be a number');
|
|
52
|
+
}
|
|
53
|
+
if (sigma <= 0) {
|
|
54
|
+
throw new RangeError('sigma must be positive');
|
|
55
|
+
}
|
|
56
|
+
let u = rng.nextFloat();
|
|
57
|
+
while (u === 0 || u === 1) {
|
|
58
|
+
u = rng.nextFloat();
|
|
59
|
+
}
|
|
60
|
+
return sigma * Math.sqrt(-2 * Math.log(u));
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
export const cauchy = (rng, median = 0, scale = 1) => {
|
|
64
|
+
if (!rng || typeof rng.nextFloat !== 'function') {
|
|
65
|
+
throw new TypeError('First argument must be RNG instance');
|
|
66
|
+
}
|
|
67
|
+
if (typeof median !== 'number') {
|
|
68
|
+
throw new TypeError('median must be a number');
|
|
69
|
+
}
|
|
70
|
+
if (typeof scale !== 'number') {
|
|
71
|
+
throw new TypeError('scale must be a number');
|
|
72
|
+
}
|
|
73
|
+
if (scale <= 0) {
|
|
74
|
+
throw new RangeError('scale must be positive');
|
|
75
|
+
}
|
|
76
|
+
const u = rng.nextFloat();
|
|
77
|
+
return median + scale * Math.tan(Math.PI * (u - 0.5));
|
|
78
|
+
};
|
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
const TWO_PI = 2 * Math.PI;
|
|
2
2
|
|
|
3
3
|
export const normal = (rng, mean = 0, stddev = 1) => {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
4
|
+
if (!rng || typeof rng.nextFloat !== 'function') {
|
|
5
|
+
throw new TypeError('First argument must be RNG instance');
|
|
6
|
+
}
|
|
7
|
+
if (typeof mean !== 'number') {
|
|
8
|
+
throw new TypeError('mean must be a number');
|
|
9
|
+
}
|
|
10
|
+
if (typeof stddev !== 'number') {
|
|
11
|
+
throw new TypeError('stddev must be a number');
|
|
12
|
+
}
|
|
13
|
+
if (stddev <= 0) {
|
|
14
|
+
throw new RangeError('stddev must be positive');
|
|
15
|
+
}
|
|
13
16
|
|
|
14
17
|
let u1, u2;
|
|
15
18
|
do {
|
|
@@ -22,27 +25,34 @@ export const normal = (rng, mean = 0, stddev = 1) => {
|
|
|
22
25
|
};
|
|
23
26
|
|
|
24
27
|
export const exponential = (rng, lambda = 1) => {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
28
|
+
if (!rng || typeof rng.nextFloat !== 'function') {
|
|
29
|
+
throw new TypeError('First argument must be RNG instance');
|
|
30
|
+
}
|
|
31
|
+
if (typeof lambda !== 'number') {
|
|
32
|
+
throw new TypeError('lambda must be a number');
|
|
33
|
+
}
|
|
34
|
+
if (lambda <= 0) {
|
|
35
|
+
throw new RangeError('lambda must be positive');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
let u = rng.nextFloat();
|
|
39
|
+
while (u === 0 || u === 1) {
|
|
40
|
+
u = rng.nextFloat();
|
|
41
|
+
}
|
|
42
|
+
const result = -Math.log(1 - u) / lambda;
|
|
43
|
+
return Number.isFinite(result) ? result : -Math.log(u) / lambda;
|
|
44
|
+
};
|
|
38
45
|
|
|
39
46
|
export const poisson = (rng, lambda) => {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
47
|
+
if (!rng || typeof rng.nextFloat !== 'function') {
|
|
48
|
+
throw new TypeError('First argument must be RNG instance');
|
|
49
|
+
}
|
|
50
|
+
if (typeof lambda !== 'number') {
|
|
51
|
+
throw new TypeError('lambda must be a number');
|
|
52
|
+
}
|
|
53
|
+
if (lambda <= 0) {
|
|
54
|
+
throw new RangeError('lambda must be positive');
|
|
55
|
+
}
|
|
46
56
|
|
|
47
57
|
const L = Math.exp(-lambda);
|
|
48
58
|
let k = 0;
|
|
@@ -57,15 +67,69 @@ export const poisson = (rng, lambda) => {
|
|
|
57
67
|
};
|
|
58
68
|
|
|
59
69
|
export const uniform = (rng, min, max) => {
|
|
60
|
-
|
|
61
|
-
|
|
70
|
+
if (!rng || typeof rng.nextFloat !== 'function') {
|
|
71
|
+
throw new TypeError('First argument must be RNG instance');
|
|
72
|
+
}
|
|
73
|
+
if (typeof min !== 'number') {
|
|
74
|
+
throw new TypeError('min must be a number');
|
|
75
|
+
}
|
|
76
|
+
if (typeof max !== 'number') {
|
|
77
|
+
throw new TypeError('max must be a number');
|
|
78
|
+
}
|
|
79
|
+
if (min >= max) {
|
|
80
|
+
throw new RangeError('min must be less than max');
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return min + rng.nextFloat() * (max - min);
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
export const normals = (rng, count, mean = 0, stddev = 1) => {
|
|
87
|
+
if (typeof count !== 'number' || !Number.isInteger(count)) {
|
|
88
|
+
throw new TypeError('count must be an integer');
|
|
62
89
|
}
|
|
63
|
-
if (
|
|
64
|
-
throw new
|
|
90
|
+
if (count <= 0) {
|
|
91
|
+
throw new RangeError('count must be positive');
|
|
65
92
|
}
|
|
66
|
-
|
|
67
|
-
|
|
93
|
+
const result = new Array(count);
|
|
94
|
+
let cached = null;
|
|
95
|
+
|
|
96
|
+
for (let i = 0; i < count; i++) {
|
|
97
|
+
if (cached !== null) {
|
|
98
|
+
result[i] = cached * stddev + mean;
|
|
99
|
+
cached = null;
|
|
100
|
+
} else {
|
|
101
|
+
let u1, u2;
|
|
102
|
+
do {
|
|
103
|
+
u1 = rng.nextFloat();
|
|
104
|
+
u2 = rng.nextFloat();
|
|
105
|
+
} while (u1 <= 0 || u2 <= 0);
|
|
106
|
+
|
|
107
|
+
const z0 = Math.sqrt(-2 * Math.log(u1)) * Math.cos(TWO_PI * u2);
|
|
108
|
+
const z1 = Math.sqrt(-2 * Math.log(u1)) * Math.sin(TWO_PI * u2);
|
|
109
|
+
result[i] = z0 * stddev + mean;
|
|
110
|
+
cached = z1;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return result;
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
export const exponentials = (rng, count, lambda = 1) => {
|
|
118
|
+
if (typeof count !== 'number' || !Number.isInteger(count)) {
|
|
119
|
+
throw new TypeError('count must be an integer');
|
|
120
|
+
}
|
|
121
|
+
if (count <= 0) {
|
|
122
|
+
throw new RangeError('count must be positive');
|
|
123
|
+
}
|
|
124
|
+
const result = new Array(count);
|
|
125
|
+
|
|
126
|
+
for (let i = 0; i < count; i++) {
|
|
127
|
+
let u = rng.nextFloat();
|
|
128
|
+
while (u === 0 || u === 1) {
|
|
129
|
+
u = rng.nextFloat();
|
|
130
|
+
}
|
|
131
|
+
result[i] = -Math.log(1 - u) / lambda;
|
|
68
132
|
}
|
|
69
133
|
|
|
70
|
-
return
|
|
134
|
+
return result;
|
|
71
135
|
};
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
export function cohensD(group1, group2) {
|
|
2
|
+
if (!Array.isArray(group1) || group1.length === 0) {
|
|
3
|
+
throw new TypeError('group1 must be non-empty array');
|
|
4
|
+
}
|
|
5
|
+
if (!Array.isArray(group2) || group2.length === 0) {
|
|
6
|
+
throw new TypeError('group2 must be non-empty array');
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const n1 = group1.length;
|
|
10
|
+
const n2 = group2.length;
|
|
11
|
+
|
|
12
|
+
const mean1 = group1.reduce((a, b) => a + b, 0) / n1;
|
|
13
|
+
const mean2 = group2.reduce((a, b) => a + b, 0) / n2;
|
|
14
|
+
|
|
15
|
+
let var1 = 0, var2 = 0;
|
|
16
|
+
for (let i = 0; i < n1; i++) {
|
|
17
|
+
var1 += (group1[i] - mean1) ** 2;
|
|
18
|
+
}
|
|
19
|
+
for (let i = 0; i < n2; i++) {
|
|
20
|
+
var2 += (group2[i] - mean2) ** 2;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
var1 /= n1 - 1;
|
|
24
|
+
var2 /= n2 - 1;
|
|
25
|
+
|
|
26
|
+
const pooledStd = Math.sqrt(((n1 - 1) * var1 + (n2 - 1) * var2) / (n1 + n2 - 2));
|
|
27
|
+
|
|
28
|
+
return (mean1 - mean2) / pooledStd;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function hedgesG(group1, group2) {
|
|
32
|
+
const d = cohensD(group1, group2);
|
|
33
|
+
const n1 = group1.length;
|
|
34
|
+
const n2 = group2.length;
|
|
35
|
+
const n = n1 + n2;
|
|
36
|
+
const correction = 1 - (3 / (4 * n - 9));
|
|
37
|
+
|
|
38
|
+
return d * correction;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function correlation(x, y) {
|
|
42
|
+
if (!Array.isArray(x) || !Array.isArray(y)) {
|
|
43
|
+
throw new TypeError('x and y must be arrays');
|
|
44
|
+
}
|
|
45
|
+
if (x.length !== y.length || x.length === 0) {
|
|
46
|
+
throw new RangeError('x and y must have same non-zero length');
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const n = x.length;
|
|
50
|
+
const meanX = x.reduce((a, b) => a + b, 0) / n;
|
|
51
|
+
const meanY = y.reduce((a, b) => a + b, 0) / n;
|
|
52
|
+
|
|
53
|
+
let sumXY = 0, sumX2 = 0, sumY2 = 0;
|
|
54
|
+
|
|
55
|
+
for (let i = 0; i < n; i++) {
|
|
56
|
+
const dx = x[i] - meanX;
|
|
57
|
+
const dy = y[i] - meanY;
|
|
58
|
+
sumXY += dx * dy;
|
|
59
|
+
sumX2 += dx * dx;
|
|
60
|
+
sumY2 += dy * dy;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return sumXY / Math.sqrt(sumX2 * sumY2);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function cramersV(contingencyTable) {
|
|
67
|
+
if (!Array.isArray(contingencyTable) || contingencyTable.length === 0) {
|
|
68
|
+
throw new TypeError('contingencyTable must be non-empty array');
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const rows = contingencyTable.length;
|
|
72
|
+
const cols = contingencyTable[0].length;
|
|
73
|
+
|
|
74
|
+
let n = 0;
|
|
75
|
+
for (let i = 0; i < rows; i++) {
|
|
76
|
+
for (let j = 0; j < cols; j++) {
|
|
77
|
+
if (typeof contingencyTable[i][j] !== 'number') {
|
|
78
|
+
throw new TypeError('all table entries must be numbers');
|
|
79
|
+
}
|
|
80
|
+
n += contingencyTable[i][j];
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
let chi2 = 0;
|
|
85
|
+
const rowTotals = new Array(rows);
|
|
86
|
+
const colTotals = new Array(cols);
|
|
87
|
+
|
|
88
|
+
for (let i = 0; i < rows; i++) {
|
|
89
|
+
rowTotals[i] = 0;
|
|
90
|
+
for (let j = 0; j < cols; j++) {
|
|
91
|
+
rowTotals[i] += contingencyTable[i][j];
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
for (let j = 0; j < cols; j++) {
|
|
96
|
+
colTotals[j] = 0;
|
|
97
|
+
for (let i = 0; i < rows; i++) {
|
|
98
|
+
colTotals[j] += contingencyTable[i][j];
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
for (let i = 0; i < rows; i++) {
|
|
103
|
+
for (let j = 0; j < cols; j++) {
|
|
104
|
+
const expected = (rowTotals[i] * colTotals[j]) / n;
|
|
105
|
+
const diff = contingencyTable[i][j] - expected;
|
|
106
|
+
chi2 += (diff * diff) / expected;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const minDim = Math.min(rows, cols) - 1;
|
|
111
|
+
return Math.sqrt(chi2 / (n * minDim));
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export function etaSquared(groups) {
|
|
115
|
+
if (!Array.isArray(groups) || groups.length === 0) {
|
|
116
|
+
throw new TypeError('groups must be non-empty array of arrays');
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
let totalN = 0;
|
|
120
|
+
const groupMeans = new Array(groups.length);
|
|
121
|
+
const groupSizes = new Array(groups.length);
|
|
122
|
+
|
|
123
|
+
for (let i = 0; i < groups.length; i++) {
|
|
124
|
+
if (!Array.isArray(groups[i])) {
|
|
125
|
+
throw new TypeError('each group must be an array');
|
|
126
|
+
}
|
|
127
|
+
groupSizes[i] = groups[i].length;
|
|
128
|
+
totalN += groupSizes[i];
|
|
129
|
+
groupMeans[i] = groups[i].reduce((a, b) => a + b, 0) / groupSizes[i];
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const grandMean = groups.flat().reduce((a, b) => a + b, 0) / totalN;
|
|
133
|
+
|
|
134
|
+
let ssB = 0;
|
|
135
|
+
for (let i = 0; i < groups.length; i++) {
|
|
136
|
+
ssB += groupSizes[i] * (groupMeans[i] - grandMean) ** 2;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
let ssT = 0;
|
|
140
|
+
for (let i = 0; i < groups.length; i++) {
|
|
141
|
+
for (let j = 0; j < groups[i].length; j++) {
|
|
142
|
+
ssT += (groups[i][j] - grandMean) ** 2;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return ssB / ssT;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export function glasssDelta(control, treatment, pooledStd = null) {
|
|
150
|
+
if (!Array.isArray(control) || control.length === 0) {
|
|
151
|
+
throw new TypeError('control must be non-empty array');
|
|
152
|
+
}
|
|
153
|
+
if (!Array.isArray(treatment) || treatment.length === 0) {
|
|
154
|
+
throw new TypeError('treatment must be non-empty array');
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const meanControl = control.reduce((a, b) => a + b, 0) / control.length;
|
|
158
|
+
const meanTreatment = treatment.reduce((a, b) => a + b, 0) / treatment.length;
|
|
159
|
+
|
|
160
|
+
let std = pooledStd;
|
|
161
|
+
|
|
162
|
+
if (std === null) {
|
|
163
|
+
let varControl = 0;
|
|
164
|
+
for (let i = 0; i < control.length; i++) {
|
|
165
|
+
varControl += (control[i] - meanControl) ** 2;
|
|
166
|
+
}
|
|
167
|
+
varControl /= control.length - 1;
|
|
168
|
+
std = Math.sqrt(varControl);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return (meanTreatment - meanControl) / std;
|
|
172
|
+
}
|