Workflow

An analysis can be run in one call or as a sequence of lower-level steps. Both produce the same inference and the same result summary table; they differ only in how much of the pipeline is exposed.

One endpoint per analysis

Whether carrying out the one-call workflow or the lower-level workflow, each analysis concerns a single pharmacokinetic endpoint, selected with the endpoint argument. A study that requires bioequivalence to be shown for both Cmax and AUC should therefore be run as two separate analyses on the same dataset (one per endpoint), each returning its own result.

One-call workflow (the typical usage)

The pumas_be function runs preprocessing, inference, assessment, and result-table construction in a single call, returning a BioequivalenceEndpointOutput. This is the recommended entry point for most analyses.

using Bioequivalence, PharmaDatasets

be_data = dataset(joinpath("bioequivalence", "RTRT_TRTR", "PJ2017_4_4"))
out = pumas_be(be_data, FDA_HighlyVariable; endpoint = :AUC)
Observation Counts
Sequence ╲ Period 1 2 3 4
RTRT 27 27 26 26
TRTR 27 27 26 26
Paradigm: Replicated crossover (supports ABE, ABEL, and RSABE)
Model: Mixed model with REML (unequal variance)
Criteria: FDA RSABE for HV
Endpoint: AUC
Formulations: Reference(R), Test(T)
Results(AUC) Assessment Criteria
R Geometric Marginal Mean 339.9
Geometric Naive Mean 340.8
T Geometric Marginal Mean 375.9
Geometric Naive Mean 376.7
Geometric Mean T/R Ratio (%) 110.6 Pass GMR ∈ [80, 125]
Degrees of Freedom 140.7
90% Confidence Interval (%) [102.9, 118.9]
Variability CVᵣ (%) | σ̂ᵣ 35.4 | 0.3436 OK for RS CVᵣ ≥ Minimal CVᵣ
CVₜ (%) | σ̂ₜ 27.87 | 0.2735
Variability Ratio (%) 79.6
ANOVA Formulation (p-value) 0.02205
Sequence (p-value) 0.9038
Period (p-value) 0.1555
Reference Scaling Params Reference Scaling Constant (θ | k = √θ) 0.7967 | 0.8926
Minimal CVᵣ for Reference Scaling (%) 30.0 | 0.294
Reference Scaling Analysis Geometric Mean T/R Ratio (%) 111
Standard Error (Log Scale) 0.0438
90% Confidence Interval (%) [103.2, 119.5]
Degrees of Freedom 50
Howe's Approx RSABE Stat (95%) -0.05172 Pass ≤ 0

The first positional argument is the dataset; the optional second positional argument is the regulatory criterion (default StandardBioequivalenceCriterion). See the pumas_be docstring for the full argument list.

Displaying out (above) renders the result summary table, the typical output. The same result can also be introspected programmatically through out.result_table (an OrderedDict) or out.result (a DataFrame); see Output.

The out.result_table field gives a symbolic description of the summary table:

out.result_table
OrderedCollections.OrderedDict{Symbol, Any} with 35 entries:
  :PARADIGM                  => replicated_crossover_supports_abe_abel_and_rsabe
  :INFERENCE_MODEL_TYPE      => unequal_variance_mixed_model_reml
  :CRITERIA                  => FDA_HighlyVariable
  :SECTION_REFERENCE         => "R"
  :MARG_GM_REF               => 339.947
  :NAIVE_GM_REF              => 340.767
  :SECTION_FORMULATION_1     => "T"
  :MARG_GM_TRT               => 375.865
  :NAIVE_GM_TRT              => 376.685
  :GMR                       => 1.106
  :DF                        => 140.7
  :CI                        => (1.029, 1.189)
  :POINT_ESTIMATE_PASS       => true
  :POINT_ESTIMATE_RULE       => "GMR ∈ [80, 125]"
  :SECTION_MODEL_OUT_1       => "Variability"
  :CVᵣ                       => 0.354
  :CVₜ                       => 0.2787
  :SIGMA_RATIO               => 0.796
  :SECTION_MODEL_OUT_2       => "ANOVA"
  :ANOVA_P_VALUE_FORMULATION => 0.02205
  :ANOVA_P_VALUE_SEQUENCE    => 0.90383
  :ANOVA_P_VALUE_PERIOD      => 0.15552
  :SECTION_MODEL_OUT_3       => "Reference Scaling Params"
  :REFERENCE_SCALE           => 0.796689
  :CV_MIN_VALUE              => 0.3
  ⋮                          => ⋮

You can then programmatically access specific fields, for example out.result_table[:GMR]:

out.result_table[:GMR]
1.106

Another representation of the output is in a DataFrame via the result field, in particular holding the raw per-comparison inference (the estimate δ, its standard error, the confidence bounds, the GMR, and the variability estimates):

out.result
1×18 DataFrame
RowParameterδSElnLBlnUBtrt_margGMref_margGMtrt_naiveGMref_naiveGMGMRLBUBCVᵣCVₜσ_ratioσ⁺cbdof
StringFloat64Float64Float64Float64Float64Float64Float64Float64Float64Float64Float64Float64Float64Float64Float64Float64Float64
1T - R0.10040.043380.02860.1723375.865339.947376.685340.7671.1061.0291.1890.3540.27870.7961.007-0.05172140.7

Exponentiating the log-scale estimate δ recovers the GMR shown above:

