@numero-uno-jpn/rstats 0.5.0 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (168) hide show
  1. package/CHANGELOG.md +101 -0
  2. package/README.ja.md +36 -20
  3. package/README.md +34 -18
  4. package/THIRD_PARTY_NOTICES.md +25 -5
  5. package/dist/chunks/chunk-2JCIB5H2.js +2 -0
  6. package/dist/chunks/chunk-7JVSDGQQ.js +2 -0
  7. package/dist/chunks/chunk-7NK4SA6B.js +2 -0
  8. package/dist/chunks/chunk-AJYQNFXK.js +2 -0
  9. package/dist/chunks/chunk-DRNMIEYH.js +2 -0
  10. package/dist/chunks/chunk-NAYDS7CL.js +2 -0
  11. package/dist/chunks/chunk-NOCEAM44.js +2 -0
  12. package/dist/chunks/chunk-QNAIK4PY.js +2 -0
  13. package/dist/chunks/chunk-TPC6MTYC.js +2 -0
  14. package/dist/chunks/chunk-UGUQYWRQ.js +2 -0
  15. package/dist/chunks/chunk-XM2TVD63.js +2 -0
  16. package/dist/chunks/chunk-Y4IZV7XK.js +2 -0
  17. package/dist/chunks/chunk-ZWJ42QN6.js +2 -0
  18. package/dist/descriptive/basic.d.ts +73 -0
  19. package/dist/descriptive/basic.js +256 -0
  20. package/dist/descriptive/cov.d.ts +39 -0
  21. package/dist/descriptive/cov.js +105 -0
  22. package/dist/descriptive/hist.d.ts +64 -0
  23. package/dist/descriptive/hist.js +304 -0
  24. package/dist/descriptive/moments.d.ts +63 -0
  25. package/dist/descriptive/moments.js +158 -0
  26. package/dist/descriptive/quantile.d.ts +50 -0
  27. package/dist/descriptive/quantile.js +131 -0
  28. package/dist/descriptive/rank.d.ts +23 -0
  29. package/dist/descriptive/rank.js +72 -0
  30. package/dist/descriptive/table.d.ts +54 -0
  31. package/dist/descriptive/table.js +160 -0
  32. package/dist/descriptive.js +2 -0
  33. package/dist/distributions/arcsine.d.ts +24 -0
  34. package/dist/distributions/arcsine.js +73 -0
  35. package/dist/distributions/invgamma.d.ts +29 -0
  36. package/dist/distributions/invgamma.js +85 -0
  37. package/dist/distributions/kumaraswamy.d.ts +17 -0
  38. package/dist/distributions/kumaraswamy.js +96 -0
  39. package/dist/distributions/laplace.d.ts +23 -0
  40. package/dist/distributions/laplace.js +69 -0
  41. package/dist/distributions/multinomial.d.ts +24 -0
  42. package/dist/distributions/multinomial.js +70 -0
  43. package/dist/distributions/nbeta.js +28 -10
  44. package/dist/distributions/nchisq.js +20 -3
  45. package/dist/distributions/pareto.d.ts +23 -0
  46. package/dist/distributions/pareto.js +76 -0
  47. package/dist/distributions/summary.d.ts +25 -0
  48. package/dist/distributions/summary.js +289 -0
  49. package/dist/distributions/triangular.d.ts +25 -0
  50. package/dist/distributions/triangular.js +87 -0
  51. package/dist/distributions.js +2 -0
  52. package/dist/entries/descriptive.d.ts +7 -0
  53. package/dist/entries/descriptive.js +8 -0
  54. package/dist/entries/distributions.d.ts +41 -0
  55. package/dist/entries/distributions.js +41 -0
  56. package/dist/entries/linalg.d.ts +11 -0
  57. package/dist/entries/linalg.js +11 -0
  58. package/dist/entries/numerics.d.ts +8 -0
  59. package/dist/entries/numerics.js +6 -0
  60. package/dist/entries/special.d.ts +9 -0
  61. package/dist/entries/special.js +9 -0
  62. package/dist/entries/tests.d.ts +21 -0
  63. package/dist/entries/tests.js +20 -0
  64. package/dist/htest/ansariTest.d.ts +13 -0
  65. package/dist/htest/ansariTest.js +80 -0
  66. package/dist/htest/aov.d.ts +85 -0
  67. package/dist/htest/aov.js +178 -0
  68. package/dist/htest/binomTest.d.ts +36 -0
  69. package/dist/htest/binomTest.js +121 -0
  70. package/dist/htest/cor.d.ts +14 -0
  71. package/dist/htest/cor.js +4 -3
  72. package/dist/htest/core/ansari.d.ts +18 -0
  73. package/dist/htest/core/ansari.js +121 -0
  74. package/dist/htest/core/kolmogorov.d.ts +26 -0
  75. package/dist/htest/core/kolmogorov.js +221 -0
  76. package/dist/htest/core/mantelhaen.d.ts +26 -0
  77. package/dist/htest/core/mantelhaen.js +145 -0
  78. package/dist/htest/core/shapiro.d.ts +24 -0
  79. package/dist/htest/core/shapiro.js +142 -0
  80. package/dist/htest/core/smirnov.d.ts +20 -0
  81. package/dist/htest/core/smirnov.js +75 -0
  82. package/dist/htest/fisherTest.js +134 -120
  83. package/dist/htest/groups.d.ts +20 -0
  84. package/dist/htest/groups.js +62 -0
  85. package/dist/htest/ksTest.d.ts +31 -0
  86. package/dist/htest/ksTest.js +106 -0
  87. package/dist/htest/mantelhaenTest.d.ts +26 -0
  88. package/dist/htest/mantelhaenTest.js +196 -0
  89. package/dist/htest/mcnemarTest.d.ts +26 -0
  90. package/dist/htest/mcnemarTest.js +99 -0
  91. package/dist/htest/onewayTest.d.ts +26 -0
  92. package/dist/htest/onewayTest.js +97 -0
  93. package/dist/htest/pairwise.d.ts +64 -0
  94. package/dist/htest/pairwise.js +152 -0
  95. package/dist/htest/poissonTest.d.ts +35 -0
  96. package/dist/htest/poissonTest.js +170 -0
  97. package/dist/htest/propTest.d.ts +37 -0
  98. package/dist/htest/propTest.js +164 -0
  99. package/dist/htest/rankTests.d.ts +52 -0
  100. package/dist/htest/rankTests.js +234 -0
  101. package/dist/htest/types.d.ts +2 -0
  102. package/dist/htest/types.js +6 -0
  103. package/dist/htest/varTest.d.ts +48 -0
  104. package/dist/htest/varTest.js +118 -0
  105. package/dist/htest/wilcoxTest.js +136 -174
  106. package/dist/index.d.ts +6 -50
  107. package/dist/index.js +8 -50
  108. package/dist/integrate/quadpack.d.ts +29 -0
  109. package/dist/integrate/quadpack.js +525 -0
  110. package/dist/internal/seq.d.ts +5 -0
  111. package/dist/internal/seq.js +14 -0
  112. package/dist/internal/uniroot.js +10 -71
  113. package/dist/linalg/lu.d.ts +45 -0
  114. package/dist/linalg/lu.js +88 -0
  115. package/dist/linalg/ops.d.ts +90 -0
  116. package/dist/linalg/ops.js +320 -0
  117. package/dist/linalg.js +2 -0
  118. package/dist/models/lm.d.ts +98 -0
  119. package/dist/models/lm.js +187 -0
  120. package/dist/numerics/deriv.d.ts +37 -0
  121. package/dist/numerics/deriv.js +150 -0
  122. package/dist/numerics/integrate.d.ts +91 -0
  123. package/dist/numerics/integrate.js +264 -0
  124. package/dist/numerics/interp.d.ts +103 -0
  125. package/dist/numerics/interp.js +581 -0
  126. package/dist/numerics/ode.d.ts +23 -0
  127. package/dist/numerics/ode.js +59 -0
  128. package/dist/numerics.js +2 -0
  129. package/dist/optim/localmin.d.ts +16 -0
  130. package/dist/optim/localmin.js +97 -0
  131. package/dist/random/beta.js +1 -1
  132. package/dist/random/binomial.js +1 -1
  133. package/dist/random/continuous.js +1 -1
  134. package/dist/random/extra.d.ts +33 -0
  135. package/dist/random/extra.js +62 -0
  136. package/dist/random/gamma.js +1 -1
  137. package/dist/random/hyper.js +1 -1
  138. package/dist/random/mt.js +0 -1
  139. package/dist/random/poisson.js +0 -1
  140. package/dist/random/rank.js +1 -1
  141. package/dist/random/rng.js +1 -1
  142. package/dist/random/sample.js +1 -1
  143. package/dist/rstats.js +2 -2
  144. package/dist/special/besselIK.d.ts +38 -0
  145. package/dist/special/besselIK.js +315 -0
  146. package/dist/special/besselJY.d.ts +32 -0
  147. package/dist/special/besselJY.js +508 -0
  148. package/dist/special/besselSeries.d.ts +8 -0
  149. package/dist/special/besselSeries.js +24 -0
  150. package/dist/special/beta.js +22 -5
  151. package/dist/special/choose.d.ts +75 -0
  152. package/dist/special/choose.js +233 -0
  153. package/dist/special/erf.d.ts +28 -2
  154. package/dist/special/erf.js +37 -10
  155. package/dist/special/extra.d.ts +47 -0
  156. package/dist/special/extra.js +220 -0
  157. package/dist/special/igam.d.ts +4 -0
  158. package/dist/special/igam.js +57 -2
  159. package/dist/special/incbet.js +77 -22
  160. package/dist/special/lgammaDiff.d.ts +7 -0
  161. package/dist/special/lgammaDiff.js +16 -3
  162. package/dist/special/psigamma.d.ts +48 -0
  163. package/dist/special/psigamma.js +318 -0
  164. package/dist/special/rCompatible.d.ts +8 -0
  165. package/dist/special/rCompatible.js +1 -0
  166. package/dist/special.js +2 -0
  167. package/dist/tests.js +2 -0
  168. package/package.json +26 -1
