Tabulating Group Sequential Designs for Clinical Trials: Lucid, Structured, and Compact Summaries

Authors
Affiliations

Yujie Zhao

Merck & Co., Inc., Rahway, NJ, USA

Hiroaki Fukuda

MSD K.K., Japan

John Blischak

Merck & Co., Inc., Rahway, NJ, USA, working under contract with AgileOne, Torrance, CA, USA

Keaven Anderson

Merck & Co., Inc., Rahway, NJ, USA

Background

When it comes to clinical trials involving time-to-event endpoints, designing studies to determine the necessary sample size for a desired power is a common practice. Group sequential designs are frequently employed as a tool for such designs. Reporting the intricate details of the design into a concise yet informative table can present significant challenges.

In this post, we present a tool that efficiently consolidates and organizes extensive design information into a single, concise, and coherent table. These summarization functions are readily accessible within the R package gsDesign2.

library(gsDesign2)

gsDesign2 allows users to output summary tables in different format:

  • as_gt(): for gt tables;
  • as_rtf(): for rtf tables.

Build a design

We provide a basic example with simplified design parameters. Since the focus of this documentation is the presentation of the summary table, we skip the detailed introduction of the procedure to build the design (the `design` object below). If readers are interested, please refer to https://cran.r-project.org/package=gsDesign2.

# build the group sequential design
design <- gs_design_ahr(
  # time of interim and final analyses
  analysis_time = c(12, 24),
  # spending bound for efficacy
  upper = gs_spending_bound, 
  # O’Brien-Fleming spending function; total_spend is normally alpha
  upar = list(sf = gsDesign::sfLDOF, total_spend = 0.025),
  # fixed Z-values will be provided for futility bound
  lower = gs_b, 
  lpar = c(qnorm(0.1), -Inf, -Inf)
)

The design provided above is a comprehensive list that contains extensive information.

typeof(design)
[1] "list"

The list above comprises input parameters (accessible via design$input) and four tables (explained below). The task at hand is to merge these four tables into a comprehensive summary table that is both easily understandable and well-organized.

Table 1: Enrollment Summary

This table provides a summary of the enrollment. The total sample size can be obtained by multiplying the duration and rate.

design$enroll_rate
# A tibble: 3 × 3
  stratum duration  rate
  <chr>      <dbl> <dbl>
1 All            2  23.4
2 All            2  46.8
3 All           10  70.2

Table 2: Treatment Effect Summary

Same as the input fail_rate, which captures the treatment effect, including the median of survival, hazard ratio between treatment arms, and other related aspects.

design$fail_rate
# A tibble: 2 × 5
  stratum duration fail_rate dropout_rate    hr
  <chr>      <dbl>     <dbl>        <dbl> <dbl>
1 All            3    0.0770        0.001   0.9
2 All          100    0.0385        0.001   0.6

Table 3: Boundary Summary

A table summarizes the boundaries at each analysis, covering both the efficacy bounds (indicated by rows with bound == upper) and the futility bounds (indicated by rows with bound == lower).

design$bound
# A tibble: 3 × 7
  analysis bound probability probability0     z `~hr at bound` `nominal p`
     <int> <chr>       <dbl>        <dbl> <dbl>          <dbl>       <dbl>
1        1 upper     0.0257      0.000509  3.29          0.594    0.000509
2        1 lower     0.00486     0.100    -1.28          1.23     0.9     
3        2 upper     0.900       0.0250    1.96          0.818    0.0248  

Table 4: Analysis Summary

The table provides a summary of information at each analysis level, including details such as analysis time, sample size, events, and other relevant information. Please note that this table contains one single row for each analysis.

design$analysis
# A tibble: 2 × 9
  analysis  time     n event   ahr theta  info info0 info_frac
     <int> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>     <dbl>
1        1    12  702.  159. 0.811 0.210  39.2  39.8     0.419
2        2    24  842.  383. 0.715 0.335  93.5  95.6     1    

Summarize a design in gt

To generate a summary table in the gt format with a proper presentation, users can easily accomplish this by utilizing the summary() function and the as_gt() function, connected by the pipe operator |>. This allows for a concise and effective way to obtain a summary table that meets the desired format requirements.

These summarized gt tables include various highlighted features that allow users to customize the default table to fit the specific requirements of their studies.

design |>
  summary() |>
  as_gt()
Bound summary for AHR design
AHR approximations of ~HR at bound
Bound Z Nominal p1 ~HR at bound2 Cumulative boundary crossing probability
Alternate hypothesis Null hypothesis
Analysis: 1 Time: 12 N: 701.7 Event: 159.1 AHR: 0.81 Information fraction: 0.42
Futility -1.28 0.9000 1.2253 0.0049 0.1000
Efficacy 3.29 0.0005 0.5939 0.0257 0.0005
Analysis: 2 Time: 24 N: 842 Event: 382.6 AHR: 0.72 Information fraction: 1
Efficacy 1.96 0.0248 0.8181 0.9000 0.0250
1 One-sided p-value for experimental vs control treatment. Value < 0.5 favors experimental, > 0.5 favors control.
2 Approximate hazard ratio to cross bound.

