Multinomial

Multinomial logistic family for unordered categorical responses.

Usage

Source

Multinomial(n_categories)

Multinomial models a categorical response with K unordered levels — for example, a choice among several unranked options — using a baseline-category logit model. Unlike OrderedCategorical, no assumption is made about the ordering of categories or a shared direction of covariate effects: each non-reference category k gets its own intercept alpha_k and its own loading coefficient beta_k that rescales the shared linear predictor eta, so different categories can respond differently (even in sign) to the same covariate effect. The final category K is fixed as the reference, with alpha_K = 0 and beta_K = 0. Use this family when the response is nominal (has no natural order) with more than two levels; for binary outcomes use Binomial, and for ordinal outcomes use OrderedCategorical.

Parameters

n_categories: int
Number of response categories K (must be >= 2). Responses passed to GAM.fit() should be integer-coded 1, 2, ..., K, with category K as the reference.

Notes

Category probabilities are obtained from a softmax over per-category logits built from the shared linear predictor eta:

P(Y = k \mid \eta) = \frac{\exp(\alpha_k + \beta_k \eta)} {\sum_{j=1}^{K} \exp(\alpha_j + \beta_j \eta)}, \qquad \alpha_K = \beta_K = 0.

As with OrderedCategorical, this loss does not fit the standard GLM deviance framework: link and link_inverse are the identity on eta, and a custom irls_update drives P-IRLS while an inner maximum-likelihood step (_update_params) re-estimates the per-category intercepts alpha and loadings beta at each iteration. The reported deviance is -2 times the multinomial log-likelihood of the observed categories under the fitted probabilities,

D(y, \hat P) = -2 \sum_{i} \log \hat P(Y_i = y_i \mid \eta_i).

Examples

Fit a GAM to a three-level unordered categorical response:

import numpy as np
import whittaker as wk

rng = np.random.default_rng(0)
n = 300
x = np.linspace(-3, 3, n)
eta = np.sin(x)

alphas = np.array([0.0, 0.5])
betas = np.array([1.0, -1.5])

logits = np.column_stack(
    [alphas[0] + betas[0] * eta, alphas[1] + betas[1] * eta, np.zeros(n)]
)
probs = np.exp(logits) / np.exp(logits).sum(axis=1, keepdims=True)
y = np.array([rng.choice([1, 2, 3], p=probs[i]) for i in range(n)], dtype=float)

data = {"x": x, "y": y}

model = wk.GAM("y ~ s(x)", family=wk.Multinomial(n_categories=3))
model.fit(data, method="REML")
print(model.summary())
GAM fit summary
============================================================
Formula:    y ~ s(x)
Family:     Multinomial(K=3)
Inference:  REML
Observations: 300
Coefficients: 10

Parametric coefficients:
  Term                       Estimate    Std.Err    z value    p-value
  ------------------------ ---------- ---------- ---------- ----------
  (Intercept)                 -0.0013     0.0231     -0.057     0.9545

Approximate significance of smooth terms:
  Term                        EDF Ref.df     Chi.sq    p-value
  ------------------------ ------ ------ ---------- ----------
  s(x)                       5.39      6     87.175  1.169e-16

Total EDF:  6.39
Scale est:  1.000000
Deviance:   534.0030
Null dev:   4154.4017
Dev. expl:  87.1%
GCV score:  1.858347
AIC:        546.79
BIC:        570.46

Attributes

Name Description
category_intercepts Fitted per-category intercepts alpha_1, ..., alpha_{K-1}.
category_loadings Fitted per-category loading coefficients beta_1, ..., beta_{K-1}.
n_categories Number of response categories K.
scale_known Whether the dispersion/scale parameter is fixed rather than estimated.

category_intercepts

Fitted per-category intercepts alpha_1, ..., alpha_{K-1}.

category_intercepts: NDArray | None


category_loadings

Fitted per-category loading coefficients beta_1, ..., beta_{K-1}.

category_loadings: NDArray | None


n_categories

Number of response categories K.

n_categories: int


scale_known

Whether the dispersion/scale parameter is fixed rather than estimated.

scale_known: bool

Methods

Name Description
deviance() Total multinomial deviance, implementing the family-specific deviance for
initialize() Initialize category parameters and the starting linear predictor.
irls_update() Compute the P-IRLS working response and weights, implementing the family-specific
link() Identity link, implementing the family-specific link for Multinomial.
link_derivative() Derivative of the identity link, implementing the family-specific version for
link_inverse() Identity inverse link, implementing the family-specific link_inverse for
log_likelihood() Multinomial log-likelihood, implementing the family-specific version for Multinomial.
simulate() Draw random responses from the fitted category probabilities, implementing the
unit_deviance() Per-observation deviance contributions, implementing the family-specific version for
variance() Constant variance function, implementing the family-specific variance for

deviance()

Total multinomial deviance, implementing the family-specific deviance for

Usage

Source

deviance(
    y,
    mu,
    *,
    weights=None,
)

Multinomial.

Computes -2 times the multinomial log-likelihood of the observed categories y under the fitted category probabilities implied by the linear predictor eta = mu:

