evalstats 0.1.9__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 (52) hide show
  1. evalstats-0.1.9/LICENSE +23 -0
  2. evalstats-0.1.9/PKG-INFO +364 -0
  3. evalstats-0.1.9/README.md +310 -0
  4. evalstats-0.1.9/evalstats/__init__.py +82 -0
  5. evalstats-0.1.9/evalstats/cli.py +596 -0
  6. evalstats-0.1.9/evalstats/compare.py +902 -0
  7. evalstats-0.1.9/evalstats/config.py +29 -0
  8. evalstats-0.1.9/evalstats/core/__init__.py +0 -0
  9. evalstats-0.1.9/evalstats/core/bayes_evals.py +498 -0
  10. evalstats-0.1.9/evalstats/core/bundles.py +164 -0
  11. evalstats-0.1.9/evalstats/core/mixed_effects.py +1928 -0
  12. evalstats-0.1.9/evalstats/core/paired.py +1608 -0
  13. evalstats-0.1.9/evalstats/core/ranking.py +322 -0
  14. evalstats-0.1.9/evalstats/core/resampling.py +860 -0
  15. evalstats-0.1.9/evalstats/core/router.py +1034 -0
  16. evalstats-0.1.9/evalstats/core/stats_utils.py +47 -0
  17. evalstats-0.1.9/evalstats/core/summary.py +2404 -0
  18. evalstats-0.1.9/evalstats/core/types.py +594 -0
  19. evalstats-0.1.9/evalstats/core/variance.py +381 -0
  20. evalstats-0.1.9/evalstats/io.py +493 -0
  21. evalstats-0.1.9/evalstats/vis/__init__.py +13 -0
  22. evalstats-0.1.9/evalstats/vis/critical_difference.py +430 -0
  23. evalstats-0.1.9/evalstats/vis/forest.py +272 -0
  24. evalstats-0.1.9/evalstats/vis/heatmap.py +247 -0
  25. evalstats-0.1.9/evalstats/vis/point_estimates.py +260 -0
  26. evalstats-0.1.9/evalstats/vis/scoreboard.py +255 -0
  27. evalstats-0.1.9/evalstats.egg-info/PKG-INFO +364 -0
  28. evalstats-0.1.9/evalstats.egg-info/SOURCES.txt +50 -0
  29. evalstats-0.1.9/evalstats.egg-info/dependency_links.txt +1 -0
  30. evalstats-0.1.9/evalstats.egg-info/entry_points.txt +2 -0
  31. evalstats-0.1.9/evalstats.egg-info/requires.txt +45 -0
  32. evalstats-0.1.9/evalstats.egg-info/top_level.txt +1 -0
  33. evalstats-0.1.9/pyproject.toml +66 -0
  34. evalstats-0.1.9/setup.cfg +4 -0
  35. evalstats-0.1.9/tests/test_analyze.py +1517 -0
  36. evalstats-0.1.9/tests/test_analyze_factorial.py +544 -0
  37. evalstats-0.1.9/tests/test_bayes_binary_routing.py +605 -0
  38. evalstats-0.1.9/tests/test_cli.py +658 -0
  39. evalstats-0.1.9/tests/test_compare_models.py +366 -0
  40. evalstats-0.1.9/tests/test_compare_prompts.py +567 -0
  41. evalstats-0.1.9/tests/test_critical_difference_plot.py +226 -0
  42. evalstats-0.1.9/tests/test_io.py +314 -0
  43. evalstats-0.1.9/tests/test_lmm.py +695 -0
  44. evalstats-0.1.9/tests/test_lmm_backend_parity.py +502 -0
  45. evalstats-0.1.9/tests/test_lmm_statsmodels.py +285 -0
  46. evalstats-0.1.9/tests/test_p_values.py +481 -0
  47. evalstats-0.1.9/tests/test_permutation.py +147 -0
  48. evalstats-0.1.9/tests/test_resampling.py +319 -0
  49. evalstats-0.1.9/tests/test_scoreboard_plot.py +62 -0
  50. evalstats-0.1.9/tests/test_set_alpha_ci.py +168 -0
  51. evalstats-0.1.9/tests/test_simultaneous_ci.py +721 -0
  52. evalstats-0.1.9/tests/test_wilson_newcombe.py +626 -0
