Data Format
ferx-core reads data in NONMEM-compatible CSV format. This is the standard format used across population PK tools.
Required Columns
| Column | Type | Description |
|---|---|---|
ID |
string/numeric | Subject identifier |
TIME |
numeric | Observation/event time on the data clock |
DV |
numeric | Dependent variable (observed concentration) |
TIMEneed not start at zero. A subject’sTIMEcolumn may begin at any value (e.g. a calendar/clock time). Integration starts at each subject’s first record — matching NONMEM — so an off-zero start is not integrated over a phantom[0, first record]window.TIMEis used exactly as written everywhere it is visible: the modelTIME/Tbuiltin,[derived]columns, the survival left-truncationTENTRY, and the sdtab /predict()/simulate()output. ferx does not re-baseTIMEonto a per-subject elapsed clock (the one exception is stacked reset occasions — see System Resets).
Optional Standard Columns
| Column | Type | Default | Description |
|---|---|---|---|
EVID |
integer | 0 | Event ID: 0 = observation, 1 = dose, 2 = other event, 3 = system reset, 4 = reset + dose. If the column is omitted, the record type is inferred from AMT — see Inferring doses without an EVID column. |
AMT |
numeric | 0 | Dose amount (for EVID=1/EVID=4; also the dose-inference signal when EVID is absent) |
CMT |
integer | 1 | Compartment number (1-indexed) |
RATE |
numeric | 0 | Infusion rate. 0 = bolus, >0 = constant-rate infusion (duration = AMT/RATE). NONMEM’s coded values -1 (modeled rate via an R{cmt} $PK parameter) and -2 (modeled duration via a D{cmt} $PK parameter) are supported on both analytical and ODE models — see Infusion Doses. |
MDV |
integer | 0 | Missing DV flag. 1 = DV should be ignored (row excluded from the likelihood) |
II |
numeric | 0 | Interdose interval for repeated dosing |
SS |
integer | 0 | Steady-state flag. 1 = assume steady state |
CENS |
integer | 0 | Censoring flag. 1 = below LLOQ and DV carries the LLOQ; -1 = above ULOQ and DV carries the ULOQ; 0 = quantified. Paired with bloq_method = m3 in [fit_options] to enable likelihood-based handling — see BLOQ example. |
Missing DV on observation rows. An
EVID=0row contributes to the likelihood only when itsDVis present. If theDVis missing (.,NA, or blank), mark the rowMDV=1. As a safety net, ferx also treats anEVID=0row with a missingDVasMDV=1even when the flag is absent — the row is skipped rather than scored asDV=0— and emits a singleW_MISSING_DVwarning reporting how many rows were skipped. SetMDV=1explicitly to silence it, or fix the data if the missing values are an error.
Inferring doses without an EVID column
EVID is optional. When the column is absent, ferx infers the record type from AMT, exactly as NONMEM does: a row with a nonzero AMT is treated as a dose (EVID=1), and every other row as an observation (EVID=0). This lets legacy NONMEM datasets that omit EVID — marking dose rows only by a nonzero AMT (often with MDV=1) — administer their doses instead of silently dropping every AMT row and fitting a degenerate, dose-free model.
ID,TIME,DV,MDV,AMT
1,0,.,1,100
1,1,9.5,0,.
1,2,7.3,0,.
Here the first row (AMT=100) is inferred as a dose; the others are observations. Inference keys on AMT only — a dose always carries a nonzero amount (for infusions, AMT is the amount and RATE the rate). When an EVID column is present, its values govern and nothing is inferred.
Two non-fatal warnings (collected into the fit’s warnings) guard against a silently dose-free fit:
W_AMT_NOT_DOSED— one or more non-observation rows carryAMT != 0but were not treated as doses: anEVIDcolumn is present and codes a dose row as something other than1/4(e.g. a dose row mistypedEVID=0withMDV=1). TheirAMTwas ignored. A scored observation (MDV=0) that merely carries a redundant or forward-filledAMTdoes not trigger this.W_NO_DOSES— the dataset parsed zero dose events even though scored observations are present (usually a missingAMT/EVID). Not emitted for time-to-event/survival datasets, which legitimately have no PK doses.
Occasion Column (IOV)
When using Inter-Occasion Variability (IOV), add an occasion-index column to the dataset and specify its name with iov_column in [fit_options]. The column:
- Contains integer occasion indices (e.g. 1, 2, 3…) — one per row
- Applies to both dose rows and observation rows
- Is excluded from covariate auto-detection
Example dataset with OCC column:
ID,TIME,DV,EVID,AMT,CMT,MDV,OCC
1,0,.,1,100,1,1,1
1,1,9.5,0,.,.,0,1
1,2,7.3,0,.,.,0,1
1,24,.,1,100,1,1,2
1,25,10.1,0,.,.,0,2
1,26,8.2,0,.,.,0,2
The occasion index can be any positive integer; they do not need to start at 1 or be consecutive, but a different number means a different occasion with its own kappa EBE.
See IOV documentation for full details.
Covariate Columns
By default, any column not in the standard set above is automatically treated as a covariate. Alternatively, declare covariates explicitly with a [covariates] block in the model file; when present it is authoritative (only listed columns are covariates) and enables type tagging, validation, and the covariate output table. Covariate values are:
- Time-constant: The first non-missing value for each subject is used
- Time-varying: If values change over time for a subject, Last Observation Carried Forward (LOCF) is applied per event (NONMEM-equivalent:
[individual_parameters]is re-evaluated at each dose and observation row using that row’s covariate values)
Covariate names are case-sensitive: a column name in the data file must match the name used in [individual_parameters] expressions (and in a [covariates] block) exactly. (Standard NONMEM columns like ID/TIME are matched case-insensitively; covariate columns are not.)
Time-varying covariate scope
Time-varying covariates are supported for all analytical structural models and ODE-defined models:
- 1-compartment IV (
one_cpt_iv) — bolus and/or infusion per dose - 1-compartment oral (
one_cpt_oral) - 2-compartment IV (
two_cpt_iv) — bolus and/or infusion per dose - 2-compartment oral (
two_cpt_oral) - 3-compartment IV (
three_cpt_iv) — bolus and/or infusion per dose - 3-compartment oral (
three_cpt_oral) - All ODE-defined models (via
[odes])
For oral models, the bolus dose into compartment 1 is interpreted as the depot (NONMEM ADVAN2/ADVAN4/ADVAN12 convention) and observation read-out reads the central compartment.
The analytic Dual2 gradient path is event-driven for all analytical models; time-varying-covariate subjects fall back to finite differences for both the inner-loop gradient and the H-matrix Jacobian (a planned analytic-sensitivity extension).
Infusion routing on the event-driven path:
- IV models: central infusion (cmt=1) for all 1/2/3-cpt; peripheral infusion for 2-cpt (cmt=2) and 3-cpt (cmt=2 → periph1, cmt=3 → periph2). Steady-state amounts per channel are computed by linear superposition over the channels.
- Oral models: central infusion (cmt=2) is supported; peripheral infusion is rare clinically and still panics with a clear message (tracked as a follow-up).
Event Types (EVID)
| EVID | Meaning |
|---|---|
| 0 | Observation record. DV is used for estimation. |
| 1 | Dosing record. AMT is administered to compartment CMT. |
| 2 | Other event (typically a covariate-change marker). The compartment state is unchanged but the rate matrix is refreshed from this row’s covariate values — matching NONMEM’s $PK runs at every record semantic. Only meaningful when at least one covariate is time-varying; for time-constant data EVID=2 rows are skipped (would be no-ops). |
| 3 | System reset. All compartment amounts are set to zero at this time, and any ongoing infusion is turned off. No dose is given and DV is ignored. |
| 4 | Reset and dose. Like EVID=3 (zero every compartment, stop ongoing infusions) followed immediately by a dose of AMT into compartment CMT. |
System Resets (EVID=3 / EVID=4)
A reset record empties every compartment at its TIME, as if the subject’s drug history started over from that point. EVID=3 is a pure reset; EVID=4 resets and then administers the row’s dose into the freshly emptied system. This matches NONMEM’s reset-event semantics and is useful for, e.g., modelling washout between treatment cycles or re-using one subject record for independent dosing episodes.
ID,TIME,DV,EVID,AMT,CMT,MDV
1,0,.,1,100,1,1
1,1,9.5,0,.,.,0
1,4,6.1,0,.,.,0
1,24,.,3,.,.,1
1,24,.,1,100,1,1
1,25,9.4,0,.,.,0
Stacked occasions with a restarting clock
A common NONMEM idiom is to stack several independent dosing episodes under one subject ID, each opened by an EVID=4 record whose TIME restarts at 0 (so the records are not globally time-ordered):
ID,TIME,DV,EVID,AMT,RATE,MDV
1,0,.,4,100,100,1
1,1,9.4,0,.,.,0
1,8,2.1,0,.,.,0
1,0,.,4,100,100,1
1,1,9.6,0,.,.,0
1,8,2.0,0,.,.,0
NONMEM processes records sequentially, so the second EVID=4 begins a fresh occasion that re-uses the first occasion’s wall-clock. ferx-core reproduces this: each restarting occasion is shifted onto a single monotonic internal timeline (the reset zeros every compartment at the boundary, so no drug carries across), and the subject keeps one shared set of random effects across the occasions — exactly matching NONMEM’s EVID=4 semantics. The two occasions are not merged or double-dosed.
Diagnostics are reported on the raw data clock: the TIME column of {model}-sdtab.csv (and {model}-covtab.csv) echoes the value you wrote, and TAFD / TAD reset per occasion (time after that occasion’s first / most recent dose). The monotonic shift is purely internal to the prediction engine.
[derived]integrals and stacked resets. Integral columns with an absolute window ([from, to]or a periodicanchor) are evaluated per occasion in rawTIMEcoordinates. Each occasion is integrated independently, so crossover designs produce correct per-occasion AUC values. Occasions with no observations inside the window returnNaNfor that row.Grid-based integrals (
step = <dt>orstep = auto) evaluate on the internal shifted clock rather than rawTIME, so expressions referencingTIMEinside a grid integral will see the shifted value. Use observation-based integrals (step = obsordata_based = true) when rawTIMEmust appear inside the integrand expression.
Notes:
- Resets force the event-driven analytical / ODE prediction path — dose superposition cannot express a mid-record reset — so any analytical or ODE model supports them with no configuration.
- Reset-bearing subjects use the analytic gradient where in scope and otherwise fall back to finite-difference gradients; results are unaffected, only the gradient method.
- Resets are not supported on the EKF/SDE path (
[diffusion]models). A reset row on an SDE model emits a warning and is ignored.
Example Dataset
ID,TIME,DV,EVID,AMT,CMT,RATE,MDV,WT,CRCL
1,0,.,1,100,1,0,1,70,95
1,0.5,9.49,0,.,.,.,0,70,95
1,1,14.42,0,.,.,.,0,70,95
1,2,17.56,0,.,.,.,0,70,95
1,4,15.23,0,.,.,.,0,70,95
1,8,10.15,0,.,.,.,0,70,95
2,0,.,1,150,1,0,1,85,110
2,0.5,14.2,0,.,.,.,0,85,110
2,1,21.3,0,.,.,.,0,85,110
Key points: - Dose records (EVID=1) have MDV=1 and DV=. (missing) - Observation records (EVID=0) have MDV=0 and a valid DV - Covariates (WT, CRCL) are included as extra columns - Missing values can be represented as . or left empty
Infusion Doses
For IV infusions, set RATE to the infusion rate (amount per time unit):
ID,TIME,DV,EVID,AMT,CMT,RATE,MDV
1,0,.,1,500,1,50,1
This administers 500 units at a rate of 50 units/hour (duration = 10 hours).
NONMEM coded RATE values
NONMEM overloads the RATE column with negative codes that change its meaning:
RATE |
NONMEM meaning | ferx-core |
|---|---|---|
0 |
Bolus — route set by the dose compartment | ✅ supported |
> 0 |
Constant-rate infusion (duration = AMT/RATE) |
✅ supported |
-1 |
Infusion rate is modeled — defined by R{cmt} in $PK |
✅ supported on ODE and analytical models (#324) |
-2 |
Infusion duration is modeled — defined by D{cmt} in $PK |
✅ supported on ODE and analytical models (#324, #394) |
RATE = -2 makes the infusion duration a model parameter: declare an individual parameter D{cmt} (D1 for a dose into compartment 1, etc.) and ferx infuses AMT over that duration — rate AMT / D{cmt}, evaluated per iteration and occasion. Supported on both the analytical pk(...) engine and ode(...) models; see Modeled infusion duration for the DSL and semantics. A RATE=-2 dose with no matching D{cmt} parameter (on either engine) is a loud error — never a silent bolus.
On an analytical oral model (pk one_cpt_oral / two_cpt_oral / three_cpt_oral), a D1 into the depot (compartment 1) gives a zero-order absorption model: drug is released into the depot at a constant rate over D1, then absorbed first-order into central via KA (#400). D2 into the same model is a depot-bypassing infusion straight into central. Both stay on the closed-form analytical engine — no ode(...) block needed. (Compartment amounts in sdtab/[derived] are not available for analytical depot-zero-order subjects; the predictions themselves are exact — use an ode(...) model if you need the per-compartment amounts.) Infusion into an oral peripheral compartment is still unsupported and needs an ode(...) model.
RATE = -1 makes the infusion rate a model parameter: declare an individual parameter R{cmt} (R1 for a dose into compartment 1, etc.) and ferx infuses AMT at that rate — duration AMT / R{cmt}, evaluated per iteration and occasion (the mirror of -2). Supported on both engines; a RATE=-1 dose with no matching R{cmt} is the same loud E_MODELED_RATE_NO_PARAM error as the duration case. R{cmt}/D{cmt} are recognised compartment-indexed parameter names (like NONMEM’s reserved $PK names); recognising one only reserves it when a coded RATE actually targets that compartment.
Bioavailability and infusion shape (
F ≠ 1). ferx appliesFto an infusion the NONMEM way (#419), holding whichever quantity you specified and scaling the other so total exposure isF·AMTeither way:
- rate-defined (
RATE>0data andRATE=-1→R{cmt}): the rate is held and the duration is scaled toF·AMT/RATE. So aRATE=-1dose behaves exactly like its explicitRATE = R{cmt}twin.- duration-defined (
RATE=-2→D{cmt}): the duration is held atD{cmt}and the rate is scaled toF·AMT/D{cmt}.The two modes therefore produce a different infusion shape under
F ≠ 1(same total exposure). AtF = 1they coincide. (Earlier versions scaled the rate for every infusion, which diverged from NONMEM for rate-defined infusions; #419.)
Any other negative or non-finite RATE on a dose row is rejected. Earlier versions silently treated all coded forms as a bolus, producing wrong predictions with no warning (#324). Note -1/-2 are driven by $PK parameters — not a separate DURATION data column.
A runnable demo of the supported forms — a bolus (RATE=0) and a constant-rate infusion (RATE>0) mixed in one dataset — is in examples/dose_rate.ferx (data: data/dose_rate.csv).
Steady-State Dosing
For steady-state simulations, set SS=1 and II to the dosing interval:
ID,TIME,DV,EVID,AMT,CMT,SS,II,MDV
1,0,.,1,100,1,1,12,1
1,0.5,25.3,0,.,.,.,.,0
This assumes the subject has reached steady state with 100 units every 12 hours before the observation at TIME=0.5.
SS=1 is supported on every prediction path: analytical (1-/2-/3-cpt with or without time-varying covariates) and ODE. SS=1 also composes with LAGTIME — the lagged SS curve at time t equals the un-lagged curve at t - lagtime. See Steady-State Doses for the full reference, including the data-validation warnings emitted for malformed rows (missing II, overlapping infusions).
Multiple Doses
Multiple doses are supported as separate rows:
ID,TIME,DV,EVID,AMT,CMT,MDV
1,0,.,1,100,1,1
1,0.5,9.49,0,.,.,0
1,12,.,1,100,1,1
1,12.5,15.2,0,.,.,0
1,24,.,1,100,1,1
1,24.5,18.1,0,.,.,0
Column Name Case
Column names are case-insensitive. ID, Id, and id are all recognized. Covariate columns preserve their case as declared in the CSV header.