Split a NONMEM-shaped dosing table into observations and events
Source:R/records.R
frm_ode_records.RdA population pharmacokinetic dataset usually comes as one table in the
layout NONMEM defined and rxode2 and nlmixr2 read: one row per record,
an evid column that says whether the row is an observation or a
dose, and amt, cmt, rate, ii, addl and ss on the dose
rows. frm_ode() and frm_lincmt() take the observations and the
doses as two tables. frm_ode_records() does that split and nothing
else: it does no arithmetic on a dose, so a dose means in the result
exactly what it meant in the record.
Usage
frm_ode_records(
d,
id = "id",
time = "time",
evid = "evid",
amt = "amt",
cmt = "cmt",
rate = "rate",
dur = "dur",
ii = "ii",
addl = "addl",
ss = "ss"
)Arguments
- d
A data.frame of NONMEM-shaped records.
- id, time, evid, amt, cmt, rate, dur, ii, addl, ss
The name of the column of
dthat holds each record item. Names matchd's column names without regard to case, so the default"amt"finds a columnAMT.NULLsaysdhas no such column, and is refused whendhas one under the default name.timeandevidmust exist;amtmust exist when there is a dose. Withoutidthe whole table is one subject. Withoutcmtthe events carry nostatecolumn, whichfrm_ode()accepts only for a system of one state.
Value
A list with data, the observation rows, and events, the
dosing table for frm_ode() or frm_lincmt()'s events argument,
or NULL when there is no dose. Pass group = <id column> to the
solver, and states when cmt holds names.
What each record becomes
evid | result |
| 0 | a row of data |
| 1 | an "add" event: value = amt into state = cmt |
| 3 | a "reset" event: every compartment set to zero |
| 4 | a "reset" event and then an "add" event at the same time |
| 5 | a "replace" event, rxode2's code: the compartment set to amt |
| 6 | a "multiply" event, rxode2's code: the compartment scaled by amt |
A dose with a positive rate is an infusion of duration
amt / rate, and a positive dur, which is how rxode2 writes one,
is that duration directly. ii, addl and ss = 1 are copied to
the event as they are; frm_ode() reads them the way NONMEM does.
data keeps every column of d except the dosing items (evid,
amt, rate, dur, ii, addl, ss and mdv), so id, time,
cmt, the response and every covariate come through under their own
names, in their original row order.
A dose item is read ONLY through its argument. Every other column is
data: a rate held in a column called RATE_MG_H and not named with
rate = "RATE_MG_H" is a covariate here, and the doses are read
without it. This function cannot recognize every name a dataset
might give an item, so name each one that is not under its default
name.
What is refused, and why
Every refusal names the column or the record, because each one is a place where a record would otherwise be read with a meaning different from its own.
Record types and items:
evid = 2, NONMEM's "other event". It carries no dose and no observation, and is used to change a time-varying covariate. Dropping it would lose that change, and keeping it would make it an observation. Put the covariate on the observation rows, or intofrm_ode()'stv.Any other
evidvalue, including rxode2's codes above 6 and its classic codes of 100 and more, such as the 101 innlmixr2data::theo_sd, which fold the compartment into the number.ss = 2, NONMEM's steady state added to the current state rather than replacing it.frm_ode()'s steady state starts from an empty system, which isss = 1.A steady-state constant infusion (
ss = 1,amt = 0,rate > 0).A negative
rateordur: NONMEM's-1and-2say the rate or the duration is a model parameter, and an estimated duration moves where the solve is split, whichfrm_ode()cannot do.A dose with both a positive
rateand a positivedur.A
rate,dur,ii,addlorssthat is not finite on an event, or that is nonzero on an observation; anamt,ii,addlorssthat is nonzero on a reset (evid = 3). The split would drop each of them without a word.A
cmton a dose that is missing, zero or below, or not a whole number.An
mdvcolumn that disagrees withevid.mdv = 1on anevid = 0row is a missing observation, which would otherwise become a fitted one.
Columns:
The NONMEM items that change how a record is read and that this function does not interpret:
date,dat1,dat2,dat3,cont,call,pcmt,l1andl2.The names NONMEM's PREDPP or rxode2 read as a dose modifier, which
frm_ode()does not apply, so as data they would change nothing:ALAGn,Rn,DnandMTIMEn(any numbern),Fnfor a compartmentnof 1 or more,XSCALE,TSCALE, andlag,alag,tlagandtinfwith or without a number. A covariate with one of these names has to be renamed.Sn,FOandF0are not refused:F0andFOare two spellings of PREDPP's output fraction, and like the scaleSnit acts on an observation, not a dose.CENS,LIMIT,BLQandLLOQ, which rxode2 and nlmixr2 read as censoring of an observation. This function does not read censoring, so a censored value would be fitted as an observed one.A column argument that names no column of
d, or two columns ofdthat differ only in case, or an argument set toNULLwhiledhas a column under that item's default name.
Order:
An
idwhose records are not one contiguous block, and atimethat goes backwards within anid. NM-TRAN reads one individual as one block in time order; here the two blocks would be merged and dosed twice.A reset (
evid3 or 4), a steady-state dose (ss = 1), or a replace or multiply (evid5 or 6) at the same time as another event of the sameid,addlrepeats included. Two times are one instant when they differ by at most64 * .Machine$double.epstimes the larger of|t0|andk * iifor a repeatt0 + k * ii(|t|for a written time), which covers the rounding of that sum with a 32-fold margin. So a repeat at0 + 3 * 0.1meets a record at0.3, while records 1 ms apart att = 1e7or 1 s apart at an epoch time near1.7e9stay distinct. A replace or multiply on a DIFFERENT compartment from the other event is refused too, although the two commute: the rule does not look at compartments. NONMEM reads records at one time in the order they are listed;frm_ode()applies a reset or a steady state first whatever the listing; and rxode2 5.1.7 follows neither. Measured on a bolus of 100 into the central compartment att = 10and a steady-state dose into the depot at the same time, the central amount att = 11is 112.03 infrm_ode()and 32.58 in rxode2, in either listed order. Two plain doses at one time add, and pass.A reset while an infusion of the same
idis running, including an infusion that starts at the reset's own time andaddlrepeats.frm_ode()lets the infusion run on after the reset,frm_lincmt()refuses it, and rxode2 5.1.7 returns negative amounts after it.An observation listed after an event at the same time for the same
id. NONMEM reads such an observation after the event;frm_ode()reads every observation at an event time before the event. An observation listed before a dose is the pre-dose sample in both and passes.An observation at the time of a steady-state record, in either order.
frm_ode()reads the steady-state trough there and rxode2 the state after the dose. What NONMEM reads there was not checked, because no NONMEM was available, so no reading is safe.
rxode2 does not follow the listed order at a dose either: measured with rxode2 5.1.7, an observation at a dose time reads the state AFTER the dose whether it is listed before or after it. So a table written for rxode2 with an observation at the exact time of a dose into the observed compartment gives a different value here, where it is the trough. Nothing in the records can tell the two readings apart.
Doses of an id that has no observation are dropped, with a warning,
because frm_ode() refuses a dose for a group it does not see. Their
ids are the "dropped_ids" attribute of the result.
See also
frm_ode() for the events grammar these records become.
Examples
# Two subjects, 100 into the depot every 12 hours for four doses,
# sampled at 2, 6 and 42 hours; the second subject's first sample is
# missing.
rec <- data.frame(
ID = rep(1:2, each = 4),
TIME = rep(c(0, 2, 6, 42), 2),
EVID = rep(c(1, 0, 0, 0), 2),
MDV = c(1, 0, 0, 0, 1, 1, 0, 0),
AMT = rep(c(100, 0, 0, 0), 2),
CMT = rep(c(1, 2, 2, 2), 2),
II = rep(c(12, 0, 0, 0), 2),
ADDL = rep(c(3, 0, 0, 0), 2),
DV = c(NA, 7.1, 8.4, 3.2, NA, NA, 9.0, 3.9))
rec <- rec[!(rec$EVID == 0 & rec$MDV == 1), ]
x <- frm_ode_records(rec)
x$data
#> ID TIME CMT DV
#> 1 1 2 2 7.1
#> 2 1 6 2 8.4
#> 3 1 42 2 3.2
#> 4 2 6 2 9.0
#> 5 2 42 2 3.9
x$events
#> group time state value method duration ii addl ss
#> 1 1 0 1 100 add 0 12 3 FALSE
#> 2 2 0 1 100 add 0 12 3 FALSE
frm_lincmt(parms = list(ka = 1, ke = 0.2, V = 10), times = x$data$TIME,
group = x$data$ID, ncmt = 1, depot = TRUE,
events = x$events)
#> [1] 6.687310 3.733943 4.109285 3.733943 4.109285