Quantile Regression › Visualizing Quantile Regression
Machine Learning from ScratchAll chapters Quantile Regression › Visualizing Quantile RegressionSection 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
A table of coefficients is a weak instrument for understanding a quantile fit. The signature picture is the fan: the data, with the fitted quantile curves drawn over them. Where the fitted fan opens up, the selected quantile gaps increase. Parallel fitted levels have constant gaps, though finitely many levels cannot establish the shape of the entire conditional distribution.
PythonIn [26]Expand
from book_plot_style import PALETTE, polish_axes, use_book_style
use_book_style()
fig, ax = plt.subplots(figsize=(6.0, 4.0))
ax.scatter(
x_all[train_rows],
y_tr,
s=9,
color=PALETTE["muted"],
alpha=0.30,
label="training data",
)
ax.fill_between(
grid_x,
grid_pred[0.05],
grid_pred[0.95],
color=PALETTE["primary"],
alpha=0.18,
label="5 to 95 percent band",
)
ax.fill_between(
grid_x,
grid_pred[0.25],
grid_pred[0.75],
color=PALETTE["amber"],
alpha=0.28,
label="25 to 75 percent band",
)
ax.plot(
grid_x,
grid_pred[0.50],
color=PALETTE["muted"],
linewidth=1.1,
label=r"median, $\tau=0.5$",
)
ax.set_title("Fitted quantile curves form a widening fan")
ax.set_xlabel("Predictor $x$")
ax.set_ylabel("Response $y$")
ax.legend(loc="upper left")
polish_axes(ax)
plt.show()VisualizationOut [26]Expand

The response generator uses symmetric Gaussian noise, so its true conditional mean and median coincide. Any difference between their fitted lines is a finite-sample result. In the executed fit, the 90 percent band width grows from about 1.69 at to 17.13 at ; the 50 percent width grows from about 0.98 to 6.52. Those estimates are separate from the illustrative coefficients in the hand calculation.
The width of the bands is linear in , and it is worth isolating that fact. Plotting width against makes the comparison with a constant-variance model concrete.
PythonIn [27]Expand
fitted_width_90 = grid_pred[0.95] - grid_pred[0.05]
fitted_width_50 = grid_pred[0.75] - grid_pred[0.25]
ols_plugin_width = 2.0 * ols_halfwidthPythonIn [28]Expand
from book_plot_style import PALETTE, polish_axes, use_book_style
use_book_style()
fig, ax = plt.subplots(figsize=(6.0, 4.0))
ax.plot(
grid_x,
fitted_width_90,
color=PALETTE["primary"],
linewidth=1.1,
label="quantile band width, 5 to 95",
)
ax.plot(
grid_x,
fitted_width_50,
color=PALETTE["amber"],
linewidth=1.1,
linestyle=":",
label="quantile band width, 25 to 75",
)
ax.axhline(
ols_plugin_width,
color=PALETTE["muted"],
linewidth=1.8,
linestyle="--",
label="OLS band width (constant variance)",
)
ax.set_title("Fitted quantile widths and a constant-width reference")
ax.set_xlabel("Predictor $x$")
ax.set_ylabel("Band width in response units")
ax.legend(loc="upper left")
polish_axes(ax)
plt.show()VisualizationOut [28]Expand

