Skip to contents

Overview

This vignette introduces publication quality table production for group sequential designs in the gsDesign2 package.
It also demonstrates designs for an example scenario using multiple design approaches. We divide the document into 3 parts:

  • Design specification and derivation
  • Printing design summary tables
  • Details of output from design functions
  • Details on table output options

The reader can decide which of these sections is of interest to them.

The function used to generate bounds tables is gsDesign2::summary(). Users can use gsDesign2::as_gt() to format the above table using the gt package.

In this vignette, we introduce a general approach to bound summaries by examples using different design approaches for a time-to-event outcome:

Design Specification and Derivation

Design Parameters

The design parameters we use across the different designs derived are:

# enrollment/failure rates
enrollRates <- tibble::tibble(
   Stratum = "All", 
   duration = 12, 
   rate = 30)
failRates <- tibble::tibble(
   Stratum = "All", 
   duration = c(4, 100), 
   failRate = log(2) / 12,
   hr = c(1, .6), 
   dropoutRate = .001)

# Information fraction
IF <- (1:3)/3 
# Analysis times in months; first 2 will be ignored as IF will not be achieved
analysisTimes <- c(.01, .02, 36)  

# Experimental / Control randomization ratio
ratio <- 1 

# 1-sided Type I error
alpha <- 0.025 
# Type II error (1 - power)
beta <- 0.1 

# Upper bound
upper <- gsDesign2::gs_spending_bound     # alpha-spending bound
upar <- list(sf = gsDesign::sfLDOF, total_spend = 0.025, param = NULL, timing = NULL)
           
# Lower bound
lower <- gsDesign2::gs_spending_bound    # beta-spending bound
lpar <- list(sf = gsDesign::sfHSD, total_spend = 0.1, param = 0, timing = NULL)

# Fleming-Harrington (FH) weight functions for weighted logrank (WLR)
wgt00 <- function(x, arm0, arm1){    # Equal weighting for logrank
   gsDesign2::wlr_weight_fh(x, arm0, arm1, rho = 0, gamma = 0)}
wgt05 <- function(x, arm0, arm1){    # Early downweighting with FH(0,.5) 
   gsDesign2::wlr_weight_fh(x, arm0, arm1, rho = 0, gamma = .5)}

# Both of above tests for MaxCombo: logrank and FH(0,.5)
fh_test <- rbind(
   # Include logrank for all 3 analyses
   data.frame(rho = 0, gamma = 0, tau = -1, test = 1, Analysis = 1:3, analysisTimes = c(12, 24, 36)), 
   # Only include FH(0,.5) for analyses 2 and 3
   data.frame(rho = c(0, 0.5), gamma = 0.5, tau = -1, test = 2:3, Analysis = 3, analysisTimes = 36))

Deriving Designs

AHR design derivation

Using the design parameters above, the AHR design is derived as follows:

By using the design parameters above, one can generate an AHR model by gs_design_ahr as

x_design_ahr <- gs_design_ahr(
   enrollRates = enrollRates,
   failRates = failRates,
   IF = IF, 
   analysisTimes = analysisTimes, 
   ratio = ratio, 
   alpha = alpha, 
   beta = beta, 
   upper = upper,
   upar = upar,
   lower = lower,
   lpar = lpar
)

x_power_ahr <- gs_power_ahr(
   enrollRates = x_design_ahr$enrollRates,
   failRates = x_design_ahr$failRates,
   events = c(100, 200, 300),
   analysisTimes = NULL,
   upper = upper,
   upar = upar,
   lower = lower,
   lpar = lpar
)

WLR design derivation

x_design_wlr <- gs_design_wlr(
   enrollRates = enrollRates,
   failRates = failRates,
   weight = wgt05, 
   IF = NULL, 
   analysisTimes = sort(unique(x_design_ahr$analysis$Time)), 
   ratio = ratio, 
   alpha = alpha, 
   beta = beta,   
   upper = upper,
   upar = upar,
   lower = lower,
   lpar = lpar
)

x_power_wlr <- gs_power_wlr(
   enrollRates = x_design_wlr$enrollRates,
   failRates = x_design_wlr$failRates,
   weight = wgt05, 
   events = c(50, 100, 150),
   analysisTimes = NULL, 
   upper = upper,
   upar = upar,
   lower = lower,
   lpar = lpar
)

Default Summary Table Production

