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 Architecture Overview
22.1 Class hierarchy
PKNCA uses four S3 classes that form a strict pipeline. Each wraps the previous one:
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(...)toPKNCAdata()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 = TRUEinPKNCAconc() - 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 = TRUEin theiradd.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.