round(exp(out.result[1, :δ]), digits = 3)
1.106

Lower-level workflow

While using pumas_be is typically fine for most cases, the same analysis can be dissected and run as five steps:

preprocess_be → run_be → assess_be → set_result_table! → result_summary_table

StepFunctionRole
1preprocess_beStandardize the dataset (see Input Data).
2run_beFit the model and compute the inference.
3assess_beEvaluate the inference against a criterion.
4set_result_table!Render the assessment into the output's result table.
5result_summary_tablePretty-print the populated table.

While these five steps are more lengthy, in cases where intermediate results are needed they can be of use.

More details on each of the steps

1. Preprocessing.

pp_data = preprocess_be(be_data; endpoint = :AUC)

The preprocess_be function takes a raw dataset (with id, sequence, period, and an endpoint column) and returns a standardized DataFrame with columns id, id_within_sequence, sequence, period, formulation, and endpoint, where the endpoint is log-transformed and missing observations are dropped (see Input Data).

2. Inference.

out = run_be(pp_data; reference_scale = reference_scale_default(FDA_HighlyVariable))

A dataset in the form returned by preprocess_be is the required input to run_be, which fits the model and returns a BioequivalenceEndpointOutput carrying the inference, but not yet any assessment.

The run_be function is criterion-agnostic: when calling through pumas_be each criterion sets the inference options it needs implicitly, whereas run_be has fixed defaults of its own (an omitted reference_scale is treated as the FDA highly-variable constant θ ≈ 0.7967, which together with the model defaults happens to match FDA_HighlyVariable), so to reproduce any other criterion you must pass its options explicitly. Pass the criterion's reference-scaling constant via reference_scale_default. The EMA criteria additionally default to the subjects-fixed linear model, so EMA_HighlyVariable also requires custom_model = LinearModel, wsv_model = additive_sequence_wsv_model, and homogeneity = true (see Models):

out = run_be(
    pp_data;
    reference_scale = reference_scale_default(EMA_HighlyVariable),
    custom_model    = LinearModel,
    wsv_model       = additive_sequence_wsv_model,
    homogeneity     = true,
)

The run_be function also accepts endpoint_name to label the endpoint in the output, which pumas_be sets from endpoint.

3. Assessment.

decision = assess_be(FDA_HighlyVariable, out)

The assess_be function takes a regulatory criterion and the run_be output and returns a BioequivalenceEndpointDecision carrying the per-comparison assessments. It does not mutate out (see Output).

4. Rendering the result table.

set_result_table!(decision)

The set_result_table! function renders the decision into out.result_table (a symbolic OrderedDict description of the result summary table), mutating the output in place.

5. The summary table.

result_summary_table(out)

The result_summary_table function produces a table object that displays as plain text in the terminal and as a formatted table in rich environments such as these docs.

Worked example: the five steps reproduce the one-call result

Running the five steps on the dataset from the one-call workflow above yields the same analysis as pumas_be(be_data, FDA_HighlyVariable; endpoint = :AUC):

# Step 1
pp_data = preprocess_be(be_data; endpoint = :AUC)

# Step 2
out_steps = run_be(
    pp_data;
    reference_scale = reference_scale_default(FDA_HighlyVariable),
    endpoint_name   = "AUC",
)

# Step 3
decision = assess_be(FDA_HighlyVariable, out_steps)

# Step 4
set_result_table!(decision)

# Step 5
result_summary_table(out_steps)
Paradigm: Replicated crossover (supports ABE, ABEL, and RSABE)
Model: Mixed model with REML (unequal variance)
Criteria: FDA RSABE for HV
Endpoint: AUC
Formulations: Reference(R), Test(T)
Results(AUC) Assessment Criteria
R Geometric Marginal Mean 339.9
Geometric Naive Mean 340.8
T Geometric Marginal Mean 375.9
Geometric Naive Mean 376.7
Geometric Mean T/R Ratio (%) 110.6 Pass GMR ∈ [80, 125]
Degrees of Freedom 140.7
90% Confidence Interval (%) [102.9, 118.9]
Variability CVᵣ (%) | σ̂ᵣ 35.4 | 0.3436 OK for RS CVᵣ ≥ Minimal CVᵣ
CVₜ (%) | σ̂ₜ 27.87 | 0.2735
Variability Ratio (%) 79.6
ANOVA Formulation (p-value) 0.02205
Sequence (p-value) 0.9038
Period (p-value) 0.1555
Reference Scaling Params Reference Scaling Constant (θ | k = √θ) 0.7967 | 0.8926
Minimal CVᵣ for Reference Scaling (%) 30.0 | 0.294
Reference Scaling Analysis Geometric Mean T/R Ratio (%) 111
Standard Error (Log Scale) 0.0438
90% Confidence Interval (%) [103.2, 119.5]
Degrees of Freedom 50
Howe's Approx RSABE Stat (95%) -0.05172 Pass ≤ 0

This is the same table shown for out at the top of the page, which can be checked directly:

out_steps.result_table == out.result_table
true

Two details are worth noting. The endpoint_name argument is passed explicitly because run_be does not see the endpoint argument; without it the Endpoint row of the summary would not be labelled AUC. And reference_scale is passed explicitly for clarity, although an omitted reference_scale already defaults to the FDA highly-variable constant (see run_be); for FDA_HighlyVariable this is the only inference option the criterion sets, whereas EMA_HighlyVariable also requires the model options shown above.