nonmem2R goodness of fit plot functions

Introduction

Herein is a demo of functions for GOF plots in the nonmem2R R-package. The functions provide building blocks for doing GOF plots with flexibility for user specific formatting and or layout. There are also functions for combining single GOF’s into one and there are basic GOF’s combining a specific type of GOF’s into one single plot, e.g. the function basic.GOF4 which generated the example below.

## `geom_smooth()` using formula = 'y ~ x'
## `geom_smooth()` using formula = 'y ~ x'
## `geom_smooth()` using formula = 'y ~ x'
## Warning: The following aesthetics were dropped during statistical transformation: x and
## y.
## ℹ This can happen when ggplot fails to infer the correct grouping structure in
##   the data.
## ℹ Did you forget to specify a `group` aesthetic or to convert a numerical
##   variable into a factor?
## `geom_smooth()` using formula = 'y ~ x'
## `geom_smooth()` using formula = 'y ~ x'

The GOF functions build on ggplot2 and as far as possible return an ggplot-object that can be further modified by adding ggplot formatting. However, some functions use grid.arrange from the R-package gridExtra to combine different type of GOF’s. These functions plot the combined set of GOF’s but does not return an ggplot object, and hence the plots can therefore not be easily further modified/formatted. To still provide options to add ggplot formatting these functions have an argument global.ggplot.options which can be used to add extra ggplot functions on each individual GOF before the GOF’s are combined into one graph by grid.arrange.

Formatting of e.g. lines and plot symbols can also be controlled by the argument control and the function GOF.control or using set.GOF.params to affect all GOF’s.

Axis labels are set for most common NONMEM variables, e.g. CWRES is by default labelled “Cond. weighted res” and TIME is by default labelled “Time after first dose(h)”. See examples in section Labels and formatting options with further details on controling axis labels, changing the caption text and other formatting of the GOF plots.

All GOF functions will by default add a caption in the bottom of the graph to indicate date when figure was generated together with the full path to the script. However, this require that the script name has been set using set.script.name, and then the script name will be pasted together with the date and path as derived from getwd. If script name has not been set no caption will be added. Each GOF plot will have a red dashed reference line and blue solid lines are smoother’s representing the actual data.

All functions require a data.frame as input and thus NONMEM output tables, e.g, sdtab, cotab,.., should be loaded prior using the functions, and if using multiple output tables it’s recommenced to just cbind all of them to a single sdtab data.frame.

Functions

Tailored GOF functions

There are a set of specific and tailored GOF functions.

do.individual.GOF will provide GOF plots of PRED, IPRED and DV vs TIME (default) with one panel per subject This will generate one page/plot per 20 subjects (default) and therefore this function is best used when exporting to a PDF. The X axis is default set to TIME but e.g. time after last dose (TAPD) or any column in the input data.frame. Below is a small example from 6 subjects.

do.individual.GOF(subset(sdtab,DV>0 & ID<7 & TIME<50))

On top of basic.GOF4 with an example in the beginning of this document, there is also basic.GOF6 which adds 2 extra GOF plots showing GOF’s for residuals (CWRES) as histogram and as qqnorm plot. Except for number of bins in the histogram of CWRES both have the same options and parameters, e.g. the X-axis value for 1’st and 2’nd GOF on the bottom row is set by idv1 and idv2respectively. In the example below the defult (IPRED and TIME) is replaced by TAPD and PRED.

basic.GOF6(subset(sdtab,DV>0),idv1="TAPD",idv2="PRED")
## `geom_smooth()` using formula = 'y ~ x'
## `geom_smooth()` using formula = 'y ~ x'
## `geom_smooth()` using formula = 'y ~ x'
## Warning: `stat(density)` was deprecated in ggplot2 3.4.0.
## ℹ Please use `after_stat(density)` instead.
## ℹ The deprecated feature was likely used in the nonmem2R package.
##   Please report the issue to the authors.
## This warning is displayed once every 8 hours.
## Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
## generated.
## Warning: The following aesthetics were dropped during statistical transformation: x and
## y.
## ℹ This can happen when ggplot fails to infer the correct grouping structure in
##   the data.
## ℹ Did you forget to specify a `group` aesthetic or to convert a numerical
##   variable into a factor?
## `geom_smooth()` using formula = 'y ~ x'
## `geom_smooth()` using formula = 'y ~ x'

There are 4 tailored functions for doing GOF plots for ETA’s: basic.eta.GOF, eta.cov.GOF, eta.cat.GOF, and eta.pairs.GOF. These functions identifies all columns named ETA1,..,ETA9,ET10,.. in the input data.frame and provides different types of GOF plots for these columns. All 4 functions will by default exclude ETA’s which are constant i.e. when set as FIX in your nonmem model (use drop.fixed=FALSE to override) and each ETA is scaled to unit variance (use standardize=FALSE to override). All but the basic.eta.GOF function return an ggplot object and can be further modified as any ggplot object.

