evalaudit 0.2.1__tar.gz → 0.3.0__tar.gz

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 (33) hide show
  1. {evalaudit-0.2.1 → evalaudit-0.3.0}/.gitignore +2 -1
  2. evalaudit-0.3.0/CHANGELOG.md +255 -0
  3. {evalaudit-0.2.1 → evalaudit-0.3.0}/PKG-INFO +80 -31
  4. {evalaudit-0.2.1 → evalaudit-0.3.0}/README.md +78 -29
  5. {evalaudit-0.2.1 → evalaudit-0.3.0}/evalaudit/_types.py +73 -23
  6. {evalaudit-0.2.1 → evalaudit-0.3.0}/evalaudit/compare.py +6 -2
  7. {evalaudit-0.2.1 → evalaudit-0.3.0}/evalaudit/pairwise.py +236 -79
  8. evalaudit-0.3.0/evalaudit/scores.py +219 -0
  9. evalaudit-0.3.0/examples/pairwise_cluster_study.py +178 -0
  10. {evalaudit-0.2.1 → evalaudit-0.3.0}/pyproject.toml +2 -2
  11. {evalaudit-0.2.1 → evalaudit-0.3.0}/tests/test_agreement.py +1722 -1682
  12. {evalaudit-0.2.1 → evalaudit-0.3.0}/tests/test_compare.py +916 -802
  13. evalaudit-0.3.0/tests/test_confidence.py +120 -0
  14. {evalaudit-0.2.1 → evalaudit-0.3.0}/tests/test_judge.py +129 -9
  15. {evalaudit-0.2.1 → evalaudit-0.3.0}/tests/test_pairwise.py +780 -19
  16. evalaudit-0.3.0/tests/test_scores.py +436 -0
  17. evalaudit-0.2.1/AUDIT.md +0 -671
  18. evalaudit-0.2.1/evalaudit/scores.py +0 -136
  19. evalaudit-0.2.1/tests/test_scores.py +0 -181
  20. {evalaudit-0.2.1 → evalaudit-0.3.0}/.github/workflows/tests.yml +0 -0
  21. {evalaudit-0.2.1 → evalaudit-0.3.0}/LICENSE +0 -0
  22. {evalaudit-0.2.1 → evalaudit-0.3.0}/evalaudit/__init__.py +0 -0
  23. {evalaudit-0.2.1 → evalaudit-0.3.0}/evalaudit/agreement.py +0 -0
  24. {evalaudit-0.2.1 → evalaudit-0.3.0}/evalaudit/audit.py +0 -0
  25. {evalaudit-0.2.1 → evalaudit-0.3.0}/evalaudit/judge.py +0 -0
  26. {evalaudit-0.2.1 → evalaudit-0.3.0}/evalaudit/power.py +0 -0
  27. {evalaudit-0.2.1 → evalaudit-0.3.0}/examples/.gitkeep +0 -0
  28. {evalaudit-0.2.1 → evalaudit-0.3.0}/examples/interval_method_study.py +0 -0
  29. {evalaudit-0.2.1 → evalaudit-0.3.0}/tests/__init__.py +0 -0
  30. {evalaudit-0.2.1 → evalaudit-0.3.0}/tests/test_audit.py +0 -0
  31. {evalaudit-0.2.1 → evalaudit-0.3.0}/tests/test_power.py +0 -0
  32. {evalaudit-0.2.1 → evalaudit-0.3.0}/tests/test_readme.py +0 -0
  33. {evalaudit-0.2.1 → evalaudit-0.3.0}/tests/test_version.py +0 -0
@@ -16,4 +16,5 @@ htmlcov/
16
16
  .idea/
17
17
  data/raw/
18
18
  outputs/
