Quantile Regression
Learn quantile regression from first principles: pinball loss, linear programming, conditional quantiles, crossing diagnostics, and Python implementation.
Machine Learning from ScratchAll chapters Quantile RegressionFull chapter · Sections in this chapter1 / 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
Quantile Regression
A courier company wants to promise a delivery time. A forecast of the average duration does not tell it what fraction of parcels will arrive late. Quoting a much longer time may reduce late deliveries but make the service less attractive. A conditional 90th percentile addresses the operational question: what duration has at least 90 percent of deliveries at or below it for a route with these predictors? An estimated percentile still needs validation before it becomes a service promise.
Ordinary least squares answers a different question: it fits a linear conditional-mean model when that mean is correctly specified. Fitting the coefficients does not require constant conditional variance. A constant-width prediction band is an additional modeling choice, not a restriction imposed by least squares. Quantile regression instead fits a conditional quantile at a chosen level . For a continuous conditional distribution with a unique quantile, the true 0.9 quantile has 90 percent of probability at or below it. Fitted linear quantiles need not achieve that exact conditional fraction in a finite sample.
The mechanism is a change of loss function. Squared error is minimized by the conditional mean. The pinball loss (an asymmetric absolute-value loss) is minimized by the conditional quantile. Once we make that swap, the familiar linear model machinery (design matrices, gradients, regularization) mostly carries over, but the geometry changes in important ways. The objective becomes piecewise linear and non-differentiable at zero, so we solve a linear program rather than a linear system. The fitted curves are also no longer guaranteed to be ordered across .
This chapter is the last stop in our tour of Part V: Linear and Generalized Linear Models. Everything we built for multiple linear regression, the design matrix, the intercept column, the loss-plus-optimizer framing from gradient descent and numerical optimization, reappears here with a different loss. What we gain is a model that speaks directly about risk, spread, and tail behavior, which is exactly the language we will need when we move to time-to-event data in Part VI.
Advantages
The first advantage is a direct description of different parts of the conditional distribution without choosing a complete distributional family. A mean alone does not determine spread or shape, even for symmetric, light-tailed distributions. Fitting levels such as estimates several conditional cutoffs; the gaps between them describe spread. The linear functional form remains an assumption at each level. Heteroscedasticity can therefore become a feature we estimate rather than something hidden by a single-width summary.
The second advantage is robustness in the response direction. Because the pinball loss grows linearly rather than quadratically, its response score is bounded at fixed predictors, unlike the least-squares score. This does not bound every finite-sample coefficient change; a sample tail quantile can still be determined by one extreme response. At the median, quantile regression is least absolute deviations regression, a maximum likelihood estimate for the regression coefficients under independent Laplace errors centered at zero with a common fixed positive scale, which we first met in maximum likelihood estimation. At a fixed predictor vector, the estimating score is bounded as the response varies; extreme predictors can still have high leverage. For the pinball loss the residual slopes are on the positive side and on the negative side, with magnitudes and respectively, so no slope ever exceeds in magnitude.
A useful population property is equivariance under continuous strictly increasing transformations. For such a function , quantiles obey ; log and exponential transformations on their respective domains satisfy this condition. If the conditional quantile of is modeled correctly, exponentiating its estimate targets the corresponding quantile of positive , without a conditional-mean retransformation correction. This does not say that two restricted linear fits, one on and one on , agree exactly after transformation in a finite sample.
Finally, quantile regression is a natural tool for asymmetric decision problems. Inventory levels, staffing, safety margins, and service-level agreements can involve quantile targets when their costs or probability constraints have the appropriate form. A blood-pressure reference chart that flags the top 5 percent of children by age is a quantile curve, not a mean curve. When underage and overage costs are positive and linear in the size of the error, expected pinball loss at the cost-implied matches that decision problem up to a positive scale factor.
Disadvantages
The main practical annoyance is quantile crossing. In the population the quantile function is nondecreasing in , so for every . However, each is estimated by its own optimization, so nothing in the procedure forces the fitted curve at to lie below the fitted curve at . In finite samples the curves can cross; the risk depends on sample size, the design, the predictor domain, and functional-form misspecification. At a fixed predictor value, narrowing a nested pair of target levels cannot increase its population quantile gap, leaving less separation for estimation error to overcome. A crossing fitted value is not just undesirable. It cannot be the ordered quantile function of a conditional distribution and can reverse the endpoints of a prediction band. We will see how to diagnose this, and how rearrangement repairs it, later in the chapter.
The second limitation is that efficiency at the center is worse than least squares when least squares is appropriate. For independent, identically distributed Gaussian observations with positive variance , the sample mean has variance while the sample median has asymptotic variance , so their relative efficiency (the ratio of variances) is for the median, meaning we need roughly times as much data for the same precision. The same asymptotic relative-efficiency factor applies to correctly specified median regression with independent homoscedastic Gaussian errors and a regular design. Tail precision depends on the conditional density and design as well as : normal tails have low density and can be difficult to estimate, but uncertainty need not increase monotonically toward the tails for every distribution.
Third, the objective is not smooth, so the fitted coefficients need not change smoothly as the data change; jumps can occur when an optimum switches between non-unique solutions. The general multivariable problem has no least-squares-style matrix formula, although special cases have quantile shortcuts. Ordinary differentiation is insufficient at zero residuals; we use a linear program. Its runtime depends on the sample size, predictor count, matrix representation, conditioning, and solver settings, and the piecewise-linear structure can produce non-unique optima when the data are degenerate (duplicate covariate patterns, for example), which complicates inference. Common regular asymptotic standard-error estimates use resampling or estimates of the conditional density; they do not automatically follow the OLS formula .
Fourth, and most often confused, a fitted quantile band is not a confidence interval. The band targets coverage of individual responses (here is the estimated conditional -quantile), a statement about the spread of the data; a confidence interval is a statement about the uncertainty of an estimated parameter such as a regression coefficient. The first can be wide because the data are noisy; the second can be narrow because the sample is large. Both need to be reported, and they answer different questions.
Formula
A regression model for the mean answers the question "what is the average response at this ?" A regression model for a quantile answers a sharper question: "what cutoff has at most a fraction of responses strictly below it and at least a fraction at or below it at this ?" To make that question precise we need three ingredients: a definition of a conditional quantile, a loss function whose minimizer is that quantile, and a finite-sample optimization problem we can solve. The first two are population statements; the third is what turns the idea into an algorithm.
We begin with notation. Let be a response variable and let the full predictor vector be , with for the intercept. There are nonconstant predictors and entries in the design vector. The cumulative distribution function of is , and the -quantile of , for , is
The infimum handles distributions with flat regions or jumps; where is continuous and strictly increasing it agrees with the ordinary inverse; otherwise it remains a generalized inverse. Conditioning on the predictors gives the target:
where:
- : the conditional cumulative distribution function of given
- : the value of that conditional CDF evaluated at a candidate response value
- : the infimum, the greatest lower bound of the set of response values satisfying the inequality
- : the conditional -quantile of at a given predictor value , the smallest response value whose conditional CDF reaches at least
For intuition, consider the case where is normal with mean and standard deviation . Then
where:
- : the quantile function of the standard normal distribution, evaluated at probability , which is the same -value used for confidence intervals in the z-test
- : the conditional mean of given
- : the conditional standard deviation of given
At we get , because . At we get . So if we assume normality, the conditional quantile function is just the mean function shifted by a multiple of the conditional standard deviation. Ordinary least squares estimates and, when it assumes constant variance, treats as a single number. Quantile regression estimates directly, which lets and both vary with , and lets the shape vary too.
A quantile is a local summary of a distribution: it depends on where probability mass accumulates near the cut, not just on a global average. That local character explains two features that will reappear throughout the chapter. First, different quantiles can respond to the predictors in different ways, so a single slope is not enough. Second, precision depends on the density near the selected cutoff and on the predictor design. Low normal-tail density makes extreme levels difficult in our Gaussian example, but there is no universal ordering of quantile variances. The pinball loss below encodes exactly this local, asymmetric view of the data.
Conditional quantiles and pinball loss
To estimate a quantile we need a loss function minimized at that quantile. In the following expectation calculations assume for squared error and for pinball loss. For the density-based derivatives, assume the distribution has a continuous density near the candidate cutoff. Start with the familiar case. Minimizing the expected squared error over the candidate value ,
leads to the mean. Differentiating with respect to (the finite second moment allows the locally integrable derivative to pass through the expectation) gives
which is zero exactly when . Setting the derivative to zero and checking that the second derivative, , confirms a minimum. Squared error is married to the mean.
Now replace squared error with the asymmetric absolute-value loss known as the pinball loss, or check loss. Here is the signed residual between an observation and a candidate value , and is the target quantile level:
where:
- : the residual, the signed difference between the observation and the candidate value,
- : the target quantile level in
- : the pinball loss at level for residual
- : the candidate value at which the pinball loss is being evaluated
For , the positive-residual branch is steeper: its slope magnitude is , compared with on the negative side. A compact way to write the same function is
where:
- : the residual, the signed difference between the observation and the candidate value,
- : the target quantile level in
- : the indicator function, equal to 1 when its argument is true and 0 otherwise
- : the candidate value at which the pinball loss is being evaluated, so that the loss is a function of and of
- : the pinball loss at level for residual
Check the two branches: if the indicator is zero and we get ; if the indicator is one and we get , which is positive because and . At both slopes have magnitude , and , so the pinball loss is just half the absolute error.
The asymmetry is the whole point. For a high quantile, say , a positive residual (an observation above the fitted value) is penalized by slope , while a negative residual is penalized by slope . For one unrestricted sample cutoff that minimizes this loss, at most 90 percent of observations lie strictly below it and at least 90 percent lie at or below it. For a low quantile the push goes the other way. This is how a single loss function encodes a target probability level. The left branch has slope (magnitude ) and the right branch has slope . These magnitudes are equal only when . Any other makes the two arms differ in slope, and the loss is asymmetric.
To see that this loss is minimized by the -quantile, let be the probability density function of and write the expected loss at a candidate value , where is the candidate value being scored, is the density of , and is the pinball loss defined above. We obtain
The first integral collects the observations below , where the residual is negative and the loss uses the branch; the second collects observations above .
where:
- : the candidate value being scored as a quantile
- : the probability density function of
- : the pinball loss at level , defined above
- : the expected pinball loss at the candidate value
Now differentiate with respect to . The finite first moment makes the expected loss finite; the residual-loss derivatives are bounded by 1 in magnitude, which justifies differentiation at continuity points of the CDF. Both integrands vanish at the boundary because each factor contains , so the Leibniz rule gives no boundary contribution and we only need the derivative of the integrands.
The derivative of with respect to is , and that of is . At either moving integration endpoint , the factor is zero, so each boundary contribution vanishes. Recognizing the remaining integrals as probabilities gives:
where:
- : the cumulative distribution function of evaluated at
The two terms combine: , leaving . Setting this to zero gives . When the CDF is strictly increasing at this cutoff, it selects the unique ; a flat region can give multiple minimizing values. The second derivative is , so is convex and this stationary point is a global minimum. The pinball loss is therefore the loss whose minimizer is the quantile, exactly as squared error is the loss whose minimizer is the mean.
For a finite sample, define and let be the fraction strictly below . The subgradient interval of the average pinball loss, as a function of , is . A minimum occurs exactly when this interval contains zero, giving the counting rule:
In words: the number of observations strictly below the estimate is at most , and the number at or below it is at least ; dividing by , at most a fraction of the data lie strictly below the estimate and at least a fraction lie at or below it. Ties at exactly are what allow both inequalities to hold when is not an integer, so the two sides can disagree by the mass pinned at .
The figure below makes the asymmetry visible. Each curve is a pinball loss for a different . The two constant residual slopes, together with the kink at zero, give the quantile counting condition derived above.
PythonIn [3]Expand
u_grid = np.linspace(-2.5, 2.5, 401)
pinball_levels = (0.1, 0.5, 0.9)
pinball_curves = {
level: np.where(u_grid >= 0, level * u_grid, (level - 1) * u_grid)
for level in pinball_levels
}
squared_comparison = 0.5 * u_grid**2PythonIn [4]Expand
import matplotlib.pyplot as plt
from book_plot_style import PALETTE, polish_axes, use_book_style
use_book_style()
fig, ax = plt.subplots(figsize=(6.0, 4.8))
for tau_level, color, line_style in zip(
pinball_levels,
[PALETTE["primary"], PALETTE["amber"], PALETTE["teal"]],
["-", "-.", ":"],
):
ax.plot(
u_grid,
pinball_curves[tau_level],
color=color,
linewidth=1.1,
linestyle=line_style,
label=rf"$\tau = {tau_level}$",
)
ax.plot(
u_grid,
squared_comparison,
color=PALETTE["muted"],
linewidth=1.6,
linestyle="--",
label="squared error (scaled)",
)
ax.axvline(0.0, color=PALETTE["axis"], linewidth=0.8, alpha=0.4)
ax.set_title("Pinball loss versus squared error")
ax.set_xlabel(r"Residual $u = y - q$")
ax.set_ylabel(r"Loss $\rho_\tau(u)$")
ax.set_ylim(-0.6, 2.6)
ax.legend(loc="upper center", bbox_to_anchor=(0.5, -0.18), ncol=2)
polish_axes(ax)
plt.show()VisualizationOut [4]Expand

