NMwork - Create Nonmem Parameter Tables
Philip Delff
February 11, 2026
Source:vignettes/ParameterTables.Rmd
ParameterTables.RmdObjectives
Create parameter tables, using annotation of control stream parameter sections
Print parameter tables to various formats (R console, jpg, word, ppt, pdf, include in Rmd document)
Modify subsets of information provided in parameter section annotations
Use flexible tools to generate information when parameter sections are less diligently annotated
Introduction
A table of model parameter estimates is some of the most basic model
information. Nevertheless, it is often cumbersome to get a decently
annotated table of parameter estimates. In Nonmem control streams, the
definition of parameters such as ’s, ’s and ’s is handled in
$THETA, $OMEGA and $SIGMA while
the definition of variables such as CL or KIN
is handled elsewhere, typically in $PK, $PRED
or $ERROR. This allows for flexibility in model definition
but it also leaves some book keeping to the user for interpretation of
the parameter estimates. One can choose to avoid interpretation of
parameters by outputting the variables themselves in
$TABLE. That way, they can simulate out the (often) more
easily interpretable variable values without relying on translation
between parameters and variables. However, by interpreting the parameter
values (preferably including appropriate transformation to physiological
variables), one can make direct use of other properties estimated by
Nonmem such as parameter precision as estimated in the $COVARIANCE step.
This vignette presents a flexible and easy-to-use framework for handling
this bookkeeping, with the benefit of automated generation of annotated
parameter tables.
Let’s break the generation of parameter tables into four steps.
Annotation of control streams (down to the user)
Compilation of annotations, parameter estimates and precision, if present (tools available in
NMdata)Formatting of parameter table information (
NMwork::createParameterTable)Printing of parameter table information (
NMwork::printParameterTable)
Provided that step 1. is done informatively, step 2 is fully wrapped into step 3. This means, you can end up with as simple a workflow as this
| Estimate (RSE%) | |||
|---|---|---|---|
| THETA(2)* | Central volume, TVV2 (L) | 74.6 (1.4%) | [66, 84] |
| THETA(3)* | Clearance, TVCL (L/h) | 4.4 (7.9%) | [3.5, 5.5] |
| THETA(4)* | Peripheral volume, TVV3 (L) | 160 (4.8%) | [99, 260] |
| THETA(5)* | Intercomparmental clearance, TVQ (L/h) | 8.44 (2.9%) | [7.5, 9.5] |
| THETA(6)* | Age effect on clearance, AGECL | 1.41 (42.2%) | [1.1, 1.9] |
| THETA(7)* | Body-weight effect on clearance, WEIGHTCL | 1.21 (191.6%) | [0.6, 2.4] |
| THETA(8)* | Male effect on clearance, MALECL | 3.22 (10.4%) | [2.5, 4.1] |
| Inter-Individual Variances | |||
| OMEGA(1,1) | KA | 0 (fixed) |
|
| OMEGA(2,2) | V2 | 0.178 [44%] (17.8%) | [0.12, 0.24] |
| OMEGA(3,3) | CL | 0.24 [52%] (18.5%) | [0.15, 0.33] |
| OMEGA(4,4) | V3 | 0 (fixed) |
|
| OMEGA(5,5) | Q | 0 (fixed) |
|
| Residual Error | |||
| SIGMA(1,1) | Prop err | 0.0810471 [28.6%] (6.7%) | [0.07, 0.092] |
| SIGMA(2,2) | Add err | 0 (fixed) |
|
file.mod <- system.file("nonmem/xgxr134.mod",package="NMwork")
partab <- createParameterTable(file.mod)
printParameterTable(partab,format="html") In the rest of this document, we will see what all this did, how you can get it to work, and how you can fill in the information if this was not done during model development (the annotation in step 1 above).
While createParameterTable() and
printParameterTable() are offered by NMwork,
the underlying functionality is largely provided by NMdata.
For the best experience, make sure to keep NMdata up to date from
CRAN.
When to Use This
The methods shown in this document are intended to automate
generation of publication-ready parameter estimate tables. Because of
the informative value of such tables and because they are easy to
generate (especially with a few simple habits of how to document
parameter interpretation in control streams), the recommendation is to
use them during model development as well as for reporting. However, the
solutions provided by createParameterTables() and
printParameterTables() are not very flexible in terms of
the final output. While the underlying functions provided in
NMdata to retrieve and compile the data are intended to
work as generally as possible, NMwork includes many
decisions related to formatting etc. The user may not agree with all
this, and in that case they may need to edit the code to get their
prefered behavior.
Creating the Parameter Table
(createParameterTable())
Formatting Features
createParameterTable() uses the following columns to
format the parameter table. Hence, large parts of this document
describes techniques to fill in the columns of interest to
createParameterTable(). These are (all characters):
-
symbol: Typically, the variable name associated with the parameter, e.g. “CL”. -
label: The parameter label, e.g. “Clearance”. -
trans: Parameter transformation (character strings). -
unit: The parameter unit, e.g. “L/h”. -
panel: Grouping variable. Can be an intermediary short-hand code like “struct” or “iiv”.
createParameterTable() will use those columns for
formatting, if available. Sensible default values are being used where
possible. If no symbol column is available, the parameter
name (i.e. THETA(1)) will be used. The default parameter
description is “label, symbol (unit)” (unit) If label
and/or unit are missing, the resulting parameter
description will just omit them.
Example 1: Annotation of Control Stream Parameter Sections
This approach is the simplest to show because it is what we already
did in the introduction. All the preparations are done in the control
stream, and createParamaterTable() can do the rest of the
work.
Take a look at the parameter sections of the control stream of the model we just read for the parameter table above.
;; format: %idx: %symbol ; %label [%unit] ; %trans
$THETA (.1) ; 1 : TVKA ; Absorption rate [1/h] ; log
$THETA (3) ; 2 : TVV2 ; Central volume [L] ; log
$THETA (1) ; 3 : TVCL ; Clearance [L/h] ; log
$THETA (4) ; 4 : TVV3 ; Peripheral volume [L] ; log
$THETA (-1) ; 5 : TVQ ; Intercomparmental clearance [L/h] ; log
$THETA .1 ; 6 : AGECL ; Age effect on clearance []; log
$THETA .1 ; 7 : WEIGHTCL ; Body-weight effect on clearance []; log
$THETA .1 ; 8 : MALECL ; Male effect on clearance []; log
$OMEGA 0 FIX ; 1 : KA
$OMEGA 0.1 ; 2 : V2
$OMEGA 0.1 ; 3 : CL
$OMEGA 0 FIX ; 4 : V3
$OMEGA 0 FIX ; 5 : Q
;; format.sigma: %symbol - %label ; %trans
$SIGMA 0.1 ; SigP - Prop err ; propErr
$SIGMA 0 FIX ; SigA - Add err ; addErr
Each $THETA definition is annotated with descriptive
information such as the variable “symbol” (TVKA), label (Absorption
rate), unit (1/h), and transformation (log). I know I skipped the
idx column too, but that is intentional. While such a
counter can be very useful during model development,
createParameterTable() does not need it.
Notice a few things about the format - The format of the annotations
is up to the user as long as it is consistent within parmeter types
(i.e. all $THETAs annotations must be consistent). -
Delimiters can vary. between idx and symbol, a colon (:) is used,
between symbol and label it a semicolon, and the unit is in brackets.
The latter acrually means the at the delimitor between label and unit is
a left bracket ([), while the delimitor between unit and trans is a
composite of a right bracket and a semicolon (];). Spaces surrounding
delimiters are dropped, so “] ;”, “] ;” and “];” all mean the same. By
the way, tabulator characters are treated like spaces. - As a note of
how the parameters have been documented, the modeler has left a comment
string starting with “format:” where the column names in the desired
table have been labeled with %-signs. In fact, that’s what
createParameterTable() used to learn how to generate the
table. - The $OMEGA annotations only contain
idx and symbol. In this case all the
ETAs are used for log-normal distributed between-subject
variability so a diligent annotation of these parameters are redundant.
Since the formatting of idx and symbol (and of
course the delimiter between them) are consistent with the
$THETA annotations. The missing fields, label,
unit and trans will be filled with
NAs. - The $SIGMA parameters are annotated
differently. The annotation columns are symbol,
label, and trans, but the delimiters are
different, and unit is not included. - The annotation
scheme used for $SIGMAs is documented in a comment starting
with format.sigma:. createParameterTable()
finds this automatically.
createParameterTable() collects
head(partab,2)| model | TABLENO | NMREP | table.step | par.type | parameter | par.name | i | j | FIX | value | cond | eigCor | partLik | se | seStdDevCor | stdDevCor | termStat | est | iblock | blocksize | initstr | idx | symbol | label | unit | trans | panel | panel.label | rse | CI.l | CI.u | est.orig | corr | tab.lab | tab.corr | tab.lab.ltx | parameter.ltx |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| xgxr134 | 2 | 1 | IMP | THETA | THETA1 | THETA(1) | 1 | NA | 0 | 0.795533 | 28.1379000 | 0.0969534 | 1.560360 | 0.0639614 | 0 | 0 | 0 | 0.795533 | NA | NA | (.1) | 1 | TVKA | Absorption rate | 1/h | log | FixedEff | Fixed Effects | 0.0804007 | 0.6701687 | 0.9208973 | 0.795533 | NA | Absorption rate, TVKA (1/h) | NA | Absorption rate, TVKA (1h) | \(\theta_{1}\) |
| xgxr134 | 2 | 1 | IMP | THETA | THETA2 | THETA(2) | 2 | NA | 0 | 4.311820 | 0.0969534 | 0.2659430 | -0.523522 | 0.0587818 | 0 | 0 | 0 | 4.311820 | NA | NA |
|
2 | TVV2 | Central volume | L | log | FixedEff | Fixed Effects | 0.0136327 | 4.1966077 | 4.4270323 | 4.311820 | NA | Central volume, TVV2 (L) | NA | Central volume, TVV2 (L) | \(\theta_{2}\) |
Example: Specify Formats
If you have a control stream with consistent annotation but without
the “format” lines to document the annotation format in the
control stream, you can include that format in
createParameterTables() using the
args.ParsText argument.
createParameterTable(file.lst=file.mod,args.ParsText=list(format="%idx - %symbol - %unit",format.omega="%idx-%symbol"))The reason for the argument name args.ParsText is that
that all you provide will be passed to
NMdata::NMreadParsText() which is what
createParameterTable() uses to process the control stream.
If formats are passed here, they overrule what may be defined in the
control stream.
Example: Specify Selected Annotation Values
What we have seen so far relies on the modeler to diligently annotate
all parameters, and all text in the parameter table is coming directly
from the control stream. However, it may be desired to edit some of the
information. createParameterTable() has the arguments
df.repair and by.repair to do this.
createParameterTable() uses
NMdata::mergeCoal() to do this. The basic idea is that
non-NA values in df.repair are inserted into
(overwriting) the parameter table by matching the columns provided in
by.repair. by.repair is like by
in merges, but mergeCoal() prioritizes
df.repair over the parameter annotations in the control
stream.
df.repair <- data.frame(symbol="WEIGHTCL",label="Bodyweight effect on clearance")
partab <- createParameterTable(file.mod,df.repair=df.repair,by.repair="symbol") |>
printParameterTable(format="html")by.repair can be a character vector of any length. But
the main limitation of this approach is that you can only use one
by.repair vector. This means you need one variable (say
symbol) to be consistently read from the control stream.
You could merge by parameter (like THETA1,
OMEGA(1,1) etc.) but that breaks the benefit of linking
parameters to variables in the control stream.
Example: Construct Annotations First
Sometimes you may need to edit the table in more detail that what
df.repair and by.repair allow for.
createParameterTable() offers the argument
df.labs for the user to completely format the labels prior
to the table generation. This allows for say multiple steps of
mergeCoal or other manual editing, or whatever automated
techniques the user may have.
dt.labs <- NMreadParsText(file.mod)
df.repair <- data.frame(symbol="WEIGHTCL",label="Bodyweight effect on clearance")
dt.labs <- NMwork:::mergeCoal(dt.labs,df.repair,by="symbol",as.fun="data.table")
dt.labs[par.type=="OMEGA",label:=paste("IIV:",label)]
createParameterTable(file.mod,df.labs=dt.labs) |>
printParameterTable(format="html")| Estimate (RSE%) | |||
|---|---|---|---|
| THETA(2)* | Central volume, TVV2 (L) | 74.6 (1.4%) | [66, 84] |
| THETA(3)* | Clearance, TVCL (L/h) | 4.4 (7.9%) | [3.5, 5.5] |
| THETA(4)* | Peripheral volume, TVV3 (L) | 160 (4.8%) | [99, 260] |
| THETA(5)* | Intercomparmental clearance, TVQ (L/h) | 8.44 (2.9%) | [7.5, 9.5] |
| THETA(6)* | Age effect on clearance, AGECL | 1.41 (42.2%) | [1.1, 1.9] |
| THETA(7)* | Bodyweight effect on clearance, WEIGHTCL | 1.21 (191.6%) | [0.6, 2.4] |
| THETA(8)* | Male effect on clearance, MALECL | 3.22 (10.4%) | [2.5, 4.1] |
| Inter-Individual Variances | |||
| OMEGA(1,1) | IIV: NA | 0 (fixed) |
|
| OMEGA(2,2) | IIV: NA | 0.178 [44%] (17.8%) | [0.12, 0.24] |
| OMEGA(3,3) | IIV: NA | 0.24 [52%] (18.5%) | [0.15, 0.33] |
| OMEGA(4,4) | IIV: NA | 0 (fixed) |
|
| OMEGA(5,5) | IIV: NA | 0 (fixed) |
|
| Residual Error | |||
| SIGMA(1,1) | Prop err | 0.0810471 [28.6%] (6.7%) | [0.07, 0.092] |
| SIGMA(2,2) | Add err | 0 (fixed) |
|
A function to consider for automated detection of relationship
between parameters and Nonmem variables (like what is called
symbol above) is NMdata::NMrelate().
NMrelate(file.mod)| model | par.name | par.type | i | j | nrep.LHS | nrep.par | LHS | label | code | parameter |
|---|---|---|---|---|---|---|---|---|---|---|
| xgxr134 | THETA(1) | THETA | 1 | NA | 1 | 1 | LTVKA | LTVKA | LTVKA=THETA(1) | THETA1 |
| xgxr134 | THETA(2) | THETA | 2 | NA | 1 | 1 | LTVV2 | LTVV2 | LTVV2=THETA(2) | THETA2 |
| xgxr134 | THETA(3) | THETA | 3 | NA | 1 | 1 | LTVCL | LTVCL | LTVCL=THETA(3) | THETA3 |
| xgxr134 | THETA(4) | THETA | 4 | NA | 1 | 1 | LTVV3 | LTVV3 | LTVV3=THETA(4) | THETA4 |
| xgxr134 | THETA(5) | THETA | 5 | NA | 1 | 1 | LTVQ | LTVQ | LTVQ=THETA(5) | THETA5 |
| xgxr134 | THETA(6) | THETA | 6 | NA | 1 | 1 | AGECL | AGECL | AGECL=THETA(6) | THETA6 |
| xgxr134 | THETA(7) | THETA | 7 | NA | 1 | 1 | WEIGHTCL | WEIGHTCL | WEIGHTCL=THETA(7) | THETA7 |
| xgxr134 | THETA(8) | THETA | 8 | NA | 1 | 1 | MALECL | MALECL | MALECL=THETA(8) | THETA8 |
| xgxr134 | OMEGA(1,1) | OMEGA | 1 | 1 | 1 | 1 | KA | KA | KA=EXP(MU_1+ETA(1)) | OMEGA(1,1) |
| xgxr134 | OMEGA(2,2) | OMEGA | 2 | 2 | 1 | 1 | V2 | V2 | V2=EXP(MU_2+ETA(2)) | OMEGA(2,2) |
| xgxr134 | OMEGA(3,3) | OMEGA | 3 | 3 | 1 | 1 | CL | CL | CL=EXP(MU_3+ETA(3)) | OMEGA(3,3) |
| xgxr134 | OMEGA(4,4) | OMEGA | 4 | 4 | 1 | 1 | V3 | V3 | V3=EXP(MU_4+ETA(4)) | OMEGA(4,4) |
| xgxr134 | OMEGA(5,5) | OMEGA | 5 | 5 | 1 | 1 | Q | Q | Q=EXP(MU_5+ETA(5)) | OMEGA(5,5) |
| xgxr134 | SIGMA(1,1) | SIGMA | 1 | 1 | 1 | 2 | SIGP | SIGP | SIGP=SIGMA(1,1) | SIGMA(1,1) |
| xgxr134 | SIGMA(1,1) | SIGMA | 1 | 1 | 2 | 2 | Y | Y - SIGMA(1,1) | Y=F+F*ERR(1)+ERR(2) | SIGMA(1,1) |
| xgxr134 | SIGMA(2,2) | SIGMA | 2 | 2 | 1 | 2 | SIGA | SIGA | SIGA=SIGMA(2,2) | SIGMA(2,2) |
| xgxr134 | SIGMA(2,2) | SIGMA | 2 | 2 | 2 | 2 | Y | Y - SIGMA(2,2) | Y=F+F*ERR(1)+ERR(2) | SIGMA(2,2) |
NMrelate() looks at the control stream to identify
variable assignments, shown in the code column. The
label column is an attempt to identify the name of the
variable the parameter is associated with. This works very well with
some limitations. For instance, THETAs will normally not
work with referencing because a MU will be defined based on
the parameter, which is no more informative than the parameter name
itself. Also, the control stream does not need to create named variables
at all and could use parameters like THETA and
ETA directly in $DES. On the other hand, by
always creating named variables, you could automate this step using
NMrelate().
For example, if the $OMEGA’s are not annotated, we can
use NMrelate to fill these in. The parameter sections in
this model look like this:
;; format: %idx: %symbol ; %label [%unit] ; %trans
$THETA (.1) ; 1 : TVKA ; Absorption rate [1/h] ; log
$THETA (3) ; 2 : TVV2 ; Central volume [L] ; log
$THETA (1) ; 3 : TVCL ; Clearance [L/h] ; log
$THETA (4) ; 4 : TVV3 ; Peripheral volume [L] ; log
$THETA (-1) ; 5 : TVQ ; Intercomparmental clearance [L/h] ; log
$THETA .1 ; 6 : AGECL ; Age effect on clearance []; log
$THETA .1 ; 7 : WEIGHTCL ; Body-weight effect on clearance []; log
$THETA .1 ; 8 : MALECL ; Male effect on clearance []; log
$OMEGA 0 FIX
$OMEGA 0.1
$OMEGA 0.1
$OMEGA 0 FIX
$OMEGA 0 FIX
;; format.sigma: %symbol - %label ; %trans
$SIGMA 0.1 ; SigP - Prop err ; propErr
$SIGMA 0 FIX ; SigA - Add err ; addErr
df.labs.b <- NMreadParsText(file.mod.b)[par.type!="OMEGA"] |>
rbind(setnames(NMrelate(file.mod.b)[par.type=="OMEGA"],"label","symbol"),fill=TRUE)Now we filled in the symbol column for the
$OMEGA parameters so createParameterTable()
can label the parameters.
df.labs.b[,.(par.type,par.name,symbol,label,unit,trans)]| par.type | par.name | symbol | label | unit | trans |
|---|---|---|---|---|---|
| THETA | THETA(1) | TVKA | Absorption rate | 1/h | log |
| THETA | THETA(2) | TVV2 | Central volume | L | log |
| THETA | THETA(3) | TVCL | Clearance | L/h | log |
| THETA | THETA(4) | TVV3 | Peripheral volume | L | log |
| THETA | THETA(5) | TVQ | Intercomparmental clearance | L/h | log |
| THETA | THETA(6) | AGECL | Age effect on clearance | log | |
| THETA | THETA(7) | WEIGHTCL | Body-weight effect on clearance | log | |
| THETA | THETA(8) | MALECL | Male effect on clearance | log | |
| SIGMA | SIGMA(1,1) | SigP | Prop err | NA | propErr |
| SIGMA | SIGMA(2,2) | SigA | Add err | NA | addErr |
| OMEGA | OMEGA(1,1) | KA | NA | NA | NA |
| OMEGA | OMEGA(2,2) | V2 | NA | NA | NA |
| OMEGA | OMEGA(3,3) | CL | NA | NA | NA |
| OMEGA | OMEGA(4,4) | V3 | NA | NA | NA |
| OMEGA | OMEGA(5,5) | Q | NA | NA | NA |
And the parameter table becomes
createParameterTable(file.lst=file.mod.b,df.labs=df.labs.b) |>
printParameterTable(format="html")| Estimate (RSE%) | |||
|---|---|---|---|
| THETA(2)* | Central volume, TVV2 (L) | 74.6 (1.4%) | [66, 84] |
| THETA(3)* | Clearance, TVCL (L/h) | 4.4 (7.9%) | [3.5, 5.5] |
| THETA(4)* | Peripheral volume, TVV3 (L) | 160 (4.8%) | [99, 260] |
| THETA(5)* | Intercomparmental clearance, TVQ (L/h) | 8.44 (2.9%) | [7.5, 9.5] |
| THETA(6)* | Age effect on clearance, AGECL | 1.41 (42.2%) | [1.1, 1.9] |
| THETA(7)* | Body-weight effect on clearance, WEIGHTCL | 1.21 (191.6%) | [0.6, 2.4] |
| THETA(8)* | Male effect on clearance, MALECL | 3.22 (10.4%) | [2.5, 4.1] |
| Inter-Individual Variances | |||
| OMEGA(1,1) | KA | 0 (fixed) |
|
| OMEGA(2,2) | V2 | 0.178 [44%] (17.8%) | [0.12, 0.24] |
| OMEGA(3,3) | CL | 0.24 [52%] (18.5%) | [0.15, 0.33] |
| OMEGA(4,4) | V3 | 0 (fixed) |
|
| OMEGA(5,5) | Q | 0 (fixed) |
|
| Residual Error | |||
| SIGMA(1,1) | Prop err | 0.0810471 [28.6%] (6.7%) | [0.07, 0.092] |
| SIGMA(2,2) | Add err | 0 (fixed) |
|
Printing the Parameter Table
(printParameterTable())
printParameterTable() prints the output from
createParameterTable() using predefined templates.
Currently, it has two templates, of which the first is designed for
display in the R console, i.e. intended for interactive use. The other
is intended for reporting with all information explicitly expressed.
printParameterTable() supports use of three “engines” to
write tables: kable, pmtables, and
flextable. Switch between those using the
engine argument. The format argument specifies
whether pdf, html
| engine | format | Description |
|---|---|---|
| kable, pmtables | latex | latex code. Use in .tex/.Rmd or similar files. |
| kable, pmtables | file.pdf | A path to a standalone pdf file to be generated. Absolute paths may not be supported with pmtables engine. |
| kable, pmtables | A standalone pdf file in a temporary location, intended for interactive use. | |
| kable | R | A simplified format printed in the R console. For interactive use. |
| kable | html | html code. To be used in html documents |
| flextable | NA | NA |
Currently, the flextable returns a flextable object
which the user can save to png, docx, pptx, etc.
Example: Include in Rmd report
If createParameterTables() has not already been called
during the analysis, the code could be this simple. Notice, here we
choose engine="pmtables" to use pmtables. This is because
pmtables works with footnotes even for large tables spanning multiple
pages. Use engine="kable" to get kable in stead (or of
course engine="flextable" can be used too.
createParameterTable(file.mod) |>
printParameterTable(partab,format="latex", engine="pmtables") Depending on the model and the (missing) annotations of parameter
definitions, running createParameterTable() may take a
little customization, as discussed above. Therefore, it may be desired
to load the result of that step, and then run
printParameterTable(). In that case
Subsetting Parameters to Print
Notice not all parameters are printed above. The argument
include.fix controls whether to include fixed parameters.
The options are “notZero (default, inclde only fixed variables different
from zero), TRUE (include fixed parameters) and
FALSE (omit all fixed parameters). Also, see additional
arguments to control what parameters to include: include,
include.pattern, drop,
drop.pattern in the manual.
Title and Footnote Generation
printParameterTable() by default inserts a title with
the model name. This can be modified through the caption
argument. Footnotes are also included.