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
enroll_rate <- define_enroll_rate(
  duration = 12,
  rate = 30
)
fail_rate <- define_fail_rate(
  duration = c(4, 100),
  fail_rate = log(2) / 12,
  hr = c(1, .6),
  dropout_rate = .001
)

# Information fraction
info_frac <- (1:3) / 3
# Analysis times in months; first 2 will be ignored as info_frac will not be achieved
analysis_time <- 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, analysis_time = 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, analysis_time = 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(
  enroll_rate = enroll_rate,
  fail_rate = fail_rate,
  info_frac = info_frac,
  analysis_time = analysis_time,
  ratio = ratio,
  alpha = alpha,
  beta = beta,
  upper = upper,
  upar = upar,
  lower = lower,
  lpar = lpar
)

x_power_ahr <- gs_power_ahr(
  enroll_rate = x_design_ahr$enroll_rate,
  fail_rate = x_design_ahr$fail_rate,
  event = c(100, 200, 300),
  analysis_time = NULL,
  upper = upper,
  upar = upar,
  lower = lower,
  lpar = lpar
)

WLR design derivation

x_design_wlr <- gs_design_wlr(
  enroll_rate = enroll_rate,
  fail_rate = fail_rate,
  weight = wgt05,
  info_frac = NULL,
  analysis_time = sort(unique(x_design_ahr$analysis$time)),
  ratio = ratio,
  alpha = alpha,
  beta = beta,
  upper = upper,
  upar = upar,
  lower = lower,
  lpar = lpar
) |> to_integer()

