stats.bioequivalence¶
Average bioequivalence: the two one-sided tests on the geometric mean ratio.
Test and reference are bioequivalent when the 90 % confidence interval of the geometric mean ratio of the exposure lies within 80-125 % (FDA 2026, EMA 2010, ICH M13A 2024), which is the two one-sided tests procedure of Schuirmann (1987) at \(\alpha = 0.05\). The interval comes from the design of the study: a 2x2 crossover (each subject receives both formulations in two periods, in one of two sequences) is analysed with the period differences of Chow & Liu (2009, ch. 3), which is the analysis of variance with sequence, period and subject-within-sequence effects on the log scale; a paired design uses the within-subject differences; parallel groups use the Welch interval.
A replicate design, in which at least one formulation is given twice
(Design.REPLICATE, the sequences TRTR/RTRT, TRT/RTR and TRRT/RTTR), is
analysed with the fixed effects analysis of variance of the log values with
sequence, subject within sequence, period and formulation, which is Method A
of the EMA; it separates the within-subject variability of the reference
(cv_intra_r) from the one of the test (cv_intra_t) and is what the
reference-scaled acceptance criteria need. bioequivalence(..., scaling=...)
applies them: the average bioequivalence with expanding limits of the EMA
(ABEL), the reference-scaled average bioequivalence of the FDA (RSABE) and
the two narrow therapeutic index rules. The mixed model (Method B of the EMA,
the FDA model) is out of scope; it needs a restricted maximum likelihood fit
which the dependencies of the package do not carry.
Design
¶
Bases: StrEnum
Design of a bioequivalence study.
BEParameter
dataclass
¶
BEParameter(
name,
unit,
gmr,
ci_low,
ci_high,
ci_level,
limits,
bioequivalent,
p_lower,
p_upper,
p_value,
log_ratio,
se_log,
df,
design,
cv_intra,
p_period,
p_sequence,
n_test,
n_reference,
carryover=(),
cv_intra_r=float("nan"),
cv_intra_t=float("nan"),
scaled=False,
limits_scaled=None,
criterion=None,
sd_ratio_upper=float("nan"),
anova=None,
)
Bioequivalence of one parameter.
Attributes:
| Name | Type | Description |
|---|---|---|
name |
str
|
name of the parameter |
unit |
str
|
unit of the parameter |
gmr |
float
|
geometric mean ratio test / reference |
ci_low |
float
|
lower bound of the interval of the ratio |
ci_high |
float
|
upper bound of the interval |
ci_level |
float
|
level of the interval |
limits |
tuple[float, float]
|
acceptance limits of the ratio |
bioequivalent |
bool
|
whether the interval lies within the limits |
p_lower |
float
|
p value of the test against the lower limit |
p_upper |
float
|
p value of the test against the upper limit |
p_value |
float
|
the larger of the two, the p value of the TOST procedure |
log_ratio |
float
|
\(\ln \mathrm{GMR}\) |
se_log |
float
|
standard error of |
df |
float
|
degrees of freedom |
design |
Design
|
the design of the analysis |
cv_intra |
float
|
within-subject coefficient of variation \(\sqrt{e^{\sigma_e^2} - 1}\), |
p_period |
float
|
p value of the period effect (crossover), |
p_sequence |
float
|
p value of the sequence (carryover) effect (crossover), |
n_test |
int
|
number of test values |
n_reference |
int
|
number of reference values |
carryover |
tuple[str, ...]
|
the subjects whose pre-dose concentration exceeds the
carryover threshold ( |
cv_intra_r |
float
|
within-subject CV of the reference formulation alone, from
its replicates; |
cv_intra_t |
float
|
within-subject CV of the test formulation alone; |
scaled |
bool
|
whether the acceptance rule was derived from the variability
of the reference or replaced by a narrow therapeutic index rule
( |
limits_scaled |
tuple[float, float] | None
|
the derived limits, |
criterion |
float | None
|
the upper confidence bound of the FDA scaled criterion,
\(\le 0\) for a bioequivalent formulation; |
sd_ratio_upper |
float
|
the upper 90 % bound of \(s_{wT}/s_{wR}\) of the
narrow therapeutic index criterion of the FDA, |
anova |
DataFrame | None
|
the analysis of variance table of a replicate design (source,
|
to_dict
¶
The fields as a dictionary with the enumerations as strings.
Returns:
| Type | Description |
|---|---|
dict[str, Any]
|
Field name to value. |
BEResult
dataclass
¶
Bioequivalence of several parameters.
Attributes:
| Name | Type | Description |
|---|---|---|
parameters |
dict[str, BEParameter]
|
parameter name to its result |
bioequivalent |
bool
|
whether every parameter is bioequivalent |
limits |
tuple[float, float]
|
the acceptance limits which were requested, the |
ci_level |
float
|
the level of the intervals |
abel_limits
¶
The expanding limits of the EMA for a within-subject CV of the reference.
with \(k = 0.760\); the CV is capped at 50 % before it is used, so that
the limits never leave 69.84-143.19 % (EMA 2010, 4.1.10). At or below a CV
of 30 % the EMA does not widen at all and the limits stay 80.00-125.00 %,
which is the switching condition PowerTOST::scABEL uses as well: the
formula would give 80.003-124.995 % just above 30 % and so return limits
slightly narrower than the unscaled ones.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
cv_intra_r
|
float
|
the within-subject coefficient of variation of the reference formulation, as a fraction. |
required |
Returns:
| Type | Description |
|---|---|
tuple[float, float]
|
The lower and the upper acceptance limit. |
Raises:
| Type | Description |
|---|---|
ValueError
|
if |
rsabe_criterion
¶
rsabe_criterion(
difference,
se_difference,
df_difference,
s2_wr,
df_wr,
*,
sigma_w0=FDA_SIGMA_W0,
delta=FDA_DELTA,
alpha=0.05,
)
The upper confidence bound of the scaled criterion of the FDA.
The linearized criterion is
and its upper \(1-\alpha\) confidence bound is Howe's approximation, the point estimate plus the root of the squared distances of the one-sided bounds of its two parts (FDA progesterone guidance 2011):
The formulations pass the criterion when \(U \le 0\).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
difference
|
float
|
the estimate \(\hat d\) of \(\ln \mathrm{GMR}\). |
required |
se_difference
|
float
|
its standard error. |
required |
df_difference
|
float
|
its degrees of freedom. |
required |
s2_wr
|
float
|
the within-subject variance of the reference. |
required |
df_wr
|
float
|
its degrees of freedom. |
required |
Other Parameters:
| Name | Type | Description |
|---|---|---|
sigma_w0 |
float
|
the regulatory standard deviation of the criterion, 0.25 for a highly variable drug and 0.10 for a narrow therapeutic index drug. |
delta |
float
|
the bioequivalence limit the criterion is built from. |
alpha |
float
|
the level of the one-sided bound. |
Returns:
| Type | Description |
|---|---|
float
|
The upper bound |
float
|
difference or of the reference variance. |
tost
¶
tost(
test,
reference,
*,
limits=(0.8, 1.25),
ci_level=0.9,
design=None,
scaling="none",
scaled_parameters=DEFAULT_SCALED_PARAMETERS,
)
Two one-sided tests of the geometric mean ratio against the acceptance limits.
\(t_L = (\ln \mathrm{GMR} - \ln \theta_L) / \mathrm{se}\),
\(t_U = (\ln \theta_U - \ln \mathrm{GMR}) / \mathrm{se}\), each tested one-sided
with the degrees of freedom of the design at \(\alpha = (1 - \mathrm{ci\_level}) / 2\);
rejecting both is the same as the interval at ci_level lying within
the limits (Schuirmann 1987). Without a standard error (a single
subject, or two samples without a within-subject difference) the two
tests are undefined: the p values and the interval are NaN and the
parameter is not bioequivalent, as in compare.
A REPLICATE design is analysed with the fixed effects analysis of
variance of _replicate (EMA Method A) and reports cv_intra_r,
cv_intra_t and the anova table with the ratio; n_test and
n_reference count the administrations there, not the subjects, since a
subject carries several of each. A subject who misses a period is kept and
the unbalanced design is fitted as it is, which is what the least squares
fit is for; the subjects with fewer administrations than their sequence
asks for are named in a log line at info level.
scaling replaces the acceptance rule by one of the reference-scaled
rules of the guidances:
| value | rule |
|---|---|
"none" |
the 90 % interval within limits, the default |
"ema" |
average bioequivalence with expanding limits (ABEL): for cmax alone, and only above a cv_intra_r of 30 %, the limits widen to \(e^{\pm 0.760 s_{wR}}\) (capped at 69.84-143.19 %) and the point estimate must lie within 80.00-125.00 % |
"fda" |
reference-scaled average bioequivalence (RSABE): above \(s_{wR} = 0.294\) the upper 95 % bound of \((\mu_T-\mu_R)^2 - \theta s_{wR}^2\) must not be positive and the point estimate must lie within 80.00-125.00 %; below it the unscaled analysis decides |
"fda_nti" |
the same criterion with \(\sigma_{w0} = 0.10\) and always scaled, plus the unscaled 90 % interval within 80.00-125.00 % and the upper 90 % bound of \(s_{wT}/s_{wR}\) at most 2.500 |
"ema_nti" |
the limits tightened to 90.00-111.11 % |
Every rule but "ema_nti" needs the within-subject variability of the
reference and therefore a replicate design. limits carries the limits
the verdict was taken against, limits_scaled the derived ones and
criterion the bound of the FDA rule, which has no limits at all;
BEResult.limits stays the limits which were requested.
The point estimate of the two rules of the FDA is
\(e^{\hat d}\) of the subject-level mean of the within-subject
differences, the estimate the criterion itself is built on, and not the
formulation effect gmr of the analysis of variance; the two agree on a
balanced design and differ on an unbalanced one, and the guidance takes
both conditions on the same number. gmr keeps reporting the effect of
the analysis of variance either way. The point estimate of the EMA rule is
gmr, which is the estimate its interval is built on.
scaled_parameters names the parameters whose limits the EMA widens,
("cmax",) by default; a steady state study whose peak is called
cmax_ss passes scaled_parameters=("cmax_ss",). It has no effect on the
rules of the FDA, which scale every parameter, or on "ema_nti", which
tightens every parameter it is asked for.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
test
|
ParameterSample
|
the test sample. |
required |
reference
|
ParameterSample
|
the reference sample. |
required |
limits
|
tuple[float, float]
|
acceptance limits of the ratio. |
(0.8, 1.25)
|
ci_level
|
float
|
level of the interval, 0.90 for the usual \(\alpha = 0.05\). |
0.9
|
design
|
Design | str | None
|
the design, as the member or as its string, detected from the samples by default. |
None
|
scaling
|
Scaling | str
|
the acceptance rule, see the table above. |
'none'
|
scaled_parameters
|
Sequence[str]
|
the parameters whose limits |
DEFAULT_SCALED_PARAMETERS
|
Returns:
| Type | Description |
|---|---|
BEParameter
|
The result of the parameter. |
Raises:
| Type | Description |
|---|---|
ValueError
|
for reversed limits, an unknown design or scaling, a design the samples do not support, or a scaling the design cannot carry. |
carryover_table
¶
The pre-dose concentration of every subject against its own maximum.
ICH M13A (2024, 2.2.3.3), the FDA ANDA bioequivalence guidance and the EMA bioequivalence guideline all draw the same line: a period whose pre-dose concentration is more than 5 % of that subject's \(C_\mathrm{max}\) of the same period carries drug from the previous period, and the subject "should be dropped from the study evaluation of that period"; M13A adds that a statistical test for carryover "is not considered relevant", so this comparison replaces it.
The pre-dose value of a sample is the last value strictly before its
dose time. A sample recorded at the dose time counts as a pre-dose sample
for an extravascular route only, where it is drawn before the dose is
swallowed; after an intravenous bolus or during an infusion the value at
the dose time is the post-dose value of this period and says nothing about
the previous one (reading it would flag every subject with a fraction of
1). A sample whose schedule carries no value before the dose has no
pre-dose value: predose is NaN and the sample is not flagged.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
batch
|
Timecourses
|
the timecourses of the period, one sample per subject |
required |
result
|
ParameterResult
|
the analysis of that batch, for \(C_\mathrm{max}\) |
required |
Other Parameters:
| Name | Type | Description |
|---|---|---|
threshold |
float
|
the share of \(C_\mathrm{max}\) above which the sample is flagged, 0.05 of the three guidances |
Returns:
| Type | Description |
|---|---|
DataFrame
|
One row per sample with the sample dimensions, |
DataFrame
|
|
Raises:
| Type | Description |
|---|---|
ValueError
|
if the result carries no |
carryover_labels
¶
The labels of the subjects carryover_table flags.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
batch
|
Timecourses
|
the timecourses of the period |
required |
result
|
ParameterResult
|
the analysis of that batch |
required |
Other Parameters:
| Name | Type | Description |
|---|---|---|
dim |
str
|
the sample dimension whose labels name the subjects |
threshold |
float
|
the share of Cmax above which a sample is flagged |
Returns:
| Type | Description |
|---|---|
list[str]
|
The labels, as strings, in the order of the samples. |
Raises:
| Type | Description |
|---|---|
ValueError
|
if |
bioequivalence
¶
bioequivalence(
test,
reference,
parameters=("auc_inf_obs", "cmax"),
*,
dim="individual",
limits=(0.8, 1.25),
ci_level=0.9,
design=None,
scaling="none",
scaled_parameters=DEFAULT_SCALED_PARAMETERS,
include_excluded=False,
carryover="ignore",
carryover_threshold=0.05,
test_batch=None,
reference_batch=None,
**indexers,
)
Average bioequivalence of the parameters of two results.
Every parameter is taken with ParameterResult.sample(name, dim, **indexers)
from both results and tested with tost; the study is bioequivalent
when every parameter is. A subject which a result marks excluded
(pkpdutils.nca.NCAResult.exclude) is left out unless include_excluded
asks for it.
With the timecourses of the two periods (test_batch, reference_batch)
the pre-dose concentrations are read as well (carryover_table):
carryover="flag" names the subjects above the threshold in
BEParameter.carryover and carryover="exclude" drops them from the
analysis of every parameter, which is what ICH M13A (2024) and the FDA ANDA
guidance ask for.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
test
|
ParameterResult
|
the result of the test formulation. |
required |
reference
|
ParameterResult
|
the result of the reference formulation. |
required |
parameters
|
Sequence[str]
|
the parameters to test. |
('auc_inf_obs', 'cmax')
|
dim
|
str
|
the sample dimension of the individuals. |
'individual'
|
limits
|
tuple[float, float]
|
acceptance limits of the ratio. |
(0.8, 1.25)
|
ci_level
|
float
|
level of the intervals. |
0.9
|
design
|
Design | str | None
|
the design, as the member or as its string, detected from the samples by default. |
None
|
scaling
|
Scaling | str
|
the acceptance rule, |
'none'
|
scaled_parameters
|
Sequence[str]
|
the parameters whose limits |
DEFAULT_SCALED_PARAMETERS
|
include_excluded
|
bool
|
analyse the excluded subjects as well. |
False
|
carryover
|
Literal['ignore', 'flag', 'exclude']
|
what to do with a subject whose pre-dose concentration
exceeds |
'ignore'
|
carryover_threshold
|
float
|
the share of Cmax which counts as carryover. |
0.05
|
test_batch
|
Timecourses | None
|
the timecourses the test result was computed from, needed for the carryover check. |
None
|
reference_batch
|
Timecourses | None
|
the timecourses of the reference result. |
None
|
**indexers
|
Any
|
coordinate label per remaining sample dimension. |
{}
|
Returns:
| Type | Description |
|---|---|
BEResult
|
The result. |
Raises:
| Type | Description |
|---|---|
ValueError
|
for an unknown design, for a carryover check without the
batches, or as |