probcal
Universal post-hoc probability calibration for binary classifiers: methods, metrics, diagnostics, and auditable offsetting — numpy-only.
probcal unifies the binary calibration literature (Platt, temperature, beta, isotonic,
centered isotonic, histogram binning, scaling-binning, BBQ, ENIR, Venn–Abers, spline
calibration), an extensive catalog of calibration evaluation metrics and statistical tests,
calibration visualization on both probability and logit scales, an auditable logit-offset
(central tendency) adjustment, automatic method selection under nested validation, and two
data flows (prefit and cross-validation). Primary application domain: credit-risk PD models;
the package is fully general.
Status: released on PyPI, beta. The API is stable enough to build on; breaking changes bump the minor version until 1.0.
Installation
pip install probcal # runtime: numpy only
pip install "probcal[viz]" # + matplotlib for probcal.plots
Development setup (tests, lint, type-check):
git clone https://github.com/wlazlod/probcal && cd probcal
uv sync --extra dev
Quickstart
from probcal import BetaCalibrator, make_pd_portfolio
from probcal.metrics import calibration_guardrails
port = make_pd_portfolio(n=8000, random_state=42) # synthetic 3% PD portfolio
g_before = calibration_guardrails(port.y, port.scores)
print(f"before: slope={g_before.slope:.3f} intercept={g_before.intercept:+.3f} ok={g_before.all_ok}")
cal = BetaCalibrator().fit(port.scores, port.y)
p = cal.predict_proba(port.scores)
g_after = calibration_guardrails(port.y, p)
print(f"after: slope={g_after.slope:.3f} intercept={g_after.intercept:+.3f} ok={g_after.all_ok}")
print()
print(cal.interpret())
Output:
before: slope=0.968 intercept=-0.765 ok=False
after: slope=1.000 intercept=+0.000 ok=True
Interpretation[BetaCalibrator]
parameter value
--------- --------
a 0.875054
b 1.58922
c -1.15227
- a = 0.875: sensitivity near s -> 0; a < 1 raises the smallest probabilities (model was overconfident in the low tail), a > 1 deepens them
- b = 1.589: sensitivity near s -> 1; the mirrored reading for the high tail
- c = -1.152: base-rate shift of -1.152 log-odds, odds factor 0.316
- identity map corresponds to (a, b, c) = (1, 1, 0)
- a != b (gap -0.714): asymmetric tail distortion that no symmetric (Platt/temperature) map could express
Automatic selection, model wrapping, offsetting, and threshold translation:
from probcal import CalibratedModel, CalibratorSelector, PlattCalibrator
sel = CalibratorSelector().fit(s_cal, y_cal) # nested CV, log-loss criterion
wrapped = CalibratedModel(model, PlattCalibrator(), flow="prefit").fit(X_cal, y_cal)
wrapped.offset_to(target_mean=0.031) # auditable central-tendency stage
lo_z, hi_z = wrapped.interval_inverse(0.0, 0.02, space="logit") # "PD <= 2%" in raw margins
Why probcal
| Capability | probcal | scikit-learn | netcal | probcal (R)² | single-method packages¹ |
|---|---|---|---|---|---|
| Calibration methods | 11 | 2 | many | 5 binary³ | 1 each |
| Runtime dependencies | numpy | scipy stack | torch stack | native R | varies |
| Logit-scale diagnostics (low-PD readable) | yes | — | — | — | — |
| First-class auditable offset (central tendency) | yes | — | — | — | — |
| Automatic selection under nested validation | yes | — | — | — | — |
| Venn–Abers intervals | yes | — | — | — | venn-abers |
| Metric catalog with selection-suitability guidance | yes | partial | partial | partial | — |
| Per-grade regulatory backtests (binomial, Jeffreys) | yes | — | — | — | — |
| Kernel calibration error and test (SKCE, Widmann et al.) | yes | — | — | yes | — |
Calibrated→raw threshold translation (interval_inverse) |
yes | — | — | — | — |
| SHAP additivity repair on the calibrated scale | yes | — | — | — | — |
Parameter interpretation (interpret()) on every method |
yes | — | — | partial | — |
¹ betacal, venn-abers, ml-insights. ² prdm0/probcal (P. R. Diniz Marinho), unaffiliated — see the FAQ. Verified against v0.2.0, 2026-08-08. ³ Platt, temperature, beta, isotonic, histogram binning; its multiclass methods (Dirichlet, vector scaling, one-vs-rest) are out of probcal's binary scope.
Performance note: the ICI family (ici/e50/e90/emax) shares one LOESS fit anchored
to grid_size=512 quantile points instead of refitting at every observation — the same
device R's stats::lowess uses via its delta parameter (fit at spaced points, interpolate
the rest) — and smooth_ece pre-aggregates its residual measure onto bins=8192 cells
before the bandwidth bisection. Measured on this host: ici at n=50,000 dropped from 192.2s
(v0.1.2) to 1.2s, and loess(grid_size=512) now fits n=1,000,000 points in under 30s.
grid_size=None and bins=None recover the exact pre-0.1.3 values and cost, so nothing is
lost for portfolios small enough to afford it. Still numpy-only; Rust acceleration remains
out of scope unless a future workload demands it.
Documentation
Built with mkdocs-material; run locally with uv run mkdocs serve. Start with
Getting started, then the Concepts chapters — the package's theoretical foundation —
and the executed PD calibration walkthrough notebook. The Visualization chapter is a
gallery of every plot, regenerated deterministically by docs/scripts/generate_figures.py.
License
MIT. See LICENSE and docs/LICENSING.md for the conceptual-reference policy on
GPL-licensed R packages.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file probcal-0.1.3.tar.gz.
File metadata
- Download URL: probcal-0.1.3.tar.gz
- Upload date:
- Size: 858.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d5a5a6712360036ad550f4d349bffe150178c984c144e0059550a8eb3754c5bb
|
|
| MD5 |
ed44121a32f608f528bb8104edf653ff
|
|
| BLAKE2b-256 |
1d404f592fc97ef99c238b8406ae0d21f786d7c646a6ca7a6a7f33ead1bde719
|
Provenance
The following attestation bundles were made for probcal-0.1.3.tar.gz:
Publisher:
publish.yml on wlazlod/probcal
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
probcal-0.1.3.tar.gz -
Subject digest:
d5a5a6712360036ad550f4d349bffe150178c984c144e0059550a8eb3754c5bb - Sigstore transparency entry: 2493296511
- Sigstore integration time:
-
Permalink:
wlazlod/probcal@cb96a16777c3a6b37e4f04f49997b5bd6109dc95 -
Branch / Tag:
refs/tags/v0.1.3 - Owner: https://github.com/wlazlod
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@cb96a16777c3a6b37e4f04f49997b5bd6109dc95 -
Trigger Event:
push
-
Statement type:
File details
Details for the file probcal-0.1.3-py3-none-any.whl.
File metadata
- Download URL: probcal-0.1.3-py3-none-any.whl
- Upload date:
- Size: 95.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
90ade6eefc88343bc8d0c14bcbd4d4e8ff062d8189bde490c8f3f421373d82af
|
|
| MD5 |
23b4aa850b46b5db4086b6b58017b021
|
|
| BLAKE2b-256 |
cb9be855ce6c8ea7ba6e99470ccf7a4e752bdee87a423f964f5a6f5a5d4e03b7
|
Provenance
The following attestation bundles were made for probcal-0.1.3-py3-none-any.whl:
Publisher:
publish.yml on wlazlod/probcal
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
probcal-0.1.3-py3-none-any.whl -
Subject digest:
90ade6eefc88343bc8d0c14bcbd4d4e8ff062d8189bde490c8f3f421373d82af - Sigstore transparency entry: 2493296527
- Sigstore integration time:
-
Permalink:
wlazlod/probcal@cb96a16777c3a6b37e4f04f49997b5bd6109dc95 -
Branch / Tag:
refs/tags/v0.1.3 - Owner: https://github.com/wlazlod
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@cb96a16777c3a6b37e4f04f49997b5bd6109dc95 -
Trigger Event:
push
-
Statement type: