Overview
The generic0 model implements a generic random effect with a user-specified precision structure. It provides the most direct way to define custom Gaussian random effects by specifying the precision matrix structure directly.
This model is ideal when:
- You have a specific precision structure in mind (e.g., from domain knowledge)
- You want to implement custom correlation patterns
- You need to use a precision matrix derived from network/graph structures
Mathematical Formulation
The generic0 model defines random effects with:
\[\mathbf{u} \sim \mathcal{N}(\mathbf{0}, \tau^{-1} C^{-1})\]
Equivalently, the precision matrix is:
\[\mathbf{Q} = \tau \mathbf{C}\]
where:
- \(\mathbf{C}\) is a known, fixed, symmetric, non-singular \(n \times n\) precision structure matrix
- \(\tau\) is the precision hyperparameter (estimated from data)
- \(\mathbf{u}\) is the \(n\)-vector of random effects
Key Property
The matrix \(\mathbf{C}\) determines the structure of the precision, while \(\tau\) controls the scale. Larger values of \(\tau\) lead to smaller variance and stronger shrinkage toward zero.
When to Use Generic0
Custom Precision Structures
When you have a specific precision matrix from domain knowledge (e.g., physical constraints, network topology).
Graph-Based Dependencies
When dependencies are defined by a graph adjacency or Laplacian matrix.
Heterogeneous Variance
When different random effects should have different variances (diagonal C with varying entries).
Testing Custom Models
When prototyping new random effect structures before implementing specialized models.
Specification in pyINLA
Allowed Parameters
| Parameter | Required | Description |
|---|---|---|
id |
Yes | Column name for the grouping/index variable |
model |
Yes | Must be 'generic0' |
Cmatrix |
Yes | The \(n \times n\) precision structure matrix (must be symmetric, positive definite) |
hyper |
No | Prior specification for \(\log(\tau)\) |
constr |
No | Sum-to-zero constraint. Default: False |
extraconstr |
No | Dict with keys 'A' and 'e' for custom linear constraints |
n |
No | Dimension of the random effect (auto-detected from Cmatrix if omitted) |
diagonal |
No | Extra constant added to the precision diagonal for numerical stability (default: 0.0) |
weights |
No | Per-observation weighting vector applied to the random-effect contribution. Length must equal the number of observations. |
Per-Parameter Runnable Examples
Click any parameter to expand a minimal runnable snippet. All snippets assume the shared setup block below (8 latent positions, 80 observations).
# shared setup for every snippet below
import numpy as np
import pandas as pd
import scipy.sparse as sp
from pyinla import pyinla
rng = np.random.default_rng(42)
n = 8
nobs = 80
df = pd.DataFrame({
'y': rng.standard_normal(nobs),
'x': rng.standard_normal(nobs),
'idx': rng.integers(1, n + 1, size=nobs),
})
# Pass Cmatrix as a scipy.sparse matrix; the engine consumes it as (i, j, x)
# triplets, so a dense numpy adjacency would be misread. sp.eye(n) (sparse
# identity) is the simplest non-degenerate Cmatrix.
C = sp.eye(n, format='csr')
id + model + Cmatrix required trio (minimal call)
# Default: constr=False, prior=loggamma(1, 5e-5).
formula = {
'response': 'y',
'fixed': ['1', 'x'],
'random': [{'id': 'idx', 'model': 'generic0', 'Cmatrix': C}],
}
result = pyinla(formula=formula, family='gaussian', data=df)
Cmatrix non-trivial structure (RW1-style tridiagonal)
# An RW1-style precision matrix: tridiag(-1, 2, -1) with 1 on the corner
# diagonal entries. This is rank-1 deficient (constant in null space),
# so we must set constr=True and drop the intercept for identifiability.
main = np.full(n, 2.0); main[0] = main[-1] = 1.0
off = np.full(n - 1, -1.0)
R = sp.diags([off, main, off], offsets=[-1, 0, 1], format='csr')
formula = {
'response': 'y',
'fixed': ['0', 'x'],
'random': [{'id': 'idx', 'model': 'generic0', 'Cmatrix': R,
'constr': True}],
}
result = pyinla(formula=formula, family='gaussian', data=df)
hyper override the precision prior
# Default: prior='loggamma', param=[1, 0.00005], initial=4, fixed=False.
# Below: PC prior on the marginal SD with P(sigma > 1) = 0.01.
formula = {
'response': 'y',
'fixed': ['1', 'x'],
'random': [{'id': 'idx', 'model': 'generic0', 'Cmatrix': C,
'hyper': [{'prior': 'pc.prec', 'param': [1, 0.01]}]}],
}
result = pyinla(formula=formula, family='gaussian', data=df)
n explicit latent dimension
# Auto-detected from Cmatrix's row count by default; pass explicitly to assert size.
formula = {
'response': 'y',
'fixed': ['1', 'x'],
'random': [{'id': 'idx', 'model': 'generic0', 'Cmatrix': C, 'n': n}],
}
result = pyinla(formula=formula, family='gaussian', data=df)
constr sum-to-zero constraint (default False)
# Default is False because generic0 is proper as long as Cmatrix is full
# rank. Set constr=True for rank-deficient Cmatrices (e.g. RW1-like
# structure). Drop the intercept since the constraint absorbs the level.
formula = {
'response': 'y',
'fixed': ['0', 'x'],
'random': [{'id': 'idx', 'model': 'generic0', 'Cmatrix': C,
'constr': True}],
}
result = pyinla(formula=formula, family='gaussian', data=df)
extraconstr additional linear constraints \(\mathbf{A}\mathbf{x} = \mathbf{e}\)
# Pin the first and last latent positions to zero.
A = np.zeros((2, n))
A[0, 0] = 1.0
A[1, n - 1] = 1.0
e = np.array([0.0, 0.0])
formula = {
'response': 'y',
'fixed': ['1', 'x'],
'random': [{'id': 'idx', 'model': 'generic0', 'Cmatrix': C,
'extraconstr': {'A': A, 'e': e}}],
}
result = pyinla(formula=formula, family='gaussian', data=df)
diagonal extra constant on the precision diagonal
# Small numerical-stability jitter; useful when Cmatrix is near-singular.
formula = {
'response': 'y',
'fixed': ['1', 'x'],
'random': [{'id': 'idx', 'model': 'generic0', 'Cmatrix': C,
'diagonal': 1e-4}],
}
result = pyinla(formula=formula, family='gaussian', data=df)
weights per-observation weighting
# Per-observation weight vector applied to the generic0 contribution.
# Length must equal the number of observations.
w = rng.uniform(0.5, 1.5, size=nobs)
formula = {
'response': 'y',
'fixed': ['1', 'x'],
'random': [{'id': 'idx', 'model': 'generic0', 'Cmatrix': C,
'weights': w}],
}
result = pyinla(formula=formula, family='gaussian', data=df)
Hyperparameters
The generic0 model has one hyperparameter:
| Parameter | Internal Scale | Description |
|---|---|---|
| \(\tau\) | \(\log(\tau)\) | Precision scaling parameter |
Prior Specification
'random': [{ 'id': 'idx', 'model': 'generic0', 'Cmatrix': C, 'hyper': [{ 'prior': 'pc.prec', # PC prior for precision 'param': [1, 0.01], # P(sigma > 1) = 0.01 'initial': 4 # Starting value for log(tau) }] }]
Fixed Precision
import math 'hyper': [{ 'initial': math.log(10), # Fix precision at tau = 10 'fixed': True }]
Prior catalog (one snippet per family; 1:1 with the generic0 parity scenarios)
Every snippet below sets a single prior on the log-precision \(\theta_1 = \log\tau\). All assume the shared setup from the Per-Parameter Runnable Examples (Cmatrix C defined, dataframe df with column idx).
prec · loggamma classical gamma prior on \(\tau\) (default)
formula = {
'response': 'y', 'fixed': ['1', 'x'],
'random': [{'id': 'idx', 'model': 'generic0', 'Cmatrix': C,
'hyper': [{'prior': 'loggamma', 'param': [1, 0.01]}]}],
}
result = pyinla(formula=formula, family='gaussian', data=df)
prec · pc.prec PC prior on \(\sigma = 1/\sqrt{\tau}\)
formula = {
'response': 'y', 'fixed': ['1', 'x'],
'random': [{'id': 'idx', 'model': 'generic0', 'Cmatrix': C,
'hyper': [{'prior': 'pc.prec', 'param': [1, 0.01]}]}],
}
result = pyinla(formula=formula, family='gaussian', data=df)
prec · fixed pin \(\tau\) at a known value
import math
formula = {
'response': 'y', 'fixed': ['1', 'x'],
'random': [{'id': 'idx', 'model': 'generic0', 'Cmatrix': C,
'hyper': [{'initial': math.log(10), 'fixed': True}]}],
}
result = pyinla(formula=formula, family='gaussian', data=df)
prec · flat improper flat on \(\theta_1\)
formula = {
'response': 'y', 'fixed': ['1', 'x'],
'random': [{'id': 'idx', 'model': 'generic0', 'Cmatrix': C,
'hyper': [{'prior': 'flat', 'param': []}]}],
}
result = pyinla(formula=formula, family='gaussian', data=df)
prec · logtnormal half-normal on \(\tau\) (log scale)
formula = {
'response': 'y', 'fixed': ['1', 'x'],
'random': [{'id': 'idx', 'model': 'generic0', 'Cmatrix': C,
'hyper': [{'prior': 'logtnormal', 'param': [0.0, 1.0]}]}],
}
result = pyinla(formula=formula, family='gaussian', data=df)
prec · normal / gaussian normal on \(\theta_1\)
# 'gaussian' is accepted as an alias for 'normal'. Same prior.
formula = {
'response': 'y', 'fixed': ['1', 'x'],
'random': [{'id': 'idx', 'model': 'generic0', 'Cmatrix': C,
'hyper': [{'prior': 'normal', 'param': [0.0, 1.0]}]}],
}
result = pyinla(formula=formula, family='gaussian', data=df)
prec · laplace double-exponential on \(\theta_1\)
formula = {
'response': 'y', 'fixed': ['1', 'x'],
'random': [{'id': 'idx', 'model': 'generic0', 'Cmatrix': C,
'hyper': [{'prior': 'laplace', 'param': [0.0, 1.0]}]}],
}
result = pyinla(formula=formula, family='gaussian', data=df)
prec · pc.mgamma PC prior, modified-gamma variant
formula = {
'response': 'y', 'fixed': ['1', 'x'],
'random': [{'id': 'idx', 'model': 'generic0', 'Cmatrix': C,
'hyper': [{'prior': 'pc.mgamma', 'param': [1.0]}]}],
}
result = pyinla(formula=formula, family='gaussian', data=df)
prec · jeffreystdf Jeffreys-type reference prior
formula = {
'response': 'y', 'fixed': ['1', 'x'],
'random': [{'id': 'idx', 'model': 'generic0', 'Cmatrix': C,
'hyper': [{'prior': 'jeffreystdf', 'param': []}]}],
}
result = pyinla(formula=formula, family='gaussian', data=df)
prec · expression: … custom log-density (R expression)
# A custom log-prior supplied as an R expression string. The body is
# evaluated by the C engine; `theta` is the internal-scale variable.
expr = "expression: log_dens = -0.5 * theta * theta; log_dens;"
formula = {
'response': 'y', 'fixed': ['1', 'x'],
'random': [{'id': 'idx', 'model': 'generic0', 'Cmatrix': C,
'hyper': [{'prior': expr, 'param': []}]}],
}
result = pyinla(formula=formula, family='gaussian', data=df)
prec · table: … custom tabulated log-density
# Inline tabulated prior: alternating (theta, log_density) pairs.
# The C engine requires at least 4 grid points.
table_prior = "table: -6 -18 -2 -2 2 -2 6 -18"
formula = {
'response': 'y', 'fixed': ['1', 'x'],
'random': [{'id': 'idx', 'model': 'generic0', 'Cmatrix': C,
'hyper': [{'prior': table_prior, 'param': []}]}],
}
result = pyinla(formula=formula, family='gaussian', data=df)
Constraints
Sum-to-Zero Constraint
Use constr=True to impose \(\sum_{i=1}^n u_i = 0\):
'random': [{ 'id': 'idx', 'model': 'generic0', 'Cmatrix': C, 'constr': True }]
Note on Constraints
When constr=True is used, pyINLA automatically adds a small diagonal regularization (diagonal = 1e-4) for numerical stability.
Relationship to Other Models
- IID: Generic0 with \(\mathbf{C} = \mathbf{I}\) is equivalent to an IID random effect