Format for associations is:
LHS ~ fun(COVARIATE, THETA..., ref = ..., ...)
LHS: Selector for a fixed-effect (theta) parameter, exactly as
in add_prm_association() (the{m}, {name} or {label}, unquoted).
Multiple parameters can share one association with + (eg, a
covariate that affects both CL and Q through the same theta).
RHS COVARIATE: The first (positional) argument. The bare,
unquoted name of a contcov/catcov column (see xpose::xp_var()) --
not a fixed-effect or omega selector.
RHS THETA...: One or more further positional arguments,
selecting the fixed-effect parameter(s) that carry the covariate
effect magnitude (same selector rules as LHS -- the{m}/{name}/
{label}, unquoted). How many are expected depends on fun; see the
built-in list below.
RHS ref: Required, named, no default, for every
association regardless of fun. This is the covariate value (for
continuous covariates) or raw level (for categorical covariates)
that the effect is normalized against -- the point at which the
reported effect ratio is exactly 1. There is no way to safely infer
this from the xpdb alone (NONMEM control streams commonly
normalize a covariate against a hardcoded constant baked into the
code, which is invisible to xpose), so it must always be stated
explicitly, the same way add_prm_association()'s nmboxcox
requires an explicit lambda.
All built-ins express the covariate's effect as a multiplicative
effect_ratio on the parameter's typical value, and are constructed so
that effect_ratio == 1 whenever the covariate equals ref,
regardless of the theta value. Available built-ins:
linear(COV, THETA, ref=): \(1 + \theta (COV - ref)\)
power(COV, THETA, ref=): \((COV / ref)^\theta\) (allometric)
exponential(COV, THETA, ref=): \(e^{\theta (COV - ref)}\)
additive(COV, THETA, ref=): \((\theta + COV) / (\theta + ref)\).
An uncommon but simple form where the covariate is added directly (with
an implicit coefficient of 1, unlike linear's explicit slope) to an
intercept-like THETA, eg CL = THETA(n) + WT in the underlying model.
hockey(COV, THETA_LO, THETA_HI, ref=, brk=ref): PsN's
"hockey-stick" two-slope piecewise-linear model --
\(1 + \theta_{lo} (COV - ref)\) when COV <= brk,
\(1 + \theta_{hi} (COV - ref)\) when COV > brk. brk (the
breakpoint) defaults to ref (PsN's usual default: breakpoint =
normalization reference), but can be given separately, eg for a
covariate normalized to its observed median while the clinically
meaningful cutpoint is a round number (ref = 90, brk = 60 for an
eGFR-like covariate).
catshift(COV, THETA..., ref=): One theta per non-reference
raw level of a categorical covariate, effect_ratio = 1 + THETA_i
for level i (1 at ref). Assumes the typical NONMEM pattern of
one theta per non-reference category (eg
IF (RACE.EQ.2) CLCOV = THETA(9)). Thetas are matched to
non-reference levels in ascending raw-value order -- if that order
is ambiguous or wrong for a given model, use custom() instead.
For anything else, custom(COV, THETA..., ref=, fun=) is the escape
hatch: fun is a function of (cov, ref, theta) (theta is always a
numeric vector, even when only one THETA selector is given) returning
the effect ratio. Because custom() can't be verified by
construction the way the built-ins can, add_cov_association()
validates it at declaration time by evaluating fun(ref, ref, theta)
for a few probe values of theta (not the currently-fitted value,
which could coincidentally pass while fun is still wrong for other
theta values) and requires each to equal 1; if it doesn't, the error
states the required invariant and shows what fun actually returned,
rather than failing silently or only much later during plotting.
A note on parameter/theta scale
The computed effect ratio never touches the LHS parameter's own fitted
value -- only the covariate-effect theta(s), the covariate value, and
ref -- so it does not matter whether the LHS was itself parameterized
on a log, logit, or identity scale in the control stream (get_prm()'s
transform argument only affects diagonal OMEGA/SIGMA reporting
(variance -> SD, covariance -> correlation); THETA values are always
the raw fitted estimate regardless of transform, so there is nothing
to reconcile there either).
What is assumed is that the chosen association -- a builtin or
custom() -- is an exact match for the functional form actually used
in the model code, including any scale factor baked into that code
(eg a covariate effect entered as THETA(n)*(COV-ref)/100). There is
no way to verify this from the xpdb alone; if a builtin's literal
formula (see above) doesn't match the real model, the result will be
a numerically valid but silently wrong effect ratio, not an error --
the same caveat add_prm_association() already carries for CV%
calculation.
If the covariate-effect theta itself is not already on the scale a
builtin expects (eg it was fitted on a logit or other transformed
scale, or needs some other rescaling to match one of the literal
formulas above), transform it back with mutate_prm() before
declaring the association -- the same recommended workflow as
add_prm_association()'s own untransformed-theta requirement.