From be6bc518bc63e32535a579e94f4e12930cae4b55 Mon Sep 17 00:00:00 2001 From: Yihui Xie Date: Wed, 2 Sep 2026 15:15:49 -0400 Subject: [PATCH 1/9] Replace gt with lt for summary tables (#371) Replace the heavy gt dependency with the lightweight lt package for rendering simulation summary tables. - Add lt to Imports; keep gt in Suggests for the deprecated as_gt(). - Add R/lt.R: re-export the lt() generic and port as_gt.simtrial_gs_wlr() to lt.simtrial_gs_wlr(). lt has no tidyselect, so spanner/label columns are enumerated explicitly and moved into contiguous blocks with lt_move(). - Deprecate as_gt(): the generic now emits .Deprecated("lt", ...) and the simtrial_gs_wlr method guards with assert_gt_installed(). It still returns a gt_tbl for one release. - Convert all vignettes gt -> lt. - pkgdown: reference lt.simtrial_gs_wlr; NEWS: add major-changes entry. Co-Authored-By: Claude Opus 4.8 --- DESCRIPTION | 1 + NAMESPACE | 3 + NEWS.md | 4 + R/as_gt.R | 29 ++- R/lt.R | 178 ++++++++++++++++++ _pkgdown.yml | 1 + man/as_gt.Rd | 13 +- man/lt-methods.Rd | 95 ++++++++++ man/reexports.Rd | 16 ++ ...crepancy-between-simtrial-and-survival.Rmd | 2 +- vignettes/maxcombo.Rmd | 18 +- vignettes/rmst.Rmd | 6 +- vignettes/routines.Rmd | 20 +- vignettes/sim_fixed_design_custom.Rmd | 14 +- vignettes/sim_fixed_design_simple.Rmd | 20 +- vignettes/sim_gs_design_simple.Rmd | 14 +- 16 files changed, 383 insertions(+), 51 deletions(-) create mode 100644 R/lt.R create mode 100644 man/lt-methods.Rd create mode 100644 man/reexports.Rd diff --git a/DESCRIPTION b/DESCRIPTION index f92b511d..9aa3157f 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -44,6 +44,7 @@ Imports: doFuture, foreach, future, + lt, methods, mvtnorm, stats, diff --git a/NAMESPACE b/NAMESPACE index f016fa3c..10127f35 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -3,6 +3,7 @@ S3method(as_gt,fixed_design) S3method(as_gt,gs_design) S3method(as_gt,simtrial_gs_wlr) +S3method(lt::lt,simtrial_gs_wlr) S3method(summary,simtrial_gs_wlr) S3method(wlr,counting_process) S3method(wlr,default) @@ -18,6 +19,7 @@ export(fh) export(fit_pwexp) export(get_analysis_date) export(get_cut_date_by_event) +export(lt) export(maxcombo) export(mb) export(milestone) @@ -56,6 +58,7 @@ importFrom(future, nbrOfWorkers, plan ) +importFrom(lt,lt) importFrom(methods,is) importFrom(mvtnorm, GenzBretz, diff --git a/NEWS.md b/NEWS.md index 27439205..e737ffbd 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,5 +1,9 @@ # simtrial 1.0.2 +## Major changes + +- The heavy **gt** dependency is replaced by the lightweight **lt** package for rendering summary tables (#371). The `lt()` generic is re-exported so `summary()` output can be rendered with `lt()` after only loading simtrial. `as_gt()` is deprecated but kept for one release; it still returns a `gt_tbl` object and now requires the suggested **gt** package to be installed. + ## Bug fixes - `sim_gs_n()` is updated to provide updated efficacy bound when it is one-sided design (#348, thanks to @LittleBeannie). diff --git a/R/as_gt.R b/R/as_gt.R index ccaeba5d..a8674f83 100644 --- a/R/as_gt.R +++ b/R/as_gt.R @@ -18,23 +18,48 @@ #' Convert summary table to a gt object #' +#' `as_gt()` is deprecated in favor of [lt()], which produces a lightweight +#' HTML table without the heavy \pkg{gt} dependency. `as_gt()` is kept for one +#' release so existing code that customizes the output with \pkg{gt} functions +#' keeps working; it still returns a `gt_tbl` object and requires \pkg{gt} to be +#' installed. New code should use [lt()]; see [lt-methods] for details. +#' #' @param x A summary object of a fixed or group sequential design. #' @param ... Additional arguments (not used). #' #' @return A gt table. #' +#' @seealso [lt()], [lt-methods] +#' #' @export as_gt <- function(x, ...) { + .Deprecated("lt", package = "simtrial", + msg = paste( + "as_gt() is deprecated and will be removed in a future release;", + "please use lt() instead." + )) UseMethod("as_gt", x) } +# stop with an informative message when gt is not installed, since it is only +# a suggested (optional) dependency now that as_gt() is deprecated +assert_gt_installed <- function() { + if (!requireNamespace("gt", quietly = TRUE)) stop( + "The 'gt' package is required by the deprecated as_gt(); ", + "install it with install.packages('gt'), or use lt() instead.", + call. = FALSE + ) +} + #' @param x A object returned by [summary()]. #' @param title Title of the gt table. #' @param subtitle Subtitle of the gt table. #' @param ... Additional parameters (not used). #' -#' @return A gt table summarizing the simulation results. +#' @return A gt table summarizing the simulation results. This method is +#' deprecated; use [lt()] instead. It still returns a `gt_tbl` object for one +#' release and requires \pkg{gt} to be installed. #' @export #' @rdname as_gt #' @@ -101,6 +126,8 @@ as_gt <- function(x, ...) { as_gt.simtrial_gs_wlr <- function(x, title = "Summary of simulation results by WLR tests", subtitle = NULL, ...){ + assert_gt_installed() + # get the default subtitle if (is.null(subtitle)) { subtitle <- paste0("Weighted by ", attributes(x)$method) diff --git a/R/lt.R b/R/lt.R new file mode 100644 index 00000000..563c4f09 --- /dev/null +++ b/R/lt.R @@ -0,0 +1,178 @@ +# Copyright (c) 2026 Merck & Co., Inc., Rahway, NJ, USA and its affiliates. +# All rights reserved. +# +# This file is part of the simtrial program. +# +# simtrial is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +# Re-export the lt() generic so users can call lt() on a simulation summary after +# only loading simtrial (without also attaching lt or qualifying with lt::). +# This also makes S3 dispatch robust regardless of package load order. +# See https://github.com/yihui/lt/issues/4. + +#' @importFrom lt lt +#' @export +lt::lt + +#' Create an lt table from a simulation summary +#' +#' S3 method for [lt()] that converts a group sequential simulation summary +#' (a `simtrial_gs_wlr` object returned by [summary()]) into a formatted lt +#' table. This is the lightweight replacement for the deprecated [as_gt()]. +#' +#' @param data A summary object returned by [summary()]. +#' @param title Title of the lt table. +#' @param subtitle Subtitle of the lt table. +#' @param ... Additional arguments (not used). +#' +#' @return An `lt_tbl` object summarizing the simulation results. +#' +#' @name lt-methods +#' +#' @seealso [as_gt()] +#' +#' @exportS3Method lt::lt +#' +#' @examples +#' +#' # Parameters for enrollment +#' enroll_rampup_duration <- 4 # Duration for enrollment ramp up +#' enroll_duration <- 16 # Total enrollment duration +#' enroll_rate <- gsDesign2::define_enroll_rate( +#' duration = c( +#' enroll_rampup_duration, enroll_duration - enroll_rampup_duration), +#' rate = c(10, 30)) +#' +#' # Parameters for treatment effect +#' delay_effect_duration <- 3 # Delay treatment effect in months +#' median_ctrl <- 9 # Survival median of the control arm +#' median_exp <- c(9, 14) # Survival median of the experimental arm +#' dropout_rate <- 0.001 +#' fail_rate <- gsDesign2::define_fail_rate( +#' duration = c(delay_effect_duration, 100), +#' fail_rate = log(2) / median_ctrl, +#' hr = median_ctrl / median_exp, +#' dropout_rate = dropout_rate) +#' +#' # Other related parameters +#' alpha <- 0.025 # Type I error +#' beta <- 0.1 # Type II error +#' ratio <- 1 # Randomization ratio (experimental:control) +#' +#' # Build a one-sided group sequential design +#' design <- gsDesign2::gs_design_ahr( +#' enroll_rate = enroll_rate, fail_rate = fail_rate, +#' ratio = ratio, alpha = alpha, beta = beta, +#' analysis_time = c(12, 24, 36), +#' upper = gsDesign2::gs_spending_bound, +#' upar = list(sf = gsDesign::sfLDOF, total_spend = alpha), +#' lower = gsDesign2::gs_b, +#' lpar = rep(-Inf, 3)) +#' +#' # Define cuttings of 2 IAs and 1 FA +#' ia1_cut <- create_cut(target_event_overall = ceiling(design$analysis$event[1])) +#' ia2_cut <- create_cut(target_event_overall = ceiling(design$analysis$event[2])) +#' fa_cut <- create_cut(target_event_overall = ceiling(design$analysis$event[3])) +#' +#' # Run simulations +#' simulation <- sim_gs_n( +#' n_sim = 3, +#' sample_size = ceiling(design$analysis$n[3]), +#' enroll_rate = design$enroll_rate, +#' fail_rate = design$fail_rate, +#' test = wlr, +#' cut = list(ia1 = ia1_cut, ia2 = ia2_cut, fa = fa_cut), +#' weight = fh(rho = 0, gamma = 0.5)) +#' +#' # Summarize simulations +#' simulation |> +#' summary(bound = gsDesign::gsDesign(k = 3, test.type = 1, sfu = gsDesign::sfLDOF)$upper$bound) |> +#' lt() +#' +#' # Summarize simulations and compare with the planned design +#' simulation |> +#' summary(design = design) |> +#' lt() +lt.simtrial_gs_wlr <- function(data, + title = "Summary of simulation results by WLR tests", + subtitle = NULL, ...){ + x <- data + + # get the default subtitle + if (is.null(subtitle)) { + subtitle <- paste0("Weighted by ", attributes(x)$method) + } + + # if it is not compared with the design + if (attributes(x)$compare_with_design == "no") { + as.data.frame(x) |> + lt::lt() |> + lt::lt_label(sim_time = "Time", sim_n = "N", sim_event = "Event", sim_upper_prob = "Crossing probability") |> + lt::lt_move(columns = c("sim_time", "sim_n", "sim_event"), after = "analysis") |> + lt::lt_header(title = title, subtitle = subtitle) + } else { + # get the design type, either one-sided or two-sided + design_type <- attributes(x)$design_type + + # lt has no tidyselect, so enumerate the columns of each spanner explicitly. + # Spanned columns must be contiguous, which is ensured by the lt_move() calls + # below (mirroring gt::cols_move() in as_gt()). + time_cols <- grep("_time$", names(x), value = TRUE) + event_cols <- grep("_event$", names(x), value = TRUE) + n_cols <- grep("_n$", names(x), value = TRUE) + upper_cols <- grep("_upper_prob$", names(x), value = TRUE) + lower_cols <- grep("_lower_prob$", names(x), value = TRUE) + + # build an lt table as return, moving the paired asymptotic/simulated columns + # right after `analysis` so each spanner covers a contiguous block + ans <- as.data.frame(x) |> + lt::lt() |> + lt::lt_move( + columns = c("asy_time", "sim_time", "asy_n", "sim_n", "asy_event", "sim_event"), + after = "analysis") + + # for a two-sided design, keep the efficacy (upper) and futility (lower) + # probability columns contiguous within their own spanners + if (design_type == "two-sided") { + ans <- ans |> + lt::lt_move( + columns = c("asy_upper_prob", "sim_upper_prob", "asy_lower_prob", "sim_lower_prob"), + after = "sim_event") + } + + ans <- ans |> + lt::lt_spanner(label = "Time", columns = time_cols) |> + lt::lt_spanner(label = "Events", columns = event_cols) |> + lt::lt_spanner(label = "N", columns = n_cols) |> + lt::lt_spanner( + label = "Probability of crossing efficacy bounds under H1", + columns = upper_cols) + + if (design_type == "two-sided") { + ans <- ans |> lt::lt_spanner( + label = "Probability of crossing futility bounds under H1", + columns = lower_cols) + } + + # label the asymptotic/simulated/analysis columns, mirroring the + # starts_with()/matches() rules used by gt::cols_label() in as_gt() + labels <- ifelse( + startsWith(names(x), "asy"), "Asymptotic", + ifelse(startsWith(names(x), "sim"), "Simulated", "Analysis")) + labels <- stats::setNames(as.list(labels), names(x)) + + do.call(lt::lt_label, c(list(ans), labels)) |> + lt::lt_header(title = title, subtitle = subtitle) + } +} diff --git a/_pkgdown.yml b/_pkgdown.yml index 2a4fc697..e4fd5146 100644 --- a/_pkgdown.yml +++ b/_pkgdown.yml @@ -59,6 +59,7 @@ reference: - title: "Summarize simulations" contents: - summary.simtrial_gs_wlr + - lt.simtrial_gs_wlr - as_gt.simtrial_gs_wlr - title: "Randomization algorithms" diff --git a/man/as_gt.Rd b/man/as_gt.Rd index 8917196b..48a78bbe 100644 --- a/man/as_gt.Rd +++ b/man/as_gt.Rd @@ -26,10 +26,16 @@ as_gt(x, ...) \value{ A gt table. -A gt table summarizing the simulation results. +A gt table summarizing the simulation results. This method is +deprecated; use \code{\link[lt:lt]{lt()}} instead. It still returns a \code{gt_tbl} object for one +release and requires \pkg{gt} to be installed. } \description{ -Convert summary table to a gt object +\code{as_gt()} is deprecated in favor of \code{\link[lt:lt]{lt()}}, which produces a lightweight +HTML table without the heavy \pkg{gt} dependency. \code{as_gt()} is kept for one +release so existing code that customizes the output with \pkg{gt} functions +keeps working; it still returns a \code{gt_tbl} object and requires \pkg{gt} to be +installed. New code should use \code{\link[lt:lt]{lt()}}; see \link[gsDesign:lt-methods]{lt-methods} for details. } \examples{ @@ -92,3 +98,6 @@ simulation |> summary(design = design) |> simtrial::as_gt() } +\seealso{ +\code{\link[lt:lt]{lt()}}, \link[gsDesign:lt-methods]{lt-methods} +} diff --git a/man/lt-methods.Rd b/man/lt-methods.Rd new file mode 100644 index 00000000..32565a54 --- /dev/null +++ b/man/lt-methods.Rd @@ -0,0 +1,95 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/lt.R +\name{lt-methods} +\alias{lt-methods} +\alias{lt.simtrial_gs_wlr} +\title{Create an lt table from a simulation summary} +\usage{ +\method{lt}{simtrial_gs_wlr}( + data, + title = "Summary of simulation results by WLR tests", + subtitle = NULL, + ... +) +} +\arguments{ +\item{data}{A summary object returned by \code{\link[=summary]{summary()}}.} + +\item{title}{Title of the lt table.} + +\item{subtitle}{Subtitle of the lt table.} + +\item{...}{Additional arguments (not used).} +} +\value{ +An \code{lt_tbl} object summarizing the simulation results. +} +\description{ +S3 method for \code{\link[lt:lt]{lt()}} that converts a group sequential simulation summary +(a \code{simtrial_gs_wlr} object returned by \code{\link[=summary]{summary()}}) into a formatted lt +table. This is the lightweight replacement for the deprecated \code{\link[=as_gt]{as_gt()}}. +} +\examples{ + +# Parameters for enrollment +enroll_rampup_duration <- 4 # Duration for enrollment ramp up +enroll_duration <- 16 # Total enrollment duration +enroll_rate <- gsDesign2::define_enroll_rate( + duration = c( + enroll_rampup_duration, enroll_duration - enroll_rampup_duration), + rate = c(10, 30)) + +# Parameters for treatment effect +delay_effect_duration <- 3 # Delay treatment effect in months +median_ctrl <- 9 # Survival median of the control arm +median_exp <- c(9, 14) # Survival median of the experimental arm +dropout_rate <- 0.001 +fail_rate <- gsDesign2::define_fail_rate( + duration = c(delay_effect_duration, 100), + fail_rate = log(2) / median_ctrl, + hr = median_ctrl / median_exp, + dropout_rate = dropout_rate) + +# Other related parameters +alpha <- 0.025 # Type I error +beta <- 0.1 # Type II error +ratio <- 1 # Randomization ratio (experimental:control) + +# Build a one-sided group sequential design +design <- gsDesign2::gs_design_ahr( + enroll_rate = enroll_rate, fail_rate = fail_rate, + ratio = ratio, alpha = alpha, beta = beta, + analysis_time = c(12, 24, 36), + upper = gsDesign2::gs_spending_bound, + upar = list(sf = gsDesign::sfLDOF, total_spend = alpha), + lower = gsDesign2::gs_b, + lpar = rep(-Inf, 3)) + +# Define cuttings of 2 IAs and 1 FA +ia1_cut <- create_cut(target_event_overall = ceiling(design$analysis$event[1])) +ia2_cut <- create_cut(target_event_overall = ceiling(design$analysis$event[2])) +fa_cut <- create_cut(target_event_overall = ceiling(design$analysis$event[3])) + +# Run simulations +simulation <- sim_gs_n( + n_sim = 3, + sample_size = ceiling(design$analysis$n[3]), + enroll_rate = design$enroll_rate, + fail_rate = design$fail_rate, + test = wlr, + cut = list(ia1 = ia1_cut, ia2 = ia2_cut, fa = fa_cut), + weight = fh(rho = 0, gamma = 0.5)) + +# Summarize simulations +simulation |> + summary(bound = gsDesign::gsDesign(k = 3, test.type = 1, sfu = gsDesign::sfLDOF)$upper$bound) |> + lt() + +# Summarize simulations and compare with the planned design +simulation |> + summary(design = design) |> + lt() +} +\seealso{ +\code{\link[=as_gt]{as_gt()}} +} diff --git a/man/reexports.Rd b/man/reexports.Rd new file mode 100644 index 00000000..0e579bcf --- /dev/null +++ b/man/reexports.Rd @@ -0,0 +1,16 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/lt.R +\docType{import} +\name{reexports} +\alias{reexports} +\alias{lt} +\title{Objects exported from other packages} +\keyword{internal} +\description{ +These objects are imported from other packages. Follow the links +below to see their documentation. + +\describe{ + \item{lt}{\code{\link[lt:lt]{lt()}}} +}} + diff --git a/vignettes/discrepancy-between-simtrial-and-survival.Rmd b/vignettes/discrepancy-between-simtrial-and-survival.Rmd index 0c46e35c..5fd60ec7 100644 --- a/vignettes/discrepancy-between-simtrial-and-survival.Rmd +++ b/vignettes/discrepancy-between-simtrial-and-survival.Rmd @@ -13,7 +13,7 @@ vignette: > library(gsDesign) library(gsDesign2) library(dplyr) -library(gt) +library(lt) library(simtrial) library(tidyr) library(survival) diff --git a/vignettes/maxcombo.Rmd b/vignettes/maxcombo.Rmd index 626dbf8f..02c4bf81 100644 --- a/vignettes/maxcombo.Rmd +++ b/vignettes/maxcombo.Rmd @@ -51,7 +51,7 @@ When more than one test is chosen the correlation between tests is computed as s library(simtrial) library(knitr) library(dplyr) -library(gt) +library(lt) ``` ```{r} @@ -64,8 +64,8 @@ x <- sim_fixed_n( ) x |> - gt() |> - fmt_number(columns = c("ln_hr", "z", "duration", "v1", "v2", "v3"), decimals = 2) + lt() |> + lt_format(columns = c("ln_hr", "z", "duration", "v1", "v2", "v3"), decimals = 2) ``` ### Generating data with `sim_pw_surv()` @@ -80,8 +80,8 @@ s <- sim_pw_surv(n = 100) s |> head() |> - gt() |> - fmt_number(columns = c("enroll_time", "fail_time", "dropout_time", "cte"), decimals = 2) + lt() |> + lt_format(columns = c("enroll_time", "fail_time", "dropout_time", "cte"), decimals = 2) ``` Once generated, we need to cut the data for analysis. Here we cut after 75 events. @@ -91,8 +91,8 @@ x <- s |> cut_data_by_event(75) x |> head() |> - gt() |> - fmt_number(columns = "tte", decimals = 2) + lt() |> + lt_format(columns = "tte", decimals = 2) ``` Now we can analyze this data. We begin with `s` to show how this can be done in a single line. @@ -127,7 +127,7 @@ In this case we use the small `aml` dataset from the survival package. library(survival) aml |> head() |> - gt() + lt() ``` We rename variables and create a stratum variable as follows: @@ -145,7 +145,7 @@ x <- aml |> transmute( x |> head() |> - gt() + lt() ``` Now we analyze the data with a MaxCombo with the logrank and FH(0, 1) and compute a $p$-value. diff --git a/vignettes/rmst.Rmd b/vignettes/rmst.Rmd index 8e10c822..feb38358 100644 --- a/vignettes/rmst.Rmd +++ b/vignettes/rmst.Rmd @@ -14,7 +14,7 @@ knitr::opts_chunk$set( comment = "#>" ) -run <- requireNamespace("gt", quietly = TRUE) && +run <- requireNamespace("lt", quietly = TRUE) && requireNamespace("survRM2", quietly = TRUE) knitr::opts_chunk$set(eval = run) ``` @@ -94,8 +94,6 @@ where $\alpha$ is a predefined significant level, and $z_{\alpha/2}$ is the uppe ```{r, message=FALSE, warning=FALSE} # Simulate NPH data from the piecewise model library(simtrial) -# Table display -library(gt) ``` ```{r} @@ -105,7 +103,7 @@ simtrial:::rmst_single_arm( time_var = data_single_arm$month, event_var = data_single_arm$evntd, tau = 10 -) |> gt() +) |> lt() ``` ## Estimation of RMST differences in 2 arms at a single time point diff --git a/vignettes/routines.Rmd b/vignettes/routines.Rmd index 07951bfb..a2d75e28 100644 --- a/vignettes/routines.Rmd +++ b/vignettes/routines.Rmd @@ -15,7 +15,7 @@ knitr::opts_chunk$set( ) run <- requireNamespace("dplyr", quietly = TRUE) && - requireNamespace("gt", quietly = TRUE) + requireNamespace("lt", quietly = TRUE) knitr::opts_chunk$set(eval = run) ``` @@ -48,7 +48,7 @@ The package could be extended in many ways in the future, including: ```{r, message=FALSE, warning=FALSE} library(simtrial) -library(gt) +library(lt) library(dplyr) ``` @@ -143,8 +143,8 @@ x <- sim_pw_surv( ) head(x) |> - gt() |> - fmt_number(columns = c("enroll_time", "fail_time", "dropout_time", "cte"), decimals = 2) + lt() |> + lt_format(columns = c("enroll_time", "fail_time", "dropout_time", "cte"), decimals = 2) ``` ## Cutting data for analysis @@ -157,8 +157,8 @@ Observations enrolled after the input `cut_date` are deleted and events and cens y <- cut_data_by_date(x, cut_date = 5) head(y) |> - gt() |> - fmt_number(columns = "tte", decimals = 2) + lt() |> + lt_format(columns = "tte", decimals = 2) ``` For instance, if we wish to cut the entire dataset when 50 events are observed in the Positive stratum we can use the `get_cut_date_by_event` function as follows: @@ -186,8 +186,8 @@ The counting process format is further discussed in the next section where we co ten150 <- counting_process(y150, arm = "experimental") head(ten150) |> - gt() |> - fmt_number(columns = c("tte", "o_minus_e", "var_o_minus_e"), decimals = 2) + lt() |> + lt_format(columns = c("tte", "o_minus_e", "var_o_minus_e"), decimals = 2) ``` ## Logrank and weighted logrank testing @@ -282,8 +282,8 @@ sim_fixed_n( timing_type = 1:5, # Use all possible data cutoff methods rho_gamma = rho_gamma # FH test(s) to use; in this case, logrank ) |> - gt() |> - fmt_number(columns = c("ln_hr", "z", "duration")) + lt() |> + lt_format(columns = c("ln_hr", "z", "duration")) ``` If you look carefully, you should be asking why the cutoff with the planned number of events is so different than the other data cutoff methods. diff --git a/vignettes/sim_fixed_design_custom.Rmd b/vignettes/sim_fixed_design_custom.Rmd index 45223bcf..1ab3587a 100644 --- a/vignettes/sim_fixed_design_custom.Rmd +++ b/vignettes/sim_fixed_design_custom.Rmd @@ -12,7 +12,7 @@ vignette: > library(gsDesign2) library(simtrial) library(dplyr) -library(gt) +library(lt) library(doFuture) library(tibble) set.seed(2025) @@ -64,7 +64,7 @@ uncut_data_a <- sim_pw_surv(n = n, stratum = stratum, block = block, The output of `sim_pw_surv()` is subject-level observations, including stratum, enrollment time for the observation, treatment group the observation is randomized to, failure time, dropout time, calendar time of enrollment plot the minimum of failure time and dropout time ( `cte`), and an failure and dropout indicator (`fail = 1` is a failure, `fail = 0` is a dropout). ```{r} -uncut_data_a |> head() |> gt() |> tab_header("An Overview of Simulated TTE data") +uncut_data_a |> head() |> lt() |> lt_header("An Overview of Simulated TTE data") ``` ## Scenario b) Differential dropout rates @@ -208,7 +208,7 @@ cut_date <- cut_date_d cat("The cutoff date is ", round(cut_date, 2)) cut_data <- uncut_data |> cut_data_by_date(cut_date) -cut_data |> head() |> gt() |> tab_header(paste0("An Overview of TTE data Cut at ", round(cut_date, 2), "Months")) +cut_data |> head() |> lt() |> lt_header(paste0("An Overview of TTE data Cut at ", round(cut_date, 2), "Months")) ``` # Step 3: Run tests @@ -258,7 +258,7 @@ sim_res <- tribble( sim_res_mc$method, sim_res_mc$parameter, NA, NA, NA, sim_res_mc$p_value ) -sim_res |> gt() |> tab_header("One Simulation Results") +sim_res |> lt() |> lt_header("One Simulation Results") ``` @@ -354,7 +354,7 @@ plan("sequential") The output from the parallel computation resembles the output of `sim_fix_n()` described in the vignette [Simulate Fixed Designs with Ease via sim_fixed_n](https://merck.github.io/simtrial/articles/sim_fixed_design_simple.html). Each row in the output corresponds to the simulation results for each testing method per each repeation. ```{r} -ans |> head() |> gt() |> tab_header("Overview Each Simulation results") +ans |> head() |> lt() |> lt_header("Overview Each Simulation results") ``` # Step 5: Summarize simulations @@ -374,8 +374,8 @@ ans_mc <- ans |> ans_non_mc |> union(ans_mc) |> - gt() |> - tab_header("Summary from 100 simulations") + lt() |> + lt_header("Summary from 100 simulations") ``` diff --git a/vignettes/sim_fixed_design_simple.Rmd b/vignettes/sim_fixed_design_simple.Rmd index 532f4f6e..34b82682 100644 --- a/vignettes/sim_fixed_design_simple.Rmd +++ b/vignettes/sim_fixed_design_simple.Rmd @@ -12,7 +12,7 @@ vignette: > library(gsDesign2) library(simtrial) library(dplyr) -library(gt) +library(lt) set.seed(2027) ``` @@ -59,10 +59,10 @@ In this approach, users can obtain the sample size and targeted events from the x <- fixed_design_ahr(enroll_rate = enroll_rate, fail_rate = fail_rate, alpha = 0.025, power = 0.85, ratio = 1, study_duration = total_duration) |> to_integer() -x |> summary() |> gt() |> - tab_header(title = "Sample Size and Targeted Events Based on AHR Method", +x |> summary() |> lt() |> + lt_header(title = "Sample Size and Targeted Events Based on AHR Method", subtitle = "Fixed Design with 85% Power, One-sided 2.5% Type I error") |> - fmt_number(columns = c(4, 5, 7), decimals = 2) + lt_format(columns = c(4, 5, 7), decimals = 2) ``` Now we set the derived targeted sample size, enrollment rate, and event count from the above. @@ -109,9 +109,9 @@ Here we have just run 2 simulated trials and see how the different cutoffs vary ```{r} sim_res |> - gt() |> - tab_header("Tests for Each Simulation Result", subtitle = "Logrank Test for Different Analysis Cutoffs") |> - fmt_number(columns = c(4, 5, 7), decimals = 2) + lt() |> + lt_header("Tests for Each Simulation Result", subtitle = "Logrank Test for Different Analysis Cutoffs") |> + lt_format(columns = c(4, 5, 7), decimals = 2) ``` # Step 3: Summarize simulations @@ -142,10 +142,10 @@ sim_res |> `Mean duration` = mean(duration)) |> mutate(`Sample size` = sample_size, `Targeted events` = target_event) |> - gt() |> - tab_header(title = "Summary of 100 simulations by 5 different analysis cutoff methods", + lt() |> + lt_header(title = "Summary of 100 simulations by 5 different analysis cutoff methods", subtitle = "Tested by logrank") |> - fmt_number(columns = c(2:4), decimals = 2) + lt_format(columns = c(2:4), decimals = 2) ``` We can also do things like summarize distribution of event counts at the planned study duration. diff --git a/vignettes/sim_gs_design_simple.Rmd b/vignettes/sim_gs_design_simple.Rmd index a3712f50..d0c4a3b5 100644 --- a/vignettes/sim_gs_design_simple.Rmd +++ b/vignettes/sim_gs_design_simple.Rmd @@ -12,7 +12,7 @@ vignette: > library(gsDesign2) library(simtrial) library(dplyr) -library(gt) +library(lt) set.seed(2025) ``` @@ -165,8 +165,8 @@ The `z` column is the test statistic for the logrank test (estimate / se). The `info` and `info0` columns are the information at the current analysis under the alternate and null hypotheses, respectively. ```{r} -sim_res |> head(n = 6) |> gt() |> tab_header("Overview Each Simulation results") |> - fmt_number(columns = c(5, 8:12), decimals = 2) +sim_res |> head(n = 6) |> lt() |> lt_header("Overview Each Simulation results") |> + lt_format(columns = c(5, 8:12), decimals = 2) ``` # Step 3: Summarize simulations @@ -180,10 +180,10 @@ sim_res |> summarize(`Mean time` = mean(cut_date), `sd(time)` = sd(cut_date), `Simulated power` = mean(z >= eff_bound)) |> ungroup() |> mutate(`Asymptotic power` = x$bound$probability[x$bound$bound == "upper"]) |> - gt() |> - tab_header("Summary of 100 simulations") |> - fmt_number(columns = 2, decimals = 1) |> - fmt_number(columns = 3:5, decimals = 2) + lt() |> + lt_header("Summary of 100 simulations") |> + lt_format(columns = 2, decimals = 1) |> + lt_format(columns = 3:5, decimals = 2) ``` ## References From 46a5f6565acd18738d3a06e3541f39eb8c5628ba Mon Sep 17 00:00:00 2001 From: Yihui Xie Date: Wed, 2 Sep 2026 15:24:25 -0400 Subject: [PATCH 2/9] Render raw sim_gs_n() output as a plain lt table The raw output of sim_gs_n() carries the "simtrial_gs_wlr" class but lacks the attributes added by summary(). Since the re-exported lt() generic now dispatches to lt.simtrial_gs_wlr(), guard the method: when the object is not a summary (no compare_with_design attribute), fall back to a plain lt table, mirroring how bare gt() rendered it before. Fixes the vignette build failure in CI. Co-Authored-By: Claude Opus 4.8 --- R/lt.R | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/R/lt.R b/R/lt.R index 563c4f09..5c5abdec 100644 --- a/R/lt.R +++ b/R/lt.R @@ -109,6 +109,13 @@ lt.simtrial_gs_wlr <- function(data, subtitle = NULL, ...){ x <- data + # The raw output of sim_gs_n() also carries the "simtrial_gs_wlr" class but is + # not a summary (it lacks the attributes added by summary()). In that case fall + # back to a plain lt table, mirroring how bare gt() used to render it. + if (is.null(attributes(x)$compare_with_design)) { + return(lt::lt(as.data.frame(x), ...)) + } + # get the default subtitle if (is.null(subtitle)) { subtitle <- paste0("Weighted by ", attributes(x)$method) From 6e975b19157c016e8f8804565413ea811c0cf967 Mon Sep 17 00:00:00 2001 From: Yihui Xie Date: Wed, 2 Sep 2026 15:33:44 -0400 Subject: [PATCH 3/9] Fix Rd cross-references to local lt topics Regenerate man pages so links to lt() and lt-methods resolve to the local topics instead of gsDesign. The initial roxygen run predated the lt re-export being loaded, so it mis-resolved [lt-methods] to gsDesign:lt-methods, tripping the "error on warning" R CMD check. Co-Authored-By: Claude Opus 4.8 --- man/as_gt.Rd | 8 ++++---- man/lt-methods.Rd | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/man/as_gt.Rd b/man/as_gt.Rd index 48a78bbe..2270a58c 100644 --- a/man/as_gt.Rd +++ b/man/as_gt.Rd @@ -27,15 +27,15 @@ as_gt(x, ...) A gt table. A gt table summarizing the simulation results. This method is -deprecated; use \code{\link[lt:lt]{lt()}} instead. It still returns a \code{gt_tbl} object for one +deprecated; use \code{\link[=lt]{lt()}} instead. It still returns a \code{gt_tbl} object for one release and requires \pkg{gt} to be installed. } \description{ -\code{as_gt()} is deprecated in favor of \code{\link[lt:lt]{lt()}}, which produces a lightweight +\code{as_gt()} is deprecated in favor of \code{\link[=lt]{lt()}}, which produces a lightweight HTML table without the heavy \pkg{gt} dependency. \code{as_gt()} is kept for one release so existing code that customizes the output with \pkg{gt} functions keeps working; it still returns a \code{gt_tbl} object and requires \pkg{gt} to be -installed. New code should use \code{\link[lt:lt]{lt()}}; see \link[gsDesign:lt-methods]{lt-methods} for details. +installed. New code should use \code{\link[=lt]{lt()}}; see \link{lt-methods} for details. } \examples{ @@ -99,5 +99,5 @@ simulation |> simtrial::as_gt() } \seealso{ -\code{\link[lt:lt]{lt()}}, \link[gsDesign:lt-methods]{lt-methods} +\code{\link[=lt]{lt()}}, \link{lt-methods} } diff --git a/man/lt-methods.Rd b/man/lt-methods.Rd index 32565a54..bf88e130 100644 --- a/man/lt-methods.Rd +++ b/man/lt-methods.Rd @@ -25,7 +25,7 @@ An \code{lt_tbl} object summarizing the simulation results. } \description{ -S3 method for \code{\link[lt:lt]{lt()}} that converts a group sequential simulation summary +S3 method for \code{\link[=lt]{lt()}} that converts a group sequential simulation summary (a \code{simtrial_gs_wlr} object returned by \code{\link[=summary]{summary()}}) into a formatted lt table. This is the lightweight replacement for the deprecated \code{\link[=as_gt]{as_gt()}}. } From 0dc2fd6bf68fde45e47f9d583a97a30d0d1d8e01 Mon Sep 17 00:00:00 2001 From: Yihui Xie Date: Thu, 3 Sep 2026 14:47:38 -0500 Subject: [PATCH 4/9] apply suggestion [ci skip] Co-authored-by: John Blischak --- vignettes/rmst.Rmd | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/vignettes/rmst.Rmd b/vignettes/rmst.Rmd index feb38358..09afd1fc 100644 --- a/vignettes/rmst.Rmd +++ b/vignettes/rmst.Rmd @@ -14,8 +14,7 @@ knitr::opts_chunk$set( comment = "#>" ) -run <- requireNamespace("lt", quietly = TRUE) && - requireNamespace("survRM2", quietly = TRUE) +run <- requireNamespace("survRM2", quietly = TRUE) knitr::opts_chunk$set(eval = run) ``` From efb5b4fbae5589a5a17bb2cb37d575581f21a0bc Mon Sep 17 00:00:00 2001 From: Yihui Xie Date: Thu, 3 Sep 2026 15:50:12 -0400 Subject: [PATCH 5/9] no need to require lt --- vignettes/routines.Rmd | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/vignettes/routines.Rmd b/vignettes/routines.Rmd index a2d75e28..cf495e9d 100644 --- a/vignettes/routines.Rmd +++ b/vignettes/routines.Rmd @@ -14,8 +14,7 @@ knitr::opts_chunk$set( comment = "#>" ) -run <- requireNamespace("dplyr", quietly = TRUE) && - requireNamespace("lt", quietly = TRUE) +run <- requireNamespace("dplyr", quietly = TRUE) knitr::opts_chunk$set(eval = run) ``` From 6e7562105d2eaedf35162f0fbbe7143350ba5c53 Mon Sep 17 00:00:00 2001 From: Yihui Xie Date: Thu, 3 Sep 2026 15:52:44 -0400 Subject: [PATCH 6/9] Enable lt tables in discrepancy vignette The two summary tables in this vignette were previously commented out. Convert them from gt to lt and uncomment. gt::fmt_number(columns = everything()) had no direct lt equivalent (lt lacks tidyselect), so select the numeric columns explicitly with names(fr)[sapply(fr, is.numeric)]. Co-Authored-By: Claude Opus 4.8 --- vignettes/discrepancy-between-simtrial-and-survival.Rmd | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vignettes/discrepancy-between-simtrial-and-survival.Rmd b/vignettes/discrepancy-between-simtrial-and-survival.Rmd index 5fd60ec7..6e17f97d 100644 --- a/vignettes/discrepancy-between-simtrial-and-survival.Rmd +++ b/vignettes/discrepancy-between-simtrial-and-survival.Rmd @@ -86,7 +86,7 @@ scenarios <- tribble( 6, "Strong null", 3, 18, .25, 6, "Strong null", 4, 12, .2, ) -# scenarios |> gt() +scenarios |> lt() ``` ```{r} @@ -103,7 +103,7 @@ fr <- scenarios |> select(-x_rate) |> filter(Period > 0, Scenario > 0) |> ungroup() -# fr |> gt() |> fmt_number(columns = everything(), decimals = 2) +fr |> lt() |> lt_format(columns = names(fr)[sapply(fr, is.numeric)], decimals = 2) fr <- fr |> mutate(fail_rate = rate, dropout_rate = 0.001, stratum = "All") From 784f5f406eaa987dc06412e4b52d2ad3c80f1243 Mon Sep 17 00:00:00 2001 From: Yihui Xie Date: Thu, 3 Sep 2026 17:08:08 -0400 Subject: [PATCH 7/9] I think we are better off without specifying 2 decimal places (columns like 'senario', 'period', 'duration' are integers and shouldn't have 2 decimal places) --- vignettes/discrepancy-between-simtrial-and-survival.Rmd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vignettes/discrepancy-between-simtrial-and-survival.Rmd b/vignettes/discrepancy-between-simtrial-and-survival.Rmd index 6e17f97d..5ca66c84 100644 --- a/vignettes/discrepancy-between-simtrial-and-survival.Rmd +++ b/vignettes/discrepancy-between-simtrial-and-survival.Rmd @@ -103,7 +103,7 @@ fr <- scenarios |> select(-x_rate) |> filter(Period > 0, Scenario > 0) |> ungroup() -fr |> lt() |> lt_format(columns = names(fr)[sapply(fr, is.numeric)], decimals = 2) +fr |> lt() fr <- fr |> mutate(fail_rate = rate, dropout_rate = 0.001, stratum = "All") From 76a63ca90df4f33ed55db252328cff86432876de Mon Sep 17 00:00:00 2001 From: Yihui Xie Date: Thu, 3 Sep 2026 17:16:18 -0400 Subject: [PATCH 8/9] Fix missing efficacy/futility spanner in lt() table lt matches each spanner to the visual position of its first column, then spans the next length(columns) columns. The spanner columns were listed in grep() (original-name) order, e.g. c("sim_time", "asy_time"), but lt_move() lays them out asymptotic-before-simulated, e.g. c("asy_time", "sim_time"). The mismatched first column shifted every spanner's colspan, cascading until the last spanner ("Probability of crossing efficacy bounds under H1") had no column to anchor to and was silently dropped from the rendered table. List each spanner's columns in the same paired asy-before-sim order that lt_move() uses, reusing shared vectors so the move layout and the spanner selections cannot drift apart. Verified (via a node-baked render) that the one-sided table now shows Time/N/Events/Efficacy and the two-sided table additionally shows Futility. Co-Authored-By: Claude Opus 4.8 --- R/lt.R | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/R/lt.R b/R/lt.R index 5c5abdec..9fa8dedf 100644 --- a/R/lt.R +++ b/R/lt.R @@ -133,20 +133,23 @@ lt.simtrial_gs_wlr <- function(data, design_type <- attributes(x)$design_type # lt has no tidyselect, so enumerate the columns of each spanner explicitly. - # Spanned columns must be contiguous, which is ensured by the lt_move() calls - # below (mirroring gt::cols_move() in as_gt()). - time_cols <- grep("_time$", names(x), value = TRUE) - event_cols <- grep("_event$", names(x), value = TRUE) - n_cols <- grep("_n$", names(x), value = TRUE) - upper_cols <- grep("_upper_prob$", names(x), value = TRUE) - lower_cols <- grep("_lower_prob$", names(x), value = TRUE) + # The columns must be listed in the same paired order (asymptotic before + # simulated) that lt_move() lays them out below, because lt matches a + # spanner to the visual position of its first column and then spans the + # next length(columns) columns; listing them in any other order would + # misalign the spanners (and silently drop later ones). + time_cols <- c("asy_time", "sim_time") + n_cols <- c("asy_n", "sim_n") + event_cols <- c("asy_event", "sim_event") + upper_cols <- c("asy_upper_prob", "sim_upper_prob") + lower_cols <- c("asy_lower_prob", "sim_lower_prob") # build an lt table as return, moving the paired asymptotic/simulated columns # right after `analysis` so each spanner covers a contiguous block ans <- as.data.frame(x) |> lt::lt() |> lt::lt_move( - columns = c("asy_time", "sim_time", "asy_n", "sim_n", "asy_event", "sim_event"), + columns = c(time_cols, n_cols, event_cols), after = "analysis") # for a two-sided design, keep the efficacy (upper) and futility (lower) @@ -154,7 +157,7 @@ lt.simtrial_gs_wlr <- function(data, if (design_type == "two-sided") { ans <- ans |> lt::lt_move( - columns = c("asy_upper_prob", "sim_upper_prob", "asy_lower_prob", "sim_lower_prob"), + columns = c(upper_cols, lower_cols), after = "sim_event") } From fb556f34b450981afe708fd995497b4a29bf19f3 Mon Sep 17 00:00:00 2001 From: Yihui Xie Date: Fri, 4 Sep 2026 11:36:42 -0400 Subject: [PATCH 9/9] remove library(lt) since we don't use any lt_*() functions from {lt} --- vignettes/discrepancy-between-simtrial-and-survival.Rmd | 1 - 1 file changed, 1 deletion(-) diff --git a/vignettes/discrepancy-between-simtrial-and-survival.Rmd b/vignettes/discrepancy-between-simtrial-and-survival.Rmd index 5ca66c84..19019cdc 100644 --- a/vignettes/discrepancy-between-simtrial-and-survival.Rmd +++ b/vignettes/discrepancy-between-simtrial-and-survival.Rmd @@ -13,7 +13,6 @@ vignette: > library(gsDesign) library(gsDesign2) library(dplyr) -library(lt) library(simtrial) library(tidyr) library(survival)