Inter-Occasion Variability (IOV)
This example extends the one-compartment oral warfarin model to account for occasion-to-occasion variability in clearance. The complete model file is examples/warfarin_iov.ferx.
When to use IOV
Use IOV when:
- Subjects have data from multiple study periods (crossover designs, multiple visits)
- You expect within-subject PK variability across periods that is larger than pure residual error
- NONMEM models used
OMEGA BLOCK SAMEsyntax for one or more parameters
Dataset
The dataset must include an occasion-index column. Add an integer OCC column (or any name you choose — tell FeRx via iov_column):
ID,TIME,DV,EVID,AMT,CMT,MDV,OCC
1,0,.,1,100,1,1,1
1,1,9.49,0,.,.,0,1
1,2,14.42,0,.,.,0,1
1,3,17.56,0,.,.,0,1
1,24,.,1,100,1,1,2
1,25,10.1,0,.,.,0,2
1,26,8.2,0,.,.,0,2
Occasion 1 = first period, occasion 2 = second period. Dose records and observation records in the same period share the same OCC value.
Model File (Option A — diagonal IOV)
This is the contents of examples/warfarin_iov.ferx:
model warfarin_iov
[parameters]
theta TVCL(0.2, 0.001, 10.0)
theta TVV(10.0, 0.1, 500.0)
theta TVKA(1.5, 0.01, 50.0)
omega ETA_CL ~ 0.09 # between-subject variability in CL
omega ETA_V ~ 0.04
omega ETA_KA ~ 0.30
kappa KAPPA_CL ~ 0.01 # inter-occasion variability in CL
sigma PROP_ERR ~ 0.02
[individual_parameters]
CL = TVCL * exp(ETA_CL + KAPPA_CL)
V = TVV * exp(ETA_V)
KA = TVKA * exp(ETA_KA)
[structural_model]
pk one_cpt_oral(cl=CL, v=V, ka=KA)
[error_model]
DV ~ proportional(PROP_ERR)
[fit_options]
method = foce
iov_column = OCC
covariance = false
The key additions compared to a standard model:
kappa KAPPA_CL ~ 0.01— declares IOV on CL with starting variance 0.01+ KAPPA_CLin the CL expression — kappa enters just like a BSV etaiov_column = OCC— tells FeRx which dataset column carries occasion labels
Set covariance = true if you want standard errors on the IOV variance (the shipped example leaves it off to keep the demo fast).
Model File (Option B — correlated IOV)
If you have IOV on both CL and V and suspect they covary across occasions, use block_kappa:
[parameters]
...
omega ETA_CL ~ 0.09
omega ETA_V ~ 0.04
omega ETA_KA ~ 0.30
block_kappa (KAPPA_CL, KAPPA_V) = [0.05, 0.01, 0.03]
sigma PROP_ERR ~ 0.02
[individual_parameters]
CL = TVCL * exp(ETA_CL + KAPPA_CL)
V = TVV * exp(ETA_V + KAPPA_V)
KA = TVKA * exp(ETA_KA)
The three values in [0.05, 0.01, 0.03] are the lower triangle of Ω_IOV:
0.05= Var(KAPPA_CL)0.01= Cov(KAPPA_CL, KAPPA_V)0.03= Var(KAPPA_V)
Running the Fit
The repository does not ship a warfarin dataset with occasion labels — point --data at your own NONMEM-format CSV that includes an OCC column (see Dataset above):
ferx examples/warfarin_iov.ferx --data path/to/your_occ_data.csv
Or via the Rust API:
#![allow(unused)] fn main() { let result = fit_from_files("examples/warfarin_iov.ferx", "path/to/your_occ_data.csv")?; println!("Omega IOV: {:?}", result.omega_iov); }
Interpreting Output
The console summary gains a KAPPA (IOV) Estimates block, and the fit YAML gains an omega_iov: section.
Console output (Option A, single kappa on CL):
--- KAPPA (IOV) Estimates ---
KAPPA_CL = 0.041200 (CV% = 20.3) SE = 0.008000
For Option B with off-diagonal correlations a --- Correlations --- block follows the diagonal entries.
Per-subject, per-occasion kappa EBEs are returned on FitResult.ebe_kappas. They are not yet emitted as columns in the sdtab CSV — access them via the Rust API if you need per-occasion plots:
#![allow(unused)] fn main() { for (i, subject_kappas) in result.ebe_kappas.iter().enumerate() { for (k, kappa_vec) in subject_kappas.iter().enumerate() { println!("subject {} occasion {}: {:?}", i + 1, k + 1, kappa_vec); } } }
Note: per-kappa shrinkage is not yet computed (
shrinkage_kappais always returned empty). If you need a shrinkage diagnostic for IOV, derive it manually fromebe_kappasandomega_iov.
Tips
- Start with IOV on CL only — it is the most commonly occasion-sensitive parameter. Add IOV on other parameters only when the OFV improvement justifies it.
- Compare OFV between BSV-only and IOV models — the difference is a likelihood-ratio test for the added IOV variance(s). Note that for a variance parameter at the boundary (H₀: σ² = 0) the asymptotic null is a 50:50 mixture of a point mass at 0 and χ²₁, not a plain χ²₁.
- Use FOCEI for proportional errors — the interaction term matters more when individual predictions vary across occasions.
- SAEM is not supported with IOV — use
foceorfocei.