diff --git a/articles/articles/story-update-boundary.html b/articles/articles/story-update-boundary.html new file mode 100644 index 00000000..bf88541c --- /dev/null +++ b/articles/articles/story-update-boundary.html @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/articles/gsDesign2.html b/articles/gsDesign2.html index 76e34578..8921f0bc 100644 --- a/articles/gsDesign2.html +++ b/articles/gsDesign2.html @@ -170,23 +170,23 @@

Enrollment rates) enroll_rate %>% gt() -
- @@ -670,23 +670,23 @@

Failure and dropout rates) fail_rate %>% gt()

-
- @@ -1161,23 +1161,23 @@

Fixed designd %>% summary() %>% as_gt()

-
- @@ -1637,23 +1637,23 @@

Fixed design
 d$enroll_rate %>% gt()

-
- @@ -2180,23 +2180,23 @@

Simple efficacy bound definition title = "1-sided group sequential bound using AHR method", subtitle = "Lan-DeMets spending to approximate O'Brien-Fleming bound" )

-
- @@ -2743,23 +2743,23 @@

Symmetric 2-sided bounds title = "2-sided symmetric group sequential bound using AHR method", subtitle = "Lan-DeMets spending to approximate O'Brien-Fleming bound" )

-
- @@ -3329,23 +3329,23 @@

Asymmetric 2-sided bounds subtitle = "Lan-DeMets spending to approximate O'Brien-Fleming bound for efficacy, futility disaster check at IA1, IA2 only" )

-
- diff --git a/articles/index.html b/articles/index.html index 991b657f..210db5c4 100644 --- a/articles/index.html +++ b/articles/index.html @@ -107,6 +107,8 @@

Designs by AHR

Spending time examples
+
Efficacy and futility boundary update
+

Designs with binary endpoints

diff --git a/articles/story-ahr-under-nph.html b/articles/story-ahr-under-nph.html index e052d1da..383b8d51 100644 --- a/articles/story-ahr-under-nph.html +++ b/articles/story-ahr-under-nph.html @@ -276,23 +276,23 @@

Computing average hazard ratio) avehr %>% gt()

-
- @@ -742,23 +742,23 @@

Computing average hazard ratio= as.numeric(total_duration) ) xx %>% gt()

-
- @@ -1224,23 +1224,23 @@

Computing average hazard ratioxx %>% summarize(AHR = exp(sum(event * log(hr) / sum(event)))) %>% gt()

-
- @@ -1704,23 +1704,23 @@

Deriving the design) avehr %>% gt()

-
- @@ -2245,23 +2245,23 @@

Simulation to verify power ) %>% gt() %>% fmt_number(column = 2:9, decimals = 3)

-
- @@ -2775,23 +2775,23 @@

Simulation to verify power -
- @@ -3280,23 +3280,23 @@

Computing average hazard ratio
 ahr2 <- ahr(enroll_rate, fail_rate, total_duration)
 ahr2 %>% gt()

-
- @@ -3741,23 +3741,23 @@

Computing average hazard ratio
 xx <- pw_info(enroll_rate, fail_rate, total_duration)
 xx %>% gt()

-
- @@ -4230,23 +4230,23 @@

Computing average hazard ratio ungroup() %>% summarise(lnhr = sum(event * log(hr)) / sum(event), AHR = exp(lnhr)) %>% gt()

-
- @@ -4709,23 +4709,23 @@

Deriving the design= fail_rate, total_duration = total_duration ) %>% gt()

-
- @@ -5215,23 +5215,23 @@

Simulation to verify powersummarise(rate = sum(rate), duration = last(duration)) er %>% gt()

-
- @@ -5713,23 +5713,23 @@

Simulation to verify power) %>% gt() %>% fmt_number(column = 2:9, decimals = 3)

-
- @@ -6242,23 +6242,23 @@

Simulation to verify power= fail_rate, total_duration = total_duration ) %>% gt()

-
- diff --git a/articles/story-compare-power-delay-effect.html b/articles/story-compare-power-delay-effect.html index ed0fd6b6..ead5d70d 100644 --- a/articles/story-compare-power-delay-effect.html +++ b/articles/story-compare-power-delay-effect.html @@ -122,23 +122,23 @@

Overviewenroll_rate %>% gt() %>% tab_header(title = "Enrollment Table of Scenario 1")

-
- @@ -584,23 +584,23 @@

Overviewfail_rate %>% gt() %>% tab_header(title = "Failure Table of Scenario 1")

-
- @@ -1204,23 +1204,23 @@

