17  Writing Custom Parameter Functions

17.1 The parameter registry

Every NCA parameter that PKNCA can compute is registered in an internal registry via add.interval.col(). When you request auclast = TRUE in an interval, PKNCA looks up the registered function for auclast and calls it with the right arguments.

You can extend the registry with your own parameters — new functions that compute anything derivable from concentrations, times, doses, or other already-computed parameters.

# 123 parameters registered by default
length(get.interval.cols())
[1] 203

17.2 Anatomy of add.interval.col()

add.interval.col(
  name        = "my.param",         # column name used in intervals data frame
  FUN         = "pk.calc.my.param", # character string: function name
  values      = c(FALSE, TRUE),     # allowed values in intervals (FALSE = skip, TRUE = compute)
  unit_type   = "time",             # units category (see below)
  pretty_name = "My parameter",     # human-readable label
  desc        = "What it does",     # one-line description
  depends     = NULL,               # other parameters that must be computed first
  sparse      = FALSE,              # TRUE if this is a sparse-PK parameter
  formalsmap  = list(),             # how to map registered arg names to your function args
  datatype    = "interval"          # "interval" (default), "individual", or "population"
)

Key rule: FUN must be a character string (the function name), not a function object.

17.2.1 Available unit_type values

"time", "conc", "auc", "aumc", "clearance", "volume", "fraction", "dose", "amount", "%", "count", "unitless", "inverse_time", "renal_clearance", "auc_dosenorm", "conc_dosenorm", "aumc_dosenorm", "amount_dose"


17.3 Example 1: derived from other parameters

A parameter that divides two already-computed parameters. Use depends to declare what must be computed first, and formalsmap to map PKNCA’s internal argument names to your function’s parameter names.

# Exposure ratio: AUClast / Cmax  (units: time, since AUC/conc = time × conc/conc = time)
pk.calc.exposure.ratio <- function(auclast, cmax) {
  auclast / cmax
}

add.interval.col(
  name        = "exposure.ratio",
  FUN         = "pk.calc.exposure.ratio",
  values      = c(FALSE, TRUE),
  unit_type   = "time",
  pretty_name = "Exposure ratio (AUClast / Cmax)",
  desc        = "AUClast divided by Cmax",
  depends     = c("auclast", "cmax"),
  formalsmap  = list(auclast = "auclast", cmax = "cmax")
)
o_nca_er <- pk.nca(PKNCAdata(o_conc, o_dose, intervals = data.frame(
  start          = 0, end = Inf,
  auclast        = TRUE,
  cmax           = TRUE,
  exposure.ratio = TRUE
)))

as.data.frame(o_nca_er) |>
  filter(PPTESTCD %in% c("auclast", "cmax", "exposure.ratio")) |>
  select(subject, PPTESTCD, PPORRES) |>
  tidyr::pivot_wider(names_from = PPTESTCD, values_from = PPORRES) |>
  mutate(check = auclast / cmax) |>
  arrange(subject)
# A tibble: 12 × 5
   subject auclast  cmax exposure.ratio check
   <ord>     <dbl> <dbl>          <dbl> <dbl>
 1 6          71.7  6.44          11.1  11.1 
 2 7          88.0  7.09          12.4  12.4 
 3 8          86.8  7.56          11.5  11.5 
 4 11         77.9  8              9.74  9.74
 5 3          95.9  8.2           11.7  11.7 
 6 2          88.7  8.33          10.7  10.7 
 7 4         103.   8.6           11.9  11.9 
 8 9          83.9  9.03           9.30  9.30
 9 12        115.   9.75          11.8  11.8 
10 10        136.  10.2           13.3  13.3 
11 1         147.  10.5           14.0  14.0 
12 5         118.  11.4           10.4  10.4 

17.4 Example 2: computed from raw concentration-time data

A parameter that accesses conc and time directly. PKNCA passes the raw vectors from the concentration data. No depends needed — just use conc and time as argument names and map them in formalsmap.

# Concentration at exactly 2 hours (interpolated)
pk.calc.c_at_2h <- function(conc, time) {
  interp.extrap.conc(conc = conc, time = time, time.out = 2)
}

