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:
doses <- NMcreateDoses(TIME=c(0,24),AMT=c(300,150),
ADDL=c(5),II=c(24),CMT=1)
data.sim <- NMaddSamples(doses,TIME=0:(24*7),CMT=2)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
file.mod <- "/path/to/model/file"
simres <- NMsim(file.mod=file.mod,
data=data.frame.for.simulation)typical=TRUE) is a subject with all ETAs = 0simres.typ <- NMsim(file.mod=file.mod,data=data.sim,
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.
file2.mod <- "models/xgxr114.mod"
simres.typ2 <- NMsim(file.mod=c("2 compartments"=file.mod,
"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)
res <- NMscanData(file.mod,quiet=T)
data.sim.ind <- merge(subset(data.sim,select=-ID),
data.frame(ID=unique(res$ID)))
setorder(data.sim.ind,ID,TIME,EVID)
simres.ebe <- NMsim(file.mod,
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.
$SIMULATIONsubproblems 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.simres.subprob <- NMsim(file.mod=file.mod,
data=data.sim,
name.sim="Subproblems",
subproblems=1000)
## sampleCovs() replicates data.sim for each subject,
## with covariates sampled from dt.covs
dt.covs <- NMscanData(file.mod,quiet=T,as.fun="data.table") |>
findCovs(by=c("ID")) ## find covs in estimated data
data.sim.nsubjs <- sampleCovs(data=data.sim,Nsubjs=1000,
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")
simres.datarep <- NMsim(file.mod=file.mod,
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.
simres.vpc <- NMsim(file.mod
,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
res <- NMscanData(file.mod,quiet=T)
res[,DOSEC:= factor(res$DOSE, levels = res$DOSE,
labels = paste(res$DOSE, "mg"))] #for labels
# generate the vpc
vpc <-
tidyvpc::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()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 resultsNMsim() 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!