import whittaker as wk
import numpy as np
# Set subject and observation counts
rng = np.random.default_rng(7)
n_subjects = 10
obs_per_subject = 8
n = n_subjects * obs_per_subject
# Draw random subject intercepts and predictors
subject_id = np.repeat(np.arange(n_subjects), obs_per_subject)
subject_intercepts = rng.normal(0, 2, size=n_subjects)
x = rng.uniform(0, 5, size=n)
# Simulate response with linear trend and subject offsets
y = (
2 + 0.5 * x
+ subject_intercepts[subject_id]
+ rng.normal(0, 0.5, size=n)
)
data = {"y": y, "x": x, "subject_id": subject_id}Add a Random Effect
A random effect smooth s(group_id, bs='re') treats group-level intercepts as penalised deviations from the global mean. This is equivalent to a classical random intercept: groups with little data get pulled strongly toward the overall mean (shrinkage), while well-observed groups stay closer to their empirical mean. Use this when you have repeated observations on many subjects or clusters and want to account for between-group variation without overfitting.
Generate data
Simulate 10 subjects, each with a different underlying intercept and a small number of observations. The true intercepts are drawn from a normal distribution, exactly what the random effect prior assumes.
Each subject has its own random offset on top of a linear trend. Without accounting for this grouping, the residuals would be correlated within subjects.
Fit
Fit a model with a linear effect of x and a random effect for subject_id.
# Fit linear trend with random subject intercepts
model = wk.GAM("y ~ x + s(subject_id, bs='re')").fit(data)
model.summary()GAM fit summary
============================================================
Formula: y ~ x + s(subject_id, bs='re')
Family: Gaussian(link='identity')
Inference: GCV
Observations: 80
Coefficients: 11
Parametric coefficients:
Term Estimate Std.Err t value p-value
------------------------ ---------- ---------- ---------- ----------
(Intercept) 1.7729 0.1002 17.700 < 1e-16
x 0.3901 0.0358 10.892 1.198e-16
Approximate significance of smooth terms:
Term EDF Ref.df Chi.sq p-value
------------------------ ------ ------ ---------- ----------
s(subject_id, bs='re') 8.88 9 690.509 < 1e-16
Total EDF: 10.88
Scale est: 0.191451
Deviance: 13.2325
Null dev: 177.3253
Dev. expl: 92.5%
GCV score: 0.221597
AIC: 105.66
BIC: 131.59
The random effect appears as a single smooth term in the summary. Its EDF reflects how much variation exists across groups relative to the penalty. More between-subject variation leads to a higher EDF (less shrinkage). Check that the linear term for x has a coefficient close to the true value of 0.5.
Partial effects
wk.partial_effects(model)The partial effects plot for x shows the linear trend. The random effect panel shows the estimated subject-level deviations, each shrunk toward zero. Subjects with fewer observations or noisier data will be pulled more strongly toward the centre.
Diagnostics
wk.check(model)With a correctly specified random effect, the Q-Q plot should be straight and the residuals should show no remaining within-subject pattern. Systematic curvature in the residual plots would suggest the linear x term needs to be replaced with a smooth.