Instead of outputting 4 detailed tables (a table of enrollment rates, a table of failure rates, a table of analysis summary, a table of bounds summary), users can get a com pensive summary table by calling summary(x), where x is the object returned either by gs_design_ahr or gs_design_wlr. The summary() function produces an overall summary table for bounds for publication in a protocol.

For example, the default output of summary() for the AHR method is

x_design_ahr %>% 
  summary() %>% 
  gt::gt() %>% 
  gt::fmt_number(columns = c(3:6), decimals = 4)
Bound Z ~HR at bound Nominal p Alternate hypothesis Null hypothesis
Analysis: 1 Time: 11.7 N: 479.6 Events: 121.5 AHR: 0.85 IF: 0.33
Futility −0.9400 1.1856 0.8259 0.0338 0.1760
Efficacy 3.7100 0.5101 0.0001 0.0027 0.0001
Analysis: 2 Time: 20.3 N: 493.1 Events: 243 AHR: 0.74 IF: 0.66
Futility 0.6300 0.9228 0.2656 0.0666 0.7387
Efficacy 2.5100 0.7246 0.0060 0.4135 0.0061
Analysis: 3 Time: 36 N: 493.1 Events: 364.6 AHR: 0.69 IF: 1
Futility 1.9900 0.8118 0.0233 0.1006 0.9773
Efficacy 1.9900 0.8116 0.0231 0.9000 0.0246

Please note the summary() can also be applied to objected returned by gs_power_ahr(). For example,

x_power_ahr %>% 
  summary() %>% 
  gt::gt() %>% 
  gt::fmt_number(columns = c(3:6), decimals = 4)
Bound Z ~HR at bound Nominal p Alternate hypothesis Null hypothesis
Analysis: 1 Time: 10.4 N: 428.7 Events: 100 AHR: 0.87 IF: 0.33
Futility −1.1200 1.2537 0.8691 0.0341 0.0333
Efficacy 3.7100 0.4735 0.0001 0.0015 0.0001
Analysis: 2 Time: 16.7 N: 493.1 Events: 200 AHR: 0.78 IF: 0.67
Futility 0.1000 0.9863 0.4616 0.0671 0.0667
Efficacy 2.5100 0.6984 0.0060 0.2262 0.0060
Analysis: 3 Time: 26.2 N: 493.1 Events: 300 AHR: 0.71 IF: 1
Futility 1.3900 0.8503 0.0822 0.1006 0.1000
Efficacy 1.9900 0.7926 0.0231 0.8029 0.0250

And the default output of summary() for the WLR method is

x_design_wlr %>% 
  summary() %>% 
  gt::gt() %>% 
  gt::fmt_number(columns = c(3:6), decimals = 4)
Bound Z ~wHR at bound Nominal p Alternate hypothesis Null hypothesis
Analysis: 1 Time: 11.7 N: 361.4 Events: 91.6 wAHR: 0.79 IF: 0.14
Futility −1.1700 1.2773 0.8792 0.0141 0.1215
Efficacy 6.0200 0.2841 0.0000 0.0000 0.0000
Analysis: 2 Time: 20.3 N: 371.6 Events: 183.2 wAHR: 0.69 IF: 0.46
Futility 0.5700 0.9187 0.2831 0.0464 0.7227
Efficacy 3.1600 0.6273 0.0008 0.2139 0.0008
Analysis: 3 Time: 36 N: 371.6 Events: 274.7 wAHR: 0.64 IF: 1
Futility 1.9600 0.7890 0.0247 0.1001 0.9782
Efficacy 1.9600 0.7890 0.0247 0.9000 0.0225

Note that summary() can also be applied to summarize an object returned by gs_power_wlr().

x_power_wlr %>% 
  summary() %>% 
  gt::gt() %>% 
  gt::fmt_number(columns = c(3:6), decimals = 4)
Bound Z ~wHR at bound Nominal p Alternate hypothesis Null hypothesis
Analysis: 1 Time: 8.3 N: 255.8 Events: 50 wAHR: 0.86 IF: 0.13
Futility −2.2200 1.8741 0.9868 0.0134 0.0132
Efficacy 6.0600 0.1800 0.0000 0.0000 0.0000
Analysis: 2 Time: 12.3 N: 371.6 Events: 100 wAHR: 0.78 IF: 0.51
Futility −1.7300 1.4136 0.9583 0.0512 0.0508
Efficacy 2.9400 0.5558 0.0017 0.0362 0.0017
Analysis: 3 Time: 16.6 N: 371.6 Events: 150 wAHR: 0.72 IF: 1
Futility −1.4600 1.2682 0.9272 0.1001 0.1000
Efficacy 1.9700 0.7250 0.0245 0.4566 0.0250

