@mark1russell7/rigor-statistics 0.0.0-stage → 0.1.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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Mark Russell
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,68 @@
1
- # Temporary Holding Version
1
+ # @mark1russell7/rigor-statistics
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ The statistics of measured work. It gives quantiles, bootstrap intervals, the sign test, the paired permutation test, Wilson intervals and the Holm correction. It also gives rank correlation, the comparison with a baseline beyond the noise, and the Brier score of predictions.
4
+
5
+ ```sh
6
+ npm install @mark1russell7/rigor-statistics
7
+ ```
8
+
9
+ ## Compare a candidate with a baseline
10
+
11
+ ```ts
12
+ import { compareRatios, noiseFloor } from "@mark1russell7/rigor-statistics";
13
+
14
+ // The ratio candidate / baseline of each input, and the measure of 5 seeds or folds of the same run.
15
+ const noise = noiseFloor(foldMedians); // 2 standard deviations; it needs 5 values or more
16
+ const c = compareRatios(ratios, { noise });
17
+
18
+ c.wins, c.losses, c.signP; // how often, by any amount
19
+ c.effect; // by how much: the median, p10 and p90 of the inputs that are not ties
20
+ c.gains, c.drops; // how often beyond the noise, with Wilson intervals
21
+ ```
22
+
23
+ A promotion reads `gains` against `drops` and `effect`. A sign test alone is not enough, because it counts a win of any size. Judge a change of a default against the current default, not against "no change".
24
+
25
+ ## The functions
26
+
27
+ | Function | What it gives |
28
+ | --- | --- |
29
+ | `mean`, `sd`, `median`, `summarize` | The descriptive statistics. `sd` is the sample standard deviation. |
30
+ | `quantile(values, p, method)`, `quantileSorted` | The `p` quantile, with the `"linear"` method (type 7, the default) or the `"nearest-rank"` method (type 1). |
31
+ | `bootstrap(data, statistic, options)` | A percentile bootstrap interval of any statistic. The options are `runs`, `level` or `ends`, `seed` and `method`. |
32
+ | `bootstrapMedian`, `bootstrapClusters` | The interval of the median, and a bootstrap that draws whole clusters (boards, seeds, sites). |
33
+ | `bootstrapTwo(a, b, statistic, options)` | A bootstrap interval of a statistic of two independent samples, for example two arms of a benchmark. Each run draws all of `a`, then all of `b`. |
34
+ | `bootstrapMedianRatio(numerator, denominator, options)` | The interval of `median(numerator) / median(denominator)`. It is the uncertainty of the ratio, not the spread of the samples. |
35
+ | `signTest(better, worse, sides)`, `signTestPower(n, level)` | The sign test, and the fewest wins out of `n` that the test calls significant. |
36
+ | `pairedPermutation(candidate, baseline, options)` | The paired permutation test (random sign flips). It uses the size of each difference. |
37
+ | `wilson(k, n, z)` | The Wilson interval of a proportion. |
38
+ | `holm(pValues, level)` | The Holm correction of a family of tests. |
39
+ | `spearman`, `kendallTau`, `ranks` | The rank correlations, with mean ranks for ties. |
40
+ | `compareRatios`, `noiseFloor` | The comparison with a baseline, and the noise floor of the 5-seed rule. |
41
+ | `brier`, `calibration` | The Brier score of predictions, and the hit rate of each stated level. |
42
+
43
+ Each function throws a RangeError for an empty list, for a NaN and for a parameter out of its range. Thus a missing value cannot make a quiet NaN in a record.
44
+
45
+ ## Moves from the copies
46
+
47
+ The tests prove that these functions give the same numbers as the copies in the engines, to the bit:
48
+
49
+ | The copy | The function |
50
+ | --- | --- |
51
+ | `quantile` of highways, packing and layout-contract (`quantileOf`) | `quantile(values, p)` |
52
+ | `quantile` of lag and lag-hud, `percentile` of lag (with `p / 100`) | `quantile(values, p, "nearest-rank")` |
53
+ | `median` of coeval, lag, crawler and packing | `median(values)` |
54
+ | `bootstrapMedian` of packing | `bootstrapMedian(sortedValues, { runs, level, seed })`. Give the values sorted, because packing draws from the sorted list. |
55
+ | `bootstrapMedian` of highways | `bootstrap(values, (s) => quantile(s, 0.5), { runs, level, seed })` |
56
+ | `bootstrap` of layout-contract | `bootstrapClusters(rows, (r) => r.cluster, statistic, { runs, seed, ends: [0.025, 0.975] })` |
57
+ | `signTestP` of packing | `signTest(better, worse)` |
58
+ | `signTestPower` of highways | `signTestPower(n, level)` |
59
+ | `wilson` of packing | `wilson(k, n, z)`. packing rounds the ends to 4 places at the call. |
60
+ | `spearman` of the highways backtest, `kendallTau` of highways | `spearman`, `kendallTau` |
61
+
62
+ A copy that gives NaN, 0 or `undefined` for an empty list must test the list at the call, because these functions throw.
63
+
64
+ `median(values)` gives the midpoint `(a + b) / 2` of the two middle values. `quantile(values, 0.5)` gives `a + 0.5 * (b - a)`. These two can differ in the last bit (at most 1 ulp). Use the function that the copy used.
65
+
66
+ ## License
67
+
68
+ MIT
@@ -0,0 +1,249 @@
1
+ /**
2
+ * The descriptive statistics: the mean, the standard deviation, the median, the quantiles and a summary of a list.
3
+ * Each function throws a RangeError for an empty list or for a value that is NaN. Thus a missing value cannot make a
4
+ * quiet NaN in a record.
5
+ */
6
+ /** The two definitions of a quantile that the engines use (Hyndman and Fan, 1996). */
7
+ type QuantileMethod = "linear" | "nearest-rank";
8
+ /** A new list with the values from the smallest to the largest. */
9
+ export declare function sorted(values: readonly number[]): number[];
10
+ export declare function mean(values: readonly number[]): number;
11
+ /** The sample standard deviation (the divisor is n - 1). It needs 2 values or more. */
12
+ export declare function sd(values: readonly number[]): number;
13
+ /**
14
+ * The `p` quantile of a list that is already sorted. Use it when you take several quantiles of one list.
15
+ * The linear method gives the same numbers as the copies in highways, packing and layout-contract. The
16
+ * nearest-rank method gives the same numbers as the copies in lag, lag-hud and the backtest of highways.
17
+ */
18
+ export declare function quantileSorted(sortedValues: readonly number[], p: number, method?: QuantileMethod): number;
19
+ /** The `p` quantile of a list. */
20
+ export declare function quantile(values: readonly number[], p: number, method?: QuantileMethod): number;
21
+ /**
22
+ * The median: the middle value, or the midpoint `(a + b) / 2` of the two middle values. The midpoint is correctly
23
+ * rounded. `quantile(values, 0.5)` computes `a + 0.5 * (b - a)`, which can differ from it in the last bit.
24
+ */
25
+ export declare function median(values: readonly number[]): number;
26
+ /** The summary of a list. The quartiles use the linear method. */
27
+ interface Summary {
28
+ readonly n: number;
29
+ readonly mean: number;
30
+ /** The sample standard deviation, or NaN for one value. */
31
+ readonly sd: number;
32
+ readonly min: number;
33
+ /** The 0.25 quantile. */
34
+ readonly q1: number;
35
+ readonly median: number;
36
+ /** The 0.75 quantile. */
37
+ readonly q3: number;
38
+ readonly max: number;
39
+ /** The interquartile range: q3 - q1. */
40
+ readonly iqr: number;
41
+ }
42
+ export declare function summarize(values: readonly number[]): Summary;
43
+ interface BootstrapOptions {
44
+ /** The number of samples. The default is 10 000. */
45
+ readonly runs?: number;
46
+ /** The level of the interval. The default is 0.95. */
47
+ readonly level?: number;
48
+ /** The seed of the draws. The default is 1. */
49
+ readonly seed?: number | string;
50
+ /** The quantile method of the two ends of the interval. The default is linear. */
51
+ readonly method?: QuantileMethod;
52
+ /**
53
+ * The quantiles of the two ends, in place of `level`, for example [0.025, 0.975] or an interval with one side.
54
+ * The default is from the level: (1 - level) / 2 and 1 - (1 - level) / 2. In floating point, (1 - 0.95) / 2 is
55
+ * not 0.025, so a copy that writes 0.025 needs this option to give the same numbers.
56
+ */
57
+ readonly ends?: readonly [number, number];
58
+ }
59
+ interface Interval {
60
+ /** The statistic of all the data. */
61
+ readonly estimate: number;
62
+ readonly low: number;
63
+ readonly high: number;
64
+ readonly level: number;
65
+ readonly runs: number;
66
+ /** The number of runs whose statistic is a finite number. Only these runs make the interval. */
67
+ readonly finite: number;
68
+ /** The seed as a 32-bit number. */
69
+ readonly seed: number;
70
+ }
71
+ /**
72
+ * A percentile bootstrap interval of `statistic` over `data`. A run whose statistic is not finite (NaN or an
73
+ * infinity) does not count. When no run is finite, low and high are equal to the estimate.
74
+ */
75
+ export declare function bootstrap<T>(data: readonly T[], statistic: (sample: readonly T[]) => number, options?: BootstrapOptions): Interval;
76
+ /**
77
+ * A percentile bootstrap interval of `statistic` over two independent samples `a` and `b`. Use it when the two
78
+ * samples are not paired, for example two arms of a benchmark. Each run draws `a.length` items of `a` with
79
+ * replacement, and then `b.length` items of `b` with replacement. All the draws come from one stream of
80
+ * `random(seed)`. The statistic reads the two drawn samples. The estimate is the statistic of all of `a` and `b`. A
81
+ * run whose statistic is not finite does not count. When no run is finite, low and high are equal to the estimate.
82
+ */
83
+ export declare function bootstrapTwo<A, B>(a: readonly A[], b: readonly B[], statistic: (a: readonly A[], b: readonly B[]) => number, options?: BootstrapOptions): Interval;
84
+ /**
85
+ * A bootstrap interval of the ratio `median(numerator) / median(denominator)` of two independent samples. The
86
+ * interval is the uncertainty of the ratio of the two medians. It is not the spread of the samples. A run whose
87
+ * denominator median is 0 is not finite, so it does not count.
88
+ */
89
+ export declare function bootstrapMedianRatio(numerator: readonly number[], denominator: readonly number[], options?: BootstrapOptions): Interval;
90
+ /** A bootstrap interval of the median. */
91
+ export declare function bootstrapMedian(values: readonly number[], options?: BootstrapOptions): Interval;
92
+ /**
93
+ * A cluster bootstrap: each run draws whole clusters with replacement, and the statistic reads the rows of the drawn
94
+ * clusters. Use it when the rows of one cluster (one board, one seed, one site) are not independent. The clusters
95
+ * are in the order of their first row.
96
+ */
97
+ export declare function bootstrapClusters<T>(rows: readonly T[], cluster: (row: T) => string, statistic: (rows: readonly T[]) => number, options?: BootstrapOptions): Interval;
98
+ /**
99
+ * The sign test of paired results: `better` inputs where the candidate wins and `worse` where it loses. The ties
100
+ * do not count. One-sided (the default), it is the p of `better` wins or more if the two are equal. Two-sided, it is
101
+ * two times the tail of the larger count, at most 1. The one-sided p is equal to `signTestP` of packing.
102
+ */
103
+ export declare function signTest(better: number, worse: number, sides?: 1 | 2): number;
104
+ /**
105
+ * The smallest number of wins out of `n` that the sign test calls significant at `level`. It is undefined when even
106
+ * n wins out of n are not enough. Use it before a run, to know how many inputs a claim needs.
107
+ */
108
+ export declare function signTestPower(n: number, level?: number, sides?: 1 | 2): number | undefined;
109
+ /** The z of a two-sided 95% interval. */
110
+ export declare const Z95 = 1.959963984540054;
111
+ /** An interval of a proportion. */
112
+ interface Proportion {
113
+ readonly low: number;
114
+ readonly high: number;
115
+ }
116
+ /**
117
+ * The Wilson score interval of `k` successes in `n` trials (Wilson, 1927). It keeps its level for a small n and for
118
+ * a proportion near 0 or 1, where the normal interval does not. For n = 0 the interval is 0 to 1.
119
+ */
120
+ export declare function wilson(k: number, n: number, z?: number): Proportion;
121
+ /** One test after the Holm correction. */
122
+ interface Corrected {
123
+ /** The p of the test. */
124
+ readonly p: number;
125
+ /** The p after the correction: compare it with the level of the whole family. */
126
+ readonly adjusted: number;
127
+ /** The adjusted p is less than the level. */
128
+ readonly reject: boolean;
129
+ }
130
+ /**
131
+ * The Holm step-down correction of a family of tests (Holm, 1979). It keeps the chance of one false claim or more
132
+ * in the family at most `level`. It has no condition on how the tests depend on each other. The result is in the
133
+ * order of the input.
134
+ */
135
+ export declare function holm(pValues: readonly number[], level?: number): Corrected[];
136
+ interface PermutationOptions {
137
+ /** The number of random sign flips. The default is 10 000. */
138
+ readonly runs?: number;
139
+ /** The default is 1. */
140
+ readonly seed?: number | string;
141
+ /** The statistic of the differences. The default is the mean. */
142
+ readonly statistic?: (differences: readonly number[]) => number;
143
+ }
144
+ interface PermutationResult {
145
+ /** The statistic of the observed differences `candidate[i] - baseline[i]`. */
146
+ readonly observed: number;
147
+ /** The two-sided p: (1 + the flips with a statistic as far from 0 or farther) / (1 + runs). */
148
+ readonly p: number;
149
+ readonly runs: number;
150
+ }
151
+ /**
152
+ * The paired permutation test (a random sign flip of each difference). It needs no normal distribution and uses
153
+ * the size of each difference, which the sign test does not. A pair with a difference of 0 adds nothing.
154
+ */
155
+ export declare function pairedPermutation(candidate: readonly number[], baseline: readonly number[], options?: PermutationOptions): PermutationResult;
156
+ /** The ranks of the values, from 1. Equal values get the mean of their ranks. */
157
+ export declare function ranks(values: readonly number[]): number[];
158
+ /**
159
+ * The Spearman rank correlation: the Pearson correlation of the ranks, with mean ranks for ties. The value is from
160
+ * -1 to 1. When one list has no spread, the value is 0. It is equal to the copy in the backtest of highways.
161
+ */
162
+ export declare function spearman(a: readonly number[], b: readonly number[]): number;
163
+ /** The largest number of values for which `kendallTau` examines each pair. Above it, a seeded sample of pairs. */
164
+ export declare const KENDALL_EXACT = 1500;
165
+ /**
166
+ * The Kendall tau-b of two lists (Kendall, 1938). The value is from -1 to 1. A pair with a tie in both lists does
167
+ * not count. When the lists have fewer than 2 values, or no pair counts, the value is 1. Above `KENDALL_EXACT`
168
+ * values, a seeded sample of 200 000 pairs gives an estimate. It is equal to the copy in highways.
169
+ */
170
+ export declare function kendallTau(a: readonly number[], b: readonly number[], seed?: number | string): number;
171
+ interface NoiseOptions {
172
+ /** The number of standard deviations. The default is 2. */
173
+ readonly k?: number;
174
+ /** The fewest values (seeds or folds) that give a floor. The default is 5, the 5-seed rule. */
175
+ readonly minimum?: number;
176
+ }
177
+ /**
178
+ * The noise floor of a measure: k sample standard deviations of its value over independent seeds or folds. A
179
+ * difference smaller than the floor is not a result.
180
+ */
181
+ export declare function noiseFloor(values: readonly number[], options?: NoiseOptions): number;
182
+ interface CompareOptions {
183
+ /** "higher" (the default): a ratio above 1 is a gain. "lower": a ratio below 1 is a gain, as for a time or a cost. */
184
+ readonly better?: "higher" | "lower";
185
+ /** A ratio at this distance from 1 or nearer is a tie. The default is 0: only a ratio of exactly 1 is a tie. */
186
+ readonly tie?: number;
187
+ /** A gain or a loss counts only beyond this distance from 1. The default is 0. Use noiseFloor. */
188
+ readonly noise?: number;
189
+ }
190
+ /** The effect on the inputs that are not ties. */
191
+ interface Effect {
192
+ readonly n: number;
193
+ readonly median: number;
194
+ /** The 0.1 quantile (linear). */
195
+ readonly p10: number;
196
+ /** The 0.9 quantile (linear). */
197
+ readonly p90: number;
198
+ }
199
+ interface Comparison {
200
+ /** The number of inputs. */
201
+ readonly n: number;
202
+ readonly ties: number;
203
+ /** The inputs where the candidate is better, by any amount. */
204
+ readonly wins: number;
205
+ /** The inputs where the candidate is worse, by any amount. */
206
+ readonly losses: number;
207
+ /** The one-sided sign test of wins against losses. */
208
+ readonly signP: number;
209
+ /** The ratios of the inputs that are not ties, or undefined when each input is a tie. */
210
+ readonly effect: Effect | undefined;
211
+ /** The inputs that gain beyond the noise. */
212
+ readonly gains: number;
213
+ /** The inputs that lose beyond the noise. */
214
+ readonly drops: number;
215
+ /** gains / n, with its Wilson interval. */
216
+ readonly gainShare: number;
217
+ readonly gainInterval: Proportion;
218
+ /** drops / n, with its Wilson interval. */
219
+ readonly dropShare: number;
220
+ readonly dropInterval: Proportion;
221
+ }
222
+ /**
223
+ * The comparison of a candidate with a baseline, from the ratio candidate / baseline of each input. It answers the
224
+ * three questions of a promotion. How often is the candidate better? By how much? How often is it worse beyond the
225
+ * noise? A promotion reads `gains` against `drops` and `effect`, not `signP` alone, because a sign test counts a
226
+ * win of any size.
227
+ */
228
+ export declare function compareRatios(ratios: readonly number[], options?: CompareOptions): Comparison;
229
+ /** A prediction: the stated probability that the claim holds, and if it held. */
230
+ interface Forecast {
231
+ readonly p: number;
232
+ readonly hit: boolean;
233
+ }
234
+ /** The Brier score: the mean of (p - outcome)^2. 0 is perfect, and 0.25 is a constant 0.5. Lower is better. */
235
+ export declare function brier(forecasts: readonly Forecast[]): number;
236
+ /** The predictions of one stated level. */
237
+ interface CalibrationLevel {
238
+ readonly stated: number;
239
+ readonly n: number;
240
+ readonly hits: number;
241
+ /** hits / n. A calibrated level has a rate near `stated`. */
242
+ readonly rate: number;
243
+ /** The Wilson interval of the rate. A level is overconfident when `stated` is above it. */
244
+ readonly interval: Proportion;
245
+ }
246
+ /** The hit rate of each stated level, from the lowest level. */
247
+ export declare function calibration(forecasts: readonly Forecast[]): CalibrationLevel[];
248
+ export type { BootstrapOptions, CalibrationLevel, CompareOptions, Comparison, Corrected, Effect, Forecast, Interval, NoiseOptions, PermutationOptions, PermutationResult, Proportion, QuantileMethod, Summary };
249
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../src/describe.ts","../src/bootstrap.ts","../src/tests.ts","../src/rank.ts","../src/compare.ts"],"mappings":";;;;;;KAOY;;wBAYI,OAAO;wBAIP,KAAK;;wBAQL,GAAG;;;;;;wBAcH,eAAe,iCAAiC,WAAW,SAAQ;;wBAgBnE,SAAS,2BAA2B,WAAW,SAAQ;;;;;wBASvD,OAAO;;UAQN;WACN;WACA;;WAEA;WACA;;WAEA;WACA;;WAEA;WACA;;WAEA;;wBAGK,UAAU,4BAA4B;UCrFrC;;WAEN;;WAEA;;WAEA;;WAEA,SAAS;;;;;;WAMT;;UAGM;;WAEN;WACA;WACA;WACA;WACA;;WAEA;;WAEA;;;;;;wBAOK,UAAU,GAAG,eAAe,KAAK,YAAY,iBAAiB,gBAAgB,UAAS,mBAAwB;;;;;;;;wBAsB/G,aAAa,GAAG,GAC9B,YAAY,KACZ,YAAY,KACZ,YAAY,YAAY,KAAK,YAAY,gBACzC,UAAS,mBACR;;;;;;wBAwBa,qBAAqB,8BAA8B,gCAAgC,UAAS,mBAAwB;;wBA+BpH,gBAAgB,2BAA2B,UAAS,mBAAwB;;;;;;wBAS5E,kBAAkB,GAChC,eAAe,KACf,UAAU,KAAK,cACf,YAAY,eAAe,gBAC3B,UAAS,mBACR;;;;;;wBCtGa,SAAS,gBAAgB,eAAe;;;;;wBAaxC,cAAc,WAAW,gBAAc;;qBAO1C;;UAGI;WACN;WACA;;;;;;wBAOK,OAAO,WAAW,WAAW,aAAkB;;UAc9C;;WAEN;;WAEA;;WAEA;;;;;;;wBAQK,KAAK,4BAA4B,iBAAe;UAa/C;;WAEN;;WAEA;;WAEA,aAAa;;UAGP;;WAEN;;WAEA;WACA;;;;;;wBAOK,kBAAkB,8BAA8B,6BAA6B,UAAS,qBAA0B;;wBCtHhH,MAAM;;;;;wBAgBN,SAAS,sBAAsB;;qBAmBlC;;;;;;wBAQG,WAAW,sBAAsB,sBAAsB;UC/CtD;;WAEN;;WAEA;;;;;;wBAOK,WAAW,2BAA2B,UAAS;UAM9C;;WAEN;;WAEA;;WAEA;;;UAIM;WACN;WACA;;WAEA;;WAEA;;UAGM;;WAEN;WACA;;WAEA;;WAEA;;WAEA;;WAEA,QAAQ;;WAER;;WAEA;;WAEA;WACA,cAAc;;WAEd;WACA,cAAc;;;;;;;;wBAST,cAAc,2BAA2B,UAAS,iBAAsB;;UA6CvE;WACN;WACA;;;wBASK,MAAM,oBAAoB;;UAQzB;WACN;WACA;WACA;;WAEA;;WAEA,UAAU;;;wBAIL,YAAY,oBAAoB,aAAa"}
package/build/index.js ADDED
@@ -0,0 +1,499 @@
1
+ import { random } from "@mark1russell7/rigor-generators";
2
+ // src/describe.ts
3
+ function checked(name, values) {
4
+ if (values.length === 0) throw new RangeError(`${name}: the list is empty.`);
5
+ for (const v of values) if (Number.isNaN(v)) throw new RangeError(`${name}: the list has a NaN.`);
6
+ }
7
+ /** A new list with the values from the smallest to the largest. */
8
+ function sorted(values) {
9
+ return [...values].sort((a, b) => a - b);
10
+ }
11
+ function mean(values) {
12
+ checked("mean", values);
13
+ let sum = 0;
14
+ for (const v of values) sum += v;
15
+ return sum / values.length;
16
+ }
17
+ /** The sample standard deviation (the divisor is n - 1). It needs 2 values or more. */
18
+ function sd(values) {
19
+ checked("sd", values);
20
+ if (values.length < 2) throw new RangeError("sd: the list needs 2 values or more.");
21
+ const m = mean(values);
22
+ let sum = 0;
23
+ for (const v of values) sum += (v - m) ** 2;
24
+ return Math.sqrt(sum / (values.length - 1));
25
+ }
26
+ /**
27
+ * The `p` quantile of a list that is already sorted. Use it when you take several quantiles of one list.
28
+ * The linear method gives the same numbers as the copies in highways, packing and layout-contract. The
29
+ * nearest-rank method gives the same numbers as the copies in lag, lag-hud and the backtest of highways.
30
+ */
31
+ function quantileSorted(sortedValues, p, method = "linear") {
32
+ checked("quantile", sortedValues);
33
+ if (!(p >= 0 && p <= 1)) throw new RangeError(`quantile: p is from 0 to 1, not ${p}.`);
34
+ const n = sortedValues.length;
35
+ if (method === "nearest-rank") return sortedValues[Math.min(n - 1, Math.max(0, Math.ceil(p * n) - 1))];
36
+ const at = (n - 1) * p;
37
+ const low = Math.floor(at);
38
+ const part = at - low;
39
+ const a = sortedValues[low];
40
+ if (part === 0) return a;
41
+ const b = sortedValues[low + 1];
42
+ if (a === b) return a;
43
+ return a + part * (b - a);
44
+ }
45
+ /** The `p` quantile of a list. */
46
+ function quantile(values, p, method = "linear") {
47
+ checked("quantile", values);
48
+ return quantileSorted(sorted(values), p, method);
49
+ }
50
+ /**
51
+ * The median: the middle value, or the midpoint `(a + b) / 2` of the two middle values. The midpoint is correctly
52
+ * rounded. `quantile(values, 0.5)` computes `a + 0.5 * (b - a)`, which can differ from it in the last bit.
53
+ */
54
+ function median(values) {
55
+ checked("median", values);
56
+ const s = sorted(values);
57
+ const middle = Math.floor(s.length / 2);
58
+ return s.length % 2 === 1 ? s[middle] : (s[middle - 1] + s[middle]) / 2;
59
+ }
60
+ function summarize(values) {
61
+ checked("summarize", values);
62
+ const s = sorted(values);
63
+ const q1 = quantileSorted(s, .25);
64
+ const q3 = quantileSorted(s, .75);
65
+ return {
66
+ n: s.length,
67
+ mean: mean(values),
68
+ sd: s.length < 2 ? NaN : sd(values),
69
+ min: s[0],
70
+ q1,
71
+ median: median(s),
72
+ q3,
73
+ max: s[s.length - 1],
74
+ iqr: q3 - q1
75
+ };
76
+ }
77
+ // src/bootstrap.ts
78
+ /**
79
+ * The percentile bootstrap. Each run draws n items with replacement (`below(n)` of rigor-generators, which is
80
+ * `floor(next() * n)`), and computes the statistic of the sample. The interval is two quantiles of the statistics.
81
+ * With the same seed, the draws are equal to the draws of the copies in highways, packing and layout-contract.
82
+ * `bootstrapTwo` draws two independent samples in each run: first the items of `a`, then the items of `b`.
83
+ */
84
+ /**
85
+ * A percentile bootstrap interval of `statistic` over `data`. A run whose statistic is not finite (NaN or an
86
+ * infinity) does not count. When no run is finite, low and high are equal to the estimate.
87
+ */
88
+ function bootstrap(data, statistic, options = {}) {
89
+ if (data.length === 0) throw new RangeError("bootstrap: the data is empty.");
90
+ const n = data.length;
91
+ const sample = new Array(n);
92
+ return percentile("bootstrap", options, (r) => {
93
+ for (let i = 0; i < n; i++) sample[i] = data[r.below(n)];
94
+ return statistic(sample);
95
+ }, () => statistic(data));
96
+ }
97
+ /**
98
+ * A percentile bootstrap interval of `statistic` over two independent samples `a` and `b`. Use it when the two
99
+ * samples are not paired, for example two arms of a benchmark. Each run draws `a.length` items of `a` with
100
+ * replacement, and then `b.length` items of `b` with replacement. All the draws come from one stream of
101
+ * `random(seed)`. The statistic reads the two drawn samples. The estimate is the statistic of all of `a` and `b`. A
102
+ * run whose statistic is not finite does not count. When no run is finite, low and high are equal to the estimate.
103
+ */
104
+ function bootstrapTwo(a, b, statistic, options = {}) {
105
+ if (a.length === 0) throw new RangeError("bootstrapTwo: the first sample is empty.");
106
+ if (b.length === 0) throw new RangeError("bootstrapTwo: the second sample is empty.");
107
+ const na = a.length;
108
+ const nb = b.length;
109
+ const sampleA = new Array(na);
110
+ const sampleB = new Array(nb);
111
+ return percentile("bootstrapTwo", options, (r) => {
112
+ for (let i = 0; i < na; i++) sampleA[i] = a[r.below(na)];
113
+ for (let i = 0; i < nb; i++) sampleB[i] = b[r.below(nb)];
114
+ return statistic(sampleA, sampleB);
115
+ }, () => statistic(a, b));
116
+ }
117
+ /**
118
+ * A bootstrap interval of the ratio `median(numerator) / median(denominator)` of two independent samples. The
119
+ * interval is the uncertainty of the ratio of the two medians. It is not the spread of the samples. A run whose
120
+ * denominator median is 0 is not finite, so it does not count.
121
+ */
122
+ function bootstrapMedianRatio(numerator, denominator, options = {}) {
123
+ return bootstrapTwo(numerator, denominator, (a, b) => median(a) / median(b), options);
124
+ }
125
+ /**
126
+ * The shared part of each percentile bootstrap. It examines the options, and starts one stream of `random(seed)`.
127
+ * Each run gives the stream to `run`, which draws the samples and returns their statistic. Then `all` gives the
128
+ * estimate. The order of the draws is the order in `run`.
129
+ */
130
+ function percentile(name, options, run, all) {
131
+ const runs = options.runs ?? 1e4;
132
+ if (!Number.isSafeInteger(runs) || runs < 1) throw new RangeError(`${name}: runs is a whole number of 1 or more, not ${runs}.`);
133
+ const tail = (1 - (options.level ?? .95)) / 2;
134
+ const [lowEnd, highEnd] = options.ends ?? [tail, 1 - tail];
135
+ if (!(lowEnd >= 0 && lowEnd < highEnd && highEnd <= 1)) throw new RangeError(`${name}: the ends are 0 <= low < high <= 1, not ${lowEnd} and ${highEnd}.`);
136
+ const level = options.ends ? highEnd - lowEnd : options.level ?? .95;
137
+ if (!(level > 0 && level < 1)) throw new RangeError(`${name}: level is more than 0 and less than 1, not ${level}.`);
138
+ const r = random(options.seed ?? 1);
139
+ const values = [];
140
+ for (let i = 0; i < runs; i++) {
141
+ const v = run(r);
142
+ if (Number.isFinite(v)) values.push(v);
143
+ }
144
+ const estimate = all();
145
+ if (values.length === 0) return {
146
+ estimate,
147
+ low: estimate,
148
+ high: estimate,
149
+ level,
150
+ runs,
151
+ finite: 0,
152
+ seed: r.seed
153
+ };
154
+ const s = sorted(values);
155
+ const method = options.method ?? "linear";
156
+ return {
157
+ estimate,
158
+ low: quantileSorted(s, lowEnd, method),
159
+ high: quantileSorted(s, highEnd, method),
160
+ level,
161
+ runs,
162
+ finite: s.length,
163
+ seed: r.seed
164
+ };
165
+ }
166
+ /** A bootstrap interval of the median. */
167
+ function bootstrapMedian(values, options = {}) {
168
+ return bootstrap(values, median, options);
169
+ }
170
+ /**
171
+ * A cluster bootstrap: each run draws whole clusters with replacement, and the statistic reads the rows of the drawn
172
+ * clusters. Use it when the rows of one cluster (one board, one seed, one site) are not independent. The clusters
173
+ * are in the order of their first row.
174
+ */
175
+ function bootstrapClusters(rows, cluster, statistic, options = {}) {
176
+ const groups = /* @__PURE__ */ new Map();
177
+ for (const row of rows) {
178
+ const key = cluster(row);
179
+ const list = groups.get(key);
180
+ if (list) list.push(row);
181
+ else groups.set(key, [row]);
182
+ }
183
+ const result = bootstrap([...groups.values()], (drawn) => statistic(drawn.flat()), options);
184
+ const estimate = statistic(rows);
185
+ return result.finite === 0 ? {
186
+ ...result,
187
+ estimate,
188
+ low: estimate,
189
+ high: estimate
190
+ } : {
191
+ ...result,
192
+ estimate
193
+ };
194
+ }
195
+ // src/tests.ts
196
+ /**
197
+ * The tests of a difference: the sign test, the paired permutation test, the Wilson interval of a proportion and
198
+ * the Holm correction for several tests.
199
+ */
200
+ function whole(name, value) {
201
+ if (!Number.isSafeInteger(value) || value < 0) throw new RangeError(`${name}: ${value} is not a whole number of 0 or more.`);
202
+ }
203
+ /** The log of each binomial term C(n, k) / 2^n, for each k from 0 to n. */
204
+ function halfBinomialLogs(n) {
205
+ const logs = [];
206
+ let logChoose = 0;
207
+ for (let i = 0; i <= n; i++) {
208
+ if (i > 0) logChoose += Math.log((n - i + 1) / i);
209
+ logs.push(logChoose - n * Math.LN2);
210
+ }
211
+ return logs;
212
+ }
213
+ /** P(X >= k) for X ~ Binomial(n, 1/2), summed from the largest term so that a small tail keeps its digits. */
214
+ function upperTail(k, n) {
215
+ if (k <= 0) return 1;
216
+ if (k > n) return 0;
217
+ const logs = halfBinomialLogs(n).slice(k);
218
+ const top = Math.max(...logs);
219
+ const sum = logs.reduce((s, l) => s + Math.exp(l - top), 0);
220
+ return Math.min(1, Math.exp(top) * sum);
221
+ }
222
+ /**
223
+ * The sign test of paired results: `better` inputs where the candidate wins and `worse` where it loses. The ties
224
+ * do not count. One-sided (the default), it is the p of `better` wins or more if the two are equal. Two-sided, it is
225
+ * two times the tail of the larger count, at most 1. The one-sided p is equal to `signTestP` of packing.
226
+ */
227
+ function signTest(better, worse, sides = 1) {
228
+ whole("signTest", better);
229
+ whole("signTest", worse);
230
+ const n = better + worse;
231
+ if (n === 0) return 1;
232
+ if (sides === 1) return upperTail(better, n);
233
+ return Math.min(1, 2 * upperTail(Math.max(better, worse), n));
234
+ }
235
+ /**
236
+ * The smallest number of wins out of `n` that the sign test calls significant at `level`. It is undefined when even
237
+ * n wins out of n are not enough. Use it before a run, to know how many inputs a claim needs.
238
+ */
239
+ function signTestPower(n, level = .05, sides = 2) {
240
+ whole("signTestPower", n);
241
+ for (let k = Math.ceil(n / 2); k <= n; k++) if (signTest(k, n - k, sides) < level) return k;
242
+ }
243
+ /** The z of a two-sided 95% interval. */
244
+ const Z95 = 1.959963984540054;
245
+ /**
246
+ * The Wilson score interval of `k` successes in `n` trials (Wilson, 1927). It keeps its level for a small n and for
247
+ * a proportion near 0 or 1, where the normal interval does not. For n = 0 the interval is 0 to 1.
248
+ */
249
+ function wilson(k, n, z = Z95) {
250
+ whole("wilson", k);
251
+ whole("wilson", n);
252
+ if (k > n) throw new RangeError(`wilson: k ${k} is more than n ${n}.`);
253
+ if (n === 0) return {
254
+ low: 0,
255
+ high: 1
256
+ };
257
+ const p = k / n;
258
+ const z2 = z * z;
259
+ const denominator = 1 + z2 / n;
260
+ const center = (p + z2 / (2 * n)) / denominator;
261
+ const half = z / denominator * Math.sqrt(p * (1 - p) / n + z2 / (4 * n * n));
262
+ return {
263
+ low: Math.max(0, center - half),
264
+ high: Math.min(1, center + half)
265
+ };
266
+ }
267
+ /**
268
+ * The Holm step-down correction of a family of tests (Holm, 1979). It keeps the chance of one false claim or more
269
+ * in the family at most `level`. It has no condition on how the tests depend on each other. The result is in the
270
+ * order of the input.
271
+ */
272
+ function holm(pValues, level = .05) {
273
+ for (const p of pValues) if (!(p >= 0 && p <= 1)) throw new RangeError(`holm: each p is from 0 to 1, not ${p}.`);
274
+ const m = pValues.length;
275
+ const order = pValues.map((p, i) => ({
276
+ p,
277
+ i
278
+ })).sort((a, b) => a.p - b.p || a.i - b.i);
279
+ const adjusted = new Array(m);
280
+ let running = 0;
281
+ order.forEach(({ p, i }, rank) => {
282
+ running = Math.max(running, Math.min(1, (m - rank) * p));
283
+ adjusted[i] = running;
284
+ });
285
+ return pValues.map((p, i) => ({
286
+ p,
287
+ adjusted: adjusted[i],
288
+ reject: adjusted[i] < level
289
+ }));
290
+ }
291
+ /**
292
+ * The paired permutation test (a random sign flip of each difference). It needs no normal distribution and uses
293
+ * the size of each difference, which the sign test does not. A pair with a difference of 0 adds nothing.
294
+ */
295
+ function pairedPermutation(candidate, baseline, options = {}) {
296
+ if (candidate.length !== baseline.length) throw new RangeError(`pairedPermutation: the lists have ${candidate.length} and ${baseline.length} values.`);
297
+ const differences = candidate.map((c, i) => c - baseline[i]);
298
+ const statistic = options.statistic ?? mean;
299
+ const runs = options.runs ?? 1e4;
300
+ if (!Number.isSafeInteger(runs) || runs < 1) throw new RangeError(`pairedPermutation: runs is a whole number of 1 or more, not ${runs}.`);
301
+ const observed = statistic(differences);
302
+ const r = random(options.seed ?? 1);
303
+ const flipped = new Array(differences.length);
304
+ let extreme = 0;
305
+ for (let run = 0; run < runs; run++) {
306
+ for (let i = 0; i < differences.length; i++) flipped[i] = r.next() < .5 ? -differences[i] : differences[i];
307
+ if (Math.abs(statistic(flipped)) >= Math.abs(observed) * (1 - 1e-12)) extreme++;
308
+ }
309
+ return {
310
+ observed,
311
+ p: (1 + extreme) / (1 + runs),
312
+ runs
313
+ };
314
+ }
315
+ // src/rank.ts
316
+ /**
317
+ * Rank correlation: do two measures put the same items in the same order? Use it to test that a cheap proxy (a
318
+ * predicted cost, a fast measure) orders the items as the real measure does.
319
+ */
320
+ function samePairs(name, a, b) {
321
+ if (a.length !== b.length) throw new RangeError(`${name}: the lists have ${a.length} and ${b.length} values.`);
322
+ for (const v of a) if (Number.isNaN(v)) throw new RangeError(`${name}: the first list has a NaN.`);
323
+ for (const v of b) if (Number.isNaN(v)) throw new RangeError(`${name}: the second list has a NaN.`);
324
+ }
325
+ /** The ranks of the values, from 1. Equal values get the mean of their ranks. */
326
+ function ranks(values) {
327
+ const order = values.map((x, i) => [x, i]).sort((p, q) => p[0] - q[0]);
328
+ const out = new Array(values.length);
329
+ for (let i = 0; i < order.length;) {
330
+ let j = i;
331
+ while (j + 1 < order.length && order[j + 1][0] === order[i][0]) j++;
332
+ for (let k = i; k <= j; k++) out[order[k][1]] = (i + j) / 2 + 1;
333
+ i = j + 1;
334
+ }
335
+ return out;
336
+ }
337
+ /**
338
+ * The Spearman rank correlation: the Pearson correlation of the ranks, with mean ranks for ties. The value is from
339
+ * -1 to 1. When one list has no spread, the value is 0. It is equal to the copy in the backtest of highways.
340
+ */
341
+ function spearman(a, b) {
342
+ samePairs("spearman", a, b);
343
+ const ra = ranks(a);
344
+ const rb = ranks(b);
345
+ const mean = (xs) => xs.reduce((s, x) => s + x, 0) / xs.length;
346
+ const ma = mean(ra);
347
+ const mb = mean(rb);
348
+ let num = 0;
349
+ let da = 0;
350
+ let db = 0;
351
+ for (let i = 0; i < ra.length; i++) {
352
+ num += (ra[i] - ma) * (rb[i] - mb);
353
+ da += (ra[i] - ma) ** 2;
354
+ db += (rb[i] - mb) ** 2;
355
+ }
356
+ return da === 0 || db === 0 ? 0 : num / Math.sqrt(da * db);
357
+ }
358
+ /** The largest number of values for which `kendallTau` examines each pair. Above it, a seeded sample of pairs. */
359
+ const KENDALL_EXACT = 1500;
360
+ const KENDALL_SAMPLE = 2e5;
361
+ /**
362
+ * The Kendall tau-b of two lists (Kendall, 1938). The value is from -1 to 1. A pair with a tie in both lists does
363
+ * not count. When the lists have fewer than 2 values, or no pair counts, the value is 1. Above `KENDALL_EXACT`
364
+ * values, a seeded sample of 200 000 pairs gives an estimate. It is equal to the copy in highways.
365
+ */
366
+ function kendallTau(a, b, seed = 1) {
367
+ samePairs("kendallTau", a, b);
368
+ const n = a.length;
369
+ if (n < 2) return 1;
370
+ let concordant = 0;
371
+ let discordant = 0;
372
+ let tieA = 0;
373
+ let tieB = 0;
374
+ const visit = (i, j) => {
375
+ const da = Math.sign(a[i] - a[j]);
376
+ const db = Math.sign(b[i] - b[j]);
377
+ if (da === 0 && db === 0) return;
378
+ if (da === 0) tieA++;
379
+ else if (db === 0) tieB++;
380
+ else if (da === db) concordant++;
381
+ else discordant++;
382
+ };
383
+ if (n <= 1500) for (let i = 0; i < n; i++) for (let j = i + 1; j < n; j++) visit(i, j);
384
+ else {
385
+ const r = random(seed);
386
+ for (let k = 0; k < KENDALL_SAMPLE; k++) {
387
+ const i = r.below(n);
388
+ const j = r.below(n);
389
+ if (i !== j) visit(i, j);
390
+ }
391
+ }
392
+ const den = Math.sqrt((concordant + discordant + tieA) * (concordant + discordant + tieB));
393
+ return den === 0 ? 1 : (concordant - discordant) / den;
394
+ }
395
+ // src/compare.ts
396
+ /**
397
+ * The comparison of a candidate with a baseline, and the calibration of predictions. These functions make two fleet
398
+ * rules into code. First, a change of a default is judged against the current default. The measure is the inputs that
399
+ * gain and the inputs that lose beyond the noise, with the effect size. A sign test alone is not enough. Second, each
400
+ * prediction states a probability, and the Brier score measures how good the stated probabilities were.
401
+ */
402
+ /**
403
+ * The noise floor of a measure: k sample standard deviations of its value over independent seeds or folds. A
404
+ * difference smaller than the floor is not a result.
405
+ */
406
+ function noiseFloor(values, options = {}) {
407
+ const minimum = options.minimum ?? 5;
408
+ if (values.length < minimum) throw new RangeError(`noiseFloor: ${values.length} values; the rule needs ${minimum} or more seeds or folds.`);
409
+ return (options.k ?? 2) * sd(values);
410
+ }
411
+ /**
412
+ * The comparison of a candidate with a baseline, from the ratio candidate / baseline of each input. It answers the
413
+ * three questions of a promotion. How often is the candidate better? By how much? How often is it worse beyond the
414
+ * noise? A promotion reads `gains` against `drops` and `effect`, not `signP` alone, because a sign test counts a
415
+ * win of any size.
416
+ */
417
+ function compareRatios(ratios, options = {}) {
418
+ if (ratios.length === 0) throw new RangeError("compareRatios: the list is empty.");
419
+ const sign = (options.better ?? "higher") === "higher" ? 1 : -1;
420
+ const tie = options.tie ?? 0;
421
+ const noise = options.noise ?? 0;
422
+ if (!(tie >= 0) || !(noise >= 0)) throw new RangeError("compareRatios: tie and noise are 0 or more.");
423
+ let ties = 0;
424
+ let wins = 0;
425
+ let losses = 0;
426
+ let gains = 0;
427
+ let drops = 0;
428
+ const changed = [];
429
+ for (const ratio of ratios) {
430
+ if (!Number.isFinite(ratio)) throw new RangeError(`compareRatios: each ratio is a finite number, not ${ratio}.`);
431
+ const d = sign * (ratio - 1);
432
+ if (Math.abs(ratio - 1) <= tie) {
433
+ ties++;
434
+ continue;
435
+ }
436
+ changed.push(ratio);
437
+ if (d > 0) wins++;
438
+ else losses++;
439
+ if (d > noise) gains++;
440
+ else if (-d > noise) drops++;
441
+ }
442
+ const n = ratios.length;
443
+ const s = sorted(changed);
444
+ const effect = s.length === 0 ? void 0 : {
445
+ n: s.length,
446
+ median: median(s),
447
+ p10: quantileSorted(s, .1),
448
+ p90: quantileSorted(s, .9)
449
+ };
450
+ return {
451
+ n,
452
+ ties,
453
+ wins,
454
+ losses,
455
+ signP: signTest(wins, losses),
456
+ effect,
457
+ gains,
458
+ drops,
459
+ gainShare: gains / n,
460
+ gainInterval: wilson(gains, n),
461
+ dropShare: drops / n,
462
+ dropInterval: wilson(drops, n)
463
+ };
464
+ }
465
+ function checkedForecasts(name, forecasts) {
466
+ if (forecasts.length === 0) throw new RangeError(`${name}: the list is empty.`);
467
+ for (const f of forecasts) if (!(f.p >= 0 && f.p <= 1)) throw new RangeError(`${name}: each p is from 0 to 1, not ${f.p}.`);
468
+ }
469
+ /** The Brier score: the mean of (p - outcome)^2. 0 is perfect, and 0.25 is a constant 0.5. Lower is better. */
470
+ function brier(forecasts) {
471
+ checkedForecasts("brier", forecasts);
472
+ let sum = 0;
473
+ for (const f of forecasts) sum += (f.p - (f.hit ? 1 : 0)) ** 2;
474
+ return sum / forecasts.length;
475
+ }
476
+ /** The hit rate of each stated level, from the lowest level. */
477
+ function calibration(forecasts) {
478
+ checkedForecasts("calibration", forecasts);
479
+ const levels = /* @__PURE__ */ new Map();
480
+ for (const f of forecasts) {
481
+ const level = levels.get(f.p) ?? {
482
+ n: 0,
483
+ hits: 0
484
+ };
485
+ level.n++;
486
+ if (f.hit) level.hits++;
487
+ levels.set(f.p, level);
488
+ }
489
+ return [...levels].sort((a, b) => a[0] - b[0]).map(([stated, { n, hits }]) => ({
490
+ stated,
491
+ n,
492
+ hits,
493
+ rate: hits / n,
494
+ interval: wilson(hits, n)
495
+ }));
496
+ }
497
+ export { KENDALL_EXACT, Z95, bootstrap, bootstrapClusters, bootstrapMedian, bootstrapMedianRatio, bootstrapTwo, brier, calibration, compareRatios, holm, kendallTau, mean, median, noiseFloor, pairedPermutation, quantile, quantileSorted, ranks, sd, signTest, signTestPower, sorted, spearman, summarize, wilson };
498
+
499
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/describe.ts","../src/bootstrap.ts","../src/tests.ts","../src/rank.ts","../src/compare.ts"],"sourcesContent":["/**\n * The descriptive statistics: the mean, the standard deviation, the median, the quantiles and a summary of a list.\n * Each function throws a RangeError for an empty list or for a value that is NaN. Thus a missing value cannot make a\n * quiet NaN in a record.\n */\n\n/** The two definitions of a quantile that the engines use (Hyndman and Fan, 1996). */\nexport type QuantileMethod =\n /** Type 7: a linear step between the two nearest ranks. The default of R and NumPy. */\n | \"linear\"\n /** Type 1: the smallest value with at least the share `p` of the values at or below it. */\n | \"nearest-rank\";\n\nfunction checked(name: string, values: readonly number[]): void {\n if (values.length === 0) throw new RangeError(`${name}: the list is empty.`);\n for (const v of values) if (Number.isNaN(v)) throw new RangeError(`${name}: the list has a NaN.`);\n}\n\n/** A new list with the values from the smallest to the largest. */\nexport function sorted(values: readonly number[]): number[] {\n return [...values].sort((a, b) => a - b);\n}\n\nexport function mean(values: readonly number[]): number {\n checked(\"mean\", values);\n let sum = 0;\n for (const v of values) sum += v;\n return sum / values.length;\n}\n\n/** The sample standard deviation (the divisor is n - 1). It needs 2 values or more. */\nexport function sd(values: readonly number[]): number {\n checked(\"sd\", values);\n if (values.length < 2) throw new RangeError(\"sd: the list needs 2 values or more.\");\n const m = mean(values);\n let sum = 0;\n for (const v of values) sum += (v - m) ** 2;\n return Math.sqrt(sum / (values.length - 1));\n}\n\n/**\n * The `p` quantile of a list that is already sorted. Use it when you take several quantiles of one list.\n * The linear method gives the same numbers as the copies in highways, packing and layout-contract. The\n * nearest-rank method gives the same numbers as the copies in lag, lag-hud and the backtest of highways.\n */\nexport function quantileSorted(sortedValues: readonly number[], p: number, method: QuantileMethod = \"linear\"): number {\n checked(\"quantile\", sortedValues);\n if (!(p >= 0 && p <= 1)) throw new RangeError(`quantile: p is from 0 to 1, not ${p}.`);\n const n = sortedValues.length;\n if (method === \"nearest-rank\") return sortedValues[Math.min(n - 1, Math.max(0, Math.ceil(p * n) - 1))] as number;\n const at = (n - 1) * p;\n const low = Math.floor(at);\n const part = at - low;\n const a = sortedValues[low] as number;\n if (part === 0) return a;\n const b = sortedValues[low + 1] as number;\n if (a === b) return a;\n return a + part * (b - a);\n}\n\n/** The `p` quantile of a list. */\nexport function quantile(values: readonly number[], p: number, method: QuantileMethod = \"linear\"): number {\n checked(\"quantile\", values);\n return quantileSorted(sorted(values), p, method);\n}\n\n/**\n * The median: the middle value, or the midpoint `(a + b) / 2` of the two middle values. The midpoint is correctly\n * rounded. `quantile(values, 0.5)` computes `a + 0.5 * (b - a)`, which can differ from it in the last bit.\n */\nexport function median(values: readonly number[]): number {\n checked(\"median\", values);\n const s = sorted(values);\n const middle = Math.floor(s.length / 2);\n return s.length % 2 === 1 ? (s[middle] as number) : ((s[middle - 1] as number) + (s[middle] as number)) / 2;\n}\n\n/** The summary of a list. The quartiles use the linear method. */\nexport interface Summary {\n readonly n: number;\n readonly mean: number;\n /** The sample standard deviation, or NaN for one value. */\n readonly sd: number;\n readonly min: number;\n /** The 0.25 quantile. */\n readonly q1: number;\n readonly median: number;\n /** The 0.75 quantile. */\n readonly q3: number;\n readonly max: number;\n /** The interquartile range: q3 - q1. */\n readonly iqr: number;\n}\n\nexport function summarize(values: readonly number[]): Summary {\n checked(\"summarize\", values);\n const s = sorted(values);\n const q1 = quantileSorted(s, 0.25);\n const q3 = quantileSorted(s, 0.75);\n return {\n n: s.length,\n // The mean and sd of the list in its own order, so that they are equal to mean(values) and sd(values).\n mean: mean(values),\n sd: s.length < 2 ? Number.NaN : sd(values),\n min: s[0] as number,\n q1,\n median: median(s),\n q3,\n max: s[s.length - 1] as number,\n iqr: q3 - q1,\n };\n}\n","/**\n * The percentile bootstrap. Each run draws n items with replacement (`below(n)` of rigor-generators, which is\n * `floor(next() * n)`), and computes the statistic of the sample. The interval is two quantiles of the statistics.\n * With the same seed, the draws are equal to the draws of the copies in highways, packing and layout-contract.\n * `bootstrapTwo` draws two independent samples in each run: first the items of `a`, then the items of `b`.\n */\nimport { type Random, random } from \"@mark1russell7/rigor-generators\";\nimport { type QuantileMethod, median, quantileSorted, sorted } from \"./describe.ts\";\n\nexport interface BootstrapOptions {\n /** The number of samples. The default is 10 000. */\n readonly runs?: number;\n /** The level of the interval. The default is 0.95. */\n readonly level?: number;\n /** The seed of the draws. The default is 1. */\n readonly seed?: number | string;\n /** The quantile method of the two ends of the interval. The default is linear. */\n readonly method?: QuantileMethod;\n /**\n * The quantiles of the two ends, in place of `level`, for example [0.025, 0.975] or an interval with one side.\n * The default is from the level: (1 - level) / 2 and 1 - (1 - level) / 2. In floating point, (1 - 0.95) / 2 is\n * not 0.025, so a copy that writes 0.025 needs this option to give the same numbers.\n */\n readonly ends?: readonly [number, number];\n}\n\nexport interface Interval {\n /** The statistic of all the data. */\n readonly estimate: number;\n readonly low: number;\n readonly high: number;\n readonly level: number;\n readonly runs: number;\n /** The number of runs whose statistic is a finite number. Only these runs make the interval. */\n readonly finite: number;\n /** The seed as a 32-bit number. */\n readonly seed: number;\n}\n\n/**\n * A percentile bootstrap interval of `statistic` over `data`. A run whose statistic is not finite (NaN or an\n * infinity) does not count. When no run is finite, low and high are equal to the estimate.\n */\nexport function bootstrap<T>(data: readonly T[], statistic: (sample: readonly T[]) => number, options: BootstrapOptions = {}): Interval {\n if (data.length === 0) throw new RangeError(\"bootstrap: the data is empty.\");\n const n = data.length;\n const sample = new Array<T>(n);\n return percentile(\n \"bootstrap\",\n options,\n (r) => {\n for (let i = 0; i < n; i++) sample[i] = data[r.below(n)] as T;\n return statistic(sample);\n },\n () => statistic(data),\n );\n}\n\n/**\n * A percentile bootstrap interval of `statistic` over two independent samples `a` and `b`. Use it when the two\n * samples are not paired, for example two arms of a benchmark. Each run draws `a.length` items of `a` with\n * replacement, and then `b.length` items of `b` with replacement. All the draws come from one stream of\n * `random(seed)`. The statistic reads the two drawn samples. The estimate is the statistic of all of `a` and `b`. A\n * run whose statistic is not finite does not count. When no run is finite, low and high are equal to the estimate.\n */\nexport function bootstrapTwo<A, B>(\n a: readonly A[],\n b: readonly B[],\n statistic: (a: readonly A[], b: readonly B[]) => number,\n options: BootstrapOptions = {},\n): Interval {\n if (a.length === 0) throw new RangeError(\"bootstrapTwo: the first sample is empty.\");\n if (b.length === 0) throw new RangeError(\"bootstrapTwo: the second sample is empty.\");\n const na = a.length;\n const nb = b.length;\n const sampleA = new Array<A>(na);\n const sampleB = new Array<B>(nb);\n return percentile(\n \"bootstrapTwo\",\n options,\n (r) => {\n for (let i = 0; i < na; i++) sampleA[i] = a[r.below(na)] as A;\n for (let i = 0; i < nb; i++) sampleB[i] = b[r.below(nb)] as B;\n return statistic(sampleA, sampleB);\n },\n () => statistic(a, b),\n );\n}\n\n/**\n * A bootstrap interval of the ratio `median(numerator) / median(denominator)` of two independent samples. The\n * interval is the uncertainty of the ratio of the two medians. It is not the spread of the samples. A run whose\n * denominator median is 0 is not finite, so it does not count.\n */\nexport function bootstrapMedianRatio(numerator: readonly number[], denominator: readonly number[], options: BootstrapOptions = {}): Interval {\n return bootstrapTwo(numerator, denominator, (a, b) => median(a) / median(b), options);\n}\n\n/**\n * The shared part of each percentile bootstrap. It examines the options, and starts one stream of `random(seed)`.\n * Each run gives the stream to `run`, which draws the samples and returns their statistic. Then `all` gives the\n * estimate. The order of the draws is the order in `run`.\n */\nfunction percentile(name: string, options: BootstrapOptions, run: (r: Random) => number, all: () => number): Interval {\n const runs = options.runs ?? 10_000;\n if (!Number.isSafeInteger(runs) || runs < 1) throw new RangeError(`${name}: runs is a whole number of 1 or more, not ${runs}.`);\n const tail = (1 - (options.level ?? 0.95)) / 2;\n const [lowEnd, highEnd] = options.ends ?? [tail, 1 - tail];\n if (!(lowEnd >= 0 && lowEnd < highEnd && highEnd <= 1)) throw new RangeError(`${name}: the ends are 0 <= low < high <= 1, not ${lowEnd} and ${highEnd}.`);\n const level = options.ends ? highEnd - lowEnd : (options.level ?? 0.95);\n if (!(level > 0 && level < 1)) throw new RangeError(`${name}: level is more than 0 and less than 1, not ${level}.`);\n const r = random(options.seed ?? 1);\n const values: number[] = [];\n for (let i = 0; i < runs; i++) {\n const v = run(r);\n if (Number.isFinite(v)) values.push(v);\n }\n const estimate = all();\n if (values.length === 0) return { estimate, low: estimate, high: estimate, level, runs, finite: 0, seed: r.seed };\n const s = sorted(values);\n const method = options.method ?? \"linear\";\n return { estimate, low: quantileSorted(s, lowEnd, method), high: quantileSorted(s, highEnd, method), level, runs, finite: s.length, seed: r.seed };\n}\n\n/** A bootstrap interval of the median. */\nexport function bootstrapMedian(values: readonly number[], options: BootstrapOptions = {}): Interval {\n return bootstrap(values, median, options);\n}\n\n/**\n * A cluster bootstrap: each run draws whole clusters with replacement, and the statistic reads the rows of the drawn\n * clusters. Use it when the rows of one cluster (one board, one seed, one site) are not independent. The clusters\n * are in the order of their first row.\n */\nexport function bootstrapClusters<T>(\n rows: readonly T[],\n cluster: (row: T) => string,\n statistic: (rows: readonly T[]) => number,\n options: BootstrapOptions = {},\n): Interval {\n const groups = new Map<string, T[]>();\n for (const row of rows) {\n const key = cluster(row);\n const list = groups.get(key);\n if (list) list.push(row);\n else groups.set(key, [row]);\n }\n const clusters = [...groups.values()];\n const result = bootstrap(clusters, (drawn) => statistic(drawn.flat()), options);\n // The estimate of all the rows in their own order, not in the order of the clusters.\n const estimate = statistic(rows);\n return result.finite === 0 ? { ...result, estimate, low: estimate, high: estimate } : { ...result, estimate };\n}\n","/**\n * The tests of a difference: the sign test, the paired permutation test, the Wilson interval of a proportion and\n * the Holm correction for several tests.\n */\nimport { random } from \"@mark1russell7/rigor-generators\";\nimport { mean } from \"./describe.ts\";\n\nfunction whole(name: string, value: number): void {\n if (!Number.isSafeInteger(value) || value < 0) throw new RangeError(`${name}: ${value} is not a whole number of 0 or more.`);\n}\n\n/** The log of each binomial term C(n, k) / 2^n, for each k from 0 to n. */\nfunction halfBinomialLogs(n: number): number[] {\n const logs: number[] = [];\n let logChoose = 0;\n for (let i = 0; i <= n; i++) {\n if (i > 0) logChoose += Math.log((n - i + 1) / i);\n logs.push(logChoose - n * Math.LN2);\n }\n return logs;\n}\n\n/** P(X >= k) for X ~ Binomial(n, 1/2), summed from the largest term so that a small tail keeps its digits. */\nfunction upperTail(k: number, n: number): number {\n if (k <= 0) return 1;\n if (k > n) return 0;\n const logs = halfBinomialLogs(n).slice(k);\n const top = Math.max(...logs);\n const sum = logs.reduce((s, l) => s + Math.exp(l - top), 0);\n return Math.min(1, Math.exp(top) * sum);\n}\n\n/**\n * The sign test of paired results: `better` inputs where the candidate wins and `worse` where it loses. The ties\n * do not count. One-sided (the default), it is the p of `better` wins or more if the two are equal. Two-sided, it is\n * two times the tail of the larger count, at most 1. The one-sided p is equal to `signTestP` of packing.\n */\nexport function signTest(better: number, worse: number, sides: 1 | 2 = 1): number {\n whole(\"signTest\", better);\n whole(\"signTest\", worse);\n const n = better + worse;\n if (n === 0) return 1;\n if (sides === 1) return upperTail(better, n);\n return Math.min(1, 2 * upperTail(Math.max(better, worse), n));\n}\n\n/**\n * The smallest number of wins out of `n` that the sign test calls significant at `level`. It is undefined when even\n * n wins out of n are not enough. Use it before a run, to know how many inputs a claim needs.\n */\nexport function signTestPower(n: number, level = 0.05, sides: 1 | 2 = 2): number | undefined {\n whole(\"signTestPower\", n);\n for (let k = Math.ceil(n / 2); k <= n; k++) if (signTest(k, n - k, sides) < level) return k;\n return undefined;\n}\n\n/** The z of a two-sided 95% interval. */\nexport const Z95 = 1.959963984540054;\n\n/** An interval of a proportion. */\nexport interface Proportion {\n readonly low: number;\n readonly high: number;\n}\n\n/**\n * The Wilson score interval of `k` successes in `n` trials (Wilson, 1927). It keeps its level for a small n and for\n * a proportion near 0 or 1, where the normal interval does not. For n = 0 the interval is 0 to 1.\n */\nexport function wilson(k: number, n: number, z: number = Z95): Proportion {\n whole(\"wilson\", k);\n whole(\"wilson\", n);\n if (k > n) throw new RangeError(`wilson: k ${k} is more than n ${n}.`);\n if (n === 0) return { low: 0, high: 1 };\n const p = k / n;\n const z2 = z * z;\n const denominator = 1 + z2 / n;\n const center = (p + z2 / (2 * n)) / denominator;\n const half = (z / denominator) * Math.sqrt((p * (1 - p)) / n + z2 / (4 * n * n));\n return { low: Math.max(0, center - half), high: Math.min(1, center + half) };\n}\n\n/** One test after the Holm correction. */\nexport interface Corrected {\n /** The p of the test. */\n readonly p: number;\n /** The p after the correction: compare it with the level of the whole family. */\n readonly adjusted: number;\n /** The adjusted p is less than the level. */\n readonly reject: boolean;\n}\n\n/**\n * The Holm step-down correction of a family of tests (Holm, 1979). It keeps the chance of one false claim or more\n * in the family at most `level`. It has no condition on how the tests depend on each other. The result is in the\n * order of the input.\n */\nexport function holm(pValues: readonly number[], level = 0.05): Corrected[] {\n for (const p of pValues) if (!(p >= 0 && p <= 1)) throw new RangeError(`holm: each p is from 0 to 1, not ${p}.`);\n const m = pValues.length;\n const order = pValues.map((p, i) => ({ p, i })).sort((a, b) => a.p - b.p || a.i - b.i);\n const adjusted = new Array<number>(m);\n let running = 0;\n order.forEach(({ p, i }, rank) => {\n running = Math.max(running, Math.min(1, (m - rank) * p));\n adjusted[i] = running;\n });\n return pValues.map((p, i) => ({ p, adjusted: adjusted[i] as number, reject: (adjusted[i] as number) < level }));\n}\n\nexport interface PermutationOptions {\n /** The number of random sign flips. The default is 10 000. */\n readonly runs?: number;\n /** The default is 1. */\n readonly seed?: number | string;\n /** The statistic of the differences. The default is the mean. */\n readonly statistic?: (differences: readonly number[]) => number;\n}\n\nexport interface PermutationResult {\n /** The statistic of the observed differences `candidate[i] - baseline[i]`. */\n readonly observed: number;\n /** The two-sided p: (1 + the flips with a statistic as far from 0 or farther) / (1 + runs). */\n readonly p: number;\n readonly runs: number;\n}\n\n/**\n * The paired permutation test (a random sign flip of each difference). It needs no normal distribution and uses\n * the size of each difference, which the sign test does not. A pair with a difference of 0 adds nothing.\n */\nexport function pairedPermutation(candidate: readonly number[], baseline: readonly number[], options: PermutationOptions = {}): PermutationResult {\n if (candidate.length !== baseline.length) throw new RangeError(`pairedPermutation: the lists have ${candidate.length} and ${baseline.length} values.`);\n const differences = candidate.map((c, i) => c - (baseline[i] as number));\n const statistic = options.statistic ?? mean;\n const runs = options.runs ?? 10_000;\n if (!Number.isSafeInteger(runs) || runs < 1) throw new RangeError(`pairedPermutation: runs is a whole number of 1 or more, not ${runs}.`);\n const observed = statistic(differences);\n const r = random(options.seed ?? 1);\n const flipped = new Array<number>(differences.length);\n let extreme = 0;\n for (let run = 0; run < runs; run++) {\n for (let i = 0; i < differences.length; i++) flipped[i] = r.next() < 0.5 ? -(differences[i] as number) : (differences[i] as number);\n // A small tolerance, so that a flip with the same statistic up to rounding counts as extreme.\n if (Math.abs(statistic(flipped)) >= Math.abs(observed) * (1 - 1e-12)) extreme++;\n }\n return { observed, p: (1 + extreme) / (1 + runs), runs };\n}\n","/**\n * Rank correlation: do two measures put the same items in the same order? Use it to test that a cheap proxy (a\n * predicted cost, a fast measure) orders the items as the real measure does.\n */\nimport { random } from \"@mark1russell7/rigor-generators\";\n\nfunction samePairs(name: string, a: readonly number[], b: readonly number[]): void {\n if (a.length !== b.length) throw new RangeError(`${name}: the lists have ${a.length} and ${b.length} values.`);\n for (const v of a) if (Number.isNaN(v)) throw new RangeError(`${name}: the first list has a NaN.`);\n for (const v of b) if (Number.isNaN(v)) throw new RangeError(`${name}: the second list has a NaN.`);\n}\n\n/** The ranks of the values, from 1. Equal values get the mean of their ranks. */\nexport function ranks(values: readonly number[]): number[] {\n const order = values.map((x, i) => [x, i] as const).sort((p, q) => p[0] - q[0]);\n const out = new Array<number>(values.length);\n for (let i = 0; i < order.length; ) {\n let j = i;\n while (j + 1 < order.length && (order[j + 1] as readonly [number, number])[0] === (order[i] as readonly [number, number])[0]) j++;\n for (let k = i; k <= j; k++) out[(order[k] as readonly [number, number])[1]] = (i + j) / 2 + 1;\n i = j + 1;\n }\n return out;\n}\n\n/**\n * The Spearman rank correlation: the Pearson correlation of the ranks, with mean ranks for ties. The value is from\n * -1 to 1. When one list has no spread, the value is 0. It is equal to the copy in the backtest of highways.\n */\nexport function spearman(a: readonly number[], b: readonly number[]): number {\n samePairs(\"spearman\", a, b);\n const ra = ranks(a);\n const rb = ranks(b);\n const mean = (xs: readonly number[]): number => xs.reduce((s, x) => s + x, 0) / xs.length;\n const ma = mean(ra);\n const mb = mean(rb);\n let num = 0;\n let da = 0;\n let db = 0;\n for (let i = 0; i < ra.length; i++) {\n num += ((ra[i] as number) - ma) * ((rb[i] as number) - mb);\n da += ((ra[i] as number) - ma) ** 2;\n db += ((rb[i] as number) - mb) ** 2;\n }\n return da === 0 || db === 0 ? 0 : num / Math.sqrt(da * db);\n}\n\n/** The largest number of values for which `kendallTau` examines each pair. Above it, a seeded sample of pairs. */\nexport const KENDALL_EXACT = 1500;\nconst KENDALL_SAMPLE = 200_000;\n\n/**\n * The Kendall tau-b of two lists (Kendall, 1938). The value is from -1 to 1. A pair with a tie in both lists does\n * not count. When the lists have fewer than 2 values, or no pair counts, the value is 1. Above `KENDALL_EXACT`\n * values, a seeded sample of 200 000 pairs gives an estimate. It is equal to the copy in highways.\n */\nexport function kendallTau(a: readonly number[], b: readonly number[], seed: number | string = 1): number {\n samePairs(\"kendallTau\", a, b);\n const n = a.length;\n if (n < 2) return 1;\n let concordant = 0;\n let discordant = 0;\n let tieA = 0;\n let tieB = 0;\n const visit = (i: number, j: number): void => {\n const da = Math.sign((a[i] as number) - (a[j] as number));\n const db = Math.sign((b[i] as number) - (b[j] as number));\n if (da === 0 && db === 0) return;\n if (da === 0) tieA++;\n else if (db === 0) tieB++;\n else if (da === db) concordant++;\n else discordant++;\n };\n if (n <= KENDALL_EXACT) {\n for (let i = 0; i < n; i++) for (let j = i + 1; j < n; j++) visit(i, j);\n } else {\n const r = random(seed);\n for (let k = 0; k < KENDALL_SAMPLE; k++) {\n const i = r.below(n);\n const j = r.below(n);\n if (i !== j) visit(i, j);\n }\n }\n const den = Math.sqrt((concordant + discordant + tieA) * (concordant + discordant + tieB));\n return den === 0 ? 1 : (concordant - discordant) / den;\n}\n","/**\n * The comparison of a candidate with a baseline, and the calibration of predictions. These functions make two fleet\n * rules into code. First, a change of a default is judged against the current default. The measure is the inputs that\n * gain and the inputs that lose beyond the noise, with the effect size. A sign test alone is not enough. Second, each\n * prediction states a probability, and the Brier score measures how good the stated probabilities were.\n */\nimport { median, quantileSorted, sd, sorted } from \"./describe.ts\";\nimport { type Proportion, signTest, wilson } from \"./tests.ts\";\n\nexport interface NoiseOptions {\n /** The number of standard deviations. The default is 2. */\n readonly k?: number;\n /** The fewest values (seeds or folds) that give a floor. The default is 5, the 5-seed rule. */\n readonly minimum?: number;\n}\n\n/**\n * The noise floor of a measure: k sample standard deviations of its value over independent seeds or folds. A\n * difference smaller than the floor is not a result.\n */\nexport function noiseFloor(values: readonly number[], options: NoiseOptions = {}): number {\n const minimum = options.minimum ?? 5;\n if (values.length < minimum) throw new RangeError(`noiseFloor: ${values.length} values; the rule needs ${minimum} or more seeds or folds.`);\n return (options.k ?? 2) * sd(values);\n}\n\nexport interface CompareOptions {\n /** \"higher\" (the default): a ratio above 1 is a gain. \"lower\": a ratio below 1 is a gain, as for a time or a cost. */\n readonly better?: \"higher\" | \"lower\";\n /** A ratio at this distance from 1 or nearer is a tie. The default is 0: only a ratio of exactly 1 is a tie. */\n readonly tie?: number;\n /** A gain or a loss counts only beyond this distance from 1. The default is 0. Use noiseFloor. */\n readonly noise?: number;\n}\n\n/** The effect on the inputs that are not ties. */\nexport interface Effect {\n readonly n: number;\n readonly median: number;\n /** The 0.1 quantile (linear). */\n readonly p10: number;\n /** The 0.9 quantile (linear). */\n readonly p90: number;\n}\n\nexport interface Comparison {\n /** The number of inputs. */\n readonly n: number;\n readonly ties: number;\n /** The inputs where the candidate is better, by any amount. */\n readonly wins: number;\n /** The inputs where the candidate is worse, by any amount. */\n readonly losses: number;\n /** The one-sided sign test of wins against losses. */\n readonly signP: number;\n /** The ratios of the inputs that are not ties, or undefined when each input is a tie. */\n readonly effect: Effect | undefined;\n /** The inputs that gain beyond the noise. */\n readonly gains: number;\n /** The inputs that lose beyond the noise. */\n readonly drops: number;\n /** gains / n, with its Wilson interval. */\n readonly gainShare: number;\n readonly gainInterval: Proportion;\n /** drops / n, with its Wilson interval. */\n readonly dropShare: number;\n readonly dropInterval: Proportion;\n}\n\n/**\n * The comparison of a candidate with a baseline, from the ratio candidate / baseline of each input. It answers the\n * three questions of a promotion. How often is the candidate better? By how much? How often is it worse beyond the\n * noise? A promotion reads `gains` against `drops` and `effect`, not `signP` alone, because a sign test counts a\n * win of any size.\n */\nexport function compareRatios(ratios: readonly number[], options: CompareOptions = {}): Comparison {\n if (ratios.length === 0) throw new RangeError(\"compareRatios: the list is empty.\");\n const sign = (options.better ?? \"higher\") === \"higher\" ? 1 : -1;\n const tie = options.tie ?? 0;\n const noise = options.noise ?? 0;\n if (!(tie >= 0) || !(noise >= 0)) throw new RangeError(\"compareRatios: tie and noise are 0 or more.\");\n let ties = 0;\n let wins = 0;\n let losses = 0;\n let gains = 0;\n let drops = 0;\n const changed: number[] = [];\n for (const ratio of ratios) {\n if (!Number.isFinite(ratio)) throw new RangeError(`compareRatios: each ratio is a finite number, not ${ratio}.`);\n const d = sign * (ratio - 1);\n if (Math.abs(ratio - 1) <= tie) {\n ties++;\n continue;\n }\n changed.push(ratio);\n if (d > 0) wins++;\n else losses++;\n if (d > noise) gains++;\n else if (-d > noise) drops++;\n }\n const n = ratios.length;\n const s = sorted(changed);\n const effect = s.length === 0 ? undefined : { n: s.length, median: median(s), p10: quantileSorted(s, 0.1), p90: quantileSorted(s, 0.9) };\n return {\n n,\n ties,\n wins,\n losses,\n signP: signTest(wins, losses),\n effect,\n gains,\n drops,\n gainShare: gains / n,\n gainInterval: wilson(gains, n),\n dropShare: drops / n,\n dropInterval: wilson(drops, n),\n };\n}\n\n/** A prediction: the stated probability that the claim holds, and if it held. */\nexport interface Forecast {\n readonly p: number;\n readonly hit: boolean;\n}\n\nfunction checkedForecasts(name: string, forecasts: readonly Forecast[]): void {\n if (forecasts.length === 0) throw new RangeError(`${name}: the list is empty.`);\n for (const f of forecasts) if (!(f.p >= 0 && f.p <= 1)) throw new RangeError(`${name}: each p is from 0 to 1, not ${f.p}.`);\n}\n\n/** The Brier score: the mean of (p - outcome)^2. 0 is perfect, and 0.25 is a constant 0.5. Lower is better. */\nexport function brier(forecasts: readonly Forecast[]): number {\n checkedForecasts(\"brier\", forecasts);\n let sum = 0;\n for (const f of forecasts) sum += (f.p - (f.hit ? 1 : 0)) ** 2;\n return sum / forecasts.length;\n}\n\n/** The predictions of one stated level. */\nexport interface CalibrationLevel {\n readonly stated: number;\n readonly n: number;\n readonly hits: number;\n /** hits / n. A calibrated level has a rate near `stated`. */\n readonly rate: number;\n /** The Wilson interval of the rate. A level is overconfident when `stated` is above it. */\n readonly interval: Proportion;\n}\n\n/** The hit rate of each stated level, from the lowest level. */\nexport function calibration(forecasts: readonly Forecast[]): CalibrationLevel[] {\n checkedForecasts(\"calibration\", forecasts);\n const levels = new Map<number, { n: number; hits: number }>();\n for (const f of forecasts) {\n const level = levels.get(f.p) ?? { n: 0, hits: 0 };\n level.n++;\n if (f.hit) level.hits++;\n levels.set(f.p, level);\n }\n return [...levels]\n .sort((a, b) => a[0] - b[0])\n .map(([stated, { n, hits }]) => ({ stated, n, hits, rate: hits / n, interval: wilson(hits, n) }));\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,68 @@
1
1
  {
2
+ "$schema": "https://json.schemastore.org/package",
2
3
  "name": "@mark1russell7/rigor-statistics",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
4
+ "version": "0.1.1",
5
+ "description": "The statistics of measured work: quantiles, bootstrap intervals, the sign test, the paired permutation test, Wilson intervals, the Holm correction, rank correlation, the comparison with a baseline beyond the noise, and the Brier score.",
6
+ "keywords": [
7
+ "statistics",
8
+ "bootstrap",
9
+ "quantile",
10
+ "median",
11
+ "sign-test",
12
+ "permutation-test",
13
+ "wilson",
14
+ "holm",
15
+ "spearman",
16
+ "kendall",
17
+ "brier",
18
+ "calibration",
19
+ "benchmark",
20
+ "effect-size"
21
+ ],
22
+ "homepage": "https://github.com/mark1russell7/rigor/tree/main/packages/statistics#readme",
23
+ "bugs": {
24
+ "url": "https://github.com/mark1russell7/rigor/issues"
25
+ },
26
+ "repository": {
27
+ "type": "git",
28
+ "url": "git+https://github.com/mark1russell7/rigor.git",
29
+ "directory": "packages/statistics"
30
+ },
31
+ "license": "MIT",
32
+ "author": "Mark Russell (https://github.com/mark1russell7)",
33
+ "type": "module",
34
+ "main": "./build/index.js",
35
+ "files": [
36
+ "build"
37
+ ],
38
+ "publishConfig": {
39
+ "access": "public"
40
+ },
41
+ "engines": {
42
+ "node": ">=22"
43
+ },
44
+ "dependencies": {
45
+ "@mark1russell7/rigor-generators": "^0.1.0"
46
+ },
47
+ "devDependencies": {
48
+ "@mark1russell7/cue": "^1.0.2",
49
+ "@types/node": "^26.6.5",
50
+ "tsdown": "^0.23.1",
51
+ "typescript": "^7.0.2",
52
+ "vitest": "^5.0.3"
53
+ },
54
+ "scripts": {
55
+ "build": "tsdown",
56
+ "test": "vitest run",
57
+ "test:watch": "vitest",
58
+ "typecheck": "tsc -p tsconfig.json && tsc -p tsconfig.test.json"
59
+ },
60
+ "types": "./build/index.d.ts",
61
+ "exports": {
62
+ ".": {
63
+ "types": "./build/index.d.ts",
64
+ "import": "./build/index.js"
65
+ },
66
+ "./package.json": "./package.json"
67
+ }
6
68
  }