Tightening the limits reduces power for a given sample size [🇷 for BE/BA]

posted by Helmut Homepage – Vienna, Austria, 2024-01-30 10:43 (142 d 08:48 ago) – Posting: # 23850
Views: 1,220

Hi Imph,

❝ Thank you for the response.

Welcome.

❝ It's about a study passed with 80-125% limits.

All is good. Celebrate.

❝ Then our regulation asked us to tighten the limits to 90-111.

That’s strange, politely speaking. Did the agency see/accept the protocol?

❝ Do we have to verify that the power is above 80% after tightening the limits?

No. Read the quote of the WHO above and my article again.
Post hoc power (\(\small{\widehat{\pi}}\)) is – completely – irrelevant in BE. Only a priori power (\(\small{\pi}\)) is important in designing a study.

Even if all of your assumptions (CV, T/R-ratio, eligible subjects) based on the conventional limits of 80–125% were exactly realized (then \(\small{\widehat{\pi}=\pi}\)), for the tightened limits power will be lower than planned. As long as you pass BE, it does not matter. If you fail – which is very likely – start an argument with the agency, but please not based on post hoc power.

Example:

library(PowerTOST)
CV     <- 0.175
design <- "2x2x2"
theta0 <- 0.95
target <- 0.80
theta1 <- c(0.80, 0.90)
theta2 <- c(1.25, 1 / 0.90)
n      <- sampleN.TOST(CV = CV, design = design, theta0 = theta0,
                       theta1 = theta1[1], theta2 = theta2[1],
                       targetpower = target, print = FALSE)[["Sample size"]]
CI     <- CI.BE(CV = CV, pe = theta0, n = n, design = design)
comp   <- data.frame(theta1 = theta1, theta2 = theta2, n = n, PE = theta0,
                     lower.CL = CI[["lower"]], upper.CL = CI[["upper"]],
                     BE = "fail", power = NA)
for (j in 1:nrow(comp)) {
  if (comp$lower.CL[j] >= as.numeric(comp$theta1[j]) &
      comp$upper.CL[j] <= as.numeric(comp$theta2[j])) comp$BE[j] <- "pass"
  comp$power[j]  <- power.TOST(CV = CV, design = design, theta0 = theta0,
                               theta1 = theta1[j], theta2 = theta2[j], n = n)
  comp$theta1[j] <- sprintf("%.4f", as.numeric(comp$theta1[j]))
  comp$theta2[j] <- sprintf("%.4f", as.numeric(comp$theta2[j]))
  comp$PE[j]     <- sprintf("%.4f", as.numeric(comp$PE[j]))
}
comp[, c(5:6, 8)] <- round(comp[, c(5:6, 8)], 4)
names(comp)[c(1:2, 7)]  <- c("L", "U", "BE?")
txt <- paste("Study designed based on the conventional limits",
             "\n{L = 0.8000, U = 1.2500}; all assumptions are",
             "\nexactly realized in the study.\n")
cat(txt); print(comp, row.names = FALSE)

Study designed based on the conventional limits
{L = 0.8000, U = 1.2500}; all assumptions are
exactly realized in the study.
      L      U  n     PE lower.CL upper.CL  BE?  power
 0.8000 1.2500 16 0.9500   0.8526   1.0585 pass 0.8401
 0.9000 1.1111 16 0.9500
   0.8526   1.0585 fail 0.0694

You will fail with the tightened limits (lower confidence limit below L) and the loss in power will be massive.
For tightened limits much larger sample sizes would be required – unless the T/R-ratio is closer to 1. That’s why the FDA requires more strict batch release specifications and the default theta0 = 0.975 in the function sampleN.NTID(). See also this article.

CV     <- seq(0.1, 0.2, 0.01)
design <- "2x2x2"
theta0 <- 0.95 # not a good idea for NTIDs
target <- 0.80
theta1 <- c(0.80, 0.90)
theta2 <- c(1.25, 1 / 0.90)
comp   <- data.frame(CV = CV,
                     L1 = rep(theta1[1], length(CV)),
                     U1 = rep(theta2[1], length(CV)), n1 = NA, pwr1 = NA,
                     L2 = rep(theta1[2], length(CV)),
                     U2 = rep(theta2[2], length(CV)), n2 = NA, pwr2 = NA)
for (j in seq_along(CV)) {
  tmp          <- sampleN.TOST(CV = CV[j], design = design, theta0 = theta0,
                               theta1 = theta1[1], theta2 = theta2[1],
                               targetpower = target, print = FALSE)
  if (tmp[["Sample size"]] < 12) {
    comp$n1[j]   <- 12 # force to minimum acc. to guidelines
    comp$pwr1[j] <- power.TOST(CV = CV[j], design = design, theta0 = theta0,
                               theta1 = theta1[1], theta2 = theta2[1], n = 12)
  } else {
    comp$n1[j]   <- tmp[["Sample size"]]
    comp$pwr1[j] <- tmp[["Achieved power"]]
  }
  tmp          <- sampleN.TOST(CV = CV[j], design = design, theta0 = theta0,
                               theta1 = theta1[2], theta2 = theta2[2],
                               targetpower = target, print = FALSE)
  comp$n2[j]   <- tmp[["Sample size"]]
  comp$pwr2[j] <- tmp[["Achieved power"]]
}
comp             <- round(comp, 4)
names(comp)[2:9] <- rep(c("L", "U", "n", "power"), 2)
print(comp, row.names = FALSE)

   CV   L    U  n  power   L      U   n  power
 0.10 0.8 1.25 12 0.9883 0.9 1.1111  44 0.8040
 0.11 0.8 1.25 12 0.9724 0.9 1.1111  54 0.8115
 0.12 0.8 1.25 12 0.9476 0.9 1.1111  62 0.8007
 0.13 0.8 1.25 12 0.9148 0.9 1.1111  74 0.8083
 0.14 0.8 1.25 12 0.8753 0.9 1.1111  84 0.8022
 0.15 0.8 1.25 12 0.8305 0.9 1.1111  96 0.8018
 0.16 0.8 1.25 14 0.8487 0.9 1.1111 110 0.8055
 0.17 0.8 1.25 14 0.8057 0.9 1.1111 122 0.8003
 0.18 0.8 1.25 16 0.8204 0.9 1.1111 138 0.8045
 0.19 0.8 1.25 18 0.8294 0.9 1.1111 152 0.8014
 0.20 0.8 1.25 20 0.8347 0.9 1.1111 168 0.8015


Dif-tor heh smusma 🖖🏼 Довге життя Україна! [image]
Helmut Schütz
[image]

The quality of responses received is directly proportional to the quality of the question asked. 🚮
Science Quotes

Complete thread:

UA Flag
Activity
 Admin contact
23,059 posts in 4,841 threads, 1,646 registered users;
41 visitors (0 registered, 41 guests [including 6 identified bots]).
Forum time: 20:31 CEST (Europe/Vienna)

Friends don’t let friends use Excel for statistics!    Jonathan D. Cryer

The Bioequivalence and Bioavailability Forum is hosted by
BEBAC Ing. Helmut Schütz
HTML5