has_erglm <- requireNamespace("erglm", quietly = TRUE) knitr::opts_chunk$set( collapse = TRUE, comment = "#>", fig.width = 7, fig.height = 5, eval = has_erglm )
cat( "**Note:** the erglm package is not installed, so the code in this", "vignette was not evaluated. Install erglm to see the output for", "yourself: `install.packages(\"erglm\")`." )
An exposure-response plot shows how a response variable relates to a drug
exposure variable (Cmax, AUC, and so on). The usual
building blocks are a fitted curve with an uncertainty band, some
quantile-binned summary points to check the curve against the raw data,
the raw data itself, and maybe some side panels showing how exposure is
distributed across other variables of interest. erplots gives you a
small set of functions for assembling exactly that, layer by layer, in a
style that should feel familiar if you've used ggplot2's + to build up
a plot.
The one thing erplots deliberately does not do is fit models. You fit a model with whatever tool suits the job -- logistic regression, generalised linear models, non-linear least squares, or a package purpose-built for exposure-response modelling -- and then hand the fitted object to erplots. As long as that object can produce predictions in the way erplots expects (more on this at the end), the plotting code doesn't care what kind of model it is.
This vignette works through the pieces one at a time, using
erglm, a companion package that
fits simple exposure-response models and provides an example dataset,
erglm_data. You don't need erglm to use erplots on your own models,
but it's a convenient way to get a fitted model for these examples.
library(erplots) library(erglm)
To orient you to the synthetic dataset, we'll start by taking a look
at the structure of the erglm_data data frame.
head(erglm_data)
As you can see, erglm_data has exposure columns labelled aucss and
cmaxss, and response columns of different kinds: ae1 and ae2 are
binary variables showing whether a particular adverse event did or
did not occur, biomarker_change is a continuous response variable,
and ae_count provides a count response (the number of adverse
events of some kind). The dataset also provides covariate and design
variables such as sex and treatment that might be used for
grouping or stratifying the plot. We'll start with ae1.
mod <- erglm_model(ae1 ~ aucss, erglm_data, family = binomial())
Every erplots plot starts with er_plot(), which tells erplots which
column is the exposure and which is the response. On its own it doesn't
draw anything -- it's just bookkeeping, the same way ggplot(df, aes(x,
y)) doesn't draw anything until you add a geom:
erglm_data |> er_plot(exposure = aucss, response = ae1)
In fact, this particular code doesn't draw anything at all. One point of
difference between erplots and ggplot2 is that print() doesn't render
the plot: all it does is print out a summary of your plot specification.
If you want to render the plot, you need to explicitly add plot() to
the end of your pipeline:
erglm_data |> er_plot(exposure = aucss, response = ae1) |> plot()
As you can see, at this stage the output is little more than a blank canvas.
er_plot_add_model() draws the model's fitted curve, with a confidence
ribbon around it. This is where the fitted model you built earlier comes
in:
erglm_data |> er_plot(exposure = aucss, response = ae1) |> er_plot_add_model(mod) |> plot()
Fitted curves are easier to trust when you can check them against a
simple, model-free summary of the raw data. er_plot_add_quantiles()
splits the exposure range into bins of roughly equal size and, within
each bin, plots the observed response rate (or mean, for a continuous or
count response) with a confidence interval:
erglm_data |> er_plot(exposure = aucss, response = ae1) |> er_plot_add_model(mod) |> er_plot_add_quantiles() |> plot()
You can change the number of bins with the n_bins argument, e.g.
er_plot_add_quantiles(n_bins = 6).
er_plot_add_data() overlays the individual observations on top of the
curve. For a binary response the points are jittered vertically a
little, since the underlying values are exactly 0 or 1 and would
otherwise draw as two solid lines:
erglm_data |> er_plot(exposure = aucss, response = ae1) |> er_plot_add_model(mod) |> er_plot_add_quantiles() |> er_plot_add_data() |> plot()
er_plot_add_summary() places a small text annotation in the corner of
the plot that has the least data in it. The default draws the model's
headline p-value, if it has one:
erglm_data |> er_plot(exposure = aucss, response = ae1) |> er_plot_add_model(mod) |> er_plot_add_quantiles() |> er_plot_add_summary(model = mod) |> plot()
The layers above all describe the response. er_plot_add_groups()
instead shows how exposure itself is distributed, split by one or more
other variables -- useful for checking, say, whether exposure differs by
treatment arm. It adds a small boxplot panel below the main plot, and
unlike the other layers you can call it more than once to add several
panels side by side:
erglm_data |> er_plot(exposure = aucss, response = ae1) |> er_plot_add_model(mod) |> er_plot_add_quantiles() |> er_plot_add_groups(group_by = treatment) |> plot()
Every layer above is optional, and you can mix and match them in any combination. A fairly complete plot might look like this:
erglm_data |> er_plot(exposure = aucss, response = ae1) |> er_plot_add_model(mod) |> er_plot_add_quantiles() |> er_plot_add_data() |> er_plot_add_summary(model = mod) |> er_plot_add_groups(group_by = c(treatment, sex)) |> plot()
If you'd like to compare, say, two treatment arms on the same plot, pass
stratify_by to er_plot(). This adds a colour (and, where relevant,
fill) aesthetic to every layer, with one shared legend. Because the
curve now needs to differ between groups, the model you pass in should
include the stratification variable as a predictor:
mod_strat <- erglm_model(ae1 ~ aucss + sex, erglm_data, family = binomial()) erglm_data |> er_plot(exposure = aucss, response = ae1, stratify_by = sex) |> er_plot_add_model(mod_strat) |> er_plot_add_quantiles() |> er_plot_add_data() |> plot()
Everything above works the same way for a continuous response (e.g. a change from baseline in some biomarker) or a count response (e.g. the number of adverse events a patient experienced). erplots figures out which kind of response you have from the data, and adjusts the quantile summary accordingly -- a mean and interval instead of a rate:
mod_cont <- erglm_model(biomarker_change ~ aucss, erglm_data, family = gaussian()) erglm_data |> er_plot(exposure = aucss, response = biomarker_change) |> er_plot_add_model(mod_cont) |> er_plot_add_quantiles() |> er_plot_add_data() |> plot()
For a genuine count response, it's worth telling erplots explicitly via
response_type = "count", so that it uses an interval designed for
counts rather than treating it as an ordinary continuous measurement:
mod_count <- erglm_model(ae_count ~ aucss, erglm_data, family = poisson()) erglm_data |> er_plot(exposure = aucss, response = ae_count, response_type = "count") |> er_plot_add_model(mod_count) |> er_plot_add_quantiles() |> er_plot_add_data() |> plot()
Everything above changes what's drawn. er_plot_theme() changes how
it looks, without remapping any aesthetic: axis/legend labels, a plot
title, axis limits, the overall ggplot2 theme, a discrete colour/fill
palette for stratification, and more.
erglm_data |> er_plot(exposure = aucss, response = ae1, stratify_by = sex) |> er_plot_add_model(mod_strat) |> er_plot_add_quantiles() |> er_plot_add_data() |> er_plot_theme( xlab = "Steady-state AUC", theme_base = ggplot2::theme_minimal(), color_discrete = ggplot2::scale_colour_brewer(palette = "Dark2"), fill_discrete = ggplot2::scale_fill_brewer(palette = "Dark2") ) |> plot()
See Theming erplots
for every argument er_plot_theme() supports.
This vignette only shows the default look for each layer. Every layer also has one or more alternative styles you can switch to -- spaghetti plots instead of a ribbon, violin plots instead of boxplots, a density-style overlay for the raw data when you have a lot of points, and so on -- and you can write your own if none of the built-in options fit. The following articles, part of the package website at https://erplots.djnavarro.net/, cover this in more depth:
er_plot_theme() argument in detail -- labels, limits, the visual
theme, the stratification palette, formatters, and panel heights.Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.