The erplots package supplies a mini-language for generating exposure-response plots commonly used in pharmacometric analyses. It is designed to be model agnostic, in the sense that it will work for any modelling tool that implements a few key interface functions (see Implementing the model interface). It can support binary response data, continuous response data, and count response data. This article focuses on continuous data, using a linear regression model fitted using the erglm package. It covers the substantive aspects of plot construction, not the superficial features like labels, palettes, and visual theme. Those are covered by the theming erplots article, which applies unchanged regardless of response type.
Fit the model first
mod_gaussian <- erglm_model(biomarker_change ~ aucss, erglm_data, family = gaussian())er_plot() auto-detects whether a response is binary
(logical, or values entirely in {0, 1}) or continuous, and
you can override the detection with response_type.
biomarker_change auto-detects as
"continuous".
Defining plots
erglm_data |>
er_plot(aucss, biomarker_change) |>
er_plot_add_model(mod_gaussian) |>
er_plot_add_quantiles() |>
plot()
Stratification
Stratification adds colour across all layers, and requires a model
that includes the stratification variable as a term. See the binary responses article for
a fuller worked example, including how to suppress stratification for
specific layers with keep_strata = FALSE.
mod_gaussian_sex <- erglm_model(
biomarker_change ~ aucss + sex, erglm_data, family = gaussian()
)
erglm_data |>
er_plot(aucss, biomarker_change, stratify_by = sex) |>
er_plot_add_model(mod_gaussian_sex) |>
er_plot_add_quantiles() |>
er_plot_add_data() |>
plot()
Model layer
The model layer doesn’t look at response_type at all –
it only consumes er_predict()/er_simulate()
output – so it works exactly the same way as for a binary response. See
the binary responses article
for er_style_model_spaghetti() and the
parameter-uncertainty rationale behind it; the default builder is used
here:
erglm_data |>
er_plot(aucss, biomarker_change) |>
er_plot_add_model(mod_gaussian) |>
er_plot_add_quantiles() |>
plot()
Summary layer
The summary layer doesn’t look at response_type at all
either – it only consumes whatever the model’s own
er_summary() method returns – so it works exactly the same
way as for a binary response. See the binary responses article for
er_style_summary_gof() and the full four-builder set; the
default builder is used here:
erglm_data |>
er_plot(aucss, biomarker_change) |>
er_plot_add_model(mod_gaussian) |>
er_plot_add_summary(model = mod_gaussian) |>
plot()
Quantile layer
For a continuous response, each exposure-quantile bin is summarised by its mean with a t-interval, rather than a rate with a Clopper-Pearson interval:
erglm_data |>
er_plot(aucss, biomarker_change) |>
er_plot_add_model(mod_gaussian) |>
er_plot_add_quantiles(bins = 6, conf_level = .8) |>
plot()
The t-interval is a reasonable default for a genuinely continuous
response like this one. See the count
responses article for a case – low-count Poisson data – where the
t-interval approximation can misbehave (a negative lower bound), and the
response_type = "count" declaration that fixes it.
Data layer
er_plot_add_data() adds the raw observations at their
true (exposure, response) coordinates via
er_style_data_overlay(), the default and only built-in
builder for a continuous response – no jitter is needed, since the
response isn’t confined to 0/1:
erglm_data |>
er_plot(aucss, biomarker_change) |>
er_plot_add_model(mod_gaussian) |>
er_plot_add_data() |>
plot()
There’s no built-in panel-based alternative for a continuous response
– er_style_data_boxjitter() (the panel-based
responders/non-responders design covered in the binary
responses article) is binary-only. If you need a panel-based builder
here (e.g. a single colour-encoded panel), you can write a custom one
and tag it with er_style_tag(fn, layout = "panel") – see
the Extending erplots article.
Group layer
The group layer doesn’t look at response_type at all –
it only consumes the exposure variable – so it works exactly the same
way as for a binary response. See the binary responses article for
multiple grouping variables and er_style_group_violin();
the default builder and a single grouping variable are shown here:
erglm_data |>
er_plot(aucss, biomarker_change) |>
er_plot_add_model(mod_gaussian) |>
er_plot_add_quantiles() |>
er_plot_add_groups(group_by = aucss) |>
plot()
A second model package: emaxnls
Everything above uses erglm’s GLM-based models. To underline that
erplots genuinely doesn’t know or care which package fitted the model,
here’s the same pipeline driven by an Emax (sigmoidal dose-response)
model fitted with emaxnls,
which registers its own
er_predict()/er_simulate()/er_summary()
methods:
mod_emax <- emax_nls(
structural_model = rsp_1 ~ exp_1,
covariate_model = list(E0 ~ 1, Emax ~ 1, logEC50 ~ 1),
data = emax_df
)
emax_df |>
er_plot(exp_1, rsp_1) |>
er_plot_add_model(mod_emax) |>
er_plot_add_summary(model = mod_emax, style = er_style_summary_coefficients) |>
er_plot_add_quantiles() |>
er_plot_add_data() |>
plot()
er_style_summary_coefficients() is used here instead of
the default er_style_summary_pvalue() because an Emax model
has no single privileged coefficient to headline –
er_summary.emaxnls() reports p_value = NULL
and returns one row per parameter (E0, Emax,
logEC50) in coefficients instead.
emaxnls also implements the interface for
emax_logistic() models (binary responses). In R’s
object-oriented terms, an emaxlogistic object carries class
c("emaxlogistic", "emaxnls"), so it automatically reuses
the same
er_predict()/er_simulate()/er_summary()
methods written for a plain emaxnls object (a mechanism
called S3 inheritance), rather than emaxnls needing to write a second,
separate set of methods. Those shared methods branch internally to keep
predictions in [0, 1] and report
r_squared = NA (rather than a meaningless value) in
er_summary()’s glance whenever they’re handed
an emaxlogistic object specifically. None of this detail
matters for using emax_logistic() with erplots – fitting
one and passing it to er_plot_add_model() just works, the
same as any other model. See the binary
responses article for that response type in general.