eval-toolkit 0.38.0__tar.gz → 0.40.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.
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/CHANGELOG.md +98 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/PKG-INFO +1 -1
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/src/eval_toolkit/__init__.py +4 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/src/eval_toolkit/_parallel.py +13 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/src/eval_toolkit/_version.py +1 -1
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/src/eval_toolkit/calibration.py +159 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/src/eval_toolkit/leakage.py +8 -1
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/src/eval_toolkit/metrics.py +85 -1
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/golden/public_api/snapshot.json +26 -1
- eval_toolkit-0.40.0/tests/test_calibration_binary_adapters.py +203 -0
- eval_toolkit-0.40.0/tests/test_is_metric_defined_for_slice.py +109 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/.gitignore +0 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/LICENSE +0 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/README.md +0 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/STYLE.md +0 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/docs/archive/README.md +0 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/docs/research/README.md +0 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/docs/research/datasets/README.md +0 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/docs/research/papers/data-integrity/README.md +0 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/docs/research/papers/eval-ecosystem/README.md +0 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/docs/research/papers/inference/README.md +0 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/docs/research/papers/prompt-injection/README.md +0 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/docs/source/methodology/README.md +0 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/pyproject.toml +0 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/src/eval_toolkit/__main__.py +0 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/src/eval_toolkit/_deprecated.py +0 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/src/eval_toolkit/analysis.py +0 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/src/eval_toolkit/artifacts.py +0 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/src/eval_toolkit/bootstrap.py +0 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/src/eval_toolkit/claims.py +0 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/src/eval_toolkit/config.py +0 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/src/eval_toolkit/docs.py +0 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/src/eval_toolkit/embeddings.py +0 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/src/eval_toolkit/evidence.py +0 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/src/eval_toolkit/harness.py +0 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/src/eval_toolkit/loaders.py +0 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/src/eval_toolkit/manifest.py +0 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/src/eval_toolkit/operating_points.py +0 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/src/eval_toolkit/paths.py +0 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/src/eval_toolkit/plotting.py +0 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/src/eval_toolkit/protocols.py +0 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/src/eval_toolkit/provenance.py +0 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/src/eval_toolkit/py.typed +0 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/src/eval_toolkit/schemas/manifest.v1.json +0 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/src/eval_toolkit/schemas/manifest.v2.json +0 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/src/eval_toolkit/schemas/manifest.v3.json +0 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/src/eval_toolkit/schemas/results.v1.json +0 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/src/eval_toolkit/schemas/results_full.v1.json +0 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/src/eval_toolkit/seeds.py +0 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/src/eval_toolkit/splits.py +0 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/src/eval_toolkit/text_dedup.py +0 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/src/eval_toolkit/thresholds.py +0 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/baseline/test_plotting_visual/plot_bootstrap_distribution.png +0 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/baseline/test_plotting_visual/plot_confusion_matrix_grid.png +0 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/baseline/test_plotting_visual/plot_lift_ci.png +0 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/baseline/test_plotting_visual/plot_metric_bars.png +0 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/baseline/test_plotting_visual/plot_pareto_frontier.png +0 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/baseline/test_plotting_visual/plot_pr_curve.png +0 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/baseline/test_plotting_visual/plot_reliability_diagram.png +0 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/baseline/test_plotting_visual/plot_roc_curve.png +0 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/baseline/test_plotting_visual/plot_score_histograms.png +0 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/baseline/test_plotting_visual/plot_slice_metric_heatmap.png +0 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/benchmarks/__init__.py +0 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/benchmarks/test_kernel_benchmarks.py +0 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/conftest.py +0 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/golden/bootstrap_ci/cases.json +0 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/golden/data/dedup_holdout.jsonl +0 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/golden/data/dedup_holdout_expected.json +0 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/golden/data/dedup_holdout_provenance.md +0 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/golden/docs/expected.md +0 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/golden/docs/input.md +0 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/golden/docs/metrics.json +0 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/golden/test_dedup_holdout_calibration.py +0 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/strategies.py +0 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_analysis.py +0 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_artifacts.py +0 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_block_bootstrap_on_folds.py +0 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_bootstrap_calibration_mc.py +0 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_bootstrap_edge_cases.py +0 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_bootstrap_golden.py +0 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_bootstrap_njobs.py +0 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_bootstrap_props.py +0 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_bootstrap_research_grounded.py +0 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_bootstrap_unit.py +0 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_calibration_bootstrap_chain.py +0 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_calibration_determinism.py +0 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_calibration_optimization_failures.py +0 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_calibration_props.py +0 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_calibration_research_grounded.py +0 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_calibration_unit.py +0 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_claims.py +0 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_claims_coverage.py +0 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_claims_props.py +0 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_cli.py +0 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_config.py +0 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_coverage_bootstrap.py +0 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_coverage_calibration.py +0 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_coverage_harness.py +0 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_coverage_metrics.py +0 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_coverage_plotting.py +0 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_dedup_split_leakage_chain.py +0 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_deprecations.py +0 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_docs_golden.py +0 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_docs_props.py +0 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_embeddings.py +0 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_evidence_validators.py +0 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_harness_edge_cases.py +0 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_harness_fault_injection.py +0 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_harness_folded.py +0 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_harness_internals.py +0 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_harness_metric_options.py +0 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_harness_parallelism.py +0 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_harness_smoke.py +0 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_import_boundaries.py +0 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_leakage.py +0 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_leakage_error_paths.py +0 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_leakage_props.py +0 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_loaders.py +0 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_loaders_coverage.py +0 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_loaders_props.py +0 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_logging.py +0 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_manifest.py +0 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_manifest_contamination_round_trip.py +0 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_manifest_props.py +0 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_manifest_validation.py +0 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_metrics_props.py +0 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_metrics_stratified_subsets.py +0 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_metrics_unit.py +0 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_misc_coverage.py +0 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_numeric_edge_cases.py +0 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_operating_points.py +0 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_operating_points_props.py +0 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_parallel.py +0 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_paths.py +0 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_pipeline_e2e.py +0 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_plotting_edge.py +0 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_plotting_smoke.py +0 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_plotting_visual.py +0 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_protocol_conformance.py +0 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_provenance.py +0 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_public_api.py +0 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_recall_at_fpr.py +0 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_reference_equivalence.py +0 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_reproducibility_integration.py +0 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_schemas.py +0 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_seeds.py +0 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_splits.py +0 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_splits_leakage_integration.py +0 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_splits_props.py +0 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_text_dedup.py +0 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_text_dedup_coverage.py +0 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_text_dedup_props.py +0 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_text_dedup_strategies.py +0 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_thresholds.py +0 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_thresholds_constant_score.py +0 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_thresholds_coverage.py +0 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_thresholds_props.py +0 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_thresholds_research_grounded.py +0 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_tokenization_leakage_check.py +0 -0
- {eval_toolkit-0.38.0 → eval_toolkit-0.40.0}/tests/test_v09_contracts.py +0 -0
|
@@ -7,6 +7,104 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.40.0] — 2026-05-18 — fit_platt_binary + fit_beta_binary (closes #43)
|
|
11
|
+
|
|
12
|
+
Completes the binary scalar-prob calibrator family started in v0.35.0
|
|
13
|
+
with `fit_temperature_binary` (#28). Consumers running the canonical
|
|
14
|
+
4-calibrator audit battery (temperature + isotonic + Platt + Beta) now
|
|
15
|
+
have library-first primitives for all four, eliminating ~100 LOC of
|
|
16
|
+
hand-roll across the calibration battery pattern.
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
|
|
20
|
+
- **`eval_toolkit.fit_platt_binary(y_true, y_score)`** — binary-
|
|
21
|
+
probability adapter for `fit_platt_calibrator`. Returns
|
|
22
|
+
`((a, b), apply)` where `(a, b)` are the fitted Platt slope and
|
|
23
|
+
intercept and `apply: (n,) -> (n,)` is the scalar-in / scalar-out
|
|
24
|
+
callable that maps probabilities through `σ(a·s + b)`. Trivially
|
|
25
|
+
wraps `fit_platt_calibrator`; exposes `(a, b)` explicitly so
|
|
26
|
+
consumers can log the parameters in a `RunManifest` without
|
|
27
|
+
introspecting the `PlattFit` dataclass.
|
|
28
|
+
- **`eval_toolkit.fit_beta_binary(y_true, y_score)`** — 3-parameter
|
|
29
|
+
Beta calibration per Kull et al. 2017. Returns `((a, b, c), apply)`
|
|
30
|
+
where `(a, b, c)` are the coefficients on `log(s)`, `log(1-s)`, and
|
|
31
|
+
the intercept respectively. Implemented directly (rather than
|
|
32
|
+
wrapping `fit_beta_calibrator` which returns only the callable) so
|
|
33
|
+
the fitted coefficients are captured alongside the apply function.
|
|
34
|
+
- Both functions: 5 doctest examples each (run in CI via
|
|
35
|
+
`.doctest-modules`) + 15 unit tests covering shape contracts, param
|
|
36
|
+
exposure, parity with the underlying calibrators, single-class
|
|
37
|
+
rejection, and a family-consistency test verifying all 3 binary
|
|
38
|
+
adapters (temperature, Platt, Beta) satisfy the same
|
|
39
|
+
`(params_tuple, apply)` shape.
|
|
40
|
+
|
|
41
|
+
### Why mirror `fit_temperature_binary`'s tuple shape
|
|
42
|
+
|
|
43
|
+
#43's body proposed `Callable[[NDArray], NDArray]` as the return type
|
|
44
|
+
(just the apply). We landed on `tuple[params, apply]` instead —
|
|
45
|
+
matches the existing v0.35.0 contract and gives consumers the params
|
|
46
|
+
they need for audit-battery `RunManifest` entries. Consumers wanting
|
|
47
|
+
just the apply can use `_, apply = fit_platt_binary(...)`.
|
|
48
|
+
|
|
49
|
+
### Protocol stability
|
|
50
|
+
|
|
51
|
+
No Protocol shape changes. Per v0.39.0's roadmap Gate 2 update, v1.0
|
|
52
|
+
requires ≥2 minors without Protocol edits — v0.40.0 is minor 1 of 2
|
|
53
|
+
toward that target.
|
|
54
|
+
|
|
55
|
+
## [0.39.0] — 2026-05-18 — consumer-feedback batch (closes #39, #40, #41)
|
|
56
|
+
|
|
57
|
+
Three issues lifted from `prompt-injection-detection-prototype v1.0.0`
|
|
58
|
+
consumer-side workarounds into upstream toolkit primitives + docs.
|
|
59
|
+
All small scope; the consumer had filed each with a "Rationale for
|
|
60
|
+
upstream landing" section per the *"Working around a library
|
|
61
|
+
limitation without filing an upstream issue"* anti-pattern discipline.
|
|
62
|
+
|
|
63
|
+
### Added
|
|
64
|
+
|
|
65
|
+
- **`eval_toolkit.metrics.is_metric_defined_for_slice`** + module-level
|
|
66
|
+
constant **`SINGLE_CLASS_INCOMPATIBLE_METRICS`** (`frozenset({"auroc",
|
|
67
|
+
"auprc"})`). AUROC and AUPRC are mathematically undefined on
|
|
68
|
+
single-class slices — ranking metrics require both classes. The
|
|
69
|
+
primitive takes ``is_single_class: bool`` (caller computes from the
|
|
70
|
+
slice's class distribution) and returns whether the metric is
|
|
71
|
+
defined. Use at the per-cell layer to filter `(metric, slice)`
|
|
72
|
+
combinations BEFORE bootstrap so degenerate values never pollute
|
|
73
|
+
downstream artifacts. Default incompatible set overridable per call.
|
|
74
|
+
Closes #39.
|
|
75
|
+
|
|
76
|
+
### Changed
|
|
77
|
+
|
|
78
|
+
- **`LeakageCheck.name` Protocol member redeclared as `@property`**
|
|
79
|
+
(was `name: str` class-level annotation). Strictly contract-
|
|
80
|
+
tightening: existing consumers who *read* `check.name` keep working;
|
|
81
|
+
this fixes the `mypy --strict` rejection of
|
|
82
|
+
`list[LeakageCheck] = [CrossSplitLeakageCheck()]` that surfaced when
|
|
83
|
+
consumers built lists of frozen-dataclass leakage checks. Runtime
|
|
84
|
+
`isinstance(check, LeakageCheck)` was always fine — only mypy strict
|
|
85
|
+
was unhappy with read-only-vs-settable Protocol semantics (PEP 544).
|
|
86
|
+
Closes #40.
|
|
87
|
+
|
|
88
|
+
### Documentation
|
|
89
|
+
|
|
90
|
+
- **`docs/source/methodology/parallelism.md` §"Memory model:
|
|
91
|
+
worker-copy semantics"** — new section documenting joblib loky's
|
|
92
|
+
pickle-based worker IPC and its memory implications for DataFrame-
|
|
93
|
+
bearing specs (`n_jobs × spec_size` resident memory; the bug that
|
|
94
|
+
OOM-killed a 128-core BCa bootstrap sweep on prompt-injection-
|
|
95
|
+
detection-prototype's marginal-CI regen). Worked example of the
|
|
96
|
+
file-path-shared-state pattern + recommended ceiling formula
|
|
97
|
+
(`min(N, available_RAM_GB / spec_size_GB)`). Cross-referenced from
|
|
98
|
+
the `parallel_map` docstring in `_parallel.py`. Closes #41.
|
|
99
|
+
|
|
100
|
+
### Why bundled
|
|
101
|
+
|
|
102
|
+
Three small consumer-feedback items from the same downstream project;
|
|
103
|
+
ship as one minor rather than three patches. Pattern matches the
|
|
104
|
+
v0.37.0 "TokenizationLeakageCheck + per-module floors" bundling —
|
|
105
|
+
small consumer-driven fixes consolidated to keep release-prep
|
|
106
|
+
overhead per item low.
|
|
107
|
+
|
|
10
108
|
## [0.38.0] — 2026-05-18 — executable examples (myst-nb migration)
|
|
11
109
|
|
|
12
110
|
Docs-only minor. Migrates the 14 walkthrough pages in
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: eval-toolkit
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.40.0
|
|
4
4
|
Summary: Reusable evaluation contracts for binary classification: metrics, bootstrap CIs, calibration, artifacts, and evidence gates.
|
|
5
5
|
Project-URL: Homepage, https://github.com/brandon-behring/eval-toolkit
|
|
6
6
|
Project-URL: Documentation, https://brandon-behring.github.io/eval-toolkit/
|
|
@@ -83,8 +83,10 @@ _EXPORTS: dict[str, str] = {
|
|
|
83
83
|
"DEFAULT_STRATEGY": "eval_toolkit.calibration",
|
|
84
84
|
"CostMatrix": "eval_toolkit.calibration",
|
|
85
85
|
"bayes_optimal_threshold": "eval_toolkit.calibration",
|
|
86
|
+
"fit_beta_binary": "eval_toolkit.calibration",
|
|
86
87
|
"fit_beta_calibrator": "eval_toolkit.calibration",
|
|
87
88
|
"fit_isotonic_calibrator": "eval_toolkit.calibration",
|
|
89
|
+
"fit_platt_binary": "eval_toolkit.calibration",
|
|
88
90
|
"fit_platt_calibrator": "eval_toolkit.calibration",
|
|
89
91
|
"fit_temperature": "eval_toolkit.calibration",
|
|
90
92
|
"fit_temperature_binary": "eval_toolkit.calibration",
|
|
@@ -164,6 +166,7 @@ _EXPORTS: dict[str, str] = {
|
|
|
164
166
|
"write_manifest": "eval_toolkit.manifest",
|
|
165
167
|
# --- metrics ---
|
|
166
168
|
"DEFAULT_ASSUMED_PRIORS": "eval_toolkit.metrics",
|
|
169
|
+
"SINGLE_CLASS_INCOMPATIBLE_METRICS": "eval_toolkit.metrics",
|
|
167
170
|
"ThresholdResult": "eval_toolkit.metrics",
|
|
168
171
|
"brier_decomposition": "eval_toolkit.metrics",
|
|
169
172
|
"brier_score": "eval_toolkit.metrics",
|
|
@@ -173,6 +176,7 @@ _EXPORTS: dict[str, str] = {
|
|
|
173
176
|
"expected_calibration_error_l2": "eval_toolkit.metrics",
|
|
174
177
|
"expected_calibration_error_l2_debiased": "eval_toolkit.metrics",
|
|
175
178
|
"headline_metrics": "eval_toolkit.metrics",
|
|
179
|
+
"is_metric_defined_for_slice": "eval_toolkit.metrics",
|
|
176
180
|
"metrics_at_threshold": "eval_toolkit.metrics",
|
|
177
181
|
"pr_auc": "eval_toolkit.metrics",
|
|
178
182
|
"precision_at_prior": "eval_toolkit.metrics",
|
|
@@ -48,6 +48,19 @@ def parallel_map[T, R](
|
|
|
48
48
|
state (use ``np.random.SeedSequence(seed).spawn(n)`` for resample
|
|
49
49
|
loops so ``n_jobs > 1`` produces identical results to ``n_jobs == 1``
|
|
50
50
|
for the same seed).
|
|
51
|
+
- **Worker-copy memory model.** Each loky worker receives a full copy
|
|
52
|
+
of every argument bound at ``delayed(fn)(item)`` call time —
|
|
53
|
+
loky's worker IPC is pickle-based, so caller objects duplicate
|
|
54
|
+
rather than alias. For DataFrame-bearing specs this means
|
|
55
|
+
``n_jobs × spec_size`` resident memory; a 128-core sweep over a
|
|
56
|
+
30 GB-per-cell BCa bootstrap will OOM-kill before any cell
|
|
57
|
+
finishes. Pass DataFrames via file-path indirection (parquet
|
|
58
|
+
path + reload inside ``fn``) for memory-bounded parallelism, or
|
|
59
|
+
cap ``n_jobs`` at ``min(N, available_RAM_GB / spec_size_GB)``.
|
|
60
|
+
NumPy arrays >~1 MB transparently use joblib's memmap fast path
|
|
61
|
+
and do NOT duplicate. See
|
|
62
|
+
``methodology/parallelism.md#memory-model-worker-copy-semantics``
|
|
63
|
+
for the shared-state pattern with a worked example.
|
|
51
64
|
|
|
52
65
|
Parameters
|
|
53
66
|
----------
|
|
@@ -53,8 +53,10 @@ __all__ = [
|
|
|
53
53
|
"CostMatrix",
|
|
54
54
|
"PlattFit",
|
|
55
55
|
"bayes_optimal_threshold",
|
|
56
|
+
"fit_beta_binary",
|
|
56
57
|
"fit_beta_calibrator",
|
|
57
58
|
"fit_isotonic_calibrator",
|
|
59
|
+
"fit_platt_binary",
|
|
58
60
|
"fit_platt_calibrator",
|
|
59
61
|
"fit_temperature",
|
|
60
62
|
"fit_temperature_binary",
|
|
@@ -1135,6 +1137,163 @@ def fit_temperature_binary(
|
|
|
1135
1137
|
return t_optimal, apply
|
|
1136
1138
|
|
|
1137
1139
|
|
|
1140
|
+
def fit_platt_binary(
|
|
1141
|
+
y_true: np.ndarray, y_score: np.ndarray
|
|
1142
|
+
) -> tuple[tuple[float, float], Callable[[np.ndarray], np.ndarray]]:
|
|
1143
|
+
r"""Binary-probability adapter for :func:`fit_platt_calibrator`.
|
|
1144
|
+
|
|
1145
|
+
Mirror of :func:`fit_temperature_binary`: returns ``((a, b), apply)``
|
|
1146
|
+
where ``(a, b)`` are the fitted Platt slope and intercept and
|
|
1147
|
+
``apply: (n,) -> (n,)`` is a scalar-in / scalar-out callable that
|
|
1148
|
+
maps any input probability array through :math:`\sigma(a \cdot s + b)`.
|
|
1149
|
+
|
|
1150
|
+
Trivially wraps :func:`fit_platt_calibrator` — added in v0.40.0 to
|
|
1151
|
+
provide naming consistency with :func:`fit_temperature_binary` and
|
|
1152
|
+
:func:`fit_beta_binary` for the 4-calibrator audit-battery pattern
|
|
1153
|
+
(temperature, isotonic, Platt, Beta). The ``(a, b)`` tuple is exposed
|
|
1154
|
+
explicitly so consumers can log it in a :class:`RunManifest` without
|
|
1155
|
+
introspecting the :class:`PlattFit` dataclass.
|
|
1156
|
+
|
|
1157
|
+
Parameters
|
|
1158
|
+
----------
|
|
1159
|
+
y_true : np.ndarray, shape (n,)
|
|
1160
|
+
Binary validation labels in {0, 1}.
|
|
1161
|
+
y_score : np.ndarray, shape (n,)
|
|
1162
|
+
Validation predicted probabilities of class 1, in [0, 1]. Values
|
|
1163
|
+
at the extremes are clipped to ``[1e-7, 1 - 1e-7]`` by the
|
|
1164
|
+
underlying Platt fitter.
|
|
1165
|
+
|
|
1166
|
+
Returns
|
|
1167
|
+
-------
|
|
1168
|
+
tuple
|
|
1169
|
+
``((a, b), apply)`` — ``a`` is the Platt slope, ``b`` is the
|
|
1170
|
+
intercept, ``apply`` maps probabilities through the fitted
|
|
1171
|
+
sigmoid.
|
|
1172
|
+
|
|
1173
|
+
Raises
|
|
1174
|
+
------
|
|
1175
|
+
ValueError
|
|
1176
|
+
On shape mismatch, empty input, non-finite scores, or single-class
|
|
1177
|
+
``y_true`` (propagated from :func:`fit_platt_calibrator`).
|
|
1178
|
+
RuntimeError
|
|
1179
|
+
If the L-BFGS-B optimizer fails to converge.
|
|
1180
|
+
|
|
1181
|
+
Examples
|
|
1182
|
+
--------
|
|
1183
|
+
>>> import numpy as np
|
|
1184
|
+
>>> rng = np.random.default_rng(0)
|
|
1185
|
+
>>> n = 500
|
|
1186
|
+
>>> y_val = rng.binomial(1, 0.3, size=n).astype(int)
|
|
1187
|
+
>>> p_val = np.clip(y_val * 0.6 + rng.normal(0, 0.2, n), 0.01, 0.99)
|
|
1188
|
+
>>> (a, b), apply = fit_platt_binary(y_val, p_val)
|
|
1189
|
+
>>> isinstance(a, float) and isinstance(b, float)
|
|
1190
|
+
True
|
|
1191
|
+
>>> apply(np.array([0.1, 0.5, 0.9])).shape == (3,)
|
|
1192
|
+
True
|
|
1193
|
+
|
|
1194
|
+
See Also
|
|
1195
|
+
--------
|
|
1196
|
+
fit_platt_calibrator : underlying canonical Platt fitter (returns ``PlattFit``).
|
|
1197
|
+
fit_temperature_binary : 1-parameter sibling.
|
|
1198
|
+
fit_beta_binary : 3-parameter sibling.
|
|
1199
|
+
"""
|
|
1200
|
+
fit = fit_platt_calibrator(y_true, y_score)
|
|
1201
|
+
return (fit.a, fit.b), fit.transform
|
|
1202
|
+
|
|
1203
|
+
|
|
1204
|
+
def fit_beta_binary(
|
|
1205
|
+
y_true: np.ndarray, y_score: np.ndarray
|
|
1206
|
+
) -> tuple[tuple[float, float, float], Callable[[np.ndarray], np.ndarray]]:
|
|
1207
|
+
r"""Binary-probability adapter for :func:`fit_beta_calibrator` per Kull et al. 2017.
|
|
1208
|
+
|
|
1209
|
+
Mirror of :func:`fit_temperature_binary`: returns
|
|
1210
|
+
``((a, b, c), apply)`` where ``(a, b, c)`` are the 3-parameter Beta
|
|
1211
|
+
calibration coefficients (``a`` on :math:`\log s`, ``b`` on
|
|
1212
|
+
:math:`\log(1-s)`, ``c`` intercept) and ``apply`` is the scalar-in /
|
|
1213
|
+
scalar-out callable.
|
|
1214
|
+
|
|
1215
|
+
Added in v0.40.0 for naming consistency with
|
|
1216
|
+
:func:`fit_temperature_binary` and :func:`fit_platt_binary` in the
|
|
1217
|
+
4-calibrator audit-battery pattern. Implemented directly (rather
|
|
1218
|
+
than wrapping :func:`fit_beta_calibrator`) so the fitted ``(a, b, c)``
|
|
1219
|
+
parameters can be exposed alongside the apply callable — the
|
|
1220
|
+
existing :func:`fit_beta_calibrator` returns only the callable.
|
|
1221
|
+
|
|
1222
|
+
Beta calibration is a strict generalization of Platt scaling
|
|
1223
|
+
(Kull 2017 §3); it empirically dominates Platt on most real-world
|
|
1224
|
+
classifiers at the cost of one extra parameter. Unlike Platt and
|
|
1225
|
+
isotonic, the Beta sigmoid is *not* monotone in the raw score.
|
|
1226
|
+
|
|
1227
|
+
Parameters
|
|
1228
|
+
----------
|
|
1229
|
+
y_true : np.ndarray, shape (n,)
|
|
1230
|
+
Binary validation labels in {0, 1}.
|
|
1231
|
+
y_score : np.ndarray, shape (n,)
|
|
1232
|
+
Validation predicted probabilities of class 1, in (0, 1). Values
|
|
1233
|
+
at the extremes are clipped to ``[1e-7, 1 - 1e-7]`` so the
|
|
1234
|
+
log-link is finite.
|
|
1235
|
+
|
|
1236
|
+
Returns
|
|
1237
|
+
-------
|
|
1238
|
+
tuple
|
|
1239
|
+
``((a, b, c), apply)`` — ``(a, b, c)`` are the Beta calibration
|
|
1240
|
+
coefficients; ``apply`` maps probabilities through
|
|
1241
|
+
:math:`\sigma(a \log s + b \log(1-s) + c)`.
|
|
1242
|
+
|
|
1243
|
+
Raises
|
|
1244
|
+
------
|
|
1245
|
+
ValueError
|
|
1246
|
+
On shape mismatch, empty input, non-finite scores, or single-class
|
|
1247
|
+
``y_true``.
|
|
1248
|
+
|
|
1249
|
+
Examples
|
|
1250
|
+
--------
|
|
1251
|
+
>>> import numpy as np
|
|
1252
|
+
>>> rng = np.random.default_rng(0)
|
|
1253
|
+
>>> n = 500
|
|
1254
|
+
>>> y_val = rng.binomial(1, 0.3, size=n).astype(int)
|
|
1255
|
+
>>> p_val = np.clip(y_val * 0.6 + rng.normal(0, 0.2, n), 0.01, 0.99)
|
|
1256
|
+
>>> (a, b, c), apply = fit_beta_binary(y_val, p_val)
|
|
1257
|
+
>>> all(isinstance(x, float) for x in (a, b, c))
|
|
1258
|
+
True
|
|
1259
|
+
>>> apply(np.array([0.1, 0.5, 0.9])).shape == (3,)
|
|
1260
|
+
True
|
|
1261
|
+
|
|
1262
|
+
See Also
|
|
1263
|
+
--------
|
|
1264
|
+
fit_beta_calibrator : underlying Beta fitter (returns only the callable).
|
|
1265
|
+
fit_temperature_binary : 1-parameter sibling.
|
|
1266
|
+
fit_platt_binary : 2-parameter sibling.
|
|
1267
|
+
|
|
1268
|
+
References
|
|
1269
|
+
----------
|
|
1270
|
+
Kull, M., Filho, T. S., & Flach, P. "Beta calibration: a well-founded
|
|
1271
|
+
and easily implemented improvement on logistic calibration for binary
|
|
1272
|
+
classifiers." AISTATS 2017. arXiv:1607.06770.
|
|
1273
|
+
"""
|
|
1274
|
+
from sklearn.linear_model import LogisticRegression # noqa: PLC0415
|
|
1275
|
+
|
|
1276
|
+
y_true_arr, y_score_arr = _validate_calibrator_inputs(y_true, y_score)
|
|
1277
|
+
s_clipped = np.clip(y_score_arr, _SCORE_CLIP_LO, _SCORE_CLIP_HI)
|
|
1278
|
+
features = np.column_stack([np.log(s_clipped), np.log(1.0 - s_clipped)])
|
|
1279
|
+
lr = LogisticRegression(C=1e9, solver="lbfgs", max_iter=2000)
|
|
1280
|
+
lr.fit(features, y_true_arr)
|
|
1281
|
+
a = float(lr.coef_[0][0])
|
|
1282
|
+
b = float(lr.coef_[0][1])
|
|
1283
|
+
c = float(lr.intercept_[0])
|
|
1284
|
+
|
|
1285
|
+
def apply(scores: np.ndarray) -> np.ndarray:
|
|
1286
|
+
arr = np.asarray(scores, dtype=float).ravel()
|
|
1287
|
+
if not np.isfinite(arr).all():
|
|
1288
|
+
raise ValueError("scores contains NaN or inf")
|
|
1289
|
+
clipped = np.clip(arr, _SCORE_CLIP_LO, _SCORE_CLIP_HI)
|
|
1290
|
+
z = a * np.log(clipped) + b * np.log(1.0 - clipped) + c
|
|
1291
|
+
out: np.ndarray = (1.0 / (1.0 + np.exp(-z))).astype(float)
|
|
1292
|
+
return out
|
|
1293
|
+
|
|
1294
|
+
return (a, b, c), apply
|
|
1295
|
+
|
|
1296
|
+
|
|
1138
1297
|
def fit_temperature_oracle(
|
|
1139
1298
|
y_true: np.ndarray, y_score: np.ndarray
|
|
1140
1299
|
) -> tuple[float, Callable[[np.ndarray], np.ndarray]]:
|
|
@@ -218,9 +218,16 @@ class LeakageCheck(Protocol):
|
|
|
218
218
|
name : str
|
|
219
219
|
Stable identifier for the check, written into the
|
|
220
220
|
:class:`LeakageFinding` and used for downstream filtering.
|
|
221
|
+
Read-only (a ``@property`` on the Protocol) so the natural
|
|
222
|
+
``@dataclass(frozen=True)`` implementation pattern is
|
|
223
|
+
compatible with ``mypy --strict``: a frozen attribute satisfies
|
|
224
|
+
a read-only Protocol member but not a settable one (PEP 544).
|
|
221
225
|
"""
|
|
222
226
|
|
|
223
|
-
|
|
227
|
+
@property
|
|
228
|
+
def name(self) -> str: # pragma: no cover
|
|
229
|
+
"""Stable check identifier (read-only)."""
|
|
230
|
+
...
|
|
224
231
|
|
|
225
232
|
def validate(self, splits: Mapping[str, EvalSlice]) -> LeakageFinding: # pragma: no cover
|
|
226
233
|
"""Validate the splits and return one :class:`LeakageFinding`."""
|
|
@@ -8,7 +8,7 @@ JSON-serializable values.
|
|
|
8
8
|
|
|
9
9
|
from __future__ import annotations
|
|
10
10
|
|
|
11
|
-
from collections.abc import Mapping
|
|
11
|
+
from collections.abc import Collection, Mapping
|
|
12
12
|
from dataclasses import dataclass
|
|
13
13
|
from typing import TYPE_CHECKING, Final, Literal, overload
|
|
14
14
|
|
|
@@ -95,6 +95,7 @@ def _empty_strategy_guard(
|
|
|
95
95
|
|
|
96
96
|
__all__ = [
|
|
97
97
|
"DEFAULT_ASSUMED_PRIORS",
|
|
98
|
+
"SINGLE_CLASS_INCOMPATIBLE_METRICS",
|
|
98
99
|
"ThresholdResult",
|
|
99
100
|
"brier_decomposition",
|
|
100
101
|
"brier_score",
|
|
@@ -104,6 +105,7 @@ __all__ = [
|
|
|
104
105
|
"expected_calibration_error_l2",
|
|
105
106
|
"expected_calibration_error_l2_debiased",
|
|
106
107
|
"headline_metrics",
|
|
108
|
+
"is_metric_defined_for_slice",
|
|
107
109
|
"metrics_at_threshold",
|
|
108
110
|
"pr_auc",
|
|
109
111
|
"precision_at_prior",
|
|
@@ -115,6 +117,88 @@ __all__ = [
|
|
|
115
117
|
"stratified_recall",
|
|
116
118
|
]
|
|
117
119
|
|
|
120
|
+
SINGLE_CLASS_INCOMPATIBLE_METRICS: Final[frozenset[str]] = frozenset({"auroc", "auprc"})
|
|
121
|
+
"""Metrics that are mathematically undefined on single-class slices.
|
|
122
|
+
|
|
123
|
+
AUROC and AUPRC are *ranking* metrics — both require at least one
|
|
124
|
+
positive AND one negative example to produce a well-defined score.
|
|
125
|
+
sklearn's implementations return degenerate ``1.0`` or ``0.0`` (or
|
|
126
|
+
warn) when called on single-class input; those values entering
|
|
127
|
+
bootstrap/CI artifacts produce misleading downstream evidence.
|
|
128
|
+
|
|
129
|
+
Overridable per call to :func:`is_metric_defined_for_slice` via the
|
|
130
|
+
``incompatible_metrics`` kwarg. Surfaced by v0.39.0 / closes #39.
|
|
131
|
+
"""
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def is_metric_defined_for_slice(
|
|
135
|
+
metric_name: str,
|
|
136
|
+
*,
|
|
137
|
+
is_single_class: bool,
|
|
138
|
+
incompatible_metrics: Collection[str] = SINGLE_CLASS_INCOMPATIBLE_METRICS,
|
|
139
|
+
) -> bool:
|
|
140
|
+
"""Return ``True`` iff a metric is well-defined for the given class distribution.
|
|
141
|
+
|
|
142
|
+
Use at the per-cell layer to filter ``(metric, slice)`` combinations
|
|
143
|
+
BEFORE bootstrap so degenerate values never pollute the downstream
|
|
144
|
+
artifacts (CI bands, MDE estimates, marginal-effect summaries).
|
|
145
|
+
|
|
146
|
+
Parameters
|
|
147
|
+
----------
|
|
148
|
+
metric_name : str
|
|
149
|
+
Metric identifier. Case-insensitive. Common values:
|
|
150
|
+
``"auroc"``, ``"auprc"``, ``"ece"``, ``"brier"``, ``"recall"``,
|
|
151
|
+
``"precision"``, ``"recall_at_fpr"``.
|
|
152
|
+
is_single_class : bool
|
|
153
|
+
Whether the slice has only one observed class (all-positive
|
|
154
|
+
or all-negative ``y_true``). Caller computes this from the
|
|
155
|
+
slice's label vector — the function does not inspect data.
|
|
156
|
+
incompatible_metrics : Collection[str], optional
|
|
157
|
+
Metric names that are undefined on single-class slices. Default
|
|
158
|
+
:data:`SINGLE_CLASS_INCOMPATIBLE_METRICS` (``{"auroc", "auprc"}``).
|
|
159
|
+
Override to widen (e.g., add ``"recall_at_fpr"`` if your threshold
|
|
160
|
+
selector also requires both classes) or narrow per call. Compared
|
|
161
|
+
case-insensitively.
|
|
162
|
+
|
|
163
|
+
Returns
|
|
164
|
+
-------
|
|
165
|
+
bool
|
|
166
|
+
``True`` if the metric is well-defined for the slice's class
|
|
167
|
+
distribution; ``False`` if it would be degenerate.
|
|
168
|
+
|
|
169
|
+
Examples
|
|
170
|
+
--------
|
|
171
|
+
>>> is_metric_defined_for_slice("auroc", is_single_class=True)
|
|
172
|
+
False
|
|
173
|
+
>>> is_metric_defined_for_slice("auroc", is_single_class=False)
|
|
174
|
+
True
|
|
175
|
+
>>> is_metric_defined_for_slice("ece", is_single_class=True)
|
|
176
|
+
True
|
|
177
|
+
>>> is_metric_defined_for_slice("AUROC", is_single_class=True)
|
|
178
|
+
False
|
|
179
|
+
>>> is_metric_defined_for_slice(
|
|
180
|
+
... "recall_at_fpr",
|
|
181
|
+
... is_single_class=True,
|
|
182
|
+
... incompatible_metrics=frozenset({"auroc", "auprc", "recall_at_fpr"}),
|
|
183
|
+
... )
|
|
184
|
+
False
|
|
185
|
+
|
|
186
|
+
Notes
|
|
187
|
+
-----
|
|
188
|
+
The function takes ``is_single_class`` rather than inspecting
|
|
189
|
+
``y_true`` directly to keep the primitive cheap (no array
|
|
190
|
+
materialisation) and composable with caller-side slice taxonomies
|
|
191
|
+
where the single-class property is known a priori from slice
|
|
192
|
+
semantics (e.g., a benign-only or attack-only split).
|
|
193
|
+
|
|
194
|
+
See Also
|
|
195
|
+
--------
|
|
196
|
+
SINGLE_CLASS_INCOMPATIBLE_METRICS : the default incompatibility set.
|
|
197
|
+
"""
|
|
198
|
+
if not is_single_class:
|
|
199
|
+
return True
|
|
200
|
+
return metric_name.lower() not in {m.lower() for m in incompatible_metrics}
|
|
201
|
+
|
|
118
202
|
|
|
119
203
|
@dataclass(frozen=True, slots=True)
|
|
120
204
|
class ThresholdResult:
|
|
@@ -75,6 +75,7 @@
|
|
|
75
75
|
"RecallAtFprResult",
|
|
76
76
|
"RunManifest",
|
|
77
77
|
"RunResult",
|
|
78
|
+
"SINGLE_CLASS_INCOMPATIBLE_METRICS",
|
|
78
79
|
"Scorer",
|
|
79
80
|
"SimilarityAuditFinding",
|
|
80
81
|
"SimilarityAuditReport",
|
|
@@ -133,9 +134,11 @@
|
|
|
133
134
|
"fdr_bh_correct",
|
|
134
135
|
"figure_metadata",
|
|
135
136
|
"file_sha256",
|
|
137
|
+
"fit_beta_binary",
|
|
136
138
|
"fit_beta_calibrator",
|
|
137
139
|
"fit_isotonic_calibrator",
|
|
138
140
|
"fit_operating_points",
|
|
141
|
+
"fit_platt_binary",
|
|
139
142
|
"fit_platt_calibrator",
|
|
140
143
|
"fit_temperature",
|
|
141
144
|
"fit_temperature_binary",
|
|
@@ -144,6 +147,7 @@
|
|
|
144
147
|
"frozen_config",
|
|
145
148
|
"headline_metrics",
|
|
146
149
|
"headline_present_gate",
|
|
150
|
+
"is_metric_defined_for_slice",
|
|
147
151
|
"iter_folds_with_pool",
|
|
148
152
|
"load_prediction_arrays",
|
|
149
153
|
"low_fpr_feasibility_gate",
|
|
@@ -827,6 +831,12 @@
|
|
|
827
831
|
"kind": "class",
|
|
828
832
|
"signature": "(run_id: 'str', git_sha: 'str | None', config: 'dict[str, object]', by_slice: 'dict[str, dict[str, object]]' = <factory>, by_fold: 'dict[str, RunResult]' = <factory>, fold_summary: 'dict[str, dict[str, object]]' = <factory>, claim_report: 'dict[str, object]' = <factory>, prediction_artifacts: 'list[dict[str, object]]' = <factory>, evidence_axes: 'list[dict[str, object]]' = <factory>, pairing_metadata: 'dict[str, object]' = <factory>, aggregate_evidence: 'dict[str, object]' = <factory>, threshold_policy: 'dict[str, object]' = <factory>, schema_version: 'str' = 'v1') -> None"
|
|
829
833
|
},
|
|
834
|
+
"SINGLE_CLASS_INCOMPATIBLE_METRICS": {
|
|
835
|
+
"doc_first_line": "Build an immutable unordered collection of unique elements.",
|
|
836
|
+
"kind": "value",
|
|
837
|
+
"type": "frozenset",
|
|
838
|
+
"value": "['auprc', 'auroc']"
|
|
839
|
+
},
|
|
830
840
|
"Scorer": {
|
|
831
841
|
"bases": [
|
|
832
842
|
"Protocol"
|
|
@@ -1026,7 +1036,7 @@
|
|
|
1026
1036
|
"doc_first_line": "str(object='') -> str",
|
|
1027
1037
|
"kind": "value",
|
|
1028
1038
|
"type": "str",
|
|
1029
|
-
"value": "'0.
|
|
1039
|
+
"value": "'0.40.0'"
|
|
1030
1040
|
},
|
|
1031
1041
|
"apply_operating_points": {
|
|
1032
1042
|
"doc_first_line": "Apply fitted thresholds to a mixed-class or single-class target slice.",
|
|
@@ -1188,6 +1198,11 @@
|
|
|
1188
1198
|
"kind": "function",
|
|
1189
1199
|
"signature": "(path: 'Path | str', *, strict: 'bool' = False) -> 'str | None'"
|
|
1190
1200
|
},
|
|
1201
|
+
"fit_beta_binary": {
|
|
1202
|
+
"doc_first_line": "Binary-probability adapter for :func:`fit_beta_calibrator` per Kull et al. 2017.",
|
|
1203
|
+
"kind": "function",
|
|
1204
|
+
"signature": "(y_true: 'np.ndarray', y_score: 'np.ndarray') -> 'tuple[tuple[float, float, float], Callable[[np.ndarray], np.ndarray]]'"
|
|
1205
|
+
},
|
|
1191
1206
|
"fit_beta_calibrator": {
|
|
1192
1207
|
"doc_first_line": "Beta calibration per Kull et al. 2017 [#kull]_.",
|
|
1193
1208
|
"kind": "function",
|
|
@@ -1203,6 +1218,11 @@
|
|
|
1203
1218
|
"kind": "function",
|
|
1204
1219
|
"signature": "(y_true: 'np.ndarray', y_score: 'np.ndarray', selectors: 'Sequence[ThresholdSelector]', *, fitted_on_slice: 'str' = '', scorer_name: 'str' = '') -> 'dict[str, FittedOperatingPoint]'"
|
|
1205
1220
|
},
|
|
1221
|
+
"fit_platt_binary": {
|
|
1222
|
+
"doc_first_line": "Binary-probability adapter for :func:`fit_platt_calibrator`.",
|
|
1223
|
+
"kind": "function",
|
|
1224
|
+
"signature": "(y_true: 'np.ndarray', y_score: 'np.ndarray') -> 'tuple[tuple[float, float], Callable[[np.ndarray], np.ndarray]]'"
|
|
1225
|
+
},
|
|
1206
1226
|
"fit_platt_calibrator": {
|
|
1207
1227
|
"doc_first_line": "Platt 1999 [#platt]_ sigmoid scaling with Lin 2007 [#lin]_ Laplace-smoothed targets.",
|
|
1208
1228
|
"kind": "function",
|
|
@@ -1243,6 +1263,11 @@
|
|
|
1243
1263
|
"kind": "function",
|
|
1244
1264
|
"signature": "(path: 'str' = 'headline', *, severity: 'GateSeverity' = 'error') -> 'EvidenceGate'"
|
|
1245
1265
|
},
|
|
1266
|
+
"is_metric_defined_for_slice": {
|
|
1267
|
+
"doc_first_line": "Return ``True`` iff a metric is well-defined for the given class distribution.",
|
|
1268
|
+
"kind": "function",
|
|
1269
|
+
"signature": "(metric_name: 'str', *, is_single_class: 'bool', incompatible_metrics: 'Collection[str]' = frozenset({'auprc', 'auroc'})) -> 'bool'"
|
|
1270
|
+
},
|
|
1246
1271
|
"iter_folds_with_pool": {
|
|
1247
1272
|
"doc_first_line": "Compose a :class:`Splitter` with a :class:`PoolBuilder`.",
|
|
1248
1273
|
"kind": "function",
|