The impact of a great model depends a lot on how well it is
communicated. A parameter table can help; it brings together critical
details of the model in a systematic way. However, creating parameter
tables can be tedious, especially for modeling systems like
NONMEM
where information is distributed among numerous text
files. Here we describe a systematic approach for generating parameter
tables in NONMEM
.
NONMEM results end up in many places.
The list
file has the final parameter estimates in a
readable form, as well as many diagnostic values, such as standard
errors (when available) and shrinkage estimates.
Various ancillary outputs may be available, such as
*.ext
, *.cov
, *.xml
, etc, giving
more regular and/or more specific versions of model components.
Bootstrap estimates of parameter uncertainty are probably in some third-party format, since bootstrapping is usually performed independently of model estimation.
Some of the most important information may not be captured anywhere! You may know that THETA1 is “apparent oral clearance” in your model, and that the units are liters. But to NONMEM it is just THETA1.
When you are making parameter tables by hand, no problem: your memory supplies all the integration, as well as missing details. But manual table generation can be tedious, time-consuming, and error-prone. If, rather, we want to automate the generation of parameter tables, we’re going to have to be more systematic about where and how the data sources are stored.
The most fundamental storage convention is the concept of a
project
. This is a path – preferably a relative
path, to a directory where your models live.
A second useful convention is the habit of keeping all the files
related to a specific model in a project
subdirectory with
a unique name: i.e., the name of the model. While not strictly
necessary, it really helps with organization and keeps NONMEM models
from interfering with eachother while running.
I mentioned relative paths above. Relative to what? A third useful
convention is to write all file paths relative to the file in which you
are writing them. So if I have a model in
home/project/1001/
, and I write a script
home/script/analysis.R
that processes model ouptut, the
path to the model directory will be written as
../project/1001
.
Okay, cut to the chase: we want an R function that creates a NONMEM parameter table. The challenges to be solved are as follows.
The package nonmemica
provides the function
partab
that creates a parameter table for a single NONMEM
model. partab
knows where the models live, because you
explicitly pass a project
argument or (if you are lazy like
me) you set an option near the top of your script.
Install and load nonmemica
like this:
Specify the project directory like this:
Or this:
Users should not have to worry much about object orientation, but knowing something about it can be very helpful.
Briefly, object orientation means that a function does different
things to different object types. Preparing a meal is very different
from preparing a tax return. And formatting a data.frame
is
very different from formatting a POSIXlt
. Try
to see all ways things get formatted in R. Typically we just say
format
and R finds the right method, depending on what it
is we are formatting.
partab
is no different. If you say
partab(1001)
, the software sees that you are trying to
create a parameter table from a number, which doesn’t really make sense.
So it assumes 1001 is a model name, converts it to character, and goes
looking for the function partab.character()
. In fact, most
of the interesting arguments to partab
are actually
documented under partab.character
for this reason.
You’ve probably noticed … R usage is undergoing a revolution. The
magrittr
package recently gave us a “forward pipe operator”
that fundamentally changes how R can be used. Even if you don’t use
piping, an awareness will help you understand the occasional example.
For instance, the following have the same meaning.
Traditional:
Piped:
Essentially, the left-hand-side is the first argument to the function on the right-hand-side, and the result can be used as input to yet-another-function in an ongoing chain. That means we can express functionality in a natural order, rather than reverse-nested order. This:
is easier to write and easier to understand (in my opinion) than this:
but the two are equivalent.
partab
gets estimates and standard errors from
*.xml
. Make sure your NONMEM execution procedure calls for
this file to be created and returned. It should appear in the model
directory as a sibling of *.lst
. If your xml file is in an
unusual place, you can specify the path explicitly using the
xmlfile
argument (othewise partab
will
guess).
partab
gets bootstrap estimates from
bootstrap_dirx/bootstrap_results.csv
(in the model
directory). This is created using PsN. partab
will try to
find the latest file if there are more than one. You can specify the
path explicitly using the bootcsv
argument.
Let’s face it. NONMEM does not have integrated metadata. It knows the final estimate of THETA1, but it does not know what THETA1 means in human terms. A really useful parameter table should give a symbolic interpretation of THETA1, such as CL/F, units (if any), and hopefully a short definition.
The bad news is: you have to supply these yourself. The good new is: if you do it systematically, you can save a lot of effort.
partab
looks in two places for metadata. First, it looks
in the control stream for the items mentioned in the fields
argument. It will guess where the control stream is, but you can supply
the ctlfile
argument. You can also modify the fields
argument to change what it seeks.
partab
hopes to find each parameter on a line by itself,
with semicolon-delimited fields trailing on the same line.
$THETA
(0,10,50) ; CL/F; clearance; L/h
(0,10,100) ; Vc/F; central volume; L
(0,0.2,5) ; Ka; absorption rate; 1/h
By the way, the same conventions apply to tabled items. You can specify in your control stream how you think about table output.
$TABLE NOPRINT FILE=mod2.tab ONEHEADER
ID ; ID; subject identifier;
TIME ; TIME; time; h
IPRE ; IPRED; individual prediction; ng/mL
CL ; CLI; posthoc systemic clearance; L/h
ETA1 ; BSV_CL; clearance variability;
Of course, these won’t show up in a parameter table, but you’ll see them in a list of item definitions if you do this:
library(magrittr)
library(nonmemica)
options(project = system.file('project/model',package='nonmemica'))
1001 %>% definitions %>% head
## item symbol label unit
## 1 theta_01 CL/F clearance L/h
## 2 theta_02 Vc/F central volume L
## 3 theta_03 Ka absorption rate constant 1/h
## 4 theta_04 Q/F intercompartmental clearance L/h
## 5 theta_05 Vp/F peripheral volume L
## 6 theta_06 WT_CL weight effect on clearance <NA>
If you like, you can write out the defintions and edit them. You can change the file location or stick with the default. If you didn’t like exactly what was in the control stream, you can ignore it by passing a zero-length argument for ctlfile.
The metafile needs to stay comma-separated. White space will be stripped, and blanks, spaces, or dots will be understood as NA.
By default, the parameter table looks like this (but if bootstraps
can’t be found, the ci
columm will be missing).
## parameter estimate prse ci symbol
## 1 theta_01 9.51 9.67 (7.24, 11.2) CL/F
## 2 theta_02 22.8 9.54 (19.7, 25.4) Vc/F
## 3 theta_03 0.0714 7.35 (0.0646, 0.0803) Ka
## 4 theta_04 3.47 15.4 (3.02, 4.59) Q/F
## 5 theta_05 113 21 (90.5, 380) Vp/F
## 6 theta_06 1.19 28.3 (0.88, 1.3) WT_CL
## 7 theta_07 1.02 11 (0.53, 1.73) MALE_CL
## 8 omega_01_01 0.214 22.8 (0.151, 0.319) IIV_CL
## 9 omega_02_01 0.121 26.4 CL_V
## 10 omega_02_02 0.0945 33.2 (0.07, 0.179) IIV_Vc
## 11 omega_03_01 -0.0116 173 CL_Ka
## 12 omega_03_02 -0.0372 36.1 Vc_Ka
## 13 omega_03_03 0.0466 34.7 (0.0475, 0.146) IIV_Ka
## 14 sigma_01_01 0.0492 10.9 (-0.039, 0.0165) ERR_PROP
## 15 sigma_02_02 0.202 33.5 (-0.0512, -0.0097) ERR_ADD
## label unit
## 1 clearance L/h
## 2 central volume L
## 3 absorption rate constant 1/h
## 4 intercompartmental clearance L/h
## 5 peripheral volume L
## 6 weight effect on clearance <NA>
## 7 male effect on clearance <NA>
## 8 interindividual variability on clearance <NA>
## 9 interindividual clearance-volume covariance <NA>
## 10 interindividual variability on central volume <NA>
## 11 interindividual clearance-Ka covariance <NA>
## 12 interindividual volume-Ka covariance <NA>
## 13 interindividual variability on Ka <NA>
## 14 proportional error <NA>
## 15 additive error <NA>
If you want to modify the table, save it first.
For better debugging information, set
verbose = TRUE
.
partab
uses global options project
and
nested
to guess where the run directory is, and uses that
to guess where the source files are (metafile
,
xmlfile
,ctlfile
,bootcsv
). You can
supply any of these directly.
By default, partab
grabs the 5th and 95th percentile of
bootstraps as lo
and hi
, but you can specify
other levels using the like-named arguments. partab
formats
these and other numerics as character(unless you say
format = F
), first limiting to the number of significant
digits you specify (default digits = 3
). If
ci = TRUE
(the default), lo
and
hi
will be combined into a single interval using the
default values for open
, sep
, and
close
. Also, you can get absolute standard errors by
setting relative = FALSE
. Let’s compare the default table
above with something a bit more “raw”.
## parameter estimate se lo hi symbol
## 1 theta_01 9.50751181 0.919790154 7.23607500 11.165510000 CL/F
## 2 theta_02 22.79071262 2.175293028 19.68059000 25.360120000 Vc/F
## 3 theta_03 0.07143288 0.005249901 0.06458277 0.080336250 Ka
## 4 theta_04 3.47447234 0.535642513 3.01994200 4.586354000 Q/F
## 5 theta_05 113.26550777 23.729595389 90.54828000 380.010800000 Vp/F
## 6 theta_06 1.19214554 0.337188000 0.88016420 1.301607000 WT_CL
## 7 theta_07 1.02437681 0.112446588 0.52951980 1.733120000 MALE_CL
## 8 omega_01_01 0.21385145 0.048857713 0.15135260 0.319490800 IIV_CL
## 9 omega_02_01 0.12075418 0.031922082 NA NA CL_V
## 10 omega_02_02 0.09453026 0.031343114 0.06995132 0.179007400 IIV_Vc
## 11 omega_03_01 -0.01162268 0.020065690 NA NA CL_Ka
## 12 omega_03_02 -0.03720323 0.013425269 NA NA Vc_Ka
## 13 omega_03_03 0.04656332 0.016175082 0.04754577 0.145969600 IIV_Ka
## 14 sigma_01_01 0.04916786 0.005383747 -0.03904045 0.016537490 ERR_PROP
## 15 sigma_02_02 0.20181742 0.067636559 -0.05122678 -0.009697708 ERR_ADD
## label unit
## 1 clearance L/h
## 2 central volume L
## 3 absorption rate constant 1/h
## 4 intercompartmental clearance L/h
## 5 peripheral volume L
## 6 weight effect on clearance <NA>
## 7 male effect on clearance <NA>
## 8 interindividual variability on clearance <NA>
## 9 interindividual clearance-volume covariance <NA>
## 10 interindividual variability on central volume <NA>
## 11 interindividual clearance-Ka covariance <NA>
## 12 interindividual volume-Ka covariance <NA>
## 13 interindividual variability on Ka <NA>
## 14 proportional error <NA>
## 15 additive error <NA>
You may want to do other things as well, such as converting error
estimates to standard deviation or CV%. In my experience, it is
difficult to do this in an automated way, as it depends on hard-to-guess
properties, such as whether your distributions are log normal, and
whether error has been coded as a sigma or a theta. If your goal is
routinely to present random effects next to the corresponding parameter
estimate, consider using the same symbol for each, and then “unstacking”
the data with something like
dplyr:spread(x, parameter, symbol)
.
To some extent, the science of creating a parameter table is a
separate issue from the aesthetics. How should you render the table for
viewing by your audience? nonmemica
is deliberately
agnostic on this question, since there is no one right answer, and very
many good ones. In an rmarkdown environment, consider
knitr::kable
.
parameter | estimate | prse | ci | symbol | label | unit |
---|---|---|---|---|---|---|
theta_01 | 9.51 | 9.67 | (7.24, 11.2) | CL/F | clearance | L/h |
theta_02 | 22.8 | 9.54 | (19.7, 25.4) | Vc/F | central volume | L |
theta_03 | 0.0714 | 7.35 | (0.0646, 0.0803) | Ka | absorption rate constant | 1/h |
theta_04 | 3.47 | 15.4 | (3.02, 4.59) | Q/F | intercompartmental clearance | L/h |
theta_05 | 113 | 21 | (90.5, 380) | Vp/F | peripheral volume | L |
theta_06 | 1.19 | 28.3 | (0.88, 1.3) | WT_CL | weight effect on clearance | NA |
theta_07 | 1.02 | 11 | (0.53, 1.73) | MALE_CL | male effect on clearance | NA |
omega_01_01 | 0.214 | 22.8 | (0.151, 0.319) | IIV_CL | interindividual variability on clearance | NA |
omega_02_01 | 0.121 | 26.4 | CL_V | interindividual clearance-volume covariance | NA | |
omega_02_02 | 0.0945 | 33.2 | (0.07, 0.179) | IIV_Vc | interindividual variability on central volume | NA |
omega_03_01 | -0.0116 | 173 | CL_Ka | interindividual clearance-Ka covariance | NA | |
omega_03_02 | -0.0372 | 36.1 | Vc_Ka | interindividual volume-Ka covariance | NA | |
omega_03_03 | 0.0466 | 34.7 | (0.0475, 0.146) | IIV_Ka | interindividual variability on Ka | NA |
sigma_01_01 | 0.0492 | 10.9 | (-0.039, 0.0165) | ERR_PROP | proportional error | NA |
sigma_02_02 | 0.202 | 33.5 | (-0.0512, -0.0097) | ERR_ADD | additive error | NA |
You can also try pander::pander
.
parameter | estimate | prse | ci | symbol |
---|---|---|---|---|
theta_01 | 9.51 | 9.67 | (7.24, 11.2) | CL/F |
theta_02 | 22.8 | 9.54 | (19.7, 25.4) | Vc/F |
theta_03 | 0.0714 | 7.35 | (0.0646, 0.0803) | Ka |
theta_04 | 3.47 | 15.4 | (3.02, 4.59) | Q/F |
theta_05 | 113 | 21 | (90.5, 380) | Vp/F |
theta_06 | 1.19 | 28.3 | (0.88, 1.3) | WT_CL |
theta_07 | 1.02 | 11 | (0.53, 1.73) | MALE_CL |
omega_01_01 | 0.214 | 22.8 | (0.151, 0.319) | IIV_CL |
omega_02_01 | 0.121 | 26.4 | CL_V | |
omega_02_02 | 0.0945 | 33.2 | (0.07, 0.179) | IIV_Vc |
omega_03_01 | -0.0116 | 173 | CL_Ka | |
omega_03_02 | -0.0372 | 36.1 | Vc_Ka | |
omega_03_03 | 0.0466 | 34.7 | (0.0475, 0.146) | IIV_Ka |
sigma_01_01 | 0.0492 | 10.9 | (-0.039, 0.0165) | ERR_PROP |
sigma_02_02 | 0.202 | 33.5 | (-0.0512, -0.0097) | ERR_ADD |
label | unit |
---|---|
clearance | L/h |
central volume | L |
absorption rate constant | 1/h |
intercompartmental clearance | L/h |
peripheral volume | L |
weight effect on clearance | NA |
male effect on clearance | NA |
interindividual variability on clearance | NA |
interindividual clearance-volume covariance | NA |
interindividual variability on central volume | NA |
interindividual clearance-Ka covariance | NA |
interindividual volume-Ka covariance | NA |
interindividual variability on Ka | NA |
proportional error | NA |
additive error | NA |
If you have the nonmemica
package installed, you can
find this document using
vignette('parameter-table', package = 'nonmemica')
. Of
course, the usual R help is available as well. Contact the mantainer for
bug fixes and feature requests.
Making a NONMEM parameter table can be tedious. The
nonmemica
package provides partab
, a flexible
way of assembling the main sources of information and presenting them
nicely. A good parameter table should have rich metadata: symbols,
labels, and units at least. partab
supports several options
for encoding metadata systematically.