Detailed Summary Table Formatting

Here we demonstrate options for formatting analysis rows, bound rows as well as other table parameters such as titles, labels and footnotes.

Custom the Variables to be Summaried for Each Analysis

In the above default table summary table generated by summary(x), the variables used to summarize each analysis includes Analysis, Time, N(sample size), Events, AHR, and IF (information fraction). But users can customize these variables chosen using analysis_vars = ... and the corresponding decimals displayed using the argument analysis_decimals = .... For example

summary(
   x_design_ahr,
   analysis_vars = c("N", "Events"),
   analysis_decimals = c(1, 1)
   ) %>% 
   gt::gt() %>% 
   gt::fmt_number(columns = c(3:6), decimals = 4)
Bound Z ~HR at bound Nominal p Alternate hypothesis Null hypothesis
Analysis: 1 N: 479.6 Events: 121.5
Futility −0.9400 1.1856 0.8259 0.0338 0.1760
Efficacy 3.7100 0.5101 0.0001 0.0027 0.0001
Analysis: 2 N: 493.1 Events: 243
Futility 0.6300 0.9228 0.2656 0.0666 0.7387
Efficacy 2.5100 0.7246 0.0060 0.4135 0.0061
Analysis: 3 N: 493.1 Events: 364.6
Futility 1.9900 0.8118 0.0233 0.1006 0.9773
Efficacy 1.9900 0.8116 0.0231 0.9000 0.0246

Please note that there is no need to input "Analysis" into analysis_vars = ... as it will always appear.

Custom the Bound Names

Users can also customize the bound names. In the default output generated by summary(x), the bound name is c("Efficacy", "Futility"), which can be changed into c("A is better", "B is better") for a 2-sided design by using the argument bound_names = .... For example,

summary(
   x_design_ahr,
   bound_names = c("A is better", "B is better")
   ) %>% 
   mutate_if(is.numeric, round, digits = 4) %>% 
   gt::gt() %>% 
   gt::fmt_number(columns = c(3:6), decimals = 4)
#> `mutate_if()` ignored the following grouping variables:
#>  Column `Analysis`
Bound Z ~HR at bound Nominal p Alternate hypothesis Null hypothesis
Analysis: 1 Time: 11.7 N: 479.6 Events: 121.5 AHR: 0.85 IF: 0.33
B is better −0.9400 1.1856 0.8259 0.0338 0.1760
A is better 3.7100 0.5101 0.0001 0.0027 0.0001
Analysis: 2 Time: 20.3 N: 493.1 Events: 243 AHR: 0.74 IF: 0.66
B is better 0.6300 0.9228 0.2656 0.0666 0.7387
A is better 2.5100 0.7246 0.0060 0.4135 0.0061
Analysis: 3 Time: 36 N: 493.1 Events: 364.6 AHR: 0.69 IF: 1
B is better 1.9900 0.8118 0.0233 0.1006 0.9773
A is better 1.9900 0.8116 0.0231 0.9000 0.0246

Custom into a gt Table and Add Title/SubTitle/Footnotes/Spanners

Users can also use as_gt() to get the the above R table into a gt table. Furthermore, they can edit the title/subtitle/spanner/footnotes of the gt table by using the arguments in summary.

summary(x_design_ahr) %>% 
   as_gt(title = "Summary of the Crossing Probability",
         subtitle = "by Using gs_design_ahr",
         colname_spanner = "Cumulative boundary crossing probability",
         colname_spannersub = c("Alternate hypothesis", "Null hypothesis"),
         footnote = list(content = c("approximate hazard ratio to cross bound.", 
                                     "gs_design_ahr is a function in gsDesign2.",
                                     "AHR is average hazard ratio; IF is information fraction."),
                         location = c("~HR at bound", NA, NA),
                         attr = c("colname", "subtitle", "analysis")))
Summary of the Crossing Probability
by Using gs_design_ahr1
Bound Nominal p ~HR at bound2 Cumulative boundary crossing probability
Alternate hypothesis Null hypothesis
Analysis: 1 Time: 11.7 N: 479.6 Events: 121.5 AHR: 0.85 IF: 0.333
Futility 0.8259 1.1856 0.0338 0.1760
Efficacy 0.0001 0.5101 0.0027 0.0001
Analysis: 2 Time: 20.3 N: 493.1 Events: 243 AHR: 0.74 IF: 0.663
Futility 0.2656 0.9228 0.0666 0.7387
Efficacy 0.0060 0.7246 0.4135 0.0061
Analysis: 3 Time: 36 N: 493.1 Events: 364.6 AHR: 0.69 IF: 13
Futility 0.0233 0.8118 0.1006 0.9773
Efficacy 0.0231 0.8116 0.9000 0.0246
1 gs_design_ahr is a function in gsDesign2.
2 approximate hazard ratio to cross bound.
3 AHR is average hazard ratio; IF is information fraction.

