@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,105 @@
1
+ // Covariance and correlation of vectors and matrices: R's cov and cor with `method` and `use`.
2
+ //
3
+ // Written for rStats.js (no ported code). A matrix is an array of rows, its columns the variables. Pearson
4
+ // covariances and correlations are computed in double-double (src/internal/summary.ts, src/htest/cor.ts); Spearman's
5
+ // are Pearson's of the ranks (ranked after missing values are dropped as `use` says); Kendall's covariance is the
6
+ // sum of sign(x_i - x_j) sign(y_i - y_j) over all ordered pairs, as R reports it, and the correlation is tau-b.
7
+ // `use` follows R: "everything" (a missing value makes the entry NaN), "all.obs" (missing values are an error),
8
+ // "complete.obs" and "na.or.complete" (rows with any missing value are dropped) and "pairwise.complete.obs" (each
9
+ // pair of variables uses the rows where both are present).
10
+ import { kendall, pearsonDD } from "../htest/cor.js";
11
+ import { covariance } from "../internal/summary.js";
12
+ import { rank } from "./rank.js";
13
+ const METHODS = ["pearson", "kendall", "spearman"];
14
+ const USES = ["all.obs", "complete.obs", "pairwise.complete.obs", "everything", "na.or.complete"];
15
+ function match(v, all, dflt, what) {
16
+ if (v === undefined)
17
+ return dflt;
18
+ if (all.includes(v))
19
+ return v;
20
+ const hit = all.filter((a) => a.startsWith(v));
21
+ if (v.length === 0 || hit.length !== 1)
22
+ throw new RangeError(`invalid '${what}' argument`);
23
+ return hit[0];
24
+ }
25
+ function isMatrix(x) {
26
+ return x.length > 0 && Array.isArray(x[0]);
27
+ }
28
+ /** Columns of a matrix (array of rows), or the vector as one column */
29
+ function columns(x) {
30
+ if (!isMatrix(x))
31
+ return [Array.from(x)];
32
+ const ncol = x[0].length;
33
+ return Array.from({ length: ncol }, (_, j) => x.map((r) => r[j]));
34
+ }
35
+ function compute(x, y, options, correlation) {
36
+ const method = match(options.method, METHODS, "pearson", "method");
37
+ const use = match(options.use, USES, "everything", "use");
38
+ if (y === undefined && !isMatrix(x))
39
+ throw new TypeError("supply both 'x' and 'y' or a matrix-like 'x'");
40
+ const cx = columns(x);
41
+ const cy = y === undefined ? cx : columns(y);
42
+ const n = cx[0]?.length ?? 0;
43
+ if (cy.some((c) => c.length !== n))
44
+ throw new RangeError("incompatible dimensions");
45
+ const all = [...cx, ...cy];
46
+ if (use === "all.obs" && all.some((c) => c.some(Number.isNaN)))
47
+ throw new RangeError("missing observations in cov/cor");
48
+ if (!correlation && use === "pairwise.complete.obs" && method !== "pearson") {
49
+ throw new RangeError("cannot handle 'pairwise.complete.obs'");
50
+ }
51
+ let keepRows;
52
+ if (use === "complete.obs" || use === "na.or.complete") {
53
+ keepRows = Array.from({ length: n }, (_, i) => all.every((c) => !Number.isNaN(c[i])));
54
+ if (!keepRows.some(Boolean)) {
55
+ if (use === "complete.obs")
56
+ throw new RangeError("no complete element pairs");
57
+ return y === undefined || isMatrix(x) || isMatrix(y) ? cx.map(() => cy.map(() => NaN)) : NaN;
58
+ }
59
+ }
60
+ const prep = (c) => {
61
+ const v = keepRows === undefined ? c : c.filter((_, i) => keepRows[i]);
62
+ // ranks for Spearman, missing values kept in place (pairwise ranks are taken per pair below)
63
+ return method === "spearman" && use !== "pairwise.complete.obs" ? rank(v, { naLast: "keep" }) : v;
64
+ };
65
+ const px = cx.map(prep);
66
+ const py = y === undefined ? px : cy.map(prep);
67
+ const entry = (a, b) => {
68
+ let u = a;
69
+ let v = b;
70
+ if (use === "pairwise.complete.obs") {
71
+ const ok = a.map((t, i) => !Number.isNaN(t) && !Number.isNaN(b[i]));
72
+ u = a.filter((_, i) => ok[i]);
73
+ v = b.filter((_, i) => ok[i]);
74
+ if (method === "spearman") {
75
+ u = rank(u);
76
+ v = rank(v);
77
+ }
78
+ }
79
+ else if (u.some(Number.isNaN) || v.some(Number.isNaN))
80
+ return NaN;
81
+ if (method === "kendall") {
82
+ if (correlation)
83
+ return kendall(u, v).tau;
84
+ return 2 * kendall(u, v).s;
85
+ }
86
+ if (correlation)
87
+ return pearsonDD(u, v)[0];
88
+ return covariance(u, v);
89
+ };
90
+ const out = px.map((a) => py.map((b) => entry(a, b)));
91
+ // as in R, the correlation matrix of x alone has 1 on the diagonal, whatever the column holds
92
+ if (correlation && y === undefined && use !== "pairwise.complete.obs")
93
+ out.forEach((r, i) => (r[i] = 1));
94
+ return (y === undefined ? false : !isMatrix(x) && !isMatrix(y)) ? out[0][0] : out;
95
+ }
96
+ export function cov(x, y, options = {}) {
97
+ if (y !== undefined && !Array.isArray(y) && !ArrayBuffer.isView(y))
98
+ return compute(x, undefined, y, false);
99
+ return compute(x, y, options, false);
100
+ }
101
+ export function cor(x, y, options = {}) {
102
+ if (y !== undefined && !Array.isArray(y) && !ArrayBuffer.isView(y))
103
+ return compute(x, undefined, y, true);
104
+ return compute(x, y, options, true);
105
+ }
@@ -0,0 +1,64 @@
1
+ import type { Sample } from "../htest/types.js";
2
+ /** Options for {@link pretty}. */
3
+ export interface PrettyOptions {
4
+ /** Desired number of intervals. Default `5`. */
5
+ n?: number;
6
+ /** Minimal number of intervals. Default `n / 3` rounded down. */
7
+ minN?: number;
8
+ /** Factor applied to the cell when the range is very small. Default `0.75`. */
9
+ shrinkSml?: number;
10
+ /** Bias towards larger units. Default `1.5`. */
11
+ highUBias?: number;
12
+ /** Bias towards 5 over 2 times a power of 10. Default `0.5 + 1.5 * highUBias`. */
13
+ u5Bias?: number;
14
+ }
15
+ /**
16
+ * About n + 1 equally spaced "round" values (1, 2 or 5 times a power of 10) covering the range of `x`. Equivalent to
17
+ * R's `pretty()`.
18
+ *
19
+ * @example
20
+ * ```ts
21
+ * pretty([0.3, 9.6]); // [0, 2, 4, 6, 8, 10]
22
+ * ```
23
+ */
24
+ export declare function pretty(x: Sample, options?: PrettyOptions): number[];
25
+ /** Options for {@link hist}. */
26
+ export interface HistOptions {
27
+ /**
28
+ * Break points, a number of cells (a suggestion for pretty), or a rule: `"Sturges"` (default), `"Scott"` or
29
+ * `"FD"` (Freedman-Diaconis).
30
+ */
31
+ breaks?: readonly number[] | number | "Sturges" | "Scott" | "FD" | "Freedman-Diaconis";
32
+ /** Cells closed on the right, (a, b] (default `true`). */
33
+ right?: boolean;
34
+ /** Close the first cell on the left (the last on the right when `right` is false). Default `true`. */
35
+ includeLowest?: boolean;
36
+ }
37
+ /** Result of {@link hist}. */
38
+ export interface HistResult {
39
+ breaks: number[];
40
+ counts: number[];
41
+ /** counts / (n * cell width) */
42
+ density: number[];
43
+ mids: number[];
44
+ }
45
+ /**
46
+ * Histogram counts. Equivalent to R's `hist(x, plot = FALSE)`: break points (by default pretty points over the range
47
+ * with about Sturges' number of cells), counts, densities and cell midpoints. Non-finite values are dropped.
48
+ *
49
+ * @example
50
+ * ```ts
51
+ * hist([1, 2, 2, 3, 3, 3, 4]).counts; // [3, 3, 1] for breaks [1, 2, 3, 4]
52
+ * ```
53
+ */
54
+ export declare function hist(x: Sample, options?: HistOptions): HistResult;
55
+ /**
56
+ * Empirical distribution function of `x`: a function returning the fraction of values at or below its argument.
57
+ * Equivalent to R's `ecdf()`. `NaN` values are dropped.
58
+ *
59
+ * @example
60
+ * ```ts
61
+ * ecdf([3, 1, 2])(2); // 2/3
62
+ * ```
63
+ */
64
+ export declare function ecdf(x: Sample): (q: number) => number;
@@ -0,0 +1,304 @@
1
+ // Histogram counts and pretty break points: R's hist (with plot = FALSE), pretty and ecdf.
2
+ //
3
+ // Written for rStats.js to reproduce R's documented behaviour and output. pretty picks a unit of 1, 2, 5 or 10 times
4
+ // a power of ten close to range / n, preferring the larger units by the biases high.u.bias and u5.bias described in
5
+ // R's ?pretty, then extends to integer multiples of the unit that cover the data (with a 1e-10 relative rounding
6
+ // allowance) and keeps at least min.n cells; a range that is tiny relative to its magnitude gets a scale derived
7
+ // from the magnitude instead. hist chooses the number of cells by Sturges' (1926), Scott's (1979) or Freedman and
8
+ // Diaconis' (1981) rule, or as given, takes pretty break points over the data range, and counts after moving the
9
+ // breaks by 1e-7 times a typical cell width, so that values lying on a break up to rounding fall on the intended side.
10
+ // The order of floating-point operations follows R where the results are compared bit for bit.
11
+ import { median, range, sd } from "./basic.js";
12
+ import { IQR, quantile } from "./quantile.js";
13
+ import { seqLength } from "../internal/seq.js";
14
+ /**
15
+ * About n + 1 equally spaced "round" values (1, 2 or 5 times a power of 10) covering the range of `x`. Equivalent to
16
+ * R's `pretty()`.
17
+ *
18
+ * @example
19
+ * ```ts
20
+ * pretty([0.3, 9.6]); // [0, 2, 4, 6, 8, 10]
21
+ * ```
22
+ */
23
+ export function pretty(x, options = {}) {
24
+ const finite = Array.from(x).filter(Number.isFinite);
25
+ if (finite.length === 0)
26
+ return [];
27
+ const n = options.n ?? 5;
28
+ const minN = options.minN ?? Math.floor(n / 3);
29
+ const shrink = options.shrinkSml ?? 0.75;
30
+ const h = options.highUBias ?? 1.5;
31
+ const h5 = options.u5Bias ?? 0.5 + 1.5 * h;
32
+ let lo = Infinity;
33
+ let up = -Infinity;
34
+ for (const v of finite) {
35
+ if (v < lo)
36
+ lo = v;
37
+ if (v > up)
38
+ up = v;
39
+ }
40
+ const d = up - lo;
41
+ // Step 1: the scale (target cell width)
42
+ let c;
43
+ let small;
44
+ if (d === 0 && up === 0) {
45
+ c = 1;
46
+ small = true;
47
+ }
48
+ else {
49
+ c = Math.max(Math.abs(lo), Math.abs(up));
50
+ const t = h5 >= 1.5 * h + 0.5 ? 1 / (1 + h) : 1.5 / (1 + h5);
51
+ const bound = (1 + t) * Math.max(1, n) * Number.EPSILON;
52
+ small = d < c * bound * 3;
53
+ }
54
+ if (small) {
55
+ if (c > 10)
56
+ c = 9 + c / 10;
57
+ c *= shrink;
58
+ if (minN > 1)
59
+ c /= minN;
60
+ }
61
+ else {
62
+ c = n > 1 ? d / n : d;
63
+ if (d === Infinity && n >= 2)
64
+ c = up / n - lo / n;
65
+ }
66
+ const tiny = 2 ** -20 * DBL_MIN;
67
+ if (c < tiny)
68
+ c = tiny;
69
+ else if (c > Number.MAX_VALUE / 1.25)
70
+ c = Number.MAX_VALUE / 1.25;
71
+ // Step 2: the unit, walking up b, 2b, 5b, 10b while the larger unit is close enough
72
+ const b = 10 ** Math.floor(Math.log10(c));
73
+ const steps = [
74
+ [2 * b, h],
75
+ [5 * b, h5],
76
+ [10 * b, h],
77
+ ];
78
+ let u = b;
79
+ for (const [next, w] of steps) {
80
+ if (next - c < w * (c - u))
81
+ u = next;
82
+ else
83
+ break;
84
+ }
85
+ // Step 3: integer multiples of the unit covering the data
86
+ const rho = 1e-10;
87
+ let s = Math.floor(lo / u + rho);
88
+ while (s * u > lo + rho * u)
89
+ s--;
90
+ while (!Number.isFinite(s * u))
91
+ s++;
92
+ let t = Math.ceil(up / u - rho);
93
+ while (t * u < up - rho * u)
94
+ t++;
95
+ while (!Number.isFinite(t * u))
96
+ t--;
97
+ // Step 4: at least minN cells
98
+ let k = Math.trunc(0.5 + t - s);
99
+ if (k < minN) {
100
+ const e = minN - k;
101
+ const half = Math.floor(e / 2);
102
+ const odd = e % 2;
103
+ if (lo === 0 && s === 0 && up !== 0)
104
+ t += e;
105
+ else if (up === 0 && t === 0 && lo !== 0)
106
+ s -= e;
107
+ else if (s >= 0) {
108
+ t += half;
109
+ s -= half + odd;
110
+ }
111
+ else {
112
+ s -= half;
113
+ t += half + odd;
114
+ }
115
+ k = minN;
116
+ }
117
+ // Step 5: end points (kept at the data ends when the multiples are within the rounding allowance)
118
+ const L = s * u < lo ? s * u : lo;
119
+ const U = t * u > up ? t * u : up;
120
+ // Step 6: the sequence, with values that are zero up to rounding set to 0
121
+ if (k <= 0)
122
+ return [L];
123
+ let out;
124
+ if (Number.isFinite(U - L))
125
+ out = seqLength(L, U, k + 1);
126
+ else {
127
+ // U - L overflows: step by U/k - L/k instead
128
+ const step = U / k - L / k;
129
+ out = Array.from({ length: k + 1 }, (_, i) => L + i * step);
130
+ out[k] = U;
131
+ }
132
+ const delta = U / k - L / k;
133
+ return out.map((v) => (Math.abs(v) < 1e-14 * delta ? 0 : v));
134
+ }
135
+ /** Smallest positive normal double */
136
+ const DBL_MIN = 2.2250738585072014e-308;
137
+ /** Rounding to 5 significant decimal digits (0 and non-finite values unchanged) */
138
+ function round5(v) {
139
+ return v === 0 || !Number.isFinite(v) ? v : Number(v.toPrecision(5));
140
+ }
141
+ /** Suggested number of classes for the data `xs` (finite values) by the named rule */
142
+ function classCount(xs, rule) {
143
+ const n = xs.length;
144
+ const [lo, hi] = range(xs);
145
+ const r = hi - lo;
146
+ switch (rule.toLowerCase()) {
147
+ case "sturges":
148
+ return Math.ceil(Math.log2(n) + 1);
149
+ case "scott": {
150
+ const h = 3.5 * sd(xs) * n ** (-1 / 3);
151
+ return h > 0 ? Math.max(1, Math.ceil(r / h)) : 1;
152
+ }
153
+ case "fd":
154
+ case "freedman-diaconis": {
155
+ const xr = xs.map(round5);
156
+ let h = 2 * IQR(xr);
157
+ for (let alpha = 1 / 8; h === 0 && alpha >= 1 / 512; alpha /= 2) {
158
+ const [qa, qb] = quantile(xr, [alpha, 1 - alpha]);
159
+ h = (qb - qa) / (1 - 2 * alpha);
160
+ }
161
+ if (h === 0)
162
+ h = 3.5 * sd(xs);
163
+ return h > 0 ? Math.ceil((r / h) * n ** (1 / 3)) : 1;
164
+ }
165
+ default:
166
+ throw new RangeError("unknown 'breaks' algorithm");
167
+ }
168
+ }
169
+ /** Reference cell size for the counting fuzz: median width (> 5 breaks), data range (<= 3), smallest positive width */
170
+ function fuzzScale(br, dataRange) {
171
+ const widths = br.slice(1).map((v, i) => v - br[i]);
172
+ if (br.length > 5)
173
+ return median(widths);
174
+ if (br.length <= 3)
175
+ return dataRange;
176
+ return Math.min(...widths.filter((w) => w > 0));
177
+ }
178
+ /**
179
+ * Histogram counts. Equivalent to R's `hist(x, plot = FALSE)`: break points (by default pretty points over the range
180
+ * with about Sturges' number of cells), counts, densities and cell midpoints. Non-finite values are dropped.
181
+ *
182
+ * @example
183
+ * ```ts
184
+ * hist([1, 2, 2, 3, 3, 3, 4]).counts; // [3, 3, 1] for breaks [1, 2, 3, 4]
185
+ * ```
186
+ */
187
+ export function hist(x, options = {}) {
188
+ const xs = Array.from(x).filter(Number.isFinite);
189
+ const n = xs.length;
190
+ const right = options.right ?? true;
191
+ let includeLowest = options.includeLowest ?? true;
192
+ const spec = options.breaks ?? "Sturges";
193
+ let br;
194
+ if (typeof spec !== "string" && typeof spec !== "number" && spec.length > 1) {
195
+ br = [...spec].sort((a, b) => a - b);
196
+ }
197
+ else {
198
+ includeLowest = true;
199
+ let k = typeof spec === "string" ? classCount(xs, spec) : typeof spec === "number" ? spec : spec[0];
200
+ if (!Number.isFinite(k) || k < 1)
201
+ throw new RangeError("invalid number of 'breaks'");
202
+ k = Math.min(k, 1e6);
203
+ const [lo, hi] = range(xs);
204
+ br = pretty([lo, hi], { n: k, minN: 1 });
205
+ for (let i = 1; i < br.length; i++) {
206
+ if (!(br[i] - br[i - 1] > 0))
207
+ throw new RangeError("'breaks' are not strictly increasing");
208
+ }
209
+ }
210
+ const nb = br.length;
211
+ // Breaks moved by a small tolerance so that values on a break up to rounding fall on the intended side
212
+ const [dlo, dhi] = n > 0 ? range(xs) : [0, 0];
213
+ const tau = 1e-7 * fuzzScale(br, dhi - dlo);
214
+ const f = br.map((v, i) => {
215
+ if (right)
216
+ return i === 0 && includeLowest ? v - tau : v + tau;
217
+ if (i < nb - 1)
218
+ return v - tau;
219
+ return includeLowest ? v + tau : v - tau;
220
+ });
221
+ const counts = new Array(Math.max(0, nb - 1)).fill(0);
222
+ let total = 0;
223
+ for (const v of xs) {
224
+ const cell = cellOf(v, f, right, includeLowest);
225
+ if (cell >= 0) {
226
+ counts[cell]++;
227
+ total++;
228
+ }
229
+ }
230
+ if (total < n)
231
+ throw new RangeError("some 'x' not counted; maybe 'breaks' do not span range of 'x'");
232
+ const density = counts.map((c, i) => c / (n * (br[i + 1] - br[i])));
233
+ const mids = counts.map((_, i) => 0.5 * (br[i + 1] + br[i]));
234
+ return { breaks: br, counts, density, mids };
235
+ }
236
+ /** 0-based cell of `v` among the (shifted) breaks `f`, or -1 when it is in no cell */
237
+ function cellOf(v, f, right, includeLowest) {
238
+ const last = f.length - 1;
239
+ if (last < 1)
240
+ return -1;
241
+ if (right) {
242
+ if (v === f[0] && includeLowest)
243
+ return 0;
244
+ if (!(v > f[0] && v <= f[last]))
245
+ return -1;
246
+ // smallest j with v <= f[j]; the cell is j - 1
247
+ let a = 1;
248
+ let b = last;
249
+ while (a < b) {
250
+ const m = (a + b) >> 1;
251
+ if (v <= f[m])
252
+ b = m;
253
+ else
254
+ a = m + 1;
255
+ }
256
+ return a - 1;
257
+ }
258
+ if (v === f[last] && includeLowest)
259
+ return last - 1;
260
+ if (!(v >= f[0] && v < f[last]))
261
+ return -1;
262
+ // largest j with f[j] <= v; the cell is j
263
+ let a = 0;
264
+ let b = last - 1;
265
+ while (a < b) {
266
+ const m = (a + b + 1) >> 1;
267
+ if (f[m] <= v)
268
+ a = m;
269
+ else
270
+ b = m - 1;
271
+ }
272
+ return a;
273
+ }
274
+ /**
275
+ * Empirical distribution function of `x`: a function returning the fraction of values at or below its argument.
276
+ * Equivalent to R's `ecdf()`. `NaN` values are dropped.
277
+ *
278
+ * @example
279
+ * ```ts
280
+ * ecdf([3, 1, 2])(2); // 2/3
281
+ * ```
282
+ */
283
+ export function ecdf(x) {
284
+ const s = Array.from(x)
285
+ .filter((v) => !Number.isNaN(v))
286
+ .sort((a, b) => a - b);
287
+ const n = s.length;
288
+ if (n < 1)
289
+ throw new RangeError("'x' must have 1 or more non-missing values");
290
+ return (q) => {
291
+ if (Number.isNaN(q))
292
+ return NaN;
293
+ let lo = 0;
294
+ let hi = n;
295
+ while (lo < hi) {
296
+ const mid = (lo + hi) >> 1;
297
+ if (s[mid] <= q)
298
+ lo = mid + 1;
299
+ else
300
+ hi = mid;
301
+ }
302
+ return lo / n;
303
+ };
304
+ }
@@ -0,0 +1,63 @@
1
+ import { type NaRmOptions } from "./basic.js";
2
+ import type { Sample } from "../htest/types.js";
3
+ /** Options for {@link skewness} and {@link kurtosis}. */
4
+ export interface MomentOptions {
5
+ /** e1071's type: 1, 2 or 3. Default `3`. */
6
+ type?: 1 | 2 | 3;
7
+ }
8
+ /**
9
+ * Sample skewness. Equivalent to `e1071::skewness()` (missing values dropped); `{ type: 1 }` is jStat's skewness.
10
+ */
11
+ export declare function skewness(x: Sample, options?: MomentOptions): number;
12
+ /**
13
+ * Sample excess kurtosis. Equivalent to `e1071::kurtosis()` (missing values dropped); `{ type: 1 }` is jStat's
14
+ * kurtosis.
15
+ */
16
+ export declare function kurtosis(x: Sample, options?: MomentOptions): number;
17
+ /**
18
+ * Geometric mean exp(mean(log x)). Equivalent to `DescTools::Gmean()`: NaN with a negative value, 0 with a zero.
19
+ */
20
+ export declare function geometricMean(x: Sample, options?: NaRmOptions): number;
21
+ /**
22
+ * The most frequent values (all of them, in increasing order) and their frequency. Equivalent to `DescTools::Mode()`
23
+ * (missing values dropped): `value` is `[NaN]` and `frequency` NaN when every value occurs once.
24
+ */
25
+ export declare function modes(x: Sample): {
26
+ value: number[];
27
+ frequency: number;
28
+ };
29
+ /** Options for {@link meanAD}. */
30
+ export interface MeanADOptions {
31
+ /** Centre: the mean (default) or `"median"`, or a number. */
32
+ center?: "mean" | "median" | number;
33
+ }
34
+ /** Mean absolute deviation from the centre. Equivalent to `DescTools::MeanAD()` (missing values dropped). */
35
+ export declare function meanAD(x: Sample, options?: MeanADOptions): number;
36
+ /** Options for {@link cv}. */
37
+ export interface CvOptions extends NaRmOptions {
38
+ /** Small-sample correction. Default `false`. */
39
+ unbiased?: boolean;
40
+ }
41
+ /**
42
+ * Coefficient of variation sd / mean (sample standard deviation). Equivalent to `DescTools::CoefVar()`.
43
+ */
44
+ export declare function cv(x: Sample, options?: CvOptions): number;
45
+ /** Options for {@link scale}. */
46
+ export interface ScaleOptions {
47
+ /** Subtract the column means (`true`, default), nothing (`false`) or the given values. */
48
+ center?: boolean | readonly number[];
49
+ /**
50
+ * Divide by the root mean square of each (centred) column, sqrt(sum v^2 / (n - 1)) (`true`, default: the standard
51
+ * deviation when centred), by nothing (`false`) or by the given values.
52
+ */
53
+ scale?: boolean | readonly number[];
54
+ }
55
+ /**
56
+ * Centre and scale the columns of a matrix (array of rows; a vector is one column). Equivalent to R's `scale()`:
57
+ * `center` and `scale` report the values used (R's attributes), when any.
58
+ */
59
+ export declare function scale(x: Sample | readonly (readonly number[])[], options?: ScaleOptions): {
60
+ scaled: number[][];
61
+ center?: number[];
62
+ scale?: number[];
63
+ };
@@ -0,0 +1,158 @@
1
+ // Shape and location summaries that R's base lacks, with the definitions of the packages they are checked against:
2
+ // skewness and kurtosis (e1071, types 1 to 3), geometricMean, modes, meanAD and cv (DescTools' Gmean, Mode, MeanAD
3
+ // and CoefVar). Also scale (R's base).
4
+ //
5
+ // Written for rStats.js from the published definitions (no ported code). With m_k the central moments (divisor n),
6
+ // g1 = m3 / m2^1.5 and g2 = m4 / m2^2 - 3; e1071's type 1 is (g1, g2), type 2 the adjusted estimators of SAS and SPSS
7
+ // (Joanes and Gill, The Statistician 47, 1998), type 3 (the default, as in MINITAB and BMDP) b1 = g1 ((n-1)/n)^1.5 and
8
+ // b2 = (g2 + 3) ((n-1)/n)^2 - 3. The central moments are summed after subtracting the double-double mean.
9
+ import { values } from "./basic.js";
10
+ import { meanDD, mean as meanOf, variance } from "../internal/summary.js";
11
+ import { median } from "./basic.js";
12
+ /** Central sums sum (x - mean)^2, ^3, ^4 of values without NaN */
13
+ function centralSums(xs) {
14
+ const m = meanDD(xs);
15
+ let s2 = 0;
16
+ let s3 = 0;
17
+ let s4 = 0;
18
+ for (const v of xs) {
19
+ const d = v - m[0] - m[1];
20
+ const d2 = d * d;
21
+ s2 += d2;
22
+ s3 += d2 * d;
23
+ s4 += d2 * d2;
24
+ }
25
+ return [s2, s3, s4];
26
+ }
27
+ function checkType(type) {
28
+ if (![1, 2, 3].includes(type))
29
+ throw new RangeError("Invalid 'type' argument.");
30
+ }
31
+ /**
32
+ * Sample skewness. Equivalent to `e1071::skewness()` (missing values dropped); `{ type: 1 }` is jStat's skewness.
33
+ */
34
+ export function skewness(x, options = {}) {
35
+ const type = options.type ?? 3;
36
+ checkType(type);
37
+ const xs = values(x, true);
38
+ const n = xs.length;
39
+ const [s2, s3] = centralSums(xs);
40
+ const g1 = (Math.sqrt(n) * s3) / s2 ** 1.5;
41
+ if (type === 1)
42
+ return g1;
43
+ if (type === 2) {
44
+ if (n < 3)
45
+ throw new RangeError("Need at least 3 complete observations.");
46
+ return (g1 * Math.sqrt(n * (n - 1))) / (n - 2);
47
+ }
48
+ return g1 * (1 - 1 / n) ** 1.5;
49
+ }
50
+ /**
51
+ * Sample excess kurtosis. Equivalent to `e1071::kurtosis()` (missing values dropped); `{ type: 1 }` is jStat's
52
+ * kurtosis.
53
+ */
54
+ export function kurtosis(x, options = {}) {
55
+ const type = options.type ?? 3;
56
+ checkType(type);
57
+ const xs = values(x, true);
58
+ const n = xs.length;
59
+ const [s2, , s4] = centralSums(xs);
60
+ const r = (n * s4) / (s2 * s2);
61
+ if (type === 1)
62
+ return r - 3;
63
+ if (type === 2) {
64
+ if (n < 4)
65
+ throw new RangeError("Need at least 4 complete observations.");
66
+ return (((n + 1) * (r - 3) + 6) * (n - 1)) / ((n - 2) * (n - 3));
67
+ }
68
+ return r * (1 - 1 / n) ** 2 - 3;
69
+ }
70
+ /**
71
+ * Geometric mean exp(mean(log x)). Equivalent to `DescTools::Gmean()`: NaN with a negative value, 0 with a zero.
72
+ */
73
+ export function geometricMean(x, options = {}) {
74
+ const xs = values(x, options.naRm);
75
+ if (xs === undefined)
76
+ return NaN;
77
+ if (xs.some((v) => v < 0))
78
+ return NaN;
79
+ if (xs.includes(0))
80
+ return 0;
81
+ return Math.exp(meanOf(xs.map(Math.log)));
82
+ }
83
+ /**
84
+ * The most frequent values (all of them, in increasing order) and their frequency. Equivalent to `DescTools::Mode()`
85
+ * (missing values dropped): `value` is `[NaN]` and `frequency` NaN when every value occurs once.
86
+ */
87
+ export function modes(x) {
88
+ const xs = values(x, true);
89
+ const counts = new Map();
90
+ for (const v of xs)
91
+ counts.set(v === 0 ? 0 : v, (counts.get(v === 0 ? 0 : v) ?? 0) + 1);
92
+ let best = 0;
93
+ for (const c of counts.values())
94
+ best = Math.max(best, c);
95
+ if (best <= 1)
96
+ return { value: [NaN], frequency: NaN };
97
+ const value = [...counts.entries()].filter(([, c]) => c === best).map(([v]) => v);
98
+ return { value: value.sort((a, b) => a - b), frequency: best };
99
+ }
100
+ /** Mean absolute deviation from the centre. Equivalent to `DescTools::MeanAD()` (missing values dropped). */
101
+ export function meanAD(x, options = {}) {
102
+ const xs = values(x, true);
103
+ const c = options.center ?? "mean";
104
+ const center = c === "mean" ? meanOf(xs) : c === "median" ? median(xs) : c;
105
+ return meanOf(xs.map((v) => Math.abs(v - center)));
106
+ }
107
+ /**
108
+ * Coefficient of variation sd / mean (sample standard deviation). Equivalent to `DescTools::CoefVar()`.
109
+ */
110
+ export function cv(x, options = {}) {
111
+ const xs = values(x, options.naRm);
112
+ if (xs === undefined)
113
+ return NaN;
114
+ const n = xs.length;
115
+ let res = Math.sqrt(variance(xs)) / meanOf(xs);
116
+ if (options.unbiased)
117
+ res *= 1 - 1 / (4 * (n - 1)) + (res * res) / n + 1 / (2 * (n - 1) ** 2);
118
+ return res;
119
+ }
120
+ /**
121
+ * Centre and scale the columns of a matrix (array of rows; a vector is one column). Equivalent to R's `scale()`:
122
+ * `center` and `scale` report the values used (R's attributes), when any.
123
+ */
124
+ export function scale(x, options = {}) {
125
+ const rows = x.length > 0 && Array.isArray(x[0]) ? x.map((r) => [...r]) : Array.from(x, (v) => [v]);
126
+ const nc = rows[0]?.length ?? 0;
127
+ const col = (j) => rows.map((r) => r[j]);
128
+ const result = { scaled: rows };
129
+ const c = options.center ?? true;
130
+ if (c !== false) {
131
+ const center = c === true ? Array.from({ length: nc }, (_, j) => meanOf(col(j).filter((v) => !Number.isNaN(v)))) : [...c];
132
+ if (center.length !== nc)
133
+ throw new RangeError("length of 'center' must equal the number of columns of 'x'");
134
+ for (const r of rows)
135
+ for (let j = 0; j < nc; j++)
136
+ r[j] = r[j] - center[j];
137
+ result.center = center;
138
+ }
139
+ const s = options.scale ?? true;
140
+ if (s !== false) {
141
+ const sc = s === true
142
+ ? Array.from({ length: nc }, (_, j) => {
143
+ const v = col(j).filter((t) => !Number.isNaN(t));
144
+ let ss = 0;
145
+ for (const t of v)
146
+ ss += t * t;
147
+ return Math.sqrt(ss / Math.max(1, v.length - 1));
148
+ })
149
+ : [...s];
150
+ if (sc.length !== nc)
151
+ throw new RangeError("length of 'scale' must equal the number of columns of 'x'");
152
+ for (const r of rows)
153
+ for (let j = 0; j < nc; j++)
154
+ r[j] = r[j] / sc[j];
155
+ result.scale = sc;
156
+ }
157
+ return result;
158
+ }