Calibration Methods¶
Every calibrator follows the scikit-learn transformer API: .fit(scores,
labels) and .transform(scores), plus sample_weight where it is
meaningful. All are binary; for multiclass see Multiclass Calibration.
Which calibrator should I use?¶
If you don’t want to think about it:
CenteredIsotonicCalibrator. It is non-parametric, has nothing
to tune, is monotone, and has no plateaus.
You want |
Use |
Notes |
|---|---|---|
A drop-in isotonic replacement, no tuning |
Collapses isotonic’s flat steps to points and interpolates. O(n). |
|
A smooth curve, and you can afford cross-validation |
Monotone spline; picks its own smoothing by CV on log-loss. |
|
A smooth curve with smoothing you control |
Same model, you set |
|
Exactly scikit-learn’s isotonic behaviour |
Thin wrapper, plus optional plateau diagnostics. |
|
Guaranteed strictly increasing output |
|
|
To allow small ranking violations if they fit better |
|
|
Accuracy near specific decision thresholds |
Research-grade; needs your operating thresholds. |
Base Classes¶
- class calibre.BaseCalibrator(enable_diagnostics=False)[source]¶
Bases:
BaseEstimator,TransformerMixinBase class for all calibrators.
All calibrator classes should inherit from this base class to ensure consistent API and functionality. This follows the scikit-learn transformer interface with fit/transform/fit_transform methods.
- Parameters:
enable_diagnostics (bool) – Whether to run plateau diagnostics after fitting.
Notes
Subclasses must implement the fit() and transform() methods. The fit_transform() method is provided by default.
Examples
>>> import numpy as np >>> from calibre import BaseCalibrator >>> >>> class SimpleCalibrator(BaseCalibrator): ... def __init__(self, enable_diagnostics=False): ... super().__init__(enable_diagnostics=enable_diagnostics) ... def fit(self, X, y): ... self.mean_ = np.mean(y) ... return self ... ... def transform(self, X): ... return np.full_like(X, self.mean_) >>> >>> X = np.array([0.1, 0.3, 0.5]) >>> y = np.array([0, 1, 1]) >>> >>> cal = SimpleCalibrator() >>> _ = cal.fit(X, y) >>> cal.transform(X) array([0.66666667, 0.66666667, 0.66666667])
- fit(X, y, sample_weight=None)[source]¶
Fit the calibrator.
This method implements the template method pattern: it handles data storage and diagnostics, while delegating the actual fitting logic to the abstract _fit_impl() method that subclasses must implement.
- Parameters:
- Returns:
Returns self for method chaining.
- Return type:
- transform(X)[source]¶
Apply calibration to new data.
- Parameters:
X (ndarray) – The values to be calibrated.
- Returns:
Calibrated values.
- Raises:
NotImplementedError – This method must be implemented by subclasses.
- Return type:
- fit_transform(X, y, sample_weight=None, **fit_params)[source]¶
Fit the calibrator and then transform the data.
This is a convenience method that combines fit() and transform() in a single call. The default implementation simply calls fit() followed by transform().
- Parameters:
- Returns:
Calibrated values.
- Return type:
Examples
>>> import numpy as np >>> from calibre import IsotonicCalibrator >>> X = np.array([0.1, 0.3, 0.5, 0.7, 0.9]) >>> y = np.array([0, 0, 1, 1, 1]) >>> cal = IsotonicCalibrator() >>> X_calibrated = cal.fit_transform(X, y)
- has_diagnostics()[source]¶
Check if diagnostic information is available.
- Returns:
True if diagnostics have been computed and are available.
- Return type:
has_diag
Examples
>>> from calibre import IsotonicCalibrator >>> import numpy as np >>> >>> X = np.array([0.1, 0.3, 0.5]) >>> y = np.array([0, 1, 1]) >>> >>> cal = IsotonicCalibrator(enable_diagnostics=True) >>> _ = cal.fit(X, y) >>> cal.has_diagnostics() True
- get_diagnostics()[source]¶
Get diagnostic results.
- Returns:
- Diagnostic results from plateau analysis, or None if
diagnostics were not computed or are not available.
- Return type:
dict | None
Examples
>>> from calibre import IsotonicCalibrator >>> import numpy as np >>> >>> X = np.array([0.1, 0.3, 0.5]) >>> y = np.array([0, 1, 1]) >>> >>> cal = IsotonicCalibrator(enable_diagnostics=True) >>> _ = cal.fit(X, y) >>> cal.get_diagnostics()["n_plateaus"] 1
- diagnostic_summary()[source]¶
Get a human-readable summary of diagnostic analysis.
- Returns:
Human-readable plateau summary.
- Return type:
summary
Examples
>>> from calibre import IsotonicCalibrator >>> import numpy as np >>> >>> X = np.array([0.1, 0.3, 0.5, 0.7, 0.9]) >>> y = np.array([0, 0, 1, 1, 1]) >>> >>> cal = IsotonicCalibrator(enable_diagnostics=True) >>> _ = cal.fit(X, y) >>> print(cal.diagnostic_summary()) Detected 2 plateau(s): Warnings: ... Plateau 1 at [0.100, 0.300] has only 2 samples - may be unreliable ... Plateau 2 at [0.500, 0.900] has only 3 samples - may be unreliable
- set_fit_request(*, sample_weight='$UNCHANGED$')¶
Configure whether metadata should be requested to be passed to the
fitmethod.Note that this method is only relevant when this estimator is used as a sub-estimator within a meta-estimator and metadata routing is enabled with
enable_metadata_routing=True(seesklearn.set_config()). Please check the User Guide on how the routing mechanism works.The options for each parameter are:
True: metadata is requested, and passed tofitif provided. The request is ignored if metadata is not provided.False: metadata is not requested and the meta-estimator will not pass it tofit.None: metadata is not requested, and the meta-estimator will raise an error if the user provides it.str: metadata should be passed to the meta-estimator with this given alias instead of the original name.
The default (
sklearn.utils.metadata_routing.UNCHANGED) retains the existing request. This allows you to change the request for some parameters and not others.Added in version 1.3.
Parameters¶
- sample_weightstr, True, False, or None, default=sklearn.utils.metadata_routing.UNCHANGED
Metadata routing for
sample_weightparameter infit.
Returns¶
- selfobject
The updated object.
- Parameters:
self (BaseCalibrator)
- Return type:
- class calibre.MonotonicMixin[source]
Mixin for calibrators that maintain monotonicity.
This mixin provides utility methods for calibrators that aim to preserve or enforce monotonic relationships between inputs and outputs.
- check_monotonicity(y)[source]
Check if an array is monotonically increasing.
- enforce_monotonicity(y)[source]
Enforce monotonicity on an array.
Notes
This is a utility mixin that doesn’t require any specific attributes. It’s designed to be mixed in with BaseCalibrator subclasses that need monotonicity guarantees.
- static check_monotonicity(y, strict=False)[source]
Check if an array is monotonically increasing.
- Parameters:
- Returns:
True if the array is monotonic according to the specified criteria.
- Return type:
Examples
>>> import numpy as np >>> from calibre.base import MonotonicMixin >>> >>> y1 = np.array([0.1, 0.2, 0.3, 0.4]) >>> MonotonicMixin.check_monotonicity(y1) True >>> >>> y2 = np.array([0.1, 0.3, 0.2, 0.4]) >>> MonotonicMixin.check_monotonicity(y2) False >>> >>> y3 = np.array([0.1, 0.2, 0.2, 0.3]) >>> MonotonicMixin.check_monotonicity(y3, strict=False) True >>> MonotonicMixin.check_monotonicity(y3, strict=True) False
- static enforce_monotonicity(y, inplace=False)[source]
Enforce monotonicity on an array.
This method ensures the array is non-decreasing by replacing any value that is less than the previous value with the previous value.
- Parameters:
- Returns:
Monotonically increasing version of the input array.
- Return type:
Examples
>>> import numpy as np >>> from calibre.base import MonotonicMixin >>> >>> y = np.array([0.1, 0.3, 0.2, 0.5, 0.4]) >>> y_mono = MonotonicMixin.enforce_monotonicity(y) >>> print(y_mono) [0.1 0.3 0.3 0.5 0.5] >>> >>> # Original array unchanged >>> print(y) [0.1 0.3 0.2 0.5 0.4]
Recommended Default¶
Centered Isotonic Calibrator¶
- class calibre.CenteredIsotonicCalibrator(clip_output=True, enable_diagnostics=False)[source]¶
Bases:
BaseCalibratorCentered isotonic regression for granularity-preserving calibration.
Runs weighted PAVA, collapses each flat block to its weighted-centroid predictor value, and interpolates linearly between those points. Compared with standard isotonic regression this preserves the score ordering inside what would otherwise be a plateau, at no cost in monotonicity.
- Parameters:
- calibration_curve_¶
The fitted calibration map.
.xholds the block centroids and.ytheir pooled values.
- n_features_in_¶
Always 1. Present for scikit-learn compatibility.
Notes
Standard isotonic regression is the L2 projection onto the monotone cone and is optimal for that objective; CIR is not a minimiser of the same criterion. The justification is inferential rather than variational: within a flat block the data support a single pooled rate, and linear interpolation between consecutive pooled estimates is the minimal assumption that neither invents structure nor throws away the ordering. Oron & Flournoy report substantially lower estimation error than isotonic regression when monotonicity violations are present at sample sizes typical of dose-response studies.
Extrapolation holds the end values constant, matching the convention of
sklearn.isotonic.IsotonicRegression(out_of_bounds="clip").Examples
>>> import numpy as np >>> from calibre import CenteredIsotonicCalibrator >>> >>> x = np.array([0.1, 0.2, 0.3, 0.4, 0.5]) >>> y = np.array([0, 0, 1, 0, 1]) >>> >>> cal = CenteredIsotonicCalibrator() >>> cal.fit(x, y) CenteredIsotonicCalibrator()
PAVA gives
[0, 0, 0.5, 0.5, 1], i.e. blocks{0.1,0.2} -> 0,{0.3,0.4} -> 0.5,{0.5} -> 1. The interior block collapses to its centroid 0.35; the leading block anchors at its inner edge 0.2, so the curve stays flat to the left of it:>>> cal.calibration_curve_.x array([0.2 , 0.35, 0.5 ]) >>> cal.transform(np.array([0.15, 0.35])) array([0. , 0.5])
See also
IsotonicCalibrator : The piecewise-constant fit CIR is derived from.
- transform(X)[source]¶
Map scores through the fitted calibration curve.
- Parameters:
X (ndarray) – Scores to calibrate.
- Returns:
Calibrated probabilities.
- Return type:
ndarray of shape (n_samples,)
- Raises:
AttributeError – If called before
fit().
- set_fit_request(*, sample_weight='$UNCHANGED$')¶
Configure whether metadata should be requested to be passed to the
fitmethod.Note that this method is only relevant when this estimator is used as a sub-estimator within a meta-estimator and metadata routing is enabled with
enable_metadata_routing=True(seesklearn.set_config()). Please check the User Guide on how the routing mechanism works.The options for each parameter are:
True: metadata is requested, and passed tofitif provided. The request is ignored if metadata is not provided.False: metadata is not requested and the meta-estimator will not pass it tofit.None: metadata is not requested, and the meta-estimator will raise an error if the user provides it.str: metadata should be passed to the meta-estimator with this given alias instead of the original name.
The default (
sklearn.utils.metadata_routing.UNCHANGED) retains the existing request. This allows you to change the request for some parameters and not others.Added in version 1.3.
Parameters¶
- sample_weightstr, True, False, or None, default=sklearn.utils.metadata_routing.UNCHANGED
Metadata routing for
sample_weightparameter infit.
Returns¶
- selfobject
The updated object.
- Parameters:
self (CenteredIsotonicCalibrator)
- Return type:
Centered isotonic regression (Oron & Flournoy 2017) runs PAVA, then collapses each flat block to its weighted centroid and interpolates linearly between the collapsed points. The result is calibrated as well as isotonic regression but keeps almost all of the input’s distinct values.
Other Calibrators¶
Isotonic Calibrator¶
- class calibre.IsotonicCalibrator(y_min=None, y_max=None, increasing=True, out_of_bounds='clip', enable_diagnostics=False)[source]¶
Bases:
BaseCalibratorIsotonic regression calibrator.
This calibrator wraps sklearn’s IsotonicRegression for probability calibration.
- Parameters:
y_min (float | None) – Lower bound for the calibrated values.
y_max (float | None) – Upper bound for the calibrated values.
increasing (bool) – Whether the calibration function should be increasing.
out_of_bounds (str) – How to handle out-of-bounds values in transform. Options: ‘nan’, ‘clip’, ‘raise’.
enable_diagnostics (bool) – Whether to enable plateau diagnostics analysis.
Examples
>>> import numpy as np >>> from calibre import IsotonicCalibrator >>> >>> X = np.array([0.1, 0.2, 0.3, 0.4, 0.5]) >>> y = np.array([0, 0, 1, 1, 1]) >>> >>> # Basic usage >>> cal = IsotonicCalibrator() >>> _ = cal.fit(X, y) >>> X_calibrated = cal.transform(X) >>> >>> # With diagnostics >>> cal = IsotonicCalibrator(enable_diagnostics=True) >>> _ = cal.fit(X, y) >>> cal.has_diagnostics() True >>> cal.get_diagnostics()["n_plateaus"] 2
Notes
Isotonic regression finds the best monotonic fit to the data, which is particularly useful for calibration because well-calibrated predictions should maintain the rank order of predictions while improving probability estimates.
See also
NearlyIsotonicCalibrator : Relaxed monotonicity constraint SmoothedIsotonicCalibrator : Isotonic with smoothing
- transform(X)[source]¶
Apply isotonic calibration to new data.
- Parameters:
X (ndarray) – The values to be calibrated.
- Returns:
Calibrated values.
- Raises:
ValueError – If called before fit().
- Return type:
- set_fit_request(*, sample_weight='$UNCHANGED$')¶
Configure whether metadata should be requested to be passed to the
fitmethod.Note that this method is only relevant when this estimator is used as a sub-estimator within a meta-estimator and metadata routing is enabled with
enable_metadata_routing=True(seesklearn.set_config()). Please check the User Guide on how the routing mechanism works.The options for each parameter are:
True: metadata is requested, and passed tofitif provided. The request is ignored if metadata is not provided.False: metadata is not requested and the meta-estimator will not pass it tofit.None: metadata is not requested, and the meta-estimator will raise an error if the user provides it.str: metadata should be passed to the meta-estimator with this given alias instead of the original name.
The default (
sklearn.utils.metadata_routing.UNCHANGED) retains the existing request. This allows you to change the request for some parameters and not others.Added in version 1.3.
Parameters¶
- sample_weightstr, True, False, or None, default=sklearn.utils.metadata_routing.UNCHANGED
Metadata routing for
sample_weightparameter infit.
Returns¶
- selfobject
The updated object.
- Parameters:
self (IsotonicCalibrator)
- Return type:
Spline Calibrator¶
- class calibre.SplineCalibrator(n_knots=10, degree=3, knots='quantile', alpha=None, link='logit', cv=5, max_cv_samples=20000, random_state=0, clip_output=True, enable_diagnostics=False)[source]¶
Bases:
BaseCalibratorMonotone spline calibration with cross-validated smoothing.
Fits
\[g\big(f(x)\big) = \theta + \sum_k \delta_k I_k(x), \qquad \delta_k \ge 0\]where the \(I_k\) are I-splines (each non-decreasing) and \(g\) is the link. Because every basis function is non-decreasing and every coefficient is non-negative, \(f\) is non-decreasing by construction; the link is increasing, so the calibrated probability is too.
- Parameters:
n_knots (int) – Number of knots. The basis has
n_knots + degree - 1functions. Used only whenalphais given; otherwise cross-validation selects it.degree (int) – B-spline degree. 3 gives the usual cubic behaviour.
knots (str) –
"quantile"(default) places knots at score quantiles;"uniform"spaces them evenly. Quantile is normally right for calibration, where scores pile up wherever the base model is confident and uniform knots spend resolution on empty regions.alpha (float | None) – Roughness penalty on the coefficient increments.
None(default) selects it, along withn_knots, by cross-validation. A number fixes it and skips cross-validation.link (str) –
"logit"(default) fits a penalised Bernoulli likelihood: log-loss is the proper score for binary labels, and predictions land in(0, 1)with no clipping."identity"fits penalised least squares on the probability scale – a single bounded linear solve.cv (int) – Number of cross-validation folds. Stratified when
yis binary.max_cv_samples (int | None) – Cap on the number of observations used for hyperparameter selection. The final model is always refit on the full sample; this only bounds the cost of the search, which would otherwise fit the grid once per fold over every row (at n=100k that is ~50s against 0.3s for a single fit). Selecting two scalars from a large random subsample costs essentially nothing statistically. Set to
Noneto search on all of the data.random_state (int | None) – Seed for the cross-validation split. Defaults to
0so thatfitis reproducible: cross-validation here only selects a hyperparameter, and a fit that silently returns a different curve on each identical call is a trap. PassNoneto draw the split from the global RNG instead.clip_output (bool) – Clip calibrated values into
[0, 1]. A no-op forlink="logit".enable_diagnostics (bool) – Whether to enable plateau diagnostics analysis.
- basis_¶
The fitted basis. Its knots come from the same fit that produced
coef_.
- intercept_¶
Fitted intercept, on the link scale.
- coef_¶
Fitted non-negative increment coefficients.
- alpha_¶
The penalty actually used – selected by cross-validation, or echoed back from
alpha.
- n_knots_¶
The knot count actually used.
- n_features_in_¶
Always 1. Present for scikit-learn compatibility.
Notes
Non-negative coefficients on a plain B-spline basis do not give monotonicity. B-spline basis functions are bumps, so a non-negative combination of them is a non-negative function and nothing more – a single non-negative coefficient already traces a curve that rises and then falls. Monotonicity requires non-negativity on the coefficient differences, which is exactly what the I-spline (cumulative) basis encodes; see
calibre._core.MonotoneSplineBasis. This is the construction behind the SCOP-splines of Pya & Wood (2015) in R’sscamand the penalised B-splines of Eilers & Marx (1996).Cross-validation selects a hyperparameter and then refits on all the data. It is not a search for whichever fold’s model scored best on its own validation split: that selects on noise and ships a model trained on only
(cv-1)/cvof the sample. Folds are scored by log-loss – a proper score – rather than by \(R^2\).Examples
>>> import numpy as np >>> from calibre import SplineCalibrator >>> >>> rng = np.random.default_rng(0) >>> x = rng.random(500) >>> y = (rng.random(500) < x).astype(float) >>> >>> cal = SplineCalibrator(alpha=0.1).fit(x, y) >>> fitted = cal.transform(np.linspace(0, 1, 200)) >>> bool(np.all(np.diff(fitted) >= -1e-10)) # monotone by construction True >>> bool(fitted.min() >= 0.0 and fitted.max() <= 1.0) True
See also
CenteredIsotonicCalibrator : Non-parametric, needs no tuning, also plateau-free. RegularizedIsotonicCalibrator : Same basis, penalty specified rather than tuned.
- transform(X)[source]¶
Map scores through the fitted calibration curve.
- Parameters:
X (ndarray) – Scores to calibrate.
- Returns:
Calibrated probabilities.
- Return type:
ndarray of shape (n_samples,)
- Raises:
AttributeError – If called before
fit().
- calibration_curve(n_points=200)[source]¶
Sample the fitted map onto a grid, for plotting or inspection.
- Parameters:
n_points (int) – Number of grid points across the fitted score range.
- Returns:
The sampled curve.
- Return type:
PiecewiseLinear
- Raises:
AttributeError – If called before
fit().
- set_fit_request(*, sample_weight='$UNCHANGED$')¶
Configure whether metadata should be requested to be passed to the
fitmethod.Note that this method is only relevant when this estimator is used as a sub-estimator within a meta-estimator and metadata routing is enabled with
enable_metadata_routing=True(seesklearn.set_config()). Please check the User Guide on how the routing mechanism works.The options for each parameter are:
True: metadata is requested, and passed tofitif provided. The request is ignored if metadata is not provided.False: metadata is not requested and the meta-estimator will not pass it tofit.None: metadata is not requested, and the meta-estimator will raise an error if the user provides it.str: metadata should be passed to the meta-estimator with this given alias instead of the original name.
The default (
sklearn.utils.metadata_routing.UNCHANGED) retains the existing request. This allows you to change the request for some parameters and not others.Added in version 1.3.
Parameters¶
- sample_weightstr, True, False, or None, default=sklearn.utils.metadata_routing.UNCHANGED
Metadata routing for
sample_weightparameter infit.
Returns¶
- selfobject
The updated object.
- Parameters:
self (SplineCalibrator)
- Return type:
Regularized Isotonic Calibrator¶
- class calibre.RegularizedIsotonicCalibrator(alpha='auto', n_knots=10, degree=3, knots='quantile', link='logit', cv=5, scoring='log_loss', random_state=0, clip_output=True, enable_diagnostics=False)[source]¶
Bases:
BaseCalibratorMonotone calibration with an explicit roughness penalty.
Solves
\[\min_{\theta,\ \delta \ge 0}\ \mathcal{L}\big(\theta + M\delta;\ y, w\big) + \alpha \lVert \Delta\delta \rVert^2\]where
Mis an I-spline design, sodelta >= 0makes the fit monotone by construction, and \(\Delta\delta\) is the second difference of the underlying B-spline coefficients.- Parameters:
alpha (float | str) – Roughness penalty.
0gives an unpenalised monotone spline; larger values drive the fit toward the best monotone straight line.n_knots (int) – Number of knots in the basis.
degree (int) – B-spline degree.
knots (str) –
"quantile"or"uniform"knot placement.link (str) –
"logit"or"identity". Seecalibre.SplineCalibrator.cv (int) – Number of cross-validation folds used when a hyperparameter is left at
"auto". Ignored when every hyperparameter is pinned.scoring (str) – Proper scoring rule the
"auto"search minimises. Deliberately not a calibration error: ECE and its relatives are minimised by a constant forecast, so selecting on one would reward throwing resolution away.random_state (int | None) – Seed for the cross-validation split, so an
"auto"selection is reproducible.clip_output (bool) – Clip calibrated values into
[0, 1].enable_diagnostics (bool) – Whether to enable plateau diagnostics analysis.
- basis_¶
The fitted basis.
- intercept_¶
Fitted intercept, on the link scale.
- coef_¶
Fitted non-negative increment coefficients.
- n_features_in_¶
Always 1. Present for scikit-learn compatibility.
Notes
The penalty is on curvature, not on magnitude. A ridge penalty \(\alpha\sum_i \beta_i^2\) buys no smoothness at all: unconstrained its solution is \(\beta = y/(1+\alpha)\), a uniform deflation of every probability that breaks mean calibration by construction and drives all predictions to zero as \(\alpha\) grows. A second-difference penalty leaves any straight line unpenalised, so the identity map and the empirical base rate both survive it.
Why a fixed basis rather than one parameter per score. Putting a parameter at every unique score makes this a smoothing-spline problem whose penalty operator scales like \(h^{-2} \sim n^{2}\), so the normal equations scale like \(n^{4}\). That is ill-conditioned in a way no solver choice repairs – a constrained QP stops converging above a few thousand distinct scores, ADMM diverges, and a matrix-free least-squares solve fails to converge while the fitted mean collapses away from the base rate. A modest fixed basis with a coefficient penalty – the P-spline construction of Eilers & Marx (1996), as used by the SCOP-splines of Pya & Wood (2015) – has none of those regimes: it fits 100,000 points in milliseconds with monotonicity guaranteed structurally.
Note
alpha=0no longer reduces to isotonic regression. It gives an unpenalised monotone regression spline, which is smooth rather than piecewise constant. For the exact isotonic fit usecalibre.IsotonicCalibrator; to remove isotonic’s plateaus without leaving the non-parametric family, usecalibre.CenteredIsotonicCalibrator.Examples
>>> import numpy as np >>> from calibre import RegularizedIsotonicCalibrator >>> >>> rng = np.random.default_rng(0) >>> x = rng.random(500) >>> y = (rng.random(500) < x).astype(float) >>> >>> cal = RegularizedIsotonicCalibrator(alpha=1.0).fit(x, y) >>> fitted = cal.transform(np.linspace(0, 1, 200)) >>> bool(np.all(np.diff(fitted) >= -1e-10)) True
See also
SplineCalibrator : Same estimator with the penalty chosen by cross-validation. CenteredIsotonicCalibrator : Non-parametric and plateau-free. IsotonicCalibrator : The exact isotonic fit.
- ALPHA_GRID = (0.0, 0.001, 0.01, 0.1, 1.0, 10.0, 100.0)¶
Candidate roughness penalties searched when
alpha="auto". Matches the grid SplineCalibrator has always used for the same parameter.
- transform(X)[source]¶
Map scores through the fitted calibration curve.
- Parameters:
X (ndarray) – Scores to calibrate.
- Returns:
Calibrated probabilities.
- Return type:
ndarray of shape (n_samples,)
- Raises:
AttributeError – If called before
fit().
- set_fit_request(*, sample_weight='$UNCHANGED$')¶
Configure whether metadata should be requested to be passed to the
fitmethod.Note that this method is only relevant when this estimator is used as a sub-estimator within a meta-estimator and metadata routing is enabled with
enable_metadata_routing=True(seesklearn.set_config()). Please check the User Guide on how the routing mechanism works.The options for each parameter are:
True: metadata is requested, and passed tofitif provided. The request is ignored if metadata is not provided.False: metadata is not requested and the meta-estimator will not pass it tofit.None: metadata is not requested, and the meta-estimator will raise an error if the user provides it.str: metadata should be passed to the meta-estimator with this given alias instead of the original name.
The default (
sklearn.utils.metadata_routing.UNCHANGED) retains the existing request. This allows you to change the request for some parameters and not others.Added in version 1.3.
Parameters¶
- sample_weightstr, True, False, or None, default=sklearn.utils.metadata_routing.UNCHANGED
Metadata routing for
sample_weightparameter infit.
Returns¶
- selfobject
The updated object.
- Parameters:
- Return type:
Note
This is a monotone spline with a second-difference (curvature) penalty. It is
not ridge regression, and alpha=0 is not isotonic regression.
Relaxed PAVA Calibrator¶
- class calibre.RelaxedPAVACalibrator(epsilon='auto', min_slope='auto', cv=5, scoring='log_loss', random_state=0, clip_output=True, enable_diagnostics=False)[source]¶
Bases:
BaseCalibratorIsotonic regression with a lower bound on each adjacent increment.
Solves
\[\min_{z} \sum_i w_i (y_i - z_i)^2 \quad\text{s.t.}\quad z_{i+1} - z_i \ge L_i\]in O(n) via the cumulative-shift reduction (see
calibre._core.shift_to_pava()): substituting \(u_i = z_i - \sum_{j<i} L_j\) turns the constraint into \(u_{i+1} \ge u_i\), so one weighted PAVA on the shifted targets solves it exactly.One signed bound spans three estimators:
epsilon = 0standard isotonic regression
epsilon > 0epsilon-monotone: decreases up to
epsilonallowedmin_slope > 0strictly increasing, so no plateau can form at all
- Parameters:
epsilon (float | str) – Largest decrease permitted between adjacent unique scores, in the units of
y. Soepsilon=0.02means “tolerate a drop of up to 2 percentage points”.min_slope (float | str) – Minimum required increase between adjacent unique scores. Mutually exclusive with a non-zero
epsilon; this is the direction that eliminates plateaus."auto"(the default) uses0.01 / n_unique, but only on the untouched default path – that is, whenepsilonwas also left at"auto"and the search settled on0. Namingepsilonyourself, includingepsilon=0, leaves the slope at0and the estimator exactly as documented in the table above.cv (int) – Number of cross-validation folds used when a hyperparameter is left at
"auto". Ignored when every hyperparameter is pinned.scoring (str) – Proper scoring rule the
"auto"search minimises. Deliberately not a calibration error: ECE and its relatives are minimised by a constant forecast, so selecting on one would reward throwing resolution away.random_state (int | None) – Seed for the cross-validation split, so an
"auto"selection is reproducible.clip_output (bool) – Clip calibrated values into
[0, 1].enable_diagnostics (bool) – Whether to enable plateau diagnostics analysis.
- calibration_curve_¶
The fitted calibration map.
- n_features_in_¶
Always 1. Present for scikit-learn compatibility.
Notes
epsilonis an absolute tolerance on the target scale, deliberately. An earlier version of this class derived its threshold as a percentile of|diff(y)|over the score-sorted targets, which cannot work for this package’s primary use case: with binary labels those differences are all 0 or 1, so any percentile collapses to either 0 – the relaxation never binds and the estimator is silently just PAVA – or 1, where it never constrains anything. There is no intermediate setting to choose.Relaxing monotonicity is not free: a decrease in the calibration map reverses the ranking of every score pair it spans, which costs discrimination. To preserve granularity,
min_slopeis usually the better direction, since it removes plateaus while keeping the map strictly increasing.That is why the default is a slope rather than nothing. PAVA’s plateaus are an artefact of pooling adjacent violators, not a finding about the data, and at
min_slope=0this estimator keeps only 1-4% of the input’s distinct values. A slope small enough to be invisible in the score recovers almost all of them: measured on logit-inflated designs at n from 300 to 3000, the default retains 80-95% of distinct values for a Brier cost in the fifth decimal. It is 80-95% rather than all of them becauseclip_outputflattens the two ends of a fit that saturates 0 and 1; the plateaus that survive the default are at the boundaries, not in the interior. It scales as1 / n_uniquebecause a fixed slope safe at n=1000 would need an output range of 10 at n=1e6, and clipping would flatten it back into the plateaus it exists to prevent.Examples
>>> import numpy as np >>> from calibre import RelaxedPAVACalibrator >>> >>> x = np.array([0.1, 0.2, 0.3, 0.4, 0.5]) >>> y = np.array([0, 0, 1, 0, 1]) >>> >>> RelaxedPAVACalibrator(epsilon=0.0).fit_transform(x, y) array([0. , 0. , 0.5, 0.5, 1. ])
Left alone, the default breaks that tie apart rather than reporting two scores as indistinguishable:
>>> default = RelaxedPAVACalibrator().fit_transform(x, y) >>> bool(np.all(np.diff(default) > 0)) True
A minimum slope leaves no plateau anywhere:
>>> fitted = RelaxedPAVACalibrator(min_slope=0.05).fit_transform(x, y) >>> bool(np.all(np.diff(fitted) > 0)) True
The bound itself is exact only without clipping. Clipping into
[0, 1]can shorten the increments that straddle a boundary, so the guarantee degrades from “>= min_slope” to “> 0” there:>>> exact = RelaxedPAVACalibrator( ... min_slope=0.05, clip_output=False ... ).fit_transform(x, y) >>> bool(np.all(np.diff(exact) >= 0.05 - 1e-12)) True >>> float(exact.min()) # below 0, hence the clipping -0.025
See also
IsotonicCalibrator : The
epsilon = 0special case. CenteredIsotonicCalibrator : Removes plateaus without relaxing monotonicity. NearlyIsotonicCalibrator : Penalises violations instead of bounding them.- EPSILON_GRID = (0.0, 0.001, 0.005, 0.01, 0.02, 0.05, 0.1)¶
Candidate tolerances searched when
epsilon="auto". 0.0 is included so selection can return strict isotonic regression when that fits best.
- transform(X)[source]¶
Map scores through the fitted calibration curve.
- Parameters:
X (ndarray) – Scores to calibrate.
- Returns:
Calibrated values.
- Return type:
ndarray of shape (n_samples,)
- Raises:
AttributeError – If called before
fit().
- set_fit_request(*, sample_weight='$UNCHANGED$')¶
Configure whether metadata should be requested to be passed to the
fitmethod.Note that this method is only relevant when this estimator is used as a sub-estimator within a meta-estimator and metadata routing is enabled with
enable_metadata_routing=True(seesklearn.set_config()). Please check the User Guide on how the routing mechanism works.The options for each parameter are:
True: metadata is requested, and passed tofitif provided. The request is ignored if metadata is not provided.False: metadata is not requested and the meta-estimator will not pass it tofit.None: metadata is not requested, and the meta-estimator will raise an error if the user provides it.str: metadata should be passed to the meta-estimator with this given alias instead of the original name.
The default (
sklearn.utils.metadata_routing.UNCHANGED) retains the existing request. This allows you to change the request for some parameters and not others.Added in version 1.3.
Parameters¶
- sample_weightstr, True, False, or None, default=sklearn.utils.metadata_routing.UNCHANGED
Metadata routing for
sample_weightparameter infit.
Returns¶
- selfobject
The updated object.
- Parameters:
self (RelaxedPAVACalibrator)
- Return type:
Bounds each adjacent increment: epsilon permits small decreases, while
min_slope forbids plateaus outright. Solved by shift-to-PAVA in O(n).
Nearly Isotonic Calibrator¶
- class calibre.NearlyIsotonicCalibrator(lam='auto', method='path', cv=5, scoring='log_loss', random_state=0, clip_output=True, enable_diagnostics=False)[source]¶
Bases:
BaseCalibratorNearly-isotonic regression for flexible monotonic calibration.
This calibrator implements nearly-isotonic regression, which relaxes the strict monotonicity constraint of standard isotonic regression by penalizing rather than prohibiting violations. This allows for a more flexible fit while still maintaining a generally monotonic trend.
- Parameters:
lam (float | str) – Regularization parameter controlling the strength of monotonicity constraint. Higher values enforce stricter monotonicity.
method (str) –
Solver for the optimization problem. Both are exact and agree to solver tolerance;
pathis the faster and needs no CVXPY.'path': the exact solution path (O(n log n)).'cvx': convex optimization via CVXPY.
cv (int) – Number of cross-validation folds used when a hyperparameter is left at
"auto". Ignored when every hyperparameter is pinned.scoring (str) – Proper scoring rule the
"auto"search minimises. Deliberately not a calibration error: ECE and its relatives are minimised by a constant forecast, so selecting on one would reward throwing resolution away.random_state (int | None) – Seed for the cross-validation split, so an
"auto"selection is reproducible.clip_output (bool) – Clip calibrated values into
[0, 1]. Appropriate for probability calibration; turn it off to recover the unconstrained optimum of the objective above, which is what the estimator is actually defined as.enable_diagnostics (bool) – Whether to enable plateau diagnostics analysis.
Notes
Nearly-isotonic regression solves the following optimization problem:
\[\min_{\beta} \sum_{i=1}^{n} (y_i - \beta_i)^2 + \lambda \sum_{i=1}^{n-1} \max(0, \beta_i - \beta_{i+1})\]where \(\beta\) is the calibrated output, \(y\) are the true labels, and \(\lambda > 0\) controls the strength of the monotonicity penalty.
This formulation penalizes violations of monotonicity proportionally to their magnitude, allowing small violations when they significantly improve the fit.
Interpreting lam. Read it as a bias-variance knob on pooling rather than as permission for non-monotone structure:
lam = 0returns the data untouched,lam -> infreturns the isotonic fit, and intermediate values give shorter plateaus than isotonic regression – finer granularity – in exchange for bounded violations.This is not the calibrator to reach for if you want granularity. The granularity above is real but it is not free, and here it is not even cheap. Because the objective fits one value per observation to the labels, a small
lamreturns something close to the raw 0/1 labels: lots of distinct values, all of them overfitted. Measured out of sample on a logit-inflated design at n=3000,lam=0.001keeps 1074 distinct values at a held-out Brier of 0.191 against isotonic’s 0.116, and every step up thelamgrid buys score back by giving granularity away until, bylam=100, the fit is isotonic regression.That frontier is dominated. On the same data
CenteredIsotonicCalibratorkeeps 2647 distinct values at a held-out Brier of 0.1159 – more granularity than anylamreaches, at a better score than isotonic. So there is no defaultlamworth moving to, and none is claimed: unlikeRelaxedPAVACalibrator, whose default now breaks plateaus apart at a cost in the fifth decimal, this estimator’s defaults leave it close to isotonic on purpose. Use it when you want bounded monotonicity violations – the thing it uniquely provides – and use CIR or the spline calibrators when you want resolution.Scaling differs from the source paper. Tibshirani, Hoefling & Tibshirani (2011, Technometrics 53(1), 54-61) put a factor of 1/2 on the squared-error term:
\[\min_{\beta} \tfrac{1}{2} \sum_i (y_i - \beta_i)^2 + \lambda_{\text{paper}} \sum_i \max(0, \beta_i - \beta_{i+1})\]The objective above omits it, so
lamhere is twice the paper’s \(\lambda\):\[\lambda_{\text{here}} = 2\,\lambda_{\text{paper}}\]Double any penalty value taken from the paper before passing it in. Both solvers are pinned against the authors’ R implementation (
neariso) intests/test_r_reference.py.Examples
>>> import numpy as np >>> from calibre import NearlyIsotonicCalibrator >>> >>> X = np.array([0.1, 0.2, 0.3, 0.4, 0.5]) >>> y = np.array([0.12, 0.18, 0.35, 0.25, 0.55]) >>> >>> cal = NearlyIsotonicCalibrator(lam=0.5) >>> _ = cal.fit(X, y) >>> X_calibrated = cal.transform(np.array([0.15, 0.35, 0.55]))
See also
IsotonicCalibrator : Strict monotonicity constraint RegularizedIsotonicCalibrator : L2 regularization with strict monotonicity
- LAM_GRID = (0.01, 0.1, 0.5, 1.0, 5.0, 10.0, 50.0, 100.0)¶
Candidate lambdas searched when
lam="auto". Spans “essentially the raw data” to “essentially isotonic”, logarithmically.
- transform(X)[source]¶
Map scores through the fitted calibration curve.
- Parameters:
X (ndarray) – The values to be calibrated.
- Returns:
Calibrated values.
- Return type:
X_calibrated
- Raises:
AttributeError – If called before
fit().
- set_fit_request(*, sample_weight='$UNCHANGED$')¶
Configure whether metadata should be requested to be passed to the
fitmethod.Note that this method is only relevant when this estimator is used as a sub-estimator within a meta-estimator and metadata routing is enabled with
enable_metadata_routing=True(seesklearn.set_config()). Please check the User Guide on how the routing mechanism works.The options for each parameter are:
True: metadata is requested, and passed tofitif provided. The request is ignored if metadata is not provided.False: metadata is not requested and the meta-estimator will not pass it tofit.None: metadata is not requested, and the meta-estimator will raise an error if the user provides it.str: metadata should be passed to the meta-estimator with this given alias instead of the original name.
The default (
sklearn.utils.metadata_routing.UNCHANGED) retains the existing request. This allows you to change the request for some parameters and not others.Added in version 1.3.
Parameters¶
- sample_weightstr, True, False, or None, default=sklearn.utils.metadata_routing.UNCHANGED
Metadata routing for
sample_weightparameter infit.
Returns¶
- selfobject
The updated object.
- Parameters:
self (NearlyIsotonicCalibrator)
- Return type:
Note
Penalises rather than forbids monotonicity violations. Two exact solvers:
method="path" (default, pure NumPy) and method="cvx" (CVXPY). Note
that lam is twice the source paper’s lambda.
Smoothed Isotonic Calibrator¶
- class calibre.SmoothedIsotonicCalibrator(window_length=None, poly_order=3, adaptive=False, min_window=5, max_window=None, enable_diagnostics=False)[source]¶
Bases:
BaseCalibrator,MonotonicMixinIsotonic regression with Savitzky-Golay smoothing.
Fits weighted PAVA on the pooled unique scores, smooths the fitted values, restores monotonicity with a running maximum, and interpolates linearly between the resulting knots.
- Parameters:
window_length (int | None) – Window length for the Savitzky-Golay filter, in distinct scores. Forced odd and capped at the number of distinct scores. If None, uses
max(5, n_distinct // 10).poly_order (int) – Polynomial order for the filter. Values below 1 are raised to 1.
adaptive (bool) – Size the window per point from local density instead of using one fixed window.
min_window (int) – Minimum window length when
adaptive=True. Values below 3 are raised to 3.max_window (int | None) – Maximum window length when
adaptive=True. If None, usesn_distinct // 5.enable_diagnostics (bool) – Run plateau diagnostics after fitting.
- calibration_curve_¶
The fitted calibration map, on the distinct training scores.
- poly_order_¶
poly_orderafter validation.
- min_window_¶
min_windowafter validation.
- n_features_in_¶
Always 1. Present for scikit-learn compatibility.
Notes
Window lengths count distinct scores, not observations. Tied scores are pooled before smoothing, because a filter applied across repeated abscissae smooths over points that carry no separate information, and an interpolant cannot be built on repeated abscissae at all.
This estimator does not preserve granularity well. Restoring monotonicity with a running maximum re-flattens the curve wherever the filter introduced a dip, so plateaus come back: on the package’s test datasets it retains roughly 13-16% of the distinct input values, against 100% for
CenteredIsotonicCalibrator. If granularity is why you are here, use that instead.Examples
>>> import numpy as np >>> from calibre import SmoothedIsotonicCalibrator >>> >>> X = np.array([0.1, 0.2, 0.3, 0.4, 0.5]) >>> y = np.array([0.12, 0.18, 0.35, 0.25, 0.55]) >>> >>> cal = SmoothedIsotonicCalibrator(window_length=7) >>> _ = cal.fit(X, y) >>> p = cal.transform(np.array([0.15, 0.45])) >>> bool(p[0] <= p[1]) True
See also
IsotonicCalibrator : Isotonic regression without smoothing. CenteredIsotonicCalibrator : Smooth by construction rather than by repair.
- transform(X)[source]¶
Map scores through the fitted calibration curve.
- Parameters:
X (ndarray) – Scores to calibrate.
- Returns:
Calibrated probabilities.
- Return type:
ndarray of shape (n_samples,)
- Raises:
AttributeError – If called before
fit().
- set_fit_request(*, sample_weight='$UNCHANGED$')¶
Configure whether metadata should be requested to be passed to the
fitmethod.Note that this method is only relevant when this estimator is used as a sub-estimator within a meta-estimator and metadata routing is enabled with
enable_metadata_routing=True(seesklearn.set_config()). Please check the User Guide on how the routing mechanism works.The options for each parameter are:
True: metadata is requested, and passed tofitif provided. The request is ignored if metadata is not provided.False: metadata is not requested and the meta-estimator will not pass it tofit.None: metadata is not requested, and the meta-estimator will raise an error if the user provides it.str: metadata should be passed to the meta-estimator with this given alias instead of the original name.
The default (
sklearn.utils.metadata_routing.UNCHANGED) retains the existing request. This allows you to change the request for some parameters and not others.Added in version 1.3.
Parameters¶
- sample_weightstr, True, False, or None, default=sklearn.utils.metadata_routing.UNCHANGED
Metadata routing for
sample_weightparameter infit.
Returns¶
- selfobject
The updated object.
- Parameters:
self (SmoothedIsotonicCalibrator)
- Return type:
Note
Savitzky-Golay smoothing of an isotonic fit. Retained for compatibility;
prefer SplineCalibrator or
RegularizedIsotonicCalibrator for a smooth curve.
Research¶
Cost- and Data-Informed Isotonic Calibrator¶
- class calibre.CDIIsotonicCalibrator(thresholds=None, threshold_weights=None, bandwidth=0.05, alpha=0.05, gamma=0.15, window=25, normalize_scores=True, clip_output=True)[source]¶
Bases:
BaseEstimator,TransformerMixinCost- and Data-Informed Isotonic calibrator (CDI-ISO).
- Parameters:
thresholds (Iterable[float] | None) – Operating thresholds in [0,1] that matter economically. If None, uniform attention across the score range is assumed.
threshold_weights (Iterable[float] | None) – Nonnegative weights matching thresholds. If None, equal weights.
bandwidth (float) – Half-width h of the triangular kernel around each threshold (in score units, after optional min-max normalization). Defaults to 0.05.
alpha (float) – Significance level for the two-proportion normal approximation used to gate minimum-slope enforcement (default 0.05 -> z≈1.96).
gamma (float) – Global multiplier in [0,1] for the minimum-slope budget phi_i (default 0.15).
window (int) – Number of adjacent unique-score points used on each side to form the left/right evidence blocks (default 25). Automatically clipped at edges.
normalize_scores (bool) – If True (default), min-max normalize training scores to [0,1] for the economics kernel; the same affine scaling is applied at transform time.
clip_output (bool) – If True (default), clip calibrated outputs to [0,1].
Notes
Builds local bounds L_i = phi_i - epsilon_i on sorted unique training scores.
Solves a single weighted PAVA on shifted labels (O(n)) and shifts back.
Predictions are stepwise-constant in the training score order.
- fit(scores, y, sample_weight=None)[source]¶
Fit CDI-ISO on (scores, y).
- Parameters:
- Returns:
Returns self for method chaining.
- Raises:
ValueError – If scores and y have different lengths, y contains invalid values, or sample_weight has invalid values.
- Return type:
- transform(scores)[source]¶
Map new scores to calibrated probabilities (stepwise-constant).
- Parameters:
scores (ndarray) – Input scores to calibrate.
- Returns:
Calibrated probabilities in [0,1] (if clip_output=True).
- Raises:
RuntimeError – If called before fit().
- Return type:
- adjacency_bounds_()[source]¶
Return the learned local bounds L_i per adjacency (shape: m-1).
Returns None if not fitted.
- Return type:
ndarray | None
- cumulative_shift_()[source]¶
Return the cumulative shift R_i (shape: m) or None if not fitted.
- Return type:
ndarray | None
- set_fit_request(*, sample_weight='$UNCHANGED$', scores='$UNCHANGED$')¶
Configure whether metadata should be requested to be passed to the
fitmethod.Note that this method is only relevant when this estimator is used as a sub-estimator within a meta-estimator and metadata routing is enabled with
enable_metadata_routing=True(seesklearn.set_config()). Please check the User Guide on how the routing mechanism works.The options for each parameter are:
True: metadata is requested, and passed tofitif provided. The request is ignored if metadata is not provided.False: metadata is not requested and the meta-estimator will not pass it tofit.None: metadata is not requested, and the meta-estimator will raise an error if the user provides it.str: metadata should be passed to the meta-estimator with this given alias instead of the original name.
The default (
sklearn.utils.metadata_routing.UNCHANGED) retains the existing request. This allows you to change the request for some parameters and not others.Added in version 1.3.
Parameters¶
- sample_weightstr, True, False, or None, default=sklearn.utils.metadata_routing.UNCHANGED
Metadata routing for
sample_weightparameter infit.- scoresstr, True, False, or None, default=sklearn.utils.metadata_routing.UNCHANGED
Metadata routing for
scoresparameter infit.
Returns¶
- selfobject
The updated object.
- Parameters:
self (CDIIsotonicCalibrator)
- Return type:
- set_transform_request(*, scores='$UNCHANGED$')¶
Configure whether metadata should be requested to be passed to the
transformmethod.Note that this method is only relevant when this estimator is used as a sub-estimator within a meta-estimator and metadata routing is enabled with
enable_metadata_routing=True(seesklearn.set_config()). Please check the User Guide on how the routing mechanism works.The options for each parameter are:
True: metadata is requested, and passed totransformif provided. The request is ignored if metadata is not provided.False: metadata is not requested and the meta-estimator will not pass it totransform.None: metadata is not requested, and the meta-estimator will raise an error if the user provides it.str: metadata should be passed to the meta-estimator with this given alias instead of the original name.
The default (
sklearn.utils.metadata_routing.UNCHANGED) retains the existing request. This allows you to change the request for some parameters and not others.Added in version 1.3.
Parameters¶
- scoresstr, True, False, or None, default=sklearn.utils.metadata_routing.UNCHANGED
Metadata routing for
scoresparameter intransform.
Returns¶
- selfobject
The updated object.
- Parameters:
self (CDIIsotonicCalibrator)
- Return type:
Note
CDI-ISO is research-grade. It uses economic decision theory and statistical evidence to decide where monotonicity should be enforced strictly, and requires you to specify the operating thresholds where discrimination matters most.
Usage Examples¶
Basic Example¶
import numpy as np
from calibre import CenteredIsotonicCalibrator
rng = np.random.default_rng(42)
X = rng.uniform(0, 1, 1000)
y = rng.binomial(1, X).astype(float)
calibrator = CenteredIsotonicCalibrator().fit(X, y)
X_new = rng.uniform(0, 1, 100)
y_calibrated = calibrator.transform(X_new)
Warning
Always fit the calibrator on data the model did not train on. A model’s
scores on its own training data are already too good, so a calibrator fitted
there learns the wrong correction. Use a held-out split, or
cross_val_calibrate() for out-of-fold predictions.
Comparing Methods¶
import numpy as np
from calibre import (
CenteredIsotonicCalibrator,
IsotonicCalibrator,
RegularizedIsotonicCalibrator,
RelaxedPAVACalibrator,
SplineCalibrator,
unique_value_counts,
)
calibrators = {
"Isotonic": IsotonicCalibrator(),
"Centered": CenteredIsotonicCalibrator(),
"Spline": SplineCalibrator(),
"Relaxed PAVA": RelaxedPAVACalibrator(min_slope=1e-5),
"Regularized": RegularizedIsotonicCalibrator(alpha=0.1),
}
for name, cal in calibrators.items():
out = cal.fit(X, y).transform(X)
n = unique_value_counts(out)["n_unique_y_pred"]
print(f"{name:14s} {n:5d} distinct values")
CDI-ISO Usage Example¶
import numpy as np
from calibre import CDIIsotonicCalibrator
cdi_cal = CDIIsotonicCalibrator(
thresholds=[0.3, 0.7], # operating decision thresholds
threshold_weights=[0.6, 0.4], # relative importance
bandwidth=0.1, # kernel bandwidth around thresholds
gamma=0.2, # minimum slope strength
alpha=0.05, # significance level
window=30, # evidence window size
)
cdi_cal.fit(X, y)
y_calibrated = cdi_cal.transform(X_new)
bounds = cdi_cal.adjacency_bounds_()
breakpoints = cdi_cal.breakpoints_()
print(f"CDI calibrator learned {len(bounds)} local bounds")
print(f"Calibration function has {len(breakpoints[0])} breakpoints")