The above objective can also be realized by using functions in the R package gt for custom design of table layout. We note that as_gt() always produces a gt object and, thus, can be further customized with gt package formatting functions. In the future, we to support rich text format using a function as_rtf() in a fashion similar to as_gt().

Custom the Variables to Display

Users can select the variables to be displayed in the summary table by using the argument display_colunm = ....

summary(x_design_ahr) %>% 
   as_gt(display_columns = c("Analysis", "Bound", "Z", "Probability"))
Bound summary for AHR design
AHR approximations of ~HR at bound
Bound Z Cumulative boundary crossing probability
Alternate hypothesis Null hypothesis
Analysis: 1 Time: 11.7 N: 479.6 Events: 121.5 AHR: 0.85 IF: 0.33
Futility -0.94 0.0338 0.1760
Efficacy 3.71 0.0027 0.0001
Analysis: 2 Time: 20.3 N: 493.1 Events: 243 AHR: 0.74 IF: 0.66
Futility 0.63 0.0666 0.7387
Efficacy 2.51 0.4135 0.0061
Analysis: 3 Time: 36 N: 493.1 Events: 364.6 AHR: 0.69 IF: 1
Futility 1.99 0.1006 0.9773
Efficacy 1.99 0.9000 0.0246

Custom Whether to Show Infinity Bound or Not

Users have options to either show the infinity bounds or not by taking advantage of display_inf_bound = ....

summary(x_design_ahr) %>% 
   as_gt(display_inf_bound = FALSE)
Bound summary for AHR design
AHR approximations of ~HR at bound
Bound Nominal p1 ~HR at bound2 Cumulative boundary crossing probability
Alternate hypothesis Null hypothesis
Analysis: 1 Time: 11.7 N: 479.6 Events: 121.5 AHR: 0.85 IF: 0.33
Futility 0.8259 1.1856 0.0338 0.1760
Efficacy 0.0001 0.5101 0.0027 0.0001
Analysis: 2 Time: 20.3 N: 493.1 Events: 243 AHR: 0.74 IF: 0.66
Futility 0.2656 0.9228 0.0666 0.7387
Efficacy 0.0060 0.7246 0.4135 0.0061
Analysis: 3 Time: 36 N: 493.1 Events: 364.6 AHR: 0.69 IF: 1
Futility 0.0233 0.8118 0.1006 0.9773
Efficacy 0.0231 0.8116 0.9000 0.0246
1 One-sided p-value for experimental vs control treatment. Values < 0.5 favor experimental, > 0.5 favor control.
2 Approximate hazard ratio to cross bound.

Details of Output from Design/Power Functions

There are four components in the objects returned by either gs_design_ahr()/gs_design_wlr() or gs_power_ahr()/gs_power_wlr(): 1. failure rates: a table summarizing failure rate and dropout rate. 1. enrollment rates: a table summarizing the enrollment rate. 1. bounds: a table summarize the bound of each analysis. 1. analysis: a table summarize the each analysis, with each one row for one analysis one hypothsis.

Failure Rates

The failure rates of different gsDesign object can be obtained by using x$failRates, where x is the object returned either by gs_design_ahr or gs_design_wlr. For example, the failure rates of the AHR design derivation can be returned by calling

x_design_ahr$failRates %>% 
   gt::gt() %>% 
   gt::fmt_number(columns = 3:5, decimals = 4)
Stratum duration failRate hr dropoutRate
All 4 0.0578 1.0000 0.0010
All 100 0.0578 0.6000 0.0010

Please note that both x_design_ahr and x_wlr returns the same failure rates, which is the same as that inputted as failRates. To verify, let’s take a look at the failure rate of the WLR design derivation, which are shown as below.

x_design_wlr$failRates %>% 
   gt::gt() %>% 
   gt::fmt_number(columns = 3:5, decimals = 4)
Stratum duration failRate hr dropoutRate
All 4 0.0578 1.0000 0.0010
All 100 0.0578 0.6000 0.0010

