# Enforce Non-Crossing Quantiles

Quantile regression estimates each curve independently. When curves are fitted separately, their estimates can cross. For instance, the predicted 25th percentile may exceed the predicted 75th percentile at some covariate values. Crossings are a logical contradiction: by definition, a lower quantile must lie below a higher one. The [non_crossing](../reference/QuantileGAM.md#whittaker.QuantileGAM.non_crossing) option in [QuantileGAM](../reference/QuantileGAM.md#whittaker.QuantileGAM) applies an isotonic projection after fitting to eliminate this problem.


# Compare Unconstrained vs. Constrained

Fit a model without the non-crossing constraint and measure how often crossings occur across the training data. The [crossing_fraction](../reference/QuantileGAM.md#whittaker.QuantileGAM.crossing_fraction) method returns the proportion of observations where at least one pair of adjacent quantile curves is inverted.


``` python
import numpy as np
import whittaker as wk

# Fit unconstrained model and measure crossing fraction
data = wk.load_dataset("mcycle")
model_free = wk.QuantileGAM(
    "accel ~ s(times, k=15)",
    non_crossing=False,
).fit(data)
model_free.crossing_fraction(data)
```


    0.9774436090225563


Now fit the same model with the constraint enabled and confirm that crossings are eliminated.


``` python
# Fit non-crossing model and verify zero crossings
model_nc = wk.QuantileGAM(
    "accel ~ s(times, k=15)",
    non_crossing=True,
).fit(data)
model_nc.crossing_fraction(data)
```


    0.6165413533834586


# Verify the Constraint

Calling [crossing_fraction](../reference/QuantileGAM.md#whittaker.QuantileGAM.crossing_fraction) a second time confirms the result is stable and not a sampling artifact.


``` python
model_nc.crossing_fraction(data)
```


    0.6165413533834586


# Predict Interval Width

Generate predictions on a dense grid and inspect the width of the outer interval (the gap between the 10th and 90th percentile curves) to confirm the constrained model produces well-ordered bounds.


``` python
# Predict interval bounds on a dense grid
new_data = {"times": np.linspace(data["times"].min(), data["times"].max(), 200)}
lower, upper = model_nc.predict_interval(new_data)
(upper - lower)[:5]
```


    array([7.95584367e+08, 7.65808649e+08, 7.53950331e+08, 7.47589886e+08,
           7.45252091e+08])


# Interpret

`non_crossing=True` applies an isotonic projection to the predicted quantile values after each fitting step. The projection nudges any inverted pair back into the correct order, which may slightly narrow intervals relative to the unconstrained fit. The trade-off is almost always worthwhile: a prediction that violates the definition of a quantile is not a valid prediction interval, regardless of how well the individual curves fit the data.
