Prediction Intervals and Split Conformal Prediction › Visualizing Split Conformal Prediction
Machine Learning from ScratchAll chapters Prediction Intervals and Split Conformal Prediction › Visualizing Split Conformal PredictionSection view · Sections in this chapter7 / 10 ↓
Reading tools Definitions, links & notes
Choose your expertise level to adjust how many terms are explained. Beginners see more tooltips, experts see fewer to maintain reading flow. Hover over underlined terms for instant definitions.
Article links
Make inline references clickable
The figures reuse the fitted models, calibration scores and test observations. Ordinary calculation cells prepare the additional cutoff, prediction grid, bin summaries and set-size counts; rendering cells do not refit the models or regenerate the data.
The histogram displays calibration absolute residuals and the cutoffs for two nominal levels. Scores to the right of a cutoff exceed that calibrated margin. The plot illustrates an empirical order statistic, not a promise that an identical fraction of every future batch will lie in the tail. It is a descriptive view of one synthetic calibration sample drawn once from the seeded generator, not a repeated-seed evaluation of variability in the cutoff.
PythonIn [20]Expand
q20, _ = conformal_quantile(scores_ca, 0.20)VisualizationOut [21]Expand

The calibration residuals have a long right tail in this synthetic example. The 90% cutoff exceeds the 80% cutoff, illustrating the width cost of the higher target on these same scores. The size of that increase depends on the score distribution.
The second figure shows the intervals themselves in input space. The band is the model plus and minus the fixed margin, and the points are test observations coloured by whether they fall inside.
PythonIn [22]Expand
grid = np.linspace(x_te.min(), x_te.max(), 200)
grid_pred = model.predict(grid.reshape(-1, 1))VisualizationOut [23]Expand

On this seeded test split, the uncovered points are more frequent at large absolute inputs, where the noise is larger. The band has the same vertical width everywhere, so it cannot adapt to that local noise. The binned coverage in the next figure measures the difference directly.
The third figure quantifies that gap. We split the test inputs into five bins by and compute coverage within each bin, for the constant-width and the normalized-score intervals. The horizontal line marks nominal coverage.
PythonIn [24]Expand
n_bins = 5
bin_edges = np.quantile(np.abs(x_te), np.linspace(0, 1, n_bins + 1))
bin_idx = np.clip(np.digitize(np.abs(x_te), bin_edges[1:-1]), 0, n_bins - 1)
centers, cov_const, cov_norm_b = [], [], []
for b in range(n_bins):
mask = bin_idx == b
centers.append(np.abs(x_te)[mask].mean())
cov_const.append(covered[mask].mean())
cov_norm_b.append(covered_n[mask].mean())VisualizationOut [25]Expand

Because the horizontal axis is , moving right means moving farther from the low-noise center. Constant-width coverage falls at larger . Normalization changes that pattern, but its crude scale estimate also under-covers the innermost bin. Similar overall coverage can therefore coexist with quite different local failures; neither score has a conditional-coverage guarantee.
The fourth figure shows coverage against nominal level for the sweep we computed, with the shifted-distribution result plotted as a single failing point.
VisualizationOut [26]Expand

The exchangeable evaluation lies near the diagonal and can cross it because calibration and test samples are finite. The shifted sample has coverage of about 0.807 at the same nominal 0.90 level. The predictor and calibrated margin are unchanged in that comparison; the input distribution was deliberately shifted. These are evaluations of one calibration sample and one shifted test draw, illustrating a failure mode when the original sampling assumption no longer applies.
The final figure summarizes the classification side. The distribution of prediction-set sizes tells us how much work the conformal threshold is doing.
PythonIn [27]Expand
sizes = np.arange(0, set_sizes.max() + 1)
counts = np.array([(set_sizes == s).sum() for s in sizes])VisualizationOut [28]Expand

Set size counts the labels retained by this particular probability score and calibrated cutoff. A larger set leaves more labels unresolved.
- An empty set necessarily misses the true label.
- A set containing all labels necessarily includes the true label, provided the declared class space is exhaustive.
- The size of a proper nonempty subset, including a singleton when , does not by itself determine an individual inclusion probability.
Set size alone does not identify an out-of-distribution input. Those conditional error-rate and out-of-distribution interpretations require separate evaluation. The displayed counts come from one seeded classification split, so the absence of empty sets is a conditional observation on that sample rather than a repeated-seed estimate of the empty-set rate.
Comments
Add to the conversation
A question, a correction, or an example from your own work.
No comments yet. Be the first to share your thoughts!