# Fit a Convex Smooth

A convex function curves upward and so its slope is non-decreasing from left to right. This shape arises naturally in physics and engineering: the motorcycle helmet acceleration data from `mcycle` shows a sharp drop followed by a recovery, a pattern with a pronounced convex region. The `bs="cx"` basis imposes convexity across the entire range of the predictor, which prevents the smooth from developing the extra wiggles that a flexible unconstrained basis sometimes fits to noise.


# Fit both models

Load the `mcycle` dataset and fit a convex-constrained model alongside an unconstrained baseline. Both use the same formula; only the basis changes.


``` python
import whittaker as wk

data = wk.load_dataset("mcycle")

# Fit convex-constrained and unconstrained models
m_convex = wk.GAM("accel ~ s(times, bs='cx')").fit(data)
m_free   = wk.GAM("accel ~ s(times, bs='tp')").fit(data)
```


# Summarize the convex model

The EDF for the convex smooth reflects the constraint: it cannot be lower than 1 (linear) but the curvature is restricted to a single direction, so very high EDF values are suppressed.


``` python
m_convex.summary()
```


    GAM fit summary
    ============================================================
    Formula:    accel ~ s(times, bs='cx')
    Family:     Gaussian(link='identity')
    Inference:  GCV
    Observations: 133
    Coefficients: 20

    Parametric coefficients:
      Term                       Estimate    Std.Err    t value    p-value
      ------------------------ ---------- ---------- ---------- ----------
      (Intercept)                -29.0127     4.1901     -6.924  2.128e-10

    Approximate significance of smooth terms:
      Term                        EDF Ref.df     Chi.sq    p-value
      ------------------------ ------ ------ ---------- ----------
      s(times, bs='cx')          8.58      9    139.823    < 1e-16

    Total EDF:  9.58
    Scale est:  1783.561548
    Deviance:   220128.6540
    Null dev:   357878.4929
    Dev. expl:  38.5%
    GCV score:  1921.990623
    AIC:        1382.70
    BIC:        1410.39


# Compare the smooths

Let's plot the partial effect from each model. The convex smooth follows a bowl-shaped trajectory and the unconstrained smooth is free to develop local reversals that may or may not be physically meaningful.


``` python
m_convex.plot()
```


<style>
  #altair-viz-2963939f0181446bb39324f581fb7084.vega-embed {
    width: 100%;
    display: flex;
  }

  #altair-viz-2963939f0181446bb39324f581fb7084.vega-embed details,
  #altair-viz-2963939f0181446bb39324f581fb7084.vega-embed details summary {
    position: relative;
  }
</style>


``` python
m_free.plot()
```


<style>
  #altair-viz-bdecfaf6765644c89ccbd6ef21c7ce35.vega-embed {
    width: 100%;
    display: flex;
  }

  #altair-viz-bdecfaf6765644c89ccbd6ef21c7ce35.vega-embed details,
  #altair-viz-bdecfaf6765644c89ccbd6ef21c7ce35.vega-embed details summary {
    position: relative;
  }
</style>


# Check diagnostics

Run the four-panel diagnostic check on the convex model. If the residuals vs fitted plot shows a systematic arch, the convexity constraint may be too strong for this region of the data.


``` python
wk.check(m_convex)
```


<style>
  #altair-viz-e2e46eecb82c4ed38e79227a7132f4d4.vega-embed {
    width: 100%;
    display: flex;
  }

  #altair-viz-e2e46eecb82c4ed38e79227a7132f4d4.vega-embed details,
  #altair-viz-e2e46eecb82c4ed38e79227a7132f4d4.vega-embed details summary {
    position: relative;
  }
</style>


# When convexity is appropriate

The convex basis is most useful when theory dictates the shape (cost curves in economics, voltage-discharge curves in battery modeling, or recovery curves in biomechanics). Imposing the constraint when it matches reality reduces variance without introducing meaningful bias. When shape is uncertain, `bs="tp"` remains the safer default.
