Skip to content
ConformityEstimators and pipelines

Estimators and pipelines

Choosing a component

Component When to use it
ConformalRegressor A single numeric target needs a held-out prediction range without a parametric noise assumption; see regression guidance.
ConformalClassifier A categorical decision should expose several plausible labels or trigger review; see classification guidance.
BaseConformalPredictor You are extending the calibration lifecycle with a new score; use a concrete wrapper for ordinary prediction.
Public metrics and evaluate You need to assess coverage and efficiency on independent observations; see diagnostics.

Shared estimator behavior

Constructors retain configuration, fit trains a fresh clone, and set_params invalidates fitted and calibrated state. Point predictions work after fitting; uncertainty output needs calibration. score measures point performance. Fit and evaluation weights do not weight calibration.

When preprocessing learns from data and calibration is automatic, pass the entire preprocessing and prediction pipeline to the conformal wrapper. The wrapper then splits first, so calibration rows cannot affect the scaler, feature selector, model, or tuning step. This is the recommended default for fit-managed calibration and cross-validation.

Notice: Do not put a learned transformer before a final-step wrapper when relying on its automatic calibration split. The transformer fits before the wrapper can split, so it learns from calibration features and the usual split-conformal coverage argument no longer applies.

The getting started and classification examples use this placement. If you reserve calibration observations yourself before fitting, a final-step wrapper with manual calibration is also valid; see below.

Manual calibration with the complete pipeline

The same full-pipeline wrapper works when you manage the calibration split yourself. Fit it only on training rows with auto_calibrate=False, then call calibrate on raw calibration features. Prediction and evaluation also take raw features; there is no need to index into the pipeline.

import numpy as np
from sklearn.linear_model import Ridge
from sklearn.model_selection import train_test_split
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler

from conformity import ConformalRegressor

rng = np.random.default_rng(42)
X = rng.normal(size=(200, 3))
y = X[:, 0] + rng.normal(size=200)
X_dev, X_test, y_dev, y_test = train_test_split(X, y, test_size=0.2, random_state=1)
X_train, X_calib, y_train, y_calib = train_test_split(
    X_dev, y_dev, test_size=0.25, random_state=2
)
model = ConformalRegressor(
    Pipeline([("scaler", StandardScaler()), ("ridge", Ridge())]),
    auto_calibrate=False,
).fit(X_train, y_train)
model.calibrate(X_calib, y_calib)
points, intervals = model.predict(X_test, alpha=0.1, return_interval=True)
report = model.evaluate(X_test, y_test, alpha=0.1)
assert intervals.shape == (len(X_test), 2)
print(report["coverage"], report["mean_width"])

This split has 120 training, 40 calibration and 40 test observations. All preprocessing is fitted on the training rows; calibrate, predict and evaluate each receive the original features.

Why the split matters

Calibration measures errors of an already fixed prediction procedure. That procedure includes learned preprocessing as well as the model: StandardScaler, for example, estimates means and variances from the rows passed to fit. Hold calibration rows out of all those fitting steps so their scores and a future observation's score can be treated exchangeably under the usual split-conformal assumptions. Keeping only calibration targets out of fitting is insufficient for this argument if preprocessing still learns from calibration features.

Situation Placement and calibration Why
You can reserve calibration before fitting Wrap the entire Pipeline, set auto_calibrate=False, then call fit and calibrate Every learned step fits on training rows only, and you control the held-out partition.
You need ordinary fit to create calibration automatically, including inside cross-validation folds Wrap the entire Pipeline, with auto_calibrate=True The wrapper splits before fitting any learned step. A final-step wrapper cannot control preceding transformers.
Preceding transformations are stateless or fixed independently of calibration A final-step wrapper can use automatic calibration Those transformations learn nothing from the internal calibration rows. Future inputs must receive the same fixed transformation.
You only need point predictions during development Either placement supports sklearn point scoring Successful point scoring does not establish a conformal coverage guarantee; reserve fresh calibration for the selected procedure.

Both a full-pipeline wrapper and a final-step wrapper can preserve the usual marginal coverage guarantee when used with the appropriate split and assumptions. Placement alone is not enough: neither layout fixes distribution shift, tuning on final calibration outcomes, or reusing training rows as calibration.

If a final-step wrapper is required

Standard Pipeline does not expose a final step's custom calibrate, evaluate, predict_interval, or predict_set methods. Those calls need transformed features passed to the final wrapper. Wrapping the complete pipeline avoids this extra plumbing: the conformal methods accept raw features and the wrapped pipeline performs the transformations.

Whole-pipeline wrapper and automatic calibration

The example below deliberately wraps the whole pipeline to demonstrate automatic calibration isolation: the internal split must happen before the scaler fits. The wrapper clones its constructor estimator, fits the clone as estimator_, and leaves the original unfitted. Dense arrays, CSR/CSC sparse matrices and pandas DataFrames are accepted, subject to the wrapped model's own requirements. DataFrame column names are retained for column-selecting pipelines.

import numpy as np
from sklearn.linear_model import Ridge
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler

from conformity import ConformalRegressor

rng = np.random.default_rng(42)
X = rng.normal(size=(200, 3))
y = X[:, 0] + rng.normal(size=200)
wrapped = Pipeline([("scaler", StandardScaler()), ("ridge", Ridge())])
model = ConformalRegressor(wrapped, tts_kwargs={"random_state": 0}).fit(X, y)
assert not hasattr(wrapped[-1], "coef_")
assert model.estimator_[0].n_samples_seen_ == 160
assert model.n_calibration_ == 40
model.set_params(estimator__ridge__alpha=2.0)
assert not hasattr(model, "estimator_")
assert not model.is_calibrated_

Results and interpretation

The scaler sees only 160 fitting rows; 40 are reserved for residual calibration. set_params invalidates the fitted clone and all calibration state, so refit after changing model configuration. Refitting creates a fresh clone. Recalibration replaces scores and warns when scores already exist.

Workflow choices

Constructor auto_calibrate=True supports normal scikit-learn fit, cloning and cross-validation. Use auto_calibrate=False when you have an explicit independent calibration partition. Fit-time overrides are available but constructor choices are easier to preserve through cloning.

An outer learned preprocessing step followed by an automatically calibrating wrapper is supported computationally, but preprocessing then learns from the internal calibration features. That combination does not satisfy the usual split-conformal proof. Manual calibration held out before the entire pipeline fit avoids this issue; stateless or independently fixed transformations also avoid learning from the internal split. See the preserved pipeline discussion.

Fit weights and step-qualified *__sample_weight vectors are validated and sliced with the fitting split. Calibration remains unweighted. Other fit metadata is forwarded unchanged; callers must handle its alignment. Weighted evaluation describes a weighted test population and does not establish weighted calibration coverage.

Extending the base class

BaseConformalPredictor is abstract: subclasses must implement calibrate to compute and store held-out scores. Its shared lifecycle manages the fitted clone, input validation, score storage and finite-sample thresholds. A new scoring method also needs its own prediction-region construction and statistical justification; inheriting the lifecycle alone does not establish weighted or conditional coverage.

Next steps

Use cross-validation for point-model development, then reserve untouched calibration and test data for final uncertainty evaluation. Inspect the generated class references for signatures and lifecycle methods.