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 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(...)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.