While NONMEM offers great flexibility for estimation of PK and PK/PD models, many users find the simulation features in NONMEM insufficient and turn to alternative software for simulation. This leads to additional work of model reimplementation, with risk that the simulation model may deviate from the estimated model due to bugs in the reimplementation. For a wide range of model types, the limitation is not in NONMEM’s ability to perform such simulations, but rather in the lack of a simple user-interface to obtain the simulations. NMsim
(Delff 2024b) provides such an interface as an R package readily available on CRAN, allowing the modeler to simulate models directly from an estimation control stream.
The goal of NMsim is to automate the NONMEM simulation workflow and provide a simple, flexible, and powerful R interface. With this automation, post-processing of model estimates can to great extent be automated.
NONMEM | Third-party | NMsim | |
---|---|---|---|
Implementation | None | Error-prone | None |
Execution | Tedious | Easy | Easy |
Depends on NONMEM | Yes | No | Yes |
Runtime | Fair | Fast | Fair |
NMsim
does not simulate, translate, or otherwise interpret a NONMEM
model. Instead, it automates the NONMEM simulation workflow (including
execution of NONMEM) and wraps it all into one R function. Provided
with a path to a NONMEM control stream and a data.frame to simulate,
NMsim
will do the following:
file.mod
($INPUT
and $DATA
matching the saved simulation data set; $SIMULATE
instead of
$ESTIMATION
and $COVARIANCE
).ext
file)NMsim
can call NONMEM directly or via PSN
. If NMsim
is run on a
system where NONMEM cannot be executed, NMsim
can still prepare the
simulation control stream and data file.
NMsim
is in itself a relatively small R package. It makes extensive use of
functionality to handle NONMEM data and control streams provided by
the R package
NMdata
(Delff 2024a).
Before conducting a simulation, an input data set is needed. The simulation input data set should be a data.frame, and NMsim()
will also return a data.frame. The input data:
ID
, TIME
, CMT
, AMT
, etc. plus covariates)There are no requirements to how the data sets are created. NMsim provides convenient helper functions that can optionally be used. E.g., the data set used in these simulations can be created this way:
<- NMcreateDoses(TIME=c(0,24),AMT=c(300,150),
doses ADDL=c(5),II=c(24),CMT=1)
<- NMaddSamples(doses,TIME=0:(24*7),CMT=2) data.sim
When providing a simulation data set, the default NMsim()
behavior is to sample a new subject (ETA’s).
library(NMsim) ## Used version 0.2.3
<- "/path/to/model/file"
file.mod <- NMsim(file.mod=file.mod,
simres data=data.frame.for.simulation)
typical=TRUE
) is a subject with all ETAs = 0<- NMsim(file.mod=file.mod,data=data.sim,
simres.typ typical=TRUE)
Notice that no information about the model is needed except for the control stream file path. The simulation is based on evaluation of PRED
, IPRED
, and optionally Y
. Options exist for building more advanced simulation models. The models shown here are based on data available in the package xgxr
(Stein et al. 2021).
Figure 1: PRED
, IPRED
, and Y
(if defined in control stream) are easily obtained with NMsim. Example from xgxr021
(Stein et al. 2021).
Multiple models can be simulated using the same data set in one
function call by supplying more than one model in the file.mod
argument. The models can be simulated on multiple data sets by
submitting a list of data.frames in the data
argument. NMsim will
return one data.frame with all the results for easy post-processing.
<- "models/xgxr114.mod"
file2.mod <- NMsim(file.mod=c("2 compartments"=file.mod,
simres.typ2 "1 compartment"=file2.mod),
data=data.sim,
typical=TRUE)
## The "model" column is used to distinguish the two models
ggplot(simres.typ2[EVID==2,],aes(TIME,PRED,colour=model))+
geom_line()
Figure 2: Simulation of multiple models and even multiple data sets is handled within one NMsim()
call.
NMsim_EBE
re-uses estimated individual ETAs from original datasetaddResVar()
if needed## Example simulating subjects from estimation data set
library(NMdata)
<- NMscanData(file.mod,quiet=T)
res <- merge(subset(data.sim,select=-ID),
data.sim.ind data.frame(ID=unique(res$ID)))
setorder(data.sim.ind,ID,TIME,EVID)
<- NMsim(file.mod,
simres.ebe data=data.sim.ind,
method.sim=NMsim_EBE,
table.vars=c("CL","V2","IPRED","PRED"))
New subjects can be simulated in multiple ways with NMsim.
$SIMULATION
subproblems
argument translates to the SUBPROBLEMS
NONMEM subroutine, replicating the simulation the specified number of times with new seedssimPopEtas()
function can generate a synthetic .phi file with
a simulated population that can be reused in future NMsim()
calls. This can be combined with sampling of covariates in R (sampleCovs()
),
allowing reuse of the same subjects across multiple simulations.<- NMsim(file.mod=file.mod,
simres.subprob data=data.sim,
name.sim="Subproblems",
subproblems=1000)
## sampleCovs() replicates data.sim for each subject,
## with covariates sampled from dt.covs
<- NMscanData(file.mod,quiet=T,as.fun="data.table") |>
dt.covs findCovs(by=c("ID")) ## find covs in estimated data
<- sampleCovs(data=data.sim,Nsubjs=1000,
data.sim.nsubjs data.covs=dt.covs,col.id.covs="ID",covs=c("WEIGHTB"))
simPopEtas(file.mod=file.mod,N=1000,seed.R=1231,
file.phi="simres/xgxr021_1000subjs.phi")
<- NMsim(file.mod=file.mod,
simres.datarep data=data.sim.nsubjs,
method.sim=NMsim_EBE,
file.phi="simres/xgxr021_1000subjs.phi",
name.sim="datarep")
Figure 3: Prediction intervals. New subjects can be simulated in multiple ways with NMsim. A simulated population can be reused across simulations.
Using NMsim, one can easily automate a visual predictive check. If the data argument is not provided in the function call, NMsim
will re-use estimation data.
<- NMsim(file.mod
simres.vpc table.vars=c("PRED","IPRED", "Y")
,name.sim="vpc_result",
,subproblems=250) ,
Visual predictive checks can be generated using the tidyvpc
package.
# read in NONMEM input data
<- NMscanData(file.mod,quiet=T)
res := factor(res$DOSE, levels = res$DOSE,
res[,DOSEClabels = paste(res$DOSE, "mg"))] #for labels
# generate the vpc
<-
vpc ::observed(res[EVID==0,], x = TIME, y = DV) |>
tidyvpc::simulated(simres.vpc[EVID==0,], y = Y) |>
tidyvpc::stratify(~DOSEC) |>
tidyvpc::binning(bin = "ntile", nbins = 9) |>
tidyvpc::vpcstats() tidyvpc
Figure 4: Visual predictive check generated using simulations from NMsim
NMsim()
argumentsNMsim must be configured with the path to the NONMEM executable. This can be done for each NMsim()
call using the path.nonmem
argument, but more easily it can be configured globally in the following way. You can also set where NMsim will store intermediate NONMEM files (dir.sims
) and where to store final results (dir.res
).
library(NMdata)
NMdataConf(path.nonmem = "/opt/NONMEM/nm75/run/nmfe75")
## or on Windows, it could be
NMdataConf(path.nonmem = "c:/nm75g64/run/nmfe75.bat")
NMdataConf(dir.sims="simtmp", ## location of sim tmp files
dir.res="simres") ## location of sim results
NMsim()
has many features which are explained and demonstrated in manuals and vignettes. A few often-used arguments are:
table.vars
and carry.out
: Redefine the output table. This can dramatically speed up simulations and save memory. E.g., table.vars=c("PRED","IPRED")
.name.sim
: Assign a name to the simulation and the generated files. Keeps order and separates results files between simulations.seed.R
and seed.nm
: Define seed, either through R, or directly as the seed used in NONMEM simulation control stream.See the NMsim website for code, more publications, vignettes, and news and check out our other poster at PAGE 2025: Simulation with parameter uncertainty using NMsim!