Skip to content

feat: add bioequivalence inferential statistics functions#547

Open
billdenney wants to merge 15 commits into
mainfrom
bioequivalence-functions
Open

feat: add bioequivalence inferential statistics functions#547
billdenney wants to merge 15 commits into
mainfrom
bioequivalence-functions

Conversation

@billdenney

Copy link
Copy Markdown
Member

Add fitbe_models(), fitbe_table(), and fitbe_calculate() to compute average bioequivalence statistics from NCA results: per-endpoint linear mixed-effects models on log-transformed values, geometric mean ratios with 90 percent confidence intervals, Satterthwaite degrees of freedom, and intra-subject CV. The functions consume the long-format output of a PKNCAresults object directly.

The statistical engine packages (lme4, lmerTest, emmeans) are Suggests and guarded with requireNamespace(); tests skip when they are absent. Adds the v50-bioequivalence vignette, tests, and documentation. Formalizes the prototype workflow from PR 490.

Sang-j111 and others added 4 commits December 1, 2025 15:50
…set, I get the warning; 'No grouping variables found in the PKNCA object. Defaulting to '~ sequence + occasion + formulation'and error message; 'Reference value, "3", not found in column, "form". No sure why but can confirm that those messages work.
Add fitbe_models(), fitbe_table(), and fitbe_calculate() to compute average
bioequivalence statistics from NCA results: per-endpoint linear mixed-effects
models on log-transformed values, geometric mean ratios with 90 percent
confidence intervals, Satterthwaite degrees of freedom, and intra-subject CV.
The functions consume the long-format output of a PKNCAresults object directly.

The statistical engine packages (lme4, lmerTest, emmeans) are Suggests and
guarded with requireNamespace(); tests skip when they are absent. Adds the
v50-bioequivalence vignette, tests, and documentation. Formalizes the
prototype workflow from PR 490.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@billdenney

Copy link
Copy Markdown
Member Author

Based on the functions @Sang-j111 build in #490

