
Perform shortcut graphical multiple comparison procedures with group sequential designs
Source:R/graph_test_shortcut_gsd.R
graph_test_shortcut_gsd.RdExtends graph_test_shortcut() to group sequential designs where hypotheses
can be tested at multiple analyses (interim and final). At each analysis,
the significance level available for each hypothesis is determined by a
spending function evaluated at the information fraction. The group
sequential boundaries (critical values) are computed from the spending using
the joint distribution of test statistics across analyses.
The procedure supports two modes
controlled by the look_back parameter:
look_back = FALSE(default): At each analysis, rejection decisions are based on repeated p-values at the current analysis only. A repeated p-value at analysis \(k\) is the minimum significance level at which the group sequential boundary at analysis \(k\) would be crossed. The graphical shortcut procedure (graph_test_shortcut()) is applied at each analysis using repeated p-values, and the graph is updated before proceeding to the next analysis.look_back = TRUE: Rejection decisions are based on sequential p-values, which consider all analyses up to the current one. A sequential p-value is the minimum of repeated p-values across all analyses conducted so far. Like the default mode, the procedure processes analyses sequentially, applyinggraph_test_shortcut()at each analysis using sequential p-values and updating the graph before proceeding. When a hypothesis becomes testable at a later analysis (via graph update), itsfirst_rejected_atis set to the earliest analysis where its boundary was crossed, whiledecision_atrecords the analysis where the rejection was operationally processed.
Usage
graph_test_shortcut_gsd(
graph,
p,
alpha = 0.025,
info_frac,
spending_fn,
look_back = FALSE,
verbose = FALSE,
test_values = FALSE
)Arguments
- graph
An initial graph as returned by
graph_create().- p
A numeric matrix of p-values with \(m\) rows (hypotheses) and \(K\) columns (analyses), where \(K\) is the maximum number of analyses across all hypotheses. For hypotheses not tested at every analysis, use
NAfor the columns without data. Each hypothesis must have at least one non-NAvalue.- alpha
A numeric scalar of the overall significance level, which should be between 0 & 1. The default is 0.025 for one-sided hypothesis testing problems; another common choice is 0.05 for two-sided hypothesis testing problems.
- info_frac
Information fractions at each analysis. Can be:
A numeric vector of length \(K\) — same fractions for all hypotheses. Only allowed when
pcontains noNAvalues (i.e., all hypotheses have the same number of analyses).A numeric matrix with \(m\) rows (hypotheses) and \(K\) columns (analyses) — different fractions per hypothesis. When
pcontainsNApadding,info_fracmust be a matrix withNAin the same positions asp.
Non-
NAvalues must be positive and monotonically non-decreasing per hypothesis. Values greater than 1 are allowed (e.g., when more information is collected than planned). The spending functions cap the cumulative spending atalphafor information fractions at or above 1. The last non-NAvalue does not need to be 1, allowing the procedure to be applied up to an interim analysis.- spending_fn
Spending function(s) for computing group sequential boundaries. Can be:
A single function — applied to all hypotheses.
A list of \(m\) functions — one per hypothesis.
Each function must accept two arguments:
alpha(significance level) andinfo_frac(information fraction), and return the cumulative alpha spent. Built-in options includespending_of(),spending_pocock(),spending_hsd(), andspending_linear().- look_back
A logical scalar or vector controlling the testing strategy. Can be:
A single logical — applied to all hypotheses.
A logical vector of length \(m\) — one per hypothesis, allowing different strategies for different hypotheses.
For hypotheses with
look_back = FALSE(the default), rejection decisions at each analysis are based on repeated p-values at that analysis only. For hypotheses withlook_back = TRUE, rejection decisions are based on sequential p-values which consider all analyses up to the current one. Thelook_back = TRUEoption can lead to additional rejections because a hypothesis may have crossed its boundary at an earlier analysis but only becomes testable (via graph update) at a later analysis.- verbose
A logical scalar specifying whether to include the boundary table in results. When
verbose = TRUE, a table of nominal p-value boundaries is computed for each hypothesis at all possible weights from the graph's closure (viagraph_generate_weights()). This enables manual verification of rejection decisions. The default isFALSE.- test_values
A logical scalar specifying whether to include the per-analysis rejection details in results. When
test_values = TRUE, the rejection sequence, analysis at which each rejection occurred, and the nominal p-value boundaries are reported. The default istest_values = FALSE.
Value
An S3 object of class gsd_graph_report with a list of elements:
inputs- Input parameters, including the initial graph, p-values, alpha, information fractions, and spending functions.outputs- Output parameters:repeated_p- An m x K matrix of repeated p-values at each analysis,sequential_p- An m x K matrix of sequential p-values (cumulative minimum of repeated p-values),adjusted_p- Adjusted p-values from the shortcut procedure (adjusted repeated p-values whenlook_back = FALSE, adjusted sequential p-values whenlook_back = TRUE),rejected- Logical vector of rejection decisions,decision_at- Integer vector indicating the analysis at which each hypothesis's decision was made. For rejected hypotheses, this is the analysis where the rejection was operationally processed. For non-rejected hypotheses, this is the last analysis where the hypothesis was tested,first_rejected_at- Integer vector indicating the earliest analysis at which each hypothesis's boundary was crossed. For non-rejected hypotheses, this isNA. Whenlook_back = TRUE, this may be earlier thandecision_atif a hypothesis crossed its boundary at a prior analysis but only became testable at a later analysis,last_rejected_at- Integer vector indicating the latest analysis at which each hypothesis's boundary was crossed. For non-rejected hypotheses, this isNA. Comparingfirst_rejected_atandlast_rejected_atshows whether the rejection is supported by data at multiple analyses or only at a single analysis,rejection_sequence- Character vector giving the order in which hypotheses were rejected across all analyses,graph- Updated graph after removing all rejected hypotheses.
test_values- Per-analysis details (iftest_values = TRUE). A list of length \(K\) (one entry per analysis). Each entry is a data frame containing the hypothesis name, current weight, observed p-value, nominal boundary, and rejection decision at that analysis. Entries areNULLfor analyses where no hypotheses are active. Whenlook_back = TRUEand a hypothesis is rejected at an earlier analysis, additional rows show the nominal p-value and boundary at each prior analysis, with aLook_backcolumn indicating these rows.boundary_table- Boundary lookup table (ifverbose = TRUE). A named list with one data frame per hypothesis. Each data frame contains the columnsWeight,Alpha.Allocated, andBoundary.kfor each analysis \(k\), showing the nominal p-value boundary at each analysis for every possible weight from the graph's closure. This table is independent of observed p-values and can be used to manually verify rejection decisions.
References
Maurer, W., and Bretz, F. (2013). Multiple testing in group sequential trials using graphical approaches. Statistics in Biopharmaceutical Research, 5(4), 311-320.
Zhao, Y., Liu, Q., Sun, L. Z., and Anderson, K. M. (2025). Adjusted inference for multiple testing procedure in group-sequential designs. Biometrical Journal, 67(1), e70020. doi:10.1002/bimj.70020
See also
graph_test_shortcut() for the fixed-sample (non-sequential) shortcut
procedure, sequential_p() for computing sequential p-values,
repeated_p() for computing repeated p-values,
spending_of(), spending_pocock(), spending_hsd(),
spending_linear() for spending functions.
Examples
# A graphical procedure with two hypotheses tested at two analyses
hypotheses <- c(0.5, 0.5)
transitions <- rbind(c(0, 1), c(1, 0))
g <- graph_create(hypotheses, transitions)
# P-values at interim (50% info) and final (100% info) analyses
p <- rbind(
H1 = c(0.024, 0.01),
H2 = c(0.015, 0.005)
)
graph_test_shortcut_gsd(
graph = g,
p = p,
alpha = 0.025,
info_frac = c(0.5, 1),
spending_fn = spending_of
)
#>
#> Test parameters ($inputs) ------------------------------------------------------
#> Initial graph
#>
#> --- Hypothesis weights ---
#> H1: 0.5
#> H2: 0.5
#>
#> --- Transition weights ---
#> H1 H2
#> H1 0 1
#> H2 1 0
#>
#> Alpha = 0.025
#>
#> Information fractions
#> Analysis_1 Analysis_2
#> H1 0.5 1
#> H2 0.5 1
#>
#> P-values
#> Analysis_1 Analysis_2
#> H1 0.024000 0.010000
#> H2 0.015000 0.005000
#>
#> Spending functions
#> H1: O'Brien-Fleming
#> H2: O'Brien-Fleming
#>
#> Look back = FALSE
#>
#> Test summary ($outputs) --------------------------------------------------------
#> Hypothesis Adj.p* Reject Tested.at First.Rej.at Last.Rej.at Look.back
#> H1 0.010094 TRUE 2 2 2 FALSE
#> H2 0.010051 TRUE 2 2 2 FALSE
#> (*) Adjusted p-values account for both the group sequential design and the
#> graphical multiple comparison procedure. Based on repeated p-values when
#> look_back = FALSE, and sequential p-values when look_back = TRUE.
#>
#> Rejection sequence: H2 -> H1
#>
#> Final updated graph after removing rejected hypotheses
#>
#> --- Hypothesis weights ---
#> H1: NA
#> H2: NA
#>
#> --- Transition weights ---
#> H1 H2
#> H1 NA NA
#> H2 NA NA
#>
# With look_back = TRUE (sequential p-values)
graph_test_shortcut_gsd(
graph = g,
p = p,
alpha = 0.025,
info_frac = c(0.5, 1),
spending_fn = spending_of,
look_back = TRUE
)
#>
#> Test parameters ($inputs) ------------------------------------------------------
#> Initial graph
#>
#> --- Hypothesis weights ---
#> H1: 0.5
#> H2: 0.5
#>
#> --- Transition weights ---
#> H1 H2
#> H1 0 1
#> H2 1 0
#>
#> Alpha = 0.025
#>
#> Information fractions
#> Analysis_1 Analysis_2
#> H1 0.5 1
#> H2 0.5 1
#>
#> P-values
#> Analysis_1 Analysis_2
#> H1 0.024000 0.010000
#> H2 0.015000 0.005000
#>
#> Spending functions
#> H1: O'Brien-Fleming
#> H2: O'Brien-Fleming
#>
#> Look back = TRUE
#>
#> Test summary ($outputs) --------------------------------------------------------
#> Hypothesis Adj.p* Reject Tested.at First.Rej.at Last.Rej.at Look.back
#> H1 0.010094 TRUE 2 2 2 TRUE
#> H2 0.010051 TRUE 2 2 2 TRUE
#> (*) Adjusted p-values account for both the group sequential design and the
#> graphical multiple comparison procedure. Based on repeated p-values when
#> look_back = FALSE, and sequential p-values when look_back = TRUE.
#>
#> Rejection sequence: H2 -> H1
#>
#> Final updated graph after removing rejected hypotheses
#>
#> --- Hypothesis weights ---
#> H1: NA
#> H2: NA
#>
#> --- Transition weights ---
#> H1 H2
#> H1 NA NA
#> H2 NA NA
#>
# Different spending functions per hypothesis
graph_test_shortcut_gsd(
graph = g,
p = p,
alpha = 0.025,
info_frac = c(0.5, 1),
spending_fn = list(spending_of, spending_pocock)
)
#>
#> Test parameters ($inputs) ------------------------------------------------------
#> Initial graph
#>
#> --- Hypothesis weights ---
#> H1: 0.5
#> H2: 0.5
#>
#> --- Transition weights ---
#> H1 H2
#> H1 0 1
#> H2 1 0
#>
#> Alpha = 0.025
#>
#> Information fractions
#> Analysis_1 Analysis_2
#> H1 0.5 1
#> H2 0.5 1
#>
#> P-values
#> Analysis_1 Analysis_2
#> H1 0.024000 0.010000
#> H2 0.015000 0.005000
#>
#> Spending functions
#> H1: O'Brien-Fleming
#> H2: Pocock
#>
#> Look back = FALSE
#>
#> Test summary ($outputs) --------------------------------------------------------
#> Hypothesis Adj.p* Reject Tested.at First.Rej.at Last.Rej.at Look.back
#> H1 0.019426 TRUE 2 2 2 FALSE
#> H2 0.019426 TRUE 2 2 2 FALSE
#> (*) Adjusted p-values account for both the group sequential design and the
#> graphical multiple comparison procedure. Based on repeated p-values when
#> look_back = FALSE, and sequential p-values when look_back = TRUE.
#>
#> Rejection sequence: H2 -> H1
#>
#> Final updated graph after removing rejected hypotheses
#>
#> --- Hypothesis weights ---
#> H1: NA
#> H2: NA
#>
#> --- Transition weights ---
#> H1 H2
#> H1 NA NA
#> H2 NA NA
#>
# User-defined spending functions can also be used, e.g., wrapping
# gsDesign::sfHSD(). See vignette("group-sequential-testing") for details.
# Different information fractions per hypothesis
graph_test_shortcut_gsd(
graph = g,
p = p,
alpha = 0.025,
info_frac = rbind(c(0.5, 1), c(0.6, 1)),
spending_fn = spending_of
)
#>
#> Test parameters ($inputs) ------------------------------------------------------
#> Initial graph
#>
#> --- Hypothesis weights ---
#> H1: 0.5
#> H2: 0.5
#>
#> --- Transition weights ---
#> H1 H2
#> H1 0 1
#> H2 1 0
#>
#> Alpha = 0.025
#>
#> Information fractions
#> Analysis_1 Analysis_2
#> H1 0.5 1
#> H2 0.6 1
#>
#> P-values
#> Analysis_1 Analysis_2
#> H1 0.024000 0.010000
#> H2 0.015000 0.005000
#>
#> Spending functions
#> H1: O'Brien-Fleming
#> H2: O'Brien-Fleming
#>
#> Look back = FALSE
#>
#> Test summary ($outputs) --------------------------------------------------------
#> Hypothesis Adj.p* Reject Tested.at First.Rej.at Last.Rej.at Look.back
#> H1 0.010202 TRUE 2 2 2 FALSE
#> H2 0.010202 TRUE 2 2 2 FALSE
#> (*) Adjusted p-values account for both the group sequential design and the
#> graphical multiple comparison procedure. Based on repeated p-values when
#> look_back = FALSE, and sequential p-values when look_back = TRUE.
#>
#> Rejection sequence: H2 -> H1
#>
#> Final updated graph after removing rejected hypotheses
#>
#> --- Hypothesis weights ---
#> H1: NA
#> H2: NA
#>
#> --- Transition weights ---
#> H1 H2
#> H1 NA NA
#> H2 NA NA
#>
# Different numbers of analyses per hypothesis (NA padding)
# H1 at analyses 1-2, H2 at 1-3, H3 at 2-3, H4 at 1 and 3
g4 <- graph_create(
rep(0.25, 4),
rbind(
c(0, 1 / 3, 1 / 3, 1 / 3),
c(1 / 3, 0, 1 / 3, 1 / 3),
c(1 / 3, 1 / 3, 0, 1 / 3),
c(1 / 3, 1 / 3, 1 / 3, 0)
)
)
p4 <- rbind(
H1 = c(0.024, 0.01, NA),
H2 = c(0.015, 0.005, 0.001),
H3 = c(NA, 0.012, 0.004),
H4 = c(0.05, NA, 0.015)
)
# info_frac must be a matrix with NA matching p
info_frac4 <- rbind(
H1 = c(0.5, 1, NA),
H2 = c(1 / 3, 2 / 3, 1),
H3 = c(NA, 0.5, 1),
H4 = c(0.4, NA, 1)
)
graph_test_shortcut_gsd(
graph = g4,
p = p4,
alpha = 0.025,
info_frac = info_frac4,
spending_fn = spending_of
)
#>
#> Test parameters ($inputs) ------------------------------------------------------
#> Initial graph
#>
#> --- Hypothesis weights ---
#> H1: 0.25
#> H2: 0.25
#> H3: 0.25
#> H4: 0.25
#>
#> --- Transition weights ---
#> H1 H2 H3 H4
#> H1 0.000000 0.333333 0.333333 0.333333
#> H2 0.333333 0.000000 0.333333 0.333333
#> H3 0.333333 0.333333 0.000000 0.333333
#> H4 0.333333 0.333333 0.333333 0.000000
#>
#> Alpha = 0.025
#>
#> Information fractions
#> Analysis_1 Analysis_2 Analysis_3
#> H1 0.5000000 1.0000000 NA
#> H2 0.3333333 0.6666667 1
#> H3 NA 0.5000000 1
#> H4 0.4000000 NA 1
#>
#> P-values
#> Analysis_1 Analysis_2 Analysis_3
#> H1 0.024000 0.010000 NA
#> H2 0.015000 0.005000 0.001000
#> H3 NA 0.012000 0.004000
#> H4 0.050000 NA 0.015000
#>
#> Spending functions
#> H1: O'Brien-Fleming
#> H2: O'Brien-Fleming
#> H3: O'Brien-Fleming
#> H4: O'Brien-Fleming
#>
#> Look back = FALSE
#>
#> Test summary ($outputs) --------------------------------------------------------
#> Hypothesis Adj.p* Reject Tested.at First.Rej.at Last.Rej.at Look.back
#> H1 0.040376 FALSE 2 -- -- FALSE
#> H2 0.004075 TRUE 3 3 3 FALSE
#> H3 0.012051 TRUE 3 3 3 FALSE
#> H4 0.030085 FALSE 3 -- -- FALSE
#> (*) Adjusted p-values account for both the group sequential design and the
#> graphical multiple comparison procedure. Based on repeated p-values when
#> look_back = FALSE, and sequential p-values when look_back = TRUE.
#>
#> Rejection sequence: H2 -> H3
#>
#> Final updated graph after removing rejected hypotheses
#>
#> --- Hypothesis weights ---
#> H1: 0.5
#> H2: NA
#> H3: NA
#> H4: 0.5
#>
#> --- Transition weights ---
#> H1 H2 H3 H4
#> H1 0 NA NA 1
#> H2 NA NA NA NA
#> H3 NA NA NA NA
#> H4 1 NA NA 0
#>