@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
@@ -0,0 +1,234 @@
1
+ // Rank-based k-sample and two-sample tests: R's kruskal.test, friedman.test, quade.test, fligner.test and mood.test.
2
+ //
3
+ // Written for rStats.js from the published statistics (no ported code), with ties handled by average ranks:
4
+ // - Kruskal and Wallis (JASA 47, 1952): H = (12 / (N (N + 1)) sum R_i^2 / n_i - 3 (N + 1)) / (1 - sum (t^3 - t) /
5
+ // (N^3 - N)), chi-squared with k - 1 degrees of freedom.
6
+ // - Friedman (JASA 32, 1937), ranks within blocks: 12 sum (R_j - n (k + 1) / 2)^2 / (n k (k + 1) - sum (t^3 - t) /
7
+ // (k - 1)), chi-squared with k - 1 degrees of freedom.
8
+ // - Quade (JASA 74, 1979), as given by Conover (Practical Nonparametric Statistics, 1999, section 5.8): blocks ranked
9
+ // by their range, Q_i, S_ij = Q_i (R_ij - (k + 1) / 2), A = sum S_ij^2, B = sum_j (sum_i S_ij)^2 / b,
10
+ // F = (b - 1) B / (A - B) on (k - 1, (b - 1)(k - 1)) degrees of freedom; when A = B (all treatments tied within
11
+ // every block's order) the p-value is (1 / k!)^(b - 1), as Conover suggests.
12
+ // - Fligner and Killeen (JASA 71, 1976), centred at the group medians: scores a = qnorm((1 + rank|x| / (N + 1)) / 2),
13
+ // statistic sum n_i (mean a_i - mean a)^2 / var(a), chi-squared with k - 1 degrees of freedom (the median-centred
14
+ // form of Conover, Johnson and Johnson, Technometrics 23, 1981).
15
+ // - Mood (Ann. Math. Stat. 25, 1954): scores (i - (N + 1) / 2)^2 by position, averaged over tied positions (Mielke,
16
+ // Technometrics 9, 1967); the sum over the first sample is standardised with the exact mean and variance of a linear
17
+ // rank statistic, E = m mean(a), V = m n / (N (N - 1)) sum (a_i - mean(a))^2, and compared with the normal.
18
+ import { pf1 } from "../distributions/f.js";
19
+ import { pgamma1 } from "../distributions/gamma.js";
20
+ import { pnorm, qnorm } from "../distributions/normal.js";
21
+ import { rank, tieCounts, variance } from "../internal/summary.js";
22
+ import { completeRows, median, toGroups } from "./groups.js";
23
+ import { matchAlternative } from "./types.js";
24
+ function chisqResult(method, name, statistic, df) {
25
+ return {
26
+ method,
27
+ statistic,
28
+ statisticName: name,
29
+ parameter: df,
30
+ parameterName: "df",
31
+ pValue: pgamma1(statistic, df / 2, 2, false, false),
32
+ };
33
+ }
34
+ function sumTies(x) {
35
+ let s = 0;
36
+ for (const t of tieCounts(x))
37
+ s += t * t * t - t;
38
+ return s;
39
+ }
40
+ /**
41
+ * Kruskal-Wallis rank sum test. Equivalent to R's `kruskal.test()`.
42
+ *
43
+ * @param x An array of samples (each with at least one non-missing value, as R requires), or one sample with group
44
+ * labels `g`.
45
+ * @param g Group labels, parallel to `x`.
46
+ */
47
+ export function kruskalTest(x, g) {
48
+ const groups = toGroups(x, g, "error");
49
+ const k = groups.length;
50
+ if (k < 2)
51
+ throw new RangeError("all observations are in the same group");
52
+ const all = groups.flat();
53
+ const n = all.length;
54
+ const r = rank(all);
55
+ let stat = 0;
56
+ let pos = 0;
57
+ for (const grp of groups) {
58
+ let s = 0;
59
+ for (let i = 0; i < grp.length; i++)
60
+ s += r[pos + i];
61
+ pos += grp.length;
62
+ stat += (s * s) / grp.length;
63
+ }
64
+ stat = ((12 * stat) / (n * (n + 1)) - 3 * (n + 1)) / (1 - sumTies(all) / (n * n * n - n));
65
+ return chisqResult("Kruskal-Wallis rank sum test", "Kruskal-Wallis chi-squared", stat, k - 1);
66
+ }
67
+ /**
68
+ * Friedman rank sum test for an unreplicated complete block design. Equivalent to R's `friedman.test()` with a
69
+ * matrix.
70
+ *
71
+ * @param y Blocks as rows, treatments as columns. Rows with a missing value are dropped.
72
+ */
73
+ export function friedmanTest(y) {
74
+ const rows = completeRows(y);
75
+ const n = rows.length;
76
+ const k = y[0]?.length ?? 0;
77
+ // R needs two complete blocks (with one, its matrix of ranks collapses to a vector)
78
+ if (n < 2 || k < 2)
79
+ throw new RangeError("'y' must have at least two complete rows and two columns");
80
+ const colSums = new Array(k).fill(0);
81
+ let ties = 0;
82
+ for (const row of rows) {
83
+ const r = rank(row);
84
+ for (let j = 0; j < k; j++)
85
+ colSums[j] += r[j];
86
+ ties += sumTies(row);
87
+ }
88
+ let ss = 0;
89
+ for (const s of colSums)
90
+ ss += (s - (n * (k + 1)) / 2) ** 2;
91
+ const stat = (12 * ss) / (n * k * (k + 1) - ties / (k - 1));
92
+ return chisqResult("Friedman rank sum test", "Friedman chi-squared", stat, k - 1);
93
+ }
94
+ /**
95
+ * Quade test for an unreplicated complete block design. Equivalent to R's `quade.test()` with a matrix.
96
+ *
97
+ * @param y Blocks as rows, treatments as columns. Rows with a missing value are dropped.
98
+ */
99
+ export function quadeTest(y) {
100
+ const rows = completeRows(y);
101
+ // as in R, b counts all blocks, including those dropped for a missing value
102
+ const b = y.length;
103
+ const k = y[0]?.length ?? 0;
104
+ if (rows.length < 2 || k < 2)
105
+ throw new RangeError("'y' must have at least two complete rows and two columns");
106
+ const q = rank(rows.map((r) => Math.max(...r) - Math.min(...r)));
107
+ const colS = new Array(k).fill(0);
108
+ let A = 0;
109
+ rows.forEach((row, i) => {
110
+ const r = rank(row);
111
+ for (let j = 0; j < k; j++) {
112
+ const s = q[i] * (r[j] - (k + 1) / 2);
113
+ A += s * s;
114
+ colS[j] += s;
115
+ }
116
+ });
117
+ let B = 0;
118
+ for (const s of colS)
119
+ B += s * s;
120
+ B /= b;
121
+ const df1 = k - 1;
122
+ const df2 = (b - 1) * (k - 1);
123
+ let statistic;
124
+ let pValue;
125
+ let parameter = [df1, df2];
126
+ if (A === B) {
127
+ statistic = NaN;
128
+ parameter = [NaN, NaN];
129
+ // log-gamma form of (k!)^(1 - b), which overflows directly for large b
130
+ let lf = 0;
131
+ for (let i = 2; i <= k; i++)
132
+ lf += Math.log(i);
133
+ pValue = Math.exp((1 - b) * lf);
134
+ }
135
+ else {
136
+ statistic = ((b - 1) * B) / (A - B);
137
+ pValue = pf1(statistic, df1, df2, false, false);
138
+ }
139
+ return {
140
+ method: "Quade test",
141
+ statistic,
142
+ statisticName: "Quade F",
143
+ parameter,
144
+ parameterName: ["num df", "denom df"],
145
+ pValue,
146
+ };
147
+ }
148
+ /**
149
+ * Fligner-Killeen (median) test of homogeneity of variances. Equivalent to R's `fligner.test()`.
150
+ *
151
+ * @param x An array of samples (each with at least one non-missing value, as R requires), or one sample with group
152
+ * labels `g`.
153
+ * @param g Group labels, parallel to `x`.
154
+ */
155
+ export function flignerTest(x, g) {
156
+ const groups = toGroups(x, g, "error");
157
+ const k = groups.length;
158
+ if (k < 2)
159
+ throw new RangeError("all observations are in the same group");
160
+ const centred = groups.flatMap((grp) => {
161
+ const m = median(grp);
162
+ return grp.map((v) => Math.abs(v - m));
163
+ });
164
+ const n = centred.length;
165
+ const r = rank(centred);
166
+ const a = r.map((v) => qnorm((1 + v / (n + 1)) / 2));
167
+ let total = 0;
168
+ for (const v of a)
169
+ total += v;
170
+ const abar = total / n;
171
+ // sum n_i (mean a_i - mean a)^2: the form sum S_i^2 / n_i - n mean(a)^2 cancels when the statistic is small
172
+ let stat = 0;
173
+ let pos = 0;
174
+ for (const grp of groups) {
175
+ let s = 0;
176
+ for (let i = 0; i < grp.length; i++)
177
+ s += a[pos + i];
178
+ pos += grp.length;
179
+ stat += grp.length * (s / grp.length - abar) ** 2;
180
+ }
181
+ stat /= variance(a);
182
+ return chisqResult("Fligner-Killeen test of homogeneity of variances", "Fligner-Killeen:med chi-squared", stat, k - 1);
183
+ }
184
+ /**
185
+ * Mood's two-sample test of scale. Equivalent to R's `mood.test()`. `NaN` values are dropped; ties get the average
186
+ * of the scores of their positions.
187
+ */
188
+ export function moodTest(x, y, options = {}) {
189
+ // as in R, infinite values are dropped along with NaN
190
+ const xs = Array.from(x).filter(Number.isFinite);
191
+ const ys = Array.from(y).filter(Number.isFinite);
192
+ const alternative = matchAlternative(options.alternative);
193
+ const m = xs.length;
194
+ const n = ys.length;
195
+ const N = m + n;
196
+ if (N < 3)
197
+ throw new RangeError("not enough observations");
198
+ const z = xs.concat(ys);
199
+ // positions in sorted order; tied values share the mean of the scores of their positions
200
+ const order = Array.from({ length: N }, (_, i) => i).sort((p, q) => z[p] - z[q]);
201
+ const a = new Array(N);
202
+ let i = 0;
203
+ while (i < N) {
204
+ let j = i;
205
+ while (j + 1 < N && z[order[j + 1]] === z[order[i]])
206
+ j++;
207
+ let s = 0;
208
+ for (let t = i; t <= j; t++)
209
+ s += (t + 1 - (N + 1) / 2) ** 2;
210
+ s /= j - i + 1;
211
+ for (let t = i; t <= j; t++)
212
+ a[order[t]] = s;
213
+ i = j + 1;
214
+ }
215
+ let T = 0;
216
+ for (let t = 0; t < m; t++)
217
+ T += a[t];
218
+ let abar = 0;
219
+ for (const v of a)
220
+ abar += v;
221
+ abar /= N;
222
+ let ss = 0;
223
+ for (const v of a)
224
+ ss += (v - abar) ** 2;
225
+ const E = m * abar;
226
+ const V = ((m * n) / (N * (N - 1))) * ss;
227
+ const zstat = (T - E) / Math.sqrt(V);
228
+ const pValue = alternative === "less"
229
+ ? pnorm(zstat)
230
+ : alternative === "greater"
231
+ ? pnorm(zstat, { lowerTail: false })
232
+ : 2 * pnorm(-Math.abs(zstat));
233
+ return { method: "Mood two-sample test of scale", alternative, statistic: zstat, statisticName: "Z", pValue };
234
+ }
@@ -47,3 +47,5 @@ export interface HTest {
47
47
  export declare function matchAlternative(a: string | undefined): Alternative;
48
48
  /** Validate the confidence level (R's t.test also accepts exactly 0 and 1; with 1 the interval is ±Inf) */
49
49
  export declare function checkConfLevel(level: number): void;
50
+ /** Validate the confidence level strictly inside (0, 1), as most of R's tests require */
51
+ export declare function checkConfLevelOpen(level: number): void;
@@ -18,3 +18,9 @@ export function checkConfLevel(level) {
18
18
  throw new RangeError("confLevel must be a number between 0 and 1");
19
19
  }
20
20
  }
