Nothing
#' @title Function to calculate the sequential rejection boundaries for the time-to-first-event (TTFE) method.
#' @description
#' Constructs two-sided group sequential critical boundaries for the two-sample TTFE log-rank Z-statistics at prespecified
#' calendar times. The function converts stage-wise variance estimates into information fractions, allocates incremental Type
#' I error using a spending function, and solves for the corresponding multivariate normal critical values under the
#' canonical joint distribution assumption. It also returns stage-wise rejection indicators (with early stopping enforced).
#'
#'
#' @param result A list returned by \code{TwoSample.Z.Var.Estimator.Sequential.TTFE()}.
#' @param spend A Type I error spending function. This should be a function of a
#' single argument \code{t} (information fraction) returning cumulative alpha
#' spent by information fraction \code{t} (e.g., \code{OBF}).
#' @param calendars Numeric vector of interim analysis calendar times (in years).
#' @param alpha Overall two-sided Type I error rate. Default typically \code{0.05}.
#' @param planned.n Planned total sample size used to define the information scale.
#' @param Iunit Information per subject at the final analysis.
#'
#' @returns
#' A list containing:
#' \itemize{
#' \item \code{Z.stats}: Stage-wise TTFE Z-statistics.
#' \item \code{vars}: Stage-wise variance estimates.
#' \item \code{raw.information}: Stage-wise information fractions.
#' \item \code{TTFE.bdry}: Two-sided critical boundaries at each analysis stage.
#' \item \code{TTFE.reject}: Stage-wise rejection indicators (1 = reject at that
#' stage, 0 otherwise), with later stages set to 0 after the first rejection.
#' \item \code{nu}: Stage-wise information fractions after handling edge cases
#' (e.g., reaching 100\% information early).
#' \item \code{pi}: Incremental Type I error allocated to each stage.
#' \item \code{total.ns}: Total sample size contributing at each analysis stage
#' (copied from \code{result}).
#' }
#'
#' @export
#' @importFrom stats qnorm
#'
TwoSample.Boundary.TTFE <- function(result, spend, calendars, alpha, planned.n, Iunit){
# Extract the results from the input object
Z.stats <- result$Z.stats
vars <- result$vars
total.ns <- result$total.ns
nu <- c()
pi <- c()
TTFE.bdry <- c()
# Step 1a: Calculate the information fraction for this simulated data
for (j in 1:(length(calendars))){
nu[j] = vars[j]/(planned.n*Iunit)
nu[length(calendars)] = 1
}
# Step 1b: Calculate the correlation matrix (We already know that the LogRank test does have)
raw.information <- nu
canonical.corr <- diag(3)
for (p in 1:(3-1)){
for (q in (p+1):3) {
canonical.corr[p,q] <- sqrt(raw.information[p]/raw.information[q])
canonical.corr[q,p] <- canonical.corr[p,q]
}
}
# Step 2: Calculate the allocation of type I error and rejection boundaries
if (nu[1] >= 1){
# Reached 100% information at stage 1, in that case, treat the first analysis as the final one.
# Carried the 100% information fraction forward and set the stages 2 and 3 boundaries to 99
nu = c(1, 1, 1)
pi = c(alpha, 0, 0)
TTFE.bdry = c(qnorm(p = alpha/2, lower.tail = FALSE), 99, 99)
} else if (nu[2] >= 1){
# Reached 100% information at stage 2, in that case, treat the 2nd interim analysis as the final one.
# Carried the 100% information fraction forward and set the stage 3 boundary to 99
nu = c(nu[1], 1, 1)
pi = c(spend(nu[1]), alpha - spend(nu[1]), 0)
TTFE.bdry[1] = qnorm(p = pi[1]/2, lower.tail = FALSE)
TTFE.bdry[2] = find.c(previous.c = TTFE.bdry[1], pi = pi[2], corr = canonical.corr[c(1:2),c(1:2)])
TTFE.bdry[3] = 99
} else if (nu[2]< nu[1]){
# Observed a smaller information fraction at a later stage
# in that case, skip stage 2 and move on to the final stage
# set the stage 2 boundary to 88
nu = c(nu[1], nu[1], 1)
pi = c(spend(nu[1]), 0, alpha - spend(nu[1]))
TTFE.bdry[1] = qnorm(p = pi[1]/2, lower.tail = FALSE)
TTFE.bdry[2] = 88
TTFE.bdry[3] = find.c(previous.c = TTFE.bdry[1], pi = pi[3], corr = canonical.corr[c(1,3),c(1,3)])
} else {
# Trial proceed as planned
# Calculate the allocation of Type I errors
pi[1] <- spend(nu[1])
for (j in 2:(length(calendars) - 1)){
pi[j] <- spend(nu[j]) - spend(nu[j-1])
}
pi[length(calendars)] <- alpha - spend(nu[length(calendars) - 1])
# Calculate the rejection boundary
TTFE.bdry[1] = qnorm(p = pi[1]/2, lower.tail = FALSE)
for (j in 2:length(calendars)){
TTFE.bdry[j] <- find.c(previous.c = TTFE.bdry[1:j-1], pi = pi[j], corr = canonical.corr[1:j,1:j])
}
}
# Step 4: Compare the Z statistics and the rejection boundary
TTFE.reject <- 1*(Z.stats >= TTFE.bdry|Z.stats <= -TTFE.bdry)
# Step 5: find the first reject and set the later values to zero
if (any(TTFE.reject == 1)) {
first <- which(TTFE.reject == 1)[1]
if (first < length(TTFE.reject)) {
TTFE.reject[(first + 1):length(TTFE.reject)] <- 0
}
}
return(list(Z.stats = Z.stats,
vars = vars,
raw.information = raw.information,
TTFE.bdry = TTFE.bdry,
TTFE.reject = TTFE.reject,
nu = nu,
pi = pi,
total.ns = total.ns))
}
Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.