@randomEffects

Random effects capture between-group variability in nonlinear mixed-effects models, allowing individual-level (or site-level, cluster-level, etc.) deviations from population parameters. The @randomEffects block declares these latent variables, each linked to a grouping column that defines the hierarchical structure.

Core Syntax

Each random effect is declared as an assignment. The right-hand side must be a RandomEffect(distribution; column=:GROUP_COL) call, where distribution is any univariate or multivariate distribution and column specifies the grouping variable in the data.

name = RandomEffect(Normal(0.0, 1.0); column=:GROUP_COL)

Multiple random effects with different distributions and grouping structures can be declared in a single block:

re = @randomEffects begin
    η_id = RandomEffect(TDist(6.0); column=:ID)
    η_mv = RandomEffect(MvNormal([0.0, 0.0], [0.5 0.0; 0.0 0.8]); column=:ID)
    η_site = RandomEffect(SkewNormal(0.0, 1.0, 0.8); column=:SITE)
end

Parsing Rules

The following constraints are enforced at macro expansion time:

  • The block must be begin ... end.
  • Only assignments are allowed.
  • The left-hand side must be a symbol.
  • The right-hand side must be a RandomEffect(...) call.
  • Exactly one positional argument is required: the distribution expression.
  • The column keyword is required and must be a symbol.
  • Unsupported keywords are rejected.
  • The symbols t and ξ (reserved for time) are forbidden inside distribution expressions.

Available Bindings in Distribution Expressions

Distribution expressions are not limited to literal values. At construction time, NoLimits resolves the following symbols within each distribution expression:

  • Fixed effects declared in @fixedEffects
  • Constant covariates declared in @covariates
  • Model functions generated by parameter blocks (e.g., neural networks, soft trees, splines, normalizing flows)
  • Helper functions declared in @helpers

This enables flexible, data-driven random-effect distributions whose parameters depend on covariates, learned representations, or user-defined transformations.

Example: Multiple Grouping Structures

Models may include random effects at several hierarchical levels simultaneously. In the following example, subject-level effects (η_id, η_mv) are grouped by :ID, while a site-level effect (η_site) is grouped by :SITE.

using NoLimits
using Distributions

model = @Model begin
    @fixedEffects begin
        s = RealNumber(0.3, scale=:log)
        μ_re = RealVector([0.0, 0.0])
        Ω_re = RealPSDMatrix([0.4 0.0; 0.0 0.7], scale=:cholesky)
    end

    @covariates begin
        t = Covariate()
    end

    @randomEffects begin
        η_id = RandomEffect(TDist(6.0); column=:ID)
        η_mv = RandomEffect(MvNormal(μ_re, Ω_re); column=:ID)
        η_site = RandomEffect(SkewNormal(0.0, 1.0, 0.7); column=:SITE)
    end

    @formulas begin
        μ = tanh(η_id) + η_mv[1]^2 + tanh(η_mv[2]) + η_site^2
        y ~ Laplace(μ, s)
    end
end

Example: Covariate- and Function-Parameterized Distributions

Random-effect distributions can be parameterized by neural networks, soft trees, or other learned functions of covariates. This is useful when the distribution of individual-level parameters is expected to vary systematically with observed characteristics.

using NoLimits
using Distributions
using Lux

chain = Chain(Dense(2, 4, tanh), Dense(4, 1))

model = @Model begin
    @helpers begin
        softplus(u) = log1p(exp(u))
    end

    @fixedEffects begin
        ζη = NNParameters(chain; function_name=:NN1, calculate_se=false)
        Γη = SoftTreeParameters(2, 2; function_name=:ST1, calculate_se=false)
        sη = RealNumber(0.6, scale=:log)
        sy = RealNumber(0.4, scale=:log)
    end

    @covariates begin
        t = Covariate()
        x = ConstantCovariateVector([:Age, :BMI]; constant_on=:ID)
    end

    @randomEffects begin
        η = RandomEffect(
            LogNormal(
                NN1([x.Age, x.BMI], ζη)[1] + ST1([x.Age, x.BMI], Γη)[1],
                sη
            );
            column=:ID
        )
    end

    @formulas begin
        y ~ Gamma(softplus(η) + 1e-6, sy)
    end
end

Example: Normalizing Flow Random Effects

For applications where standard parametric distributions are insufficiently flexible, NoLimits supports normalizing planar flows as random-effect distributions. A NormalizingPlanarFlow(ψ) transforms a base distribution through a sequence of invertible mappings, with parameters ψ registered as fixed effects via NPFParameter.

using NoLimits
using Distributions

model = @Model begin
    @fixedEffects begin
        ψ = NPFParameter(1, 3, seed=1, calculate_se=false)
        sy = RealNumber(0.3, scale=:log)
    end

    @covariates begin
        t = Covariate()
    end

    @randomEffects begin
        η_flow = RandomEffect(NormalizingPlanarFlow(ψ); column=:ID)
    end

    @formulas begin
        y ~ Gamma(log1p(abs(η_flow)^2) + 1e-6, sy)
    end
end

Metadata and Builder Accessors

The parsed random-effects object provides programmatic access to its components through the following accessor functions:

AccessorReturns
get_re_namesVector of random effect names
get_re_groupsNamedTuple mapping each effect to its grouping column
get_re_typesNamedTuple mapping each effect to its distribution type symbol
get_re_symsNamedTuple mapping each effect to the symbols used in its distribution
get_re_dist_exprsDistribution expressions (as parsed)
get_create_random_effect_distributionFunction that constructs distributions at runtime given fixed effects, covariates, model functions, and helpers
get_re_logpdfFunction computing the joint log-density of all random effects

Grouping and Data Requirements

  • The column keyword defines the hierarchical level at which each random effect varies. This grouping structure is used during data-model construction, batching, and estimation.
  • When a model includes multiple grouping columns, any constant covariate used within a random-effect distribution must declare constant_on for the corresponding grouping column to ensure consistency.
  • Grouping columns must be present in the data and may not have unique values per observation (which would render the random effects unidentifiable).

Row-Varying Group Membership Within an Individual

NoLimits supports one grouping column per declared random effect. If you need an interaction grouping such as SITE:YEAR, create that composite column in the data and point column at it explicitly.

Whether a non-primary_id grouping column may change within an individual depends on the model class:

ContextMay a non-primary_id grouping column vary within an individual?Semantics
Non-ODE formulasYesThe random effect used for each observation row is selected from that row's grouping value.
Discrete-time HMM outcomesYesRow-wise selection is applied at each observation row.
ODE modelsNoRandom effects must remain uniquely defined at the individual level during ODE evaluation.
Continuous-time HMM outcomesYesRow-wise selection is applied at each observation row.

Two related constraints still apply:

  • Random effects referenced in @preDifferentialEquation must be grouped by primary_id.
  • Constant covariates keep their current validation rules: they must remain constant within primary_id and within every declared constant_on grouping.