Random Effects

Generic0 Model

Type 0 generic specification with user-defined precision structure matrix C.

← Back to Random Effects

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