Level up your pharmacometrics skills with a hands-on workshop alongside ACoP 2026. Register here.
MonolixSuite in R
Auto Light Dark
Auto Light Dark

Reference-scaled average bioequivalence - EMA approach (ABEL)

The EMA implements reference-scaling as ABEL : Average Bioequivalence with Expanding Limits. The name describes the mechanism: it is the ordinary ABE test (90% CI of the test/reference ratio), but the fixed 80-125% acceptance limits are allowed to expand as a function of the reference product's within-subject variability (= within-subject CV of reference product). The more variable the reference, the wider the limits (up to a defined cap) while a point-estimate constraint prevents the two products from differing too much on average.

Unlike the FDA method, EMA applies this expansion to Cmax only and caps the widening at = 50%.

Note: For the general RSABE concept (why fixed limits fail for highly variable drugs, the replicate-design requirement, and the GMR safeguard) see the RSABE overview.

Concept and mathematics

ABEL keeps the ordinary bioequivalence test, where the 90% confidence interval of the test/reference geometric mean ratio (GMR), but lets the Cmax acceptance limits widen as a function of the reference product's within-subject variability . The analysis has three steps.

  1. Within-reference variability ( ):

is the residual standard deviation (on natural-log scale) of an ANOVA fitted to the reference observations only (EMA Method A):

Because is nested in (each subject belongs to a single sequence), absorbs so the model reduces to with the same residual mean square. The coefficient of variation follows by back-transformation:

The pooled within-subject CV from a standard BE ANOVA (test and reference together) is not . ABEL requires the reference-only estimate, hence a replicate design.

  1. Expanding limits:

With regulatory scaling constant k = 0.760, the acceptance limits for Cmax are

applied by variability band:

Acceptance limits

≤ 30%

80.00-125.00% (no widening)

30% < ≤ 50%

50%


69.84 – 143.19% (capped: )

The limits grow gradually with variability: at exactly = 30% the widening formula still gives 80-125%, so there is no abrupt step at the threshold.

AUC is never scaled: it always uses the fixed 80-125%.

  1. Decision criterion

A parameter is bioequivalent if its 90% CI lies within the limits and the GMR lies within 80-125%:

AUC uses the same rule with fixed

Full details of the method, the regulatory constants, and the acceptance criteria are given in the EMA Guideline on the investigation of bioequivalence (CPMP/EWP/QWP/1401/98 Rev. 1).

Example

NCA parameters are computed in PKanalix; the reference-scaling is done in R via
lixoftConnectors. The example uses the demo project project_repeated_4periods.pkx
(a full replicate design, TRTR/RTRT).

R
rm(list = ls())
path.prog <- dirname(rstudioapi::getSourceEditorContext()$path)

library(lixoftConnectors)
initializeLixoftConnectors(software = "pkanalix", force = TRUE)
library(dplyr)
library(flextable)
################################################################################
loadProject(file.path(dirname(path.prog), "project_repeated_4periods.pkx"))
runNCAEstimation()
runBioequivalenceEstimation()

# Regulatory (EMA) constants 
k_ema <- 0.760; cv_switch <- 0.30; cv_cap <- 0.50; pe_lo <- 0.80; pe_hi <- 1.25

# Read design + individual NCA values from project
set    <- getBioequivalenceSettings()
params <- set$computedbioequivalenceparameters$parameters
seqc   <- set$linearmodelfactors$sequence
form   <- set$linearmodelfactors$formulation
ref    <- set$linearmodelfactors$reference
occ    <- getData()$header[getData()$headerTypes == "occ"]

indiv  <- getNCAIndividualParameters()$parameters %>%
  select(id, all_of(occ), all_of(seqc), all_of(form), all_of(params))

# CVwR: reference-only ANOVA (EMA) to obtain sWR (within-subject SD of ref)
refdat <- filter(indiv, .data[[form]] == ref)
sWR <- sapply(params, function(p) {
  fit <- lm(log(refdat[[p]]) ~ factor(refdat$id) + factor(refdat[[occ]]))
  if (df.residual(fit) < 1) NA_real_ else summary(fit)$sigma   # NA -> not a replicate
})
if (anyNA(sWR)) warning("sWR is NA for some parameter(s): design is not replicated.")

# GMR + 90% CI from the PKanalix BE task (full T+R model)
be    <- getBioequivalenceResults()
test  <- setdiff(unique(indiv[[form]]), ref)[1]           # non-reference formulation
ci    <- be$confidenceIntervals[[test]]
ci    <- ci[match(params, ci$Parameter), ]
scl   <- 100

# Assemble sWR / CVwR + GMR + 90% CI, then apply the ABEL decision
res <- data.frame(
  Parameter = params,
  sWR       = sWR,
  CVwR      = sqrt(exp(sWR^2) - 1),
  GMR       = ci$Ratio   / scl,
  CI_lo     = ci$CILower / scl,
  CI_hi     = ci$CIUpper / scl,
  row.names = NULL, stringsAsFactors = FALSE)

# Expanded limits: Cmax only; widen if CVwR > 30%; sWR capped at CVwR 50%
cmax  <- grepl("cmax", res$Parameter, ignore.case = TRUE)
widen <- cmax & !is.na(res$CVwR) & res$CVwR > cv_switch
sWRc  <- pmin(res$sWR, sqrt(log(cv_cap^2 + 1)))           # cap sWR at CV = 50%

res$limit_lo <- pe_lo; res$limit_hi <- pe_hi
res$limit_lo[widen] <- exp(-k_ema * sWRc[widen])
res$limit_hi[widen] <- exp( k_ema * sWRc[widen])

res$rule <- ifelse(!cmax, "AUC: fixed 80.00-125.00%",
             ifelse(res$CVwR <= cv_switch, "Cmax: no widening (CVwR<=30%)",
             ifelse(res$CVwR <= cv_cap,    "Cmax: widened (30%<CVwR<=50%)",
                                           "Cmax: capped at CVwR 50%")))

res$PASS <- res$CI_lo >= res$limit_lo & res$CI_hi <= res$limit_hi &
            res$GMR   >= pe_lo        & res$GMR   <= pe_hi

# Report (% scale)
report <- with(res, data.frame(
  Parameter    = Parameter,
  Rule         = rule,
  `CVwR(%)`    = round(100 * CVwR, 2),
  `Lower(%)`   = round(100 * limit_lo, 2),
  `Upper(%)`   = round(100 * limit_hi, 2),
  `GMR(%)`     = round(100 * GMR, 2),
  `CI90_lo(%)` = round(100 * CI_lo, 2),
  `CI90_hi(%)` = round(100 * CI_hi, 2),
  BE           = ifelse(PASS, "PASS", "FAIL"),
  check.names  = FALSE, stringsAsFactors = FALSE))

flextable(report) %>% autofit()

How the script works. The steps correspond directly to the three-step concept above.

  • Design is read, not hardcoded. getBioequivalenceSettings() returns the parameter list and the column names, the only study-specific input is the .pkx path; getNCAIndividualParameters() gives the per-subject NCA values.

  • (step 1). On the reference-only data, the ANOVA log(PK) ~ factor(id) + factor(occasion) is fitted per parameter; factor(id) makes subject a fixed effect (EMA Method A) and summary(fit)$sigma is . The df.residual < 1 guard returns NA (with a warning()) when the design is not replicated, so is not estimable.

  • GMR + 90% CI. From the PKanalix BE task (getBioequivalenceResults()), the full test-plus-reference model, not the reference-only ANOVA. PKanalix reports these in percent, so scl = 100 converts them to fractions.

  • Expanding limits (step 2). Limits default to 0.80 - 1.25. Only Cmax rows with above the 30% switch widen; pmin(...) caps at the CV = 50% value before exp(+- k * sWRc), giving the 69.84-143.19% ceiling. AUC rows always keep the fixed limits.

  • Decision (step 3). PASS requires the 90% CI to lie inside the (possibly widened) limits and the GMR to lie inside the fixed 0.80 – 1.25 point-estimate constraint. This is the safeguard that keeps the two products within 25% on average even when the interval is widened.

  • Report. Everything is multiplied back by 100 for a percentage-scale table; the fraction scale is used internally only for the comparisons.

image-20260721-162804.png

Interpretation: all three parameters are bioequivalent. Every 90% CI sits well inside 80-125% with GMRs near 100%, and since stayed below the 30% switch, no Cmax widening was triggered.

GMR & confidence intervals plot:

As a visualisation of the results table, the plot draws each parameter's GMR and 90% CI on top of its acceptance window, making the ABEL decision easy to see: bioequivalence holds wherever the CI stays inside the band. The plot runs on the res data frame built above.

R
library(ggplot2)

pd <- within(res, {
  Parameter <- factor(Parameter, levels = Parameter)
  BE        <- ifelse(PASS, "PASS", "FAIL")
})

abel <- ggplot(pd, aes(x = Parameter)) +
  # per param acceptance limits (widened band for Cmax if CVwR > 30%)
  geom_linerange(aes(ymin = 100 * limit_lo, ymax = 100 * limit_hi),
                 colour = "grey85", linewidth = 7, lineend = "butt") +
  geom_hline(yintercept = 100, colour = "grey60") +
  geom_hline(yintercept = c(80, 125), colour = "#000000", linetype = "dashed", linewidth = 1.3) +
  geom_errorbar(aes(ymin = 100 * CI_lo, ymax = 100 * CI_hi, colour = BE),  # GMR point estimate + 90% CI
                width = 0.15, linewidth = 0.8) +
  geom_point(aes(y = 100 * GMR, colour = BE), size = 3) +
  scale_colour_manual(values = c(PASS = "#1b7837", FAIL = "#b2182b")) +
  coord_cartesian(ylim = c(70, 135)) +   
  labs(x = NULL, y = "Test / Reference ratio (%)", colour = NULL,
       title = "GMR and 90% CI vs ABEL acceptance limits") +
  theme_minimal(base_size = 12) +
  theme(legend.position = "right")
ABEL-20260721-164458.svg

The grey band is each parameter's acceptance window (here 80-125%, since no widening triggered); the dashed lines mark the fixed 80-125% reference and the solid line marks 100%.


Last updated: