probcal
Probability calibration you can put in front of a validator: numpy-only methods, metrics, and diagnostics for binary classifiers — built for regulated PD models, fully general in practice.
What the wedge is, concretely:
- Regulated-PD first. Logit-scale diagnostics that keep 1% readable,
per-grade regulatory backtests (binomial, Jeffreys), and a central-tendency
adjustment that ships as an auditable
LogitOffsetstage — never a silent refit. - Audit trail everywhere. Every method explains its own parameters
(
interpret()); every fitted object serializes to versioned JSON (never pickle) with provenance fingerprints; every refusal names the reason and the alternative — no silent clamps, no silent approximations. - Exact inverse maps. "PD ≤ 2%" translates to a raw-score threshold,
scorecard point cut-offs, or a counterfactual target, exactly
(
interval_inverse/point_inverse/Chain) — the contract recourse engines like treecf build on. - Anytime-valid monitoring.
probcal.monitorwatches deployed calibration with e-processes: the alarm keeps its type-I guarantee at every look, and reports whether a re-offset is enough or a re-fit is due. - numpy-only core.
import probcalpulls numpy and nothing else — enforced by a test. scikit-learn/optbinning/treecf adapters are opt-in extras.
Start here: the executed end-to-end notebook takes one rare-event portfolio from GBM baseline to reliability diagnosis, selection with CIs, per-grade backtests, offsetting, threshold translation, a counterfactual, monitoring, and JSON round-trips.
probcal covers the binary calibration literature (Platt, temperature, beta,
isotonic, centered isotonic, histogram binning, scaling-binning, BBQ, ENIR,
Venn–Abers, spline), an extensive metric catalog with bootstrap CIs,
automatic method selection under nested validation, and prefit/cv data flows.
Status: released on PyPI, beta. The API is stable enough to build on; breaking changes bump the minor version until 1.0 (see API stability in the docs). Serialized artifacts have a stronger promise: every 0.x release reads schema 1, enforced by golden files in CI.
Installation
pip install probcal # runtime: numpy only
pip install "probcal[viz]" # + matplotlib for probcal.plots
pip install "probcal[sklearn]" # + scikit-learn for probcal.sklearn adapters
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.
Serialization
Every fitted object round-trips through versioned, human-readable JSON — never pickle (auditable; loading executes no code):
cal.to_json("beta.json")
loaded = BetaCalibrator.from_json("beta.json") # bit-identical predictions
cal.fingerprint() # sha-256 provenance id
Compatibility promise: every 0.x release reads schema 1, enforced by committed golden files in CI; schema bumps ship only with a converter. Details: the Serialization concepts chapter.
Calibrators at a glance
| Method | Class | Scaling |
|---|---|---|
| Platt scaling | PlattCalibrator |
O(n) per IRLS iteration |
| Temperature scaling | TemperatureCalibrator |
O(n) per IRLS iteration |
| Beta calibration | BetaCalibrator |
O(n) per IRLS iteration |
| Isotonic regression | IsotonicCalibrator |
O(n log n) fit (sort + PAVA) |
| Centered isotonic (CIR) | CenteredIsotonicCalibrator |
O(n log n) fit |
| Histogram binning | HistogramBinningCalibrator |
O(n log n) fit |
| Scaling-binning | ScalingBinningCalibrator |
O(n log n) fit |
| BBQ | BBQCalibrator |
O(n log n) fit per candidate binning |
| ENIR | ENIRCalibrator |
quadratic in unique scores; intended for m ≲ 50,000 (fit warns above) |
| Venn–Abers (IVAP) | VennAbersCalibrator |
O(n log n) fit, O(log n) per prediction |
| Spline calibration | SplineCalibrator |
O(n · k) per IRLS iteration (k knots) |
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 — for every n as of this release (0.1.3 ran the exact
path for n ≤ 8192: ~1s at n=4000 on this host; now 1–6ms for all n up to 10⁵ and ~43ms at
n=10⁶, where the O(n) pre-binning dominates). 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. GPL-licensed R packages are used as conceptual references only; no GPL code is included.
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.2.0.tar.gz.
File metadata
- Download URL: probcal-0.2.0.tar.gz
- Upload date:
- Size: 1.2 MB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c3a649e16f6dcbd7ca925ea1b981b731cb5f562527a7cdb468345e9a44e8e7f5
|
|
| MD5 |
d9cacd9690ebd2cc1443f9648ce5582e
|
|
| BLAKE2b-256 |
b515b0664ceeab90996bb4eead83bfee400ffb944f8e9fea1d2b0488032f7a78
|
Provenance
The following attestation bundles were made for probcal-0.2.0.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.2.0.tar.gz -
Subject digest:
c3a649e16f6dcbd7ca925ea1b981b731cb5f562527a7cdb468345e9a44e8e7f5 - Sigstore transparency entry: 2572685085
- Sigstore integration time:
-
Permalink:
wlazlod/probcal@90044d6aba1890dca77213fa46899bd56f8290c9 -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/wlazlod
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@90044d6aba1890dca77213fa46899bd56f8290c9 -
Trigger Event:
push
-
Statement type:
File details
Details for the file probcal-0.2.0-py3-none-any.whl.
File metadata
- Download URL: probcal-0.2.0-py3-none-any.whl
- Upload date:
- Size: 133.6 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 |
c5cad7f389447e478b46d6941a164fe91f5c5e9f47cb5e45a34cd9d24c04644c
|
|
| MD5 |
1d7141840b6746af354932b4a135b85c
|
|
| BLAKE2b-256 |
8294c40a4872316c447317844faaf1e5ac75f991c88ec1c4f527c6c328231f58
|
Provenance
The following attestation bundles were made for probcal-0.2.0-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.2.0-py3-none-any.whl -
Subject digest:
c5cad7f389447e478b46d6941a164fe91f5c5e9f47cb5e45a34cd9d24c04644c - Sigstore transparency entry: 2572685231
- Sigstore integration time:
-
Permalink:
wlazlod/probcal@90044d6aba1890dca77213fa46899bd56f8290c9 -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/wlazlod
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@90044d6aba1890dca77213fa46899bd56f8290c9 -
Trigger Event:
push
-
Statement type: