AUC (by ABE) and Cmax (by ABEL): power [RSABE / ABEL]

posted by Helmut Homepage – Vienna, Austria, 2020-12-03 12:34 (317 d 19:47 ago) – Posting: # 22109
Views: 805

Hi Yura,

» Am I correct in understanding that if in the literature, for example, CV = 50% for AUC is found as the largest (for Cmax it is also more than 30%, but less than for AUC), … then it will not be correct to use sampleN.scABEL to estimate the sample size?

Uncommon that CV of AUC > CV of Cmax though it happens sometimes. You know the answer already:

» … then it will not be correct to use sampleN.scABEL to estimate the sample size?

Yes, use sampleN.TOST().

» There will be no equivalence limits scaling for this parameter.

Generally not (exceptions: partial AUCs for MR, AUC for the WHO).

When I presented this ‘side effect’ (slide 25) at the 2nd GBHI conference (Rockville, September 2015) staff of the FDA shook their heads in disbelief and we had this conversation: “This is just a draft guideline, right?” – “No, it’s final and in force for five years.” – “What‽”
At the 3rd GBHI conference (Amsterdam, April 2018) I raised the issue again and a member of the EMA’s PK Working Party replied that he doesn’t see a problem. Other members of the PKWP agreed.

» And on the other hand, for the parameter Cmax there will be conditions of forced bioequivalence, since the sample size will be at least twice the allowable size calculated by the AUC parameter.

Technically speaking the members of the PKWP were right because there is no “forced bioequivalence”. In any passing study – even if the T/R-ratio is far from 100% – the patient’s risk is still ≤5% (leaving the well-known inflation of type I error in reference-scaling aside).

» How to proceed?

According to the current guidelines (suggesting ABEL not for all PK metrics), accept it. Fortunately the WHO allows ABEL for AUC since 2018 (full replicate designs only). Let’s hope that other jurisdictions will follow.

Result of an example ([image]-script at the end):

design = 2x2x4
theta0 = 0.9 (AUC), 0.9 (Cmax)
targetpower = 80% (both PK metrics)
AUC (ABE: 80.00–125.00%)
  CVw  : 50% (T = R)
  n    : 100
  power: 80.03% for theta0 0.9000 and 1.1111
Cmax (ABEL: 77.23–129.48%)
  CVw  : 35% (T = R)
  n    : 34
  power: 81.18% for theta0 0.9000 and 1.1111
The sample size is driven by AUC.
Cmax with n = 100:
  power: 80.00% for theta0 0.8441 and 1.1847

Same but ABEL for AUC (WHO):

design = 2x2x4
theta0 = 0.9 (AUC), 0.9 (Cmax)
targetpower = 80% (both PK metrics)
AUC (ABEL: 69.84–143.19%)
  CVw  : 50% (T = R)
  n    : 28
  power: 81.43% for theta0 0.9000 and 1.1111
Cmax (ABEL: 77.23–129.48%)
  CVw  : 35% (T = R)
  n    : 34
  power: 81.18% for theta0 0.9000 and 1.1111
The sample size is driven by Cmax.
AUC with n = 34:
  power: 80.00% for theta0 0.8749 and 1.1430


On the other hand, is anybody worried if we plan a study for ABE based on the PK metric with higher variability and get an ‘incentive’ for the other one? Rather not. Here an example where the sample sizes differ more than two­fold:

design = 2x2x2
theta0 = 0.95 (AUC), 0.95 (Cmax)
targetpower = 80% (both PK metrics)
AUC (ABE: 80.00–125.00%)
  CVw  : 15%
  n    : 12
  power: 83.05% for theta0 0.9500 and 1.0526
Cmax (ABE: 80.00–125.00%)
  CVw  : 25%
  n    : 28
  power: 80.74% for theta0 0.9500 and 1.0526
The sample size is driven by Cmax.
AUC with n = 28:
  power: 80.00% for theta0 0.8857 and 1.1290


A funny one:

design = parallel
theta0 = 0.95 (AUC), 0.95 (Cmax)
targetpower = 80% (both PK metrics)
AUC (ABE: 80.00–125.00%)
  CV   : 40%
  n    : 130
  power: 80.35% for theta0 0.9500 and 1.0526
Cmax (ABE: 80.00–125.00%)
  CV   : 60%
  n    : 266
  power: 80.08% for theta0 0.9500 and 1.0526
The sample size is driven by Cmax.
AUC with n = 266:
  power: 80.00% for theta0 0.9000 and 1.1111



library(PowerTOST)
##############
# input area #
##############

