Introduction to pmxpartab

Benjamin Rich

2022-03-14

Introduction

This R package produces nice looking parameter tables for pharmacometric modeling results with ease. It is completely agnostic to the modeling software that was used.

Usage

The creation of the parameter table proceeds in 2 steps:

  1. Generate an intermediate data.frame from a list of outputs and metadata.
  2. Genreate an HTML table from the intermediate data.frame.

For the first step, the inputs are:

  1. Model outputs (parameter estimates, standard errors, etc.)
  2. Metadata, describing the outputs (labels, units, transformations, etc.)

To keep things generic, both the model outputs and metadata must be provided as R lists (it is a separate problem to extract the outputs from the modeling software into the required list format, although the package does include an auxiliary function to help deal specifically with NONMEM outputs).

For illustration, we will use a YAML description of the outputs and metadata, which can easily be read into R. First, the outputs:

library(yaml)

outputs <- yaml.load("
est:
  CL:   0.482334
  VC:   0.0592686
  nCL:  0.315414
  nVC:  0.536025
  ERRP: 0.0508497
se:
  CL:   0.0138646
  VC:   0.0055512
  nCL:  0.0188891
  nVC:  0.0900352
  ERRP: 0.0018285
fixed:
  CL:   no
  VC:   no
  nCL:  no
  nVC:  no
  ERRP: no
shrinkage:
  nCL:  9.54556
  nVC:  47.8771
")

We see that the outputs are split into separate sections: est for estimates, se for standard errors, fixed for indicating which parameters were fixed rather than estimated, shrinkage for shrinkage estimates.

Now, the metadata:

meta <- yaml.load("
parameters:
- name:  CL
  label: 'Clearance'
  units: 'L/h'
  type:  Structural

- name:  VC
  label: 'Volume'
  units: 'L'
  type:  Structural
  trans: 'exp'
  
- name:  nCL
  label: 'On Clearance'
  type:  IIV
  trans: 'SD (CV%)'
  
- name:  nVC
  label: 'On Volume'
  type:  IIV
  trans: 'SD (CV%)'
  
- name:  ERRP
  label: 'Proportional Error'
  units: '%'
  type:  RUV
  trans: '%'
")

Here, the first important thing is the name, which must match the names of the parameters in the outputs. Then, we have some optional attributes: a descriptive label, the units if applicable, a transformation trans, and a type.

Putting this all together, we can produce a data.frame like this:

parframe <- pmxparframe(outputs, meta)
parframe
##   name              label units       type    trans fixed      est          se
## 1   CL          Clearance   L/h Structural     <NA> FALSE 0.482334 0.013864600
## 2   VC             Volume     L Structural      exp FALSE 1.061060 0.005890157
## 3  nCL       On Clearance  <NA>        IIV SD (CV%) FALSE 0.315414 0.018889100
## 4  nVC          On Volume  <NA>        IIV SD (CV%) FALSE 0.536025 0.090035200
## 5 ERRP Proportional Error     %        RUV        % FALSE 5.084970 0.182850000
##         rse     lci95     uci95         pval shrinkage
## 1  2.874481 0.4551594 0.5095086 0.000000e+00        NA
## 2  0.555120 1.0495781 1.0726679 0.000000e+00        NA
## 3  5.988669 0.2783914 0.3524366 0.000000e+00   9.54556
## 4 16.796829 0.3595560 0.7124940 2.624601e-09  47.87710
## 5  3.595891 4.7265840 5.4433560 0.000000e+00        NA

Finally, our nicely formatted table looks like this:

pmxpartab(parframe)
Parameter Estimate RSE% 95% CI Shrinkage
Typical Values
Clearance (L/h) 0.482 2.87 0.455 – 0.510 -
Volume (L) 1.06 0.555 1.05 – 1.07 -
Between Subject Variability
On Clearance 0.315 (32.3%) 5.99 0.278 – 0.352 9.55%
On Volume 0.536 (57.7%) 16.8 0.360 – 0.712 47.9%
Residual Error
Proportional Error (%) 5.08 3.60 4.73 – 5.44 -

We can also do this in one shot, and add footnotes as well:

footnote <- c(
    "CI=confidence interval; RSE=relative standard error.",
    "Source: run001")

pmxpartab(pmxparframe(outputs, meta), footnote=footnote)
Parameter Estimate RSE% 95% CI Shrinkage
Typical Values
Clearance (L/h) 0.482 2.87 0.455 – 0.510 -
Volume (L) 1.06 0.555 1.05 – 1.07 -
Between Subject Variability
On Clearance 0.315 (32.3%) 5.99 0.278 – 0.352 9.55%
On Volume 0.536 (57.7%) 16.8 0.360 – 0.712 47.9%
Residual Error
Proportional Error (%) 5.08 3.60 4.73 – 5.44 -

CI=confidence interval; RSE=relative standard error.

Source: run001

It is also possible to use the pipe syntax:

outputs |> pmxparframe(meta) |> pmxpartab(footnote=footnote)
Parameter Estimate RSE% 95% CI Shrinkage
Typical Values
Clearance (L/h) 0.482 2.87 0.455 – 0.510 -
Volume (L) 1.06 0.555 1.05 – 1.07 -
Between Subject Variability
On Clearance 0.315 (32.3%) 5.99 0.278 – 0.352 9.55%
On Volume 0.536 (57.7%) 16.8 0.360 – 0.712 47.9%
Residual Error
Proportional Error (%) 5.08 3.60 4.73 – 5.44 -

CI=confidence interval; RSE=relative standard error.

Source: run001

Random effects and off-diagonal elements

There is some debate on the best way to present the random effects from a mixed-effects model (i.e., the parameter(s) that describe the (joint) distribution of the random effects, which are typically assumed to follow a multivariate normal distribution). Some modelers are accustomed to seeing the variances and covariances, while others (such as me) prefer the standard deviations and correlations. In the standard log-normal case, diagonal elements are often presented in the form of their (geometric) coefficient of variation, which can be derived from the standard deviation \(ω\) by \(\sqrt{e^{ω^2}-1}\), as is typically shown as a percentage. In any case, pmxpartab is agnostic to this choice, and gives freedom and flexibility in this respect.

In one version (call it the flat version), all estimates are at the top level of the components est, se and fixed. Here is an example:

outputs <- yaml.load("
est:
  nCL:     3.95926E-01
  nVC:     1.42749E+00 
  nCL_nVC: 8.45393E-02
se:
  nCL:     9.57069E-03
  nVC:     4.62152E-02 
  nCL_nVC: 4.26648E-02
")

meta <- yaml.load("
parameters:
- name:  'nCL'
  label: 'On CL'
  type:  IIV

- name:  'nVC'
  label: 'On Vc'
  type:  IIV

- name:  'nCL_nVC'
  label: 'Correlation CL-Vc'
  type:  IIV
")

outputs |> pmxparframe(meta) |> pmxpartab()
Parameter Estimate RSE% 95% CI
Between Subject Variability
On CL 0.396 2.42 0.377 – 0.415
On Vc 1.43 3.24 1.34 – 1.52
Correlation CL-Vc 0.0845 50.5 0.000916 – 0.168

In another version (call it the structured version), between-individual random effect parameters are contained in sub-components of est, se and fixed: - om contains the standard deviations as a named vector - om_cov contains the variance-covariance matrix - om_cor contains the correlation matrix, with standard deviations on the diagonal

Here is an example:

outputs <- list(
    est = list(
        om     = c(nCL=3.95926E-01, nVC=1.42749E+00),
        om_cov = matrix(c(1.56758E-01, 4.77799E-02, 4.77799E-02, 2.03772E+00), 2, 2),
        om_cor = matrix(c(3.95926E-01, 8.45393E-02, 8.45393E-02, 1.42749E+00), 2, 2)),
    se = list(
        om     = c(nCL=9.57069E-03, nVC=4.62152E-02),
        om_cov = matrix(c(7.57858E-03, 2.47183E-02, 2.47183E-02, 1.31943E-01), 2, 2),
        om_cor = matrix(c(9.57069E-03, 4.26648E-02, 4.26648E-02, 4.62152E-02), 2, 2)))

meta <- yaml.load("
parameters:
- name:  'om_cov(nCL,nCL)'
  label: 'Variance log(CL)'
  type:  IIV

- name:  'om_cov(nVC,nVC)'
  label: 'Variance log(Vc)'
  type:  IIV

- name:  'om_cor(nCL,nCL)'
  label: 'SD log(CL)'
  type:  IIV

- name:  'om_cor(nVC,nVC)'
  label: 'SD log(Vc)'
  type:  IIV

- name:  'om_cov(nCL,nVC)'
  label: 'Covariance log(CL)-log(Vc)'
  type:  IIV

- name:  'om_cor(nCL,nVC)'
  label: 'Correlation log(CL)-log(Vc)'
  type:  IIV
")

outputs |> pmxparframe(meta) |> pmxpartab()
Parameter Estimate RSE% 95% CI
Between Subject Variability
Variance log(CL) 0.157 4.83 0.142 – 0.172
Variance log(Vc) 2.04 6.48 1.78 – 2.30
SD log(CL) 0.396 2.42 0.377 – 0.415
SD log(Vc) 1.43 3.24 1.34 – 1.52
Covariance log(CL)-log(Vc) 0.0478 51.7 -0.000668 – 0.0962
Correlation log(CL)-log(Vc) 0.0845 50.5 0.000916 – 0.168

Bootstrap results

TBD