Shape Constraints¶
magli supports shape constraints on univariate spline terms (s()) and on
GAM / LinGAM. In backend='eilers', monotonicity and convexity
constraints are enforced via pyGAM’s iterative active-set asymmetric
difference penalty on violated B-spline coefficient differences, while
periodicity wraps the B-spline basis and penalty across the domain boundaries.
Note
Monotonicity and convexity constraints (mono_inc, mono_dec,
convex, concave) are provided by the Eilers-Marx engine
(backend='eilers'). Periodic smooths (constraint='periodic') are
supported in both engines, using wrapped B-splines in backend='eilers'
and via cyclic cubic/P-spline bases (bs='cc' / bs='cp') in
backend='wood'.
Available constraints¶
Constraint |
Penalty |
Effect |
|---|---|---|
|
\(\lambda D_2^\top D_2\) |
Standard smoothness (default) |
|
\(\lambda D_2^\top D_2 + C_{\text{inc}}(\beta)\) |
Enforces monotonically increasing curve (\(\Delta\beta_j \ge 0\)) |
|
\(\lambda D_2^\top D_2 + C_{\text{dec}}(\beta)\) |
Enforces monotonically decreasing curve (\(\Delta\beta_j \le 0\)) |
|
\(\lambda D_2^\top D_2 + C_{\text{cvx}}(\beta)\) |
Enforces convexity (U-shaped, \(\Delta^2\beta_j \ge 0\)) |
|
\(\lambda D_2^\top D_2 + C_{\text{ccv}}(\beta)\) |
Enforces concavity (∩-shaped, \(\Delta^2\beta_j \le 0\)) |
|
Circular \(\lambda D_2^\top D_2\) / cyclic |
Enforces \(f(a) = f(b)\) with wrapped basis and penalty (both backends) |
Effect of shape constraints on noisy data, where (left) constraint='mono_inc'
suppresses non-physical local dips in a dose-response curve; (centre)
constraint='mono_dec' damps rebounds in an exponential decay curve;
(right) constraint='periodic' (or Wood’s bs='cc') joins value and
slope seamlessly across the 24-hour cycle boundary.¶
How they work¶
Monotonicity and convexity combine the standard smoothness penalty \(\lambda D_2^\top D_2\) with an iterative active-set penalty \(C(\boldsymbol{\beta})\):
where \(\lambda_{\text{con}} = 10^9\), \(\lambda_{L2} = 10^{-3}\), and
\(\mathbf{v}(\boldsymbol{\beta}) \in \{0, 1\}^{K-m}\) indicates which
first differences (\(m=1\) for mono_inc / mono_dec) or second
differences (\(m=2\) for convex / concave) violate the target sign:
mono_incpenalises \(\Delta\beta_j = \beta_{j+1} - \beta_j < 0\).mono_decpenalises \(\Delta\beta_j = \beta_{j+1} - \beta_j > 0\).convexpenalises \(\Delta^2\beta_j = \beta_{j+2} - 2\beta_{j+1} + \beta_j < 0\).concavepenalises \(\Delta^2\beta_j = \beta_{j+2} - 2\beta_{j+1} + \beta_j > 0\).
Because the variation-diminishing property of B-splines guarantees that a B-spline with monotone (or convex/concave) coefficients is itself monotone (or convex/concave), iteratively updating \(\mathbf{v}(\boldsymbol{\beta})\) until convergence produces a smooth curve satisfying the directional shape constraint.
Periodicity wraps the basis and penalty:
The B-spline basis is evaluated on \([0,1]\) with \(z = z \bmod 1\), so the domain is a circle.
The last \(k\) columns are folded into the first \(k\) columns, preserving the partition of unity \(\sum_j B_j(x) = 1\) and reducing the coefficient count from \(K\) to \(K_{\text{eff}} = K - k\).
The circular second-difference penalty connects index \(K_{\text{eff}}\) back to index 1 with \(C^2\) continuity across the boundary.
When to use which¶
Monotonic increasing: dose-response curves, learning curves, cumulative distributions, prices vs. demand.
Monotonic decreasing: diminishing returns, survival curves, price elasticity.
Convex: cost functions, loss landscapes, risk curves.
Concave: utility functions, production functions.
Periodic: time-of-day effects, seasonal patterns, angular coordinates (compass directions).
Caveats¶
Sufficient condition via B-spline coefficients. Enforcing \(\Delta^2\beta_j \ge 0\) on B-spline control points is a sufficient (slightly conservative) condition for convexity of \(f(x)\).
Intercept is free. The intercept column is never penalised, so the overall vertical shift is unrestricted.
Periodicity reduces capacity. A periodic spline with \(K=10\) and \(k=3\) has \(K_{\text{eff}} = K - k = 7\) free coefficients. You may need slightly larger
n_splinesto capture the same complexity as a non-periodic spline.Not available for ``te()``. Tensor-product terms do not support shape constraints because the multi-dimensional penalty structure does not admit a simple ordered-difference formulation. Apply constraints to the marginal
s()terms instead.
Examples¶
Monotonically increasing spline:
model = GAMCore("s(0, constraint='mono_inc') + l(1)", backend="eilers")
model.fit(x, y)
Periodic seasonal effect:
model = GAMCore("s(0, n_splines=12, constraint='periodic') + l(1)", backend="eilers")
model.fit(x, y)
Single-term with monotonicity:
model = LinGAM(n_splines=12, constraint='mono_inc', backend="eilers")
model.fit(x, y)