@@ -805,21 +805,8 @@ subgroup is a subset of the all-subjects population, and the subgroup events
805805may better reflect the information available for the treatment effect
806806comparison.
807807
808- We define a helper function that creates a spending function with a custom
809- spending time:
810-
811- ``` {r spending-time-helper}
812- # Factory function: create a spending function that uses spending_time
813- # instead of info_frac for alpha allocation
814- make_spending_with_time <- function(base_spending_fn, spending_time) {
815- function(alpha, info_frac) {
816- # Use spending_time for alpha allocation, ignoring info_frac
817- # Truncate to match length (handles interim stops)
818- st <- spending_time[seq_along(info_frac)]
819- base_spending_fn(alpha, st)
820- }
821- }
822- ```
808+ The ` spending_with_time() ` function creates a spending function that uses
809+ a fixed spending time instead of the information fractions passed at runtime:
823810
824811For the oncology trial, $H_2$ (OS, all subjects) uses all-subjects OS events
825812(` r paste(c(529, 700, 800), collapse = ", ") ` ) for the correlation structure
@@ -828,13 +815,13 @@ For the oncology trial, $H_2$ (OS, all subjects) uses all-subjects OS events
828815
829816``` {r spending-time-setup}
830817# Spending time = subgroup event fractions (same as H1_OS_S)
831- spending_h2 <- make_spending_with_time (
818+ spending_h2 <- spending_with_time (
832819 spending_of,
833820 spending_time = c(185 / 295, 245 / 295, 1)
834821)
835822
836823# Similarly, H4 (PFS, all subjects) uses subgroup PFS event fractions
837- spending_h4 <- make_spending_with_time (
824+ spending_h4 <- spending_with_time (
838825 spending_of,
839826 spending_time = c(265 / 310, 1)
840827)
@@ -892,6 +879,134 @@ any function with the signature `function(alpha, info_frac)`, users can
892879encode arbitrary spending behaviors — including spending time separation —
893880without requiring changes to the ` graph_test_shortcut_gsd() ` interface.
894881
882+ ### Monitoring: Adjusting for Changed Final Information
883+
884+ In practice, the total information (e.g., total number of events) at the
885+ final analysis may differ from what was planned at the design stage. When
886+ this happens, the information fractions at earlier analyses change
887+ retroactively — not because the data changed, but because the denominator
888+ (planned total) is now different. This creates a challenge: the boundaries
889+ at earlier analyses were already computed using the ** planned** information
890+ fractions, but the correlation structure at the current analysis should
891+ reflect the ** actual** information fractions.
892+
893+ The spending time approach handles this naturally:
894+
895+ - ** Spending time** : use the planned information fractions to preserve the
896+ boundaries at analyses 1 and 2 (which have already been applied).
897+ - ** Information fraction** (` info_frac ` ): use the actual information fractions
898+ for the correlation structure, since this reflects the true joint
899+ distribution of the test statistics.
900+
901+ Consider the OS subgroup hypothesis ($H_1$) in the oncology trial. The
902+ trial was designed with a planned maximum of 295 OS events in the subgroup,
903+ giving planned information fractions of
904+ (` r paste(round(c(185, 245, 295) / 295, 3), collapse = ", ") ` ).
905+ Suppose that by the time of the final analysis, 310 events have been
906+ observed instead of 295. The actual information fractions are now
907+ (` r paste(round(c(185, 245, 310) / 310, 3), collapse = ", ") ` ):
908+
909+ ``` {r monitoring-setup}
910+ # Planned info fractions (used for boundaries at analyses 1 and 2)
911+ planned_if_h1 <- c(185 / 295, 245 / 295, 1)
912+
913+ # Actual info fractions (more events than planned at final analysis)
914+ actual_if_h1 <- c(185 / 310, 245 / 310, 1)
915+
916+ cat("Planned:", round(planned_if_h1, 4), "\n")
917+ cat("Actual: ", round(actual_if_h1, 4), "\n")
918+ ```
919+
920+ We create a spending function that uses the planned information fractions
921+ for alpha allocation, while the procedure uses the actual information
922+ fractions for the correlation structure:
923+
924+ ``` {r monitoring-spending}
925+ # Spending function using planned info fractions
926+ spending_h1_monitor <- spending_with_time(
927+ spending_of,
928+ spending_time = planned_if_h1
929+ )
930+ ```
931+
932+ To illustrate, we compare the boundaries computed under three scenarios:
933+
934+ 1 . ** Planned** : both spending and correlation use planned info fractions
935+ (the original design).
936+ 2 . ** Naive update** : both spending and correlation use actual info fractions
937+ (ignores that analyses 1 and 2 used planned boundaries).
938+ 3 . ** Correct monitoring** : spending uses planned info fractions, correlation
939+ uses actual info fractions.
940+
941+ ``` {r monitoring-comparison}
942+ alpha_h1 <- 0.01 # H1's allocated alpha
943+
944+ # Scenario 1: Planned (original design)
945+ bounds_planned <- gs_boundaries(alpha_h1, planned_if_h1, spending_of)
946+
947+ # Scenario 2: Naive update (both use actual)
948+ bounds_naive <- gs_boundaries(alpha_h1, actual_if_h1, spending_of)
949+
950+ # Scenario 3: Correct monitoring (spending=planned, correlation=actual)
951+ bounds_monitor <- gs_boundaries(alpha_h1, actual_if_h1, spending_h1_monitor)
952+
953+ monitor_table <- data.frame(
954+ Analysis = 1:3,
955+ Planned.IF = round(planned_if_h1, 4),
956+ Actual.IF = round(actual_if_h1, 4),
957+ Boundary.Planned = bounds_planned$bounds_nominal,
958+ Boundary.Naive = bounds_naive$bounds_nominal,
959+ Boundary.Monitor = bounds_monitor$bounds_nominal
960+ )
961+ knitr::kable(monitor_table, digits = 6,
962+ caption = paste("Boundaries under three scenarios",
963+ "(H1 with alpha = 0.01)"))
964+ ```
965+
966+ The key observations:
967+
968+ - ** Analyses 1 and 2** : the monitoring boundaries differ from the planned
969+ boundaries because the correlation structure has changed (the actual
970+ info fractions are smaller). However, the spending at these analyses is
971+ preserved — the same cumulative alpha is allocated.
972+ - ** Analysis 3** : the monitoring boundary reflects both the preserved
973+ spending schedule and the updated correlation structure.
974+ - ** Naive update** : changes the spending at all analyses, which is
975+ inconsistent with the boundaries already applied at analyses 1 and 2.
976+
977+ This approach extends naturally to the full graphical procedure. For
978+ monitoring at analysis 3, use the actual information fractions in
979+ ` info_frac ` and per-hypothesis spending functions with planned information
980+ fractions:
981+
982+ ``` {r monitoring-full}
983+ # Actual info fractions for all hypotheses
984+ # (only OS hypotheses affected; PFS and ORR are complete)
985+ actual_if_onc <- info_frac_onc
986+ actual_if_onc["H1_OS_S", ] <- c(185 / 310, 245 / 310, 1)
987+ actual_if_onc["H2_OS_A", ] <- c(529 / 830, 700 / 830, 1)
988+
989+ # Spending functions using planned info fractions for OS hypotheses
990+ spending_fn_monitor <- list(
991+ spending_with_time(spending_of, info_frac_onc["H1_OS_S", ]),
992+ spending_with_time(spending_of, info_frac_onc["H2_OS_A", ]),
993+ spending_of, # H3: PFS complete, no change
994+ spending_of, # H4: PFS complete, no change
995+ spending_of, # H5: ORR complete
996+ spending_of # H6: ORR complete
997+ )
998+
999+ result_monitor <- graph_test_shortcut_gsd(
1000+ graph = g_onc,
1001+ p = p_onc,
1002+ alpha = alpha_onc,
1003+ info_frac = actual_if_onc,
1004+ spending_fn = spending_fn_monitor,
1005+ look_back = TRUE
1006+ )
1007+ print(result_monitor)
1008+ ```
1009+
8951010## Additional Examples
8961011
8971012### Different Spending Functions per Hypothesis
0 commit comments