22  Architecture Overview

22.1 Class hierarchy

PKNCA uses four S3 classes that form a strict pipeline. Each wraps the previous one:

classDiagram
    class PKNCAconc {
        data: data.frame
        formula: conc~time|groups
        subject: column name
        sparse: logical
        units: concu/timeu
        exclude / exclude_half.life
    }
    class PKNCAdose {
        data: data.frame
        formula: dose~time|groups
        route: intravas./extravas.
        duration / rate
        units: doseu/timeu
    }
    class PKNCAdata {
        conc: PKNCAconc
        dose: PKNCAdose
        intervals: data.frame
        impute: method string
        units: pknca_units_table()
        options: named list
    }
    class PKNCAresults {
        result: data.frame
        data: PKNCAdata
    }

    PKNCAconc --> PKNCAdata
    PKNCAdose --> PKNCAdata
    PKNCAdata --> PKNCAresults

22.2 The interval column system

The parameter registry is the core extensibility mechanism. Every NCA parameter is registered as an interval column via add.interval.col().

# Inspect registration for auclast
cols <- get.interval.cols()
str(cols[["auclast"]])
List of 9
 $ FUN        : chr "pk.calc.auc.last"
 $ values     : logi [1:2] FALSE TRUE
 $ unit_type  : chr "auc"
 $ pretty_name: chr "AUClast"
 $ desc       : chr "The area under the concentration time curve from the beginning of the interval to the last concentration above "| __truncated__
 $ sparse     : logi FALSE
 $ formalsmap : list()
 $ depends    : NULL
 $ datatype   : chr "interval"

Each entry contains:

Field Purpose
FUN The R function that computes this parameter
depends Character vector of parameters that must be computed first
desc Human-readable description
formalsmap Maps function arguments to interval/group/options data
value.ok Validation function for the computed result
sparse Whether this parameter works for sparse PK

22.3 formalsmap

formalsmap is how PKNCA passes the right data to each parameter function. Arguments can be sourced from four places:

Source type Example What it provides
Interval column conc, time Concentrations/times for the current interval
Other parameters auclast = "auclast" A previously computed NCA result
Interval bounds start, end The interval start/end times
Options auc.method Value from PKNCA.options()

22.4 Writing a custom parameter

To add a new NCA parameter:

# Example: compute the "exposure ratio" — AUCinf / AUClast
pk.calc.exposure.ratio <- function(aucinf.obs, auclast) {
  aucinf.obs / auclast
}

add.interval.col(
  name     = "exposure.ratio",
  FUN      = "pk.calc.exposure.ratio",  # must be a character string, not the function object
  unit_type = "unitless",
  pretty_name = "Exposure Ratio (AUCinf/AUClast)",
  desc     = "Ratio of AUCinf to AUClast; >1 indicates extrapolation",
  depends  = c("aucinf.obs", "auclast"),
  formalsmap = list(aucinf.obs = "aucinf.obs", auclast = "auclast")
)

# Now it can be requested in intervals
d_conc <- as.data.frame(Theoph) |> rename(time = Time, subject = Subject)
d_dose <- Theoph |> as.data.frame() |>
  dplyr::group_by(Subject) |>
  dplyr::summarise(dose = Dose[1] * Wt[1], .groups = "drop") |>
  dplyr::rename(subject = Subject) |>
  dplyr::mutate(time = 0)

o_conc <- PKNCAconc(d_conc, conc ~ time | subject)
o_dose <- PKNCAdose(d_dose, dose ~ time | subject, route = "extravascular")

custom_interval <- data.frame(
  start = 0, end = Inf,
  auclast = TRUE, aucinf.obs = TRUE,
  exposure.ratio = TRUE
)

o_data <- PKNCAdata(o_conc, o_dose, intervals = custom_interval)
o_nca  <- pk.nca(o_data)

as.data.frame(o_nca) |>
  dplyr::filter(PPTESTCD %in% c("auclast", "aucinf.obs", "exposure.ratio")) |>
  dplyr::select(subject, PPTESTCD, PPORRES) |>
  dplyr::arrange(subject, PPTESTCD)
# A tibble: 36 × 3
   subject PPTESTCD       PPORRES
   <ord>   <chr>            <dbl>
 1 6       aucinf.obs       82.2 
 2 6       auclast          71.7 
 3 6       exposure.ratio    1.15
 4 7       aucinf.obs      101.  
 5 7       auclast          88.0 
 6 7       exposure.ratio    1.15
 7 8       aucinf.obs      102.  
 8 8       auclast          86.8 
 9 8       exposure.ratio    1.18