Linear quantile regression as an optimization problem
Now we restrict attention to linear quantile functions. For a chosen , we model
The convention that absorbs the intercept , so that with a -dimensional parameter vector.
Here:
- : the coefficient vector at quantile level , of length , indexed from to
- : the intercept at level , multiplying the constant
- : the coefficient on predictor at level , for
- : the -th nonconstant predictor, for
- : the observed predictors, to which the constant is prepended
- : the full predictor vector with the prepended constant, so that is the fitted -quantile
The notation emphasizes that the coefficients depend on : each quantile level gets its own line. The estimate is the minimizer of the average pinball loss over the sample,
Compare this to the least-squares objective, which is a smooth quadratic in and, for a full-column-rank design, admits the mathematical solution that we derived in ordinary least squares. Here the objective is a sum of piecewise linear functions of . It is convex (a sum of convex functions is convex) but it has kinks wherever a residual is exactly zero, and some optima have zero residuals. Flat sets of optimal coefficients can also contain points with no zero residuals. At a kink the derivative does not exist, so "set the gradient to zero and solve" does not work. We need the machinery of convex optimization.
The standard route is to convert the problem into a linear program, which we can then hand to a mature solver. The trick is to split each residual into its positive and negative parts. Define, for every observation, the positive and negative parts of the residual:
where:
- : the observed response for the -th observation
- : the fitted quantile at the -th predictor vector
- : the positive part of the residual, nonzero only when the observation lies above the fit
- : the negative part of the residual, nonzero only when the observation lies below the fit
Both are non-negative, and their difference reconstructs the signed residual: . In the linear program we drop the max definitions and treat and as the unknowns; the optimal solution automatically sets at most one of the pair strictly positive, since subtracting from both preserves their difference and lowers the cost by . Now check that the pinball loss can be written as a weighted sum of them. Note that if then and , while if then and . If the residual is positive then equals it, , and
If the residual is negative then , , and
using in the middle step. Either way the identity holds, and the identity is linear in the unknowns . That is precisely why the problem becomes a linear program: we minimize a linear function subject to linear constraints and non-negativity.
Written out in full, for a fixed we solve
where:
- : the coefficient vector, unrestricted in sign
- : the positive and negative parts of the -th residual, both constrained to be non-negative
- : the target quantile level
- : the -th predictor vector, including the intercept entry
- : the -th observed response
To pass this to a solver we stack everything into one vector. Let be the design matrix whose -th row is , let denote the identity matrix, let be a column of ones, let be a column of zeros, and let be the stacked unknown vector defined below. Collect the unknowns as . Then the program reads
subject to the equality constraint and the variable-bound restrictions
where:
- : the stacked vector of unknowns, of length
- : the linear cost vector
- : the design matrix
- : the identity matrix
- : the observed response vector
The constraint is the residual decomposition in matrix form, and enforces non-negativity of the split residuals.
Reading the block structure: the first block produces the fitted values, adding and subtracting reconciles them with the observed , and the cost vector puts weight on positive residuals (observations above the fitted line, that is, under-predictions) and weight on negative residuals (observations below the fitted line, that is, over-predictions). The single equation per observation is exactly the residual decomposition we derived. The problem has unknowns and equality constraints. That is larger than a least-squares solve. The two identity blocks contain only nonzero entries; at fixed predictor count the block matrix becomes sparse as grows, and a sparse representation lets solvers exploit this structure.
The dual shows how the fitted score limits sensitivity to extreme responses at fixed predictors. The dual of the program above is
where:
- : the dual variable vector, one multiplier per observation
- : the dual multiplier for the -th observation, confined to the interval
- : the observed response vector
- : the design matrix
Each observation gets a multiplier that is capped between and . At the solution, for observations with positive residual (points above the fitted line) and for observations with negative residual (points below the fitted line), with taking any value in when the residual is exactly zero. The constraint is the first-order condition: the fitted line is positioned so that the signed, capped residuals balance against every column of the design matrix. Every multiplier is confined to the interval , whose bound on the magnitude is . Because is bounded independently of , an extreme response cannot push its own multiplier beyond this cap. The equilibrium condition balances the capped scores against the design columns; it does not guarantee a bound on finite-sample coefficient changes.
For the intercept column, , stationarity gives . Let , and . At nonzero residuals the dual values are fixed at or ; at zero residuals they may take any value in . The available zero-residual values balance the intercept precisely when
Here counts negative residuals, counts ties at the fitted value, and is the target count. This is the same counting inequality derived for a sample quantile. It does not require exactly a fraction of residuals to be negative. The full vector condition also needs valid zero-residual multipliers that satisfy . We will read those multipliers from the LP result rather than treating an arbitrary endpoint of each subgradient interval as an ordinary gradient.
Mathematical properties
These properties are not decorative. They tell us what quantile regression preserves, what it gives up, and where its finite-sample behavior can differ from the population ideal.
- Monotone equivariance. Population conditional quantiles commute with continuous strictly increasing transformations on the response domain. A selected empirical order statistic has the same property. A general linear quantile-regression estimator need not commute with nonlinear transformations because the transformed function may leave the linear model family.
- Median reduction. At the pinball loss is half the absolute error, so the estimator is least absolute deviations regression. For independent Laplace errors centered at zero with a common fixed positive scale, its regression coefficients are maximum likelihood estimates.
- No ordering guarantee across . Because each is optimized separately, and for are not constrained to produce ordered fitted values at any given . Population quantiles are ordered. Appropriate consistency assumptions can recover that order in the limit, but separate finite-sample fits impose no ordering constraint.
- Asymptotic normality. For independent identically distributed rows, a correctly specified linear conditional quantile, bounded predictors with nonsingular second-moment matrix, and a positive continuous conditional density with adequate local smoothness near that quantile, the usual regular quantile-regression limit applies. Define the density-weighted design moment and the plain design moment by
where:
- : the conditional density of given , evaluated at the quantile
- : the true conditional -quantile function
- : a predictor vector drawn from the population distribution (the expectation is taken over )
- : the density-weighted design moment; density affects precision through the full sandwich covariance, together with the predictor design and
- : the unweighted design moment
Then as , where is the variance of the pinball-loss score, is the sandwich matrix (bread times meat times bread), and and are defined immediately above. The density term records the local information near the quantile. At fixed and predictor distribution, multiplying all conditional densities at the quantile by the same factor divides the asymptotic covariance by ; more general precision comparisons depend on the whole density-weighted design. In distributions such as the normal example, low tail density can increase this covariance; the formula does not imply that every distribution has larger variance at more extreme levels.
- Under these regular population conditions, the influence function is bounded as varies at a fixed predictor vector. It need not be bounded as varies: high-leverage predictors multiply the capped scores. This population statement does not bound every finite-sample coefficient change.
Example: Small Numbers and Heteroscedastic Predictions
The formulas are compact, but the logic is easiest to trust when we can compute a few quantiles by hand. We start with a single median, then a slope, and finally the heteroscedastic case that motivates the entire method. The numbers are small enough to check on paper.
A single quantile from a handful of numbers
Take five response values and ask for the median, . The counting rule says we need a value with at most observations strictly below it and at least at or below it. The value has two observations below it and three at or below it , so it satisfies the rule. Let us confirm by evaluating the average pinball loss at three candidates.
- At the residuals are . With , , so the losses are and their average is .
- At the residuals are , losses , average .
- At the residuals are , losses , average .
These values compare the same average loss at three candidate medians.
Here the pinball loss at is , and the residual is the signed distance from each observation to the candidate value .
where:
- : the residual , the signed distance from each observation to the candidate value
- : the pinball loss at , which equals
- : the candidate value being evaluated as an estimate of the sample median
The minimum is at , which is the sample median. Moving up from 5 adds loss on the three points below and removes loss on the two above; because there are more points below than above, that trade is unfavorable. The balance point is exactly where the counts cross, which is the counting rule.
A slope through the origin and the weighted-median shortcut
Now consider a one-parameter model with no intercept, , fitted to four observations, where is the single slope to be estimated and is the predictor:
| ratio | weight | ||
|---|---|---|---|
| 1 | 2 | 2.00 | 1 |
| 2 | 3 | 1.50 | 2 |
| 3 | 9 | 3.00 | 3 |
| 4 | 13 | 3.25 | 4 |
Because all and the residual in observation is , the pinball loss is positively homogeneous of degree one: for , . Writing with , the objective becomes
where:
- : the ratio of response to predictor for the -th observation
- : the -th predictor value, which also serves as the weight
- : the single slope parameter being estimated
- : the pinball loss at level , which is positively homogeneous of degree one
This is a weighted pinball loss in the single unknown , and its minimizer is the weighted -quantile of the ratios with weights . Order the ratios and accumulate weight: with weight 2, then with weight 1 (cumulative 3), then with weight 3 (cumulative 6), then with weight 4 (cumulative 10). For the target cumulative weight is . The cumulative weight jumps from (after ) to (after ), so it crosses the target of at the ratio ; by the weighted-median convention we take the smallest ratio whose cumulative weight is at least the target, giving .
Checking the loss: at the fitted values are and the residuals are , giving an average loss of . At the residuals are , average loss . At the residuals are , average loss . Both neighbours are worse, confirming the minimum. Note also that this weighted-median minimizer applies because all slopes share a single parameter multiplying positive weights ; the objective is a weighted pinball loss in whose minimizer is the weighted -quantile of the ratios.
PythonIn [5]Expand
hand_x = np.array([1.0, 2.0, 3.0, 4.0])
hand_y = np.array([2.0, 3.0, 9.0, 13.0])
ratios = hand_y / hand_x
order = np.argsort(ratios)
cum_weight = np.cumsum(hand_x[order]) / hand_x.sum()
beta_weighted_median = ratios[order][
np.searchsorted(cum_weight, 0.5, side="left")
]
def pinball_average(y, yhat, tau):
resid = y - yhat
return np.mean(np.where(resid >= 0, tau * resid, (tau - 1.0) * resid))
print(f"weighted median of ratios: {beta_weighted_median:.4f}")
for beta in (2.75, 3.00, 3.25):
print(
f" beta = {beta:.2f} average pinball loss = {pinball_average(hand_y, beta * hand_x, 0.5):.4f}"
)ConsoleOut [6]Expand
weighted median of ratios: 3.0000 beta = 2.75 average pinball loss = 0.7500 beta = 3.00 average pinball loss = 0.6250 beta = 3.25 average pinball loss = 0.6875
The printed losses reproduce the hand arithmetic exactly: , , .
The shortcut can also be read directly from the objective. Rather than comparing only three candidate slopes, evaluate the average pinball loss over a grid and mark the minimizer.
PythonIn [7]Expand
beta_grid_med = np.linspace(1.5, 4.5, 301)
loss_med = np.array(
[pinball_average(hand_y, b * hand_x, 0.5) for b in beta_grid_med]
)
loss_at_weighted_median = pinball_average(
hand_y, beta_weighted_median * hand_x, 0.5
)VisualizationOut [8]Expand

