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 aumcint.last aumcint.last.dose aumcint.all
1      FALSE           FALSE        FALSE             FALSE       FALSE
2      FALSE           FALSE        FALSE             FALSE       FALSE
  aumcint.all.dose    c0  cmax  cmin  tmax  tmin tlast tfirst clast.obs cl.last
1            FALSE FALSE FALSE FALSE FALSE FALSE FALSE  FALSE     FALSE   FALSE
2            FALSE FALSE  TRUE FALSE  TRUE FALSE FALSE  FALSE     FALSE   FALSE
  cl.all cl.int.all cl.int.last     f mrt.last mrt.all mrt.int.all mrt.int.last
1  FALSE      FALSE       FALSE FALSE    FALSE   FALSE       FALSE        FALSE
2  FALSE      FALSE       FALSE FALSE    FALSE   FALSE       FALSE        FALSE
  mrt.iv.last vss.last vss.iv.last vss.all vss.int.all vss.int.last   cav
1       FALSE    FALSE       FALSE   FALSE       FALSE        FALSE FALSE
2       FALSE    FALSE       FALSE   FALSE       FALSE        FALSE FALSE
  cav.int.last cav.int.all ctrough cstart   ptr  tlag deg.fluc swing  ceoi
1        FALSE       FALSE   FALSE  FALSE FALSE FALSE    FALSE FALSE FALSE
2        FALSE       FALSE   FALSE  FALSE FALSE FALSE    FALSE FALSE FALSE
  aucabove.predose.all aucabove.trough.all count_conc count_conc_measured
1                FALSE               FALSE      FALSE               FALSE
2                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 aumcivlast
1          FALSE         FALSE              FALSE             FALSE      FALSE
2          FALSE         FALSE              FALSE             FALSE      FALSE
  aumcivall aumcivint.last aumcivint.all half.life r.squared adj.r.squared
1     FALSE          FALSE         FALSE     FALSE     FALSE         FALSE
2     FALSE          FALSE         FALSE      TRUE     FALSE         FALSE
  lambda.z.corrxy lambda.z lambda.z.time.first lambda.z.time.last
1           FALSE    FALSE               FALSE              FALSE
2           FALSE    FALSE               FALSE              FALSE
  lambda.z.n.points clast.pred span.ratio tobit_residual adj_tobit_residual
1             FALSE      FALSE      FALSE          FALSE              FALSE
2             FALSE      FALSE      FALSE          FALSE              FALSE
  lambda.z.n.points_blq thalf.eff.last thalf.eff.iv.last kel.last kel.iv.last
1                 FALSE          FALSE             FALSE    FALSE       FALSE
2                 FALSE          FALSE             FALSE    FALSE       FALSE
  kel.all kel.int.all kel.int.last cl.iv.all cl.iv.last cl.ivint.all
1   FALSE       FALSE        FALSE     FALSE      FALSE        FALSE
2   FALSE       FALSE        FALSE     FALSE      FALSE        FALSE
  cl.ivint.last cl.sparse.last mrt.sparse.last mrt.iv.all mrt.ivint.all
1         FALSE          FALSE           FALSE      FALSE         FALSE
2         FALSE          FALSE           FALSE      FALSE         FALSE
  mrt.ivint.last vz.all vz.int.all vz.int.last vz.iv.all vz.iv.last
1          FALSE  FALSE      FALSE       FALSE     FALSE      FALSE
2          FALSE  FALSE      FALSE       FALSE     FALSE      FALSE
  vz.ivint.all vz.ivint.last vz.last vss.iv.all vss.ivint.all vss.ivint.last
1        FALSE         FALSE   FALSE      FALSE         FALSE          FALSE
2        FALSE         FALSE   FALSE      FALSE         FALSE          FALSE
  vss.sparse.last aucinf.obs aucinf.pred aumcinf.obs aumcinf.pred
1           FALSE      FALSE       FALSE       FALSE        FALSE
2           FALSE       TRUE       FALSE       FALSE        FALSE
  aucint.inf.obs aucint.inf.obs.dose aucint.inf.pred aucint.inf.pred.dose
1          FALSE               FALSE           FALSE                FALSE
2          FALSE               FALSE           FALSE                FALSE
  aumcint.inf.obs aumcint.inf.obs.dose aumcint.inf.pred aumcint.inf.pred.dose
1           FALSE                FALSE            FALSE                 FALSE
2           FALSE                FALSE            FALSE                 FALSE
  aucivinf.obs aucivinf.pred aucivpbextinf.obs aucivpbextinf.pred aumcivinf.obs
1        FALSE         FALSE             FALSE              FALSE         FALSE
2        FALSE         FALSE             FALSE              FALSE         FALSE
  aumcivinf.pred aucpext.obs aucpext.pred kel.iv.all kel.ivint.all
1          FALSE       FALSE        FALSE      FALSE         FALSE
2          FALSE       FALSE        FALSE      FALSE         FALSE
  kel.ivint.last kel.sparse.last cl.obs cl.pred cl.int.inf.obs cl.int.inf.pred
1          FALSE           FALSE  FALSE   FALSE          FALSE           FALSE
2          FALSE           FALSE  FALSE   FALSE          FALSE           FALSE
  cl.iv.obs cl.iv.pred mrt.obs mrt.pred mrt.int.inf.obs mrt.int.inf.pred
1     FALSE      FALSE   FALSE    FALSE           FALSE            FALSE
2     FALSE      FALSE   FALSE    FALSE           FALSE            FALSE
  mrt.iv.obs mrt.iv.pred mrt.md.obs mrt.md.pred vz.obs vz.pred vz.int.inf.obs
1      FALSE       FALSE      FALSE       FALSE  FALSE   FALSE          FALSE
2      FALSE       FALSE      FALSE       FALSE  FALSE   FALSE          FALSE
  vz.int.inf.pred vz.iv.obs vz.iv.pred vz.sparse.last vss.obs vss.pred
1           FALSE     FALSE      FALSE          FALSE   FALSE    FALSE
2           FALSE     FALSE      FALSE          FALSE   FALSE    FALSE
  vss.iv.obs vss.iv.pred vss.md.obs vss.md.pred vss.int.inf.obs
1      FALSE       FALSE      FALSE       FALSE           FALSE
2      FALSE       FALSE      FALSE       FALSE           FALSE
  vss.int.inf.pred cav.int.inf.obs cav.int.inf.pred thalf.eff.obs
1            FALSE           FALSE            FALSE         FALSE
2            FALSE           FALSE            FALSE         FALSE
  thalf.eff.pred thalf.eff.iv.obs thalf.eff.iv.pred kel.obs kel.pred kel.iv.obs
1          FALSE            FALSE             FALSE   FALSE    FALSE      FALSE
2          FALSE            FALSE             FALSE   FALSE    FALSE      FALSE
  kel.iv.pred kel.int.inf.obs kel.int.inf.pred auclast.dn aucall.dn
1       FALSE           FALSE            FALSE      FALSE     FALSE
2       FALSE           FALSE            FALSE      FALSE     FALSE
  aucinf.obs.dn aucinf.pred.dn aumclast.dn aumcall.dn aumcinf.obs.dn
1         FALSE          FALSE       FALSE      FALSE          FALSE
2         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.