basic.eta.GOF will provide GOF plots showing histograms and densities combined with QQ-norm plots.

basic.eta.GOF(sdtab)

eta.cov.GOF will provide GOF plots showing x-y plots of ETA’s vs continuous covariates and eta.cat.GOF will provide box-plots of all ETA’s. Both function have an argument type default set to “all-in-one”. This will generate a ggplot which is formatted by facet_grid with all ETA’s in rows and all covariates in columns, as in the example below for eta.cov.GOF.

eta.cov.GOF(subset(sdtab,DV>0),covariates=c("AGE","BWT"))
## `geom_smooth()` using formula = 'y ~ x'

However it’s also possible to get either one page for each ETA (type="eta-by-page") or as in the example below for eta.cat.GOF with one page for each covariate (type="covariate-by-page").

eta.cat.GOF(subset(sdtab,DV>0),covariates=c("gender","eGFR"),type="covariate-by-page")

The last tailored function for doing GOF plots of ETA’s is eta.pairs.GOF with an example below. Default setting will show scatter plots below the diagonal, smooth density on the diagonal, and spearman correlations above the diagonal. A 2D density can also be added on either side of the diagonal. The example below is show the effect of setting labels for ETA using set.GOF.params

set.GOF.params(eta.labels=c("Ka","Vc","CL","F"))
eta.pairs.GOF(sdtab,density2D="lower")

As mentioned some of the functions don’t return and ggplot object and the graph can thus not be further modified by adding existing ggplot functions. The functions do however have a named argument global.ggplot.options which is added to each individual ggplot functions before combining. This functionality is limited to one single additional ggplot function, e.g. facet_grid, facet_wrap or different theme’s such as theme_bw.

In the example below basic.GOF4 is modified be setting global.ggplot.options to facet_wrap(~gender) to get one panel for each gender for each of the four GOF plots.

basic.GOF4(subset(sdtab,DV>0),idv2="TAPD",global.ggplot.options=facet_wrap(~gender))
## `geom_smooth()` using formula = 'y ~ x'
## `geom_smooth()` using formula = 'y ~ x'
## `geom_smooth()` using formula = 'y ~ x'
## Warning: The following aesthetics were dropped during statistical transformation: x and
## y.
## ℹ This can happen when ggplot fails to infer the correct grouping structure in
##   the data.
## ℹ Did you forget to specify a `group` aesthetic or to convert a numerical
##   variable into a factor?
## The following aesthetics were dropped during statistical transformation: x and
## y.
## ℹ This can happen when ggplot fails to infer the correct grouping structure in
##   the data.
## ℹ Did you forget to specify a `group` aesthetic or to convert a numerical
##   variable into a factor?
## `geom_smooth()` using formula = 'y ~ x'
## `geom_smooth()` using formula = 'y ~ x'

And in the example below basic.GOF6 is modified be setting global.ggplot.options to theme_classic()

basic.GOF6(subset(sdtab,DV>0),idv2="TAPD",global.ggplot.options=theme_classic())
## `geom_smooth()` using formula = 'y ~ x'
## `geom_smooth()` using formula = 'y ~ x'
## `geom_smooth()` using formula = 'y ~ x'
## Warning: The following aesthetics were dropped during statistical transformation: x and
## y.
## ℹ This can happen when ggplot fails to infer the correct grouping structure in
##   the data.
## ℹ Did you forget to specify a `group` aesthetic or to convert a numerical
##   variable into a factor?
## `geom_smooth()` using formula = 'y ~ x'
## `geom_smooth()` using formula = 'y ~ x'

Labels and formatting options

Labels

The function get.label is used to set good looking axis labels for the most common NONMEM variables, e.g. CWRES is by default labelled “Cond. weighted res”. The dictionary of labels is controlled by the function set.GOF.dictionary.
In the example below the label for TAD is changed to “Time since last dose(days)” and a new label “Time after first dose (days)” is added for the variable TIME.

set.GOF.dictionary(TAD="Time since last dose(days)")
set.GOF.dictionary(TIME="Time since first dose(days)")

Use get.GOF.dictionary to inspect the complete dictionary of labels and default.GOF.dictionary to reset all labels to the default. It is also possible to turn of functionality and instead have column names as is for axis labels by setting the global GOF parameter axis.labels to FALSE. See next section for how to change global GOF parameters.

get.GOF.dictionary()
## $DV
## [1] "Observations"
## 
## $PRED
## [1] "Population predictions"
## 
## $IPRED
## [1] "Individual predictions"
## 
## $CWRES
## [1] "Cond. weighted res"
## 
## $IWRES
## [1] "Ind. weighted res"
## 
## $WRES
## [1] "Weighted residual"
## 
## $RES
## [1] "Residual"
## 
## $TIME
## [1] "Time since first dose(days)"
## 
## $TAPD
## [1] "Time since last dose(h)"
## 
## $TAD
## [1] "Time since last dose(days)"
## 
## $TAFD
## [1] "Time after first dose(h)"

Formatting with set.GOF.params

The formatting of lines, plot symbols and caption text is controlled by the argument control with default values controlled by the current setting of GOF parameters. A default setting of parameters is set on loading nonmem2R BUT can be modified using set.GOF.params. For example to change plot symbols for one GOF use control=GOF.control(pch.data = 1) to get circles instead of the default which is points (pch=19). To change so that all GOF’s within a script or project get a different formatting, instead use set.GOF.params(pch.data = 1), see below example.

set.GOF.params(pch.data = 1)
do.one.GOF(subset(sdtab,DV>0),"PRED","DV")
## `geom_smooth()` using formula = 'y ~ x'

The format of the caption can be changed to have a different colour or font size either for only one GOF using the control argument, or for all GOF’s using the set.GOF.params. In the below example the formatting is changed to a larger font size, and the captions only displays a partial path including only 3 folders. (We change back to the default after using basic.GOF4)

set.GOF.params(caption.path.depth=3,size.caption = 14,pch.data = 19)
basic.GOF4(subset(sdtab,DV>0))
## `geom_smooth()` using formula = 'y ~ x'
## `geom_smooth()` using formula = 'y ~ x'
## `geom_smooth()` using formula = 'y ~ x'
## Warning: The following aesthetics were dropped during statistical transformation: x and
## y.
## ℹ This can happen when ggplot fails to infer the correct grouping structure in
##   the data.
## ℹ Did you forget to specify a `group` aesthetic or to convert a numerical
##   variable into a factor?
## `geom_smooth()` using formula = 'y ~ x'
## `geom_smooth()` using formula = 'y ~ x'

### Change back to default
set.GOF.params(caption.path.depth=99,size.caption =8)

The caption text is set by the function get.caption which also is a GOF parameter that can be modified like any other GOF parameter using set.GOF.params. The default caption as shown in all examples above is a one-row caption with date and full path to the script. In the below example the get.caption function is modified to instead put the path to current working directory, the scripts name, and the date on separate rows. If modifying the function for caption text please not that the get.caption GOF parameter must be just that; a function with no argument returning a character string.

set.GOF.params(
  get.caption=function(x){
      paste(getwd(),get.GOF.params()$script.name,date(),sep="\n")
      }
  )
do.one.GOF(subset(sdtab,DV>0),"PRED","DV")
## `geom_smooth()` using formula = 'y ~ x'

Note that there is no check that the new value set for a GOF parameter make sense. To list all GOF parameters use get.GOF.params and default.GOF.params to reset all GOF parameters to the default. See section Details in packages documention for get.GOF.params.

get.GOF.params()
## $col.data
## [1] "gray20"
## 
## $cex.data
## [1] 1.5
## 
## $pch.data
## [1] 19
## 
## $alpha.data
## [1] 0.5
## 
## $col.smooth
## [1] "#3366FF"
## 
## $lty.smooth
## [1] 1
## 
## $lwd.smooth
## [1] 1
## 
## $se.smooth
## [1] TRUE
## 
## $span.smooth
## [1] 0.6666667
## 
## $degree.smooth
## [1] 1
## 
## $family.smooth
## [1] "symmetric"
## 
## $col.refline
## [1] "red"
## 
## $lty.refline
## [1] 2
## 
## $lwd.refline
## [1] 1
## 
## $fill.hist
## [1] "gray30"
## 
## $col.hist
## [1] "transparent"
## 
## $alpha.hist
## [1] 0.5
## 
## $fill.box
## [1] "steelblue"
## 
## $col.box
## [1] "black"
## 
## $alpha.box
## [1] 0.6
## 
## $axis.labels
## [1] TRUE
## 
## $add.caption
## [1] TRUE
## 
## $size.caption
## [1] 8
## 
## $col.caption
## [1] 1
## 
## $caption.path.depth
## [1] 99
## 
## $get.caption
## function (x) 
## {
##     paste(getwd(), get.GOF.params()$script.name, date(), sep = "\n")
## }
## 
## $corr.fontface
## [1] 4
## 
## $eta.labels
## [1] "Ka" "Vc" "CL" "F" 
## 
## $script.name
## [1] "MyScript.R"

GOF building block’s

This set of functions serve as building blocks for creating your own favourite set of graphs. All return an ggplot object and the output can therefore be further modified.

do.one.GOF is a simple x-y plot where you specify variables as strings, a smoother is added by default (blue line) but can be omitted. A red ref-line at y~x (default), or horizontal (at median or mean) can also be added. There are also options for the smoother. In the below example observed data (DV) is plotted versus predictions (PRED) and the graph is stratified by gender by adding the ggplot2 function facet_wrap.

do.one.GOF(subset(sdtab,DV>0),x="PRED",y="DV")+facet_wrap(~gender)
## `geom_smooth()` using formula = 'y ~ x'

do.multi.GOF do multiple y-x plots where x is shared. A smoother is added by default, but reference lines are however omitted but can be added with similar options as for do.one.GOF. Adding additional ggplot functions can be done similar as for do.one.GOF e.g. the ggplot function theme_bw as in the below example. However, since the multi-panel layout is generated by the facet facet_wrap(~variable), if modifying or adding additional facet’s it’s important to keep separate panels by variable and the recommend option is to use facet_grid. For example, adding facet_grid(variable~gender) will provide a do.multi.GOF stratified by gender.

do.multi.GOF(subset(sdtab,DV>0),x="TAPD",y=c("DV","PRED","IPRED"))+theme_bw()
## `geom_smooth()` using formula = 'y ~ x'

histGOF and qqnormGOF provide GOF’s for residuals and ETA’s to check for assumption of normality. As for the other GOF’s red lines are reference lines, blue lines are smoother’s representing the actual data. For histGOF this is a smooth density whereas there is no smoother option for qqnormGOF. We demonstrate these functions by adding extra ggplot functions, in this case facet_wrap to stratify by time after dose into four categories.

sdtab$TAPDgr<-cut(sdtab$TAPD,breaks=c(0,0.5,2,10,25))
histGOF(subset(sdtab,DV>0),"CWRES")+facet_wrap(~TAPDgr,nrow=1)

qqnormGOF(subset(sdtab,DV>0),"CWRES")+facet_wrap(~TAPDgr,nrow=1)

eta.hist.GOF and eta.qqnorm.GOF are functions specific for ETA columns in NONMEM output tables. The functions identifies all columns named ETA1,..,ETA9,ET10,... and provide a GOF plot stratified by the ETA columns found. They provide similar type of graphs as histGOF and qqnormGOF. As for do.multi.GOF the function use facet_wrap(~variable). The recommend option to add or modify facet is to use facet_grid. For example, adding facet_grid(gender~variable) will provide a GOF of ETA’s stratified by gender.

eta.qqnorm.GOF(subset(sdtab,DV>0))+facet_grid(gender~variable)

GOF builders

Finally, there are also a set of functions that can be used to combine any type of ggplot objects and plot them in one single graph: merge2GOF, merge4GOF, and merge6GOF. They differ in how many graphs they can combine: 1x2, 2x2, and 2x3, otherwise they share the same set of arguments.

In the example below GOF building blocks and GOF builders are used to generate a graph similar to basic.GOF4.

p1<-do.one.GOF(subset(sdtab,DV>0),
               "PRED",
               "DV",
               control=GOF.control(add.caption=FALSE)) + labs(title="Re-building basic.GOF4")
p2<-do.one.GOF(subset(sdtab,DV>0),
               "IPRED",
               "DV",
               control=GOF.control(add.caption=FALSE))
sqrt.abs<-function(x){sqrt(abs(x))}
p3<-do.one.GOF(subset(sdtab,DV>0),
               "IPRED",
               "CWRES",
               refline="hrefmedian",
               fy=sqrt.abs,
               control=GOF.control(add.caption=FALSE)) + labs(y=a<-expression(sqrt('|Cond. weighted res.|')))
p4<-do.one.GOF(subset(sdtab,DV>0),
               "TAPD",
               "CWRES",
               refline="href0")
merge4GOF(p1,p2,p3,p4)
## `geom_smooth()` using formula = 'y ~ x'
## `geom_smooth()` using formula = 'y ~ x'
## `geom_smooth()` using formula = 'y ~ x'
## Warning: The following aesthetics were dropped during statistical transformation: x and
## y.
## ℹ This can happen when ggplot fails to infer the correct grouping structure in
##   the data.
## ℹ Did you forget to specify a `group` aesthetic or to convert a numerical
##   variable into a factor?
## `geom_smooth()` using formula = 'y ~ x'
## `geom_smooth()` using formula = 'y ~ x'