Before starting, load the ggplot2
and ggcertara
packages, and set the default ggplot
theme to theme_certara()
:
The ggcertara
package and theme_certara
theme are meant to provide a standardized look to typical gof
plots employed by pharmacometricians. The syntax is based on ggplot2
. The ggcertara
also utilizes the patchwork
package and syntax. These will be touched upon later in this vignette.
The gof()
function uses standard column names in lower case. The following column names are recognized by the functions in this package:
Not all of these need to be present, only those that are needed for the disired plots (for instance, if no plots involving iwres
are requested, then it does not need to be present). The column names must be in lower case.
The gof_read_data()
function simplifies the data loading process by:
mdv
column and, if present, filtering the rows to keep only those with mdv = 0
For this vignette we will be using some sample data that is provided with the package. The data is contained in a file named nmtable.csv
, that was actually generated by NONMEM using a $TABLE
statement (i.e., $TABLE FILE=nmtable.csv FORMAT=,1PE15.8 ...
). It’s contents look like this:
ID | TIME | DV | PRED | IPRED | RES | WRES | CWRES | CIWRES | IRES | IWRES | MDV | EVID | TAD | CL | V | KA | ETA1 | ETA2 | ETA3 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
1 | 0.0000000 | 0.00000 | 0.00000 | 0.00000 | 0.0000000 | 0.0000000 | 0.0000000 | 0.0000000 | 0.0000000 | 0.0000000 | 1 | 1 | 0.0000000 | 3.311365 | 10.26801 | 2.860697 | -0.144312 | -0.1010664 | 0.0747987 |
1 | 0.1107401 | 16.84466 | 14.39002 | 16.98482 | 2.4546404 | 0.0413047 | 0.0306011 | -0.1043802 | -0.1401575 | -0.1038259 | 0 | 0 | 0.1107401 | 3.311365 | 10.26801 | 2.860697 | -0.144312 | -0.1010664 | 0.0747987 |
1 | 0.2493163 | 33.22216 | 26.62746 | 31.08233 | 6.5946926 | 1.2657456 | 1.1250718 | 0.8657021 | 2.1398227 | 0.8661927 | 0 | 0 | 0.2493163 | 3.311365 | 10.26801 | 2.860697 | -0.144312 | -0.1010664 | 0.0747987 |
1 | 0.4983335 | 43.48446 | 38.21232 | 43.90453 | 5.2721396 | 0.2099566 | 0.1983909 | -0.1206951 | -0.4200702 | -0.1203824 | 0 | 0 | 0.4983335 | 3.311365 | 10.26801 | 2.860697 | -0.144312 | -0.1010664 | 0.0747987 |
1 | 0.8483932 | 48.27355 | 42.64485 | 48.29827 | 5.6286966 | 0.3379642 | 0.3135719 | -0.0066225 | -0.0247160 | -0.0064387 | 0 | 0 | 0.8483932 | 3.311365 | 10.26801 | 2.860697 | -0.144312 | -0.1010664 | 0.0747987 |
1 | 2.1580771 | 32.07656 | 31.68700 | 35.66761 | 0.3895546 | -1.0379582 | -0.9256054 | -1.2668550 | -3.5910498 | -1.2667701 | 0 | 0 | 2.1580771 | 3.311365 | 10.26801 | 2.860697 | -0.144312 | -0.1010664 | 0.0747987 |
In a normal project workflow, the location of the appropriate file would be clear. For the vignette, the file can be located as follows:
Now we load the file:
Examining the data, we can see that the names are lower case and the rows have been filtered such that mdv = 0
:
#> id time dv pred ipred res wres cwres ciwres ires iwres mdv evid tad
#> 2 1 0.1107401 16.84466 14.39002 16.98482 2.4546404 0.04130471 0.0306011 -0.104380162 -0.14015753 -0.103825922 0 0 0.1107401
#> 3 1 0.2493163 33.22216 26.62746 31.08233 6.5946926 1.26574562 1.1250718 0.865702143 2.13982267 0.866192653 0 0 0.2493163
#> 4 1 0.4983335 43.48446 38.21232 43.90453 5.2721396 0.20995661 0.1983909 -0.120695079 -0.42007023 -0.120382378 0 0 0.4983335
#> 5 1 0.8483932 48.27355 42.64485 48.29827 5.6286966 0.33796419 0.3135719 -0.006622542 -0.02471603 -0.006438689 0 0 0.8483932
#> 6 1 2.1580771 32.07656 31.68700 35.66761 0.3895546 -1.03795820 -0.9256054 -1.266855000 -3.59104976 -1.266770110 0 0 2.1580771
#> 7 1 3.9157063 22.78889 17.64876 20.31905 5.1401243 2.03836621 1.8503260 1.529135290 2.46983532 1.529378070 0 0 3.9157063
#> cl v ka eta1 eta2 eta3
#> 2 3.311365 10.26801 2.860697 -0.144312 -0.1010664 0.07479871
#> 3 3.311365 10.26801 2.860697 -0.144312 -0.1010664 0.07479871
#> 4 3.311365 10.26801 2.860697 -0.144312 -0.1010664 0.07479871
#> 5 3.311365 10.26801 2.860697 -0.144312 -0.1010664 0.07479871
#> 6 3.311365 10.26801 2.860697 -0.144312 -0.1010664 0.07479871
#> 7 3.311365 10.26801 2.860697 -0.144312 -0.1010664 0.07479871
#>
#> 0
#> 831
The basic functionality is accessed through the gof()
function:
By default, the gof()
function produces a figure with 6 panels arranged in a 2-by-3 grid.
Column_1 | Column_2 | Column_3 |
---|---|---|
DV vs. IPRED | CWRES vs. TIME | CWRES vs. TAD |
DV vs. PRED | CWRES vs. PRED | |IWRES| vs. IPRED |
Note that all axes are nicely labelled (though they lack units at this point). With this simple function call, we have produced a report ready figure!
The gof()
function can easily produce a variety of panels which can be referenced as either numbered panels, named functions, or as strings.
Number | Function | Name | Description |
---|---|---|---|
1 | gof_cwres_histogram() | cwres_histogram | Histogram of CWRES |
2 | gof_cwres_qqplot() | cwres_qqplot | QQ-plot of CWRES |
3 | gof_dv_vs_ipred() | dv_vs_ipred | DV vs. IPRED |
4 | gof_dv_vs_pred() | dv_vs_pred | DV vs. PRED |
5 | gof_cwres_vs_pred() | cwres_vs_pred | CWRES vs. PRED |
6 | gof_cwres_vs_time() | cwres_vs_time | CWRES vs. TIME |
7 | gof_cwres_vs_tad() | cwres_vs_tad | CWRES vs. TAD |
8 | gof_absiwres_vs_ipred() | absiwres_vs_ipred | |IWRES| vs. IPRED |
9 | gof_absiwres_vs_time() | absiwres_vs_time | |IWRES| vs. TIME |
10 | gof_absiwres_vs_tad() | absiwres_vs_tad | |IWRES| vs. TAD |
11, -3 | gof_dv_vs_ipred(log_xy=T) | dv_vs_ipred_log | DV vs. IPRED log scale |
12, -4 | gof_dv_vs_pred(log_xy=T) | dv_vs_pred_log | DV vs. PRED log scale |
13, -5 | gof_cwres_vs_pred(log_x=T) | cwres_vs_pred_log | CWRES vs. PRED log scale |
14, -6 | gof_cwres_vs_time(log_x=T) | cwres_vs_time_log | CWRES vs. TIME log scale |
15, -7 | gof_cwres_vs_tad(log_x=T) | cwres_vs_tad_log | CWRES vs. TAD log scale |
16, -8 | gof_absiwres_vs_ipred(log_x=T) | absiwres_vs_ipred_log | |IWRES| vs. IPRED log scale |
17, -9 | gof_absiwres_vs_time(log_x=T) | absiwres_vs_time_log | |IWRES| vs. TIME log scale |
18, -10 | gof_absiwres_vs_tad(log_x=T) | absiwres_vs_tad_log | |IWRES| vs. TAD log scale |
Note that the standard 6 plots in the gof()
function correspond to the numbered panels 3, 6, 7, 4, 5, 8 from this list.
panels
option within gof()
Using the panels
argument, select panels can be selected using either the respective panel numbers or names (as strings). For example, to prduce both a histogram and QQ-plot of CWRES, select panels 1 and 2 or callthem by name cwres_histogram
and cwres_qqplot
:
For log-scale plots (panels 11-18 above), we can call their panel number or add a -
to their linear scale counterpart. For example, to obtain a plot of DV vs. IPRED on log-log scale, we can set panel=11
or panel=-3
:
We can use the layout
option to specify the number of rows and columns in a multi-panel output.
We can also call specific panels using the corresponding named function. Log variants of the plot can be obtained by specifying either log_x=T
or log_xy=T
depending on the function.
gof_list
objectIndividual panels can also be with the gof_list()
function. This returns a numbered list of the 6 standard panels by default, or all panels if all=T
is specified:
#> List of gof plots:
#>
#> 1. cwres_histogram
#> 2. cwres_qqplot
#> 3. dv_vs_ipred
#> 4. dv_vs_pred
#> 5. cwres_vs_pred
#> 6. cwres_vs_time
#> 7. cwres_vs_tad
#> 8. absiwres_vs_ipred
#> 9. absiwres_vs_time
#> 10. absiwres_vs_tad
#> 11. dv_vs_ipred_log
#> 12. dv_vs_pred_log
#> 13. cwres_vs_pred_log
#> 14. cwres_vs_time_log
#> 15. cwres_vs_tad_log
#> 16. absiwres_vs_ipred_log
#> 17. absiwres_vs_time_log
#> 18. absiwres_vs_tad_log
Individual panels can be extracted from the list:
gof_list
can also be used to build a new list of plots, by specifying empty=T
:
A composite plot can be obtained from a list using the gof_layout()
function:
There are several ways to customize the plot axis labels/units. When a recognized variable name is used in a plot (e.g. ipred
, cwres
, etc.), the functions will find a corresponding axis label in a list object which by default corresponds to a global option:
Option | Default |
---|---|
gof.label.dv | Observed Value |
gof.label.pred | Population Prediction |
gof.label.ipred | Individual Prediction |
gof.label.cwres | Conditional Weighted Residual |
gof.label.iwres | Individual Weighted Residual |
gof.label.time | Time |
gof.label.tad | Time After Dose |
gof.units.dv | NA |
gof.units.time | NA |
gof.units.tad | NA |
Every function has a labels
argument to override the default, should it be necessary. Also note when updating labels/units, it is not required to specify all labels/units, only the labels/units that are being updated need be specified.
We can update labels and units at the global level by calling the desired option within the options
function. Units for concentration (dv, pred, ipred), time, and tad can also be set using global options and will be appended to the label:
options(gof.label.cwres = 'CWRES',
gof.label.dv = 'MyDrug',
gof.units.dv="ng/mL",
gof.units.time="h",
gof.units.tad="h")
gof(dat)
We can update a default label for a single plot by calling the label
option as follows.
Finally, the usual labs()
function can be used to set or modify the labels of any plot. In this case, units do not get applied and must be explicitly included in the labels.
Now let’s restore our defaults before moving on…
# Restore defaults
options(gof.label.dv = "Observed Value",
gof.label.pred = "Population Prediction",
gof.label.ipred = "Individual Prediction",
gof.label.cwres = "Conditional Weighted Residual",
gof.label.iwres = "Individual Weighted Residual",
gof.label.time = "Time",
gof.label.tad = "Time After Dose",
gof.units.dv=NA,
gof.units.time=NA,
gof.units.tad=NA
)
To highlight outliers using a special symbol (and color), you first define a column in the dataset which is a factor with 2 levels (be mindful of the labels, as these will appear in the legend and also note the use of unicode language for special symbols). The sample data comes from a simulation, and doesn’t have any outliers, but for demonstration purposes we will pretend that |CWRES| > 2.5
is a criterion for defining outliers:
Then, use the highlight
option, like this:
This feature should be used carefully. Note that highlighting values also populates a legend at the bottom, left corner of the plot area. It works for multiple panels too:
Faceting can be applied with facet_wrap
or facet_grid
as with any ggplot
object. For this example, we will augment our data with some covariate information, specifically sex and study information. In the same folder as the dataset nmtable.csv
is a file pkpop.csv
that contains covariate information on PK population.
file <- system.file(package="ggcertara", "sample-data/pkpop.csv")
pkpop <- read.csv(file)
head(pkpop)
#> id sex age wt study
#> 1 1 1 59 65.4 1
#> 2 2 0 37 64.3 1
#> 3 3 1 38 58.3 1
#> 4 4 0 31 73.3 1
#> 5 5 0 40 109.3 1
#> 6 6 1 79 56.5 1
Note that there is one row for each id
in the dataset. We need to link this to our goodness-of-fit data by matching on the id
column. There are many ways to do this (merge()
, left_join()
, …), but a simple way is to use the basic match()
function:
We have now added sex and study to our dataset, but in order to get nice, informative labels, we should convert these to factor
:
dat$sex = factor(dat$sex, levels=0:1, labels=c('Female', 'Male'))
dat$study = factor(dat$study, levels=1:3, labels=c('Study A', 'Study B', 'Study C'))
Now, we can produce a plot of DV vs. IPRED faceted by sex as follows:
Changes that will affect all plots can be made with the update_geom_defaults()
function. For example, to change the alpha level used for points in all panels:
Note that a special custom geom, named geom_point_c()
, is defined and used in this package, so that other ggplot
code will not be affected.
For a single panel, you can just modify the ggplot
object. For example, let’s change the color and symbol of points in a DV vs. PRED plot to be different by study (which we already added to our dataset earlier):
update_geom_defaults("point_c", list(alpha=0.5, size=2.5)) # make points bigger and more opaque
gof(dat, panels=4) +
aes(color=study, shape=study) +
labs(color=NULL, shape=NULL) +
scale_color_certara()
For a patchwork
object (multiple panels), you can do the same thing by replacing the +
operator with a &
:
gof(dat, panels=3:4) &
aes(color=study, shape=study) &
labs(color=NULL, shape=NULL) &
scale_color_certara()
patchwork
is smart enough to produce a single legend for both panels (note: the theme_certara()
theme must be set globally, as it was at the beginning of this script, for this to display properly).
As with any ggplot object, geoms can be added. Remember to use the &
operator instead of +
when creating multiple panels (this notation comes from the patchwork
package).
For examples, we can add a dashed line with corresponding label showing the LLOQ to our DV vs. IPRED and PRED plots as follows:
gof(dat, panels = -(3:4)) &
geom_hline(yintercept=1.2, linetype="dashed") &
geom_text(data=data.frame(x=Inf, y=1.2), aes(x=x, y=y),
label="LLOQ = 1.2 ng/mL", hjust=1.1, vjust=-0.5, size=3)
As another example, we can add a blue linear regression line in addition to the red LOESS smooth to our panels:
#> R version 4.0.1 (2020-06-06)
#> Platform: x86_64-w64-mingw32/x64 (64-bit)
#> Running under: Windows 10 x64 (build 18363)
#>
#> Matrix products: default
#>
#> locale:
#> [1] LC_COLLATE=English_United States.1252 LC_CTYPE=English_United States.1252 LC_MONETARY=English_United States.1252
#> [4] LC_NUMERIC=C LC_TIME=English_United States.1252
#>
#> attached base packages:
#> [1] stats graphics grDevices utils datasets methods base
#>
#> other attached packages:
#> [1] kableExtra_1.1.0 ggplot2_3.3.2 rmarkdown_2.2 ggcertara_0.3 devtools_2.3.0 usethis_1.6.1 nvimcom_0.9-102
#>
#> loaded via a namespace (and not attached):
#> [1] Rcpp_1.0.4.6 lattice_0.20-41 prettyunits_1.1.1 ps_1.3.3 assertthat_0.2.1 rprojroot_1.3-2
#> [7] digest_0.6.25 R6_2.4.1 backports_1.1.7 evaluate_0.14 httr_1.4.1 highr_0.8
#> [13] pillar_1.4.4 rlang_0.4.6 rstudioapi_0.11 callr_3.4.3 Matrix_1.2-18 splines_4.0.1
#> [19] desc_1.2.0 labeling_0.3 webshot_0.5.2 readr_1.3.1 stringr_1.4.0 munsell_0.5.0
#> [25] compiler_4.0.1 xfun_0.14 pkgconfig_2.0.3 pkgbuild_1.0.8 mgcv_1.8-31 htmltools_0.5.0.9001
#> [31] tidyselect_1.1.0 tibble_3.0.1 roxygen2_7.1.1 fansi_0.4.1 viridisLite_0.3.0 crayon_1.3.4
#> [37] dplyr_1.0.0 withr_2.2.0 grid_4.0.1 nlme_3.1-148 gtable_0.3.0 lifecycle_0.2.0
#> [43] magrittr_1.5 scales_1.1.1 cli_2.0.2 stringi_1.4.6 farver_2.0.3 fs_1.4.1
#> [49] remotes_2.1.1 testthat_2.3.2 xml2_1.3.2 ellipsis_0.3.1 generics_0.0.2 vctrs_0.3.0
#> [55] tools_4.0.1 rcmdcheck_1.3.3 glue_1.4.1 purrr_0.3.4 hms_0.5.3 processx_3.4.2
#> [61] pkgload_1.1.0 yaml_2.2.1 colorspace_1.4-1 xopen_1.0.0 sessioninfo_1.1.1 rvest_0.3.5
#> [67] memoise_1.1.0 knitr_1.28 patchwork_1.0.1