19
- *.jsonl
19
+ *.jsonl/
20
+ AUDIT.md
@@ -0,0 +1,255 @@
1
+ # Changelog
2
+
3
+ Every change to evalaudit that a caller can see is recorded here. The format
4
+ follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and the
5
+ version numbers follow [semantic versioning](https://semver.org/). Before 1.0
6
+ a minor version can break things, and 0.3.0 does.
7
+
8
+ The entries for 0.2.1 and earlier were reconstructed from the git history
9
+ after the fact. There were no tags, so they record what the commits say and
10
+ not what was published on a given day.
11
+
12
+ ## [0.3.0] - Unreleased
13
+
14
+ 0.3.0 changes what `bradley_terry` reports on data you have already run, and
15
+ it turns several calls that used to return numbers into errors. Every
16
+ breaking change is under Changed, marked **Breaking**, with what to do about
17
+ it.
18
+
19
+ ### Changed
20
+
21
+ - **Breaking.** `bradley_terry` reads separability off the interval on the
22
+ gap between two ratings. Before, a pair was separable when its two rating
23
+ intervals did not overlap. Now a pair is separable when the percentile
24
+ interval on the difference between the two ratings excludes zero. Two
25
+ rating intervals can overlap while the gap between them is well measured,
26
+ because both ratings carry the error of the field average they are
27
+ measured against and that error cancels in the gap. So the old test
28
+ undercounted the pairs the data orders, and the pairs it left out were
29
+ never shown to be unordered. `n_separable` and `separable_pairs` change on
30
+ existing data, usually upward. On the README example they go from 5 of 15
31
+ pairs to 7. Nothing changes in your code. Rerun, and replace any
32
+ separability count you have published.
33
+
34
+ - **Breaking.** `separable_pairs` has two new columns, `ci_low` and
35
+ `ci_high`, the interval on `difference`. They sit between `difference` and
36
+ `p_a_beats_b`, so code that reads columns by position will read the wrong
37
+ ones. Code that selects columns by name is unaffected.
38
+
39
+ - **Breaking.** `bradley_terry` resamples items by default. Before, the
40
+ bootstrap drew single comparisons, which treats judgements of the same
41
+ prompt as independent. Now it draws item ids with replacement and takes
42
+ every comparison made on each drawn item. Where every item carries one
43
+ comparison the default draws the same resamples from the same seed as
44
+ before, and nothing moves. Where items carry several comparisons the
45
+ intervals widen and fewer pairs may separate. On simulated data shaped
46
+ like MT-Bench's human judgements, the old bootstrap covered 88% of the
47
+ time at a nominal 95% once prompts shifted model strength by a spread of
48
+ 0.5 on the log-odds scale, and 80% at a spread of 1.0.
49
+ `examples/pairwise_cluster_study.py` has the full table. To reproduce a
50
+ result from an earlier version exactly, pass `resample="comparisons"`.
51
+
52
+ - **Breaking.** `bradley_terry` raises `ValueError` in two cases that 0.2.1
53
+ accepted. Both apply only when `n_boot` is above zero and `resample` is
54
+ left at its default of `"items"`.
55
+
56
+ - Some row has no `item_id`. The item bootstrap has to know which item
57
+ each comparison belongs to.
58
+ - Every row has the same `item_id`. With one item, every resample is the
59
+ full data and every interval would have width zero.
60
+
61
+ This is the change most likely to break a working script, so here is what
62
+ to do. Earlier versions required `item_id` and read it for nothing but a
63
+ count, so a constant, a blank or the row number got past the check and
64
+ changed nothing. Look at what yours holds.
65
+
66
+ - It names the prompt each comparison was made on. Fill in any gaps and
67
+ change nothing else. Your intervals now account for comparisons sharing
68
+ a prompt, which is the reason for the change.
69
+ - It is the row number, or anything else unique per row. Nothing raises
70
+ and nothing moves.
71
+ - It is a constant or a blank. Record the prompt if you can. If you
72
+ cannot, pass `resample="comparisons"` to keep the intervals you had.
73
+ They treat every judgement as independent and run narrow wherever
74
+ prompts carry several, and the summary says so.
75
+
76
+ With `n_boot=0` no resampling happens and any `item_id` is accepted.
77
+
78
+ - **Breaking.** `score_ci(method="t")` raises `ValueError` on three inputs
79
+ it used to return numbers for. What to do depends on the input.
80
+
81
+ - 0/1 data, including all passes and all fails. The interval could run
82
+ past 0% or 100%, or come back with width zero. Use `method="wilson"`,
83
+ which `method="auto"` already picks for 0/1 data.
84
+ - A single score. It came back as NaN bounds behind two RuntimeWarnings.
85
+ One score has no spread, and no method here can put an interval around
86
+ it. Score more items.
87
+ - Continuous scores that are all the same. The interval came back with
88
+ width zero, which claims the observations fixed the mean exactly. Check
89
+ that the scorer is not returning a constant.
90
+
91
+ - **Breaking.** `score_ci(method="wilson")` raises `ValueError` on scores
92
+ that are not 0 or 1. Wilson counts passes, and on other scores it took the
93
+ count by truncating the sum of the scores. Ten scores of 0.99 printed a
94
+ mean of 0.990 beside an interval of 0.596 to 0.982. Use
95
+ `method="bootstrap"` for the interval on a mean, which `method="auto"`
96
+ already picks.
97
+
98
+ - **Breaking.** `score_ci`, `compare_paired` and `compare_independent`
99
+ raise `ValueError` for a confidence level that is not strictly between 0
100
+ and 1, with the message `confidence must be in (0, 1), got ...` that
101
+ `bradley_terry`, `rater_agreement`, `judge_validation` and `audit` already
102
+ used. Before, what came back at `confidence=1.5` depended on the method.
103
+ The bootstrap in all three raised from inside numpy. Wilson and both t
104
+ intervals returned NaN bounds. The two score intervals returned bounds
105
+ clipped to -100% and 100%. The signed-rank interval in `compare_paired`
106
+ returned an ordinary-looking interval under a "150% CI" label, which is
107
+ the case nothing would have flagged. Pass a level strictly between 0 and
108
+ 1.
109
+
110
+ - **Breaking.** Several summary strings are reworded, so any test or
111
+ pipeline that matches on them will fail.
112
+
113
+ - `BTResult.summary()` defines separable as "the interval on the gap
114
+ between the two ratings excludes zero". For pairs that do not separate
115
+ it says the data does not establish an order for them, that this does
116
+ not mean the models are level, and that more comparisons could separate
117
+ them. The phrases "meaning their intervals do not overlap", "cannot be
118
+ ordered", "an order the data does not carry" and "a ranking of noise"
119
+ are gone.
120
+ - `BTResult.summary()` adds a sentence when `resample="comparisons"` was
121
+ used on data where items carry several comparisons.
122
+ - `AgreementResult.summary()` for a scale nobody varied now ends "This
123
+ comes from a scale nobody varied. It is not perfect agreement." The old
124
+ ending was "That is not perfect agreement, it is a scale nobody varied."
125
+ - `JudgeValidation.summary()` for a rubric with one label now reads "This
126
+ comes from a rubric with one label in it. It is not perfect agreement."
127
+ The old sentence was "That is not perfect agreement, it is a rubric with
128
+ one label in it."
129
+
130
+ - The Development Status classifier moved from Alpha to Beta.
131
+
132
+ ### Added
133
+
134
+ - `bradley_terry(..., resample="items")`, with `"comparisons"` as the other
135
+ choice.
136
+ - `BTResult.pairs`, one row per pair of models with the gap, the interval on
137
+ the gap and a `separable` column. `separable_pairs` is its separable rows.
138
+ - `BTResult.resample`, recording which unit the bootstrap drew. It and
139
+ `pairs` are the last two fields of the dataclass, so building a
140
+ `BTResult` by position still works.
141
+ - `to_elo` stretches the gap intervals with the gaps and carries `pairs` and
142
+ `resample` across.
143
+ - `examples/pairwise_cluster_study.py`, which measures how narrow the
144
+ comparison bootstrap runs on MT-Bench shaped data.
145
+
146
+ ### Fixed
147
+
148
+ - Four inputs to `score_ci` and the confidence level in three functions used
149
+ to produce impossible or self-contradicting numbers. They now raise, and
150
+ they are listed under Changed because each one turns a call that returned
151
+ into an error.
152
+ - The `scores` module docstring said the interval at n=50 is roughly plus or
153
+ minus 10 points. It is about 10 at an 85% pass rate and about 13 at 50%.
154
+ - The README Coverage section said the Student-t interval in `score_ci` and
155
+ Welch's interval in `compare_independent` had nothing pinning their bounds.
156
+ Tests matching them against scipy and statsmodels had already landed. It
157
+ now counts fifteen intervals, nine tested for coverage, five pinned
158
+ against a reference and one with nothing pinning its bounds.
159
+ - The README corrected two earlier claims, about simulation coverage and
160
+ about how many checks `audit` runs.
161
+
162
+ ### Removed
163
+
164
+ - The non-overlap definition of separability. There is no option to bring
165
+ it back, because the summary built on it made a claim its own docstring
166
+ denied.
167
+ - `AUDIT.md`, an internal review of the package, is no longer tracked, so
168
+ it is not in the source distribution.
169
+
170
+ ## [0.2.1] - 2026-09-08
171
+
172
+ Reconstructed from the git history after the fact.
173
+
174
+ ### Added
175
+
176
+ - Nothing.
177
+
178
+ ### Changed
179
+
180
+ - `__version__` is read from the installed package metadata. 0.2.0 reported
181
+ `__version__` as 0.1.0, because a literal in `__init__.py` had drifted
182
+ from `pyproject.toml`.
183
+
184
+ ### Fixed
185
+
186
+ - The length-bias check reported a preference for long answers whatever
187
+ the sign of the fit. The `audit` finding for length now takes its title,
188
+ lead, action and verdict from the fit that decides, with its own lead for
189
+ when the two fits point opposite ways.
190
+
191
+ ### Removed
192
+
193
+ - Nothing.
194
+
195
+ ## [0.2.0] - 2026-09-07
196
+
197
+ Reconstructed from the git history after the fact.
198
+
199
+ ### Added
200
+
201
+ - The `pairwise` module, with `bradley_terry`, `to_elo` and `BTResult`.
202
+ Ratings come with bootstrap intervals and a count of separable pairs.
203
+ Models that fall into groups that never met, and models that never lost
204
+ or never won, get no ratings and a summary naming them. Ties are split or
205
+ dropped, and `ties="davidson"` is refused by name.
206
+ - The README code blocks are run by the test suite and checked against the
207
+ output they show.
208
+
209
+ ### Changed
210
+
211
+ - The project URLs point at github.com/mokayahezron/evalaudit.
212
+ - `choix` is a development dependency on Python 3.10 and later, used to
213
+ check the Bradley-Terry fit.
214
+
215
+ ### Fixed
216
+
217
+ - Nothing recorded.
218
+
219
+ ### Removed
220
+
221
+ - Nothing.
222
+
223
+ ## [0.1.x] - 2026-09-04 to 2026-09-07
224
+
225
+ Reconstructed from the git history after the fact. `pyproject.toml` read
226
+ 0.1.0 at the first commit, 0.0.1 from the third, and 0.1.0 again from a
227
+ README update made after the audit module landed. Which of these were
228
+ published is not recorded.
229
+
230
+ ### Added
231
+
232
+ - `score_ci`, with Wilson, bootstrap and Student-t intervals.
233
+ - `compare_paired` and `compare_independent`. McNemar with Tango's score
234
+ interval for paired binary data, the paired bootstrap, and the signed-rank
235
+ test with the Hodges-Lehmann interval. The score interval, the bootstrap
236
+ and Welch's t for independent samples.
237
+ - `rater_agreement`, Krippendorff's alpha with a bootstrap interval, an item
238
+ disagreement table and a rater dropout table. `cohens_kappa` and
239
+ `fleiss_kappa`.
240
+ - `judge_validation`, `position_bias` and `length_bias`.
241
+ - `detectable_effect` and `min_sample_size`.
242
+ - `audit`, which runs every check the supplied data supports and ranks the
243
+ findings by severity.
244
+
245
+ ### Changed
246
+
247
+ - Nothing recorded.
248
+
249
+ ### Fixed
250
+
251
+ - Nothing recorded.
252
+
253
+ ### Removed
254
+
255
+ - Nothing.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: evalaudit
3
- Version: 0.2.1
3
+ Version: 0.3.0
4
4
  Summary: Statistical validity checks for human-graded AI evaluations. Rater agreement, judge validation, and whether your result holds up.
5
5
  Project-URL: Homepage, https://github.com/mokayahezron/evalaudit
6
6
  Project-URL: Issues, https://github.com/mokayahezron/evalaudit/issues
@@ -8,7 +8,7 @@ Author-email: Hezron Mokaya <ondibahezron@gmail.com>
8
8
  License: MIT
9
9
  License-File: LICENSE
10
10
  Keywords: annotation-quality,bradley-terry,evaluation,inter-rater-reliability,krippendorff,llm,llm-as-judge,statistics
11
- Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Development Status :: 4 - Beta
12
12
  Classifier: Intended Audience :: Science/Research
13
13
  Classifier: License :: OSI Approved :: MIT License
14
14
  Classifier: Programming Language :: Python :: 3
@@ -163,9 +163,11 @@ Two rules the library follows throughout:
163
163
  | `compare` | Does the margin between two systems survive a paired test? |
164
164
  | `audit` | Everything except `pairwise`, as a report ranked by what changes the conclusion. |
165
165
 
166
- `audit` is the deliverable. It runs five of these checks and orders what it
167
- finds by what changes the conclusion. Each of those five also stands alone
168
- when you want a single number instead of a report.
166
+ `audit` is the deliverable. It runs seven checks drawn from five of these
167
+ modules and orders what it finds by what changes the conclusion. `judge`
168
+ supplies three of the seven, since judge-human agreement, position bias and
169
+ length bias are separate findings. Each of those five modules also stands
170
+ alone when you want a single number instead of a report.
169
171
 
170
172
  `pairwise` runs on its own. A leaderboard is a different question from a
171
173
  single eval and arrives as a different frame, one row per comparison rather
@@ -178,18 +180,10 @@ Scores, compare and power are the foundation those three stand on.
178
180
 
179
181
  ## Status
180
182
 
181
- All seven modules are implemented. The examples below are run in CI, so what
182
- they print here is what they print for you.
183
-
184
- | Module | State |
185
- |---|---|
186
- | `scores` | Implemented |
187
- | `compare` | Implemented |
188
- | `agreement` | Implemented |
189
- | `judge` | Implemented |
190
- | `power` | Implemented |
191
- | `audit` | Implemented |
192
- | `pairwise` | Implemented |
183
+ All seven modules are implemented. CI runs the suite on Python 3.9, 3.11 and
184
+ 3.12, including the checks that pin results against outside implementations.
185
+ The examples below are run there too, so what they print here is what they
186
+ print for you.
193
187
 
194
188
  ### What `scores` answers
195
189
 
@@ -255,17 +249,32 @@ print(bradley_terry(comparisons, seed=0).summary())
255
249
  ```
256
250
 
257
251
  ```
258
- Bradley-Terry ratings for 6 models from 600 comparisons (ties split). 5 of
259
- 15 pairs are separable at 95%, meaning their intervals do not overlap. The
260
- other 10 pairs cannot be ordered [...] Ratings are anchored on m1 at 0.
261
- Only differences between models mean anything [...]
252
+ Bradley-Terry ratings for 6 models from 600 comparisons (ties split). 7 of
253
+ 15 pairs are separable at 95%, meaning the interval on the gap between the
254
+ two ratings excludes zero. For the other 8 pairs the interval includes zero,
255
+ so this data does not establish an order for those pairs in either
256
+ direction. [...] Ratings are anchored on m1 at 0. Only differences between
257
+ models mean anything [...]
262
258
  ```
263
259
 
264
- Six models make fifteen pairs. Six hundred comparisons separate five of
265
- them, and every one of those five is a pair containing the weakest model.
266
- The top five are not told apart from each other at all. A published board
267
- would still print them in a line, one through six, and readers would take
268
- that order seriously.
260
+ Six models make fifteen pairs. Six hundred comparisons separate seven of
261
+ them. Five are the weakest model against each of the others, and the other
262
+ two put m1 and m2 above m5. No pair among the top four separates, and the
263
+ fit even rates m4 above m3, the reverse of the strengths the data was drawn
264
+ from. A published board would still print all six in a line, one through
265
+ six, and readers would take that order seriously.
266
+
267
+ `result.pairs` lists all fifteen pairs with the interval on each gap, so the
268
+ eight that do not separate can be read as well as counted.
269
+
270
+ The bootstrap behind those intervals resamples items, taking every comparison
271
+ made on one prompt together, because judgements of the same prompt move
272
+ together. Versions before 0.3.0 resampled single comparisons. On simulated
273
+ data shaped like MT-Bench, 2,575 comparisons over 80 prompts, that covered
274
+ 88% where 95% was claimed once prompts shifted model strength by a spread of
275
+ 0.5 on the log-odds scale, and 80% at a spread of 1.0.
276
+ `examples/pairwise_cluster_study.py` has the full table. Pass
277
+ `resample="comparisons"` to reproduce an older result.
269
278
 
270
279
  `to_elo` puts the same fit on the 400 point scale people expect and carries
271
280
  the intervals across with it. Leaderboards publish Elo without intervals,
@@ -281,11 +290,32 @@ and named.
281
290
  ## Coverage
282
291
 
283
292
  A statistics package earns trust by showing its intervals cover at the rate
284
- they claim. Every interval here is tested by simulation. Generate datasets
285
- with a known truth, then confirm the 95% interval contains it about 95% of
286
- the time. Runs per test range from a hundred or so to a thousand, set by what
287
- each simulation costs. Those tests run in CI and you can read them in
288
- `tests/`.
293
+ they claim. Generate datasets with a known truth, then confirm the 95%
294
+ interval contains it about 95% of the time. Runs per test range from a
295
+ hundred or so to a thousand, set by what each simulation costs.
296
+
297
+ The package builds fifteen intervals and nine of them are tested that way.
298
+ Those nine are the Wilson and bootstrap intervals on a single score, the
299
+ Tango, bootstrap and Hodges-Lehmann intervals on a paired difference, the
300
+ bootstrap intervals on Krippendorff's alpha and on judge-human alpha, and the
301
+ Bradley-Terry intervals on each rating and on each gap between two ratings.
302
+ The gap interval is tested on data where comparisons made on the same prompt
303
+ move together, which is the case its item bootstrap is for.
304
+
305
+ Five more are pinned against a reference instead, which is the sharper test
306
+ where one exists. The score interval for two independent proportions is swept
307
+ against its own p-value over every table at two group sizes, since that
308
+ interval is the test inverted and the two must never disagree about zero. The
309
+ Wilson intervals in `position_bias` and both logistic intervals in
310
+ `length_bias` are matched against `statsmodels`. The Student-t interval in
311
+ `score_ci` is matched against `scipy`, and Welch's interval in
312
+ `compare_independent` against `statsmodels`.
313
+
314
+ The last one has nothing pinning its bounds, and this is the place to say so
315
+ rather than leave you to find out. It is the two-sample bootstrap in
316
+ `compare_independent`.
317
+
318
+ Those tests run in CI and you can read them in `tests/`.
289
319
 
290
320
  ---
291
321
 
@@ -308,6 +338,25 @@ Python 3.9+, numpy, scipy, pandas. Nothing else.
308
338
 
309
339
  ---
310
340
 
341
+ ## Who wrote this
342
+
343
+ I'm Hezron Mokaya, a statistician. I have spent years on the task and
344
+ QA side of expert AI evaluation. I
345
+ graded, and I ran quality control on other people's grading. I did not design
346
+ the benchmarks.
347
+
348
+ This package is the work I do for clients, published in the open. I audit AI
349
+ evaluations for statistical validity: rater agreement, pairwise comparison,
350
+ judge validation, and whether the numbers in a report support the claim being
351
+ made from them.
352
+
353
+ If you have an eval you are about to publish, act on, or defend, I take that
354
+ work.
355
+
356
+ ondibahezron@gmail.com
357
+
358
+ ---
359
+
311
360
  ## Licence
312
361
 
313
362
  MIT.
@@ -136,9 +136,11 @@ Two rules the library follows throughout:
136
136
  | `compare` | Does the margin between two systems survive a paired test? |
137
137
  | `audit` | Everything except `pairwise`, as a report ranked by what changes the conclusion. |
138
138
 
139
- `audit` is the deliverable. It runs five of these checks and orders what it
140
- finds by what changes the conclusion. Each of those five also stands alone
141
- when you want a single number instead of a report.
139
+ `audit` is the deliverable. It runs seven checks drawn from five of these
140
+ modules and orders what it finds by what changes the conclusion. `judge`
141
+ supplies three of the seven, since judge-human agreement, position bias and
142
+ length bias are separate findings. Each of those five modules also stands
143
+ alone when you want a single number instead of a report.
142
144
 
143
145
  `pairwise` runs on its own. A leaderboard is a different question from a
144
146
  single eval and arrives as a different frame, one row per comparison rather
@@ -151,18 +153,10 @@ Scores, compare and power are the foundation those three stand on.
151
153
 
152
154
  ## Status
153
155
 
154
- All seven modules are implemented. The examples below are run in CI, so what
155
- they print here is what they print for you.
156
-
157
- | Module | State |
158
- |---|---|
159
- | `scores` | Implemented |
160
- | `compare` | Implemented |
161
- | `agreement` | Implemented |
162
- | `judge` | Implemented |
163
- | `power` | Implemented |
164
- | `audit` | Implemented |
165
- | `pairwise` | Implemented |
156
+ All seven modules are implemented. CI runs the suite on Python 3.9, 3.11 and
157
+ 3.12, including the checks that pin results against outside implementations.
158
+ The examples below are run there too, so what they print here is what they
159
+ print for you.
166
160
 
167
161
  ### What `scores` answers
168
162
 
@@ -228,17 +222,32 @@ print(bradley_terry(comparisons, seed=0).summary())
228
222
  ```
229
223
 
230
224
  ```
231
- Bradley-Terry ratings for 6 models from 600 comparisons (ties split). 5 of
232
- 15 pairs are separable at 95%, meaning their intervals do not overlap. The
233
- other 10 pairs cannot be ordered [...] Ratings are anchored on m1 at 0.
234
- Only differences between models mean anything [...]
225
+ Bradley-Terry ratings for 6 models from 600 comparisons (ties split). 7 of
226
+ 15 pairs are separable at 95%, meaning the interval on the gap between the
227
+ two ratings excludes zero. For the other 8 pairs the interval includes zero,
228
+ so this data does not establish an order for those pairs in either
229
+ direction. [...] Ratings are anchored on m1 at 0. Only differences between
230
+ models mean anything [...]
235
231
  ```
236
232
 
237
- Six models make fifteen pairs. Six hundred comparisons separate five of
238
- them, and every one of those five is a pair containing the weakest model.
239
- The top five are not told apart from each other at all. A published board
240
- would still print them in a line, one through six, and readers would take
241
- that order seriously.
233
+ Six models make fifteen pairs. Six hundred comparisons separate seven of
234
+ them. Five are the weakest model against each of the others, and the other
235
+ two put m1 and m2 above m5. No pair among the top four separates, and the
236
+ fit even rates m4 above m3, the reverse of the strengths the data was drawn
237
+ from. A published board would still print all six in a line, one through
238
+ six, and readers would take that order seriously.
239
+
240
+ `result.pairs` lists all fifteen pairs with the interval on each gap, so the
241
+ eight that do not separate can be read as well as counted.
242
+
243
+ The bootstrap behind those intervals resamples items, taking every comparison
244
+ made on one prompt together, because judgements of the same prompt move
245
+ together. Versions before 0.3.0 resampled single comparisons. On simulated
246
+ data shaped like MT-Bench, 2,575 comparisons over 80 prompts, that covered
247
+ 88% where 95% was claimed once prompts shifted model strength by a spread of
248
+ 0.5 on the log-odds scale, and 80% at a spread of 1.0.
249
+ `examples/pairwise_cluster_study.py` has the full table. Pass
250
+ `resample="comparisons"` to reproduce an older result.
242
251
 
243
252
  `to_elo` puts the same fit on the 400 point scale people expect and carries
244
253
  the intervals across with it. Leaderboards publish Elo without intervals,
@@ -254,11 +263,32 @@ and named.
254
263
  ## Coverage
255
264
 
256
265
  A statistics package earns trust by showing its intervals cover at the rate
257
- they claim. Every interval here is tested by simulation. Generate datasets
258
- with a known truth, then confirm the 95% interval contains it about 95% of
259
- the time. Runs per test range from a hundred or so to a thousand, set by what
260
- each simulation costs. Those tests run in CI and you can read them in
261
- `tests/`.
266
+ they claim. Generate datasets with a known truth, then confirm the 95%
267
+ interval contains it about 95% of the time. Runs per test range from a
268
+ hundred or so to a thousand, set by what each simulation costs.
269
+
270
+ The package builds fifteen intervals and nine of them are tested that way.
271
+ Those nine are the Wilson and bootstrap intervals on a single score, the
272
+ Tango, bootstrap and Hodges-Lehmann intervals on a paired difference, the
273
+ bootstrap intervals on Krippendorff's alpha and on judge-human alpha, and the
274
+ Bradley-Terry intervals on each rating and on each gap between two ratings.
275
+ The gap interval is tested on data where comparisons made on the same prompt
276
+ move together, which is the case its item bootstrap is for.
277
+
278
+ Five more are pinned against a reference instead, which is the sharper test
279
+ where one exists. The score interval for two independent proportions is swept
280
+ against its own p-value over every table at two group sizes, since that
281
+ interval is the test inverted and the two must never disagree about zero. The
282
+ Wilson intervals in `position_bias` and both logistic intervals in
283
+ `length_bias` are matched against `statsmodels`. The Student-t interval in
284
+ `score_ci` is matched against `scipy`, and Welch's interval in
285
+ `compare_independent` against `statsmodels`.
286
+
287
+ The last one has nothing pinning its bounds, and this is the place to say so
288
+ rather than leave you to find out. It is the two-sample bootstrap in
289
+ `compare_independent`.
290
+
291
+ Those tests run in CI and you can read them in `tests/`.
262
292
 
263
293
  ---
264
294
 
@@ -281,6 +311,25 @@ Python 3.9+, numpy, scipy, pandas. Nothing else.
281
311
 
282
312
  ---
283
313
 
314
+ ## Who wrote this
315
+
316
+ I'm Hezron Mokaya, a statistician. I have spent years on the task and
317
+ QA side of expert AI evaluation. I
318
+ graded, and I ran quality control on other people's grading. I did not design
319
+ the benchmarks.
320
+
321
+ This package is the work I do for clients, published in the open. I audit AI
322
+ evaluations for statistical validity: rater agreement, pairwise comparison,
323
+ judge validation, and whether the numbers in a report support the claim being
324
+ made from them.
325
+
326
+ If you have an eval you are about to publish, act on, or defend, I take that
327
+ work.
328
+
329
+ ondibahezron@gmail.com
330
+
331
+ ---
332
+
284
333
  ## Licence
285
334
 
286
335
  MIT.