Overview fmt_number(columns = 4, decimals = 2) %>% fmt_number(columns = 5, decimals = 4) %>% fmt_number(columns = 6:11, decimals = 2)

-
- @@ -1813,23 +1813,23 @@

An Alternative Scenarioenroll_rate %>% gt() %>% tab_header(title = "Enrollment Table of Scenario 2")

-
- @@ -2275,23 +2275,23 @@

An Alternative Scenariofail_rate %>% gt() %>% tab_header(title = "Failure Table of Scenario 2")

-
- @@ -2873,23 +2873,23 @@

An Alternative Scenario fmt_number(columns = 4, decimals = 2) %>% fmt_number(columns = 5, decimals = 4) %>% fmt_number(columns = 6:11, decimals = 2)

-
- diff --git a/articles/story-design-with-ahr.html b/articles/story-design-with-ahr.html index 82d46fae..6e229e4a 100644 --- a/articles/story-design-with-ahr.html +++ b/articles/story-design-with-ahr.html @@ -393,23 +393,23 @@

Fixed Design using AHR and Logrank title = "Sample Size and Events Required by Scenario", subtitle = "36 Month Trial duration, 2.5% One-sided Type 1 Error, 90% Power" )

-
- @@ -920,23 +920,23 @@

Fixed Design using AHR and Logrank title = "Sample Size and Events Required by Trial Duration", subtitle = "Delayed Effect of 4 Months, HR = 0.6 Thereafter; 90% Power" )

-
- @@ -1456,23 +1456,23 @@

Alternate Hypothesis Mappingpivot_wider(names_from = Scenario, values_from = AHR1) %>% gt() %>% fmt_number(columns = 2:5, decimals = 3)

-
- @@ -1969,23 +1969,23 @@

AHR method) summary(nph_asymmetric) %>% as_gt()

-
- @@ -2559,23 +2559,23 @@

AHR methodgt() %>% fmt_number(columns = "event", decimals = 1) %>% fmt_number(columns = 5:10, decimals = 4)

-
- @@ -3284,23 +3284,23 @@

Weighted Logrank Method) %>% gt() %>% fmt_number(columns = 3:6, decimals = 4)

-
- diff --git a/articles/story-design-with-ahr_files/figure-html/unnamed-chunk-2-1.png b/articles/story-design-with-ahr_files/figure-html/unnamed-chunk-2-1.png index 7bd5fe45..fa93e7d5 100644 Binary files a/articles/story-design-with-ahr_files/figure-html/unnamed-chunk-2-1.png and b/articles/story-design-with-ahr_files/figure-html/unnamed-chunk-2-1.png differ diff --git a/articles/story-design-with-ahr_files/figure-html/unnamed-chunk-5-1.png b/articles/story-design-with-ahr_files/figure-html/unnamed-chunk-5-1.png index 6850ac23..941e6802 100644 Binary files a/articles/story-design-with-ahr_files/figure-html/unnamed-chunk-5-1.png and b/articles/story-design-with-ahr_files/figure-html/unnamed-chunk-5-1.png differ diff --git a/articles/story-design-with-ahr_files/figure-html/unnamed-chunk-7-1.png b/articles/story-design-with-ahr_files/figure-html/unnamed-chunk-7-1.png index 7287cdf1..aea4dd62 100644 Binary files a/articles/story-design-with-ahr_files/figure-html/unnamed-chunk-7-1.png and b/articles/story-design-with-ahr_files/figure-html/unnamed-chunk-7-1.png differ diff --git a/articles/story-design-with-ahr_files/figure-html/unnamed-chunk-8-1.png b/articles/story-design-with-ahr_files/figure-html/unnamed-chunk-8-1.png index edab558d..84171015 100644 Binary files a/articles/story-design-with-ahr_files/figure-html/unnamed-chunk-8-1.png and b/articles/story-design-with-ahr_files/figure-html/unnamed-chunk-8-1.png differ diff --git a/articles/story-design-with-spending.html b/articles/story-design-with-spending.html index fe3e7cd2..881f9089 100644 --- a/articles/story-design-with-spending.html +++ b/articles/story-design-with-spending.html @@ -124,23 +124,23 @@

Scenario for considerationenroll_rate |> gt::gt() |> gt::tab_header(title = "Planned Relative Enrollment Rates")

-
- @@ -614,23 +614,23 @@

Scenario for considerationfail_rate |> gt::gt() |> gt::tab_header(title = "Table of Failure Rate Assumptions")

-
- @@ -1145,23 +1145,23 @@