billdenney and others added 4 commits June 10, 2026 21:24
Starting-point design for extending the bioequivalence functions on this
branch (PR #547) into the full reference-scaled / regulatory assessment space
of replicateBE (EMA/HC/GCC ABEL) and PowerTOST (FDA RSABE/NTID), plus a
regulatory-comparison assessor and the nlmixr2mbbe integration contract.
Build-ignored under design/.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… layer

Add a regulatory bioequivalence decision and reference-scaling layer on top of
the average-BE (fitbe_*) foundation, covering all major frameworks:

* be_assess() - full pass/fail assessment for ABE, EMA/HC/GCC expanding limits
  (ABEL), and the FDA reference-scaled RSABE/NTID/HVNTID frameworks. Accepts a
  PKNCAresults object or a tidy long data.frame.
* be_compare() - assess one dataset under several frameworks side by side.
* be_regulator() / be_expand_limits() - internalized regulatory constants and
  scaled acceptance limits (single source of truth).
* be_design() - crossover design classification and framework feasibility.
* be_within_var() - within-subject variability (swR/swT, CVwR/CVwT, swT:swR
  ratio CI) by ANOVA (default) or a mixed model.
* print/format/summary/as.data.frame methods for the new classes.

All regulatory constants and criteria are internalized, so PKNCA does not depend
on PowerTOST or replicateBE; the implementation was validated against those
packages during development and the tests pin the resulting expected values
(within-subject variances match replicateBE's estimator and the ABEL limits
match its scaled-limit output exactly). The FDA RSABE/NTID/HVNTID criteria
implement the linearized Howe/Hyslop bound from the guidances.

Extends the v50-bioequivalence vignette with a reference-scaling section.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Unify all nine bioequivalence functions (fitbe_* and be_*) under the
capitalized @family Bioequivalence tag so they group under a single
"Bioequivalence" heading.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Comment thread R/bioequivalence.R Outdated
group_cols <- NULL
if (inherits(object, "PKNCAresults")) {
assert_PKNCAresults(object)
data <- as.data.frame(as.data.frame(object, filter_excluded = TRUE))

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please only use as.data.frame() once.

billdenney and others added 7 commits June 11, 2026 00:28
Replace every function defined inside another bioequivalence function with a
top-level helper or a base-R primitive, so no closures are created per call and
the helpers are independently testable:

* be_within_var(): arm_var_anova() -> .be_arm_var() / .be_arm_var_na()
* be_design(): the per-subject pattern lambda -> .be_subject_pattern(); the
  replication-count lambdas vectorized (tapply over a logical); the first-value
  lambda -> `[`; drop the unused per_order line
* .be_isc(): the per-subject contrast lambda -> .be_subject_ilat()
* be_compare(): tryCatch() with an inline error handler -> try() + inherits()
* summary.be_compare(): the cell-selection lambda -> `[`

No behavior change; all bioequivalence tests pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Merge the original bioequivalence workflow prototype from Natalie Sang's
PR #490 (#490) to preserve her
development history and credit the inspirational work.

The prototype (BA_BEworkflow/NCA_BEworkflow.Rmd, with fit_BE_endpoints(),
create_BE_table(), calculate_BE()) inspired but is not directly used by the
production implementation in R/bioequivalence.R; it is removed in the next
commit.

Co-authored-by: Natalie Sang <nataliesang143@gmail.com>
Delete BA_BEworkflow/NCA_BEworkflow.Rmd and pknca.Rproj, merged in the
previous commit to preserve Natalie Sang's authorship. The prototype
functions inspired the production fitbe_*/be_* implementation in
R/bioequivalence.R but are not part of the package.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Mechanical regeneration of all man/*.Rd and DESCRIPTION via roxygen2 8.0.0
(link syntax, whitespace, Config/roxygen2/version). No source changes.

This commit is intentionally separate so the bioequivalence refactor that
follows has a clean, reviewable documentation diff; reviewers can skip this
formatting-only commit.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Collapse the two bioequivalence code paths (fitbe_models/fitbe_table/
fitbe_calculate and the be_assess internals) into one pipeline coordinated by
be_fit_models(), which runs, per endpoint:

  be_dataset() -> be_fit_model_single() -> be_extract_param() -> be_table()

* be_dataset(): validate, resolve the value column, drop excluded rows, detect
  the subject/sequence/period columns, and set the reference factor level.
* be_fit_model_single(): the only place models are fit; dispatches to
  be_fit_model_lmer/nlme/anova.
* be_extract_param(): geometric means, GMR + CI, intra-subject contrasts, and
  within-formulation variances; dispatches to be_extract_param_lmer/nlme/anova.
* be_table(): applies the regulator limits/criterion via the unchanged .be_*
  deciders and decides pass/fail.

be_assess()/be_compare() are now thin verbs over be_fit_models(). The method =
"A"/"B" argument is replaced by an auto-selected model_type ("lmer"/"anova"/
"nlme"); be_within_var() likewise takes model_type. The fitbe_* functions are
removed (they were unreleased). Output is byte-identical to the previous
implementation (verified across all fixtures and regulators); nlme is a new
model_type option.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Credit Natalie Sang (@Sang-j111) for the bioequivalence prototype in PR #490
that inspired the bioequivalence functions.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* be_design() now recommends a model type (2x2 crossover -> fixed-effects
  ANOVA, replicate -> mixed model), and .be_resolve_model_type() combines the
  regulator and design: the FDA family always uses intra-subject contrasts,
  otherwise the design decides. model_type = "nlme" requires a full replicate.
* The table now reports, on the measurement scale, the reference and test
  geometric means with their 90% confidence intervals (gm_reference/gm_test and
  _lower/_upper bounds).
* Units are extracted from the PKNCAresults object (PPSTRESU/PPORRESU), attached
  to the be_fit object as a `units` attribute (NULL when not provided), and
  emitted as a per-endpoint `units` column (they differ for Cmax vs AUC).

Existing decision columns are byte-identical to before across all fixtures and
regulators; the new columns are additive. Also regenerates man/PKNCA.Rd to list
Natalie Sang as a contributor.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants