import numpy as np
import polars as pl
import matplotlib.pyplot as plt
import pimpmyplot as pmp
from lpspline import bs
from lpspline.constraints import Monotonic, Convex, Anchor, Concave
# -------------------------- Create demo dataset with wiggly target
np.random.seed(50)
N = 1000
x = np.linspace(-5, 5, N)
by = np.random.randint(0, 3, N)
y = (np.tanh(x)+1) + np.exp(-x/5) + np.sin(2*x)/2 + np.random.randn(N)*0.05
df = pl.DataFrame({"x": x, 'by': by,"y": y})
# -------------------------- Model with constraints
anchor_points = [(-2, 1.5), (4, 2)]
model = (
# Try changing the spline using pwl or cs
+bs("x", knots=np.linspace(-10, 10, 30), degree=3, tag='bs')
.add_constraint(
# Try changing the constraint here
Convex(start=2, end = 10),
Monotonic(decreasing=True, start=-10, end = -1),
Anchor(*anchor_points),
Bound(lower=None, upper=2.5, n=10, start=0, end=2)
)
)
# -------------------------- Model without constraints for comparison
model_nocs = (
+bs("x", knots=np.linspace(-10, 10, 30), degree=3, tag='bs')
)
model.fit(X=df, y=df['y'])
model_nocs.fit(X=df, y=df['y'])
# -------------------------- Cute plot to visualize result
plt.scatter(df['x'], df['y'], color='k', s=5)
plt.plot(df['x'], model.predict(df), color='orange', linewidth=2, label='Spline with constraints')
plt.plot(df['x'], model_nocs.predict(df), color='lightblue', linewidth=2, label='Spline without constraints')
for p in anchor_points:
plt.scatter(*p, color='r', s=100, zorder=100)
plt.scatter(*p, color='r', s=100, zorder=100, label='Anchor points')
plt.axvspan(xmin=-6, xmax=-1, color='green', alpha=.03, label='Monotonic constraint')
plt.axvspan(xmin=2, xmax=6, color='blue', alpha=.03, label='Convex constraint')
pmp.legend(loc='ext side upper right')
pmp.remove_axis('top', 'right')
plt.title('Constraints showcase', fontweight='bold')