Group sequential designgs |> summary() |> gt::gt()

-
- diff --git a/articles/story-integer-design.html b/articles/story-integer-design.html index be63aa59..fbd3118d 100644 --- a/articles/story-integer-design.html +++ b/articles/story-integer-design.html @@ -149,23 +149,23 @@

Binary outcome subtitle = "on binary endpoints (unstratified design)" ) %>% fmt_number(columns = 2:5, decimals = 4)

-
- @@ -688,23 +688,23 @@

Survival outcome subtitle = "on survival endpoints (unstratified design)" ) %>% fmt_number(columns = 2:5, decimals = 4)

-
- @@ -1255,23 +1255,23 @@

Stratified design= "on binary endpoints (unstratified design)" ) %>% fmt_number(columns = 2:5, decimals = 4)

-
- diff --git a/articles/story-nph-futility.html b/articles/story-nph-futility.html index 29ff13d5..321d95a3 100644 --- a/articles/story-nph-futility.html +++ b/articles/story-nph-futility.html @@ -151,23 +151,23 @@

Initial design set-up for fixe as_gt(footnote = "Power based on 512 events") %>% fmt_number(columns = 3:4, decimals = 2) %>% fmt_number(columns = 5:6, decimals = 3)

-
- @@ -654,23 +654,23 @@

Modified Wieand futility bound= "Group sequential design with futility only at interim analyses", subtitle = "Wieand futility rule stops if HR > 1" )

-
- @@ -1194,23 +1194,23 @@

Beta-spending futility bound with title = "Group sequential design with futility only", subtitle = "Beta-spending futility bound" )

-
- @@ -1743,23 +1743,23 @@

Accumulation of events by time ratio = 1 ) head(event_accumulation, n = 7) %>% gt()

-
- diff --git a/articles/story-nph-futility_files/figure-html/unnamed-chunk-7-1.png b/articles/story-nph-futility_files/figure-html/unnamed-chunk-7-1.png index c1e6f800..45467236 100644 Binary files a/articles/story-nph-futility_files/figure-html/unnamed-chunk-7-1.png and b/articles/story-nph-futility_files/figure-html/unnamed-chunk-7-1.png differ diff --git a/articles/story-power-evaluation-with-spending-bound.html b/articles/story-power-evaluation-with-spending-bound.html index e0c6cb9d..b69676bc 100644 --- a/articles/story-power-evaluation-with-spending-bound.html +++ b/articles/story-power-evaluation-with-spending-bound.html @@ -343,23 +343,23 @@

Power Calculations
 h1 <- gs_info_binomial(p1 = .15, p2 = .1, xi1 = .5, n = c(350, 700, 1400))
 h1 %>% gt()

-
- @@ -841,23 +841,23 @@

Power Calculations) %>% gt() %>% fmt_number(columns = 3:10, decimals = 4)

-
- @@ -1385,23 +1385,23 @@

Power Calculations) %>% gt() %>% fmt_number(columns = 3:10, decimals = 4)

-
- diff --git a/articles/story-risk-difference_files/figure-html/unnamed-chunk-3-1.png b/articles/story-risk-difference_files/figure-html/unnamed-chunk-3-1.png index cdc9423f..2ea44854 100644 Binary files a/articles/story-risk-difference_files/figure-html/unnamed-chunk-3-1.png and b/articles/story-risk-difference_files/figure-html/unnamed-chunk-3-1.png differ diff --git a/articles/story-seven-test-types.html b/articles/story-seven-test-types.html index 1c09ed4a..c602e8a4 100644 --- a/articles/story-seven-test-types.html +++ b/articles/story-seven-test-types.html @@ -187,23 +187,23 @@

Example 1: Efficacy bound onlyone_sided |> summary() |> gsDesign2::as_gt(title = "Efficacy bound only", subtitle = "alpha-spending")

-
- @@ -773,23 +773,23 @@

Example 2: Symmetric 2-sided design< title = "2-sided Symmetric Design", subtitle = "Single spending function" )

-
- @@ -1384,23 +1384,23 @@

= "2-sided asymmetric design with binding futility", subtitle = "Both alpha- and beta-spending used" )

-
- @@ -1994,23 +1994,23 @@

= "2-sided asymmetric design with non-binding futility", subtitle = "Both alpha- and beta-spending used" )

-
- @@ -2614,23 +2614,23 @@

= "2-sided asymmetric safety design with binding futility", subtitle = "Alpha-spending used for both bounds, asymmetrically" )

-
- @@ -3216,23 +3216,23 @@