10 11      aucinf.obs       86.9 
# ℹ 26 more rows

22.5 Options system

PKNCA.options() is a named list stored as a package-level environment. It is accessed in three ways:

# 1. Read all options
PKNCA.options()
$adj.r.squared.factor
[1] 1e-04

$max.missing
[1] 0.5

$auc.method
[1] "lin up/log down"

$conc.na
[1] "drop"

$conc.blq
$conc.blq$first
[1] "keep"

$conc.blq$middle
[1] "drop"

$conc.blq$last
[1] "keep"


$debug
NULL

$first.tmax
[1] TRUE

$first.tmin
[1] TRUE

$allow.tmax.in.half.life
[1] FALSE

$keep_interval_cols
NULL

$min.hl.points
[1] 3

$min.span.ratio
[1] 2

$max.aucinf.pext
[1] 20

$min.hl.r.squared
[1] 0.9

$progress
[1] TRUE

$tau.choices
[1] NA

$single.dose.aucs
  start end auclast aucall aumclast aumcall aucint.last aucint.last.dose
1     0  24    TRUE  FALSE    FALSE   FALSE       FALSE            FALSE
2     0 Inf   FALSE  FALSE    FALSE   FALSE       FALSE            FALSE
  aucint.all aucint.all.dose    c0  cmax  cmin  tmax  tmin tlast tfirst
1      FALSE           FALSE FALSE FALSE FALSE FALSE FALSE FALSE  FALSE
2      FALSE           FALSE FALSE  TRUE FALSE  TRUE FALSE FALSE  FALSE
  clast.obs cl.last cl.all     f mrt.last mrt.iv.last vss.last vss.iv.last
1     FALSE   FALSE  FALSE FALSE    FALSE       FALSE    FALSE       FALSE
2     FALSE   FALSE  FALSE FALSE    FALSE       FALSE    FALSE       FALSE
    cav cav.int.last cav.int.all ctrough cstart   ptr  tlag deg.fluc swing
1 FALSE        FALSE       FALSE   FALSE  FALSE FALSE FALSE    FALSE FALSE
2 FALSE        FALSE       FALSE   FALSE  FALSE FALSE FALSE    FALSE FALSE
   ceoi aucabove.predose.all aucabove.trough.all count_conc count_conc_measured
1 FALSE                FALSE               FALSE      FALSE               FALSE
2 FALSE                FALSE               FALSE      FALSE               FALSE
  totdose volpk    ae clr.last clr.obs clr.pred    fe ertlst ermax ertmax
1   FALSE FALSE FALSE    FALSE   FALSE    FALSE FALSE  FALSE FALSE  FALSE
2   FALSE FALSE FALSE    FALSE   FALSE    FALSE FALSE  FALSE FALSE  FALSE
  sparse_auclast sparse_auc_se sparse_auc_df sparse_aumclast sparse_aumc_se
1          FALSE         FALSE         FALSE           FALSE          FALSE
2          FALSE         FALSE         FALSE           FALSE          FALSE
  sparse_aumc_df time_above aucivlast aucivall aucivint.last aucivint.all
1          FALSE      FALSE     FALSE    FALSE         FALSE        FALSE
2          FALSE      FALSE     FALSE    FALSE         FALSE        FALSE
  aucivpbextlast aucivpbextall aucivpbextint.last aucivpbextint.all half.life
1          FALSE         FALSE              FALSE             FALSE     FALSE
2          FALSE         FALSE              FALSE             FALSE      TRUE
  r.squared adj.r.squared lambda.z.corrxy lambda.z lambda.z.time.first
1     FALSE         FALSE           FALSE    FALSE               FALSE
2     FALSE         FALSE           FALSE    FALSE               FALSE
  lambda.z.time.last lambda.z.n.points clast.pred span.ratio tobit_residual
1              FALSE             FALSE      FALSE      FALSE          FALSE
2              FALSE             FALSE      FALSE      FALSE          FALSE
  adj_tobit_residual lambda.z.n.points_blq thalf.eff.last thalf.eff.iv.last
1              FALSE                 FALSE          FALSE             FALSE
2              FALSE                 FALSE          FALSE             FALSE
  kel.last kel.iv.last cl.sparse.last mrt.sparse.last vss.sparse.last
1    FALSE       FALSE          FALSE           FALSE           FALSE
2    FALSE       FALSE          FALSE           FALSE           FALSE
  aucinf.obs aucinf.pred aumcinf.obs aumcinf.pred aucint.inf.obs