This shortcut is worth remembering. For a single slope, no intercept, and positive predictors, the objective reduces to a weighted -quantile of the ratios with weights . At this is a weighted median. That is why quantile regression sometimes feels like a ranking operation rather than a fitting operation.
Heteroscedastic worked predictions
Now the case that matters. Take three illustrative quantile lines with the coefficients below. These are chosen for hand arithmetic, not claimed solver output from the later simulated dataset. Over their ordering gives a widening nominal 10th-to-90th-percentile band:
| intercept | slope | |
|---|---|---|
| 0.1 | 1.00 | 0.60 |
| 0.5 | 2.00 | 1.10 |
| 0.9 | 3.60 | 2.00 |
The fitted curves are , , and , where:
- : the fitted conditional -quantile at predictor value
- : the intercept of the fitted line at level
- : the slope of the fitted line at level
Evaluate them at three values of and record the width of the central 80 percent band, which is defined as , where is the fitted conditional -quantile and is the resulting band width in response units:
- At : the three values are , , , so .
- At : the values are , , , so .
- At : the values are , , , so .
The width is not constant: because both bounding curves are linear, the width is exactly linear in . Subtracting the two fitted lines and term by term,
which reproduces , , and at (for example, ). The slope difference is the rate at which the conditional spread grows: each additional unit of widens the 10-to-90 range by 1.40 units. An additional constant-variance normal residual model would produce a constant-width band. Least squares itself does not force that variance model.
We can also price individual predictions with the pinball loss. Suppose a new observation arrives with and . The prediction is , so the residual is . Because we use the lower branch: . Overpredicting this response at a high quantile is cheap because the lower branch of has slope (in magnitude ), so a negative residual of size is penalized by only ; a true 0.9 quantile of a continuous distribution has 90 percent of its probability below it. Our illustrative coefficients carry no empirical calibration guarantee; the small penalty here follows from the chosen loss. Now take at the same and score it against the line, whose prediction is . The residual is , so . The two residuals, and , are not of comparable size, so this comparison mainly illustrates the pricing rule: the pinball loss charges per unit on the positive side and per unit on the negative side, with both rates evaluated at the relevant .
PythonIn [9]Expand
hand_coefs = {0.1: (1.00, 0.60), 0.5: (2.00, 1.10), 0.9: (3.60, 2.00)}
rows = []
for x0 in (1.0, 5.0, 10.0):
q_lo = hand_coefs[0.1][0] + hand_coefs[0.1][1] * x0
q_mid = hand_coefs[0.5][0] + hand_coefs[0.5][1] * x0
q_hi = hand_coefs[0.9][0] + hand_coefs[0.9][1] * x0
rows.append(
{
"x": x0,
"Q(0.1|x)": q_lo,
"Q(0.5|x)": q_mid,
"Q(0.9|x)": q_hi,
"width": q_hi - q_lo,
}
)
width_check = pd.DataFrame(rows).round(2)
width_check["formula width"] = (2.60 + 1.40 * width_check["x"]).round(2)
width_check| x | Q(0.1|x) | Q(0.5|x) | Q(0.9|x) | width | formula width | |
|---|---|---|---|---|---|---|
| 0 | 1.0 | 1.6 | 3.1 | 5.6 | 4.0 | 4.0 |
| 1 | 5.0 | 4.0 | 7.5 | 13.6 | 9.6 | 9.6 |
| 2 | 10.0 | 7.0 | 13.0 | 23.6 | 16.6 | 16.6 |
The two width columns agree, which is the arithmetic identity restated in code. With the mechanics settled, we can now build the estimator properly and fit it to a realistic sample.
Implementation in NumPy, SciPy, and scikit-learn
We build the linear program from first principles with NumPy and SciPy, because the point of this chapter is to see the mechanism rather than to call it. Only after our own solver reproduces the reference fit do we bring in scikit-learn's QuantileRegressor, which solves an equivalent unpenalized problem with the HiGHS backend when configured with the settings used below.
We start with a synthetic dataset that is deliberately heteroscedastic: the response is linear in , and the noise scales up with . This is a best case for quantile regression, since the conditional quantiles really are linear, so any coverage shortfall we observe later comes from estimation noise rather than misspecification.
PythonIn [11]Expand
rng = default_rng(20240517)
n_total = 900
x_all = rng.uniform(0.0, 10.0, size=n_total)
sigma_all = 0.6 + 0.45 * x_all # spread grows with x
y_all = 2.0 + 1.1 * x_all + sigma_all * rng.standard_normal(n_total)
perm = rng.permutation(n_total)
train_rows, test_rows = perm[:600], perm[600:]
def add_intercept(x):
return np.column_stack([np.ones_like(x), x])
X_tr, y_tr = add_intercept(x_all[train_rows]), y_all[train_rows]
X_te, y_te = add_intercept(x_all[test_rows]), y_all[test_rows]ConsoleOut [12]Expand
training rows: 600, test rows: 300, parameters: 2 train noise scale ranges from 0.60 to 5.07
Two functions carry the whole method: the pinball loss for scoring, and the linear program for fitting. We write the constraint matrix in the block form derived above, and let SciPy's HiGHS interface choose its supported LP algorithm.
PythonIn [13]Expand
def pinball_loss(y, yhat, tau):
"""Average sample pinball loss for supplied predictions; its population counterpart targets the conditional tau-quantile."""
resid = y - yhat
return float(
np.mean(np.where(resid >= 0.0, tau * resid, (tau - 1.0) * resid))
)
def fit_quantile_lp(X, y, tau, return_result=False):
"""Solve min sum(tau*u + (1-tau)*v) s.t. X beta + u - v = y, u, v >= 0."""
n, p = X.shape
cost = np.concatenate(
[np.zeros(p), tau * np.ones(n), (1.0 - tau) * np.ones(n)]
)
A_eq = np.hstack([X, np.eye(n), -np.eye(n)])
bounds = [(None, None)] * p + [(0.0, None)] * (2 * n)
result = linprog(cost, A_eq=A_eq, b_eq=y, bounds=bounds, method="highs")
if not result.success:
raise RuntimeError(f"linear program failed: {result.message}")
beta = result.x[:p]
return (beta, result) if return_result else betaWe fit seven quantile levels on the training data. Each call solves one linear program. Running more levels adds their individual solve costs; those solve costs can differ across levels.
PythonIn [14]Expand
taus = np.array([0.05, 0.10, 0.25, 0.50, 0.75, 0.90, 0.95])
betas = {tau: fit_quantile_lp(X_tr, y_tr, tau) for tau in taus}
coef_table = pd.DataFrame(
{
"intercept": [betas[t][0] for t in taus],
"slope": [betas[t][1] for t in taus],
},
index=pd.Index([f"{t:g}" for t in taus], name="tau"),
).round(3)
coef_table| intercept | slope | |
|---|---|---|
| tau | ||
| 0.05 | 1.126 | 0.362 |
| 0.1 | 1.106 | 0.570 |
| 0.25 | 1.504 | 0.831 |
| 0.5 | 2.074 | 1.058 |
| 0.75 | 2.485 | 1.385 |
| 0.9 | 2.443 | 1.794 |
| 0.95 | 2.815 | 1.906 |
Read the table as a description of a stretching distribution. The true data-generating quantiles are , so their slopes are . At levels 0.05 and 0.95 these are approximately 0.360 and 1.840. The fitted slopes are estimates and their intercepts need not be monotone; we check the resulting predictions for crossings rather than inferring order from their coefficient table. One fitted sample also does not estimate the sampling variability of its coefficients.
The coefficient table can be read more directly by comparing its slope column with the true slope function. The next figure does that at the fitted levels, using the already computed coefficient vectors.
PythonIn [16]Expand
tau_slope_grid = np.linspace(0.05, 0.95, 201)
true_slope_curve = 1.1 + 0.45 * norm.ppf(tau_slope_grid)
fitted_slopes = np.array([betas[t][1] for t in taus])
true_slopes_at_fits = 1.1 + 0.45 * norm.ppf(taus)VisualizationOut [17]Expand

