Show code

2026-10-13
Motivating Example
Polynomial Functions
Step Functions
Regression Splines
Motivating Example
Polynomial Functions
Step Functions
Regression Splines
Simple Linear Regression models the association between one predictor X and an outcome Y:
\[ Y = \beta_0 + \beta_1 X + \varepsilon \]
Polynomial Regression models the association between predictor X and outcome Y with a polynomial function. For example, X can be related with Y with a cubic polynomial:
\[ Y = \beta_0 + \beta_1 X + \beta_2 X^2 + \beta_3 X^3 + \varepsilon \]
The estimates of \(\boldsymbol \beta\) can be found by minimizing the following least squares formula for a given data set:
\[ L(\boldsymbol \beta) = \sum^n_{i=1}(Y_i-\hat Y_i)^2 \]
\[ \hat Y_i = \hat \beta_0 + \hat\beta_1 X_i + \hat\beta_2 X_i^2 + \hat\beta_3 X_i^3 \]
Polynomial functions can be utilized in GLMs as well. The model below is for a logistic model:
\[ P(Y=1|X) = \frac{\exp\left\{ \beta_0 + \sum^3_{j=1}\beta_j X^j \right\}}{1 +\exp \left\{ \beta_0 + \sum^3_{j=1}\beta_j X^j\right\}} \]
linear_model = smf.ols("y ~ x", data=linear_fit_data).fit()
linear_grid = pd.DataFrame(
{"x": np.linspace(linear_fit_data["x"].min(), linear_fit_data["x"].max(), 300)}
)
linear_grid["predicted_y"] = linear_model.predict(linear_grid)
(
ggplot(linear_fit_data, aes(x="x", y="y"))
+ geom_point(size=1.2, alpha=0.65)
+ geom_line(
data=linear_grid,
mapping=aes(x="x", y="predicted_y"),
inherit_aes=False,
size=1.1,
)
+ theme_bw()
)
quartic_model = smf.ols(
"y ~ x + I(x ** 2) + I(x ** 3) + I(x ** 4)",
data=quartic_fit_data,
).fit()
quartic_grid = pd.DataFrame(
{"x": np.linspace(quartic_fit_data["x"].min(), quartic_fit_data["x"].max(), 300)}
)
quartic_grid["predicted_y"] = quartic_model.predict(quartic_grid)
(
ggplot(quartic_fit_data, aes(x="x", y="y"))
+ geom_point(size=1.2, alpha=0.65)
+ geom_line(
data=quartic_grid,
mapping=aes(x="x", y="predicted_y"),
inherit_aes=False,
size=1.1,
)
+ theme_bw()
)
Motivating Example
Polynomial Functions
Step Functions
Regression Splines
Stepwise Function will add horizontal lines to best explain the data at different ranges of X.
step_plot = step_plot_data.copy()
step_plot["x_bin"] = pd.cut(step_plot["x"], bins=10, include_lowest=True)
step_plot["predicted_y"] = step_plot.groupby(
"x_bin",
observed=True,
)["y"].transform("mean")
step_curve = step_plot.sort_values("x")
(
ggplot(step_plot, aes(x="x", y="y"))
+ geom_point(size=1.2, alpha=0.65)
+ geom_step(
data=step_curve,
mapping=aes(x="x", y="predicted_y"),
inherit_aes=False,
size=1.1,
)
+ theme_bw()
)
X with k different intervals.k-1 dummy variables indicating if the value X belongs to the interval or not.Motivating Example
Polynomial Functions
Step Functions
Regression Splines
Basis functions model the outcome Y with a set of predefined functions on X:
\[ Y = \beta_0 + \sum^p_{j=1}\beta_jb_j(X) + \varepsilon \]
\(b_j(\cdot)\) is allowed to be any function we define
knot_model_data = knot_data.assign(
hinge_0=np.maximum(knot_data["x"], 0)
)
knot_model = smf.ols(
"y ~ x + hinge_0",
data=knot_model_data,
).fit()
knot_grid = pd.DataFrame(
{"x": np.linspace(knot_data["x"].min(), knot_data["x"].max(), 300)}
)
knot_grid["hinge_0"] = np.maximum(knot_grid["x"], 0)
knot_grid["predicted_y"] = knot_model.predict(knot_grid)
(
ggplot(knot_data, aes(x="x", y="y"))
+ geom_point(size=0.5, alpha=0.65)
+ geom_line(
data=knot_grid,
mapping=aes(x="x", y="predicted_y"),
inherit_aes=False,
size=1.1,
)
+ theme_bw()
)
linear_spline_model = smf.ols(
"y ~ bs(x, knots=(-0.25, 0, 0.25), degree=1)",
data=more_knots_data,
).fit()
linear_spline_grid = pd.DataFrame(
{"x": np.linspace(more_knots_data["x"].min(), more_knots_data["x"].max(), 300)}
)
linear_spline_grid["predicted_y"] = linear_spline_model.predict(linear_spline_grid)
(
ggplot(more_knots_data, aes(x="x", y="y"))
+ geom_point(size=0.5, alpha=0.65)
+ geom_line(
data=linear_spline_grid,
mapping=aes(x="x", y="predicted_y"),
inherit_aes=False,
size=1.1,
)
+ geom_vline(
xintercept=[-0.25, 0, 0.25],
linetype="dashed",
size=0.4,
color="red",
)
+ theme_bw()
)
Once the number of knots and locations are chosen, a common basis function to utilize is the truncated power function:
\[ h(x,\xi_l) = (x-\xi_l)_+^p = \left\{ \begin{array}{cc} (x-\xi_l)^p & x>\xi_l\\ 0 & \mathrm{Otherwise} \end{array} \right. \]
For \(L\) knots:
\[ Y = \beta_0 + \sum^p_{j=1}x^j\beta_j + \sum_{l=1}^Lh(x, \xi_l)\beta_{p+l} + \varepsilon \]
For \(L\) knots:
\[ Y = \beta_0 + \sum^3_{j=1}x^j\beta_j + \sum_{l=1}^Lh(x, \xi_l)\beta_{3+l} + \varepsilon \]
cubic_spline_model = smf.ols(
"y ~ bs(x, knots=(-0.25, 0.25), degree=3)",
data=cubic_spline_data,
).fit()
cubic_spline_grid = pd.DataFrame(
{"x": np.linspace(cubic_spline_data["x"].min(), cubic_spline_data["x"].max(), 300)}
)
cubic_spline_grid["predicted_y"] = cubic_spline_model.predict(cubic_spline_grid)
(
ggplot(cubic_spline_data, aes(x="x", y="y"))
+ geom_point(size=1.2, alpha=0.65)
+ geom_line(
data=cubic_spline_grid,
mapping=aes(x="x", y="predicted_y"),
inherit_aes=False,
size=1.1,
)
+ theme_bw()
)
natural_spline_model = smf.ols(
"y ~ cr(x, knots=(-0.25, 0.25))",
data=natural_spline_data,
).fit()
natural_spline_grid = pd.DataFrame(
{"x": np.linspace(natural_spline_data["x"].min(), natural_spline_data["x"].max(), 300)}
)
natural_spline_grid["predicted_y"] = natural_spline_model.predict(natural_spline_grid)
(
ggplot(natural_spline_data, aes(x="x", y="y"))
+ geom_point(size=1.2, alpha=0.65)
+ geom_line(
data=natural_spline_grid,
mapping=aes(x="x", y="predicted_y"),
inherit_aes=False,
size=1.1,
)
+ theme_bw()
)
In Python, B-Spline typically refers to the general use of basis functions for models, such as truncated-power, cubic splines, or natural cubic splines.
When reading scientific papers, B-Splines typically refers to the implementation of one type of basis function: Cox-de Boer recursion.
For this class, we will follow the former and not the latter.