package/dist/htest/cor.js CHANGED
@@ -13,7 +13,8 @@
13
13
  // - Pairs containing NaN are dropped (Inf is kept). Pearson needs at least 3 pairs, Kendall and Spearman at least 2
14
14
  // - Kendall's exact method is the default when n < 50 and there are no ties. Even with exact = true, ties force the normal approximation
15
15
  // - Spearman's exact method (AS 89) is the default when n <= 1290 and there are no ties (off by one from "n < 1290" in R's help).
16
- // Within AS 89, n <= 9 is enumerated exactly and n >= 10 uses the Edgeworth series. With ties, the t approximation is used
16
+ // Within AS 89, n <= 9 is enumerated exactly and n >= 10 uses the Edgeworth series (this n <= 9 switch follows
17
+ // R's behaviour). With ties, the t approximation is used
17
18
  // - continuity only affects the approximations. For Kendall it moves S one step towards 0. For Spearman it
18
19
  // replaces rho with (rho + 6 / (n^3 - n)) / (1 + 6 / (n^3 - n)) (regardless of sign or alternative; R's behaviour)
19
20
  // - When the correlation is undefined (constant data), the statistic, p-value, estimate and interval are NaN. Kendall's
@@ -86,7 +87,7 @@ function scaledDeviations(x) {
86
87
  return d.map((v) => [v[0] * f, v[1] * f]);
87
88
  }