= "Alpha-spending used for both bounds, asymmetrically" ) |> gt::tab_footnote(footnote = "Integer-based sample size and event counts")

-
- @@ -3861,23 +3861,23 @@

Example 7: Alternate bound types subtitle = "Futility bounds computed to approximate HR" ) |> gt::tab_footnote(footnote = "Integer-based sample size and event counts")

-
- diff --git a/articles/story-spending-time-example.html b/articles/story-spending-time-example.html index ef0f846d..3c10c545 100644 --- a/articles/story-spending-time-example.html +++ b/articles/story-spending-time-example.html @@ -242,23 +242,23 @@

Fixed design, delayed effect= c("time", "n", "event", "ahr", "info_frac"), analysis_decimals = c(0, 0, 0, 4, 4) ) %>% as_gt()

-
- @@ -773,23 +773,23 @@

Scenario 1: less experimental bene yy %>% summary() %>% as_gt()

-
- @@ -1299,23 +1299,23 @@

Scenario 1: less experimental bene yy %>% summary() %>% as_gt()

-
- @@ -1829,23 +1829,23 @@

Scenario 2: low control event rates< yy %>% summary() %>% as_gt()

-
- @@ -2352,23 +2352,23 @@

Scenario 2: low control event rates< yy %>% summary() %>% as_gt()

-
- @@ -2997,23 +2997,23 @@

Planned designxx %>% summary() %>% as_gt()

-
- @@ -3546,23 +3546,23 @@

Calendar spendingyy %>% summary() %>% as_gt()

-
- @@ -4132,23 +4132,23 @@

Fixed increm fho %>% summary() %>% as_gt()

-
- @@ -4743,23 +4743,23 @@

Scenario with less treatment effect filter(Bound == "Efficacy") %>% gt() %>% fmt_number(columns = 3:6, decimals = 4)

-
- @@ -5240,23 +5240,23 @@

Scenario with less treatment effect summary() %>% gt() %>% fmt_number(columns = 3:6, decimals = 4)

-
- @@ -5753,23 +5753,23 @@

Scenario with longer control median summary() %>% gt() %>% fmt_number(columns = 3:6, decimals = 4)

-
- @@ -6247,23 +6247,23 @@

Scenario with longer control median summary() %>% gt() %>% fmt_number(columns = 3:6, decimals = 4)

-
- @@ -6821,23 +6821,23 @@

Planned design for bio summary() %>% gt() %>% fmt_number(columns = 3:6, decimals = 4)

-
- @@ -7309,23 +7309,23 @@

Planned design for overall popula # Using this inflation factor, set planned enrollment rates planned_enroll_rate <- enroll_rate %>% mutate(rate = rate * inflation_factor) planned_enroll_rate %>% gt()

-
- @@ -7780,23 +7780,23 @@

Planned design for overall popula ) overall_enroll_rate %>% gt()

-
- @@ -8278,23 +8278,23 @@

Planned design for overall popula summary() %>% gt() %>% fmt_number(columns = 3:6, decimals = 4)

-
- @@ -8832,23 +8832,23 @@
Biomarker subgroup powerpositive_60_enroll_rate %>% gt() %>% fmt_number(columns = "rate", decimals = 1)
-
- @@ -9317,23 +9317,23 @@
Biomarker subgroup power summary() %>% gt() %>% fmt_number(columns = 3:6, decimals = 4)
-
- @@ -9822,23 +9822,23 @@
Overall population power summary() %>% gt() %>% fmt_number(columns = 3:6, decimals = 4)
-
- @@ -10318,23 +10318,23 @@
Overall population power summary() %>% gt() %>% fmt_number(columns = 3:6, decimals = 4)
-
- @@ -10817,23 +10817,23 @@

Biomarker subgroup pre positive_40_enroll_rate %>% gt() %>% fmt_number(columns = "rate", decimals = 1)

-
- @@ -11305,23 +11305,23 @@
Biomarker positive subgroup power summary() %>% gt() %>% fmt_number(columns = 3:6, decimals = 4)
-
- @@ -11802,23 +11802,23 @@
Overall population power summary() %>% gt() %>% fmt_number(columns = 3:6, decimals = 4)
-
- diff --git a/articles/story-update-boundary.html b/articles/story-update-boundary.html new file mode 100644 index 00000000..504da465 --- /dev/null +++ b/articles/story-update-boundary.html @@ -0,0 +1,3915 @@ + + + + + + + + +Efficacy and futility boundary update • gsDesign2 + + + + + + + + + + + + + + + + + + Skip to contents + + +
+ + + + +
+
+ + + +
+

