Skip to content

Commit 7c5ec68

Browse files
committed
last.rej.at and updated spending functions
1 parent ddf3071 commit 7c5ec68

10 files changed

Lines changed: 229 additions & 86 deletions

R/graph_test_shortcut_gsd.R

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,11 @@
104104
#' hypotheses, this is `NA`. When `look_back = TRUE`, this may be
105105
#' earlier than `decision_at` if a hypothesis crossed its boundary at
106106
#' a prior analysis but only became testable at a later analysis,
107+
#' * `last_rejected_at` - Integer vector indicating the latest analysis
108+
#' at which each hypothesis's boundary was crossed. For non-rejected
109+
#' hypotheses, this is `NA`. Comparing `first_rejected_at` and
110+
#' `last_rejected_at` shows whether the rejection is supported by
111+
#' data at multiple analyses or only at a single analysis,
107112
#' * `rejection_sequence` - Character vector giving the order in which
108113
#' hypotheses were rejected across all analyses,
109114
#' * `graph` - Updated graph after removing all rejected hypotheses.
@@ -333,6 +338,7 @@ graph_test_shortcut_gsd <- function(graph,
333338
rejected = result$rejected,
334339
decision_at = result$decision_at,
335340
first_rejected_at = result$first_rejected_at,
341+
last_rejected_at = result$last_rejected_at,
336342
rejection_sequence = result$rejection_sequence,
337343
graph = if (any(result$rejected)) {
338344
graph_update(graph, result$rejected)$updated_graph
@@ -467,6 +473,7 @@ gsd_test <- function(graph, p, alpha, info_frac, spending_fn, look_back,
467473

468474
# Process analyses sequentially
469475
rejected <- structure(rep(FALSE, num_hyps), names = hyp_names)
476+
last_rejected_at <- structure(rep(NA_integer_, num_hyps), names = hyp_names)
470477
decision_at <- structure(rep(NA_integer_, num_hyps), names = hyp_names)
471478
first_rejected_at <- structure(rep(NA_integer_, num_hyps), names = hyp_names)
472479
adjusted_p <- structure(rep(NA_real_, num_hyps), names = hyp_names)
@@ -542,13 +549,19 @@ gsd_test <- function(graph, p, alpha, info_frac, spending_fn, look_back,
542549
shortcut_k$details$results[[rej_idx]]$hypotheses[hyp_name]
543550
allocated_alpha <- w_at_rejection * alpha
544551

545-
# Look back: earliest analysis where sequential p <= allocated alpha
552+
# Look back: earliest and latest analysis where boundary is crossed.
553+
# Check repeated p-values (not sequential) at each analysis against
554+
# the allocated alpha — a repeated p <= allocated alpha means the
555+
# boundary at that specific analysis is crossed.
546556
j <- which(hyp_names == hyp_name)
547-
earliest <- which(seq_p_matrix[j, 1:k] <= allocated_alpha)[1]
557+
crossed <- which(rep_p_matrix[j, 1:k] <= allocated_alpha)
548558
first_rejected_at[hyp_name] <-
549-
if (!is.na(earliest)) earliest else k
559+
if (length(crossed) > 0) crossed[1] else k
560+
last_rejected_at[hyp_name] <-
561+
if (length(crossed) > 0) crossed[length(crossed)] else k
550562
} else {
551563
first_rejected_at[hyp_name] <- k
564+
last_rejected_at[hyp_name] <- k
552565
}
553566
}
554567

@@ -627,6 +640,7 @@ gsd_test <- function(graph, p, alpha, info_frac, spending_fn, look_back,
627640
rejected = rejected,
628641
decision_at = decision_at,
629642
first_rejected_at = first_rejected_at,
643+
last_rejected_at = last_rejected_at,
630644
rejection_sequence = rejection_sequence,
631645
test_values = tv_details
632646
)

R/print.graph_report.R

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ print.graph_report <- function(x, ..., precision = 4, indent = 2, rows = 10) {
121121

122122
df_summary <- data.frame(
123123
Hypothesis = formatC(hyp_names, width = hyp_width),
124-
`Adj. P-value` = adjusted_p,
124+
Adj.p = adjusted_p,
125125
Reject = x$outputs$rejected,
126126
check.names = FALSE
127127
)

R/print.gsd_graph_report.R

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -135,17 +135,24 @@ print.gsd_graph_report <- function(x, ..., precision = 6, indent = 2) {
135135
as.character(x$outputs$first_rejected_at)
136136
)
137137

138+
last_rej_display <- ifelse(
139+
is.na(x$outputs$last_rejected_at),
140+
"--",
141+
as.character(x$outputs$last_rejected_at)
142+
)
143+
138144
df_summary <- data.frame(
139145
Hypothesis = formatC(hyp_names, width = hyp_width),
140146
Adj.P = adj_p_format,
141147
Reject = x$outputs$rejected,
142-
Decision.at = as.character(decision_at),
148+
Tested.at = as.character(decision_at),
143149
First.Rej.at = first_rej_display,
150+
Last.Rej.at = last_rej_display,
144151
Look.back = look_back,
145152
check.names = FALSE
146153
)
147154
names(df_summary)[[1]] <- formatC("Hypothesis", width = hyp_width)
148-
names(df_summary)[[2]] <- "Adj.P-value*"
155+
names(df_summary)[[2]] <- "Adj.p*"
149156

150157
print(df_summary, row.names = FALSE)
151158

R/spending_functions.R

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,21 @@
8787
#' spending_pocock(0.025, c(1/3, 2/3, 1))
8888
#' spending_hsd(0.025, c(1/3, 2/3, 1), gamma = -4)
8989
#' spending_linear(0.025, c(1/3, 2/3, 1))
90+
#'
91+
#' # User-defined spending function: piecewise combination.
92+
#' # Use O'Brien-Fleming for the first half of alpha (conservative at
93+
#' # early analyses), and Pocock for the second half (more aggressive).
94+
#' # This can be useful when a hypothesis starts with a small weight
95+
#' # (OBF spending) and later receives additional weight via graph
96+
#' # propagation (Pocock spending for the increment).
97+
#' spending_piecewise <- function(alpha, info_frac, threshold = 0.0125) {
98+
#' spending_of(pmin(alpha, threshold), info_frac) +
99+
#' spending_pocock(pmax(alpha - threshold, 0), info_frac)
100+
#' }
101+
#' spending_piecewise(0.025, c(1/3, 2/3, 1))
102+
#' # Compare: alpha = 0.0125 uses only OBF
103+
#' spending_piecewise(0.0125, c(1/3, 2/3, 1))
104+
#' spending_of(0.0125, c(1/3, 2/3, 1))
90105
spending_of <- function(alpha, info_frac) {
91106
stopifnot(
92107
"info_frac must be non-negative" = all(info_frac >= 0),
@@ -303,7 +318,10 @@ spending_with_time <- function(spending_fn, spending_time, info_frac = NULL) {
303318
#'
304319
#' @param alpha A numeric scalar of the total significance level.
305320
#' @param info_frac A numeric vector of information fractions at each analysis.
306-
#' Must be non-negative, with at most one value \eqn{\geq 1}.
321+
#' Must be non-negative, with at most one value \eqn{\geq 1}. The last
322+
#' value must be \eqn{\geq 1} (i.e., the final analysis must be included),
323+
#' because the Wang-Tsiatis constant \eqn{C} is calibrated over the full
324+
#' set of analyses.
307325
#' @param delta A numeric scalar for the shape parameter \eqn{\Delta}.
308326
#' The default is `0.5` (Pocock). Use `0` for O'Brien-Fleming.
309327
#' @param maxpts An integer scalar for the maximum number of function values
@@ -350,9 +368,12 @@ spending_with_time <- function(spending_fn, spending_time, info_frac = NULL) {
350368
#' }
351369
spending_wt <- function(alpha, info_frac, delta = 0.5,
352370
maxpts = 25000, abseps = 1e-6) {
371+
353372
stopifnot(
354373
"info_frac must be non-negative" = all(info_frac >= 0),
355374
"At most one info_frac value can be >= 1" = sum(info_frac >= 1) <= 1,
375+
"The last info_frac value must be >= 1 for spending_wt" =
376+
length(info_frac) > 0 && info_frac[length(info_frac)] >= 1,
356377
"delta must be a numeric scalar" = is.numeric(delta) && length(delta) == 1
357378
)
358379

_pkgdown.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ reference:
4545
- repeated_p
4646
- sequential_p
4747
- spending_of
48+
- spending_wt
4849
- spending_with_time
4950
- gs_boundaries
5051
- gs_corr

man/graph_test_shortcut_gsd.Rd

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

man/spending_functions.Rd

Lines changed: 15 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

man/spending_wt.Rd

Lines changed: 4 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)