x_power_wlr <- gs_power_wlr(
  enroll_rate = x_design_wlr$enroll_rate,
  fail_rate = x_design_wlr$fail_rate,
  weight = wgt05,
  event = c(50, 100, 150),
  analysis_time = 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 Event: 121.5 AHR: 0.85 Information fraction: 0.33
Futility −0.9400 1.1856 0.8259 0.0338 0.1741
Efficacy 3.7100 0.5101 0.0001 0.0027 0.0001
Analysis: 2 Time: 20.3 N: 493.1 Event: 243 AHR: 0.74 Information fraction: 0.66
Futility 0.6300 0.9228 0.2656 0.0666 0.7368
Efficacy 2.5100 0.7246 0.0060 0.4135 0.0060
Analysis: 3 Time: 36 N: 493.1 Event: 364.6 AHR: 0.69 Information fraction: 1
Futility 1.9900 0.8118 0.0233 0.1006 0.9753
Efficacy 1.9900 0.8116 0.0231 0.9000 0.0245

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 Event: 100 AHR: 0.87 Information fraction: 0.33
Futility −1.1200 1.2537 0.8691 0.0341 0.1309
Efficacy 3.7100 0.4735 0.0001 0.0015 0.0001
Analysis: 2 Time: 16.7 N: 493.1 Event: 200 AHR: 0.78 Information fraction: 0.67
Futility 0.1000 0.9863 0.4616 0.0671 0.5450
Efficacy 2.5100 0.6984 0.0060 0.2262 0.0060
Analysis: 3 Time: 26.2 N: 493.1 Event: 300 AHR: 0.71 Information fraction: 1
Futility 1.3900 0.8503 0.0822 0.1006 0.9188
Efficacy 1.9900 0.7926 0.0231 0.8029 0.0249

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.6 N: 360.3 Event: 91 AHR: 0.79 Information fraction: 0.14
Futility −1.1900 1.2832 0.8828 0.0140 0.1172
Efficacy 3.7300 0.4580 0.0001 0.0034 0.0001
Analysis: 2 Time: 20.2 N: 372 Event: 183 AHR: 0.69 Information fraction: 0.46
Futility 0.5700 0.9196 0.2853 0.0462 0.7198
Efficacy 2.5200 0.6894 0.0059 0.4368 0.0060
Analysis: 3 Time: 36 N: 372 Event: 275 AHR: 0.64 Information fraction: 1
Futility 1.9900 0.7871 0.0236 0.1001 0.9756
Efficacy 2.0200 0.7836 0.0216 0.8957 0.0228

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.9 Event: 50 AHR: 0.86 Information fraction: 0.13
Futility −1.8100 1.6700 0.9651 0.0134 0.0349
Efficacy 6.0600 0.1799 0.0000 0.0000 0.0000
Analysis: 2 Time: 12.3 N: 372 Event: 100 AHR: 0.78 Information fraction: 0.51
Futility −0.5900 1.1248 0.7218 0.0512 0.2877
Efficacy 2.9400 0.5559 0.0017 0.0362 0.0017
Analysis: 3 Time: 16.6 N: 372 Event: 150 AHR: 0.72 Information fraction: 1
Futility 0.4100 0.9358 0.3422 0.1001 0.6745
Efficacy 1.9700 0.7250 0.0245 0.4561 0.0249

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.

Customize the variables to be summarized 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), event, AHR, and info_frac (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", "event"),
  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 Event: 121.5
Futility −0.9400 1.1856 0.8259 0.0338 0.1741
Efficacy 3.7100 0.5101 0.0001 0.0027 0.0001
Analysis: 2 N: 493.1 Event: 243
Futility 0.6300 0.9228 0.2656 0.0666 0.7368
Efficacy 2.5100 0.7246 0.0060 0.4135 0.0060
Analysis: 3 N: 493.1 Event: 364.6
Futility 1.9900 0.8118 0.0233 0.1006 0.9753
Efficacy 1.9900 0.8116 0.0231 0.9000 0.0245

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

Customize 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 Event: 121.5 AHR: 0.85 Information fraction: 0.33
B is better −0.9400 1.1856 0.8259 0.0338 0.1741
A is better 3.7100 0.5101 0.0001 0.0027 0.0001
Analysis: 2 Time: 20.3 N: 493.1 Event: 243 AHR: 0.74 Information fraction: 0.66
B is better 0.6300 0.9228 0.2656 0.0666 0.7368
A is better 2.5100 0.7246 0.0060 0.4135 0.0060
Analysis: 3 Time: 36 N: 493.1 Event: 364.6 AHR: 0.69 Information fraction: 1
B is better 1.9900 0.8118 0.0233 0.1006 0.9753
A is better 1.9900 0.8116 0.0231 0.9000 0.0245

Customize 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; info_frac 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 Z Nominal p ~HR at bound2 Cumulative boundary crossing probability
Alternate hypothesis Null hypothesis
Analysis: 1 Time: 11.7 N: 479.6 Event: 121.5 AHR: 0.85 Information fraction: 0.333
Futility -0.94 0.8259 1.1856 0.0338 0.1741
Efficacy 3.71 0.0001 0.5101 0.0027 0.0001
Analysis: 2 Time: 20.3 N: 493.1 Event: 243 AHR: 0.74 Information fraction: 0.663
Futility 0.63 0.2656 0.9228 0.0666 0.7368
Efficacy 2.51 0.0060 0.7246 0.4135 0.0060
Analysis: 3 Time: 36 N: 493.1 Event: 364.6 AHR: 0.69 Information fraction: 13
Futility 1.99 0.0233 0.8118 0.1006 0.9753
Efficacy 1.99 0.0231 0.8116 0.9000 4 0.0245
1 gs_design_ahr is a function in gsDesign2.
2 approximate hazard ratio to cross bound.
3 AHR is average hazard ratio; info_frac is information fraction.
4 Cumulative alpha for final analysis (0.0245) is less than the full alpha (0.025) when the futility bound is non-binding. The smaller value subtracts the probability of crossing a futility bound before crossing an efficacy bound at a later analysis (0.025 - 0.0005 = 0.0245) under the null hypothesis.

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().

Customize 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 Event: 121.5 AHR: 0.85 Information fraction: 0.33
Futility -0.94 0.0338 0.1741
Efficacy 3.71 0.0027 0.0001
Analysis: 2 Time: 20.3 N: 493.1 Event: 243 AHR: 0.74 Information fraction: 0.66
Futility 0.63 0.0666 0.7368
Efficacy 2.51 0.4135 0.0060
Analysis: 3 Time: 36 N: 493.1 Event: 364.6 AHR: 0.69 Information fraction: 1
Futility 1.99 0.1006 0.9753
Efficacy 1.99 0.9000 1 0.0245
1 Cumulative alpha for final analysis (0.0245) is less than the full alpha (0.025) when the futility bound is non-binding. The smaller value subtracts the probability of crossing a futility bound before crossing an efficacy bound at a later analysis (0.025 - 0.0005 = 0.0245) under the null hypothesis.

Customize 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 Z Nominal p1 ~HR at bound2 Cumulative boundary crossing probability
Alternate hypothesis Null hypothesis
Analysis: 1 Time: 11.7 N: 479.6 Event: 121.5 AHR: 0.85 Information fraction: 0.33
Futility -0.94 0.8259 1.1856 0.0338 0.1741
Efficacy 3.71 0.0001 0.5101 0.0027 0.0001
Analysis: 2 Time: 20.3 N: 493.1 Event: 243 AHR: 0.74 Information fraction: 0.66
Futility 0.63 0.2656 0.9228 0.0666 0.7368
Efficacy 2.51 0.0060 0.7246 0.4135 0.0060
Analysis: 3 Time: 36 N: 493.1 Event: 364.6 AHR: 0.69 Information fraction: 1
Futility 1.99 0.0233 0.8118 0.1006 0.9753
Efficacy 1.99 0.0231 0.8116 0.9000 3 0.0245
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.
3 Cumulative alpha for final analysis (0.0245) is less than the full alpha (0.025) when the futility bound is non-binding. The smaller value subtracts the probability of crossing a futility bound before crossing an efficacy bound at a later analysis (0.025 - 0.0005 = 0.0245) under the null hypothesis.

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 hypothesis.

Failure rates

The failure rates of different gsDesign object can be obtained by using x$fail_rate, 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$fail_rate %>%
  gt::gt() %>%
  gt::fmt_number(columns = 3:5, decimals = 4)
stratum duration fail_rate dropout_rate hr
All 4 0.0578 0.0010 1.0000
All 100 0.0578 0.0010 0.6000

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

x_design_wlr$fail_rate %>%
  gt::gt() %>%
  gt::fmt_number(columns = 3:5, decimals = 4)
stratum duration fail_rate dropout_rate hr
All 4 0.0578 0.0010 1.0000
All 100 0.0578 0.0010 0.6000

Enrollment

The enrollment rate of a gs design derivation can be collected by using x$fail_rate, 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$enroll_rate %>%
  gt::gt() %>%
  gt::fmt_number(columns = 3, decimals = 4)
stratum duration rate
All 12 41.0885
x_design_wlr$enroll_rate %>%
  gt::gt() %>%
  gt::fmt_number(columns = 3, decimals = 4)
stratum duration rate
All 12 31.0000

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 event ahr theta info info0 info_frac
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 event ahr theta info info0 info_frac info_frac0
1 11.6211 360.2556 91.0000 0.7926 0.5447 3.4558 3.4759 0.1384101 0.1317923
2 20.2095 372.0000 182.9999 0.6898 0.6949 11.5089 11.7455 0.4609515 0.4453419
3 35.9888 372.0000 275.0000 0.6443 0.6819 24.9676 26.3742 1.0000000 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 bonus 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$bound %>%
  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.0001035057 3.7103 0.5101 0.0001
1 lower 0.0338 0.1740609319 −0.9382 1.1856 0.8259
2 upper 0.4135 0.0060482351 2.5114 0.7246 0.0060
2 lower 0.0666 0.7368187135 0.6262 0.9228 0.2656
3 upper 0.9000 0.0245385477 1.9931 0.8116 0.0231
3 lower 0.1006 0.9753350071 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.0034 9.762582e-05 3.7251 0.4580 0.0001
1 lower 0.0140 1.171678e-01 −1.1893 1.2832 0.8828
2 upper 0.4368 5.998566e-03 2.5158 0.6894 0.0059
2 lower 0.0462 7.198316e-01 0.5672 0.9196 0.2853
3 upper 0.8957 2.283770e-02 2.0223 0.7836 0.0216
3 lower 0.1001 9.756012e-01 1.9851 0.7871 0.0236

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. 2020. “Sample Size and Power for the Weighted Log-Rank Test and Kaplan-Meier Based Tests with Allowance for Nonproportional Hazards.” Biometrics 76 (3): 939–50.