probcal 0.1.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.
- probcal-0.1.0/.github/workflows/ci.yml +40 -0
- probcal-0.1.0/.github/workflows/docs.yml +40 -0
- probcal-0.1.0/.github/workflows/publish.yml +94 -0
- probcal-0.1.0/.gitignore +12 -0
- probcal-0.1.0/CHANGELOG.md +45 -0
- probcal-0.1.0/CITATION.cff +11 -0
- probcal-0.1.0/LICENSE +21 -0
- probcal-0.1.0/PKG-INFO +154 -0
- probcal-0.1.0/README.md +116 -0
- probcal-0.1.0/docs/DECISIONS.md +342 -0
- probcal-0.1.0/docs/LICENSING.md +16 -0
- probcal-0.1.0/docs/api/calibrators.md +15 -0
- probcal-0.1.0/docs/api/metrics.md +13 -0
- probcal-0.1.0/docs/api/tools.md +17 -0
- probcal-0.1.0/docs/api.md +11 -0
- probcal-0.1.0/docs/changelog.md +1 -0
- probcal-0.1.0/docs/concepts/auto-selection.md +79 -0
- probcal-0.1.0/docs/concepts/data-splitting.md +141 -0
- probcal-0.1.0/docs/concepts/img/belt.png +0 -0
- probcal-0.1.0/docs/concepts/img/comparison.png +0 -0
- probcal-0.1.0/docs/concepts/img/interval.png +0 -0
- probcal-0.1.0/docs/concepts/img/reliability_logit.png +0 -0
- probcal-0.1.0/docs/concepts/img/reliability_probability.png +0 -0
- probcal-0.1.0/docs/concepts/img/selection.png +0 -0
- probcal-0.1.0/docs/concepts/inverse-maps.md +119 -0
- probcal-0.1.0/docs/concepts/methods-distribution-free.md +156 -0
- probcal-0.1.0/docs/concepts/methods-nonparametric.md +248 -0
- probcal-0.1.0/docs/concepts/methods-parametric.md +240 -0
- probcal-0.1.0/docs/concepts/metrics.md +295 -0
- probcal-0.1.0/docs/concepts/offset.md +169 -0
- probcal-0.1.0/docs/concepts/shap-calibration.md +138 -0
- probcal-0.1.0/docs/concepts/visualization.md +98 -0
- probcal-0.1.0/docs/concepts/why-calibration.md +196 -0
- probcal-0.1.0/docs/faq.md +93 -0
- probcal-0.1.0/docs/getting-started.md +108 -0
- probcal-0.1.0/docs/how-it-works.md +81 -0
- probcal-0.1.0/docs/index.md +23 -0
- probcal-0.1.0/docs/javascripts/mathjax.js +16 -0
- probcal-0.1.0/docs/notebooks/pd_calibration_walkthrough.ipynb +453 -0
- probcal-0.1.0/mkdocs.yml +97 -0
- probcal-0.1.0/pyproject.toml +80 -0
- probcal-0.1.0/src/probcal/__init__.py +55 -0
- probcal-0.1.0/src/probcal/_math.py +715 -0
- probcal-0.1.0/src/probcal/_results.py +233 -0
- probcal-0.1.0/src/probcal/_validation.py +101 -0
- probcal-0.1.0/src/probcal/attribution.py +171 -0
- probcal-0.1.0/src/probcal/base.py +250 -0
- probcal-0.1.0/src/probcal/bayesian.py +255 -0
- probcal-0.1.0/src/probcal/binning.py +202 -0
- probcal-0.1.0/src/probcal/curves.py +204 -0
- probcal-0.1.0/src/probcal/datasets.py +97 -0
- probcal-0.1.0/src/probcal/isotonic.py +178 -0
- probcal-0.1.0/src/probcal/metrics/__init__.py +170 -0
- probcal-0.1.0/src/probcal/metrics/binned.py +173 -0
- probcal-0.1.0/src/probcal/metrics/grade.py +123 -0
- probcal-0.1.0/src/probcal/metrics/regression.py +103 -0
- probcal-0.1.0/src/probcal/metrics/scores.py +136 -0
- probcal-0.1.0/src/probcal/metrics/smooth.py +124 -0
- probcal-0.1.0/src/probcal/offset.py +211 -0
- probcal-0.1.0/src/probcal/parametric.py +323 -0
- probcal-0.1.0/src/probcal/plots.py +184 -0
- probcal-0.1.0/src/probcal/py.typed +0 -0
- probcal-0.1.0/src/probcal/selection.py +174 -0
- probcal-0.1.0/src/probcal/spline.py +198 -0
- probcal-0.1.0/src/probcal/thresholds.py +55 -0
- probcal-0.1.0/src/probcal/vennabers.py +165 -0
- probcal-0.1.0/src/probcal/wrapper.py +295 -0
- probcal-0.1.0/tests/test_attribution.py +132 -0
- probcal-0.1.0/tests/test_base.py +68 -0
- probcal-0.1.0/tests/test_bayesian.py +101 -0
- probcal-0.1.0/tests/test_binning.py +94 -0
- probcal-0.1.0/tests/test_curves.py +100 -0
- probcal-0.1.0/tests/test_datasets.py +59 -0
- probcal-0.1.0/tests/test_inverse_maps.py +142 -0
- probcal-0.1.0/tests/test_isotonic.py +101 -0
- probcal-0.1.0/tests/test_math.py +226 -0
- probcal-0.1.0/tests/test_math_reference.py +114 -0
- probcal-0.1.0/tests/test_metrics_binned.py +73 -0
- probcal-0.1.0/tests/test_metrics_evaluate.py +43 -0
- probcal-0.1.0/tests/test_metrics_grade.py +70 -0
- probcal-0.1.0/tests/test_metrics_regression.py +53 -0
- probcal-0.1.0/tests/test_metrics_scores.py +91 -0
- probcal-0.1.0/tests/test_metrics_smooth.py +62 -0
- probcal-0.1.0/tests/test_no_leakage.py +70 -0
- probcal-0.1.0/tests/test_offset.py +89 -0
- probcal-0.1.0/tests/test_package.py +42 -0
- probcal-0.1.0/tests/test_parametric.py +201 -0
- probcal-0.1.0/tests/test_plots.py +81 -0
- probcal-0.1.0/tests/test_results.py +94 -0
- probcal-0.1.0/tests/test_selection.py +125 -0
- probcal-0.1.0/tests/test_spline.py +91 -0
- probcal-0.1.0/tests/test_validation.py +66 -0
- probcal-0.1.0/tests/test_vennabers.py +105 -0
- probcal-0.1.0/tests/test_wrapper.py +156 -0
- probcal-0.1.0/uv.lock +2729 -0
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches:
|
|
6
|
+
- main
|
|
7
|
+
pull_request:
|
|
8
|
+
workflow_dispatch:
|
|
9
|
+
|
|
10
|
+
jobs:
|
|
11
|
+
test:
|
|
12
|
+
name: Test on Python ${{ matrix.python-version }}
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
strategy:
|
|
15
|
+
fail-fast: false
|
|
16
|
+
matrix:
|
|
17
|
+
python-version: ['3.11', '3.12', '3.13']
|
|
18
|
+
steps:
|
|
19
|
+
- uses: actions/checkout@v4
|
|
20
|
+
|
|
21
|
+
- name: Install uv
|
|
22
|
+
uses: astral-sh/setup-uv@v4
|
|
23
|
+
|
|
24
|
+
- name: Set up Python
|
|
25
|
+
uses: actions/setup-python@v5
|
|
26
|
+
with:
|
|
27
|
+
python-version: ${{ matrix.python-version }}
|
|
28
|
+
|
|
29
|
+
- name: Install dependencies
|
|
30
|
+
run: uv pip install --system -e ".[dev,viz]"
|
|
31
|
+
|
|
32
|
+
- name: Lint
|
|
33
|
+
run: ruff check .
|
|
34
|
+
|
|
35
|
+
- name: Type-check
|
|
36
|
+
if: matrix.python-version == '3.11'
|
|
37
|
+
run: mypy
|
|
38
|
+
|
|
39
|
+
- name: Run tests
|
|
40
|
+
run: pytest -q
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
name: Deploy Documentation
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches:
|
|
6
|
+
- main
|
|
7
|
+
paths:
|
|
8
|
+
- 'docs/**'
|
|
9
|
+
- 'mkdocs.yml'
|
|
10
|
+
- 'src/probcal/**'
|
|
11
|
+
- 'CHANGELOG.md'
|
|
12
|
+
workflow_dispatch:
|
|
13
|
+
|
|
14
|
+
permissions:
|
|
15
|
+
contents: write
|
|
16
|
+
|
|
17
|
+
jobs:
|
|
18
|
+
deploy:
|
|
19
|
+
runs-on: ubuntu-latest
|
|
20
|
+
steps:
|
|
21
|
+
- uses: actions/checkout@v4
|
|
22
|
+
|
|
23
|
+
- name: Set up Python
|
|
24
|
+
uses: actions/setup-python@v5
|
|
25
|
+
with:
|
|
26
|
+
python-version: '3.11'
|
|
27
|
+
|
|
28
|
+
- name: Install uv
|
|
29
|
+
uses: astral-sh/setup-uv@v4
|
|
30
|
+
|
|
31
|
+
- name: Install dependencies
|
|
32
|
+
run: uv pip install --system -e ".[docs,viz]"
|
|
33
|
+
|
|
34
|
+
- name: Configure git identity
|
|
35
|
+
run: |
|
|
36
|
+
git config user.name "github-actions[bot]"
|
|
37
|
+
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
|
38
|
+
|
|
39
|
+
- name: Build and deploy
|
|
40
|
+
run: mkdocs gh-deploy --force
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- "v*"
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
test:
|
|
10
|
+
name: Test before release
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
steps:
|
|
13
|
+
- uses: actions/checkout@v4
|
|
14
|
+
|
|
15
|
+
- name: Install uv
|
|
16
|
+
uses: astral-sh/setup-uv@v4
|
|
17
|
+
|
|
18
|
+
- name: Set up Python
|
|
19
|
+
uses: actions/setup-python@v5
|
|
20
|
+
with:
|
|
21
|
+
python-version: "3.11"
|
|
22
|
+
|
|
23
|
+
- name: Install dependencies
|
|
24
|
+
run: uv pip install --system -e ".[dev,viz]"
|
|
25
|
+
|
|
26
|
+
- name: Run tests
|
|
27
|
+
run: pytest -q
|
|
28
|
+
|
|
29
|
+
build:
|
|
30
|
+
name: Build distribution
|
|
31
|
+
needs: test
|
|
32
|
+
runs-on: ubuntu-latest
|
|
33
|
+
steps:
|
|
34
|
+
- uses: actions/checkout@v4
|
|
35
|
+
|
|
36
|
+
- name: Install uv
|
|
37
|
+
uses: astral-sh/setup-uv@v4
|
|
38
|
+
with:
|
|
39
|
+
version: "latest"
|
|
40
|
+
|
|
41
|
+
- name: Set up Python
|
|
42
|
+
uses: actions/setup-python@v5
|
|
43
|
+
with:
|
|
44
|
+
python-version: "3.11"
|
|
45
|
+
|
|
46
|
+
- name: Verify tag matches package version
|
|
47
|
+
run: |
|
|
48
|
+
VERSION="$(uvx hatchling version)"
|
|
49
|
+
[ "$VERSION" = "${GITHUB_REF_NAME#v}" ] || { echo "tag ${GITHUB_REF_NAME} does not match version ${VERSION}"; exit 1; }
|
|
50
|
+
|
|
51
|
+
- name: Build package
|
|
52
|
+
run: uv build --sdist --wheel
|
|
53
|
+
|
|
54
|
+
- name: Upload distribution artifacts
|
|
55
|
+
uses: actions/upload-artifact@v4
|
|
56
|
+
with:
|
|
57
|
+
name: dist
|
|
58
|
+
path: dist/
|
|
59
|
+
|
|
60
|
+
publish-testpypi:
|
|
61
|
+
name: Publish to TestPyPI
|
|
62
|
+
needs: build
|
|
63
|
+
runs-on: ubuntu-latest
|
|
64
|
+
environment: testpypi
|
|
65
|
+
permissions:
|
|
66
|
+
id-token: write
|
|
67
|
+
steps:
|
|
68
|
+
- name: Download distribution artifacts
|
|
69
|
+
uses: actions/download-artifact@v4
|
|
70
|
+
with:
|
|
71
|
+
name: dist
|
|
72
|
+
path: dist/
|
|
73
|
+
|
|
74
|
+
- name: Publish to TestPyPI
|
|
75
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
76
|
+
with:
|
|
77
|
+
repository-url: https://test.pypi.org/legacy/
|
|
78
|
+
|
|
79
|
+
publish-pypi:
|
|
80
|
+
name: Publish to PyPI
|
|
81
|
+
needs: publish-testpypi
|
|
82
|
+
runs-on: ubuntu-latest
|
|
83
|
+
environment: pypi
|
|
84
|
+
permissions:
|
|
85
|
+
id-token: write
|
|
86
|
+
steps:
|
|
87
|
+
- name: Download distribution artifacts
|
|
88
|
+
uses: actions/download-artifact@v4
|
|
89
|
+
with:
|
|
90
|
+
name: dist
|
|
91
|
+
path: dist/
|
|
92
|
+
|
|
93
|
+
- name: Publish to PyPI
|
|
94
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
probcal-0.1.0/.gitignore
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
Format: [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); versioning: [SemVer](https://semver.org/spec/v2.0.0.html).
|
|
6
|
+
|
|
7
|
+
## [Unreleased]
|
|
8
|
+
|
|
9
|
+
## [0.1.0] - 2026-08-07
|
|
10
|
+
|
|
11
|
+
First public release on PyPI.
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
|
|
15
|
+
- `reliability_binned`: Wilson interval bounds are now forced to contain the point estimate (floating-point noise at zero-event bins could push `ci_low` above a 0.0 event rate, breaking error-bar rendering)
|
|
16
|
+
- `plot_reliability(scale="logit")`: bins with an event rate of exactly 0 or 1 (no finite logit) are omitted from the point layer instead of rendering at the clipping floor and crushing the axis; they remain visible in the count margin
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
|
|
20
|
+
- Packaging and CI: PEP 639 license metadata (SPDX `license = "MIT"` plus `license-files`, replacing the deprecated table form), Python 3.13 classifier, `Development Status :: 4 - Beta`, a tag-vs-version consistency check in the publish workflow, and a `ci.yml` matrix running lint, type-check, and the test suite on Python 3.11/3.12/3.13
|
|
21
|
+
- Documentation: generated example figures in the visualization chapter, "In probcal" code snippets in every concept chapter, and a probcal-vs-netcal guidance note in the FAQ
|
|
22
|
+
|
|
23
|
+
- Polish: README quickstart with real printed output, feature matrix vs scikit-learn/netcal/single-method packages, performance note on the deliberate absence of Rust acceleration, docs cross-linking pass
|
|
24
|
+
- Datasets, tutorial, and user docs: `make_pd_portfolio` (beta-family generative miscalibration with exact event-rate anchoring), executed tutorial notebook `pd_calibration_walkthrough.ipynb` (diagnose → select → fit → re-anchor → backtest → threshold translation, incl. the counterfactual-engine interop recipe), `getting-started.md` with a runnable quickstart and its printed output, `how-it-works.md` pipeline walkthrough with schema, three-page mkdocstrings API reference, and the FAQ (inverse-map protocol, `Target.probability` trap, selection rules)
|
|
25
|
+
- Automatic selection: `CalibratorSelector` — default 8-candidate menu per spec, inner stratified seeded K-fold with structurally out-of-fold-only scoring, criteria log_loss/brier/ici/smooth_ece/ece_sweep (plain ECE and Hosmer–Lemeshow refused), guardrail flags on pooled out-of-fold predictions, one-standard-error parsimony tie-break, winner refit on the full set, ranked `SelectionReport`; 9 tests incl. a spy-calibrator structural no-leakage check and the parsimony-on-calibrated-data behavior
|
|
26
|
+
- Wrapper flows: `CalibratedModel` — prefit flow (score + calibrate, the credit-risk canon), cv flow with duck-typed cloning (sklearn `clone` if installed, else deepcopy) and stratified seeded folds, `ensemble=False` pooled default (one calibrator on out-of-fold scores, final model refit) vs `ensemble=True` fold averaging; `offset_to` appending inspectable `LogitOffset` stages; composed `interval_inverse` and `affine_logit_coeffs_`; `predict_proba`/`predict_proba_2d`; structural no-leakage tests with a clone-surviving spy model (out-of-fold scoring disjoint from training rows, each row scored exactly once)
|
|
27
|
+
- Inverse maps and thresholds: `interval_inverse(lo, hi, *, space, buffer_logit)` implemented across the catalog — closed forms (Platt, temperature, `LogitOffset`), block-structure searchsorted (isotonic, CIR, histogram, scaling-binning), monotone bisection (beta, spline, Venn–Abers); `UnattainableTargetError` instead of silent clamping; non-monotone calibrators refuse with an explanatory `NotImplementedError`; `thresholds.py` with `calibrated_interval_to_raw` and the masterscale `calibrated_bands_to_raw`; 16 tests covering the full spec §10 list (round-trips, block-edge semantics, offset −δ shift, buffer monotonicity, space consistency)
|
|
28
|
+
- Attribution adjustment: `adjust_attributions` → `AdjustedAttribution` — affine-exact mode (exact composed Shapley values for Platt/temperature/offset via `affine_logit_coeffs_`) and Aumann–Shapley mode (exact additivity for any calibrator, central-difference fallback on degenerate rows); logit and probability scales; shap.Explanation duck-typing without a shap import; 10 tests incl. affine/AS 1e-12 equivalence and sign/rank invariance
|
|
29
|
+
- Logit offset: `LogitOffset` — explicit-delta and target-mean modes (unique bisection root, unit-tested against a brute-force grid), audit trail (`delta_`, `pre_mean_`, `post_mean_`, timestamp), `audit_report()` with pre/post guardrails, `interpret()` with odds-factor and central-tendency readings, affine logit coefficients for attribution composition
|
|
30
|
+
- Curves, belt, and plots: `curves.py` — `reliability_binned` (Wilson CIs, both scales), `reliability_loess`, `reliability_spline`, `calibration_belt` (GiViTI-style: forward LR degree selection ≤4, information-matrix pointwise bands, identity-test p-value; reimplemented from the Nattino papers); `plots.py` ([viz]-guarded) — `plot_reliability` with logit-scale probability-labeled ticks and count margin, `plot_belt`, `plot_comparison`, `plot_interval`, `plot_selection`; `SmoothReliabilityCurve` result dataclass; 11 tests incl. a hand-checked Wilson interval and belt null/alternative behavior
|
|
31
|
+
- Metrics catalog: `probcal.metrics` complete — proper scores (`log_loss`, `brier_score`, `brier_skill_score`, Murphy decomposition with optional bias correction, log-loss calibration/refinement split), binned estimators (`ece` l1/l2/max, `ece_debiased`, `ece_sweep`, `adaptive_ece`, `hosmer_lemeshow`), binning-free estimators (`smooth_ece` with self-consistent bandwidth, `ecce`, `ici`/`e50`/`e90`/`emax`, `spiegelhalter_z`), the recalibration-regression framework (`calibration_intercept`/`_slope`/`_test`, `calibration_guardrails`), per-grade backtests (`binomial_grade_test`, `jeffreys_grade_test` with traffic lights), and `evaluate()` with seeded bootstrap percentile CIs; 38 tests incl. hand-computed cases and sklearn/scipy references
|
|
32
|
+
- Spline calibrator: `SplineCalibrator` — natural cubic basis on the logit scale, penalized IRLS with second-difference roughness penalty, λ by stratified K-fold CV on log loss, effective degrees of freedom reported, post-fit monotonicity check with warning; completes the spec §6 calibrator catalog (11 of 11)
|
|
33
|
+
- Binning + Bayesian calibrators: `HistogramBinningCalibrator` (equal-mass/equal-width, Jeffreys shrinkage, empty-bin fallback, post-fit monotonicity flag), `ScalingBinningCalibrator` (Platt stage + equal-mass binning of fitted values), `BBQCalibrator` (Beta–Binomial marginal likelihood over a B-grid, Jeffreys prior, top-3 reporting), `ENIRCalibrator` (nearly-isotonic mPAVA path from raw data to the isotonic fit, BIC-weighted ensemble, `is_monotone_ = False`); 20 tests incl. hand-computed bin rates and the path-endpoint-equals-PAVA anchor
|
|
34
|
+
- Isotonic family + Venn–Abers: `IsotonicCalibrator` (PAVA step map, tie pooling, clamping, optional linear interpolation, block-structure attributes), `CenteredIsotonicCalibrator` (CIR through weight-centered block points), `VennAbersCalibrator` (IVAP with `predict_interval()`, log-loss-minimax scalarization, width reporting in `interpret()`), `CrossVennAbersCalibrator` (stratified seeded folds, geometric-mean merge, conservative envelope interval); 19 tests incl. direct-refit agreement and width-shrinkage checks
|
|
35
|
+
- Base API + parametric calibrators: `BaseCalibrator` (fit/predict_proba/predict_proba_2d/interpret, manual get_params/set_params, `is_monotone_`, `affine_logit_coeffs_`, `interval_inverse` contract stub), `PlattCalibrator` (Lin–Lin–Weng target smoothing), `TemperatureCalibrator` (safeguarded 1-D NLL solve), `BetaCalibrator` (variants abm/ab/a, betacal negative-coefficient refit, `constraint_active_`); public exports incl. `logit`/`expit`; 30 tests (identity/distortion recovery, monotonicity, small-sample stability, interpret contract)
|
|
36
|
+
- Numerical core: `_math.py` (overflow-safe logit/expit, weighted PAVA with block structure, IRLS logistic regression with ridge stabilization and separation detection, safeguarded 1-D Newton and bisection, vectorized lgamma/erf, regularized incomplete beta and lower incomplete gamma, chi-square and normal quantiles, tricube LOESS, natural cubic spline basis), `_validation.py` (score/target/weight validation with 1e-12 clipping), `_results.py` (frozen result dataclasses with `as_dict()` and aligned-table reprs); 49 unit tests plus 9 reference tests against scipy/scikit-learn/statsmodels (betainc and gammainc within 1e-12, IRLS within rtol 1e-8)
|
|
37
|
+
- Repository scaffold: src layout with docstring-only modules, tooling (uv, ruff, black, mypy, pytest), CI workflows (docs deploy, PyPI publish — tag-gated), documentation skeleton (mkdocs-material + MathJax), MIT license, citation metadata
|
|
38
|
+
- Reference verification: all 12 ⚠ references of the spec verified against primary sources; completed bibliographic records logged in `docs/DECISIONS.md` entries 10–21
|
|
39
|
+
- Theory guidebook, chunk 1: `concepts/why-calibration.md` (definitions, sources of miscalibration, decisioning and regulatory consequences, proper-scoring-rule lens) and `concepts/methods-parametric.md` (Platt, temperature, beta: derivations, parameter interpretation, worked reading)
|
|
40
|
+
- Theory guidebook, chunk 4 (completes the guidebook — ~16,400 words total): `concepts/shap-calibration.md` (identifiability obstacle, affine-exact class, Aumann–Shapley mode, invariance properties), `concepts/inverse-maps.md` (preimage identity, generalized inverses, attainability, buffer_logit drift robustness, masterscale workflow), `concepts/auto-selection.md` (selector protocol, structural no-leakage, report reading), `concepts/visualization.md` (reliability constructions, logit-scale rationale, calibration belt)
|
|
41
|
+
- Theory guidebook, chunk 3: `concepts/metrics.md` (full metric catalog with formulas and pathologies, bootstrap-CI protocol, report-reading order, selection-suitability table), `concepts/data-splitting.md` (prefit vs cv flows, ensemble vs pooled, calibration-set sizing, nested selection), `concepts/offset.md` (uniqueness of the bisection root, King–Zeng/Elkan/Tasche equivalences, worked re-anchoring, audit practice)
|
|
42
|
+
- Theory guidebook, chunk 2: `concepts/methods-nonparametric.md` (PAVA with worked micro-example, CIR, histogram binning, scaling-binning sample-complexity argument, BBQ, ENIR, spline calibration, properties table) and `concepts/methods-distribution-free.md` (IVAP construction, validity guarantee scope, scalarization caveat, exchangeability limits, CVAP geometric-mean merge)
|
|
43
|
+
|
|
44
|
+
[Unreleased]: https://github.com/wlazlod/probcal/compare/v0.1.0...HEAD
|
|
45
|
+
[0.1.0]: https://github.com/wlazlod/probcal/releases/tag/v0.1.0
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
cff-version: 1.2.0
|
|
2
|
+
message: "If you use this software, please cite it as below."
|
|
3
|
+
type: software
|
|
4
|
+
title: "probcal: universal post-hoc probability calibration for binary classifiers"
|
|
5
|
+
authors:
|
|
6
|
+
- family-names: "Wlazło"
|
|
7
|
+
given-names: "Daniel"
|
|
8
|
+
version: 0.1.0
|
|
9
|
+
date-released: 2026-08-07
|
|
10
|
+
license: MIT
|
|
11
|
+
repository-code: "https://github.com/wlazlod/probcal"
|
probcal-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Daniel Wlazło
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
probcal-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: probcal
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Universal post-hoc probability calibration for binary classifiers: methods, metrics, diagnostics, and auditable offsetting — numpy-only.
|
|
5
|
+
Project-URL: Homepage, https://github.com/wlazlod/probcal
|
|
6
|
+
Project-URL: Documentation, https://wlazlod.github.io/probcal/
|
|
7
|
+
Project-URL: Changelog, https://github.com/wlazlod/probcal/blob/main/CHANGELOG.md
|
|
8
|
+
Project-URL: Issues, https://github.com/wlazlod/probcal/issues
|
|
9
|
+
Author: Daniel Wlazło
|
|
10
|
+
License-Expression: MIT
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Keywords: binary-classification,calibration,credit-risk,isotonic-regression,probability,reliability
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Intended Audience :: Science/Research
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
20
|
+
Requires-Python: >=3.11
|
|
21
|
+
Requires-Dist: numpy>=1.26
|
|
22
|
+
Provides-Extra: dev
|
|
23
|
+
Requires-Dist: black>=24; extra == 'dev'
|
|
24
|
+
Requires-Dist: mypy>=1.10; extra == 'dev'
|
|
25
|
+
Requires-Dist: pytest>=8; extra == 'dev'
|
|
26
|
+
Requires-Dist: ruff>=0.4; extra == 'dev'
|
|
27
|
+
Requires-Dist: scikit-learn>=1.4; extra == 'dev'
|
|
28
|
+
Requires-Dist: scipy>=1.11; extra == 'dev'
|
|
29
|
+
Requires-Dist: statsmodels>=0.14; extra == 'dev'
|
|
30
|
+
Provides-Extra: docs
|
|
31
|
+
Requires-Dist: ipykernel>=6; extra == 'docs'
|
|
32
|
+
Requires-Dist: mkdocs-jupyter>=0.24; extra == 'docs'
|
|
33
|
+
Requires-Dist: mkdocs-material>=9.5; extra == 'docs'
|
|
34
|
+
Requires-Dist: mkdocstrings[python]>=0.24; extra == 'docs'
|
|
35
|
+
Provides-Extra: viz
|
|
36
|
+
Requires-Dist: matplotlib>=3.8; extra == 'viz'
|
|
37
|
+
Description-Content-Type: text/markdown
|
|
38
|
+
|
|
39
|
+
# probcal
|
|
40
|
+
|
|
41
|
+
[](https://pypi.org/project/probcal/)
|
|
42
|
+
[](LICENSE)
|
|
43
|
+
[](pyproject.toml)
|
|
44
|
+
|
|
45
|
+
Universal post-hoc probability calibration for binary classifiers: methods, metrics,
|
|
46
|
+
diagnostics, and auditable offsetting — **numpy-only**.
|
|
47
|
+
|
|
48
|
+
`probcal` unifies the binary calibration literature (Platt, temperature, beta, isotonic,
|
|
49
|
+
centered isotonic, histogram binning, scaling-binning, BBQ, ENIR, Venn–Abers, spline
|
|
50
|
+
calibration), an extensive catalog of calibration evaluation metrics and statistical tests,
|
|
51
|
+
calibration visualization on both probability and logit scales, an auditable logit-offset
|
|
52
|
+
(central tendency) adjustment, automatic method selection under nested validation, and two
|
|
53
|
+
data flows (prefit and cross-validation). Primary application domain: credit-risk PD models;
|
|
54
|
+
the package is fully general.
|
|
55
|
+
|
|
56
|
+
**Status:** released on PyPI, beta. The API is stable enough to build on; breaking changes
|
|
57
|
+
bump the minor version until 1.0.
|
|
58
|
+
|
|
59
|
+
## Installation
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
pip install probcal # runtime: numpy only
|
|
63
|
+
pip install "probcal[viz]" # + matplotlib for probcal.plots
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Development setup (tests, lint, type-check):
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
git clone https://github.com/wlazlod/probcal && cd probcal
|
|
70
|
+
uv sync --extra dev
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Quickstart
|
|
74
|
+
|
|
75
|
+
```python
|
|
76
|
+
from probcal import BetaCalibrator, make_pd_portfolio
|
|
77
|
+
from probcal.metrics import calibration_guardrails
|
|
78
|
+
|
|
79
|
+
port = make_pd_portfolio(n=8000, random_state=42) # synthetic 3% PD portfolio
|
|
80
|
+
|
|
81
|
+
g_before = calibration_guardrails(port.y, port.scores)
|
|
82
|
+
print(f"before: slope={g_before.slope:.3f} intercept={g_before.intercept:+.3f} ok={g_before.all_ok}")
|
|
83
|
+
|
|
84
|
+
cal = BetaCalibrator().fit(port.scores, port.y)
|
|
85
|
+
p = cal.predict_proba(port.scores)
|
|
86
|
+
|
|
87
|
+
g_after = calibration_guardrails(port.y, p)
|
|
88
|
+
print(f"after: slope={g_after.slope:.3f} intercept={g_after.intercept:+.3f} ok={g_after.all_ok}")
|
|
89
|
+
print()
|
|
90
|
+
print(cal.interpret())
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Output:
|
|
94
|
+
|
|
95
|
+
```text
|
|
96
|
+
before: slope=0.968 intercept=-0.765 ok=False
|
|
97
|
+
after: slope=1.000 intercept=+0.000 ok=True
|
|
98
|
+
|
|
99
|
+
Interpretation[BetaCalibrator]
|
|
100
|
+
parameter value
|
|
101
|
+
--------- --------
|
|
102
|
+
a 0.875054
|
|
103
|
+
b 1.58922
|
|
104
|
+
c -1.15227
|
|
105
|
+
- a = 0.875: sensitivity near s -> 0; a < 1 raises the smallest probabilities (model was overconfident in the low tail), a > 1 deepens them
|
|
106
|
+
- b = 1.589: sensitivity near s -> 1; the mirrored reading for the high tail
|
|
107
|
+
- c = -1.152: base-rate shift of -1.152 log-odds, odds factor 0.316
|
|
108
|
+
- identity map corresponds to (a, b, c) = (1, 1, 0)
|
|
109
|
+
- a != b (gap -0.714): asymmetric tail distortion that no symmetric (Platt/temperature) map could express
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Automatic selection, model wrapping, offsetting, and threshold translation:
|
|
113
|
+
|
|
114
|
+
```python
|
|
115
|
+
from probcal import CalibratedModel, CalibratorSelector, PlattCalibrator
|
|
116
|
+
|
|
117
|
+
sel = CalibratorSelector().fit(s_cal, y_cal) # nested CV, log-loss criterion
|
|
118
|
+
wrapped = CalibratedModel(model, PlattCalibrator(), flow="prefit").fit(X_cal, y_cal)
|
|
119
|
+
wrapped.offset_to(target_mean=0.031) # auditable central-tendency stage
|
|
120
|
+
lo_z, hi_z = wrapped.interval_inverse(0.0, 0.02, space="logit") # "PD <= 2%" in raw margins
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## Why probcal
|
|
124
|
+
|
|
125
|
+
| Capability | probcal | scikit-learn | netcal | single-method packages¹ |
|
|
126
|
+
|---|---|---|---|---|
|
|
127
|
+
| Calibration methods | 11 | 2 | many | 1 each |
|
|
128
|
+
| Runtime dependencies | numpy | scipy stack | torch stack | varies |
|
|
129
|
+
| Logit-scale diagnostics (low-PD readable) | yes | — | — | — |
|
|
130
|
+
| First-class auditable offset (central tendency) | yes | — | — | — |
|
|
131
|
+
| Automatic selection under nested validation | yes | — | — | — |
|
|
132
|
+
| Venn–Abers intervals | yes | — | — | venn-abers |
|
|
133
|
+
| Metric catalog with selection-suitability guidance | yes | partial | partial | — |
|
|
134
|
+
| Per-grade regulatory backtests (binomial, Jeffreys) | yes | — | — | — |
|
|
135
|
+
| Calibrated→raw threshold translation (`interval_inverse`) | yes | — | — | — |
|
|
136
|
+
| SHAP additivity repair on the calibrated scale | yes | — | — | — |
|
|
137
|
+
| Parameter interpretation (`interpret()`) on every method | yes | — | — | — |
|
|
138
|
+
|
|
139
|
+
¹ betacal, venn-abers, ml-insights.
|
|
140
|
+
|
|
141
|
+
Performance note: the PAVA family and special functions are hand-rolled numpy/stdlib;
|
|
142
|
+
profiling on real workloads has not shown them to be a bottleneck. Rust acceleration is
|
|
143
|
+
deliberately out of scope unless benchmarks on production-sized portfolios say otherwise.
|
|
144
|
+
|
|
145
|
+
## Documentation
|
|
146
|
+
|
|
147
|
+
Built with mkdocs-material; run locally with `uv run mkdocs serve`. Start with
|
|
148
|
+
*Getting started*, then the *Concepts* chapters — the package's theoretical foundation —
|
|
149
|
+
and the executed *PD calibration walkthrough* notebook.
|
|
150
|
+
|
|
151
|
+
## License
|
|
152
|
+
|
|
153
|
+
MIT. See [LICENSE](LICENSE) and `docs/LICENSING.md` for the conceptual-reference policy on
|
|
154
|
+
GPL-licensed R packages.
|
probcal-0.1.0/README.md
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# probcal
|
|
2
|
+
|
|
3
|
+
[](https://pypi.org/project/probcal/)
|
|
4
|
+
[](LICENSE)
|
|
5
|
+
[](pyproject.toml)
|
|
6
|
+
|
|
7
|
+
Universal post-hoc probability calibration for binary classifiers: methods, metrics,
|
|
8
|
+
diagnostics, and auditable offsetting — **numpy-only**.
|
|
9
|
+
|
|
10
|
+
`probcal` unifies the binary calibration literature (Platt, temperature, beta, isotonic,
|
|
11
|
+
centered isotonic, histogram binning, scaling-binning, BBQ, ENIR, Venn–Abers, spline
|
|
12
|
+
calibration), an extensive catalog of calibration evaluation metrics and statistical tests,
|
|
13
|
+
calibration visualization on both probability and logit scales, an auditable logit-offset
|
|
14
|
+
(central tendency) adjustment, automatic method selection under nested validation, and two
|
|
15
|
+
data flows (prefit and cross-validation). Primary application domain: credit-risk PD models;
|
|
16
|
+
the package is fully general.
|
|
17
|
+
|
|
18
|
+
**Status:** released on PyPI, beta. The API is stable enough to build on; breaking changes
|
|
19
|
+
bump the minor version until 1.0.
|
|
20
|
+
|
|
21
|
+
## Installation
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
pip install probcal # runtime: numpy only
|
|
25
|
+
pip install "probcal[viz]" # + matplotlib for probcal.plots
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Development setup (tests, lint, type-check):
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
git clone https://github.com/wlazlod/probcal && cd probcal
|
|
32
|
+
uv sync --extra dev
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Quickstart
|
|
36
|
+
|
|
37
|
+
```python
|
|
38
|
+
from probcal import BetaCalibrator, make_pd_portfolio
|
|
39
|
+
from probcal.metrics import calibration_guardrails
|
|
40
|
+
|
|
41
|
+
port = make_pd_portfolio(n=8000, random_state=42) # synthetic 3% PD portfolio
|
|
42
|
+
|
|
43
|
+
g_before = calibration_guardrails(port.y, port.scores)
|
|
44
|
+
print(f"before: slope={g_before.slope:.3f} intercept={g_before.intercept:+.3f} ok={g_before.all_ok}")
|
|
45
|
+
|
|
46
|
+
cal = BetaCalibrator().fit(port.scores, port.y)
|
|
47
|
+
p = cal.predict_proba(port.scores)
|
|
48
|
+
|
|
49
|
+
g_after = calibration_guardrails(port.y, p)
|
|
50
|
+
print(f"after: slope={g_after.slope:.3f} intercept={g_after.intercept:+.3f} ok={g_after.all_ok}")
|
|
51
|
+
print()
|
|
52
|
+
print(cal.interpret())
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Output:
|
|
56
|
+
|
|
57
|
+
```text
|
|
58
|
+
before: slope=0.968 intercept=-0.765 ok=False
|
|
59
|
+
after: slope=1.000 intercept=+0.000 ok=True
|
|
60
|
+
|
|
61
|
+
Interpretation[BetaCalibrator]
|
|
62
|
+
parameter value
|
|
63
|
+
--------- --------
|
|
64
|
+
a 0.875054
|
|
65
|
+
b 1.58922
|
|
66
|
+
c -1.15227
|
|
67
|
+
- a = 0.875: sensitivity near s -> 0; a < 1 raises the smallest probabilities (model was overconfident in the low tail), a > 1 deepens them
|
|
68
|
+
- b = 1.589: sensitivity near s -> 1; the mirrored reading for the high tail
|
|
69
|
+
- c = -1.152: base-rate shift of -1.152 log-odds, odds factor 0.316
|
|
70
|
+
- identity map corresponds to (a, b, c) = (1, 1, 0)
|
|
71
|
+
- a != b (gap -0.714): asymmetric tail distortion that no symmetric (Platt/temperature) map could express
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Automatic selection, model wrapping, offsetting, and threshold translation:
|
|
75
|
+
|
|
76
|
+
```python
|
|
77
|
+
from probcal import CalibratedModel, CalibratorSelector, PlattCalibrator
|
|
78
|
+
|
|
79
|
+
sel = CalibratorSelector().fit(s_cal, y_cal) # nested CV, log-loss criterion
|
|
80
|
+
wrapped = CalibratedModel(model, PlattCalibrator(), flow="prefit").fit(X_cal, y_cal)
|
|
81
|
+
wrapped.offset_to(target_mean=0.031) # auditable central-tendency stage
|
|
82
|
+
lo_z, hi_z = wrapped.interval_inverse(0.0, 0.02, space="logit") # "PD <= 2%" in raw margins
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Why probcal
|
|
86
|
+
|
|
87
|
+
| Capability | probcal | scikit-learn | netcal | single-method packages¹ |
|
|
88
|
+
|---|---|---|---|---|
|
|
89
|
+
| Calibration methods | 11 | 2 | many | 1 each |
|
|
90
|
+
| Runtime dependencies | numpy | scipy stack | torch stack | varies |
|
|
91
|
+
| Logit-scale diagnostics (low-PD readable) | yes | — | — | — |
|
|
92
|
+
| First-class auditable offset (central tendency) | yes | — | — | — |
|
|
93
|
+
| Automatic selection under nested validation | yes | — | — | — |
|
|
94
|
+
| Venn–Abers intervals | yes | — | — | venn-abers |
|
|
95
|
+
| Metric catalog with selection-suitability guidance | yes | partial | partial | — |
|
|
96
|
+
| Per-grade regulatory backtests (binomial, Jeffreys) | yes | — | — | — |
|
|
97
|
+
| Calibrated→raw threshold translation (`interval_inverse`) | yes | — | — | — |
|
|
98
|
+
| SHAP additivity repair on the calibrated scale | yes | — | — | — |
|
|
99
|
+
| Parameter interpretation (`interpret()`) on every method | yes | — | — | — |
|
|
100
|
+
|
|
101
|
+
¹ betacal, venn-abers, ml-insights.
|
|
102
|
+
|
|
103
|
+
Performance note: the PAVA family and special functions are hand-rolled numpy/stdlib;
|
|
104
|
+
profiling on real workloads has not shown them to be a bottleneck. Rust acceleration is
|
|
105
|
+
deliberately out of scope unless benchmarks on production-sized portfolios say otherwise.
|
|
106
|
+
|
|
107
|
+
## Documentation
|
|
108
|
+
|
|
109
|
+
Built with mkdocs-material; run locally with `uv run mkdocs serve`. Start with
|
|
110
|
+
*Getting started*, then the *Concepts* chapters — the package's theoretical foundation —
|
|
111
|
+
and the executed *PD calibration walkthrough* notebook.
|
|
112
|
+
|
|
113
|
+
## License
|
|
114
|
+
|
|
115
|
+
MIT. See [LICENSE](LICENSE) and `docs/LICENSING.md` for the conceptual-reference policy on
|
|
116
|
+
GPL-licensed R packages.
|