The helper normally returns only coefficients. Setting return_result=True also returns SciPy's successful LP result so we can inspect its actual equality-constraint dual multipliers. We check the counting inequality with a stated numerical zero tolerance, the full stationarity condition, the dual bounds, and the primal-dual objective gap.
PythonIn [18]Expand
check_rows = []
for tau in (0.10, 0.50, 0.90):
beta, lp_result = fit_quantile_lp(X_tr, y_tr, tau, return_result=True)
resid = y_tr - X_tr @ beta
zero_tol = 1e-7 * max(1.0, float(np.max(np.abs(y_tr))))
n_negative = int(np.sum(resid < -zero_tol))
n_zero = int(np.sum(np.abs(resid) <= zero_tol))
dual = lp_result.eqlin.marginals
stationarity = float(np.max(np.abs(X_tr.T @ dual)))
dual_bounds_ok = bool(
np.all(dual >= tau - 1 - 1e-8) and np.all(dual <= tau + 1e-8)
)
count_ok = bool(n_negative <= len(y_tr) * tau <= n_negative + n_zero)
objective_gap = float(abs(lp_result.fun - y_tr @ dual) / len(y_tr))
assert (
count_ok
and dual_bounds_ok
and stationarity < 1e-7
and objective_gap < 1e-7
)
check_rows.append(
{
"tau": tau,
"negative residuals": n_negative,
"near-zero residuals": n_zero,
"count inequality": count_ok,
"max stationarity residual": stationarity,
"mean primal-dual gap": objective_gap,
}
)
pd.DataFrame(check_rows)| tau | negative residuals | near-zero residuals | count inequality | max stationarity residual | mean primal-dual gap | |
|---|---|---|---|---|---|---|
| 0 | 0.1 | 59 | 2 | True | 5.224987e-13 | 1.042129e-15 |
| 1 | 0.5 | 299 | 2 | True | 4.574119e-14 | 3.789561e-16 |
| 2 | 0.9 | 539 | 2 | True | 4.218847e-15 | 8.526513e-16 |
The zero tolerance classifies residuals near a fitted hyperplane; it is not a license to assign them an arbitrary gradient. The returned dual values satisfy the permitted intervals and balance all design columns. Primal feasibility from the LP, dual feasibility, and agreement of their objectives provide an optimality check to the stated solver precision, while leaving coefficient uniqueness as a separate question.
Now the reference implementation. scikit-learn's QuantileRegressor minimizes the same objective with an optional penalty on the coefficients; setting alpha=0 removes the penalty, and fit_intercept=False prevents it from adding a second intercept column to the one we already built.
PythonIn [20]Expand
reference = QuantileRegressor(
quantile=0.5, alpha=0.0, fit_intercept=False, solver="highs"
)
reference.fit(X_tr, y_tr)
gap = np.max(np.abs(reference.coef_ - betas[0.5]))ConsoleOut [21]Expand
our intercept, slope: 2.073922, 1.057594 scikit-learn intercept, slope: 2.073922, 1.057594 largest absolute difference: 8.88e-16
The two fits agree to solver precision for this sample. They minimize the same unpenalized objective. When its optimum is non-unique, different solvers can return different optimal coefficient vectors even without numerical error.
With the model in hand, we evaluate it on the held-out 300 observations. We report the pinball loss at each level and, more importantly for this chapter, the empirical coverage of two nominal prediction bands. The test-share-below-fit column is a per-level marginal calibration check; the two nominal bands aggregate that check over and and over and respectively.
PythonIn [22]Expand
grid_x = np.linspace(0.0, 10.0, 201)
G = add_intercept(grid_x)
pred_te = {tau: X_te @ betas[tau] for tau in taus}
grid_pred = {tau: G @ betas[tau] for tau in taus}
eval_table = pd.DataFrame(
{
"tau": taus,
"test pinball loss": [pinball_loss(y_te, pred_te[t], t) for t in taus],
"test share below fit": [
float(np.mean(y_te <= pred_te[t])) for t in taus
],
}
).round(4)
eval_table| tau | test pinball loss | test share below fit | |
|---|---|---|---|
| 0 | 0.05 | 0.3182 | 0.0767 |
| 1 | 0.10 | 0.5520 | 0.1200 |
| 2 | 0.25 | 0.9933 | 0.2767 |
| 3 | 0.50 | 1.2191 | 0.4733 |
| 4 | 0.75 | 0.9508 | 0.7567 |
| 5 | 0.90 | 0.5269 | 0.9167 |
| 6 | 0.95 | 0.3153 | 0.9467 |
The third column is a per-level marginal calibration check: for accurate quantiles in a continuous model its population target is . It is not a histogram of a fully estimated probability integral transform. Deviations are sampling noise plus any estimation error in . Our linear form is correctly specified here, so there is no model-form approximation error; the deviations happen to be small in this sample. Turning those numbers into band coverage:
PythonIn [24]Expand
cover_90 = float(np.mean((y_te >= pred_te[0.05]) & (y_te <= pred_te[0.95])))
cover_50 = float(np.mean((y_te >= pred_te[0.25]) & (y_te <= pred_te[0.75])))
ols_beta, *_ = np.linalg.lstsq(X_tr, y_tr, rcond=None)
ols_sigma = float(np.sqrt(np.mean((y_tr - X_tr @ ols_beta) ** 2)))
ols_halfwidth = float(norm.ppf(0.95) * ols_sigma)ConsoleOut [25]Expand
nominal 90% band -> empirical coverage 0.870 nominal 50% band -> empirical coverage 0.480 OLS constant 90% half-width (normal assumption): 5.25
The 90 percent band covers close to 90 percent of held-out responses and the 50 percent band close to 50 percent. This is a good outcome for this sample. The model form matches the data-generating process, and the independent holdout provides an honest check; neither fact guarantees that a fitted band will attain nominal coverage in a finite sample. The last line foreshadows the contrast we will draw visually: the OLS band has one constant half-width, while ours varies with .
Key Parameters
The quantile level is the parameter that defines the model, not a tuning knob in the usual sense. It selects which feature of the conditional distribution we estimate, and different values of produce different coefficient vectors that are not required to be ordered. Choosing is a decision-theoretic act: pick the level that appears in your cost structure or your reporting requirement, such as 0.95 for a service-level target or 0.05 for a lower reference limit.
For fit_intercept=True, QuantileRegressor adds to the mean pinball loss and leaves its separately fitted intercept unpenalized. If we instead supply a constant column with fit_intercept=False, that column is treated as an ordinary coefficient and is penalized when alpha>0. Our comparison uses alpha=0, so no coefficient is penalized. This is the regularization studied in lasso regularization. It shrinks coefficients toward zero and can zero out predictors entirely, which is useful when the number of predictors is large relative to the sample. Because the penalty acts on coefficient magnitudes, features must be on comparable scales for it to be meaningful; standardize first, as described in standardization.
The solver argument selects the underlying optimizer. "highs" is the default; practical solve time depends on the row and feature counts, sparsity, conditioning, and available hardware. The "highs-ds" and "highs-ipm" methods choose the dual simplex and interior-point variants, which can behave differently on degenerate designs. The fit_intercept flag should be set to False whenever we supply our own intercept column, to avoid fitting a redundant second intercept.
Key Methods
fit(X, y) builds and solves the linear program and stores the coefficient vector in coef_, with an intercept in intercept_ when fit_intercept=True. predict(X) evaluates the fitted linear quantile function at new rows; there is no link function and no inverse link, because the fitted conditional quantile is evaluated directly on the response scale used in training.
We compute the pinball loss ourselves rather than relying on a built-in score. The pinball loss is not a proportion of variance explained, and interpreting it requires knowing . Reporting an -like number at an arbitrary quantile invites confusion. The loss is in the units of , so a value of at means the average weighted residual magnitude is response units, not percent of anything. For inference on the coefficients (standard errors, confidence intervals, joint tests across ), the usual practice is a bootstrap over observations, refitting every within each replicate so that the correlation between levels is preserved.
Visualizing Quantile Regression
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.
Practical Implications
This section connects the fitted quantile model to practical decisions and deployment, including diagnostics.
Where Quantile Regression Helps
Quantile regression is appropriate when the decision or scientific question concerns a conditional quantile rather than a conditional mean. Service-level agreements, inventory buffers, and staffing decisions can involve quantile targets under suitable loss or probability constraints. Value-at-risk, pediatric growth charts, clinical reference limits, and wage distributions also provide quantile questions. For a single-period inventory decision with linear underage cost and overage cost , minimizing expected cost targets level . A high level is appropriate when underage is much more expensive. Other decisions need their own loss and assumptions. The method is especially informative when conditional spread changes with the predictors, because separate fitted levels can describe a fan rather than impose a constant-width band. Its response score is bounded at fixed predictors, whereas the least-squares score grows with the residual. This does not prevent a finite-sample tail fit from following one extreme response, and high-leverage predictors remain a concern.
The fit is descriptive unless additional assumptions support causal or deployment claims. A quantile regression on wages can compare associations at different conditional levels, but interpreting a coefficient as a policy effect at a selected quantile requires identification assumptions beyond a correctly specified linear quantile. Likewise, a fitted 95th percentile is not by itself a 95 percent service guarantee; coverage is an empirical property of predictions on new data. When a full predictive distribution is required, a distributional or probabilistic model may be more direct. When only the conditional mean is needed, least squares directly targets it. With a correctly specified regular linear model and independent homoscedastic Gaussian errors, it is asymptotically more efficient than median regression.
Best Practices for Fitting and Checking
Choose the quantile levels before inspecting results and tie them to decision thresholds or reporting requirements. A central 90 percent band uses and ; a central 50 percent band uses and . Fit each level independently, but retain the full coefficient vector for every level rather than only a summary. With scikit-learn’s QuantileRegressor, set fit_intercept=False when the design matrix already contains an intercept column, and use alpha=0 for an unpenalized fit. scikit-learn recommends the HiGHS methods, with solver="highs" as the default; check convergence, feasibility, and runtime on the actual design, since "highs-ds" and "highs-ipm" can behave differently on degenerate or poorly conditioned designs. If alpha>0, standardize predictors first because the penalty acts on raw coefficient magnitudes.
Evaluate the fitted levels on data held out of estimation. Compare each nominal with the empirical share of held-out responses at or below the fitted quantile, and count crossings on a dense grid of predictor values rather than only at the observed rows. If crossings appear, rearrangement sorts the fitted values across levels at each predictor value and removes them by construction, but report both raw and rearranged results because the repair changes the fitted values. For coefficient uncertainty and joint statements across levels, use a paired bootstrap over observations and refit every selected level within each replicate so that dependence between levels is preserved. Clustered or otherwise dependent observations require a resampling scheme that respects that dependence. As a diagnostic, compare the median fit with least absolute deviations and, when the errors look symmetric and homoscedastic, with ordinary least squares; a discrepancy can reflect sampling variation, asymmetric errors, misspecification, or numerical problems and should be investigated rather than treated as proof that one estimator is better.
Data Requirements and Preprocessing
The design requirements largely mirror those of linear regression. Include an intercept column unless the application justifies a no-intercept model, encode categorical predictors in the same way as for multiple linear regression, and check multicollinearity because the interpretability of individual coefficients depends on the same identifiability conditions. Missing-data handling should be decided before fitting; complete-case analysis is valid only under restrictive assumptions, and imputation that is meant to reflect sampling uncertainty should be carried out within the resampling or cross-validation loop.
Scaling is mainly a regularization issue. When alpha=0, rescaling a predictor changes its coefficient but not the fitted quantile function, apart from numerical conditioning. When alpha>0, the penalty is applied to the coefficients on the scale supplied to the solver, so standardize using training data only and map coefficients back to original units if those units are needed for interpretation. Continuous strictly increasing transformations of the response commute with population quantiles, but refitting a restricted linear model after transforming can produce a different finite-sample fit; a transformation should not be chosen solely to force a desired coverage result.
Common Pitfalls
A fitted quantile band is not a confidence interval. The interval between and describes the spread of individual responses, whereas a confidence interval describes uncertainty about an estimated parameter or curve. The first can be wide because the data are noisy. The second can be narrow because the sample is large. Reporting one while implying the other is a common source of confusion. A related mistake is treating a nominal label as a guarantee: a “90 percent” band is a claim about held-out coverage, and it should be checked on data that played no role in fitting rather than assumed from the label.
Tail levels require particular care. For independent observations and a continuous response, the expected number below the true quantile is , but a quantile regression fit is not based on only those five observations; its precision also depends on the conditional density and the design. Choosing a less extreme level, increasing the sample, or reporting uncertainty explicitly is preferable to presenting a tail estimate as precise when the data are sparse. Another frequent error is assuming that coefficients, or fitted values, are automatically ordered across . Independent optimization does not enforce that ordering, coefficient ordering alone needs the predictor domain: ordered coefficients are sufficient when every design entry is nonnegative, but negative predictors can reverse their contribution, and ordering every coefficient is not necessary on a bounded domain, and crossings should be diagnosed and either repaired by rearrangement or avoided with a joint constrained estimator. Finally, selecting levels after inspecting test-set performance, or reporting only the most favorable level, undermines the calibration check.
Computational Considerations
Each quantile level is a separate linear program with unknowns and equality constraints, where is the number of observations and is the number of nonconstant predictors. At fixed predictor count, the block constraint matrix becomes sparse as the sample grows, although our helper allocates it densely. The optimization problem is larger than a least-squares solve and has no single OLS-style normal-equation solve with a fixed linear map from responses to coefficients. SciPy’s HiGHS interface solves each level independently, and because the levels share no fitted state they can be fitted in parallel. Runtime depends on the design and solver settings. For many levels, parallelizing across is usually simpler than attempting a single monolithic optimization.
The dense formulation becomes the practical bottleneck as grows. Our dense -row matrix contains entries, so its memory grows quadratically in at fixed ; the feasible size depends on available memory. For larger data, sparse matrix representations, specialized quantile-regression solvers, or a smaller set of levels are more appropriate. Reducing the number of levels lowers computation but does not fix poor conditioning or degeneracy in a given level. Solver tolerances should be checked against the first-order conditions, especially when duplicate covariate patterns or many zero residuals make the optimal coefficient vector non-unique.
Evaluation, Reporting, and Deployment
Evaluate a quantile fit with the pinball loss at each level and with held-out calibration. The pinball loss is in the units of the response and depends on , so it should not be interpreted like or compared across levels as if a single scale measured overall fit. Report empirical coverage for any nominal band, preferably with an assessment of sampling uncertainty, and inspect calibration within relevant subgroups or predictor regions when conditional coverage matters. A marginal calibration plot can reveal systematic error, but it does not guarantee calibration at every predictor value.
In deployment, preserve the training preprocessing pipeline and the definitions of the selected levels, log predictions and outcomes, and monitor coverage and pinball loss as new data arrive. Distribution shift can invalidate a previously calibrated band, so refitting or recalibration should be triggered by evidence of drift rather than by a fixed schedule alone. If marginal finite-sample coverage is required, a conformal procedure with a separate calibration set can provide that guarantee under exchangeability, but it addresses a different target from correctness of each conditional quantile. If the goal is a full predictive distribution, distributional regression is more direct; if the goal is only a conditional mean, least squares targets that quantity directly, with the Gaussian-efficiency comparison described earlier. Quantile regression is most useful when the tail, the spread, or robustness to extreme responses is the point.
Summary
Quantile regression replaces squared error with the pinball loss , whose population minimizer is the -quantile rather than the mean. Restricting the conditional quantile to be linear in the predictors, , turns estimation into minimizing an average of piecewise linear terms. That objective is convex but non-differentiable, so we reformulate it as a linear program with unknowns, splitting each residual into non-negative positive and negative parts. The dual of that program exposes its bounded response score at fixed predictors: every observation's multiplier is capped between and , and the first-order condition reduces, at the intercept, to the counting rule that the negative-residual count must bracket after including zero residuals.
The payoff is a description of several parts of the conditional distribution rather than one mean. Fitting several levels produces a fan whose width is a local measure of spread, which lets us estimate variation in conditional spread directly. A conditional-mean model can also be combined with a separate variance model. The costs include the median-regression efficiency penalty under the regular Gaussian conditions described earlier, potentially low precision when conditional density near a cutoff is sparse, depending also on and the predictor design, no general OLS-style fixed linear map from responses to coefficients, and quantile curves that can cross because each level is estimated independently. Crossing is diagnosed by counting order violations on a grid and repaired by rearrangement, which sorts fitted values across levels at each covariate point and removes crossings by construction.
The most consequential caution is that a nominal band is not a guarantee. Reporting "90 percent" is a claim about held-out coverage, and the only honest way to support it is to measure coverage on data that were not used to fit the model, exactly as we did with the per-level calibration plot. A misspecified form, sparse information near an extreme quantile, or a new target population can invalidate a nominal label. Quantile regression alone supplies no general finite-sample coverage guarantee; methods such as split conformal prediction require separate calibration data and assumptions such as exchangeability. Part XII currently lists planned chapters on calibration and prediction uncertainty. Within those limits, quantile regression complements the conditional-mean models in Part V by targeting selected quantiles without specifying a full response distribution, in the same language we will need when the response becomes a time to an event in the chapters that follow.
Quiz
Ready to test your understanding? Take this quick quiz to reinforce what you've learned about quantile regression.
Quantile Regression Quiz
Join the discussionAsk a question or share what you learned.
Keep exploring
Reference
Citation details
Cite or share this article.


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!