Design assumptions +

+

We assume two analyses: an interim analysis (IA) and a final analysis +(FA). The IA is planned 20 months after opening enrollment, followed by +the FA at month 36. The planned enrollment period spans 14 months, with +the first 2 months having an enrollment rate of 1/3 the final rate, the +next 2 months with a rate of 2/3 of the final rate, and the final rate +for the remaining 10 months. To obtain the targeted 90% power, these +rates will be multiplied by a constant. The control arm is assumed to +follow an exponential distribution with a median of 9 months and the +dropout rate is 0.0001 per month regardless of treatment group. Finally, +the experimental treatment group is piecewise exponential with a 3-month +delayed treatment effect; that is, in the first 3 months HR = 1 and the +HR is 0.6 thereafter.

+

We use the null hypothesis information for boundary crossing +probability calculations under both the null and alternate hypotheses. +This will also imply the null hypothesis information will be used for +the information fraction used in spending functions to derive the +design.

+
+
+

One-sided design +

+

For the design, we have efficacy bounds at both the IA and FA. We use +the Lan and DeMets (1983) spending +function with a total alpha of 0.025, which approximates an +O’Brien-Fleming bound.

+
+

Planned design +

+
+upper <- gs_spending_bound
+upar <- list(sf = gsDesign::sfLDOF, total_spend = alpha, param = NULL)
+
+x <- gs_design_ahr(
+  enroll_rate = enroll_rate,
+  fail_rate = fail_rate,
+  alpha = alpha,
+  beta = beta,
+  info_frac = NULL,
+  info_scale = "h0_info",
+  analysis_time = analysis_time,
+  ratio = ratio,
+  upper = gs_spending_bound,
+  upar = upar,
+  test_upper = TRUE,
+  lower = gs_b,
+  lpar = rep(-Inf, 2),
+  test_lower = FALSE
+) |> to_integer()
+

The planned design targets:

+
    +
  • Planned events: 193, 297
  • +
  • Planned information fraction for interim and final analysis: 0.6498, +1
  • +
  • Planned alpha spending: 0.0054, 0.025
  • +
  • Planned efficacy bounds: 2.5473, 1.9896
  • +
+

We note that rounding up the final targeted events increases power +slightly over the targeted 90%.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Original design
BoundZNominal p1 +~HR at bound2 + + Cumulative boundary crossing probability +
Alternate hypothesisNull hypothesis
Analysis: 1 Time: 19.9 N: 366 Event: 193 AHR: 0.73 Information fraction: 0.65
Efficacy2.550.00540.69300.34940.0054
Analysis: 2 Time: 35.8 N: 366 Event: 297 AHR: 0.68 Information fraction: 1
Efficacy1.990.02330.79380.90230.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.
+
+
+
+

Update bounds at time of analysis +

+

We assume 180 and 280 events observed at the IA and FA, respectively. +We will assume the differences from planned are due to logistical +considerations. We also assume the protocol specifies that the full +\(\alpha\) will be spent at the final +analysis even in a case like this where there is a shortfall of events +versus the design plan.

+
+# Observed vs. planned events
+event_observed <- c(180, 280)
+event_planned <- x$analysis$event
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
Planned vs. Observed events
AnalysisPlanned eventsObserved events
IA193180
FA297280
+
+

We will utilize the gs_power_npe() function to update +efficacy bounds based on the observed events. The details of its +arguments and implementations are explained in the Appendix.

+
+# Take spending setup from original design
+upar <- x$upper$upar
+# Now update timing parameter for the interim analysis.
+# Interim spending based on observed events divided by final planned events.
+# The remaining alpha will be allocated to FA.
+upar$timing <- c(event_observed[1] / max(event_planned), 1)
+
+x_updated <- gs_power_npe(
+  # `theta = 0` provides the crossing probability under H0
+  theta = 0,
+  # Observed statistical information under H0
+  info = event_observed * x$input$ratio / (1 + x$input$ratio)^2,
+  info_scale = "h0_info",
+  # Upper bound uses spending function from planned design
+  upper = x$input$upper,
+  upar = x$input$upar,
+  test_upper = x$input$test_upper,
+  # No lower bound, but copy this from input
+  lower = x$input$lower,
+  lpar = x$input$lpar,
+  test_lower = x$input$test_lower,
+  # Binding
+  binding = x$input$binding
+)
+

