From 863ba0c3a313ab9cbfb2d38f8b7cd0794aec6f56 Mon Sep 17 00:00:00 2001 From: Wang Date: Mon, 2 Feb 2026 00:04:21 -0500 Subject: [PATCH 1/5] add new feature for toggle risk difference columns --- DESCRIPTION | 2 +- R/ae_forestly.R | 8 +++ R/format_ae_forestly.R | 8 +-- R/reactable2.R | 53 ++++++++++++++++--- man/ae_forestly.Rd | 3 ++ man/format_ae_forestly.Rd | 5 +- tests/testthat/test-ae_forestly.R | 31 +++++++++++ tests/testthat/test-format_ae_forestly.R | 18 +++---- ...lumns.Rmd => customize-toggle-buttons.Rmd} | 32 +++-------- 9 files changed, 106 insertions(+), 54 deletions(-) rename vignettes/{customize-ae-specific-columns.Rmd => customize-toggle-buttons.Rmd} (55%) diff --git a/DESCRIPTION b/DESCRIPTION index 3bf5d20..9a1cfd9 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -50,4 +50,4 @@ Suggests: VignetteBuilder: knitr Config/testthat/edition: 3 Roxygen: list(markdown = TRUE) -RoxygenNote: 7.3.2 +RoxygenNote: 7.3.3 diff --git a/R/ae_forestly.R b/R/ae_forestly.R index 884119a..5744ac2 100644 --- a/R/ae_forestly.R +++ b/R/ae_forestly.R @@ -20,6 +20,7 @@ #' #' @param outdata An `outdata` object created by [format_ae_forestly()]. #' @param display_soc_toggle A boolean value to display SOC toggle button. +#' @param display_diff_toggle A boolean value to display risk difference toggle button. #' @param filter A character value of the filter variable. #' @param filter_label A character value of the label for slider bar. #' @param filter_range A numeric vector of length 2 for the range of the slider bar. @@ -48,6 +49,7 @@ #' } ae_forestly <- function(outdata, display_soc_toggle = TRUE, + display_diff_toggle = FALSE, filter = c("prop", "n"), filter_label = NULL, filter_range = NULL, @@ -186,12 +188,18 @@ ae_forestly <- function(outdata, filter_subject$children[[2]]$attribs$`data-to` <- filter_range[2] filter_subject$children[[2]]$attribs$`data-max` <- filter_range[2] + diff_cols <- c( + names(outdata$diff) + ) + p_reactable <- reactable2( tbl, columns = outdata$reactable_columns, columnGroups = outdata$reactable_columns_group, hidden_item = paste0("'", outdata$hidden_column, "'", collapse = ", "), soc_toggle = display_soc_toggle, + diff_toggle = display_diff_toggle, + diff_columns = diff_cols, width = width, download = dowload_button, searchable = FALSE, diff --git a/R/format_ae_forestly.R b/R/format_ae_forestly.R index dc1fff9..70eaf2f 100644 --- a/R/format_ae_forestly.R +++ b/R/format_ae_forestly.R @@ -43,7 +43,6 @@ #' If NULL (default), uses "Risk Difference (%)
vs. Reference Group". #' @param fig_header Column header for risk difference figure. #' If NULL (default), uses "Risk Difference (%) + 95% CI
vs. Reference Group". -#' @param show_ae_parameter A boolean value to display AE parameter column. #' #' @return An `outdata` object. #' @@ -73,8 +72,7 @@ format_ae_forestly <- function( color = NULL, diff_label = "Treatment <- Favor -> Placebo", col_header = NULL, - fig_header = NULL, - show_ae_parameter = FALSE) { + fig_header = NULL) { display <- tolower(display) display <- match.arg( @@ -143,8 +141,6 @@ format_ae_forestly <- function( hide_n = apply(outdata$n[, 1:n_group], 1, max, na.rm = TRUE) ) - if (!show_ae_parameter) tbl <- tbl[, c(2:ncol(tbl), 1)] - rownames(tbl) <- NULL # JavaScript for plotly figures ---- @@ -262,7 +258,7 @@ format_ae_forestly <- function( col_var <- list( parameter = reactable::colDef( header = "Type", - show = show_ae_parameter + show = FALSE ), name = reactable::colDef( header = "Adverse Events", diff --git a/R/reactable2.R b/R/reactable2.R index dc03c38..f740558 100644 --- a/R/reactable2.R +++ b/R/reactable2.R @@ -48,7 +48,9 @@ #' @param label A logical value to display label as a hover text. #' @param download A logical value to display download button. #' @param soc_toggle A logical value to display SOC toggle button. +#' @param diff_toggle A logical value to display risk difference toggle button. #' @param hidden_item Vector for hidden columns. +#' @param diff_columns Character vector of risk difference column names. #' @param ... Additional arguments passed to [reactable::reactable()]. #' @inheritParams reactable::reactable #' @@ -76,7 +78,9 @@ reactable2 <- function( download = TRUE, col_def = NULL, soc_toggle = TRUE, + diff_toggle = FALSE, hidden_item = NULL, + diff_columns = NULL, ...) { # Display variable label as hover text if (label & is.null(col_def)) { @@ -110,25 +114,58 @@ reactable2 <- function( ... ) + buttons <- list() + + if (diff_toggle && !is.null(diff_columns) && length(diff_columns) > 0) { + diff_cols_js <- paste0("['", paste(diff_columns, collapse = "', '"), "']") + on_click_diff <- paste0( + "function control_diff(hidden_columns) {", + " const diffCols = ", diff_cols_js, ";", + " const allDiffHidden = diffCols.every(col => hidden_columns.includes(col));", + " if (allDiffHidden) {", + " Reactable.setHiddenColumns('", element_id, "', prevColumns => { + return prevColumns.filter(col => !diffCols.includes(col))})", + " } else {", + " Reactable.setHiddenColumns('", element_id, "', prevColumns => { + return [...new Set([...prevColumns, ...diffCols])]})", + " }", + "}", + "control_diff(Reactable.getState('", element_id, "').hiddenColumns);" + ) + + buttons <- c(buttons, list( + htmltools::tags$button( + "Show/Hide Risk Difference", + onclick = on_click_diff + ) + )) + } + if (soc_toggle) { - on_click2 <- paste0( - "function control_column(hidden_columns) {", + on_click_soc <- paste0( + "function control_soc(hidden_columns) {", " if (hidden_columns.includes('soc_name')) {", " Reactable.setHiddenColumns('", element_id, "', prevColumns => { - return prevColumns.length === 0 ? ['soc_name']:[", hidden_item, "]})", + return prevColumns.filter(col => col !== 'soc_name')})", " } else {", " Reactable.setHiddenColumns('", element_id, "', prevColumns => { - return prevColumns.length === 0 ? [ ]: ['soc_name',", hidden_item, "]})", + return [...prevColumns, 'soc_name']})", " }", "}", - "control_column(Reactable.getState('", element_id, "').hiddenColumns);" + "control_soc(Reactable.getState('", element_id, "').hiddenColumns);" ) - tbl <- htmltools::tagList( + buttons <- c(buttons, list( htmltools::tags$button( "Show/Hide SOC column", - onclick = on_click2 - ), + onclick = on_click_soc + ) + )) + } + + if (length(buttons) > 0) { + tbl <- htmltools::tagList( + buttons, tbl ) } diff --git a/man/ae_forestly.Rd b/man/ae_forestly.Rd index b9b07e4..7cfc129 100644 --- a/man/ae_forestly.Rd +++ b/man/ae_forestly.Rd @@ -7,6 +7,7 @@ ae_forestly( outdata, display_soc_toggle = TRUE, + display_diff_toggle = FALSE, filter = c("prop", "n"), filter_label = NULL, filter_range = NULL, @@ -21,6 +22,8 @@ ae_forestly( \item{display_soc_toggle}{A boolean value to display SOC toggle button.} +\item{display_diff_toggle}{A boolean value to display risk difference toggle button.} + \item{filter}{A character value of the filter variable.} \item{filter_label}{A character value of the label for slider bar.} diff --git a/man/format_ae_forestly.Rd b/man/format_ae_forestly.Rd index 77850ba..c51c47d 100644 --- a/man/format_ae_forestly.Rd +++ b/man/format_ae_forestly.Rd @@ -19,8 +19,7 @@ format_ae_forestly( color = NULL, diff_label = "Treatment <- Favor -> Placebo", col_header = NULL, - fig_header = NULL, - show_ae_parameter = FALSE + fig_header = NULL ) } \arguments{ @@ -65,8 +64,6 @@ If NULL (default), uses "Risk Difference (\%) \if{html}{\out{
}} vs. Referenc \item{fig_header}{Column header for risk difference figure. If NULL (default), uses "Risk Difference (\%) + 95\% CI \if{html}{\out{
}} vs. Reference Group".} - -\item{show_ae_parameter}{A boolean value to display AE parameter column.} } \value{ An \code{outdata} object. diff --git a/tests/testthat/test-ae_forestly.R b/tests/testthat/test-ae_forestly.R index 6e279f8..bbd5444 100644 --- a/tests/testthat/test-ae_forestly.R +++ b/tests/testthat/test-ae_forestly.R @@ -17,3 +17,34 @@ test_that("ae_forestly(): test filter and width option", { expect_true(grepl("width:1500px", html$children[[1]], fixed = TRUE)) expect_true(grepl("Number of AE in One or More Treatment Groups", html$children[[1]], fixed = TRUE)) }) + +test_that("ae_forestly(): toggle risk difference button is hidden by default", { + outdata <- metalite.ae::meta_ae_example() |> + prepare_ae_forestly( + population = "apat", + observation = "wk12", + parameter = "any;rel;ser" + ) |> + format_ae_forestly(display = c("n", "prop", "fig_prop", "fig_diff", "diff")) + + html <- outdata |> ae_forestly(display_diff_toggle = FALSE) + html_text <- as.character(html) + + expect_false(grepl("Show/Hide Risk Difference", html_text, fixed = TRUE)) +}) + +test_that("ae_forestly(): toggle risk difference button can be enabled", { + outdata <- metalite.ae::meta_ae_example() |> + prepare_ae_forestly( + population = "apat", + observation = "wk12", + parameter = "any;rel;ser" + ) |> + format_ae_forestly(display = c("n", "prop", "fig_prop", "fig_diff", "diff")) + + html <- outdata |> ae_forestly(display_diff_toggle = TRUE) + html_text <- as.character(html) + + expect_true(grepl("Show/Hide Risk Difference", html_text, fixed = TRUE)) + expect_true(grepl("control_diff", html_text, fixed = TRUE)) +}) diff --git a/tests/testthat/test-format_ae_forestly.R b/tests/testthat/test-format_ae_forestly.R index c1df0de..9f768d1 100644 --- a/tests/testthat/test-format_ae_forestly.R +++ b/tests/testthat/test-format_ae_forestly.R @@ -11,8 +11,7 @@ test_that("Set `display` to ('n', 'prop', 'diff') then one has an additional ris width_diff = 80, footer_space = 90, color = NULL, - diff_label = "Treatment <- Favor -> Placebo", - show_ae_parameter = FALSE + diff_label = "Treatment <- Favor -> Placebo" ) # expect_named(ae_frm, c("n", "prop", "diff")) @@ -35,8 +34,7 @@ test_that("Set `display` to ('n', 'prop', 'total') then one has total column", { width_diff = 80, footer_space = 90, color = NULL, - diff_label = "Treatment <- Favor -> Placebo", - show_ae_parameter = FALSE + diff_label = "Treatment <- Favor -> Placebo" ) # expect_named(ae_frm, c("n", "prop", "total")) @@ -59,8 +57,7 @@ test_that("Set `display` to ('diff', 'total') without ('n', 'prop') columns", { width_diff = 80, footer_space = 90, color = NULL, - diff_label = "Treatment <- Favor -> Placebo", - show_ae_parameter = FALSE + diff_label = "Treatment <- Favor -> Placebo" ) # expect_named(ae_frm, c("n", "prop", "total")) @@ -83,8 +80,7 @@ test_that("1. Set `display` to ('n', 'prop', 'total', 'diff') and change column width_diff = 80, footer_space = 90, color = NULL, - diff_label = "MK-XXXX <- Favor -> Placebo", - show_ae_parameter = FALSE + diff_label = "MK-XXXX <- Favor -> Placebo" ) expect_equal(ae_frm$reactable_columns$diff_fig$width, 300) @@ -96,7 +92,7 @@ test_that("1. Set `display` to ('n', 'prop', 'total', 'diff') and change column expect_equal(ae_frm$reactable_columns$prop_4$minWidth, 60) }) -test_that("Set `show` to TRUE then display column 'Type' and change color for tratment group", { +test_that("Parameter column is always hidden", { out <- test_format_ae_forestly() ae_frm <- format_ae_forestly( out, @@ -109,11 +105,11 @@ test_that("Set `show` to TRUE then display column 'Type' and change color for tr width_diff = 80, footer_space = 90, color = c("BLACK", "BLUE", "YELLOW", "PINK"), - diff_label = "Treatment <- Favor -> Placebo", - show_ae_parameter = TRUE + diff_label = "Treatment <- Favor -> Placebo" ) expect_equal(ae_frm$reactable_columns$parameter$header, "Type") + expect_equal(ae_frm$reactable_columns$parameter$show, FALSE) }) test_that("Add variable name not in n, prop, total, diff causes error", { diff --git a/vignettes/customize-ae-specific-columns.Rmd b/vignettes/customize-toggle-buttons.Rmd similarity index 55% rename from vignettes/customize-ae-specific-columns.Rmd rename to vignettes/customize-toggle-buttons.Rmd index 1f03c5c..91bd640 100644 --- a/vignettes/customize-ae-specific-columns.Rmd +++ b/vignettes/customize-toggle-buttons.Rmd @@ -1,5 +1,5 @@ --- -title: "Add/Hide Columns in the AE-Specific Tables" +title: "Toggle Risk Difference Columns in the AE-Specific Tables" authors: "Yujie Zhao" output: rmarkdown::html_document: @@ -7,7 +7,7 @@ output: number_sections: yes code_folding: hide vignette: | - %\VignetteIndexEntry{Add/Hide Columns in the AE-Specific Tables} + %\VignetteIndexEntry{Toggle Risk Difference Columns in the AE-Specific Tables} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- @@ -26,14 +26,7 @@ library(forestly) library(metalite) ``` -The interactive AE forest plots include AE-specific tables that present both numerical and visual summaries of the following: - -- The number of subjects experiencing AEs in each arm -- The percentage of subjects with AEs in each arm -- Differences in AE proportions between arms -- Confidence intervals for these AE proportion differences - -In this vignette, we guide users through customizing (show/hide) the columns displayed in the AE-specific table. +The interactive AE forest plots include AE-specific tables that present both numerical and visual summaries for each AE SOC/PT term. In this vignette, we demonstrate how to toggle the visibility of risk difference columns using an interactive button. # Step 1: build your metadata @@ -84,24 +77,15 @@ meta <- meta_adam(population = adsl, observation = adae) |> ``` -# Step 2: customize the AE specific columns - -Users can tailor the AE-specific columns by specifying the `display = ...` argument within the `format_ae_forestly()` function. The available options are as follows: - -- `"n"`: Displays a column with the number of subjects experiencing AEs. -- `"prop"`: Displays a column with the proportion of subjects experiencing AEs. -- `"diff"`: Displays a column showing the difference in AE proportions between arms. -- `"fig_prop"`: Displays a column visualizing AE proportions. -- `"fig_diff"`: Displays a column visualizing differences in AE proportions between arms. -- `"total"`: Displays a column reporting pooled results from both control and experimental arms. +# Step 2: toggle risk difference columns -By default, `display = c("n", "prop", "fig_prop", "fig_diff")` is used, which includes: (1) the number of subjects with AEs in each arm, (2) the AE proportion in each arm, (3) a visualization of AE proportions, (4) a visualization of AE proportion differences. +Users can control the display of risk difference columns using the `display_diff_toggle = ...` argument in the `ae_forestly()` function. -In the example below, we demonstrate how to add a total column alongside the default display settings. +In the example below, we enable the risk difference toggle button by setting `display_diff_toggle = TRUE`. This adds an interactive "Show/Hide Risk Difference" button above the table, allowing users to dynamically show or hide all risk difference related columns, including the difference values (`diff`) and the risk difference visualization (`fig_diff`). ```{r} meta |> prepare_ae_forestly() |> - format_ae_forestly(display = c("n", "prop", "fig_prop", "fig_diff", "total")) |> - ae_forestly() + format_ae_forestly() |> + ae_forestly(display_diff_toggle = TRUE) ``` From 26791fb7d98c5f6e1b84f0685f85f57fb278c9db Mon Sep 17 00:00:00 2001 From: Wang Date: Tue, 3 Feb 2026 21:59:01 -0500 Subject: [PATCH 2/5] update based on comments --- R/ae_forestly.R | 9 ++++-- R/reactable2.R | 44 ++++++++++++++------------ _pkgdown.yml | 1 + vignettes/customize-toggle-buttons.Rmd | 6 ++-- 4 files changed, 35 insertions(+), 25 deletions(-) diff --git a/R/ae_forestly.R b/R/ae_forestly.R index 5744ac2..6748285 100644 --- a/R/ae_forestly.R +++ b/R/ae_forestly.R @@ -192,14 +192,19 @@ ae_forestly <- function(outdata, names(outdata$diff) ) + hidden_cols <- outdata$hidden_column + if (display_diff_toggle) { + hidden_cols <- setdiff(hidden_cols, c(diff_cols, "diff_fig")) + } + p_reactable <- reactable2( tbl, columns = outdata$reactable_columns, columnGroups = outdata$reactable_columns_group, - hidden_item = paste0("'", outdata$hidden_column, "'", collapse = ", "), + hidden_item = paste0("'", hidden_cols, "'", collapse = ", "), soc_toggle = display_soc_toggle, diff_toggle = display_diff_toggle, - diff_columns = diff_cols, + diff_columns = c(diff_cols, "diff_fig"), width = width, download = dowload_button, searchable = FALSE, diff --git a/R/reactable2.R b/R/reactable2.R index f740558..cda204c 100644 --- a/R/reactable2.R +++ b/R/reactable2.R @@ -116,53 +116,55 @@ reactable2 <- function( buttons <- list() - if (diff_toggle && !is.null(diff_columns) && length(diff_columns) > 0) { - diff_cols_js <- paste0("['", paste(diff_columns, collapse = "', '"), "']") - on_click_diff <- paste0( - "function control_diff(hidden_columns) {", - " const diffCols = ", diff_cols_js, ";", - " const allDiffHidden = diffCols.every(col => hidden_columns.includes(col));", - " if (allDiffHidden) {", + if (soc_toggle) { + on_click_soc <- paste0( + "function control_soc(hidden_columns) {", + " if (hidden_columns.includes('soc_name')) {", " Reactable.setHiddenColumns('", element_id, "', prevColumns => { - return prevColumns.filter(col => !diffCols.includes(col))})", + return prevColumns.filter(col => col !== 'soc_name')})", " } else {", " Reactable.setHiddenColumns('", element_id, "', prevColumns => { - return [...new Set([...prevColumns, ...diffCols])]})", + return [...prevColumns, 'soc_name']})", " }", "}", - "control_diff(Reactable.getState('", element_id, "').hiddenColumns);" + "control_soc(Reactable.getState('", element_id, "').hiddenColumns);" ) buttons <- c(buttons, list( htmltools::tags$button( - "Show/Hide Risk Difference", - onclick = on_click_diff + "Show/Hide SOC column", + onclick = on_click_soc ) )) } - if (soc_toggle) { - on_click_soc <- paste0( - "function control_soc(hidden_columns) {", - " if (hidden_columns.includes('soc_name')) {", + if (diff_toggle && !is.null(diff_columns) && length(diff_columns) > 0) { + diff_cols_js <- paste0("['", paste(diff_columns, collapse = "', '"), "']") + on_click_diff <- paste0( + "function control_diff(hidden_columns) {", + " const diffCols = ", diff_cols_js, ";", + " const allDiffHidden = diffCols.every(col => hidden_columns.includes(col));", + " if (allDiffHidden) {", " Reactable.setHiddenColumns('", element_id, "', prevColumns => { - return prevColumns.filter(col => col !== 'soc_name')})", + return prevColumns.filter(col => !diffCols.includes(col))})", " } else {", " Reactable.setHiddenColumns('", element_id, "', prevColumns => { - return [...prevColumns, 'soc_name']})", + return [...new Set([...prevColumns, ...diffCols])]})", " }", "}", - "control_soc(Reactable.getState('", element_id, "').hiddenColumns);" + "control_diff(Reactable.getState('", element_id, "').hiddenColumns);" ) buttons <- c(buttons, list( htmltools::tags$button( - "Show/Hide SOC column", - onclick = on_click_soc + "Show/Hide Risk Difference Value", + onclick = on_click_diff ) )) } + + if (length(buttons) > 0) { tbl <- htmltools::tagList( buttons, diff --git a/_pkgdown.yml b/_pkgdown.yml index e615f6d..9dc8eab 100644 --- a/_pkgdown.yml +++ b/_pkgdown.yml @@ -37,6 +37,7 @@ articles: - customize-color - customize-ae-specific-columns - customize-diff-label + - customize-toggle-buttons - customize-xlimit - customize-width - customize-display-only-soc diff --git a/vignettes/customize-toggle-buttons.Rmd b/vignettes/customize-toggle-buttons.Rmd index 91bd640..1fd84b4 100644 --- a/vignettes/customize-toggle-buttons.Rmd +++ b/vignettes/customize-toggle-buttons.Rmd @@ -22,7 +22,8 @@ knitr::opts_chunk$set( ``` ```{r} -library(forestly) +# library(forestly) +devtools::load_all() library(metalite) ``` @@ -87,5 +88,6 @@ In the example below, we enable the risk difference toggle button by setting `di meta |> prepare_ae_forestly() |> format_ae_forestly() |> - ae_forestly(display_diff_toggle = TRUE) + ae_forestly(display_diff_toggle = TRUE, + display_soc_toggle = FALSE) ``` From f16b9c70648588a9718293e558da4cc1200bb922 Mon Sep 17 00:00:00 2001 From: "Bingjun(Benjamin) Wang" <85646030+wangben718@users.noreply.github.com> Date: Wed, 4 Feb 2026 21:47:32 -0500 Subject: [PATCH 3/5] Update forestly library usage and toggle options --- vignettes/customize-toggle-buttons.Rmd | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/vignettes/customize-toggle-buttons.Rmd b/vignettes/customize-toggle-buttons.Rmd index 1fd84b4..2975e95 100644 --- a/vignettes/customize-toggle-buttons.Rmd +++ b/vignettes/customize-toggle-buttons.Rmd @@ -22,8 +22,7 @@ knitr::opts_chunk$set( ``` ```{r} -# library(forestly) -devtools::load_all() +library(forestly) library(metalite) ``` @@ -87,7 +86,7 @@ In the example below, we enable the risk difference toggle button by setting `di ```{r} meta |> prepare_ae_forestly() |> - format_ae_forestly() |> + format_ae_forestly(display = c("n", "prop", "fig_prop", "fig_diff", "diff")) |> ae_forestly(display_diff_toggle = TRUE, - display_soc_toggle = FALSE) + display_soc_toggle = TRUE) ``` From e31bc31c18163ca5103750016fbe9de68d84f961 Mon Sep 17 00:00:00 2001 From: Wang Date: Wed, 4 Feb 2026 22:04:33 -0500 Subject: [PATCH 4/5] add back --- vignettes/customize-ae-specific-columns.Rmd | 107 ++++++++++++++++++++ 1 file changed, 107 insertions(+) create mode 100644 vignettes/customize-ae-specific-columns.Rmd diff --git a/vignettes/customize-ae-specific-columns.Rmd b/vignettes/customize-ae-specific-columns.Rmd new file mode 100644 index 0000000..3922cbb --- /dev/null +++ b/vignettes/customize-ae-specific-columns.Rmd @@ -0,0 +1,107 @@ +--- +title: "Add/Hide Columns in the AE-Specific Tables" +authors: "Yujie Zhao" +output: + rmarkdown::html_document: + self_contained: no + number_sections: yes + code_folding: hide +vignette: | + %\VignetteIndexEntry{Add/Hide Columns in the AE-Specific Tables} + %\VignetteEngine{knitr::rmarkdown} + %\VignetteEncoding{UTF-8} +--- + +```{r, include = FALSE} +knitr::opts_chunk$set( + collapse = TRUE, + comment = "#>", + message = FALSE, + warning = FALSE +) +``` + +```{r} +library(forestly) +library(metalite) +``` + +The interactive AE forest plots include AE-specific tables that present both numerical and visual summaries of the following: + +- The number of subjects experiencing AEs in each arm +- The percentage of subjects with AEs in each arm +- Differences in AE proportions between arms +- Confidence intervals for these AE proportion differences + +In this vignette, we guide users through customizing (show/hide) the columns displayed in the AE-specific table. + +# Step 1: build your metadata + +Building interactive AE forest plots starts with constructing the metadata. The detailed procedure for building metadata is covered in the vignette [Generate Interactive AE Forest Plots with Drill Down to AE Listing](https://merck.github.io/forestly/articles/forestly.html). Therefore, in this vignette, we will skip those details and directly use the metadata created there. + +```{r} +adsl <- forestly_adsl +adae <- forestly_adae + +adsl$TRTA <- factor(forestly_adsl$TRT01A, + levels = c("Xanomeline Low Dose", "Placebo"), + labels = c("Low Dose", "Placebo") +) +adae$TRTA <- factor(forestly_adae$TRTA, + levels = c("Xanomeline Low Dose", "Placebo"), + labels = c("Low Dose", "Placebo") +) + +meta <- meta_adam(population = adsl, observation = adae) |> + define_plan(plan = plan( + analysis = "ae_forestly", + population = "apat", + observation = "apat", + parameter = "any;drug-related" + )) |> + define_analysis(name = "ae_forestly", label = "Interactive Forest Plot") |> + define_population( + name = "apat", group = "TRTA", id = "USUBJID", + subset = SAFFL == "Y", label = "All Patient as Treated" + ) |> + define_observation( + name = "apat", group = "TRTA", + subset = SAFFL == "Y", label = "All Patient as Treated" + ) |> + define_parameter( + name = "any", + subset = NULL, + label = "Any AEs", + var = "AEDECOD", soc = "AEBODSYS" + ) |> + define_parameter( + name = "drug-related", + subset = toupper(AREL) == "RELATED", + label = "Drug-related AEs", + var = "AEDECOD", soc = "AEBODSYS" + ) |> + meta_build() +``` + + +# Step 2: customize the AE specific columns + +Users can tailor the AE-specific columns by specifying the `display = ...` argument within the `format_ae_forestly()` function. The available options are as follows: + +- `"n"`: Displays a column with the number of subjects experiencing AEs. +- `"prop"`: Displays a column with the proportion of subjects experiencing AEs. +- `"diff"`: Displays a column showing the difference in AE proportions between arms. +- `"fig_prop"`: Displays a column visualizing AE proportions. +- `"fig_diff"`: Displays a column visualizing differences in AE proportions between arms. +- `"total"`: Displays a column reporting pooled results from both control and experimental arms. + +By default, `display = c("n", "prop", "fig_prop", "fig_diff")` is used, which includes: (1) the number of subjects with AEs in each arm, (2) the AE proportion in each arm, (3) a visualization of AE proportions, (4) a visualization of AE proportion differences. + +In the example below, we demonstrate how to add a total column alongside the default display settings. + +```{r} +meta |> + prepare_ae_forestly() |> + format_ae_forestly(display = c("n", "prop", "fig_prop", "fig_diff", "total")) |> + ae_forestly() +``` \ No newline at end of file From 29adf76795e2a286933d50370155cc747c7f0909 Mon Sep 17 00:00:00 2001 From: Wang Date: Tue, 10 Feb 2026 15:29:05 -0500 Subject: [PATCH 5/5] update column header and toggle button --- R/ae_forestly.R | 10 ++++++++-- R/format_ae_forestly.R | 34 +++++++++++++++++++++++----------- R/prepare_ae_forestly.R | 1 + R/reactable2.R | 2 +- man/format_ae_forestly.Rd | 13 +++++++++---- 5 files changed, 42 insertions(+), 18 deletions(-) diff --git a/R/ae_forestly.R b/R/ae_forestly.R index 6748285..f08ac0a 100644 --- a/R/ae_forestly.R +++ b/R/ae_forestly.R @@ -192,9 +192,15 @@ ae_forestly <- function(outdata, names(outdata$diff) ) + all_diff_cols <- c(diff_cols, "diff_fig") + displayed_diff_cols <- intersect(all_diff_cols, c( + if ("diff" %in% outdata$display) diff_cols else NULL, + if ("fig_diff" %in% outdata$display) "diff_fig" else NULL + )) + hidden_cols <- outdata$hidden_column if (display_diff_toggle) { - hidden_cols <- setdiff(hidden_cols, c(diff_cols, "diff_fig")) + hidden_cols <- setdiff(hidden_cols, displayed_diff_cols) } p_reactable <- reactable2( @@ -204,7 +210,7 @@ ae_forestly <- function(outdata, hidden_item = paste0("'", hidden_cols, "'", collapse = ", "), soc_toggle = display_soc_toggle, diff_toggle = display_diff_toggle, - diff_columns = c(diff_cols, "diff_fig"), + diff_columns = displayed_diff_cols, width = width, download = dowload_button, searchable = FALSE, diff --git a/R/format_ae_forestly.R b/R/format_ae_forestly.R index 70eaf2f..fb7d332 100644 --- a/R/format_ae_forestly.R +++ b/R/format_ae_forestly.R @@ -38,10 +38,13 @@ #' for risk difference figure. #' @param color A vector of colors for analysis groups. #' Default value supports up to 4 groups. +#' @param ae_col_header Column header for adverse events item columns. +#' If NULL (default) and "par" specified in `components` from `prepare_ae_forestly()`, uses "Adverse Event". +#' If NULL and "soc" specified in `components` from `prepare_ae_forestly()`, uses "System Organ Class" for "soc". #' @param diff_label x-axis label for risk difference. -#' @param col_header Column header for risk difference table columns. +#' @param diff_col_header Column header for risk difference table columns. #' If NULL (default), uses "Risk Difference (%)
vs. Reference Group". -#' @param fig_header Column header for risk difference figure. +#' @param diff_fig_header Column header for risk difference figure. #' If NULL (default), uses "Risk Difference (%) + 95% CI
vs. Reference Group". #' #' @return An `outdata` object. @@ -70,9 +73,10 @@ format_ae_forestly <- function( prop_range = NULL, diff_range = NULL, color = NULL, + ae_col_header = NULL, diff_label = "Treatment <- Favor -> Placebo", - col_header = NULL, - fig_header = NULL) { + diff_col_header = NULL, + diff_fig_header = NULL) { display <- tolower(display) display <- match.arg( @@ -103,12 +107,20 @@ format_ae_forestly <- function( reference_name <- outdata$group[index_reference] # Set default headers if not provided - if (is.null(col_header)) { - col_header <- paste0("Risk Difference (%)
vs. ", reference_name) + if (is.null(ae_col_header)) { + if ("par" %in% outdata$components) { + ae_col_header <- "Adverse Event" + } else if ("soc" %in% outdata$components) { + ae_col_header <- "System Organ Class" + } + } + + if (is.null(diff_col_header)) { + diff_col_header <- paste0("Risk Difference (%)
vs. ", reference_name) } - if (is.null(fig_header)) { - fig_header <- paste0("Risk Difference (%) + 95% CI
vs. ", reference_name) + if (is.null(diff_fig_header)) { + diff_fig_header <- paste0("Risk Difference (%) + 95% CI
vs. ", reference_name) } # Input checking @@ -247,7 +259,7 @@ format_ae_forestly <- function( ) } columnGroups[[m_group + 1]] <- reactable::colGroup( - name = col_header, + name = diff_col_header, html = TRUE, columns = names(outdata$diff) ) @@ -261,7 +273,7 @@ format_ae_forestly <- function( show = FALSE ), name = reactable::colDef( - header = "Adverse Events", + header = ae_col_header, minWidth = width_term, align = "right" ), soc_name = reactable::colDef( @@ -338,7 +350,7 @@ format_ae_forestly <- function( # difference format col_diff_fig <- list(diff_fig = reactable::colDef( - header = fig_header, + header = diff_fig_header, defaultSortOrder = "desc", width = ifelse("fig_diff" %in% display, width_fig, 0), align = "center", diff --git a/R/prepare_ae_forestly.R b/R/prepare_ae_forestly.R index 6b40968..6aac709 100644 --- a/R/prepare_ae_forestly.R +++ b/R/prepare_ae_forestly.R @@ -170,6 +170,7 @@ prepare_ae_forestly <- function( order = info$order, parameter_order = parameter_order, group = res[[1]]$group, + components = components, reference_group = res[[1]]$reference_group, prop = values$prop, diff = values$diff, diff --git a/R/reactable2.R b/R/reactable2.R index cda204c..9664e1e 100644 --- a/R/reactable2.R +++ b/R/reactable2.R @@ -157,7 +157,7 @@ reactable2 <- function( buttons <- c(buttons, list( htmltools::tags$button( - "Show/Hide Risk Difference Value", + "Show/Hide Risk Difference", onclick = on_click_diff ) )) diff --git a/man/format_ae_forestly.Rd b/man/format_ae_forestly.Rd index c51c47d..a97832e 100644 --- a/man/format_ae_forestly.Rd +++ b/man/format_ae_forestly.Rd @@ -17,9 +17,10 @@ format_ae_forestly( prop_range = NULL, diff_range = NULL, color = NULL, + ae_col_header = NULL, diff_label = "Treatment <- Favor -> Placebo", - col_header = NULL, - fig_header = NULL + diff_col_header = NULL, + diff_fig_header = NULL ) } \arguments{ @@ -57,12 +58,16 @@ for risk difference figure.} \item{color}{A vector of colors for analysis groups. Default value supports up to 4 groups.} +\item{ae_col_header}{Column header for adverse events item columns. +If NULL (default) and "par" specified in \code{components} from \code{prepare_ae_forestly()}, uses "Adverse Event". +If NULL and "soc" specified in \code{components} from \code{prepare_ae_forestly()}, uses "System Organ Class" for "soc".} + \item{diff_label}{x-axis label for risk difference.} -\item{col_header}{Column header for risk difference table columns. +\item{diff_col_header}{Column header for risk difference table columns. If NULL (default), uses "Risk Difference (\%) \if{html}{\out{
}} vs. Reference Group".} -\item{fig_header}{Column header for risk difference figure. +\item{diff_fig_header}{Column header for risk difference figure. If NULL (default), uses "Risk Difference (\%) + 95\% CI \if{html}{\out{
}} vs. Reference Group".} } \value{