1      FALSE       FALSE       FALSE        FALSE          FALSE
2       TRUE       FALSE       FALSE        FALSE          FALSE
  aucint.inf.obs.dose aucint.inf.pred aucint.inf.pred.dose aucivinf.obs
1               FALSE           FALSE                FALSE        FALSE
2               FALSE           FALSE                FALSE        FALSE
  aucivinf.pred aucivpbextinf.obs aucivpbextinf.pred aucpext.obs aucpext.pred
1         FALSE             FALSE              FALSE       FALSE        FALSE
2         FALSE             FALSE              FALSE       FALSE        FALSE
  kel.sparse.last cl.obs cl.pred mrt.obs mrt.pred mrt.iv.obs mrt.iv.pred
1           FALSE  FALSE   FALSE   FALSE    FALSE      FALSE       FALSE
2           FALSE  FALSE   FALSE   FALSE    FALSE      FALSE       FALSE
  mrt.md.obs mrt.md.pred vz.obs vz.pred vz.sparse.last vss.obs vss.pred
1      FALSE       FALSE  FALSE   FALSE          FALSE   FALSE    FALSE
2      FALSE       FALSE  FALSE   FALSE          FALSE   FALSE    FALSE
  vss.iv.obs vss.iv.pred vss.md.obs vss.md.pred cav.int.inf.obs
1      FALSE       FALSE      FALSE       FALSE           FALSE
2      FALSE       FALSE      FALSE       FALSE           FALSE
  cav.int.inf.pred thalf.eff.obs thalf.eff.pred thalf.eff.iv.obs
1            FALSE         FALSE          FALSE            FALSE
2            FALSE         FALSE          FALSE            FALSE
  thalf.eff.iv.pred kel.obs kel.pred kel.iv.obs kel.iv.pred auclast.dn
1             FALSE   FALSE    FALSE      FALSE       FALSE      FALSE
2             FALSE   FALSE    FALSE      FALSE       FALSE      FALSE
  aucall.dn aucinf.obs.dn aucinf.pred.dn aumclast.dn aumcall.dn aumcinf.obs.dn
1     FALSE         FALSE          FALSE       FALSE      FALSE          FALSE
2     FALSE         FALSE          FALSE       FALSE      FALSE          FALSE
  aumcinf.pred.dn cmax.dn cmin.dn clast.obs.dn clast.pred.dn cav.dn ctrough.dn
1           FALSE   FALSE   FALSE        FALSE         FALSE  FALSE      FALSE
2           FALSE   FALSE   FALSE        FALSE         FALSE  FALSE      FALSE
  clr.last.dn clr.obs.dn clr.pred.dn exposure.ratio
1       FALSE      FALSE       FALSE          FALSE
2       FALSE      FALSE       FALSE          FALSE

$allow_partial_missing_units
[1] FALSE

$hl_method
[1] "log-linear"

$tobit_n_points_penalty
[1] 0

$tobit_optim_control
list()
# 2. Read a specific option
PKNCA.options("min.hl.points")
[1] 3
# 3. Set globally — save the old value first so you can restore it
old_val <- PKNCA.options("min.hl.points")[[1]]
PKNCA.options(min.hl.points = 4)
PKNCA.options("min.hl.points")
[1] 4
# 4. Restore by passing the saved value as a named argument
PKNCA.options(min.hl.points = old_val)
PKNCA.options("min.hl.points")
[1] 3

Prefer passing options = list(...) to PKNCAdata() over mutating global options. Global changes persist across analyses in the same session.

22.6 Sparse PK

Sparse PK (one or two samples per subject, common in preclinical or paediatric studies) is handled via a parallel code path:

  • Set sparse = TRUE in PKNCAconc()
  • Concentrations are pooled across subjects per timepoint
  • Mean concentration-time profile is computed, then NCA is run on that profile
  • Supported parameters are flagged with sparse = TRUE in their add.interval.col() registration

22.7 Key internal functions

Function Purpose
get.interval.cols() Return full parameter registry
PKNCA:::sort.interval.cols() Topological sort → execution order
PKNCA:::pk.nca.interval() Compute all parameters for one interval/group
pknca_units_table() Build unit assignment/conversion table
PKNCA_impute_method_*() Built-in imputation methods
pk.calc.auc.last() Core AUC calculation (trapezoidal)
pk.calc.half.life() λz regression with best-fit selection

This overview covers the surface. The formalsmap system, business rules (002-pk.business.rules.R), and the validation framework (v60-PKNCA-validation.Rmd) are richer topics worth exploring further.