add.interval.col(
  name        = "c.2h",
  FUN         = "pk.calc.c_at_2h",
  values      = c(FALSE, TRUE),
  unit_type   = "conc",
  pretty_name = "Concentration at 2 h",
  desc        = "Interpolated concentration at t = 2 h",
  depends     = NULL,
  formalsmap  = list(conc = "conc", time = "time")
)
o_nca_c2 <- pk.nca(PKNCAdata(o_conc, o_dose, intervals = data.frame(
  start = 0, end = Inf, c.2h = TRUE
)))

as.data.frame(o_nca_c2) |>
  filter(PPTESTCD == "c.2h") |>
  select(subject, PPORRES) |>
  arrange(subject)
# A tibble: 12 × 2
   subject PPORRES
   <ord>     <dbl>
 1 6          6.32
 2 7          6.55
 3 8          7.56
 4 11         6.80
 5 3          7.81
 6 2          8.25
 7 4          8.41
 8 9          6.35
 9 12         9.72
10 10         7.76
11 1          9.68
12 5          9.37

17.6 The formalsmap argument

formalsmap maps PKNCA’s standard argument names to your function’s parameter names. PKNCA always passes data using its own internal names; formalsmap tells it which of those names maps to which argument in your function.

Standard PKNCA argument names available via formalsmap:

PKNCA name What it provides
conc Concentration vector for the current interval
time Time vector for the current interval
dose Total dose in the current interval
start Interval start time
end Interval end time
half.life Computed half-life (if in depends)
auclast Computed AUClast (if in depends)
cmax Computed Cmax (if in depends)
(any parameter name) Any other registered parameter in depends

If your function argument names exactly match PKNCA’s standard names, formalsmap can be omitted or left empty.


17.7 Inspecting registered parameters

# Confirm your parameter is registered
cols <- get.interval.cols()
cat("exposure.ratio registered:", "exposure.ratio" %in% names(cols), "\n")
exposure.ratio registered: TRUE 
cat("c.2h registered:          ", "c.2h" %in% names(cols), "\n")
c.2h registered:           TRUE 
# Inspect registration details
str(cols[["exposure.ratio"]])
List of 9
 $ FUN        : chr "pk.calc.exposure.ratio"
 $ values     : logi [1:2] FALSE TRUE
 $ unit_type  : chr "time"
 $ pretty_name: chr "Exposure ratio (AUClast / Cmax)"
 $ desc       : chr "AUClast divided by Cmax"
 $ sparse     : logi FALSE
 $ formalsmap :List of 2
  ..$ auclast: chr "auclast"
  ..$ cmax   : chr "cmax"
 $ depends    : chr [1:2] "auclast" "cmax"
 $ datatype   : chr "interval"

17.8 Custom summary statistics for your parameter

After registering the parameter, set a custom summary rule with PKNCA.set.summary():

PKNCA.set.summary(
  "exposure.ratio",
  description = "geometric mean [CV%]",
  point  = PKNCA:::geomean,
  spread = PKNCA:::geocv
)

summary(pk.nca(PKNCAdata(o_conc, o_dose, intervals = data.frame(
  start = 0, end = Inf,
  auclast = TRUE, cmax = TRUE, exposure.ratio = TRUE
))))
 start end  N     auclast        cmax exposure.ratio
     0 Inf 12 98.7 [22.5] 8.65 [17.0]    11.4 [12.0]

Caption: auclast, cmax: geometric mean and geometric coefficient of variation; exposure.ratio: geometric mean [CV%]; N: number of subjects

17.9 Important notes

  • Registration is session-scoped. Custom parameters registered with add.interval.col() persist for the life of your R session. Re-run registration code in each session (e.g., in your analysis script or package).
  • FUN must be a character string. Passing a function object causes an error.
  • depends controls evaluation order. List all parameters your function reads. PKNCA resolves the dependency graph and ensures they are computed before your function is called.
  • Functions must be in scope when pk.nca() runs. Define them in the global environment or in a package loaded before calling pk.nca().