Enrollment

The enrollment rate of a gs design derivation can be collected by using x$failRates, where x is the object returned either by gs_design_ahr or gs_design_wlr. For example, the enrollment rates of the AHR/WLR design derivation is

x_design_ahr$enrollRates %>% 
   gt::gt() %>% 
   gt::fmt_number(columns = 3, decimals = 4)
Stratum duration rate
All 12 41.0885
x_design_wlr$enrollRates %>% 
   gt::gt() %>% 
   gt::fmt_number(columns = 3, decimals = 4)
Stratum duration rate
All 12 30.9652

It can be seen that, although the design derivation is different, the enrollment rate table share the same table structure, same enrollment period durations for each rate. Yet, the enrollment rates differ between designs only by a multiplicative constant.

Analysis

The analysis summary table has the structure of one row per analysis per hypothesis. And columns can vary with different defaults for each design option. This type of tables are useful for understanding commonalities in how designs are summarized for different models. To get analysis summary table, users can call x$analysis, where x is the object returned either by gs_design_ahr or gs_design_wlr. For example, the analysis summary of the AHR/WLR design derivation is

x_design_ahr$analysis %>% 
   gt::gt() %>% 
   gt::fmt_number(columns = 2:8, decimals = 4)
Analysis Time N Events AHR theta info info0 IF
1 11.6713 479.5569 121.5178 0.8487 0.1641 29.8909 30.3795 0.3322753
2 20.2531 493.0625 243.0357 0.7427 0.2974 59.4218 60.7589 0.6605497
3 36.0000 493.0625 364.5535 0.6917 0.3686 89.9581 91.1384 1.0000000
x_design_wlr$analysis %>% 
   gt::gt() %>% 
   gt::fmt_number(columns = 2:8, decimals = 4)
Analysis Time N Events AHR theta info info0 IF
1 11.6713 361.4046 91.5785 0.7914 0.5482 3.4990 3.5197 0.1402577
2 20.2531 371.5827 183.1570 0.6893 0.6956 11.5392 11.7776 0.4625453
3 36.0000 371.5827 274.7355 0.6442 0.6818 24.9472 26.3532 1.0000000

Bounds

The analysis summary table has the structure of One row per analysis per bound per hypothesis. Columns can vary with different defaults for each design option. To get a bouns summary table, users can call x$analysis, where x is the object returned either by gs_design_ahr or gs_design_wlr. For example, the bounds summary of the AHR/WLR design derivation is

x_design_ahr$bounds %>% 
   gt::gt() %>% 
   gt::fmt_number(columns = c(3, 5:7), decimals = 4) 
Analysis Bound Probability Probability0 Z ~HR at bound Nominal p
1 Upper 0.0027 0.0001164589 3.7103 0.5101 0.0001
1 Lower 0.0338 0.1760139199 −0.9382 1.1856 0.8259
2 Upper 0.4135 0.0060607133 2.5114 0.7246 0.0060
2 Lower 0.0666 0.7387133267 0.6262 0.9228 0.2656
3 Upper 0.9000 0.0245632791 1.9931 0.8116 0.0231
3 Lower 0.1006 0.9772765948 1.9905 0.8118 0.0233
x_design_wlr$bounds %>% 
   gt::gt() %>% 
   gt::fmt_number(columns = c(3, 5:7), decimals = 4)
Analysis Bound Probability Probability0 Z ~HR at bound Nominal p
1 Upper 0.0000 9.608385e-10 6.0220 0.2841 0.0000
1 Lower 0.0141 1.214531e-01 −1.1712 1.2773 0.8792
2 Upper 0.2139 7.998330e-04 3.1560 0.6273 0.0008
2 Lower 0.0464 7.227075e-01 0.5738 0.9187 0.2831
3 Upper 0.9000 2.251441e-02 1.9646 0.7890 0.0247
3 Lower 0.1001 9.781786e-01 1.9646 0.7890 0.0247

References

Mukhopadhyay, Pralay, Wenmei Huang, Paul Metcalfe, Fredrik Öhrn, Mary Jenner, and Andrew Stone. 2020. “Statistical and Practical Considerations in Designing of Immuno-Oncology Trials.” Journal of Biopharmaceutical Statistics 30 (6): 1130–46.

Yung, Godwin, and Yi Liu. 2019. “Sample Size and Power for the Weighted Log-Rank Test and Kaplan-Meier Based Tests with Allowance for Nonproportional Hazards.” Biometrics.