The updated efficacy bounds are 2.547, 1.99.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Updated design
with observed 180, 280 events
analysisboundzprobability1 +thetatheta1info_fracinfoinfo0info1
1upper2.5473070.005427893000.6428571454545
2upper1.9904660.025000000001.0000000707070
+1 Crossing pbability under H0.
+
+
+
+
+

Two-sided asymmetric design, beta-spending with +non-binding lower bound +

+

In this section, we investigate a 2 sided asymmetric design, with the +non-binding beta-spending futility bounds. Beta-spending refers to error +spending for the lower bound crossing probabilities under the +alternative hypothesis. Non-binding assumes the trial continues if the +lower bound is crossed for Type I, but not Type II error +computation.

+
+

Planned design +

+

In the original designs, we employ the Lan-DeMets spending function +used to approximate O’Brien-Fleming bounds (Lan +and DeMets 1983) for both efficacy and futility bounds. The total +spending for efficacy is 0.025, and for futility is 0.1. Besides, we +assume the futility test only happens at IA.

+
+upper <- gs_spending_bound
+upar <- list(sf = gsDesign::sfLDOF, total_spend = alpha, param = NULL)
+lower <- gs_spending_bound
+lpar <- list(sf = gsDesign::sfLDOF, total_spend = beta, param = NULL)
+
+x <- gs_design_ahr(
+  enroll_rate = enroll_rate,
+  fail_rate = fail_rate,
+  alpha = alpha,
+  beta = beta,
+  info_frac = NULL,
+  info_scale = "h0_info",
+  analysis_time = c(20, 36),
+  ratio = ratio,
+  upper = gs_spending_bound,
+  upar = upar,
+  test_upper = TRUE,
+  lower = lower,
+  lpar = lpar,
+  test_lower = c(TRUE, FALSE),
+  binding = FALSE
+) |> to_integer()
+

In the planned design, we have

+
    +
  • Planned events: 202, 311
  • +
  • Planned information fraction (timing): 0.6495, 1
  • +
  • Planned alpha spending: 0.0054167, 0.025
  • +
  • Planned efficacy bounds: 2.548, 1.9895
  • +
  • Planned futility bounds: 0.4778
  • +
+

Since we added futility bounds, the sample size and number of events +are larger than what we have in the 1-sided example.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Original design
BoundZNominal p1 +~HR at bound2 + + Cumulative boundary crossing probability +
Alternate hypothesisNull hypothesis
Analysis: 1 Time: 19.9 N: 382 Event: 202 AHR: 0.73 Information fraction: 0.65
Futility0.480.31640.93500.04130.6836
Efficacy2.550.00540.69870.36920.0054
Analysis: 2 Time: 36.1 N: 382 Event: 311 AHR: 0.68 Information fraction: 1
Efficacy1.990.02330.79800.9030 +3 0.0247
+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.0247) 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.0003 = 0.0247) under the null hypothesis.
+
+
+
+

Update bounds at time of analysis +

+

In practice, let us assume the observed data is generated by +simtrial::sim_pw_surv().

+
+set.seed(42)
+
+observed_data <- simtrial::sim_pw_surv(
+  n = x$analysis$n[x$analysis$analysis == 2],
+  stratum = data.frame(stratum = "All", p = 1),
+  block = c(rep("control", 2), rep("experimental", 2)),
+  enroll_rate = x$enroll_rate,
+  fail_rate = (fail_rate |> simtrial::to_sim_pw_surv())$fail_rate,
+  dropout_rate = (fail_rate |> simtrial::to_sim_pw_surv())$dropout_rate
+)
+
+observed_ia_data <- observed_data |> simtrial::cut_data_by_date(analysis_time[1])
+observed_fa_data <- observed_data |> simtrial::cut_data_by_date(analysis_time[2])
+
+# Observed vs. planned events
+event_observed <- c(sum(observed_ia_data$event), sum(observed_fa_data$event))
+event_planned <- x$analysis$event
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
Planned vs. Observed events
AnalysisPlanned number of eventsObserved number of events
IA202199
FA311312
+
+

Again, we use gs_power_npe() to calculate the updated +efficacy and futility bounds. The details of its arguments and +implementations are explained in the Appendix. We initially set up +theta = 0 for crossing probability under the null +hypothesis and theta0 = 0 for null hypothesis. As for +theta1, there are 2 options to set it up. These 2 options +arrive at the same value of theta1.

+
    +
  • +Option 1: theta1 is the weighted +average of the piecewise HR, that is,
  • +
+

\[ + -\log(\text{AHR}) = -\log(\sum_{i=1}^m w_i \; \text{HR}_m), +\]

