GLM Families API¶
The GLM module provides distribution family abstractions for P-spline smoothing beyond Gaussian responses, using Iteratively Reweighted Least Squares (IRLS).
Family Protocol¶
GaussianFamily¶
Gaussian family with identity link.
This wraps the existing Gaussian P-spline behaviour so the IRLS loop converges in a single iteration.
PoissonFamily¶
Poisson family with log link (§2.12.1, eq. 2.18–2.22).
Parameters:
-
offset(NDArray or None, default:None) –Log-scale exposure offset. When provided, mu = exp(eta) * exp(offset), i.e. eta_total = B @ alpha + offset.
Source code in src/psplines/glm.py
BinomialFamily¶
get_family¶
Resolve a family string or instance into a Family object.
Parameters:
-
family(str or Family) –One of "gaussian", "poisson", "binomial", or a Family instance.
-
trials(NDArray, default:None) –Binomial trials vector.
-
offset(NDArray, default:None) –Poisson offset (log-exposure).
Returns:
-
Family–Resolved family instance.
Source code in src/psplines/glm.py
Overview¶
The GLM framework extends P-splines to non-Gaussian responses via IRLS. Instead of solving the normal equations directly, the fitting loop iterates:
- Compute working response \(z = \eta + W^{-1}(y - \mu)\)
- Compute working weights \(W\) from the current \(\mu\)
- Solve \((B'WB + \lambda D'D)\alpha = B'Wz\)
- Update \(\eta = B\alpha\), \(\mu = h(\eta)\)
- Repeat until convergence
Each family defines the link function \(h\), the working weights, and the deviance.
Supported Families¶
| Family | Link | \(\mu = h(\eta)\) | Working weights \(W\) | Scale \(\phi\) |
|---|---|---|---|---|
| Gaussian | Identity | \(\eta\) | \(1\) | Estimated |
| Poisson | Log | \(\exp(\eta)\) | \(\text{diag}(\mu)\) | 1 |
| Binomial | Logit | \(t \cdot \text{sigmoid}(\eta)\) | \(\text{diag}(\mu(1-\pi))\) | 1 |
Usage with PSpline¶
Pass a family string (or instance) to the PSpline constructor:
from psplines import PSpline
# Poisson (count data)
spline = PSpline(x, counts, family="poisson")
# Poisson with exposure offset (rate modeling)
spline = PSpline(x, counts, family="poisson", offset=np.log(exposure))
# Binomial — Bernoulli (binary response)
spline = PSpline(x, binary_y, family="binomial")
# Binomial — grouped (y successes out of t trials)
spline = PSpline(x, successes, family="binomial", trials=trials_vec)
Custom Families¶
Any object satisfying the Family protocol can be passed directly:
from psplines.glm import Family
class MyFamily:
"""Custom family implementing the Family protocol."""
@property
def is_gaussian(self) -> bool:
return False
def initialize(self, y, **kwargs):
...
def working_response(self, y, eta, mu):
...
def working_weights(self, mu, **kwargs):
...
def deviance(self, y, mu):
...
def inverse_link(self, eta):
...
def phi(self, deviance, n, ed):
...
spline = PSpline(x, y, family=MyFamily())