The dashed line uses one residual standard deviation for every predictor value. For this heteroscedastic generator it is too wide relative to the true conditional band at small and too narrow at large . An overly wide band includes more ordinary responses; an overly narrow one excludes more. The fitted quantile widths vary with , but their finite-sample accuracy still needs checking. An OLS mean model could instead be paired with a separate conditional-variance model.
Quantile crossing, interval coverage, and limitations
Each was fit by a separate linear program, and none of those programs knew about the others. Nothing in our procedure enforces for . Even this 600-row fit can have adjacent-level crossings near the edge of the predictor range. The displayed 50 and 90 percent band endpoints are separate comparisons; their ordering does not establish order among every fitted level.
The algebra of a crossing is simple. Two fitted lines
intersect where the two right-hand sides are equal. Collecting terms in gives
so the crossing point is
where:
- : the value of the predictor at which the two fitted quantile lines intersect
- : the two quantile levels whose curves are compared
- : the estimated intercept at level
- : the estimated slope at level
provided the slopes differ. If the slopes are equal the lines are parallel and either never cross or coincide. For an example, suppose two independently fitted lines returned and . Then , and, consistently, substituting into both lines gives . To the left of the 40 percent curve sits below the 60 percent curve, as it must; to the right the ordering reverses, which is impossible for a valid conditional distribution since must hold for every .
The practical remedy is rearrangement. At any fixed , take the raw fitted values across all estimated levels and sort them into increasing order. Formally, if we have fitted curves at levels , let denote the raw fitted value at level . Define the rearranged value as the -th smallest of the raw fitted values at that , so that . In words, is the value that a sorted version of the raw fits places in -th position.
where:
- : the raw, independently fitted value at level and covariate , before any reordering
- : the rearranged value at level and covariate , obtained by sorting the raw fits across levels at that
- : the number of estimated quantile levels
Sorting happens separately at each fixed , so rearrangement changes only the assignment of raw values to levels, not the set of values themselves. Sorting is monotone by construction, so the rearranged curves cannot cross. Chernozhukov, Fernández-Val and Galichon (2010) establish finite-sample improvements in function-distance measures for rearrangement toward a monotone target. This does not promise a better estimate at every individual level or a finite-sample coverage guarantee. Here sorting is used to repair the order of our finite set of fitted values. Rearrangement is a patch, not a cure: the underlying problem is that independent estimation ignores the coupling between levels, and a joint estimator with explicit noncrossing constraints avoids crossings on the predictor domain covered by those constraints, at the cost of a larger coupled optimization problem.
PythonIn [29]Expand
small_taus = np.array([0.20, 0.40, 0.50, 0.60, 0.80])
crossing_rows, small_fits = [], {}
for n_small in (25, 50, 100, 200):
B_small = np.column_stack(
[fit_quantile_lp(X_tr[:n_small], y_tr[:n_small], t) for t in small_taus]
)
P_small = G @ B_small
small_fits[n_small] = P_small
crossing_rows.append(
{
"training rows": n_small,
"crossing adjacent-level pairs": int(
np.sum(np.diff(P_small, axis=1) < 0)
),
}
)
crossing_table = pd.DataFrame(crossing_rows)
crossing_table| training rows | crossing adjacent-level pairs | |
|---|---|---|
| 0 | 25 | 85 |
| 1 | 50 | 24 |
| 2 | 100 | 5 |
| 3 | 200 | 7 |
Each row counts the (grid point, adjacent level) pairs where the fitted curve at the lower level sits above the fitted curve at the higher level. These are diagnostics for four nested subsets, not a proof that crossings decrease whenever sample size increases. Finite-sample uncertainty and misspecification can both produce crossings. Rearranging the 25-row fit removes every crossing by construction:
PythonIn [31]Expand
raw_25 = small_fits[25]
sorted_25 = np.sort(raw_25, axis=1)
print(
f"crossings before rearrangement: {int(np.sum(np.diff(raw_25, axis=1) < 0))}"
)
print(
f"crossings after rearrangement: {int(np.sum(np.diff(sorted_25, axis=1) < 0))}"
)
print(
f"average absolute change from sorting: {np.mean(np.abs(sorted_25 - raw_25)):.3f} response units"
)ConsoleOut [32]Expand
crossings before rearrangement: 85 crossings after rearrangement: 0 average absolute change from sorting: 0.017 response units
The second count is zero for a reason that requires no data at all: sorting guarantees it. The third number tells us the price of the repair. When the raw curves barely cross, rearrangement changes almost nothing; when they cross badly, it moves the fitted values and the resulting loss changes accordingly.
PythonIn [33]Expand
line_lo = 3.0 + 1.0 * grid_x
line_hi = 3.4 + 0.9 * grid_x
cross_x = (3.0 - 3.4) / (0.9 - 1.0)
cross_y = 3.0 + cross_xPythonIn [34]Expand
from book_plot_style import PALETTE, polish_axes, use_book_style
use_book_style()
fig, ax = plt.subplots(figsize=(6.0, 4.0))
ax.plot(
grid_x,
line_lo,
color=PALETTE["primary"],
linewidth=1.1,
label=r"$\hat Q(0.4 \mid x)$",
)
ax.plot(
grid_x,
line_hi,
color=PALETTE["amber"],
linewidth=1.1,
linestyle="--",
label=r"$\hat Q(0.6 \mid x)$",
)
ax.scatter(
[cross_x],
[cross_y],
s=70,
color=PALETTE["muted"],
zorder=5,
label=r"crossing at $x^\star = 4$",
)
ax.set_title("Independently fitted quantile lines can cross")
ax.set_xlabel("Predictor $x$")
ax.set_ylabel("Fitted quantile")
ax.legend(loc="upper left")
polish_axes(ax)
plt.show()VisualizationOut [34]Expand

Now to coverage, which is the other place nominal statements can mislead. A band labelled "90 percent" is a claim about new observations: if we repeatedly draw and from the population and check whether falls inside , we would like the long-run frequency to be 0.90. Our held-out estimate was close to nominal, but that is a result to be measured, not assumed. The per-level calibration plot makes the check visual. For each level , plot the empirical fraction of held-out responses at or below the fitted -quantile against itself. A calibrated fit lies on the diagonal.
PythonIn [35]Expand
from book_plot_style import PALETTE, polish_axes, use_book_style
use_book_style()
fig, ax = plt.subplots(figsize=(6.0, 4.0))
ax.plot(
[0.0, 1.0],
[0.0, 1.0],
color=PALETTE["muted"],
linewidth=1.2,
linestyle="--",
label="perfect calibration",
)
ax.plot(
taus,
eval_table["test share below fit"],
marker="o",
color=PALETTE["primary"],
linewidth=1.0,
markersize=6,
label="fitted quantile curves",
)
ax.set_title("Held-out calibration of the quantile curves")
ax.set_xlabel(r"Nominal level $\tau$")
ax.set_ylabel("Share of test responses below the fitted quantile")
ax.legend(loc="upper left")
polish_axes(ax)
plt.show()VisualizationOut [35]Expand

The plotted fractions are close to the diagonal in this held-out sample. The measured central 90 percent coverage is 0.870 and central 50 percent coverage is 0.480. Neither measurement supplies a finite-sample guarantee or establishes calibration conditional on every : overcoverage in one predictor region can cancel undercoverage elsewhere. Misspecification, limited tail information, crossing and distribution changes can all affect performance. Standard split-conformal calibration can provide marginal finite-sample coverage under exchangeability when its calibration data are reserved appropriately; it does not automatically give conditional coverage at every predictor value.
A quantile band describes the variability of individual responses. A confidence band for a quantile curve concerns uncertainty about that fitted function. Under regularity conditions estimation uncertainty can decrease with sample size while response variability remains. A restricted linear quantile fit may also be misspecified. A marginal calibration plot can expose some errors, but it need not reveal every conditional error or produce a particular S-shape.
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!