21
+ /** Validate the confidence level strictly inside (0, 1), as most of R's tests require */
22
+ export function checkConfLevelOpen(level) {
23
+ if (!(Number.isFinite(level) && level > 0 && level < 1)) {
24
+ throw new RangeError("'conf.level' must be a single number between 0 and 1");
25
+ }
26
+ }
@@ -0,0 +1,48 @@
1
+ import { type Alternative, type HTest, type Sample } from "./types.js";
2
+ /** Options for {@link varTest}. */
3
+ export interface VarTestOptions {
4
+ /** Hypothesized ratio of the variances of `x` and `y`. Default `1`. */
5
+ ratio?: number;
6
+ /** Alternative hypothesis (unambiguous abbreviations accepted). Default `"two.sided"`. */
7
+ alternative?: Alternative | "two" | "l" | "g";
8
+ /** Confidence level of the interval. Default `0.95`. */
9
+ confLevel?: number;
10
+ }
11
+ /** Result of {@link varTest}. The F distribution has two degrees of freedom, so `parameter` is a pair. */
12
+ export interface VarTestResult extends Omit<HTest, "parameter" | "parameterName"> {
13
+ /** `[num df, denom df]`. */
14
+ parameter: [number, number];
15
+ parameterName: [string, string];
16
+ confInt: [number, number];
17
+ confLevel: number;
18
+ /** Ratio of the sample variances. */
19
+ estimate: number;
20
+ nullValue: number;
21
+ }
22
+ /**
23
+ * F test that the variances of two normal samples have a given ratio. Equivalent to R's `var.test()`. `NaN` values
24
+ * are dropped.
25
+ *
26
+ * @example
27
+ * ```ts
28
+ * varTest([1, 3, 2, 5, 4], [2, 9, 4, 12, 7, 1]).statistic; // 0.2307692
29
+ * ```
30
+ */
31
+ export declare function varTest(x: Sample, y: Sample, options?: VarTestOptions): VarTestResult;
32
+ /** Result of {@link bartlettTest}. Has no `alternative`, as R's `bartlett.test()` returns none. */
33
+ export interface BartlettTestResult extends Omit<HTest, "alternative"> {
34
+ /** Degrees of freedom (`parameterName` is `"df"`). */
35
+ parameter: number;
36
+ }
37
+ /**
38
+ * Bartlett's test that the variances of several normal samples are equal. Equivalent to R's `bartlett.test()` with
39
+ * a list of samples. `NaN` and infinite values are dropped, as in R.
40
+ *
41
+ * @param groups The samples (at least two), each with at least two finite values.
42
+ *
43
+ * @example
44
+ * ```ts
45
+ * bartlettTest([[1, 3, 2, 5], [2, 9, 4, 12, 7], [3, 3.5, 4, 2]]).statistic;
46
+ * ```
47
+ */
48
+ export declare function bartlettTest(groups: readonly Sample[]): BartlettTestResult;
@@ -0,0 +1,118 @@
1
+ // F test comparing two variances (R's var.test) and Bartlett's test of homogeneity of variances (R's bartlett.test).
2
+ //
3
+ // Written for rStats.js from the textbook formulas (no ported code). var.test: F = (s_x^2 / s_y^2) / ratio with
4
+ // (n_x - 1, n_y - 1) degrees of freedom, and the interval for the ratio from F quantiles. Both tails of the p-value
5
+ // are computed directly (not as 1 - P), so small p-values keep their relative accuracy. bartlett.test (Bartlett,
6
+ // Proc. R. Soc. A 160, 1937): K^2 = (N log s_p^2 - sum n_i log s_i^2) / (1 + (sum 1/n_i - 1/N) / (3 (k - 1))), with
7
+ // n_i = size - 1, N = sum n_i and s_p^2 the pooled variance; chi-squared with k - 1 degrees of freedom.
8
+ import { pf1, qf1 } from "../distributions/f.js";
9
+ import { pgamma1 } from "../distributions/gamma.js";
10
+ import { variance } from "../internal/summary.js";
11
+ import { checkConfLevelOpen, matchAlternative } from "./types.js";
12
+ /**
13
+ * F test that the variances of two normal samples have a given ratio. Equivalent to R's `var.test()`. `NaN` values
14
+ * are dropped.
15
+ *
16
+ * @example
17
+ * ```ts
18
+ * varTest([1, 3, 2, 5, 4], [2, 9, 4, 12, 7, 1]).statistic; // 0.2307692
19
+ * ```
20
+ */
21
+ export function varTest(x, y, options = {}) {
22
+ // as in R, infinite values are dropped along with NaN
23
+ const xs = Array.from(x).filter(Number.isFinite);
24
+ const ys = Array.from(y).filter(Number.isFinite);
25
+ const ratio = options.ratio ?? 1;
26
+ if (!(Number.isFinite(ratio) && ratio > 0))
27
+ throw new RangeError("'ratio' must be a single positive number");
28
+ const alternative = matchAlternative(options.alternative);
29
+ const confLevel = options.confLevel ?? 0.95;
30
+ checkConfLevelOpen(confLevel);
31
+ const dfx = xs.length - 1;
32
+ const dfy = ys.length - 1;
33
+ if (dfx < 1)
34
+ throw new Error("not enough 'x' observations");
35
+ if (dfy < 1)
36
+ throw new Error("not enough 'y' observations");
37
+ const estimate = variance(xs) / variance(ys);
38
+ const statistic = estimate / ratio;
39
+ const lower = pf1(statistic, dfx, dfy, true, false);
40
+ const upper = pf1(statistic, dfx, dfy, false, false);
41
+ let pValue;
42
+ let confInt;
43
+ if (alternative === "less") {
44
+ pValue = lower;
45
+ confInt = [0, estimate / qf1(1 - confLevel, dfx, dfy, true, false)];
46
+ }
47
+ else if (alternative === "greater") {
48
+ pValue = upper;
49
+ confInt = [estimate / qf1(confLevel, dfx, dfy, true, false), Infinity];
50
+ }
51
+ else {
52
+ pValue = Math.min(1, 2 * Math.min(lower, upper));
53
+ const beta = (1 - confLevel) / 2;
54
+ confInt = [estimate / qf1(1 - beta, dfx, dfy, true, false), estimate / qf1(beta, dfx, dfy, true, false)];
55
+ }
56
+ return {
57
+ method: "F test to compare two variances",
58
+ alternative,
59
+ statistic,
60
+ statisticName: "F",
61
+ parameter: [dfx, dfy],
62
+ parameterName: ["num df", "denom df"],
63
+ pValue,
64
+ confInt,
65
+ confLevel,
66
+ estimate,
67
+ estimateNames: "ratio of variances",
68
+ nullValue: ratio,
69
+ nullValueName: "ratio of variances",
70
+ };
71
+ }
72
+ /**
73
+ * Bartlett's test that the variances of several normal samples are equal. Equivalent to R's `bartlett.test()` with
74
+ * a list of samples. `NaN` and infinite values are dropped, as in R.
75
+ *
76
+ * @param groups The samples (at least two), each with at least two finite values.
77
+ *
78
+ * @example
79
+ * ```ts
80
+ * bartlettTest([[1, 3, 2, 5], [2, 9, 4, 12, 7], [3, 3.5, 4, 2]]).statistic;
81
+ * ```
82
+ */
83
+ export function bartlettTest(groups) {
84
+ const k = groups.length;
85
+ if (k < 2)
86
+ throw new RangeError("'x' must be a list with at least 2 elements");
87
+ const n = [];
88
+ const v = [];
89
+ for (const g of groups) {
90
+ // as in R, infinite values are dropped along with NaN
91
+ const s = Array.from(g).filter(Number.isFinite);
92
+ if (s.length < 2)
93
+ throw new RangeError("there must be at least 2 observations in each group");
94
+ n.push(s.length - 1);
95
+ v.push(variance(s));
96
+ }
97
+ let N = 0;
98
+ let pooled = 0;
99
+ let sumLog = 0;
100
+ let sumInv = 0;
101
+ for (let i = 0; i < k; i++) {
102
+ N += n[i];
103
+ pooled += n[i] * v[i];
104
+ sumLog += n[i] * Math.log(v[i]);
105
+ sumInv += 1 / n[i];
106
+ }
107
+ pooled /= N;
108
+ const statistic = (N * Math.log(pooled) - sumLog) / (1 + (sumInv - 1 / N) / (3 * (k - 1)));
109
+ const df = k - 1;
110
+ return {
111
+ method: "Bartlett test of homogeneity of variances",
112
+ statistic,
113
+ statisticName: "Bartlett's K-squared",
114
+ parameter: df,
115
+ parameterName: "df",
116
+ pValue: pgamma1(statistic, df / 2, 2, false, false),
117
+ };
118
+ }