Feature 1: Custom title, subtitle and spanner

The summary table is designed to display a default title, subtitle, and spanner. However, users are welcome to customize these texts to ensure that the table fits their specific study requirements.

design |>
  summary() |>
  as_gt(title = "Summary of Group Sequential Design Bounds",
        subtitle = "Showing Custom Title, Subtitle and Spanner",
        colname_spanner = "Cumulative probability to cross boundary")
Summary of Group Sequential Design Bounds
Showing Custom Title, Subtitle and Spanner
Bound Z Nominal p1 ~HR at bound2 Cumulative probability to cross boundary
Alternate hypothesis Null hypothesis
Analysis: 1 Time: 12 N: 701.7 Event: 159.1 AHR: 0.81 Information fraction: 0.42
Futility -1.28 0.9000 1.2253 0.0049 0.1000
Efficacy 3.29 0.0005 0.5939 0.0257 0.0005
Analysis: 2 Time: 24 N: 842 Event: 382.6 AHR: 0.72 Information fraction: 1
Efficacy 1.96 0.0248 0.8181 0.9000 0.0250
1 One-sided p-value for experimental vs control treatment. Value < 0.5 favors experimental, > 0.5 favors control.
2 Approximate hazard ratio to cross bound.

Feature 2: Custom display digits and select variables

The summary table is set to display two decimals by default. Nevertheless, users have the flexibility to output a greater number of decimals to ensure numerical accuracy, if desired.

design |>
  summary(
    analysis_vars = c("time", "event", "info_frac"), 
    analysis_decimals = c(0, 0, 2)
  ) |>
  as_gt(title = "Summary of Group Sequential Design Bounds",
        subtitle = "Demonstration of Number of Digits to Display")
Summary of Group Sequential Design Bounds
Demonstration of Number of Digits to Display
Bound Z Nominal p1 ~HR at bound2 Cumulative boundary crossing probability
Alternate hypothesis Null hypothesis
Analysis: 1 Time: 12 Event: 159 Information fraction: 0.42
Futility -1.28 0.9000 1.2253 0.0049 0.1000
Efficacy 3.29 0.0005 0.5939 0.0257 0.0005
Analysis: 2 Time: 24 Event: 383 Information fraction: 1
Efficacy 1.96 0.0248 0.8181 0.9000 0.0250
1 One-sided p-value for experimental vs control treatment. Value < 0.5 favors experimental, > 0.5 favors control.
2 Approximate hazard ratio to cross bound.

Feature 3: Custom columns to be displayed

The summary table currently displays all possible columns from the bound. However, users have the freedom to select specific columns they wish to include in order to create a more concise and streamlined summary table.

design |>
  summary() |>
  as_gt(display_columns = c("Analysis", "Bound", "Z", "Probability"),
        title = "Summary of Group Sequential Design Bounds",
        subtitle = "Showing Only Selected Columns")
Summary of Group Sequential Design Bounds
Showing Only Selected Columns
Bound Z Cumulative boundary crossing probability
Alternate hypothesis Null hypothesis
Analysis: 1 Time: 12 N: 701.7 Event: 159.1 AHR: 0.81 Information fraction: 0.42
Futility -1.28 0.0049 0.1000
Efficacy 3.29 0.0257 0.0005
Analysis: 2 Time: 24 N: 842 Event: 382.6 AHR: 0.72 Information fraction: 1
Efficacy 1.96 0.9000 0.0250

Feature 4: Custom footnotes

The table is designed to incorporate relevant footnotes by default. These footnotes are specifically included to assist users in understanding the statistical concepts underlying each reported column. This aids users in making statistical inferences to determine whether the hypothesis should be rejected or not.

Rather than default, users also have the flexibility to customize the footnotes in the summary table using a syntax similar to gt::tab_footnote(). This allows for further customization and refinement of the table’s footnotes to meet specific requirements.

design |>
  summary() |>
  as_gt(
    footnote = list(
      content = "Non-binding futility test at IA.",
      location = "Bound",
      attr = "colname"
    ),
    title = "Summary of Group Sequential Design Bounds",
    subtitle = "Demonstration of Footnotes"
  )
Summary of Group Sequential Design Bounds
Demonstration of Footnotes
Bound1 Z Nominal p ~HR at bound Cumulative boundary crossing probability
Alternate hypothesis Null hypothesis
Analysis: 1 Time: 12 N: 701.7 Event: 159.1 AHR: 0.81 Information fraction: 0.42
Futility -1.28 0.9000 1.2253 0.0049 0.1000
Efficacy 3.29 0.0005 0.5939 0.0257 0.0005
Analysis: 2 Time: 24 N: 842 Event: 382.6 AHR: 0.72 Information fraction: 1
Efficacy 1.96 0.0248 0.8181 0.9000 0.0250
1 Non-binding futility test at IA.

Output a design in rtf

In addition to save in the format of gt, users can also output the summary tables in rtf by using as_rtf().

design |>
  summary() |>
  as_rtf(file = "./design.rtf")