design      <- "2x2x4"
targetpower <- 0.80
theta0.Cmax <- 0.90
theta0.AUC  <- 0.90
# In replicate designs CV can be a 2-element vector
# 1st element CVw of T, 2nd element CVw of R

CV.Cmax     <- 0.35
CV.AUC      <- 0.50
method.Cmax <- "ABEL" # If clinically justified, otherwise "ABE".
method.AUC  <- "ABE"  # In most jurisdictions, can be "ABEL" for the WHO.
sdsims      <- FALSE  # Set to TRUE only if "2x3x3" design and
                      # heteroscedasticity when CV[1] > CV[2].
                      # Patience - will be slow!
# Don’t change below unless you know what you are doing.
repl        <- TRUE
designs     <- known.designs()[, c(2, 9)]
replicates  <- designs[grep("replicate", designs$name), 1]
if (!design %in% replicates) {
  repl <- FALSE
  if (length(CV.Cmax) == 2) CV.Cmax <- signif(mse2CV(sum(CV2mse(CV.Cmax))/2), 4)
  if (length(CV.AUC) == 2)  CV.AUC  <- signif(mse2CV(sum(CV2mse(CV.AUC))/2), 4)
} else {
  CV.Cmax.T <- CV.Cmax[1]
  CV.Cmax.R <- CV.Cmax[length(CV.Cmax)]
  CV.AUC.T  <- CV.AUC[1]
  CV.AUC.R  <- CV.AUC[length(CV.AUC)]
}
opt <- function(x) {  # function to find minimum theta0 for targetpower
  if (metric == "Cmax") {
    if (method.Cmax == "ABE") {
      power.TOST(CV = CV.Cmax, theta0 = x, n = n.AUC,
                 design = design) - targetpower
    } else {
      if (sdsims) {
        power.scABEL.sds(CV = CV.Cmax, theta0 = x, n = n.AUC,
                         design = design, progress = FALSE) - targetpower
      } else {
        power.scABEL(CV = CV.Cmax, theta0 = x, n = n.AUC,
                     design = design) - targetpower
      }
    }
  } else {
    if (method.AUC == "ABE") {
      power.TOST(CV = CV.AUC, theta0 = x, n = n.Cmax,
                 design = design) - targetpower
    } else {
      if (sdsims) {
        power.scABEL.sds(CV = CV.AUC, theta0 = x, n = n.Cmax,
                         design = design, progress = FALSE) - targetpower
      } else {
        power.scABEL(CV = CV.AUC, theta0 = x, n = n.Cmax,
                     design = design) - targetpower
      }
    }
  }
}
# Sample sizes, powers, lower/upper limits
if (method.AUC %in% c("ABE", "ABEL")) {
  if (method.AUC == "ABE") {
    x <- sampleN.TOST(CV = CV.AUC, theta0 = theta0.AUC, design = design,
                      targetpower = targetpower, details = FALSE, print = FALSE)
    LU.AUC <- c(lower = 80, upper = 125)
  } else {
    if (sdsims) {
      x <- sampleN.scABEL.sds(CV = CV.AUC, theta0 = theta0.AUC, design = design,
                              targetpower = targetpower, details = FALSE,
                              print = FALSE, progress = FALSE)
    } else {
      x <- sampleN.scABEL(CV = CV.AUC, theta0 = theta0.AUC, design = design,
                          targetpower = targetpower, details = FALSE, print = FALSE)
    }
    LU.AUC <- 100*scABEL(CV = CV.AUC[length(CV.AUC)])
  }
}
n.AUC     <- x[["Sample size"]]
power.AUC <- x[["Achieved power"]]
if (method.Cmax %in% c("ABE", "ABEL")) {
  if (method.Cmax == "ABE") {
    x <- sampleN.TOST(CV = CV.Cmax, theta0 = theta0.Cmax, design = design,
                      targetpower = targetpower, details = FALSE, print = FALSE)
    LU.Cmax <- c(lower = 80, upper = 125)
  } else {
    if (sdsims) {
      x <- sampleN.scABEL.sds(CV = CV.Cmax, theta0 = theta0.Cmax, design = design,
                              targetpower = targetpower, details = FALSE,
                              print = FALSE, progress = FALSE)
    } else {
      x <- sampleN.scABEL(CV = CV.Cmax, theta0 = theta0.Cmax, design = design,
                          targetpower = targetpower, details = FALSE, print = FALSE)
    }
    LU.Cmax <- 100*scABEL(CV = CV.Cmax[length(CV.Cmax)])
  }
}
n.Cmax     <- x[["Sample size"]]
power.Cmax <- x[["Achieved power"]]
n          <- c(AUC = n.AUC, Cmax = n.Cmax)
# Find most deviating theta0 for the metric with lower sample size.
if (n[["AUC"]] >= n[["Cmax"]]) {
  metric       <- "Cmax"
  x            <- uniroot(opt, interval = c(LU.Cmax[[1]]/100, theta0.Cmax), tol = 1e-8)
  theta0.Cmax1 <- x$root
  power.Cmax1  <- x$f.root + targetpower
} else {
  metric      <- "AUC"
  x           <- uniroot(opt, interval = c(LU.AUC[[1]]/100, theta0.AUC), tol = 1e-8)
  theta0.AUC1 <- x$root
  power.AUC1  <- x$f.root + targetpower
}
# Aggregate results for output.
txt <- paste0("\ndesign = ", design,
              "\ntheta0 = ", theta0.AUC, " (AUC), ", theta0.Cmax, " (Cmax)",
              "\ntargetpower = ", 100*targetpower, "% (both PK metrics)",
              "\nAUC (", method.AUC, ": ",
              sprintf("%.2f\u2013%.2f%%)", LU.AUC[["lower"]], LU.AUC[["upper"]]))
if (repl) {
  if (identical(CV.AUC.T, CV.AUC.R)) {
    txt <- paste0(txt, "\n  CVw  : ", 100*CV.AUC, "% (T = R)")
  } else {
    txt <- paste0(txt, "\n  CVw  : ", 100*CV.AUC.T, "% (T), ",
                                      100*CV.AUC.R, "% (R)")
  }
} else {
  if (design %in% c("2x2x2", "paired")) {
    txt <- paste0(txt, "\n  CVw  : ", 100*CV.AUC, "%")
  } else {
    txt <- paste0(txt, "\n  CV   : ", 100*CV.AUC, "%")
  }
}
txt <- paste0(txt, "\n  n    : ", n.AUC,
              "\n  power: ", sprintf("%.2f%%", 100*power.AUC), " for theta0 ",
              sprintf("%.4f", theta0.AUC), " and ", sprintf("%.4f", 1/theta0.AUC),
              "\nCmax (", method.Cmax, ": ",
              sprintf("%.2f\u2013%.2f%%)", LU.Cmax[["lower"]], LU.Cmax[["upper"]]))
if (repl) {
  if (identical(CV.Cmax.T, CV.Cmax.R)) {
    txt <- paste0(txt, "\n  CVw  : ", 100*CV.Cmax, "% (T = R)")
  } else {
    txt <- paste0(txt, "\n  CVw  : ", 100*CV.Cmax.T, "% (T), ",
                                      100*CV.Cmax.R, "% (R)")
  }
} else {
  if (design %in% c("2x2x2", "paired")) {
    txt <- paste0(txt, "\n  CVw  : ", 100*CV.Cmax, "%")
  } else {
    txt <- paste0(txt, "\n  CV   : ", 100*CV.Cmax, "%")
  }
}
txt <- paste0(txt, "\n  n    : ", n.Cmax,
              "\n  power: ", sprintf("%.2f%%", 100*power.Cmax), " for theta0 ",
              sprintf("%.4f", theta0.Cmax), " and ", sprintf("%.4f", 1/theta0.Cmax), "\n")
if (n[["AUC"]] != n[["Cmax"]]) {
  txt <- paste0(txt, "The sample size is driven by ",
                names(n)[which(n == max(n))], ".\n")
  if (n[["AUC"]] > n[["Cmax"]]) {
    txt <- paste0(txt, "Cmax with n = ",  n.AUC, ":\n  power: ",
                  sprintf("%.2f%%", 100*power.Cmax1), " for theta0 ",
                  sprintf("%.4f", theta0.Cmax1), " and ",
                  sprintf("%.4f", 1/theta0.Cmax1), "\n\n")
  } else {
    txt <- paste0(txt, "AUC with n = ",  n.Cmax, ":\n  power: ",
                  sprintf("%.2f%%", 100*power.AUC1), " for theta0 ",
                  sprintf("%.4f", theta0.AUC1), " and ",
                  sprintf("%.4f", 1/theta0.AUC1), "\n\n")
  }
}
cat(txt)


Dif-tor heh smusma 🖖
Helmut Schütz
[image]

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

Complete thread:

Activity
 Admin contact
21,730 posts in 4,544 threads, 1,543 registered users;
online 8 (0 registered, 8 guests [including 7 identified bots]).
Forum time: Sunday 09:21 CEST (Europe/Vienna)

Be very, very careful what you put into that head,
because you will never, ever get it out.    Thomas Wolsey

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