Derived Columns ([derived])
Maturity: beta — see Feature Maturity for what this means.
The optional [derived] block defines extra columns that are computed after the fit and appended to the sdtab output. Each line has the form:
name = expression
where name becomes a new sdtab column and expression is evaluated for every observation row.
Quick example
[derived]
CLi = CL # individual CL echoed directly
AUC = integral(IPRED, from=0, to=24) # AUC 0-24 from predicted curve
Cmax = max(IPRED) # subject-level maximum prediction
Tmax = tmax(IPRED) # time of maximum
Available variables
| Name | Meaning |
|---|---|
Individual-parameter names (CL, V, …) |
EBE-derived value for the subject — every name in [individual_parameters], including an intermediate the [structural_model] line does not bind (TVCL = THCL * 3) and a modeled-dose D{n} / R{n}. Each is re-evaluated per row with that row’s η, covariates and TIME |
Theta names (TVCL, TVV, …) |
Final population estimate |
Eta names (ETA_CL, …) |
Subject EBE |
Covariate names (WT, AGE, …) |
Subject covariate |
IPRED |
Individual prediction at the row’s time point |
PRED |
Population prediction (eta = 0) at the row’s time point |
DV |
Observed value |
TAFD |
Time after first dose |
TAD |
Time after most recent dose (SS-aware) |
TIME |
Nominal time |
compartments[i] |
Amount/concentration in ODE compartment i (0-based) or equivalent analytical state |
| state name (ODE) | Named ODE state variable directly, e.g. Ce, depot, A_central |
Operators and functions
Standard arithmetic (+, -, *, /), comparison (<, >, <=, >=, ==, !=), and logical (&&, ||, !) operators are supported. Use the mod keyword for modulo (a mod b). As well as:
| Function | Description |
|---|---|
exp(x) |
Exponential |
log(x) |
Natural logarithm |
sqrt(x) |
Square root |
abs(x) |
Absolute value |
floor(x) / ceil(x) / round(x) |
Rounding |
x ^ y |
Power (use ^, not a function call) |
max(expr) |
Subject-level maximum of expr over all observations |
min(expr) |
Subject-level minimum |
tmax(expr) |
Time at which expr is maximised |
integral(expr, from=t0, to=t1) |
AUC from t0 to t1; obs times for DV, fine grid (500 pts) for IPRED |
integral(expr, from=t0, to=t1, step=dt) |
As above with grid step dt hours (IPRED only; step= is ignored for DV) |
integral(expr, cond, from=t0, to=t1) |
As above, only time points where cond is true contribute |
integral(expr, window=P) |
Periodic AUC: one value per dosing window of length P hours |
integral(expr, window=P, anchor=A) |
Periodic AUC with windows starting at A (default 0) |
integral(expr, cond, window=P, step=dt) |
Periodic AUC, filtered by cond, with grid step dt |
The constant MACHEPS (machine epsilon, ≈ 2.22 × 10⁻¹⁶) is also available.
Computation kinds
Per-row (PerRow)
The default: the expression is evaluated independently at each observation time point and written to the corresponding sdtab row.
CLi = CL # same value every row for a subject; still PerRow
Aggregate (Aggregate)
Using max(expr), min(expr), or tmax(expr) produces a single value per subject that is broadcast to all of that subject’s rows.
Cmax = max(IPRED)
Tmax = tmax(IPRED)
A filter can be applied as the second argument inside the parentheses:
CmaxAfter12 = max(IPRED, TIME > 12)
Ctrough = min(IPRED, TAD < 1e-10)
CmaxD14 = max(IPRED, TAFD >= 312 && TAFD < 336)
The filter uses the same expression language. If no observation passes the filter the result is NaN.
Integral (Integral)
integral(expr, from=t0, to=t1) computes the trapezoidal area under expr over the half-open interval [t0, t1].
- When
exprreferencesDV, the observed values at the matching observation times are used. - When
exprreferencesIPRED, values are evaluated at the observation times within the window. Astep=dtkeyword argument causes the integrand to be evaluated on a uniform grid of spacingdtusing nearest-neighbour IPRED approximation; thestep=argument is silently ignored for DV-based integrals (warningW_DERIVED_STEP_IGNORED).
AUC24 = integral(IPRED, from=0, to=24)
AUC24_obs = integral(DV, from=0, to=24)
AUC24_grid = integral(IPRED, from=0, to=24, step=0.5)
Limitation — obs-based time-above-threshold: When using
integral(1.0, IPRED > threshold, ...)without astep=argument, only observation time points are evaluated. If the design has sparse sampling, the trapezoidal rule can miss brief periods above threshold between observations. Usestep=(e.g.step=0.1) to force grid evaluation at the cost of approximating IPRED via nearest-neighbour interpolation:TAM_TAU = integral(1.0, IPRED > MEC, window=24, anchor=0, step=0.1)
Naming rules
A [derived] name must not clash with:
- Built-in sdtab columns:
ID,TIME,DV,PRED,IPRED,CWRES,IWRES,EBE_OFV,N_OBS,TAFD,TAD,CENS,OCC,CMT - Any theta, eta, or individual-parameter name in the model
Shadowing a covariate name is permitted (the covariate column is replaced) but will emit a W_DERIVED_COVARIATE_SHADOW warning.
Referencing earlier derived columns
A derived expression may reference the name of any preceding derived column defined in the same [derived] block (sequential scoping). Forward references are not allowed.
[derived]
CLi = CL
VDSS = CLi / 0.693 # ok — CLi defined on the line above
Compartment states (ODE and analytical models)
After the fit, the state of every model compartment is available in [derived] expressions. There are two equivalent syntaxes:
- Subscript:
compartments[i]— 0-based index into the state vector. - Named (ODE models only): the ODE state variable name directly, e.g.
Ce,depot,A_central.
ODE example — effect compartment
[odes]
dA_central/dt = -CL/V * A_central
dCe/dt = Ke0 * (A_central/V - Ce)
[derived]
Ce_idx = compartments[1] # effect compartment by index
Ce_named = Ce # same value, by ODE state name
AUC_Ce = integral(Ce, from=0, to=24)
Ce_idx and Ce_named will be identical. Both refer to the raw ODE state u[1] — whatever units the ODE produces.
Analytical model compartment layout
Analytical PK models expose a fixed compartment layout:
| Model | compartments[0] |
compartments[1] |
compartments[2] |
compartments[3] |
|---|---|---|---|---|
one_cpt_iv |
central (conc) |
— | — | — |
one_cpt_oral |
depot (amount) |
central (conc) |
— | — |
two_cpt_iv |
central (conc) |
peripheral (conc) |
— | — |
two_cpt_oral |
depot (amount) |
central (conc) |
peripheral (conc) |
— |
three_cpt_iv |
central (conc) |
peripheral1 (conc) |
peripheral2 (conc) |
— |
three_cpt_oral |
depot (amount) |
central (conc) |
peripheral1 (conc) |
peripheral2 (conc) |
Named access works for analytical models too:
[structural_model]
pk two_cpt_iv(cl=CL, v1=V1, q=Q, v2=V2)
[derived]
C_periph = peripheral # named access
C_periph_idx = compartments[1] # same value, by index
Validation. The analytical peripheral/depot states are cross-checked against NONMEM 7.5.1. For 2-cpt IV/oral and 3-cpt IV/oral models, evaluating at fixed θ with η = 0 reproduces NONMEM’s tabled compartment amounts
A(i)(rescaled by the matching volume, since ferx reports central/peripheral compartments as concentrations) to within1e-4relative. Seetests/compartment_states_nonmem.rsand the control files intests/nonmem/(iv2,iv3,oral2,oral3).
Scaling
compartments[i] is never scaled. It is the raw solver state or analytical formula output:
- For analytical models: depot compartments are in amounts (dose units); central and peripheral compartments are in concentrations matching
IPRED. - For ODE models: the value is whatever the ODE produces. If your ODE tracks amounts (
dA/dt = ...) thencompartments[i]is an amount; if it tracks concentrations (dC/dt = ...) it is a concentration.
If a [scaling] block is active, IPRED is the scaled output but compartments[i] for the observed compartment remains unscaled. Compute the scaled value explicitly if needed:
[derived]
C_periph_scaled = compartments[1] / V2
integral over compartment states
When an integral(...) integrand references compartments[i] or a named ODE state variable, ferx re-runs the ODE solver (or analytical formula) at the grid points rather than interpolating from stored per-observation states. This gives an exact result at the cost of a second solver pass per subject — typically negligible for post-fit diagnostics.
[derived]
AUC_Ce = integral(compartments[1], from=0, to=24, step=0.5)
Named state variables in
integral(ODE models): ferx detects whether the integrand references a compartment by name at parse time. For this detection to work, the model must have an[odes]block (named access is not available for integrals in analytical models — use thecompartments[i]subscript form instead).
Name priority
When a named ODE state variable shares a name with a covariate, individual parameter, or other derived column:
- Built-in columns (
IPRED,TIME,DV,TAFD,TAD, etc.) — highest priority - Individual parameter names (
CL,V,KA, …) - Theta / eta names
- Covariate names
- ODE state variable names — lowest priority
If a state name shadows a covariate you will receive W_DERIVED_COVARIATE_SHADOW. If a state name coincides with an individual parameter, the parameter value wins and the compartment state is inaccessible by name — use compartments[i] instead.
Limitations (Phase 1)
compartments[i]requires a literal integer index. Dynamic indexing (compartments[N]whereNis a parameter) is not supported and will produce a parse error.- Named access is not available for integrals in analytical models. Use
compartments[i]inintegral(...)for analytical models. - SDE models (
[diffusion]block):compartments[i]returns the deterministic ODE state at each observation time. Today that is the only state there is — the EKF propagates the state covariance and never corrects the mean with the observed data, so there is no filtered trajectory for it to differ from (see What the filter does not do). The distinction becomes real when the mean update lands. - IOV subjects:
compartments[i]evaluates toNaN— compartment states are not yet available on the IOV prediction path (W_DERIVED_CMT_IOV_UNSUPPORTEDwarning is emitted). Scalar references — individual parameters (CL,V, …),ETA/KAPPA,IPRED,TAD, etc. — do honour each observation’s occasion kappa, so[derived]expressions and[output]columns built from them are occasion-correct; only compartment-state access is unavailable. - Analytical models with time-varying covariates:
compartments[i]evaluates toNaNfor affected subjects — states would require time-varying PK params that the superposition path does not support (W_DERIVED_CMT_TV_ANALYTICALwarning is emitted). - ODE models with time-varying covariates:
compartments[i]is approximate — states are computed with first-observation PK parameters whileIPREDuses the correct per-event PK (W_DERIVED_CMT_TV_ODEwarning is emitted).