TSD useful at all? [Two-Stage / GS Designs]
❝ Hi Helumt,
❝ Thank you for the nmax info, didnt know.
❝ BTW your RSABE.TSD script seems very useful !!
I don’t think that a TSD is useful at all, unless you use the observed GMR of stage 1 (argument
usePE = TRUE
) and a reasonable Nmax
. But (‼) if your final GMR is really that bad, you will fail – even if the CVwR is larger (which means more scaling).RSABE.TSD(n1 = 24, CVwR = 0.6978, GMR = 0.85, Nmax = 100,
usePE = TRUE, CVwR.2 = 0.8, GMR.2 = 0.72)
adjusted alpha : 0.0294 (Pocock’s for superiority)
design : 2x2x4 (2-sequence 4-period full replicate)
n1 : 24
futility on N : 100
futility on PE : outside 0.8000 – 1.2500 (guidances)
CVwR : 0.6978 (observed)
theta1 : 0.5700 (lower implied limit)
theta2 : 1.7545 (upper implied limit)
GMR : 0.8500 (observed)
power : 0.6369 (estimated)
Stage 2 initiated (insufficent power in stage 1)
target GMR : 0.8500 (observed)
target power : 0.8000 (fixed)
n2 : 54
N : 78
CVwR : 0.8000 (observed)
GMR : 0.7200 (observed)
theta1 : 0.5338 (lower implied limit)
theta2 : 1.8735 (upper implied limit)
power : 0.0932 (estimated, fails RSABE)
We could observe the PE-constraints according to the guidances:
Stop for futility in the interim if \(\small{GMR\notin\left\{>0.80\;\wedge<1.25\right\}}\).
However, for such an approach the GMR has to be reasonably accurate, i.e., the stage 1 sample size sufficiently large. Don’t ask me what would be sufficient… Larger deviations between GMRs are a direct consequence of higher variability. The FDA requires at least 24 enrolled subjects in replicate designs intended for reference-scaling. If the CVwR is expected to be large, I would opt for substantially more in order to get a reliable estimate.With the results of your first stage with 24 subjects:
RSABE.TSD(n1 = 24, CVwR = 0.6978, GMR = 0.72, final = FALSE)
adjusted alpha : 0.0294 (Pocock’s for superiority)
design : 2x2x4 (2-sequence 4-period full replicate)
n1 : 24
futility on PE : outside 0.8000 – 1.2500 (guidances)
CVwR : 0.6978 (observed)
theta1 : 0.5700 (lower implied limit)
theta2 : 1.7545 (upper implied limit)
GMR : 0.7200 (observed)
power : 0.1838 (estimated)
Study stopped for futility (lower PE constraint)
RSABE.TSD <- function(adj = 0.0294, design = "2x2x4", n1, CVwR, GMR,
target = 0.8, Nmax = Inf, usePE = FALSE, final = TRUE,
CVwR.2, GMR.2, risk = FALSE, details = TRUE) {
# adj : adjusted alpha (stage 1 and final analysis) like Potvin ‘Method B’
# defaults to 0.0294 (Pocock’s for superiority)
# design : "2x2x4": 2-sequence 4-period full replicate (default)
# "2x2x3": 2-sequence 3-period full replicate
# "2x3x3": 3-sequence 3-period partial replicate
# n1 : stage 1 sample size
# CvwR : observed within-subject CV of R in stage 1
# GMR : observed T/R-ratio in stage 1
# target : target (desired) power for sample size re-estimation (default 0.8)
# Nmax : futility on total sample size (default: unrestricted)
# usePE : FALSE: use a fixed (assumed) GMR
# TRUE: use the GMR observed in stage 1
# final : TRUE : final analysis (requires CVwR.2 and GMR.2)
# FALSE: interim analyis only
# risk : FALSE: TIE acc. to all publications based on the ‘implied limits’
# TRUE : additionallly TIE acc. to the ‘desired consumer risk model’
# details: TRUE : output to the console
# FALSE: data.frame of results
if (adj >= 0.05) warning ("Unadjusted alpha ", adj, " is not recommended.")
designs <- c("2x2x4", "2x2x3", "2x3x3")
if (!design %in% designs) stop ("design ", design, " is not supported.")
if (missing(n1)) stop ("Number of subjects in stage 1 (n1) must be given!")
if (missing(CVwR)) stop ("CVwR in stage 1 must be given!")
if (missing(GMR)) stop ("GMR in stage 1 must be given!")
if (target <= 0.5 | target >= 1) stop ("target ", target, " doesn’t make sense.")
nseq <- as.integer(substr(design, 3, 3))
if (Nmax <= n1 + nseq) stop (paste("Nmax <= n1 +", nseq, "doesn’t make sense."))
fClower <- 0.80 # lower boundary of the PE-constraints acc. to the guidance
fCupper <- 1.25 # upper boundary of the PE-constraints acc. to the guidance
des.verb <- c(" (2-sequence 4-period full replicate)",
" (2-sequence 3-period full replicate)",
" (3-sequence 3-period partial replicate)")
if (final) {
if (missing(CVwR.2))
stop ("CVwR in the final analysis (CVwR.2) must be given!")
if (missing(GMR.2))
stop ("GMR in the final analysis (GMR.2) must be given!")
}
suppressMessages(require(PowerTOST)) # ≥1.5-4 (2022-02-21)
limits <- function(CVwR, risk = FALSE) { # limits
thetas <- scABEL(CV = CVwR, regulator = "FDA") # implied
if (risk) { # ‘desired consumer risk model’
swR <- CV2se(CVwR)
if (swR > 0.25) {
thetas <- setNames(exp(c(-1, +1) * log(1.25) / 0.25 * swR),
c("lower", "upper"))
} else {
thetas <- setNames(c(0.8, 1.25), c("lower", "upper"))
}
}
return(thetas)
}
power <- function(alpha = 0.05, CVwR, GMR, n, design) {
return(suppressMessages(power.RSABE(alpha = alpha, CV = CVwR,
theta0 = GMR, n = n, design = design)))
}
TIE <- function(alpha = 0.05, CVwR, n, design, risk) {
return(suppressMessages(power.RSABE(alpha = alpha, CV = CVwR,
theta0 = limits(CVwR, risk)[["upper"]],
n = n, design = design, nsims = 1e6)))
}
TIE.1.1 <- TIE.1.2 <- TIE.2.1 <- TIE.2.2 <- N <- req <- NA
pwr.1 <- power(adj, CVwR, GMR, n = n1, design)
futile <- FALSE
sig <- binom.test(0.05 * 1e6, 1e6, alternative = "less",
conf.level = 0.95)$conf.int[2]
txt <- paste("adjusted alpha :", sprintf("%.4f", adj))
# Note: Pockock’s adjusted alphas are for group-sequential designs
# with one interim analysis at exactly N/2 and known variances!
if (adj == 0.0294) {
txt <- paste(txt, "(Pocock’s for superiority)")
} else {
if (adj == 0.0304) {
txt <- paste(txt, "(Pocock’s for equivalence)")
} else {
if (adj == 0.0250) {
txt <- paste(txt, "(Bonferroni’s for two independent tests)")
} else {
txt <- paste(txt, "(custom)")
}
}
}
txt <- paste(txt, "\ndesign :", design,
des.verb[design == designs],
"\nn1 :", sprintf("%3.0f", n1))
if (Nmax < Inf) {
txt <- paste(txt, "\nfutility on N :", sprintf("%3.0f", Nmax))
}
txt <- paste(txt, "\nfutility on PE : outside",
sprintf("%.4f – %.4f (guidances)", fClower, fCupper))
txt <- paste(txt, "\nCVwR :",
sprintf("%.4f (observed)", CVwR),
"\ntheta1 :",
sprintf("%.4f (lower implied limit)",
limits(CVwR)[["lower"]]))
if (risk) {
txt <- paste(txt, "\n ",
sprintf("%.4f", limits(CVwR.2, risk)[["lower"]]),
"(lower limit of the ‘desired consumer risk model’)")
}
txt <- paste(txt, "\ntheta2 :",
sprintf("%.4f (upper implied limit)",
limits(CVwR)[["upper"]]))
if (risk) {
txt <- paste(txt, "\n ",
sprintf("%.4f", limits(CVwR.2, risk)[["upper"]]),
"(upper limit of the ‘desired consumer risk model’)")
}
txt <- paste(txt, "\nGMR :", sprintf("%.4f", GMR), "(observed)")
txt <- paste(txt, "\npower :", sprintf("%.4f (estimated)", pwr.1))
if (pwr.1 >= target) { # stop in stage 1
TIE.1.1 <- TIE(adj, CVwR, n1, design, risk = FALSE)
txt <- paste0(txt, "\nStudy stopped in stage 1 (sufficient power)",
"\nempirical TIE :", sprintf(" %.4f", TIE.1.1),
" (all publications")
if (TIE.1.1 > sig) {
txt <- paste0(txt, "; significantly inflated)")
} else {
txt <- paste0(txt, ")")
}
if (risk) {
TIE.1.2 <- TIE(adj, CVwR, n1, design, risk = TRUE)
txt <- paste(txt, "\n ", sprintf("%.4f", TIE.1.2),
"(‘desired consumer risk model’")
if (TIE.1.2 > sig) {
txt <- paste0(txt, "; significantly inflated)")
} else {
txt <- paste0(txt, ")")
}
}
if (TIE.1.1 > sig) {
req <- scABEL.ad(alpha.pre = adj, theta0 = GMR, CV = CVwR,
design = design, regulator = "FDA", n = n1,
print = FALSE, details = FALSE)[["alpha.adj"]]
txt <- paste(txt, "\nAn adjusted alpha of", sprintf("%.4f", req),
"(or less)\nwould be needed to control the Type I Error.")
}
} else { # possibly initiate stage 2
if (GMR > fClower & GMR < fCupper) {
N <- sampleN.RSABE(alpha = adj, CV = CVwR, theta0 = GMR,
targetpower = target, design = design,
print = FALSE, details = FALSE)[["Sample size"]]
}
if (is.na(N) | N > Nmax | GMR <= fClower | GMR >= fCupper) {
txt <- paste(txt, "\nStudy stopped for futility")
if (GMR <= fClower | GMR >= fCupper) { # stop for GMR futility
if (GMR <= fClower) txt <- paste(txt, "(lower PE constraint)")
if (GMR >= fCupper) txt <- paste(txt, "(upper PE constraint)")
} else { # stop for Nmax futility
txt <- paste(txt, "(insufficent power in stage 1\nbut total sample size")
if (!is.na(N)) txt <- paste(txt, N)
txt <- paste(txt, "above futility limit)")
}
} else {
if (final) {
pwr.2 <- power(adj, CVwR.2, GMR.2, n = N, design)
if (GMR.2 <= 0.8 | GMR.2 >= 1.25) {
final.est <- FALSE
} else {
final.est <- TRUE
TIE.2.1 <- TIE(adj, CVwR.2, N, design, risk = FALSE)
if (risk) TIE.2.2 <- TIE(adj, CVwR.2, N, design, risk = TRUE)
}
} else {
CVwR.2 <- GMR.2 <- pwr.2 <- TIE.2.1 <- TIE.2.2 <- NA
theta1.1 <- theta1.2 <- req <- NA
}
txt <- paste(txt, "\nStage 2 initiated (insufficent power in stage 1)",
"\ntarget GMR :", sprintf("%.4f", GMR))
ifelse (usePE, txt <- paste(txt, "(observed)"),
txt <- paste(txt, "(fixed)"))
txt <- paste(txt, "\ntarget power :", sprintf("%.4f (fixed)", target),
"\nn2 :", sprintf("%3.0f", N - n1),
"\nN :", sprintf("%3.0f", N))
if (N < 24) txt <- paste0(txt,
"; less than the FDA’s minimum of 24 subjects!")
if (final) {
txt <- paste(txt, "\nCVwR :",
sprintf("%.4f (observed)", CVwR.2),
"\nGMR :", sprintf("%.4f (observed)", GMR.2),
"\ntheta1 :",
sprintf("%.4f (lower implied limit)",
limits(CVwR.2)[["lower"]]))
if (risk) {
txt <- paste(txt, "\n ",
sprintf("%.4f", limits(CVwR.2, risk)[["lower"]]),
"(lower limit of the ‘desired consumer risk model’)")
}
txt <- paste(txt, "\ntheta2 :",
sprintf("%.4f (upper implied limit)",
limits(CVwR.2)[["upper"]]))
if (risk) {
txt <- paste(txt, "\n ",
sprintf("%.4f", limits(CVwR.2, risk)[["upper"]]),
"(upper limit of the ‘desired consumer risk model’))
}
txt <- paste(txt, "\npower :", sprintf("%.4f (estimated,", pwr.2))
ifelse (pwr.2 < 0.5, txt <- paste(txt, "fails RSABE)"),
txt <- paste(txt, "may pass RSABE)"))
if (final.est) {
txt <- paste0(txt, "\nempirical TIE :", sprintf(" %.4f", TIE.2.1),
" (all publications")
if (TIE.2.1 > sig) {
txt <- paste0(txt, "; significantly inflated)")
} else {
txt <- paste0(txt, ")")
}
if (risk) {
txt <- paste(txt, "\n ", sprintf("%.4f", TIE.2.2),
"(‘desired consumer risk model’")
if (TIE.2.2 > sig) {
txt <- paste0(txt, "; significantly inflated)")
} else {
txt <- paste0(txt, ")")
}
}
if (TIE.2.1 > sig) {
req <- scABEL.ad(alpha.pre = adj, theta0 = GMR.2, CV = CVwR.2,
design = design, regulator = "FDA", n = N,
print = FALSE, details = FALSE)[["alpha.adj"]]
txt <- paste(txt, "\nAn adjusted alpha of", sprintf("%.4f", req),
"(or less)\nwould be needed to control the Type I Error.")
} else {
req <- NA
}
}
}
}
}
if (details) { # output to the console
cat(txt, "\n")
} else { # data.frame of results
# limits in stage 1
L.1.1 <- limits(CVwR, FALSE)[["lower"]]
U.1.1 <- limits(CVwR, FALSE)[["upper"]]
L.1.2 <- U.1.2 <- NA
if (risk) {
L.1.2 <- limits(CVwR, TRUE)[["lower"]]
U.1.2 <- limits(CVwR, TRUE)[["upper"]]
}
if (final) {
# limits in the final analysis
L.2.1 <- limits(CVwR.2, FALSE)[["lower"]]
U.2.1 <- limits(CVwR.2, FALSE)[["upper"]]
L.2.2 <- U.2.2 <- NA
if (risk) {
L.2.2 <- limits(CVwR.2, TRUE)[["lower"]]
U.2.2 <- limits(CVwR.2, TRUE)[["upper"]]
}
} else {
L.2.1 <- U.2.1 <- L.2.2 <- U.2.2 <- NA
}
result <- data.frame(alpha.adj = adj, design = design, n1 = n1, CVwR = CVwR,
GMR = GMR, usePE = usePE, Nmax = Nmax, risk.model = risk,
L.1.1 = L.1.1, U.1.1 = U.1.1,
L.1.2 = L.1.2, U.1.2 = U.1.2, power.1 = pwr.1,
TIE.1.1 = TIE.1.1, TIE.1.2 = TIE.1.2, futile = futile,
n2 = N - n1, N = N)
if (final) {
res2 <- data.frame(CVwR.2 = CVwR.2, GMR.2 = GMR.2,
L.2.1 = L.2.1, U.2.1 = U.2.1,
L.2.2 = L.2.2, U.2.2 = U.2.2,
power.2 = pwr.2, TIE.2.1 = TIE.2.1,
TIE.2.2 = TIE.2.2, alpha.req = req)
result <- cbind(result, res2)
}
result <- result[, colSums(is.na(result)) < nrow(result)]
return(result)
}
}
Dif-tor heh smusma 🖖🏼 Довге життя Україна!
![[image]](https://static.bebac.at/pics/Blue_and_yellow_ribbon_UA.png)
Helmut Schütz
![[image]](https://static.bebac.at/img/CC by.png)
The quality of responses received is directly proportional to the quality of the question asked. 🚮
Science Quotes
Complete thread:
- Adaptive Design for the FDA’s RSABE? Helmut 2023-12-18 11:20 [Two-Stage / GS Designs]
- Likely it does not work (potentially inflated Type I Error) Helmut 2023-12-19 11:10
- Exploring package adaptIVPT, function rss() Helmut 2023-12-20 13:27
- Extreme test case Helmut 2023-12-24 13:01
- Extreme GMR Naksh 2023-12-25 04:16
- PE outside {0.80, 1.25} not possible Helmut 2023-12-25 10:54
- PE outside {0.80, 1.25} not possible Naksh 2023-12-25 11:42
- Forget rss() Helmut 2023-12-25 13:15
- Forget rss() Naksh 2023-12-26 04:49
- TSD useful at all?Helmut 2023-12-26 12:50
- Forget rss() Naksh 2023-12-26 04:49
- Forget rss() Helmut 2023-12-25 13:15
- PE outside {0.80, 1.25} not possible Naksh 2023-12-25 11:42
- PE outside {0.80, 1.25} not possible Helmut 2023-12-25 10:54
- Extreme GMR Naksh 2023-12-25 04:16
- Extreme test case Helmut 2023-12-24 13:01
- Exploring package adaptIVPT, function rss() Helmut 2023-12-20 13:27
- Likely it does not work (potentially inflated Type I Error) Helmut 2023-12-19 11:10