88
89
  /** Pearson's correlation coefficient in double-double (so 1 - r is computed without cancellation near perfect correlation) */
89
- function pearsonDD(x, y) {
90
+ export function pearsonDD(x, y) {
90
91
  const n = x.length;
91
92
  if (n < 2)
92
93
  return [NaN, 0];
@@ -137,7 +138,7 @@ function pFromZ(z, alternative) {
137
138
  return 2 * Math.min(pnorm(z), pnorm(z, { lowerTail: false }));
138
139
  }
139
140
  /** Count concordant and discordant pairs (O(n^2)). Two Infs are tied */
140
- function kendall(x, y) {
141
+ export function kendall(x, y) {
141
142
  const n = x.length;
142
143
  let s = 0;
143
144
  for (let i = 0; i < n; i++) {
@@ -0,0 +1,18 @@
1
+ /**
2
+ * P(AB <= q) (lowerTail) or P(AB > q) for the Ansari-Bradley statistic with sample sizes m and n, without ties.
3
+ *
4
+ * @param q - Value of the statistic.
5
+ * @param m - Size of the first sample.
6
+ * @param n - Size of the second sample.
7
+ * @param lowerTail - `true` for P(AB <= q), `false` for P(AB > q).
8
+ */
9
+ export declare function pansariExact(q: number, m: number, n: number, lowerTail?: boolean): number;
10
+ /**
11
+ * P(AB = x) for the Ansari-Bradley statistic with sample sizes m and n, without ties.
12
+ */
13
+ export declare function dansariExact(x: number, m: number, n: number): number;
14
+ /**
15
+ * The smallest q with P(AB <= q) >= p (quantile of the Ansari-Bradley statistic), without ties. A relative slack of
16
+ * 64 ε on p keeps p = P(AB <= q) itself from being missed by rounding.
17
+ */
18
+ export declare function qansariExact(p: number, m: number, n: number): number;
@@ -0,0 +1,121 @@
1
+ // Exact null distribution of the Ansari-Bradley statistic, for ansari.test.
2
+ //
3
+ // Written for rStats.js from the definition (Ansari and Bradley, Annals of Mathematical Statistics 31, 1960; no
4
+ // ported code). The N = m + n pooled observations get the scores a_i = min(i, N + 1 - i) by rank, and the statistic
5
+ // is the sum of the scores of the first sample. Without ties every set of m ranks is equally likely, so the
6
+ // distribution follows from choosing a random m-subset position by position: at position t with r picks left among
7
+ // N - t + 1 positions, the position is picked with probability r / (N - t + 1). The probabilities are kept directly
8
+ // (counts overflow: C(100, 50) = 1e29), and tail probabilities are sums of positive terms.
9
+ const cache = new Map();
10
+ function ansariDist(m, n) {
11
+ const key = `${m},${n}`;
12
+ const hit = cache.get(key);
13
+ if (hit !== undefined)
14
+ return hit;
15
+ const N = m + n;
16
+ const scores = [];
17
+ for (let i = 1; i <= N; i++)
18
+ scores.push(Math.min(i, N + 1 - i));
19
+ const maxSum = scores.reduce((a, b) => a + b, 0);
20
+ // P[r][s]: probability of r picks so far with score sum s (rows r = 0..m)
21
+ let P = Array.from({ length: m + 1 }, () => new Float64Array(maxSum + 1));
22
+ P[0][0] = 1;
23
+ for (let t = 0; t < N; t++) {
24
+ const left = N - t; // positions not yet decided, including this one
25
+ const a = scores[t];
26
+ const Q = Array.from({ length: m + 1 }, () => new Float64Array(maxSum + 1));
27
+ for (let r = 0; r <= m; r++) {
28
+ const row = P[r];
29
+ const need = m - r;
30
+ if (need > left)
31
+ continue;
32
+ const pick = need / left;
33
+ for (let s = 0; s <= maxSum; s++) {
34
+ const v = row[s];
35
+ if (v === 0)
36
+ continue;
37
+ if (need > 0)
38
+ Q[r + 1][s + a] += v * pick;
39
+ if (need < left)
40
+ Q[r][s] += v * (1 - pick);
41
+ }
42
+ }
43
+ P = Q;
44
+ }
45
+ const full = P[m];
46
+ let lo = 0;
47
+ while (lo < full.length && full[lo] === 0)
48
+ lo++;
49
+ let hi = full.length - 1;
50
+ while (hi > lo && full[hi] === 0)
51
+ hi--;
52
+ const dist = { min: lo, probs: full.slice(lo, hi + 1) };
53
+ cache.set(key, dist);
54
+ return dist;
55
+ }
56
+ /**
57
+ * P(AB <= q) (lowerTail) or P(AB > q) for the Ansari-Bradley statistic with sample sizes m and n, without ties.
58
+ *
59
+ * @param q - Value of the statistic.
60
+ * @param m - Size of the first sample.
61
+ * @param n - Size of the second sample.
62
+ * @param lowerTail - `true` for P(AB <= q), `false` for P(AB > q).
63
+ */
64
+ export function pansariExact(q, m, n, lowerTail = true) {
65
+ if (Number.isNaN(q) || !(m >= 1) || !(n >= 1) || m !== Math.floor(m) || n !== Math.floor(n))
66
+ return NaN;
67
+ const { min, probs } = ansariDist(m, n);
68
+ const k = Math.floor(q + 1e-7) - min; // index of the largest value <= q
69
+ let s = 0;
70
+ if (lowerTail) {
71
+ for (let i = 0; i <= Math.min(k, probs.length - 1); i++)
72
+ s += probs[i];
73
+ }
74
+ else {
75
+ for (let i = Math.max(k + 1, 0); i < probs.length; i++)
76
+ s += probs[i];
77
+ }
78
+ return Math.min(s, 1);
79
+ }
80
+ /**
81
+ * P(AB = x) for the Ansari-Bradley statistic with sample sizes m and n, without ties.
82
+ */
83
+ export function dansariExact(x, m, n) {
84
+ if (Number.isNaN(x) || !(m >= 1) || !(n >= 1) || m !== Math.floor(m) || n !== Math.floor(n))
85
+ return NaN;
86
+ if (x !== Math.floor(x))
87
+ return 0;
88
+ const { min, probs } = ansariDist(m, n);
89
+ const i = x - min;
90
+ return i >= 0 && i < probs.length ? probs[i] : 0;
91
+ }
92
+ /**
93
+ * The smallest q with P(AB <= q) >= p (quantile of the Ansari-Bradley statistic), without ties. A relative slack of
94
+ * 64 ε on p keeps p = P(AB <= q) itself from being missed by rounding.
95
+ */
96
+ export function qansariExact(p, m, n) {
97
+ if (Number.isNaN(p) || p < 0 || p > 1 || !(m >= 1) || !(n >= 1))
98
+ return NaN;
99
+ const { min, probs } = ansariDist(m, n);
100
+ if (p > 0.5) {
101
+ // Compare the small upper tail P(AB > q) <= 1 - p instead, so that the tiny probabilities at the top are not
102
+ // lost to the rounding of a cumulative sum near 1 (p = 1 must give the maximum)
103
+ const target = (1 - p) * (1 + 64 * Number.EPSILON);
104
+ let up = 0;
105
+ for (let i = probs.length - 1; i >= 0; i--) {
106
+ // up = P(AB > min + i)
107
+ if (up > target)
108
+ return min + i + 1;
109
+ up += probs[i];
110
+ }
111
+ return min;
112
+ }
113
+ const target = p * (1 - 64 * Number.EPSILON);
114
+ let s = 0;
115
+ for (let i = 0; i < probs.length; i++) {
116
+ s += probs[i];
117
+ if (s >= target)
118
+ return min + i;
119
+ }
120
+ return min + probs.length - 1;
121
+ }
@@ -0,0 +1,26 @@
1
+ /**
2
+ * P(D_n < d), the exact distribution of the two-sided one-sample Kolmogorov-Smirnov statistic for a continuous
3
+ * distribution (Marsaglia, Tsang and Wang 2003). The matrix has size 2 floor(n d) + 1, so the cost grows like
4
+ * (n d)^3 log n; ks.test uses it for small samples.
5
+ *
6
+ * @param n - Sample size, a positive integer.
7
+ * @param d - Value of the statistic.
8
+ */
9
+ export declare function pkolmogorovExact(n: number, d: number): number;
10
+ /**
11
+ * The limiting distribution of sqrt(n) D_n (Kolmogorov's distribution), K(x) = P(sqrt(n) D_n <= x) as n -> infinity,
12
+ * or 1 - K(x) for the upper tail. Each tail is summed directly with the series that converges fast for it.
13
+ *
14
+ * @param x - Value of sqrt(n) D_n.
15
+ * @param lowerTail - `true` for K(x), `false` for 1 - K(x).
16
+ */
17
+ export declare function pkolmogorovLimit(x: number, lowerTail?: boolean): number;
18
+ /**
19
+ * P(D_n^+ >= d) for the one-sided one-sample statistic D_n^+ = sup (F_n(x) - F(x)) of a continuous distribution
20
+ * (the same distribution holds for D_n^- = sup (F(x) - F_n(x))), exactly by the Birnbaum-Tingey formula. All terms are
21
+ * positive, so small p-values keep their relative accuracy.
22
+ *
23
+ * @param n - Sample size, a positive integer.
24
+ * @param d - Value of the statistic.
25
+ */
26
+ export declare function pkolmogorovOneSidedUpper(n: number, d: number): number;
@@ -0,0 +1,221 @@
1
+ // Distribution of the one-sample Kolmogorov-Smirnov statistic D_n = sup |F_n(x) - F(x)|, for ks.test.
2
+ //
3
+ // Written for rStats.js from the published methods (no ported code):
4
+ // - Exact distribution: Marsaglia, Tsang and Wang, "Evaluating Kolmogorov's distribution", Journal of Statistical
5
+ // Software 8(18), 2003. P(D_n < d) = n! / n^n (H^n)_(k,k) with k = floor(n d) + 1 and an m x m matrix H, m = 2k - 1.
6
+ // - Limiting distribution of sqrt(n) D_n (Kolmogorov 1933):
7
+ // K(x) = 1 - 2 sum_(j>=1) (-1)^(j-1) exp(-2 j^2 x^2) (large x)
8
+ // = sqrt(2 pi) / x sum_(j>=1) exp(-(2j - 1)^2 pi^2 / (8 x^2)) (small x)
9
+ // - For d < 1/n the matrix is 1 x 1 and the closed form P(D_n < d) = n! / n^n (2 n d - 1)^n (Ruben and Gambino 1982)
10
+ // is used, with 2 n d - 1 formed exactly (it cancels near d = 1/(2n)).
11
+ // - One-sided statistic D_n^+ = sup (F_n(x) - F(x)) (Birnbaum and Tingey, Annals of Mathematical Statistics 22, 1951):
12
+ // P(D_n^+ >= d) = d sum_(j=0)^(floor(n (1 - d))) C(n, j) (1 - d - j/n)^(n-j) (d + j/n)^(j-1),
13
+ // whose terms are b(j; n, a) / a with a = d + j/n: binomial probabilities from Loader's dbinomRawDD.
14
+ import { ddAdd, ddDiv, twoProd, twoSum } from "../../special/dd.js";
15
+ import { dbinomRawDD } from "../../special/loader.js";
16
+ /** pi^2 / 8 as a double-double */
17
+ const PI2_8 = [1.2337005501361697, 7.831619385924639e-17];
18
+ /** H^n with the binary exponent kept apart: returns [matrix, e2] with the true power = matrix * 2^e2 */
19
+ function matPow(H, m, n) {
20
+ const mul = (A, B) => {
21
+ const C = new Float64Array(m * m);
22
+ for (let i = 0; i < m; i++) {
23
+ for (let l = 0; l < m; l++) {
24
+ const a = A[i * m + l];
25
+ if (a === 0)
26
+ continue;
27
+ for (let j = 0; j < m; j++)
28
+ C[i * m + j] += a * B[l * m + j];
29
+ }
30
+ }
31
+ return C;
32
+ };
33
+ // Rescale by a power of two (exact) so that the entries stay in range
34
+ const normalise = (A) => {
35
+ let mx = 0;
36
+ for (const v of A)
37
+ mx = Math.max(mx, Math.abs(v));
38
+ if (mx === 0 || !Number.isFinite(mx))
39
+ return 0;
40
+ const e = Math.floor(Math.log2(mx));
41
+ if (Math.abs(e) < 500)
42
+ return 0;
43
+ const f = 2 ** -e;
44
+ for (let i = 0; i < A.length; i++)
45
+ A[i] *= f;
46
+ return e;
47
+ };
48
+ let result;
49
+ let re = 0;
50
+ let base = H.slice();
51
+ let be = 0;
52
+ let p = n;
53
+ while (p > 0) {
54
+ if (p & 1) {
55
+ result = result === undefined ? base.slice() : mul(result, base);
56
+ re += be;
57
+ re += normalise(result);
58
+ }
59
+ p >>= 1;
60
+ if (p > 0) {
61
+ base = mul(base, base);
62
+ be *= 2;
63
+ be += normalise(base);
64
+ }
65
+ }
66
+ return [result, re];
67
+ }
68
+ /**
69
+ * P(D_n < d), the exact distribution of the two-sided one-sample Kolmogorov-Smirnov statistic for a continuous
70
+ * distribution (Marsaglia, Tsang and Wang 2003). The matrix has size 2 floor(n d) + 1, so the cost grows like
71
+ * (n d)^3 log n; ks.test uses it for small samples.
72
+ *
73
+ * @param n - Sample size, a positive integer.
74
+ * @param d - Value of the statistic.
75
+ */
76
+ export function pkolmogorovExact(n, d) {
77
+ if (Number.isNaN(d) || !(n >= 1) || n !== Math.floor(n))
78
+ return NaN;
79
+ // D_n >= 1/(2n) always, and D_n <= 1
80
+ if (d <= 1 / (2 * n))
81
+ return 0;
82
+ if (d >= 1)
83
+ return 1;
84
+ if (n * d < 1) {
85
+ // n! / n^n (2 n d - 1)^n, with n d = hi + lo exact so that 2 n d - 1 = (2 hi - 1) + 2 lo has no rounding error
86
+ const [hi, lo] = twoProd(n, d);
87
+ const t = 2 * hi - 1 + 2 * lo;
88
+ let s = 1;
89
+ let e = 0;
90
+ for (let i = 1; i <= n; i++) {
91
+ s *= (i / n) * t;
92
+ if (s < 2 ** -500) {
93
+ s *= 2 ** 500;
94
+ e -= 500;
95
+ }
96
+ }
97
+ while (e < -1000) {
98
+ s *= 2 ** -1000;
99
+ e += 1000;
100
+ }
101
+ return s * 2 ** e;
102
+ }
103
+ const k = Math.floor(n * d) + 1;
104
+ const m = 2 * k - 1;
105
+ const h = k - n * d;
106
+ const H = new Float64Array(m * m);
107
+ for (let i = 0; i < m; i++)
108
+ for (let j = 0; j < m; j++)
109
+ H[i * m + j] = i - j + 1 >= 0 ? 1 : 0;
110
+ for (let i = 0; i < m; i++) {
111
+ H[i * m] -= Math.pow(h, i + 1);
112
+ H[(m - 1) * m + i] -= Math.pow(h, m - i);
113
+ }
114
+ if (2 * h - 1 > 0)
115
+ H[(m - 1) * m] += Math.pow(2 * h - 1, m);
116
+ // divide by (i - j + 1)!
117
+ for (let i = 0; i < m; i++) {
118
+ for (let j = 0; j < m; j++) {
119
+ const r = i - j + 1;
120
+ if (r > 0) {
121
+ let f = 1;
122
+ for (let g = 2; g <= r; g++)
123
+ f *= g;
124
+ H[i * m + j] /= f;
125
+ }
126
+ }
127
+ }
128
+ const [Q, e2] = matPow(H, m, n);
129
+ // n! / n^n, multiplied in step by step with the binary exponent kept apart
130
+ let s = Q[(k - 1) * m + k - 1];
131
+ let e = e2;
132
+ for (let i = 1; i <= n; i++) {
133
+ s = (s * i) / n;
134
+ if (Math.abs(s) < 2 ** -500) {
135
+ s *= 2 ** 500;
136
+ e -= 500;
137
+ }
138
+ }
139
+ while (e > 1000) {
140
+ s *= 2 ** 1000;
141
+ e -= 1000;
142
+ }
143
+ while (e < -1000) {
144
+ s *= 2 ** -1000;
145
+ e += 1000;
146
+ }
147
+ return s * 2 ** e;
148
+ }
149
+ /**
150
+ * The limiting distribution of sqrt(n) D_n (Kolmogorov's distribution), K(x) = P(sqrt(n) D_n <= x) as n -> infinity,
151
+ * or 1 - K(x) for the upper tail. Each tail is summed directly with the series that converges fast for it.
152
+ *
153
+ * @param x - Value of sqrt(n) D_n.
154
+ * @param lowerTail - `true` for K(x), `false` for 1 - K(x).
155
+ */
156
+ export function pkolmogorovLimit(x, lowerTail = true) {
157
+ if (Number.isNaN(x))
158
+ return NaN;
159
+ if (x <= 0)
160
+ return lowerTail ? 0 : 1;
161
+ if (x === Infinity)
162
+ return lowerTail ? 1 : 0;
163
+ if (x < 1) {
164
+ // K(x) = sqrt(2 pi) / x sum exp(-(2j - 1)^2 pi^2 / (8 x^2)): terms fall off at least like exp(-pi^2 j / x^2)
165
+ // The first term carries the value; its exponent pi^2 / (8 x^2) reaches the hundreds for small x, so it is formed
166
+ // in double-double (its rounding alone is 1e-13 relative at x = 0.05)
167
+ const wd = ddDiv(PI2_8, twoProd(x, x));
168
+ const w = wd[0] + wd[1];
169
+ const first = Math.exp(-wd[0]) * (1 - wd[1]);
170
+ let s = 0;
171
+ for (let j = 2; j < 100; j++) {
172
+ const t = Math.exp(-(2 * j - 1) * (2 * j - 1) * w);
173
+ s += t;
174
+ if (t <= 1e-17 * (s + first))
175
+ break;
176
+ }
177
+ const k = (Math.sqrt(2 * Math.PI) / x) * (first + s);
178
+ return lowerTail ? k : 1 - k;
179
+ }
180
+ // 1 - K(x) = 2 sum (-1)^(j-1) exp(-2 j^2 x^2), alternating with terms below exp(-2) times the previous
181
+ let s = 0;
182
+ for (let j = 1; j < 100; j++) {
183
+ const t = Math.exp(-2 * j * j * x * x);
184
+ s += j % 2 === 1 ? t : -t;
185
+ if (t <= 1e-17 * Math.abs(s))
186
+ break;
187
+ }
188
+ const q = 2 * s;
189
+ return lowerTail ? 1 - q : q;
190
+ }
191
+ /**
192
+ * P(D_n^+ >= d) for the one-sided one-sample statistic D_n^+ = sup (F_n(x) - F(x)) of a continuous distribution
193
+ * (the same distribution holds for D_n^- = sup (F(x) - F_n(x))), exactly by the Birnbaum-Tingey formula. All terms are
194
+ * positive, so small p-values keep their relative accuracy.
195
+ *
196
+ * @param n - Sample size, a positive integer.
197
+ * @param d - Value of the statistic.
198
+ */
199
+ export function pkolmogorovOneSidedUpper(n, d) {
200
+ if (Number.isNaN(d) || !(n >= 1) || n !== Math.floor(n))
201
+ return NaN;
202
+ if (d <= 0)
203
+ return 1;
204
+ if (d > 1)
205
+ return 0;
206
+ // n (1 - d) = n - n d, with n d = hi + lo exact
207
+ const [hi, lo] = twoProd(n, d);
208
+ const jmax = Math.floor(n - hi - lo);
209
+ let s = 0;
210
+ for (let j = 0; j <= jmax; j++) {
211
+ // a = d + j/n and b = 1 - a = (n - n d - j) / n, both from the exact n d
212
+ const nb = ddAdd(twoSum(n - j, -hi), [-lo, 0]);
213
+ if (nb[0] <= 0)
214
+ break;
215
+ const na = ddAdd(twoSum(j, hi), [lo, 0]);
216
+ const a = ddDiv(na, [n, 0]);
217
+ const b = ddDiv(nb, [n, 0]);
218
+ s += dbinomRawDD(j, n - j, a, b, false) / (a[0] + a[1]);
219
+ }
220
+ return Math.min(d * s, 1);
221
+ }
@@ -0,0 +1,26 @@
1
+ /** One 2 x 2 stratum as [[a, b], [c, d]] counts */
2
+ export type Stratum = readonly [readonly [number, number], readonly [number, number]];
3
+ /**
4
+ * The distribution of S = sum of the (1, 1) cells of K 2 x 2 tables with fixed margins, as a function of the common
5
+ * odds ratio (noncentrality).
6
+ */
7
+ export declare class ConvolvedNoncentralHyper {
8
+ /** Smallest and largest attainable S */
9
+ readonly lo: number;
10
+ readonly hi: number;
11
+ /** log d0(s) for s = lo..hi (d0: the null, psi = 1, density) */
12
+ private readonly logNull;
13
+ constructor(strata: readonly Stratum[]);
14
+ /** P(S = s; psi) for s = lo..hi, for 0 < psi < Infinity (psi = 0 and Infinity give the point masses at lo and hi) */
15
+ density(psi: number): Float64Array;
16
+ /** E[S; psi] */
17
+ mean(psi: number): number;
18
+ /** P(S <= q; psi), or P(S >= q; psi) when upper */
19
+ cdf(q: number, psi: number, upper: boolean): number;
20
+ }
21
+ /** The conditional maximum likelihood estimate of the common odds ratio given the observed sum s */
22
+ export declare function conditionalMLE(h: ConvolvedNoncentralHyper, s: number): number;
23
+ /** Upper confidence limit: the psi with P(S <= s; psi) = alpha */
24
+ export declare function upperLimit(h: ConvolvedNoncentralHyper, s: number, alpha: number): number;
25
+ /** Lower confidence limit: the psi with P(S >= s; psi) = alpha */
26
+ export declare function lowerLimit(h: ConvolvedNoncentralHyper, s: number, alpha: number): number;
@@ -0,0 +1,145 @@
1
+ // Exact conditional distribution for the Mantel-Haenszel test on 2 x 2 x K tables (mantelhaen.test with
2
+ // exact = TRUE).
3
+ //
4
+ // Written for rStats.js from the textbook construction and R's behaviour. With all margins fixed, the
5
+ // (1, 1) cell of stratum k follows Fisher's noncentral hypergeometric distribution with noncentrality psi, the common
6
+ // odds ratio (Mantel and Haenszel, JNCI 22, 1959; Birch, JRSS B 26, 1964), so their sum S has the density
7
+ // d0(s) psi^s / sum_t d0(t) psi^t, where d0 is the convolution of the K central hypergeometric densities. The
8
+ // conditional MLE of psi is the noncentrality whose mean equals the observed S, and the confidence limits are the
9
+ // noncentralities at which a one-sided probability equals alpha; the roots are found with uniroot on psi in [0, 1] or
10
+ // on 1/psi in [eps, 1], as for fisher.test's 2 x 2 tables (src/htest/fisherTest.ts).
11
+ import { dhyper1 } from "../../distributions/hypergeometric.js";
12
+ import { uniroot } from "../../internal/uniroot.js";
13
+ import { ddAdd } from "../../special/dd.js";
14
+ /** Compensated (double-double) sum of v[from..to) */
15
+ function sumRange(v, from, to) {
16
+ let acc = [0, 0];
17
+ for (let i = Math.max(0, from); i < Math.min(to, v.length); i++)
18
+ acc = ddAdd(acc, [v[i], 0]);
19
+ return acc[0];
20
+ }
21
+ /** Central hypergeometric density of the (1, 1) cell of one stratum, with its support start */
22
+ function stratumDensity([[a, b], [c, d]]) {
23
+ const m = a + c; // first column
24
+ const n = b + d; // second column
25
+ const k = a + b; // first row
26
+ const lo = Math.max(0, k - n);
27
+ const hi = Math.min(k, m);
28
+ const dens = new Float64Array(hi - lo + 1);
29
+ for (let x = lo; x <= hi; x++)
30
+ dens[x - lo] = dhyper1(x, m, n, k, false);
31
+ return { lo, d: dens };
32
+ }
33
+ /** Discrete convolution of two densities on consecutive integers */
34
+ function convolve(p, q) {
35
+ const out = new Float64Array(p.length + q.length - 1);
36
+ for (let i = 0; i < p.length; i++) {
37
+ const pi = p[i];
38
+ if (pi === 0)
39
+ continue;
40
+ for (let j = 0; j < q.length; j++)
41
+ out[i + j] += pi * q[j];
42
+ }
43
+ return out;
44
+ }
45
+ /**
46
+ * The distribution of S = sum of the (1, 1) cells of K 2 x 2 tables with fixed margins, as a function of the common
47
+ * odds ratio (noncentrality).
48
+ */
49
+ export class ConvolvedNoncentralHyper {
50
+ /** Smallest and largest attainable S */
51
+ lo;
52
+ hi;
53
+ /** log d0(s) for s = lo..hi (d0: the null, psi = 1, density) */
54
+ logNull;
55
+ constructor(strata) {
56
+ let lo = 0;
57
+ let d0 = new Float64Array([1]);
58
+ for (const t of strata) {
59
+ const s = stratumDensity(t);
60
+ lo += s.lo;
61
+ d0 = convolve(d0, s.d);
62
+ }
63
+ this.lo = lo;
64
+ this.hi = lo + d0.length - 1;
65
+ this.logNull = d0.map(Math.log);
66
+ }
67
+ /** P(S = s; psi) for s = lo..hi, for 0 < psi < Infinity (psi = 0 and Infinity give the point masses at lo and hi) */
68
+ density(psi) {
69
+ const len = this.logNull.length;
70
+ const out = new Float64Array(len);
71
+ if (psi === 0 || psi === Infinity) {
72
+ out[psi === 0 ? 0 : len - 1] = 1;
73
+ return out;
74
+ }
75
+ const lp = Math.log(psi);
76
+ let top = -Infinity;
77
+ for (let i = 0; i < len; i++) {
78
+ // exp(s log psi): the exponent is formed from the support value itself (lo + i)
79
+ const v = this.logNull[i] + (this.lo + i) * lp;
80
+ out[i] = v;
81
+ if (v > top)
82
+ top = v;
83
+ }
84
+ for (let i = 0; i < len; i++)
85
+ out[i] = Math.exp(out[i] - top);
86
+ const total = sumRange(out, 0, len);
87
+ for (let i = 0; i < len; i++)
88
+ out[i] = out[i] / total;
89
+ return out;
90
+ }
91
+ /** E[S; psi] */
92
+ mean(psi) {
93
+ if (psi === 0)
94
+ return this.lo;
95
+ if (psi === Infinity)
96
+ return this.hi;
97
+ const d = this.density(psi);
98
+ let acc = [0, 0];
99
+ for (let i = 0; i < d.length; i++)
100
+ acc = ddAdd(acc, [(this.lo + i) * d[i], 0]);
101
+ return acc[0];
102
+ }
103
+ /** P(S <= q; psi), or P(S >= q; psi) when upper */
104
+ cdf(q, psi, upper) {
105
+ const d = this.density(psi);
106
+ const i = q - this.lo;
107
+ return upper ? sumRange(d, i, d.length) : sumRange(d, 0, i + 1);
108
+ }
109
+ }
110
+ /**
111
+ * Root in psi of a function that is monotone in psi, given whether the root lies below 1: search psi in [0, 1], or
112
+ * t = 1/psi in [eps, 1]
113
+ */
114
+ function oddsRoot(f, belowOne) {
115
+ return belowOne ? uniroot(f, 0, 1) : 1 / uniroot((t) => f(1 / t), Number.EPSILON, 1);
116
+ }
117
+ /** The conditional maximum likelihood estimate of the common odds ratio given the observed sum s */
118
+ export function conditionalMLE(h, s) {
119
+ if (s === h.lo)
120
+ return 0;
121
+ if (s === h.hi)
122
+ return Infinity;
123
+ const atOne = h.mean(1);
124
+ if (atOne === s)
125
+ return 1;
126
+ return oddsRoot((psi) => h.mean(psi) - s, atOne > s);
127
+ }
128
+ /** Upper confidence limit: the psi with P(S <= s; psi) = alpha */
129
+ export function upperLimit(h, s, alpha) {
130
+ if (s === h.hi)
131
+ return Infinity;
132
+ const atOne = h.cdf(s, 1, false);
133
+ if (atOne === alpha)
134
+ return 1;
135
+ return oddsRoot((psi) => alpha - h.cdf(s, psi, false), atOne < alpha);
136
+ }
137
+ /** Lower confidence limit: the psi with P(S >= s; psi) = alpha */
138
+ export function lowerLimit(h, s, alpha) {
139
+ if (s === h.lo)
140
+ return 0;
141
+ const atOne = h.cdf(s, 1, true);
142
+ if (atOne === alpha)
143
+ return 1;
144
+ return oddsRoot((psi) => h.cdf(s, psi, true) - alpha, atOne > alpha);
145
+ }
@@ -0,0 +1,24 @@
1
+ /**
2
+ * The Shapiro-Wilk coefficients a_1..a_n for a sample of size n (3 <= n <= 5000), antisymmetric
3
+ * (a_i = -a_(n+1-i)) with sum a_i^2 = 1.
4
+ */
5
+ export declare function swilkCoefficients(n: number): Float64Array;
6
+ /**
7
+ * 1 - W for a sample sorted in increasing order, formed as (r + c)(r - c) / r^2 from c = sum a_i (x_i - mean) and
8
+ * r^2 = sum a_i^2 sum (x_i - mean)^2, so that it keeps its relative accuracy when W is close to 1 (the p-value
9
+ * depends on log(1 - W)). The data are scaled by their range first.
10
+ */
11
+ export declare function swilkOneMinusW(sorted: readonly number[]): number;
12
+ /**
13
+ * The W statistic of a sample sorted in increasing order:
14
+ * W = (sum a_i x_(i))^2 / sum (x_i - mean)^2.
15
+ */
16
+ export declare function swilkW(sorted: readonly number[]): number;
17
+ /**
18
+ * The p-value P(W' <= W) of the Shapiro-Wilk test for a sample of size n (Royston's approximations), from W.
19
+ */
20
+ export declare function swilkPValue(w: number, n: number): number;
21
+ /**
22
+ * The p-value P(W' <= W) of the Shapiro-Wilk test for a sample of size n, from w1 = 1 - W (see swilkOneMinusW).
23
+ */
24
+ export declare function swilkPValueFromOneMinusW(w1: number, n: number): number;