@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,64 @@
1
+ import type { GroupLabel } from "./groups.js";
2
+ import { type PAdjustMethod } from "./pAdjust.js";
3
+ import { type Alternative, type Sample } from "./types.js";
4
+ import { type WilcoxTestOptions } from "./wilcoxTest.js";
5
+ /** Result of the pairwise comparisons (R's `pairwise.htest`). */
6
+ export interface PairwiseResult {
7
+ /** Description of the tests (R's `method`). */
8
+ method: string;
9
+ /**
10
+ * Adjusted p-values as a lower-triangular table: row `r` compares group `rowNames[r]` with each group
11
+ * `colNames[c]`; entries above the diagonal are `NaN`.
12
+ */
13
+ pValue: number[][];
14
+ /** Labels of the rows (groups 2..k). */
15
+ rowNames: string[];
16
+ /** Labels of the columns (groups 1..k-1). */
17
+ colNames: string[];
18
+ /** Adjustment method. */
19
+ pAdjustMethod: PAdjustMethod | string;
20
+ }
21
+ /** Common options of the pairwise comparisons. */
22
+ export interface PairwiseOptions {
23
+ /** p-value adjustment (see `pAdjust`). Default `"holm"`. */
24
+ pAdjustMethod?: PAdjustMethod | (string & {});
25
+ /** Alternative hypothesis for each comparison of row group against column group. Default `"two.sided"`. */
26
+ alternative?: Alternative | "two" | "l" | "g";
27
+ }
28
+ /** Options for {@link pairwiseTTest}. */
29
+ export interface PairwiseTTestOptions extends PairwiseOptions {
30
+ /** Pool the standard deviation over all groups. Default `!paired`. */
31
+ poolSd?: boolean;
32
+ /** Paired t tests (groups matched by position). Default `false`. */
33
+ paired?: boolean;
34
+ }
35
+ /**
36
+ * Pairwise t tests between groups, with adjusted p-values. Equivalent to R's `pairwise.t.test()`.
37
+ *
38
+ * @param x Response values.
39
+ * @param g Group labels, parallel to `x`.
40
+ */
41
+ export declare function pairwiseTTest(x: Sample, g: readonly GroupLabel[], options?: PairwiseTTestOptions): PairwiseResult;
42
+ /** Options for {@link pairwiseWilcoxTest}: those of the comparisons themselves, as for `wilcoxTest`. */
43
+ export interface PairwiseWilcoxTestOptions extends PairwiseOptions, Omit<WilcoxTestOptions, "alternative" | "confInt"> {
44
+ }
45
+ /**
46
+ * Pairwise Wilcoxon rank sum (or signed rank, with `paired`) tests between groups, with adjusted p-values. Equivalent
47
+ * to R's `pairwise.wilcox.test()`. `method` is that of the last comparison, as in R.
48
+ */
49
+ export declare function pairwiseWilcoxTest(x: Sample, g: readonly GroupLabel[], options?: PairwiseWilcoxTestOptions): PairwiseResult;
50
+ /** Options for {@link pairwisePropTest}. */
51
+ export interface PairwisePropTestOptions {
52
+ /** p-value adjustment (see `pAdjust`). Default `"holm"`. */
53
+ pAdjustMethod?: PAdjustMethod | (string & {});
54
+ /** Continuity correction in each two-group test. Default `true`. */
55
+ correct?: boolean;
56
+ }
57
+ /**
58
+ * Pairwise two-group tests of equal proportions, with adjusted p-values. Equivalent to R's `pairwise.prop.test()`.
59
+ *
60
+ * @param x Numbers of successes per group.
61
+ * @param n Numbers of trials per group. Groups with `NaN` in `x` or `n` are dropped (the rows and columns are then
62
+ * numbered over the groups kept, as in R).
63
+ */
64
+ export declare function pairwisePropTest(x: readonly number[], n: readonly number[], options?: PairwisePropTestOptions): PairwiseResult;
@@ -0,0 +1,152 @@
1
+ // Pairwise comparisons between groups with adjusted p-values (R's pairwise.t.test, pairwise.wilcox.test and
2
+ // pairwise.prop.test).
3
+ //
4
+ // Written for rStats.js (no ported code). Every pair of groups (i, j), i after j in the order of the sorted labels,
5
+ // is compared, and the p-values of the lower triangle are adjusted together with pAdjust. The pooled-SD t tests use
6
+ // the standard deviation pooled over all groups, s^2 = sum (n_i - 1) s_i^2 / sum (n_i - 1), with its degrees of
7
+ // freedom; the other comparisons are the two-sample tests themselves.
8
+ import { pt1 } from "../distributions/t.js";
9
+ import { mean, variance } from "../internal/summary.js";
10
+ import { pAdjust } from "./pAdjust.js";
11
+ import { propTest } from "./propTest.js";
12
+ import { tTest } from "./tTest.js";
13
+ import { matchAlternative } from "./types.js";
14
+ import { wilcoxTest } from "./wilcoxTest.js";
15
+ /** Group a sample by labels: levels sorted (numbers numerically, strings in code-unit order), values in input order */
16
+ function byLevel(x, g, dropNaN) {
17
+ const xs = Array.from(x);
18
+ if (xs.length !== g.length)
19
+ throw new RangeError("'x' and 'g' must have the same length");
20
+ const map = new Map();
21
+ for (let i = 0; i < xs.length; i++) {
22
+ const l = g[i];
23
+ if (typeof l === "number" && Number.isNaN(l))
24
+ continue;
25
+ if (dropNaN && Number.isNaN(xs[i]))
26
+ continue;
27
+ let arr = map.get(l);
28
+ if (arr === undefined)
29
+ map.set(l, (arr = []));
30
+ arr.push(xs[i]);
31
+ }
32
+ const levels = Array.from(map.keys()).sort((a, b) => typeof a === "number" && typeof b === "number" ? a - b : String(a) < String(b) ? -1 : String(a) > String(b) ? 1 : 0);
33
+ return { levels, groups: levels.map((l) => map.get(l)) };
34
+ }
35
+ /** Lower-triangular table of compare(i, j) for i > j, adjusted together */
36
+ function table(k, compare, method) {
37
+ const raw = [];
38
+ const pos = [];
39
+ for (let j = 0; j < k - 1; j++) {
40
+ for (let i = j + 1; i < k; i++) {
41
+ raw.push(compare(i, j));
42
+ pos.push([i - 1, j]);
43
+ }
44
+ }
45
+ const adj = pAdjust(raw, method);
46
+ const out = Array.from({ length: k - 1 }, () => new Array(k - 1).fill(NaN));
47
+ pos.forEach(([r, c], idx) => (out[r][c] = adj[idx]));
48
+ return out;
49
+ }
50
+ function result(method, levels, pValue, adjust) {
51
+ return {
52
+ method,
53
+ pValue,
54
+ rowNames: levels.slice(1).map(String),
55
+ colNames: levels.slice(0, -1).map(String),
56
+ pAdjustMethod: adjust,
57
+ };
58
+ }
59
+ /**
60
+ * Pairwise t tests between groups, with adjusted p-values. Equivalent to R's `pairwise.t.test()`.
61
+ *
62
+ * @param x Response values.
63
+ * @param g Group labels, parallel to `x`.
64
+ */
65
+ export function pairwiseTTest(x, g, options = {}) {
66
+ const paired = options.paired ?? false;
67
+ const poolSd = options.poolSd ?? !paired;
68
+ if (paired && poolSd)
69
+ throw new RangeError("pooling of SD is incompatible with paired tests");
70
+ const alternative = matchAlternative(options.alternative);
71
+ const adjust = options.pAdjustMethod ?? "holm";
72
+ const { levels, groups } = byLevel(x, g, !paired);
73
+ const k = levels.length;
74
+ if (k < 2)
75
+ throw new RangeError("there must be at least two groups");
76
+ let compare;
77
+ let method;
78
+ if (poolSd) {
79
+ method = "t tests with pooled SD";
80
+ const n = groups.map((s) => s.length);
81
+ const means = groups.map((s) => (s.length > 0 ? mean(s) : NaN));
82
+ let ss = 0;
83
+ let df = 0;
84
+ groups.forEach((s) => {
85
+ if (s.length > 1) {
86
+ ss += variance(s) * (s.length - 1);
87
+ df += s.length - 1;
88
+ }
89
+ });
90
+ const sd = Math.sqrt(ss / df);
91
+ compare = (i, j) => {
92
+ const t = (means[i] - means[j]) / (sd * Math.sqrt(1 / n[i] + 1 / n[j]));
93
+ if (alternative === "less")
94
+ return pt1(t, df, true, false);
95
+ if (alternative === "greater")
96
+ return pt1(t, df, false, false);
97
+ return 2 * pt1(-Math.abs(t), df, true, false);
98
+ };
99
+ }
100
+ else {
101
+ method = paired ? "paired t tests" : "t tests with non-pooled SD";
102
+ compare = (i, j) => tTest(groups[i], groups[j], { paired, alternative }).pValue;
103
+ }
104
+ return result(method, levels, table(k, compare, adjust), adjust);
105
+ }
106
+ /**
107
+ * Pairwise Wilcoxon rank sum (or signed rank, with `paired`) tests between groups, with adjusted p-values. Equivalent
108
+ * to R's `pairwise.wilcox.test()`. `method` is that of the last comparison, as in R.
109
+ */
110
+ export function pairwiseWilcoxTest(x, g, options = {}) {
111
+ const { pAdjustMethod, alternative, ...rest } = options;
112
+ const adjust = pAdjustMethod ?? "holm";
113
+ const { levels, groups } = byLevel(x, g, !(rest.paired ?? false));
114
+ const k = levels.length;
115
+ if (k < 2)
116
+ throw new RangeError("there must be at least two groups");
117
+ let method = "";
118
+ const pv = table(k, (i, j) => {
119
+ const r = wilcoxTest(groups[i], groups[j], { ...rest, ...(alternative === undefined ? {} : { alternative }) });
120
+ method = r.method;
121
+ return r.pValue;
122
+ }, adjust);
123
+ return result(method, levels, pv, adjust);
124
+ }
125
+ /**
126
+ * Pairwise two-group tests of equal proportions, with adjusted p-values. Equivalent to R's `pairwise.prop.test()`.
127
+ *
128
+ * @param x Numbers of successes per group.
129
+ * @param n Numbers of trials per group. Groups with `NaN` in `x` or `n` are dropped (the rows and columns are then
130
+ * numbered over the groups kept, as in R).
131
+ */
132
+ export function pairwisePropTest(x, n, options = {}) {
133
+ if (x.length !== n.length)
134
+ throw new RangeError("'x' and 'n' must have the same length");
135
+ // as in R, groups with a missing count are dropped and the rest numbered 1, 2, ... in their order
136
+ const xs = [];
137
+ const ns = [];
138
+ x.forEach((v, i) => {
139
+ if (Number.isNaN(v) || Number.isNaN(n[i]))
140
+ return;
141
+ xs.push(v);
142
+ ns.push(n[i]);
143
+ });
144
+ const k = xs.length;
145
+ if (k < 2)
146
+ throw new RangeError("too few groups");
147
+ const adjust = options.pAdjustMethod ?? "holm";
148
+ const correct = options.correct ?? true;
149
+ const pv = table(k, (i, j) => propTest([xs[i], xs[j]], [ns[i], ns[j]], { correct }).pValue, adjust);
150
+ const levels = Array.from({ length: k }, (_, i) => i + 1);
151
+ return result("Pairwise comparison of proportions", levels, pv, adjust);
152
+ }
@@ -0,0 +1,35 @@
1
+ import { type Alternative, type HTest } from "./types.js";
2
+ /** Options for {@link poissonTest}. */
3
+ export interface PoissonTestOptions {
4
+ /** Time base(s): one per count, nonnegative. Default `1` (or `[1, 1]`). */
5
+ T?: number | readonly number[];
6
+ /** Hypothesized rate, or rate ratio for two samples (nonnegative). Default `1`. */
7
+ r?: number;
8
+ /** Alternative hypothesis (unambiguous abbreviations accepted). Default `"two.sided"`. */
9
+ alternative?: Alternative | "two" | "l" | "g";
10
+ /** Confidence level of the interval. Default `0.95`. */
11
+ confLevel?: number;
12
+ }
13
+ /** Result of {@link poissonTest}. */
14
+ export interface PoissonTestResult extends HTest {
15
+ /** Time base (one sample) or expected first count under the null hypothesis (two samples). */
16
+ parameter: number;
17
+ confInt: [number, number];
18
+ confLevel: number;
19
+ /** Event rate (one sample) or rate ratio (two samples). */
20
+ estimate: number;
21
+ nullValue: number;
22
+ }
23
+ /**
24
+ * Exact test of a Poisson rate, or comparison of two Poisson rates. Equivalent to R's `poisson.test()`.
25
+ *
26
+ * @param x Number of events, or `[count1, count2]` to compare two rates.
27
+ * @param options See {@link PoissonTestOptions}.
28
+ *
29
+ * @example
30
+ * ```ts
31
+ * poissonTest(137, { T: 24.19893 }).pValue; // < 2.2e-16 (rate 1 against 5.66)
32
+ * poissonTest([11, 23], { T: [800, 3011] }).estimate; // rate ratio 1.80
33
+ * ```
34
+ */
35
+ export declare function poissonTest(x: number | readonly number[], options?: PoissonTestOptions): PoissonTestResult;
@@ -0,0 +1,170 @@
1
+ // Exact Poisson tests (R's poisson.test): a rate against a hypothesized value, or the ratio of two rates.
2
+ //
3
+ // Written for rStats.js from the textbook construction and R's documented behaviour. One sample: the two-sided p-value
4
+ // adds the probabilities of all counts no more likely than the observed one (relative slack 1e-7, as in binomTest), the
5
+ // tail on the far side of the mode being located by a search over integers, and the interval is the exact (Garwood
6
+ // 1936) one from gamma quantiles. Two samples: conditionally on the total, the first count is binomial with
7
+ // probability r T1 / (r T1 + T2), so the test is binomTest's and its interval for p maps to the rate ratio through
8
+ // p / (1 - p) T2 / T1.
9
+ import { dpois1, ppois1 } from "../distributions/poisson.js";
10
+ import { qgamma1 } from "../distributions/gamma.js";
11
+ import { binomTest } from "./binomTest.js";
12
+ import { checkConfLevelOpen, matchAlternative } from "./types.js";
13
+ const REL_ERR = 1 + 1e-7;
14
+ function isCount(v) {
15
+ return Number.isFinite(v) && v >= 0 && Math.abs(v - Math.round(v)) <= 1e-7 * Math.max(1, Math.abs(v));
16
+ }
17
+ /**
18
+ * Two-sided p-value of the count x under Poisson(m): the total probability of the counts whose probability is at most
19
+ * (1 + 1e-7) times that of x. The density is unimodal (increasing up to floor(m), decreasing after), so the observed
20
+ * side contributes the tail through x and the other side the tail beyond the first qualifying count j past the mode,
21
+ * located by doubling the step away from the mode (right side) and bisecting on integers.
22
+ */
23
+ function twoSidedP(x, m) {
24
+ if (m === 0)
25
+ return x === 0 ? 1 : 0;
26
+ if (x === m)
27
+ return 1;
28
+ const cut = dpois1(x, m, false) * REL_ERR;
29
+ const qualifies = (i) => dpois1(i, m, false) <= cut;
30
+ const mode = Math.floor(m);
31
+ if (x < m) {
32
+ // smallest j >= start with dpois(j) <= cut; densities decrease from the mode onwards (with m an integer, m - 1
33
+ // and m are equally likely, so starting at x + 1 picks up m when x = m - 1)
34
+ const start = Math.max(x + 1, mode);
35
+ let j = start;
36
+ if (!qualifies(start)) {
37
+ let fails = start; // largest count known not to qualify
38
+ let step = 1;
39
+ j = start + 1;
40
+ while (!qualifies(j)) {
41
+ fails = j;
42
+ step *= 2;
43
+ j = start + step;
44
+ }
45
+ while (j - fails > 1) {
46
+ const mid = fails + Math.floor((j - fails) / 2);
47
+ if (qualifies(mid))
48
+ j = mid;
49
+ else
50
+ fails = mid;
51
+ }
52
+ }
53
+ return Math.min(1, ppois1(x, m, true, false) + ppois1(j - 1, m, false, false));
54
+ }
55
+ // x > m: largest j <= end with dpois(j) <= cut; densities increase from 0 up to the mode (j = -1 when none does)
56
+ const end = Math.min(x - 1, mode);
57
+ let j;
58
+ if (!qualifies(0))
59
+ j = -1;
60
+ else if (qualifies(end))
61
+ j = end;
62
+ else {
63
+ j = 0;
64
+ let fails = end; // smallest count known not to qualify
65
+ while (fails - j > 1) {
66
+ const mid = j + Math.floor((fails - j) / 2);
67
+ if (qualifies(mid))
68
+ j = mid;
69
+ else
70
+ fails = mid;
71
+ }
72
+ }
73
+ return Math.min(1, ppois1(j, m, true, false) + ppois1(x - 1, m, false, false));
74
+ }
75
+ /**
76
+ * Exact test of a Poisson rate, or comparison of two Poisson rates. Equivalent to R's `poisson.test()`.
77
+ *
78
+ * @param x Number of events, or `[count1, count2]` to compare two rates.
79
+ * @param options See {@link PoissonTestOptions}.
80
+ *
81
+ * @example
82
+ * ```ts
83
+ * poissonTest(137, { T: 24.19893 }).pValue; // < 2.2e-16 (rate 1 against 5.66)
84
+ * poissonTest([11, 23], { T: [800, 3011] }).estimate; // rate ratio 1.80
85
+ * ```
86
+ */
87
+ export function poissonTest(x, options = {}) {
88
+ const xs = typeof x === "number" ? [x] : Array.from(x);
89
+ if (xs.length < 1 || xs.length > 2)
90
+ throw new RangeError("incorrect length of 'x'");
91
+ if (!xs.every(isCount))
92
+ throw new RangeError("'x' must be finite, nonnegative, and integer");
93
+ const counts = xs.map(Math.round);
94
+ const k = counts.length;
95
+ const Traw = options.T ?? 1;
96
+ const T = typeof Traw === "number" ? new Array(k).fill(Traw) : Array.from(Traw);
97
+ if (T.length !== k)
98
+ throw new RangeError("'x' and 'T' have incompatible length");
99
+ // as in R, a time base or rate of 0 is accepted (the counts then have mean 0 under the null hypothesis)
100
+ if (!T.every((t) => Number.isFinite(t) && t >= 0))
101
+ throw new RangeError("'T' must be nonnegative");
102
+ const r = options.r ?? 1;
103
+ if (!(Number.isFinite(r) && r >= 0))
104
+ throw new RangeError("'r' must be a single positive number");
105
+ const alternative = matchAlternative(options.alternative);
106
+ const confLevel = options.confLevel ?? 0.95;
107
+ checkConfLevelOpen(confLevel);
108
+ if (k === 2) {
109
+ const [x1, x2] = counts;
110
+ const [T1, T2] = T;
111
+ const n = x1 + x2;
112
+ // NaN (rejected by binomTest, as in R) when both r T1 and T2 are 0. The [successes, failures] form also takes
113
+ // a total of 0 (p-value 1, interval [0, 1]), where binomTest(0, 0) is an error as in R
114
+ const p0 = (r * T1) / (r * T1 + T2);
115
+ const b = binomTest([x1, x2], { p: p0, alternative, confLevel });
116
+ const toRatio = (p) => (p / (1 - p)) * (T2 / T1);
117
+ return {
118
+ method: "Comparison of Poisson rates",
119
+ alternative,
120
+ statistic: x1,
121
+ statisticName: "count1",
122
+ // R reports sum(x) r T1 / (T1 + r T2) here (r on the other time base than in the binomial probability)
123
+ parameter: (n * r * T1) / (T1 + r * T2),
124
+ parameterName: "expected count1",
125
+ pValue: b.pValue,
126
+ confInt: [toRatio(b.confInt[0]), toRatio(b.confInt[1])],
127
+ confLevel,
128
+ estimate: x1 / T1 / (x2 / T2),
129
+ estimateNames: "rate ratio",
130
+ nullValue: r,
131
+ nullValueName: "rate ratio",
132
+ };
133
+ }
134
+ const xv = counts[0];
135
+ const Tv = T[0];
136
+ const m = r * Tv;
137
+ let pValue;
138
+ if (alternative === "less")
139
+ pValue = ppois1(xv, m, true, false);
140
+ else if (alternative === "greater")
141
+ pValue = ppois1(xv - 1, m, false, false);
142
+ else
143
+ pValue = twoSidedP(xv, m);
144
+ const lower = (alpha) => (xv === 0 ? 0 : qgamma1(alpha, xv, 1, true, false));
145
+ const upper = (alpha) => qgamma1(1 - alpha, xv + 1, 1, true, false);
146
+ let ci;
147
+ if (alternative === "less")
148
+ ci = [0, upper(1 - confLevel)];
149
+ else if (alternative === "greater")
150
+ ci = [lower(1 - confLevel), Infinity];
151
+ else {
152
+ const alpha = (1 - confLevel) / 2;
153
+ ci = [lower(alpha), upper(alpha)];
154
+ }
155
+ return {
156
+ method: "Exact Poisson test",
157
+ alternative,
158
+ statistic: xv,
159
+ statisticName: "number of events",
160
+ parameter: Tv,
161
+ parameterName: "time base",
162
+ pValue,
163
+ confInt: [ci[0] / Tv, ci[1] / Tv],
164
+ confLevel,
165
+ estimate: xv / Tv,
166
+ estimateNames: "event rate",
167
+ nullValue: r,
168
+ nullValueName: "event rate",
169
+ };
170
+ }
@@ -0,0 +1,37 @@
1
+ import { type Alternative, type HTest } from "./types.js";
2
+ /** Options for {@link propTest}. */
3
+ export interface PropTestOptions {
4
+ /** Hypothesized proportion(s), one per group, each strictly between 0 and 1. Default: `0.5` for one group, and
5
+ * equality of the proportions for several. */
6
+ p?: number | readonly number[];
7
+ /** Alternative hypothesis (one or two groups without `p`; otherwise only `"two.sided"`). Default `"two.sided"`. */
8
+ alternative?: Alternative | "two" | "l" | "g";
9
+ /** Confidence level of the interval. Default `0.95`. */
10
+ confLevel?: number;
11
+ /** Apply the continuity correction (one or two groups). Default `true`. */
12
+ correct?: boolean;
13
+ }
14
+ /** Result of {@link propTest}. */
15
+ export interface PropTestResult extends Omit<HTest, "nullValue" | "nullValueName"> {
16
+ /** Degrees of freedom (`parameterName` is `"df"`). */
17
+ parameter: number;
18
+ /** The observed proportion(s). */
19
+ estimate: number | number[];
20
+ /** Hypothesized proportion(s), when given or for one group. */
21
+ nullValue?: number | number[];
22
+ nullValueName?: string | string[];
23
+ }
24
+ /**
25
+ * Test of equal or given proportions. Equivalent to R's `prop.test()`.
26
+ *
27
+ * @param x Numbers of successes, or a table of rows `[successes, failures]`.
28
+ * @param n Numbers of trials (omitted, and the options passed in its place, when `x` is a table).
29
+ * @param options See {@link PropTestOptions}.
30
+ *
31
+ * @example
32
+ * ```ts
33
+ * propTest([83, 90, 129, 70], [86, 93, 136, 82]).pValue; // equality of four proportions
34
+ * propTest(15, 20, { p: 0.5 }).confInt;
35
+ * ```
36
+ */
37
+ export declare function propTest(x: number | readonly number[] | readonly (readonly number[])[], n?: number | readonly number[] | PropTestOptions, options?: PropTestOptions): PropTestResult;
@@ -0,0 +1,164 @@
1
+ // Test of proportions (R's prop.test): one proportion against a hypothesized value, equality of k proportions, or k
2
+ // proportions against given values.
3
+ //
4
+ // Written for rStats.js from the textbook construction and R's documented behaviour (groups with a missing count are
5
+ // dropped; a given p has one value per group before dropping). The statistic is Pearson's chi-squared on the k x 2
6
+ // table of successes and failures, with Yates' continuity correction (|O - E| - c) for k <= 2, where c = 1/2 is
7
+ // reduced so as not to overshoot (to |x - n p| for one sample, and |p1 - p2| / (1/n1 + 1/n2) for two without p; the
8
+ // statistic is then exactly 0). One-sided alternatives use the signed square root of the statistic as a normal
9
+ // deviate. Intervals: the Wilson score interval (Wilson, JASA 22, 1927) with continuity correction for one proportion
10
+ // (Newcombe, Statistics in Medicine 17, 1998, method 4), and the Wald interval with continuity correction for the
11
+ // difference of two.
12
+ import { pgamma1 } from "../distributions/gamma.js";
13
+ import { pnorm, qnorm } from "../distributions/normal.js";
14
+ import { checkConfLevelOpen, matchAlternative } from "./types.js";
15
+ /**
16
+ * Test of equal or given proportions. Equivalent to R's `prop.test()`.
17
+ *
18
+ * @param x Numbers of successes, or a table of rows `[successes, failures]`.
19
+ * @param n Numbers of trials (omitted, and the options passed in its place, when `x` is a table).
20
+ * @param options See {@link PropTestOptions}.
21
+ *
22
+ * @example
23
+ * ```ts
24
+ * propTest([83, 90, 129, 70], [86, 93, 136, 82]).pValue; // equality of four proportions
25
+ * propTest(15, 20, { p: 0.5 }).confInt;
26
+ * ```
27
+ */
28
+ export function propTest(x, n, options = {}) {
29
+ // ---- inputs: successes and trials per group ----
30
+ let xs;
31
+ let ns;
32
+ let opts = options;
33
+ if (typeof x !== "number" && x.length > 0 && Array.isArray(x[0])) {
34
+ const rows = x;
35
+ if (rows.some((r) => r.length !== 2))
36
+ throw new RangeError("'x' must have 2 columns");
37
+ xs = rows.map((r) => r[0]);
38
+ ns = rows.map((r) => r[0] + r[1]);
39
+ if (n !== undefined && typeof n === "object" && !Array.isArray(n))
40
+ opts = n;
41
+ }
42
+ else {
43
+ if (n === undefined || (typeof n === "object" && !Array.isArray(n)))
44
+ throw new RangeError("'n' must be given");
45
+ xs = typeof x === "number" ? [x] : Array.from(x);
46
+ ns = typeof n === "number" ? [n] : Array.from(n);
47
+ }
48
+ if (xs.length !== ns.length)
49
+ throw new RangeError("'x' and 'n' must have the same length");
50
+ const L = xs.length;
51
+ const kept = []; // 0-based positions of the groups without missing values
52
+ for (let i = 0; i < L; i++)
53
+ if (!Number.isNaN(xs[i]) && !Number.isNaN(ns[i]))
54
+ kept.push(i);
55
+ const k = kept.length;
56
+ if (k < 1)
57
+ throw new RangeError("not enough data");
58
+ xs = kept.map((i) => xs[i]);
59
+ ns = kept.map((i) => ns[i]);
60
+ if (ns.some((v) => !(v > 0)))
61
+ throw new RangeError("elements of 'n' must be positive");
62
+ if (xs.some((v) => !(v >= 0)))
63
+ throw new RangeError("elements of 'x' must be nonnegative");
64
+ if (xs.some((v, i) => v > ns[i]))
65
+ throw new RangeError("elements of 'x' must not be greater than those of 'n'");
66
+ let p;
67
+ if (opts.p !== undefined) {
68
+ const given = typeof opts.p === "number" ? [opts.p] : Array.from(opts.p);
69
+ if (given.length !== L)
70
+ throw new RangeError("'p' must have the same length as 'x' and 'n'");
71
+ p = kept.map((i) => given[i]);
72
+ if (p.some((v) => !(v > 0 && v < 1)))
73
+ throw new RangeError("elements of 'p' must be in (0,1)");
74
+ }
75
+ const pGiven = p !== undefined;
76
+ if (k === 1 && p === undefined)
77
+ p = [0.5];
78
+ const confLevel = opts.confLevel ?? 0.95;
79
+ checkConfLevelOpen(confLevel);
80
+ let alternative = matchAlternative(opts.alternative);
81
+ if (k > 2 || (k === 2 && pGiven))
82
+ alternative = "two.sided";
83
+ const phat = xs.map((v, i) => v / ns[i]);
84
+ const delta = k === 2 ? phat[0] - phat[1] : 0;
85
+ const invSum = k === 2 ? 1 / ns[0] + 1 / ns[1] : 0;
86
+ // ---- continuity correction, reduced so as not to overshoot |O - E| ----
87
+ let yates = (opts.correct ?? true) && k <= 2 ? 0.5 : 0;
88
+ let capped = false;
89
+ if (yates > 0 && k === 1) {
90
+ const gap = Math.abs(xs[0] - ns[0] * p[0]);
91
+ if (gap <= yates)
92
+ [yates, capped] = [gap, true];
93
+ }
94
+ else if (yates > 0 && k === 2 && !pGiven) {
95
+ const gap = Math.abs(delta) / invSum;
96
+ if (gap <= yates)
97
+ [yates, capped] = [gap, true];
98
+ }
99
+ // ---- Pearson's chi-squared on the k x 2 table of successes and failures ----
100
+ const pooled = xs.reduce((a, b) => a + b, 0) / ns.reduce((a, b) => a + b, 0);
101
+ let statistic = 0;
102
+ if (!capped) {
103
+ for (let i = 0; i < k; i++) {
104
+ const pi = p?.[i] ?? pooled;
105
+ const e1 = ns[i] * pi;
106
+ const e2 = ns[i] * (1 - pi);
107
+ statistic += (Math.abs(xs[i] - e1) - yates) ** 2 / e1;
108
+ statistic += (Math.abs(ns[i] - xs[i] - e2) - yates) ** 2 / e2;
109
+ }
110
+ }
111
+ const df = p !== undefined ? k : k - 1;
112
+ let pValue;
113
+ if (alternative === "two.sided")
114
+ pValue = pgamma1(statistic, df / 2, 2, false, false);
115
+ else {
116
+ const sign = Math.sign(k === 1 ? phat[0] - p[0] : delta);
117
+ pValue = pnorm(sign * Math.sqrt(statistic), { lowerTail: alternative === "less" });
118
+ }
119
+ // ---- confidence interval ----
120
+ let confInt;
121
+ if (k === 1 || (k === 2 && !pGiven)) {
122
+ const z = qnorm(alternative === "two.sided" ? (1 + confLevel) / 2 : confLevel);
123
+ if (k === 1) {
124
+ const nn = ns[0];
125
+ const z2 = z * z;
126
+ // Wilson score bound for the shifted proportion pc (Newcombe 1998, method 4)
127
+ const wilson = (pc, sgn) => (pc + z2 / (2 * nn) + sgn * z * Math.sqrt((pc * (1 - pc)) / nn + z2 / (4 * nn * nn))) / (1 + z2 / nn);
128
+ const pUp = phat[0] + yates / nn;
129
+ const pLo = phat[0] - yates / nn;
130
+ const upper = Math.min(1, Math.max(0, pUp >= 1 ? 1 : wilson(pUp, 1)));
131
+ const lower = Math.min(1, Math.max(0, pLo <= 0 ? 0 : wilson(pLo, -1)));
132
+ confInt = alternative === "two.sided" ? [lower, upper] : alternative === "greater" ? [lower, 1] : [0, upper];
133
+ }
134
+ else {
135
+ const se = Math.sqrt(phat.reduce((acc, v, i) => acc + (v * (1 - v)) / ns[i], 0));
136
+ const w = z * se + yates * invSum;
137
+ const lower = Math.max(-1, delta - w);
138
+ const upper = Math.min(1, delta + w);
139
+ confInt = alternative === "two.sided" ? [lower, upper] : alternative === "greater" ? [lower, 1] : [-1, upper];
140
+ }
141
+ }
142
+ const names = k === 1 ? "p" : kept.map((i) => `prop ${i + 1}`);
143
+ const kind = k === 1 ? "1-sample proportions test" : `${k}-sample test for ${pGiven ? "given proportions" : "equality of proportions"}`;
144
+ const result = {
145
+ method: `${kind} ${yates !== 0 ? "with" : "without"} continuity correction`,
146
+ alternative,
147
+ statistic,
148
+ statisticName: "X-squared",
149
+ parameter: df,
150
+ parameterName: "df",
151
+ pValue,
152
+ estimate: k === 1 ? phat[0] : phat,
153
+ estimateNames: names,
154
+ };
155
+ if (p !== undefined) {
156
+ result.nullValue = k === 1 ? p[0] : p;
157
+ result.nullValueName = names;
158
+ }
159
+ if (confInt !== undefined) {
160
+ result.confInt = confInt;
161
+ result.confLevel = confLevel;
162
+ }
163
+ return result;
164
+ }
@@ -0,0 +1,52 @@
1
+ import { type GroupLabel } from "./groups.js";
2
+ import { type Alternative, type HTest, type Sample } from "./types.js";
3
+ /** Result of a k-sample test with a chi-squared statistic. Has no `alternative`, as R returns none. */
4
+ export interface ChisqStatTestResult extends Omit<HTest, "alternative"> {
5
+ /** Degrees of freedom (`parameterName` is `"df"`). */
6
+ parameter: number;
7
+ }
8
+ /**
9
+ * Kruskal-Wallis rank sum test. Equivalent to R's `kruskal.test()`.
10
+ *
11
+ * @param x An array of samples (each with at least one non-missing value, as R requires), or one sample with group
12
+ * labels `g`.
13
+ * @param g Group labels, parallel to `x`.
14
+ */
15
+ export declare function kruskalTest(x: Sample | readonly Sample[], g?: readonly GroupLabel[]): ChisqStatTestResult;
16
+ /**
17
+ * Friedman rank sum test for an unreplicated complete block design. Equivalent to R's `friedman.test()` with a
18
+ * matrix.
19
+ *
20
+ * @param y Blocks as rows, treatments as columns. Rows with a missing value are dropped.
21
+ */
22
+ export declare function friedmanTest(y: readonly (readonly number[])[]): ChisqStatTestResult;
23
+ /** Result of {@link quadeTest}: an F statistic with two degrees of freedom. */
24
+ export interface QuadeTestResult extends Omit<HTest, "alternative" | "parameter" | "parameterName"> {
25
+ /** `[num df, denom df]`. */
26
+ parameter: [number, number];
27
+ parameterName: [string, string];
28
+ }
29
+ /**
30
+ * Quade test for an unreplicated complete block design. Equivalent to R's `quade.test()` with a matrix.
31
+ *
32
+ * @param y Blocks as rows, treatments as columns. Rows with a missing value are dropped.
33
+ */
34
+ export declare function quadeTest(y: readonly (readonly number[])[]): QuadeTestResult;
35
+ /**
36
+ * Fligner-Killeen (median) test of homogeneity of variances. Equivalent to R's `fligner.test()`.
37
+ *
38
+ * @param x An array of samples (each with at least one non-missing value, as R requires), or one sample with group
39
+ * labels `g`.
40
+ * @param g Group labels, parallel to `x`.
41
+ */
42
+ export declare function flignerTest(x: Sample | readonly Sample[], g?: readonly GroupLabel[]): ChisqStatTestResult;
43
+ /** Options for {@link moodTest}. */
44
+ export interface MoodTestOptions {
45
+ /** Alternative hypothesis (unambiguous abbreviations accepted). Default `"two.sided"`. */
46
+ alternative?: Alternative | "two" | "l" | "g";
47
+ }
48
+ /**
49
+ * Mood's two-sample test of scale. Equivalent to R's `mood.test()`. `NaN` values are dropped; ties get the average
50
+ * of the scores of their positions.
51
+ */
52
+ export declare function moodTest(x: Sample, y: Sample, options?: MoodTestOptions): HTest;