@@ -0,0 +1,23 @@
1
+ MIT License
2
+ Note: This license applies to the `evalstats` Python package only.
3
+ All files in `website/` are licensed separately under CC BY-NC-ND 4.0 — see website/LICENSE.
4
+
5
+ Copyright (c) 2026 Ian Arawjo
6
+
7
+ Permission is hereby granted, free of charge, to any person obtaining a copy
8
+ of this software and associated documentation files (the "Software"), to deal
9
+ in the Software without restriction, including without limitation the rights
10
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
+ copies of the Software, and to permit persons to whom the Software is
12
+ furnished to do so, subject to the following conditions:
13
+
14
+ The above copyright notice and this permission notice shall be included in all
15
+ copies or substantial portions of the Software.
16
+
17
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23
+ SOFTWARE.
@@ -0,0 +1,364 @@
1
+ Metadata-Version: 2.4
2
+ Name: evalstats
3
+ Version: 0.1.9
4
+ Summary: Statistically sane analysis methods for comparing AI model and prompt performance.
5
+ Author: Ian Arawjo
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/ianarawjo/evalstats
8
+ Project-URL: Repository, https://github.com/ianarawjo/evalstats
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: Programming Language :: Python :: 3 :: Only
11
+ Requires-Python: >=3.9
12
+ Description-Content-Type: text/markdown
13
+ License-File: LICENSE
14
+ Requires-Dist: numpy>=1.22
15
+ Requires-Dist: scipy>=1.9
16
+ Requires-Dist: pandas>=1.5
17
+ Requires-Dist: matplotlib>=3.6
18
+ Requires-Dist: scikit-posthocs
19
+ Requires-Dist: statsmodels>=0.14
20
+ Requires-Dist: plotext
21
+ Provides-Extra: interactive
22
+ Requires-Dist: plotly>=5.0; extra == "interactive"
23
+ Provides-Extra: models
24
+ Requires-Dist: choix>=0.3; extra == "models"
25
+ Provides-Extra: lmm
26
+ Requires-Dist: pymer4>=0.9; extra == "lmm"
27
+ Requires-Dist: pyarrow>=14.0; extra == "lmm"
28
+ Requires-Dist: great_tables; extra == "lmm"
29
+ Requires-Dist: joblib; extra == "lmm"
30
+ Requires-Dist: rpy2; extra == "lmm"
31
+ Requires-Dist: polars; extra == "lmm"
32
+ Requires-Dist: scikit-learn; extra == "lmm"
33
+ Requires-Dist: formulae; extra == "lmm"
34
+ Provides-Extra: xlsx
35
+ Requires-Dist: openpyxl>=3.0; extra == "xlsx"
36
+ Provides-Extra: all
37
+ Requires-Dist: plotly>=5.0; extra == "all"
38
+ Requires-Dist: choix>=0.3; extra == "all"
39
+ Requires-Dist: openpyxl>=3.0; extra == "all"
40
+ Requires-Dist: pymer4>=0.9; extra == "all"
41
+ Requires-Dist: pyarrow>=14.0; extra == "all"
42
+ Requires-Dist: great_tables; extra == "all"
43
+ Requires-Dist: joblib; extra == "all"
44
+ Requires-Dist: rpy2; extra == "all"
45
+ Requires-Dist: polars; extra == "all"
46
+ Requires-Dist: scikit-learn; extra == "all"
47
+ Requires-Dist: formulae; extra == "all"
48
+ Provides-Extra: dev
49
+ Requires-Dist: pytest>=7.0; extra == "dev"
50
+ Requires-Dist: pytest-cov; extra == "dev"
51
+ Requires-Dist: build>=1.0; extra == "dev"
52
+ Requires-Dist: twine>=5.0; extra == "dev"
53
+ Dynamic: license-file
54
+
55
+ # evalstats
56
+
57
+ Utilities and guidance for statistically sane analyses for comparing prompt and LLM performance. Compute statistics and visualize the results.
58
+
59
+ `evalstats` helps you answer questions like:
60
+ - Is Prompt A actually better than Prompt B, or just slightly luckier on this dataset?
61
+ - Does Model A beat Model B, or only under a specific prompt phrasing?
62
+ - How sensitive is model performance to prompt wording?
63
+ - Are my performance differences large enough to be meaningful, or just noise?
64
+ - How stable are scores across runs, evaluators, or inputs?
65
+ - What statistics test should I run in X situation?
66
+
67
+ The idea is simple: you give `evalstats` your benchmark data, and it runs statistically appropriate analyses that quantify uncertainty and provide confidence bounds on your claims. Datasets can include eval scores, prompts, inputs, evaluator names, and (optionally) models. `evalstats` provides:
68
+ - Plots and tests comparing prompt performance, with bootstrapped CIs and variance
69
+ - Plots and tests comparing model performance across prompt variations
70
+ - Constraints that guide you into performing best practices, like always considering prompt sensitivity when benchmarking model performance
71
+
72
+ As well, there is a "learning" guide in `website/` which I am building out.
73
+ This will include simulation- and research-backed examples of statistics for LLM evals,
74
+ as well as example code (which will, obviously, tend to use `evalstats`, but
75
+ the lessons hold regardless of implementation).
76
+
77
+ > [!IMPORTANT]
78
+ > We are actively building out this project, both the website/guide and the package.
79
+ > If there's something you'd like to see, or guidance on a specific topic, let us know
80
+ > by raising an Issue.
81
+
82
+ ## Sample output
83
+
84
+ Running `estats.analyze()` and then `estats.print_analysis_summary(analysis)` prints a full statistical report to the terminal, including confidence interval line plots, pairwise comparisons between prompt templates, and per-input stability across runs (how stable the model is across multiple runs for the same input). Below is example excerpt from an analysis of a 4-template sentiment-classification benchmark (GPT-4.1-nano, 27 inputs, 3 runs, 3 evaluators):
85
+
86
+ ![Example terminal output](docs/example-output.png)
87
+
88
+ From this output, we can see that Minimal and Instructive are the most promising candidates, but it is statistically unclear which is better. We also see that Chain-of-thought gives the least consistent outputs across multiple runs for the same inputs, compared to the other methods.
89
+
90
+ In the most recent version of `evalstats`, there's also helpful colors to help you see
91
+ this information. For instance, comparing models and prompts at the same time,
92
+ `evalstats` shows a 4-way tie between four combinations of model-prompt:
93
+
94
+ ![Example terminal output with colors](docs/terminal-output-example.jpg)
95
+
96
+ You can also plot within notebook environments (although this feature is being actively built out over time and the least developed at the moment). The `plot_point_estimates` function produces a chart showing each template's absolute mean score with marginal confidence intervals:
97
+
98
+ ![Mean advantage plot](docs/mean_advantage.png)
99
+
100
+ ## Statistics
101
+
102
+ The specific statistical tests the `evalstats.analyze()` method runs are:
103
+
104
+ - **All pairwise prompt comparisons (paired by input)** via `all_pairwise(...)`:
105
+ - Computes mean or median difference (mean by default), bootstrapped 99% confidence interval, and p-value for every prompt template pair.
106
+ - Comparison method defaults to `method="auto"`:
107
+ - **Smoothed bootstrap with a Gaussian KDE** (`method="smooth_bootstrap"`) in situations of non-binary data. It has been verified in our simulations that for eval-type data and small sample sizes especially, smoothed is superior to the other bootstrap methods considered (percentile, BCa, Bayesian).
108
+ - **Bayesian pairwise from [`bayes_evals`](https://github.com/sambowyer/bayes_evals/tree/main) and McNemar's test**: Default methods for binary scores (0 or 1 only). Our simulations showed Bayesian pairwise was superior to bootstrap at small N. Note that Bayesian methods should technically be called credible intervals, but they estimate the confidence interval very closely.
109
+ - Multiple-comparisons correction for p-values (defaults to **Benjamini–Hochberg (fdr_bh)**).
110
+ - Also reports Wilcoxon signed-rank test p-value, in case you need it for people familiar with that test, although p-values from bootstrapped CIs are more robust
111
+
112
+ - **Bootstrap rank distribution** via `bootstrap_ranks(...)`:
113
+ - Estimates each prompt template’s `P(best)` and expected rank among the full list of prompt templates.
114
+
115
+ - **Point estimates** via `robustness_metrics(...)`:
116
+ - Descriptive stats like mean, median, std, CV, IQR, CVaR-10, and key percentiles.
117
+ - Marginal confidence intervals on absolute means/medians.
118
+
119
+ If your benchmark includes repeated runs (`R >= 3`), bootstrap-based analyses above use a **two-level nested bootstrap** (resample inputs, then runs within each input) so run-to-run stochasticity is propagated into CIs and rankings. In that case, `analyze()` also returns a seed/input variance decomposition via `seed_variance_decomposition(...)`.
120
+
121
+ If you set `method="lmm"`, `analyze()` switches to a mixed-effects path (`score ~ template + (1|input)`) with Wald CIs and parametric rank distributions. By default this uses `statsmodels` (pure Python, no additional setup required); pass `backend="pymer4"` to use R's lme4/emmeans instead (requires a separate R installation — see below). **Mixed effects model support is more experimental at the moment.**
122
+
123
+ ## Installation and Quick start CLI
124
+
125
+ ```bash
126
+ pip install evalstats
127
+ ```
128
+
129
+ For Excel (`.xlsx`) input support:
130
+
131
+ ```bash
132
+ pip install "evalstats[xlsx]"
133
+ ```
134
+
135
+ For all optional extras (including mixed-effects/LMM support):
136
+
137
+ ```bash
138
+ pip install "evalstats[all]"
139
+ ```
140
+
141
+ From the command line, `evalstats` can read a CSV or Excel file directly and print a statistical summary:
142
+
143
+ ```bash
144
+ evalstats analyze results.csv
145
+ ```
146
+
147
+ The input file should have columns `template`, `input`, and `score` (run and evaluator columns are optional). Run `evalstats analyze --help` for the full list of options and supported column aliases.
148
+
149
+ For more complex statistical analysis with mixed effects models, use `method="lmm"`. The default `statsmodels` backend works out of the box; for the optional R-based backend, see below.
150
+
151
+ ## Python API
152
+
153
+ `evalstats` main use case is as a Python API, which provides a similar entry point, the `analyze()` function. Simply pass your benchmark data in the correct format, and pass it to `analyze` to get a battery of results:
154
+
155
+ ```python
156
+ import numpy as np
157
+ import evalstats as estats
158
+
159
+ # Example raw scores for 4 templates × 3 inputs (single run, single evaluator)
160
+ your_scores = [
161
+ [0.91, 0.88, 0.86],
162
+ [0.90, 0.89, 0.84],
163
+ [0.85, 0.82, 0.80],
164
+ [0.79, 0.76, 0.74],
165
+ ]
166
+ n_templates = 4
167
+ n_inputs = 3
168
+
169
+ # scores shape: (n_templates, n_inputs, n_runs, n_evaluators)
170
+ # For a single evaluator and single run, shape is (N, M, 1, 1)
171
+ scores = np.array(your_scores).reshape(n_templates, n_inputs, 1, 1)
172
+
173
+ result = estats.BenchmarkResult(
174
+ scores=scores,
175
+ template_labels=["Minimal", "Instructive", "Few-shot", "Chain-of-thought"],
176
+ input_labels=[f"input_{i}" for i in range(n_inputs)],
177
+ )
178
+
179
+ analysis = estats.analyze(result, reference="grand_mean", n_bootstrap=5_000)
180
+ estats.print_analysis_summary(analysis)
181
+ ```
182
+
183
+ If your source data is already in a pandas DataFrame (possibly with noisy values), you can parse it directly and inspect a coercion report:
184
+
185
+ ```python
186
+ import evalstats as estats
187
+
188
+ benchmark, load_report = estats.from_dataframe(
189
+ df,
190
+ format="auto", # auto / wide / long
191
+ repair=True, # average duplicate cells + fill partial run slots
192
+ strict_complete_design=True, # set False to keep NaNs
193
+ return_report=True,
194
+ )
195
+
196
+ for line in load_report.to_lines():
197
+ print(line)
198
+
199
+ analysis = estats.analyze(benchmark)
200
+ ```
201
+
202
+ To visualize absolute prompt performance with bootstrapped 99% confidence intervals:
203
+
204
+ ```python
205
+ fig = estats.plot_point_estimates(result)
206
+ fig.savefig("mean_performance.png", dpi=150, bbox_inches="tight")
207
+ ```
208
+
209
+ ## Motivation
210
+
211
+ Most eval tools in the LLM evaluation space don't help users perform _any_ statistical tests, let alone showcase variances in performance between prompts or models. They instead present bar charts of average performance. Developers then glance at the bar chart and decide that "prompt/model A is better than B." But was it really?
212
+
213
+ Relying purely on bar charts and averages can very, very easily lead to erroneous conclusions—B might actually be more robust than A, or B performs well on an important subset of data, or there's not enough data to conclude one way or the other.
214
+
215
+ Why do people do evals this way? Well, they don't have the time, tools, or knowledge on how to do it better—frequently, they don't even know there's a better way.
216
+
217
+ `evalstats` aims to rectify this with simple, powerful defaults—just throw us your data and we'll run the stats and plot the results for you. Upstream applications, like LLM observability platforms, could take `evalstats` results and plot them in their own front-ends. Prompt optimization tools could also use `evalstats` to decide, e.g., when to cull a candidate prompt and how to present results to users.
218
+
219
+ ## Examples
220
+
221
+ ### Is one prompt "better" than others? Quantify uncertainty
222
+
223
+ When you have scores for multiple prompt templates across a set of inputs, `evalstats` computes bootstrapped 99% confidence intervals and pairwise significance tests so you can see not just which prompt scored highest on average, but how certain you can be about that ranking. It plots these to the terminal so you can check at a glance:
224
+
225
+ ![Comparing across prompts output](docs/compare-prompts-output.png)
226
+
227
+ ### Comparing across models while accounting for prompt sensitivity
228
+
229
+ A common failure mode in LLM benchmarking, both in academic papers and practitioner evaluations, is testing each model with a single prompt template and reporting the resulting scores as if they reflect stable model capabilities. In reality, model rankings can flip under semantically equivalent paraphrases of the same instruction. A benchmark result that says "Model A beats Model B" may be an artifact of prompt phrasing, not a meaningful capability difference.
230
+
231
+ Here, we can see the difference between OpenAI's `gpt-4.1-nano` and MistralAI's `ministral-8b-2512` on a small sentiment classification benchmark, quantified by bootstrapped 99% confidence intervals:
232
+
233
+ ![Comparing across models output](docs/compare-models-output.png)
234
+
235
+ In this run, multiple prompt template variations were considered, making this result more robust than trying a single prompt and calling it a day.
236
+
237
+ ### How stable is the performance across runs?
238
+
239
+ LLMs are stochastic at temperature>0. Will the performance stay similar, even upon multiple runs for the same inputs? `evalstats` offers a helpful "noise plot" which visualizes (in)stability across runs:
240
+
241
+ ![Per-input noise across runs](docs/per-input-noise.png)
242
+
243
+ ## Running Example Scripts
244
+
245
+ We provide multiple standalone example scripts that rig up a simple benchmark, collect LLM responses, and run analyses over them. From the repository root, run any example script directly:
246
+
247
+ ```bash
248
+ python examples/synthetic_mean_advantage.py
249
+ ```
250
+
251
+ Additional examples:
252
+
253
+ ```bash
254
+ # OpenAI sentiment benchmark (single run)
255
+ python examples/sentiment.py
256
+
257
+ # Multi-run variant (captures run-to-run variability)
258
+ python examples/sentiment_multirun.py
259
+
260
+ # Multi-model comparison across prompt templates
261
+ python examples/compare_models_multirun.py
262
+
263
+ # Manual API call walkthrough
264
+ python examples/sentiment_manual_api_calls.py
265
+ ```
266
+
267
+ OpenAI-powered examples require `OPENAI_API_KEY` set in your environment. But, you can easily swap out the model calls to whatever model you prefer.
268
+
269
+ ## Mixed effects models (LMM)
270
+
271
+ > [!IMPORTANT]
272
+ > Mixed effects analysis is experimental, and currently offers only the advantage of gracefully
273
+ > dealing with missing data. In the future, we plan to add factor decomposition across multiple inputs.
274
+ > We recommend only using `method="lmm"` if you need robustness to missing data (`NaN`). Keep
275
+ > in mind that missing data must be reasonably random (i.e., like sampling from a larger distribution).
276
+
277
+ `evalstats` supports mixed-effects models (`score ~ template + (1|input)`) for:
278
+ - Missing data in inputs (some score cells are `NaN`)
279
+ - Factor decomposition when multiple input factors are present
280
+
281
+ ### Default backend: statsmodels (pure Python)
282
+
283
+ No extra setup required — `statsmodels` is included in the standard `pip install evalstats`. Simply pass `method="lmm"`:
284
+
285
+ ```python
286
+ analysis = estats.analyze(result, method="lmm")
287
+ ```
288
+
289
+ `evalstats` fits the model with REML, computes Wald CIs via the delta method, and estimates rank distributions by parametric simulation.
290
+
291
+ ### Optional backend: pymer4 (requires R)
292
+
293
+ For Satterthwaite degrees of freedom and `emmeans`-based pairwise contrasts (R's gold standard for mixed models), pass `backend="pymer4"`:
294
+
295
+ ```python
296
+ analysis = estats.analyze(result, method="lmm", backend="pymer4")
297
+ ```
298
+
299
+ This requires a working R installation with the following packages:
300
+
301
+ ```r
302
+ install.packages(c(
303
+ "lme4",
304
+ "emmeans",
305
+ "tibble",
306
+ "broom",
307
+ "broom.mixed",
308
+ "lmerTest",
309
+ "report",
310
+ "car"
311
+ ))
312
+ ```
313
+
314
+ Then install the Python LMM extra:
315
+
316
+ ```bash
317
+ pip install "evalstats[lmm]"
318
+ ```
319
+
320
+ > [!NOTE]
321
+ > If your environment needs manual dependency pinning, this is the tested equivalent:
322
+ >
323
+ > ```bash
324
+ > pip install "pymer4>=0.9" great_tables joblib rpy2 polars scikit-learn formulae pyarrow
325
+ > ```
326
+
327
+ Installation details may differ on your system.
328
+
329
+ ## Future and TODO
330
+
331
+ We aim to continue to contribute to `evalstats`. Ideas for future features:
332
+ - Mixed-effects models (LMMs and potentially GLMMs) for multi-input data. Currently, `evalstats` only supports the case of one input per prompt template, rather than a grid search (cross product) of different prompt variations.
333
+ - A default "report" mode that outputs a PDF summarizing findings and diving into the details
334
+ - Integration with ChainForge as a front-end, to bring statistical analyses to plotted evals
335
+ - Help developers quantify the "semantic variance" of the provided prompt templates, and perhaps even factor this into the calculation in an intelligent way. This is important because the current implementation doesn't know about the diversity/representativity of the input dataset and prompts.
336
+ - Automatic "reliability" checking that generates minor prompt variations (e.g., lightly paraphrasing) and tests model robustness to small deviations. Implement various methods for generating minor prompt variations.
337
+
338
+ Another area of concern, but separate from the current focus on running stats over benchmarking scores, is helping users improve their eval and test set validity. Benchmark validity testing could use diagnostic tools from Item Response Theory, to converge on a smaller, higher-quality item set where every item is valuable (e.g., see Fluid Benchmarking). For each item in a set, know:
339
+ - Difficulty: What proportion of model/prompt variants get this right? Near-zero items either have bad reference answers, are genuinely unanswerable, or represent a capability so far out of range it's not discriminating anything useful. Near-ceiling items inflate scores without adding signal.
340
+ - Discrimination: Does performance on this item correlate with performance on the rest of the eval? A good item should be passed by models that do well overall and failed by models that do poorly. Low or negative discrimination is a red flag. Negative discrimination especially suggests the item may be flawed, ambiguous, or testing something orthogonal.
341
+
342
+ More practically speaking, we could:
343
+ - Flag always-pass and always-fail items for removal or replacement. Replace them with items at a similar difficulty level to what the user intended but with better discriminating power.
344
+ - Flag negative-discrimination items for inspection. These usually have one of a few problems: ambiguous wording where reasonable models disagree on interpretation, a flawed reference answer, or the item is actually measuring a different construct than the rest of the eval. Decide whether to fix or drop.
345
+ - Cluster items by similarity, either by topic or by response pattern (items that all the same models pass/fail together). Prune to the most discriminating items in each cluster. After pruning, look for construct areas that lost too many items: the user may need to write better items for that region rather than leaving it underrepresented.
346
+ - Benchmark distillation: Using an IRT-style approach similar to Fluid Benchmarking to find the most informative subset of eval items, and removing less informative ones. Could offer multiple methods for this, and simulations showing how they perform. E.g.:
347
+ - ```
348
+ Full benchmark: 1200 items
349
+ Distilled benchmark: 35 items
350
+ Token savings: 96%
351
+ Rank correlation: 0.94
352
+ ```
353
+ - Target a difficulty distribution: a well-designed benchmark has items spread across the difficulty range, with more items in the middle (where models are actually differentiated) than at the extremes. If the user's distribution is skewed too easy or hard, help them write targeted items to fill gaps.
354
+
355
+ ## Development
356
+
357
+ For package build, release validation, and maintainer workflows, see [DEVELOPMENT.md](DEVELOPMENT.md).
358
+
359
+ ## License
360
+
361
+ This repository uses two licenses:
362
+
363
+ - **`evalstats` package** (everything outside `website/`) — [MIT](LICENSE).
364
+ - **Stats for Evals Website** (everything in `website/`) — [CC BY-NC-ND 4.0](website/LICENSE). You may share it with attribution non-commercially, but commercial use and derivative works are not permitted.