+

where the weight is decided by the ratio of observed number of events +and the observed final events. In this example, the number of observed +events at the first 3 months (HR = 1) can be derived as

+
+event_first_3_month <- sum(observed_data$fail_time < 3)
+event_first_3_month
+
## [1] 72
+

and the number of observed events after 3 months (HR = 0.6) are

+
+event_after_3_month <- event_observed[2] - event_first_3_month
+event_after_3_month
+
## [1] 240
+

We can derive theta1 as -[72 / 312 \(\times \log(1) +\) 240 / 312 \(\times \log(0.6)\)] = 0.3929.

+
## [1] 0.3929428
+
    +
  • +Option 2: we use the blinded data to estimate the +AHR and theta1 is the negative logarithm of the estimated +AHR.
  • +
+
+blinded_ahr <- ahr_blinded(
+  surv = survival::Surv(
+    time = observed_fa_data$tte,
+    event = observed_fa_data$event
+  ),
+  intervals = c(3, Inf),
+  hr = c(1, 0.6),
+  ratio = 1
+)
+
+ + + + + + + + + + + + + + + + + + + +
Blinded estimation of average hazard ratio
eventahrthetainfo0
3120.67506740.392942878
+
+
## [1] 0.3929428
+

Both Option 1 and Option 2 yield the same value of +theta1. Option 1 is applicable when the event counts are +available, while Option 2 is suitable for scenarios where time-to-event +data is available, without the corresponding event counts. A value of +theta1 is computed with one of the above methods and +incorporated below.

+
+x_updated <- gs_power_npe(
+  # `theta = 0` provides the crossing probability under H0.
+  # Users have the flexibility to modify the value of `theta`,
+  # if they are interested in the crossing probability under H1 or other scenarios.
+  theta = 0,
+  # -log(AHR) = 0 for H0 is used for determining upper spending
+  theta0 = 0,
+  # -log(AHR) for H1 is used for determining the lower spending and bounds
+  theta1 = theta1,
+  # Observed statistical information under H0 with equal randomization
+  info = event_observed * ratio / (1 + ratio)^2,
+  # Upper bound
+  upper = x$input$upper,
+  upar = list(
+    sf = gsDesign::sfLDOF,
+    total_spend = alpha,
+    param = NULL,
+    # The remaining alpha will be allocated to the FA stage
+    timing = c(event_observed[1] / max(event_planned), 1)
+  ),
+  test_upper = TRUE,
+  # Lower bound
+  lower = x$input$lower,
+  lpar = list(
+    sf = gsDesign::sfLDOF,
+    total_spend = beta,
+    param = NULL,
+    # The remaining alpha will be allocated to the FA stage
+    timing = c(event_observed[1] / max(event_planned), 1)
+  ),
+  test_lower = c(TRUE, FALSE),
+  binding = x$input$binding
+)
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Updated design
by event fraction based timing
analysisboundzprobabilitythetatheta1info_fracinfoinfo0info1
1upper2.5704630.00507813600.39294280.637820549.7549.7549.75
2upper1.9879790.02286587400.39294281.000000078.0078.0078.00
1lower1.0180470.84567222200.39294280.637820549.7549.7549.75
+
+
+
+
+

Appendix +

+
+

Arguments of gs_power_npe() +

+

We provide an introduction to its arguments.

+
    +
  • +3 theta-related arguments: +
      +
    • +theta: a vector with the natural parameter for the +group sequential design; represents expected ; \(-\log(\text{AHR})\) in this case. This is +used for boundary crossing probability calculation. For example, if we +set theta = 0, then the crossing probability is the Type I +error.
    • +
    • +theta0: natural parameter for H0; used for determining +the upper (efficacy) spending and bounds. If the default of +NULL is given, then this is replaced by 0.
    • +
    • +theta1: natural parameter for H1; used for determining +the lower spending and bounds. The default is NULL, in +which case the value is replaced with the input theta.
    • +
    +
  • +
  • +3 statistical information-related arguments: +
      +
    • +info: statistical information at all analyses for input +theta. Default is 1.
    • +
    • +info0: statistical information under H0, if different +than info. It impacts null hypothesis bound calculation. +Default is NULL.
    • +
    • +info1: statistical information under hypothesis used +for futility bound calculation if different from info. It +impacts futility hypothesis bound calculation. Default is +NULL.
    • +
    +
  • +
  • +Efficacy/Futility boundary-related arguments: +
      +
    • +upper, upar and test_upper: +efficacy bounds related, same as gs_design_ahr().
    • +
    • +lower, lpar and test_lower: +futility bounds related, same as gs_design_ahr().
    • +
    • +binding: TRUE or FALSE +indicating whether it is binding or non-binding designs.
    • +
    +
  • +
