Summarize Group Sequential Designs in Nice gt Tables
Source:vignettes/story_summarize_designs.Rmd
story_summarize_designs.Rmd
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:
- the average hazard ratio (AHR) method extended from Mukhopadhyay et al. (2020) using
gsDesign2::gs_design_ahr()
; - the weighted logrank (WLR) method of Yung and Liu (2019) using
gsDesign2::gs_design_wlr()
;
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
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,
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
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()
.
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 = ...
.
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 = ...
.
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.