From 336c20c9b59bee0ea1c197c70173359a0c9a4118 Mon Sep 17 00:00:00 2001 From: Wojtek Date: Tue, 28 Jul 2026 10:01:49 +0200 Subject: [PATCH 1/2] Add exclude_rows arg to g_forest() (#1498). --- NEWS.md | 2 ++ R/g_forest.R | 28 +++++++++++++++---- man/g_forest.Rd | 13 ++++++++- tests/testthat/test-g_forest.R | 49 ++++++++++++++++++++++++++++++++++ 4 files changed, 86 insertions(+), 6 deletions(-) diff --git a/NEWS.md b/NEWS.md index 2515e19451..a9f4c7a6ab 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,6 +1,8 @@ # tern 0.9.11.9000 ### Enhancements +* Added the `exclude_rows` argument to `g_forest()` to allow excluding selected + rows from the forest plot before plotting. (#1498) * Added `factor_level_method` argument to `df_explicit_na()` to control factor level ordering when converting character or logical columns. Supported methods: `"sort_auto"` (default, locale-aware, preserves original behavior), `"sort_radix"` (byte-order / ASCII sort), and diff --git a/R/g_forest.R b/R/g_forest.R index 0b3ad34729..d69cc48ae3 100644 --- a/R/g_forest.R +++ b/R/g_forest.R @@ -47,6 +47,14 @@ #' is no longer used. #' @param newpage `r lifecycle::badge("deprecated")` `g_forest` is now generated as a `ggplot` object. This argument #' is no longer used. +#' @param exclude_rows (`integerish` or `NULL`)\cr vector of positive row +#' indices specifying rows to exclude from the forest plot. Row indices are +#' specified relative to [rtables::as_result_df()] applied to `tbl`. Values +#' must be between 1 and the number of rows in the result data frame, with no +#' missing values. The specified rows are removed before plotting. This can be +#' used to omit rows that should not be displayed in the forest plot, such as +#' rows containing non-plottable values. Defaults to `NULL`, meaning that all +#' rows are considered for plotting. #' #' @return `ggplot` forest plot and table. #' @@ -77,6 +85,7 @@ #' tbl <- basic_table() |> #' tabulate_rsp_subgroups(df) #' g_forest(tbl) +#' g_forest(tbl, exclude_rows = 1) #' #' # Odds ratio only table. #' @@ -177,7 +186,8 @@ g_forest <- function(tbl, as_list = FALSE, gp = lifecycle::deprecated(), draw = lifecycle::deprecated(), - newpage = lifecycle::deprecated()) { + newpage = lifecycle::deprecated(), + exclude_rows = NULL) { # Deprecated argument warnings if (lifecycle::is_present(width_row_names)) { lifecycle::deprecate_warn( @@ -220,6 +230,10 @@ g_forest <- function(tbl, checkmate::assert_number(font_size, lower = 0) checkmate::assert_character(col, null.ok = TRUE) checkmate::assert_true(is.null(col) | length(col) == 1 | length(col) == nrow(tbl)) + checkmate::assert_integerish( + exclude_rows, + lower = 1L, upper = nrow(as_result_df(tbl)), any.missing = FALSE, min.len = 1L, null.ok = TRUE + ) # Extract info from table mat <- matrix_form(tbl, indent_rownames = TRUE) @@ -241,6 +255,9 @@ g_forest <- function(tbl, } tbl_df <- as_result_df(tbl) + if (!is.null(exclude_rows)) { + tbl_df <- tbl_df[-exclude_rows, ] + } dat_cols <- seq(which(names(tbl_df) == "node_class") + 1, ncol(tbl_df)) tbl_df <- tbl_df[, c(which(names(tbl_df) == "row_num"), dat_cols)] names(tbl_df) <- c("row_num", tbl_stats) @@ -252,7 +269,9 @@ g_forest <- function(tbl, tbl_df[["empty_ci"]] <- rep(list(c(NA_real_, NA_real_)), nrow(tbl_df)) ci_col <- which(names(tbl_df) == "empty_ci") } - if (length(tbl_df[, ci_col][[1]]) != 2) stop("CI column must have two elements (lower and upper limits).") + if (nrow(tbl_df) >= 1 && length(tbl_df[, ci_col][[1]]) != 2) { + stop("CI column must have two elements (lower and upper limits).") + } if (!is.null(col_x)) { x_col <- col_x + 1 @@ -278,17 +297,16 @@ g_forest <- function(tbl, x_labels <- x_at # Apply log transformation - if (logx) { + if (logx && nrow(tbl_df) >= 1) { x_t <- log(x) lwr_t <- log(lwr) upr_t <- log(upr) - xlim_t <- log(xlim) } else { x_t <- x lwr_t <- lwr upr_t <- upr - xlim_t <- xlim } + xlim_t <- if (logx) log(xlim) else xlim # Set up plot area gg_plt <- ggplot(data = tbl_df) + diff --git a/man/g_forest.Rd b/man/g_forest.Rd index 9cd3fbfd54..228a7bcd3e 100644 --- a/man/g_forest.Rd +++ b/man/g_forest.Rd @@ -25,7 +25,8 @@ g_forest( as_list = FALSE, gp = lifecycle::deprecated(), draw = lifecycle::deprecated(), - newpage = lifecycle::deprecated() + newpage = lifecycle::deprecated(), + exclude_rows = NULL ) } \arguments{ @@ -91,6 +92,15 @@ is no longer used.} \item{newpage}{\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#deprecated}{\figure{lifecycle-deprecated.svg}{options: alt='[Deprecated]'}}}{\strong{[Deprecated]}} \code{g_forest} is now generated as a \code{ggplot} object. This argument is no longer used.} + +\item{exclude_rows}{(\code{integerish} or \code{NULL})\cr vector of positive row +indices specifying rows to exclude from the forest plot. Row indices are +specified relative to \code{\link[rtables:as_result_df]{rtables::as_result_df()}} applied to \code{tbl}. Values +must be between 1 and the number of rows in the result data frame, with no +missing values. The specified rows are removed before plotting. This can be +used to omit rows that should not be displayed in the forest plot, such as +rows containing non-plottable values. Defaults to \code{NULL}, meaning that all +rows are considered for plotting.} } \value{ \code{ggplot} forest plot and table. @@ -130,6 +140,7 @@ df <- extract_rsp_subgroups( tbl <- basic_table() |> tabulate_rsp_subgroups(df) g_forest(tbl) +g_forest(tbl, exclude_rows = 1) # Odds ratio only table. diff --git a/tests/testthat/test-g_forest.R b/tests/testthat/test-g_forest.R index 5e72c26d85..731f1a32d7 100644 --- a/tests/testthat/test-g_forest.R +++ b/tests/testthat/test-g_forest.R @@ -99,6 +99,55 @@ testthat::test_that("g_forest as_list argument works", { expect_snapshot_ggplot("g_forest_plot_only", g_forest_plot_only, width = 2, height = 3) }) +testthat::test_that("g_forest validates exclude_rows", { + tbl <- basic_table() |> + tabulate_rsp_subgroups(df) + + testthat::expect_error( + g_forest(tbl, exclude_rows = 0) + ) + + testthat::expect_error( + g_forest(tbl, exclude_rows = -1) + ) + + testthat::expect_error( + g_forest(tbl, exclude_rows = NA_integer_) + ) + + testthat::expect_error( + g_forest(tbl, exclude_rows = nrow(as_result_df(tbl)) + 1) + ) + + testthat::expect_error( + g_forest(tbl, exclude_rows = "1") + ) +}) + +testthat::test_that("g_forest exclude_rows works", { + tbl <- basic_table() |> + tabulate_rsp_subgroups(df) + + testthat::expect_silent( + p <- g_forest(tbl, exclude_rows = c(2, 4)) + ) + + expect_snapshot_ggplot("g_forest_exclude_rows", p, width = 15, height = 3) +}) + +testthat::test_that("g_forest works when all rows are excluded", { + tbl <- basic_table() |> + tabulate_rsp_subgroups(df) + + exclude_rows <- seq_len(nrow(as_result_df(tbl))) + + testthat::expect_silent( + p <- g_forest(tbl, exclude_rows = exclude_rows) + ) + + expect_snapshot_ggplot("g_forest_exclude_all_rows", p, width = 15, height = 3) +}) + testthat::test_that("g_forest argument deprecation warnings work", { tbl <- basic_table() |> tabulate_rsp_subgroups(df) From 3bdd1b1b308932036d61b7b3719b3443f748f4ff Mon Sep 17 00:00:00 2001 From: Wojtek Date: Fri, 31 Jul 2026 09:19:14 +0200 Subject: [PATCH 2/2] addressing PR review comments #1498. --- R/g_forest.R | 20 +++++++++++--------- man/g_forest.Rd | 13 +++++++------ 2 files changed, 18 insertions(+), 15 deletions(-) diff --git a/R/g_forest.R b/R/g_forest.R index d69cc48ae3..180f4a3bcb 100644 --- a/R/g_forest.R +++ b/R/g_forest.R @@ -49,12 +49,13 @@ #' is no longer used. #' @param exclude_rows (`integerish` or `NULL`)\cr vector of positive row #' indices specifying rows to exclude from the forest plot. Row indices are -#' specified relative to [rtables::as_result_df()] applied to `tbl`. Values -#' must be between 1 and the number of rows in the result data frame, with no -#' missing values. The specified rows are removed before plotting. This can be -#' used to omit rows that should not be displayed in the forest plot, such as -#' rows containing non-plottable values. Defaults to `NULL`, meaning that all -#' rows are considered for plotting. +#' specified relative to the data frame obtained by applying +#' [rtables::as_result_df()] to `tbl`. No elements of `exclude_rows` may be +#' missing. +#' The specified rows are removed before plotting. This can be used to omit +#' rows that should not be displayed in the forest plot, such as rows +#' containing non-plottable values. Defaults to `NULL`, meaning that all rows +#' are considered for plotting. #' #' @return `ggplot` forest plot and table. #' @@ -230,9 +231,10 @@ g_forest <- function(tbl, checkmate::assert_number(font_size, lower = 0) checkmate::assert_character(col, null.ok = TRUE) checkmate::assert_true(is.null(col) | length(col) == 1 | length(col) == nrow(tbl)) + tbl_df <- as_result_df(tbl) checkmate::assert_integerish( exclude_rows, - lower = 1L, upper = nrow(as_result_df(tbl)), any.missing = FALSE, min.len = 1L, null.ok = TRUE + lower = 1L, upper = nrow(tbl_df), any.missing = FALSE, min.len = 1L, null.ok = TRUE ) # Extract info from table @@ -254,7 +256,6 @@ g_forest <- function(tbl, arms <- NULL } - tbl_df <- as_result_df(tbl) if (!is.null(exclude_rows)) { tbl_df <- tbl_df[-exclude_rows, ] } @@ -296,7 +297,8 @@ g_forest <- function(tbl, if (is.null(x_at)) x_at <- union(xlim, vline) x_labels <- x_at - # Apply log transformation + # Apply log transformation. + # When nrow(tbl_df) == 0, x, lwr, and upr are NULL, so log() would fail. if (logx && nrow(tbl_df) >= 1) { x_t <- log(x) lwr_t <- log(lwr) diff --git a/man/g_forest.Rd b/man/g_forest.Rd index 228a7bcd3e..554f0e65f2 100644 --- a/man/g_forest.Rd +++ b/man/g_forest.Rd @@ -95,12 +95,13 @@ is no longer used.} \item{exclude_rows}{(\code{integerish} or \code{NULL})\cr vector of positive row indices specifying rows to exclude from the forest plot. Row indices are -specified relative to \code{\link[rtables:as_result_df]{rtables::as_result_df()}} applied to \code{tbl}. Values -must be between 1 and the number of rows in the result data frame, with no -missing values. The specified rows are removed before plotting. This can be -used to omit rows that should not be displayed in the forest plot, such as -rows containing non-plottable values. Defaults to \code{NULL}, meaning that all -rows are considered for plotting.} +specified relative to the data frame obtained by applying +\code{\link[rtables:as_result_df]{rtables::as_result_df()}} to \code{tbl}. No elements of \code{exclude_rows} may be +missing. +The specified rows are removed before plotting. This can be used to omit +rows that should not be displayed in the forest plot, such as rows +containing non-plottable values. Defaults to \code{NULL}, meaning that all rows +are considered for plotting.} } \value{ \code{ggplot} forest plot and table.