D(y, \hat P) = -2 \sum_{i=1}^{n} \log \hat P(Y_i = y_i \mid \eta_i).

Returns float(len(y)) as a placeholder if the family has not yet been fitted.

Parameters

y: NDArray

Observed categories, coded 1, 2, ..., K.

mu: NDArray

Linear predictor eta (this family uses the identity link, so mu and eta coincide).

weights: NDArray | None = None
Accepted for interface compatibility; does not currently affect the computed deviance.

Returns

float
The total deviance.

initialize()

Initialize category parameters and the starting linear predictor.

Usage

Source

initialize(y)

Implements the family-specific initialize for Multinomial: estimates starting intercepts alpha (from empirical log-odds relative to the reference category) and loadings beta (set to 1) via _init_params, then returns a starting linear predictor of all zeros for the outer P-IRLS loop to refine.

Parameters

y: NDArray
Observed categories, coded 1, 2, ..., K, used to compute starting intercepts.

Returns

NDArray
Initial linear predictor eta, an array of zeros with the same shape as y.

irls_update()

Compute the P-IRLS working response and weights, implementing the family-specific

Usage

Source

irls_update(
    y,
    mu,
    eta,
)

update for Multinomial.

Overrides the default GLM IRLS step: because the multinomial deviance is not a standard exponential-family deviance in eta, this method first re-estimates the per-category intercepts alpha and loadings beta by maximum likelihood (via _update_params), initializing them on the first call (via _init_params). It then computes, for each observation i with observed category k = y_i - 1 and fitted probabilities p = P(Y_i = \cdot \mid \eta_i), the gradient and negative curvature of the multinomial log-likelihood with respect to eta_i:

\frac{\partial \ell_i}{\partial \eta_i} = \sum_{j=1}^{K-1} \beta_j \left( \mathbb{1}[j = k] - p_j \right), \qquad -\frac{\partial^2 \ell_i}{\partial \eta_i^2} = \sum_{j=1}^{K-1} \beta_j^2 p_j (1 - p_j) - 2 \sum_{j < l} \beta_j \beta_l p_j p_l.

The working weight W is the (clipped) negative curvature, and the working response is the Newton step z = eta + grad / W; both feed into the outer P-IRLS smoothing loop.

Parameters

y: NDArray

Observed categories, coded 1, 2, ..., K.

mu: NDArray

Unused; present for interface compatibility with other families.

eta: NDArray
Current linear predictor values.

Returns

tuple[NDArray, NDArray]
The working response z and working weights W, both of shape (n,).




log_likelihood()

Multinomial log-likelihood, implementing the family-specific version for Multinomial.

Usage

Source

log_likelihood(
    y,
    mu,
    scale,
    *,
    weights=None,
)

Recovers the log-likelihood from deviance via \ell = -D / 2, since deviance is defined as -2 times the log-likelihood.

Parameters

y: NDArray

Observed categories, coded 1, 2, ..., K.

mu: NDArray

Linear predictor eta.

scale: float

Unused; present for interface compatibility with other families (the multinomial scale parameter is fixed, see scale_known).

weights: NDArray | None = None
Forwarded to deviance.

Returns

float
The total log-likelihood of y under the fitted model.

simulate()

Draw random responses from the fitted category probabilities, implementing the

Usage

Source

simulate(
    mu,
    scale,
    rng,
)

family-specific simulate for Multinomial.

For each observation, computes the category probabilities from the linear predictor eta = mu via _category_probs and draws one category from {1, ..., K} according to those probabilities using rng.choice.

Parameters

mu: NDArray

Linear predictor eta at which to simulate.

scale: float

Unused; present for interface compatibility with other families.

rng: object
A random number generator exposing a choice(a, p=...) method (e.g. a NumPy Generator).

Returns

NDArray
Simulated categories, coded 1, 2, ..., K, one per row of mu.

Raises

RuntimeError
If called before the family has been fitted.

unit_deviance()

Per-observation deviance contributions, implementing the family-specific version for

Usage

Source

unit_deviance(
    y,
    mu,
)

Multinomial.

For each observation i, returns -2 \log \hat P(Y_i = y_i \mid \eta_i), the per-observation contribution to deviance. Returns an array of ones if the family has not yet been fitted.

Parameters

y: NDArray

Observed categories, coded 1, 2, ..., K.

mu: NDArray
Linear predictor eta (this family uses the identity link).

Returns

NDArray
Array of shape (n,) with the deviance contribution of each observation; these sum to the value returned by deviance.

variance()

Constant variance function, implementing the family-specific variance for

Usage

Source

variance(mu)

Multinomial.

The multinomial log-likelihood does not follow the mean-variance relationship used by standard GLM families; the working weights used by P-IRLS are instead derived directly from the curvature of the multinomial log-likelihood in irls_update. This method simply returns an array of ones so it is a no-op wherever a generic variance function might otherwise be referenced.

Parameters

mu: NDArray
Linear predictor values (only used to determine output shape).

Returns

NDArray
Array of ones with the same shape as mu.