Skip to content

Commit d8910ee

Browse files
authored
Merge pull request #18 from Genentech/17-customizing-er-plots-changing-plot-titles-or-point-shapes
Add return_components option to plot_er() and plot_er_gof() for plot …
2 parents b4d400b + c852c2b commit d8910ee

File tree

11 files changed

+416
-6
lines changed

11 files changed

+416
-6
lines changed

.lintr

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,5 +14,8 @@ exclusions: list(
1414
"vignettes/basic_workflow_bin.Rmd" = list(
1515
1, # global exclusions are unnamed
1616
line_length_linter = 100:200
17+
),
18+
"R/loo_kfold.R" = list(
19+
object_name_linter = Inf
1720
)
1821
)

DESCRIPTION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
Package: BayesERtools
22
Type: Package
33
Title: Bayesian Exposure-Response Analysis Tools
4-
Version: 0.2.4.9000
4+
Version: 0.2.4.9001
55
Authors@R:
66
c(person("Kenta", "Yoshida", , "yoshida.kenta.6@gmail.com", role = c("aut", "cre"),
77
comment = c(ORCID = "0000-0003-4967-3831")),

NAMESPACE

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ S3method(plot_coveff,ermod)
3838
S3method(plot_er,ermod)
3939
S3method(plot_er,ersim)
4040
S3method(plot_er,ersim_med_qi)
41+
S3method(print,er_plot_components)
4142
S3method(print,ermod)
4243
S3method(print,ermod_cov_sel)
4344
S3method(print,ermod_exp_sel)
@@ -55,6 +56,7 @@ export(as_draws_rvars)
5556
export(build_spec_coveff)
5657
export(build_spec_coveff_one_variable)
5758
export(calc_ersim_med_qi)
59+
export(combine_er_components)
5860
export(dev_ermod_bin)
5961
export(dev_ermod_bin_cov_sel)
6062
export(dev_ermod_bin_emax)

NEWS.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@
44

55
## Minor changes
66

7+
* Allow manual breaks in `plot_er()` to control the position of the probability
8+
summary breaks for binary models
9+
* Added `return_components` option to `plot_er()` and `plot_er_gof()` that
10+
returns individual plot components (main plot, boxplot, caption) for
11+
customization before recombining with `combine_er_components()`
12+
713
# BayesERtools 0.2.4
814

915
## Major changes

R/plot_ermod.R

Lines changed: 209 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@
2929
#' - `qi_width`: Width of the quantile interval (confidence interval) for
3030
#' the observed probability summary. Only relevant for binary models.
3131
#' Default is `0.95`.
32+
#' - `return_components`: Logical, whether to return plot components as a
33+
#' list instead of a combined plot. When `TRUE`, returns a list with
34+
#' `$main` (main plot), `$boxplot` (boxplot if applicable), `$caption`
35+
#' (caption text if applicable), and `$metadata`. Default is `FALSE`.
3236
#' @param options_coef_exp List of options for configuring how the exposure
3337
#' coefficient credible interval is displayed. Possible options include:
3438
#' - `qi_width`: Width of the quantile interval (credible interval) for
@@ -99,7 +103,8 @@ plot_er.ersim_med_qi <- function(
99103
list(
100104
add_boxplot = FALSE, boxplot_height = 0.15,
101105
show_boxplot_y_title = TRUE, var_group = NULL,
102-
n_bins = 4, bin_breaks = NULL, qi_width = 0.95
106+
n_bins = 4, bin_breaks = NULL, qi_width = 0.95,
107+
return_components = FALSE
103108
),
104109
options_orig_data
105110
)
@@ -122,6 +127,7 @@ plot_er.ersim_med_qi <- function(
122127
var_exposure <- extract_var_exposure(x)
123128
add_boxplot <- options_orig_data$add_boxplot
124129
boxplot_height <- options_orig_data$boxplot_height
130+
return_components <- options_orig_data$return_components
125131

126132
# Check input
127133
check_plot_er_input(x, show_orig_data)
@@ -176,6 +182,94 @@ plot_er.ersim_med_qi <- function(
176182
)
177183
}
178184

185+
# Return components if requested --------------------------------------------
186+
if (return_components) {
187+
# Build boxplot if needed
188+
gg_boxplot <- NULL
189+
if (add_boxplot) {
190+
var_group <- options_orig_data$var_group
191+
192+
if (is.null(var_group)) {
193+
gg_boxplot <-
194+
ggplot2::ggplot(data = origdata, ggplot2::aes(
195+
x = .data[[var_exposure]],
196+
y = 0
197+
)) +
198+
ggplot2::geom_boxplot(alpha = 0.5) +
199+
ggplot2::scale_y_continuous(expand = ggplot2::expansion(mult = 0.2)) +
200+
ggplot2::theme(
201+
panel.grid.major.y = ggplot2::element_blank(),
202+
panel.grid.minor.y = ggplot2::element_blank(),
203+
axis.ticks.y = ggplot2::element_blank(),
204+
axis.title.y = ggplot2::element_blank(),
205+
axis.text.y = ggplot2::element_blank()
206+
)
207+
} else {
208+
# Define the numeric variable name with a leading dot
209+
var_group_num <- paste0(".", var_group, "_num")
210+
211+
# Convert the categorical variable to a numeric index
212+
origdata[[var_group_num]] <- as.numeric(factor(origdata[[var_group]]))
213+
214+
gg_boxplot <-
215+
ggplot2::ggplot(data = origdata, ggplot2::aes(
216+
x = .data[[var_exposure]],
217+
y = .data[[var_group_num]]
218+
)) +
219+
ggplot2::geom_boxplot(
220+
ggplot2::aes(
221+
fill = .data[[var_group]],
222+
color = .data[[var_group]]
223+
),
224+
alpha = 0.5
225+
) +
226+
ggplot2::scale_y_continuous(
227+
name = var_group,
228+
breaks = unique(origdata[[var_group_num]]),
229+
labels = levels(factor(origdata[[var_group]])),
230+
expand = ggplot2::expansion(mult = 0.2)
231+
) +
232+
ggplot2::theme(
233+
panel.grid.minor.y = ggplot2::element_blank()
234+
)
235+
if (!options_orig_data$show_boxplot_y_title) {
236+
gg_boxplot <- gg_boxplot +
237+
ggplot2::theme(axis.title.y = ggplot2::element_blank())
238+
}
239+
}
240+
241+
gg_boxplot <- gg_boxplot +
242+
ggplot2::theme(
243+
legend.position = "none",
244+
plot.margin = ggplot2::margin(t = 0)
245+
)
246+
}
247+
248+
# Build caption if needed
249+
caption <- NULL
250+
if (show_caption) {
251+
caption <- .build_caption(
252+
x, show_orig_data, show_coef_exp,
253+
options_orig_data, options_coef_exp, options_caption
254+
)
255+
}
256+
257+
# Create components list
258+
components <- list(
259+
main = gg,
260+
boxplot = gg_boxplot,
261+
caption = caption
262+
)
263+
attr(components, "metadata") <- list(
264+
boxplot_height = boxplot_height,
265+
var_exposure = var_exposure,
266+
var_resp = extract_var_resp(x),
267+
endpoint_type = attr(x, "endpoint_type")
268+
)
269+
class(components) <- c("er_plot_components", "list")
270+
return(components)
271+
}
272+
179273
# Add boxplot ---------------------------------------------------------------
180274
if (add_boxplot) {
181275
rlang::check_installed("patchwork")
@@ -587,6 +681,12 @@ plot_er.ermod <- function(
587681
#' draws. Default is `0.95`.
588682
#' @param show_caption Logical, whether to show the caption note for the
589683
#' plot. Default is `TRUE`.
684+
#' @param return_components Logical, whether to return plot components as a
685+
#' list instead of a combined plot. When `TRUE`, returns a list with
686+
#' `$main` (main plot), `$boxplot` (boxplot if applicable), `$caption`
687+
#' (caption text if applicable), and `$metadata`. This allows users to
688+
#' customize individual plot components before combining them. Default is
689+
#' `FALSE`.
590690
#'
591691
#' @details
592692
#' The following code will generate the same plot:
@@ -613,6 +713,10 @@ plot_er.ermod <- function(
613713
#' )
614714
#' }
615715
#'
716+
#' To customize plot elements (titles, shapes, themes), use
717+
#' `return_components = TRUE` to get individual plot components, modify them,
718+
#' then recombine with `combine_er_components()`.
719+
#'
616720
#' @return A ggplot object
617721
#' @examplesIf BayesERtools:::.if_run_ex_plot_er()
618722
#' \donttest{
@@ -627,6 +731,13 @@ plot_er.ermod <- function(
627731
#' plot_er_gof(ermod_bin, var_group = "Dose_mg", show_coef_exp = TRUE) *
628732
#' # Use log10 scale for exposure, need to use `*` instead of `+`
629733
#' xgxr::xgx_scale_x_log10(guide = ggplot2::guide_axis(minor.ticks = TRUE))
734+
#'
735+
#' # Customize plot components
736+
#' comps <- plot_er_gof(ermod_bin, var_group = "Dose_mg", return_components = TRUE)
737+
#' comps$main <- comps$main +
738+
#' ggplot2::labs(title = "Exposure-Response Analysis", x = "AUC (ng*h/mL)") +
739+
#' ggplot2::theme_bw()
740+
#' combine_er_components(comps)
630741
#' }
631742
#'
632743
plot_er_gof <- function(
@@ -643,7 +754,8 @@ plot_er_gof <- function(
643754
coef_size = 4,
644755
qi_width_coef = 0.95,
645756
qi_width_sim = 0.95,
646-
show_caption = TRUE) {
757+
show_caption = TRUE,
758+
return_components = FALSE) {
647759
plot_er(
648760
x,
649761
show_orig_data = TRUE,
@@ -656,7 +768,8 @@ plot_er_gof <- function(
656768
var_group = var_group,
657769
n_bins = n_bins,
658770
bin_breaks = bin_breaks,
659-
qi_width = qi_width_obs
771+
qi_width = qi_width_obs,
772+
return_components = return_components
660773
),
661774
options_coef_exp = list(
662775
qi_width = qi_width_coef,
@@ -762,3 +875,96 @@ set_pos_ci_annot <- function(x, pos_x, pos_y, var_exposure, var_resp) {
762875
.if_run_ex_plot_er <- function() {
763876
requireNamespace("xgxr", quietly = TRUE)
764877
}
878+
879+
#' Combine ER plot components
880+
#'
881+
#' @param components An object of class `er_plot_components` returned by
882+
#' `plot_er()` or `plot_er_gof()` with `return_components = TRUE` or
883+
#' `options_orig_data = list(return_components = TRUE)`.
884+
#' @param heights Numeric vector of length 2 specifying the relative heights
885+
#' of the main plot and boxplot. If `NULL` (default), uses the height from
886+
#' the metadata (based on `boxplot_height` parameter).
887+
#' @param add_caption Logical, whether to add the caption to the combined plot.
888+
#' Default is `TRUE` if caption is available.
889+
#' @param ... Additional arguments passed to `patchwork::wrap_plots()`.
890+
#'
891+
#' @return A combined ggplot object.
892+
#' @export
893+
#'
894+
#' @examples
895+
#' \dontrun{
896+
#' # Get components
897+
#' comps <- plot_er_gof(ermod_bin, return_components = TRUE)
898+
#'
899+
#' # Modify components
900+
#' comps$main <- comps$main + labs(title = "Custom Title")
901+
#'
902+
#' # Recombine
903+
#' combine_er_components(comps)
904+
#' }
905+
combine_er_components <- function(components, heights = NULL,
906+
add_caption = !is.null(components$caption),
907+
...) {
908+
if (!inherits(components, "er_plot_components")) {
909+
stop(
910+
"Input must be an object of class 'er_plot_components' ",
911+
"returned by plot_er() or plot_er_gof() with return_components = TRUE."
912+
)
913+
}
914+
915+
gg <- components$main
916+
917+
# Add boxplot if present
918+
if (!is.null(components$boxplot)) {
919+
rlang::check_installed("patchwork")
920+
921+
# Set default heights if not provided
922+
if (is.null(heights)) {
923+
boxplot_height <- attr(components, "metadata")$boxplot_height
924+
heights <- c(1 - boxplot_height, boxplot_height)
925+
}
926+
927+
# Adjust margins for combination
928+
gg <- gg +
929+
ggplot2::theme(plot.margin = ggplot2::margin(b = 0))
930+
931+
gg <-
932+
patchwork::wrap_plots(
933+
list(gg, components$boxplot),
934+
nrow = 2, heights = heights, ...
935+
) +
936+
patchwork::plot_layout(axes = "collect", guides = "collect")
937+
}
938+
939+
# Add caption if requested
940+
if (add_caption && !is.null(components$caption)) {
941+
gg <- gg +
942+
ggplot2::labs(caption = components$caption) +
943+
ggplot2::theme(
944+
plot.caption = ggplot2::element_text(family = "mono", hjust = 0)
945+
)
946+
}
947+
948+
return(gg)
949+
}
950+
951+
#' @export
952+
print.er_plot_components <- function(x, ...) {
953+
cat("ER plot components (class: er_plot_components)\n")
954+
cat("Components:\n")
955+
cat(" $main : Main ER plot\n")
956+
if (!is.null(x$boxplot)) {
957+
cat(" $boxplot : Exposure boxplot\n")
958+
}
959+
if (!is.null(x$caption)) {
960+
cat(" $caption : Caption text\n")
961+
}
962+
cat("\nMetadata:\n")
963+
metadata <- attr(x, "metadata")
964+
for (name in names(metadata)) {
965+
cat(sprintf(" %s: %s\n", name, paste(metadata[[name]], collapse = ", ")))
966+
}
967+
cat("\nUse combine_er_components() to recombine after customization.\n")
968+
cat("Or access individual components for manual combination.\n")
969+
invisible(x)
970+
}

_pkgdown.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ reference:
4242
- contents:
4343
- plot_er
4444
- plot_er_gof
45+
- combine_er_components
4546
- plot_er_exp_sel
4647
- title: 'Step 4: Model evaluation'
4748
- contents:

man/BayesERtools-package.Rd

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

man/combine_er_components.Rd

Lines changed: 45 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

man/plot_er.Rd

Lines changed: 4 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)