# RandomEffectBasis


Random effect basis (one-hot encoding with identity penalty).


Usage

``` python
RandomEffectBasis(k=-1)
```


A random effect basis represents a grouping factor -- subject ID, site, batch, cluster -- as a penalized random intercept rather than as a fixed factor with one unconstrained parameter per level. It is equivalent to mgcv's `bs="re"` basis and to the random-intercept term of a linear mixed model: each unique level of the grouping variable gets its own one-hot column, and an identity penalty shrinks the estimated level effects toward their common mean, with the amount of shrinkage controlled by a single smoothing parameter selected by GCV or REML (equivalent to the group-level variance in a mixed model). Choose this basis over an ordinary fixed factor whenever the number of levels is large, levels have unbalanced sample sizes, or the goal is to borrow strength across levels rather than to estimate every level's effect independently; choose it over [MRFBasis](MRFBasis.md#whittaker.MRFBasis) or [FactorSmoothBasis](FactorSmoothBasis.md#whittaker.FactorSmoothBasis) when the grouping levels have no spatial/ordering structure to exploit and only an exchangeable random intercept is wanted.

The covariate should be a 1-D array of group labels (strings, integers, or any hashable type).


## Parameters


`k: int = ``-1`  
Maximum number of levels to retain. If `-1` (the default), all observed levels are kept as basis functions. If a positive integer smaller than the number of observed levels, only the first `k` (in sorted level order) are used and any other levels are silently dropped from the basis (their rows become all-zero); this is rarely desirable for a random effect, so the default of `-1` should normally be left as-is unless there is a specific reason to cap the number of levels.


## Notes

Let there be `k` unique levels of the grouping factor after `fit()`. The basis matrix `B` is the `n x k` matrix of level indicators, `B[i, j] = 1` if observation `i` belongs to level `j` and `0` otherwise. The penalty matrix is the `k x k` identity,

 \mathbf{S} = \mathbf{I}\_k, 

so the roughness penalty is simply the sum of squared level effects,

 \boldsymbol{\beta}^\top \mathbf{S} \boldsymbol{\beta} = \sum\_{j=1}^{k} \beta_j^2 , 

exactly the ridge-type penalty that shrinks every group deviation toward zero as `lambda` grows, with no level treated differently from any other. Because the identity matrix is full rank, `null_space_dimension()` is always `0` -- there is no unpenalized subspace, and in principle the entire random effect can be shrunk away as `lambda \to \infty`, recovering a model with no group-level variation at all. Because the raw indicator columns are collinear with an overall intercept (every row sums to `1`), `identifiability_constraints()` returns a sum-to-zero constraint that should be enforced when the random effect is fit alongside a fixed intercept term. `fit()` raises `ValueError` if fewer than two unique levels are present, since a random effect with a single level carries no information about between-group variation.


## Examples


``` python
import numpy as np
from whittaker.smooths.random import RandomEffectBasis

rng = np.random.default_rng(0)
groups = rng.choice(["a", "b", "c", "d"], size=50)

basis = RandomEffectBasis().fit(groups)
B = basis.basis_matrix(groups)
S = basis.penalty_matrix()
B.shape, S.shape
```


    ((50, 4), (4, 4))


## Attributes

| Name | Description |
|----|----|
| [k](#k) | Requested or actual number of group levels. |
| [levels](#levels) | Sorted array of unique group labels retained during `fit()`. |
| [n_basis](#n_basis) | Number of basis functions, i.e. the number of retained group levels. |

------------------------------------------------------------------------


### k


Requested or actual number of group levels.


`k: int`


Before `fit()` is called, returns the `k` value passed at construction (the requested cap on the number of levels, or `-1` for "all levels"). After `fit()`, returns the actual number of group levels retained.


------------------------------------------------------------------------


### levels


Sorted array of unique group labels retained during `fit()`.


`levels: NDArray`


------------------------------------------------------------------------


### n_basis


Number of basis functions, i.e. the number of retained group levels.


`n_basis: int`


#### Raises


`RuntimeError`  
If accessed before `fit()` has been called.


## Methods

| Name | Description |
|----|----|
| [basis_matrix()](#basis_matrix) | Evaluate the one-hot random effect basis at `x`. |
| [fit()](#fit) | Fit the random effect basis to training data. |
| [identifiability_constraints()](#identifiability_constraints) | Return the sum-to-zero constraint row for the intercept. |
| [null_space_dimension()](#null_space_dimension) | Return `0`: the identity penalty has no unpenalized subspace. |
| [penalty_matrix()](#penalty_matrix) | Return the `(k, k)` identity penalty matrix. |

------------------------------------------------------------------------


### basis_matrix()


Evaluate the one-hot random effect basis at `x`.


Usage

``` python
basis_matrix(x)
```


#### Parameters


`x: NDArray`  
Grouping variable values. Shape `(n,)`. Values not seen during `fit()` produce an all-zero row (no level indicator is set).


#### Returns


`NDArray`  
Design matrix of shape `(n, k)` where `k` is the number of levels retained during `fit()`. Each row has at most one entry equal to `1.0`.


------------------------------------------------------------------------


### fit()


Fit the random effect basis to training data.


Usage

``` python
fit(x)
```


Determines the set of unique group levels present in `x`; these define the columns of the basis matrix produced by `basis_matrix()`.


#### Parameters


`x: NDArray`  
Training grouping variable. Shape `(n,)`, of any hashable dtype (strings, integers, etc.).


#### Returns


`RandomEffectBasis`  
Returns `self` for method chaining.


#### Raises


`ValueError`  
If fewer than 2 unique levels are present in `x`, or if `k` is requested but is less than 2.


------------------------------------------------------------------------


### identifiability_constraints()


Return the sum-to-zero constraint row for the intercept.


Usage

``` python
identifiability_constraints()
```


#### Returns


`NDArray`  
A `(1, k)` matrix of equal weights `1 / k` whose product with the coefficient vector forces the mean fitted level effect to be zero, resolving the confound between the random effect's implicit constant and a fixed model intercept.


------------------------------------------------------------------------


### null_space_dimension()


Return `0`: the identity penalty has no unpenalized subspace.


Usage

``` python
null_space_dimension()
```


Because the penalty matrix is the full-rank identity `I_k`, every basis coefficient is penalized and there is no unpenalized null space, unlike smooths with derivative-based penalties (e.g. TPRS) that leave polynomial trends unpenalized.


#### Returns


`int`  
Always `0`.


------------------------------------------------------------------------


### penalty_matrix()


Return the `(k, k)` identity penalty matrix.


Usage

``` python
penalty_matrix()
```


#### Returns


`NDArray`  
The `k x k` identity matrix, where `k` is the number of retained levels. Every coefficient is penalized equally.