+
+
+

Explanations of gs_power_npe() arguments set up in the +one-sided design +

+

We initially set up theta = 0, which provides the +crossing probability under the null hypothesis. Users have the +flexibility to modify the value of theta if they are +interested in the crossing probability under alternative hypotheses or +other scenarios. In this implementation, we leave the setup of +theta0 with its default imputed value of 0, +indicating the null hypothesis. Additionally, as we are solely dealing +with efficacy bounds, we do not adjust theta1, which +influences the futility bound.

+

Moving forward, we proceed to set up the values of the +info arguments for the input theta = 0. Since +the statistical information is event-based, and theta = 0 +(null hypothesis), the observed statistical information under null +hypothesis is

+

\[ + \text{observed number of events} \times \frac{r}{1 + r} \times +\frac{1}{1 + r}, +\]

+

where \(r\) is the randomization +ratio (experimental : control). In this example, the observed +statistical information is calculated as +info = event_observed / 4, and thus, we input 49.75, 78 for +the info argument. In this case, we retain the default +imputed value for info0, which is set as info. +The info represents the statistical information under the +null hypothesis (theta = 0). Furthermore, we leave +info1 as it is, considering our focus on efficacy bounds, +while noting that info1 does affect the futility +bounds.

+

Lastly, we establish the parameters for the upper and lower bounds. +The setup of upper, upar, +test_upper, lower, lpar, and +test_lower closely resembles the original design, with the +exception of the upar setup. Here, we have introduced the +timing parameter. At the IA, the observed number of events +is 199. If we employ the interim analysis alpha spending strategy, the +spending timing at IA is calculated as 199 / 311. Here, 311 represents +the planned number of events at FA. The remaining alpha will be +allocated to the FA stage.

+
+
+

Explanations of gs_power_npe() arguments set up in the +two-sided asymmetric design, beta-spending with non-binding lower +bound +

+

Moving forward, we proceed to set up the values of the +info arguments for the input theta = 0. +Similar to the examples in the 1-sided design above, we set +info as 199, 312/4, where 199, 312 is the observed number +of events. Because info0 is under null hypothesis, we leave +it and use its default imputed values same as info. +Furthermore, we leave info1 as its default imputed values +same as info with the local null hypothesis approach. +Though people can explicitly write the formula of info1 in +terms of the observed events, the difference is very minor, and it would +be easiest with unblinded data.

+

Lastly, we establish the parameters for the upper and lower bounds, +following the similar procedure as we have in the 1-sided design.

+
+
+
+

References +

+
+
+Lan, K. K. Gordon, and David L DeMets. 1983. “Discrete Sequential +Boundaries for Clinical Trials.” Biometrika 70 (3): +659–63. +
+
+
+
+
+ + + +
+ + + +
+
+ + + + + + + diff --git a/index.html b/index.html index ab92644f..3af4b940 100644 --- a/index.html +++ b/index.html @@ -110,7 +110,7 @@

Installationinstall.packages("gsDesign2")

Or install the development version from GitHub with:

-remotes::install_github("Merck/gsDesign2")
+remotes::install_github("Merck/gsDesign2")

Use cases diff --git a/pkgdown.yml b/pkgdown.yml index 2d56c168..28afa3ba 100644 --- a/pkgdown.yml +++ b/pkgdown.yml @@ -19,8 +19,9 @@ articles: story-risk-difference: story-risk-difference.html story-seven-test-types: story-seven-test-types.html story-spending-time-example: story-spending-time-example.html + story-update-boundary: story-update-boundary.html gsDesign2: gsDesign2.html -last_built: 2024-03-13T16:37Z +last_built: 2024-03-18T17:16Z urls: reference: https://merck.github.io/gsDesign2/reference article: https://merck.github.io/gsDesign2/articles diff --git a/reference/ahr_blinded.html b/reference/ahr_blinded.html index a71217d3..dd597d4f 100644 --- a/reference/ahr_blinded.html +++ b/reference/ahr_blinded.html @@ -1,11 +1,15 @@ -Blinded estimation of average hazard ratio — ahr_blinded • gsDesign2Blinded estimation of average hazard ratio — ahr_blinded • gsDesign2 @@ -16,7 +20,7 @@ gsDesign2 - 0.2.0.9000 + 1.1.1.1