library(tidyverse)
library(PKNCA)
data(Theoph)
conc_df <- as_tibble(Theoph) %>%
transmute(
ID = Subject,
TIME = Time,
CONC = conc
)Inspecting NCA Algorithms with Standalone PKNCA Functions
Big idea: The pk.calc.* functions expose the algorithms behind NCA metrics. They are excellent for learning how calculations work, but full analyses should usually use the structured PKNCA workflow.
Learning Objectives
By the end of this lesson, you will be able to:
- Use standalone PKNCA functions to compute core NCA metrics.
- Understand how AUC and half-life calculations operate at the algorithm level.
- Compare standalone calculations with results from the
pk.nca()workflow. - Recognize when standalone functions are appropriate (education, debugging, algorithm inspection).
Key Ideas
PKNCAprovides standalone functions that compute individual PK metrics directly.- Examples include:
pk.calc.auc.last()pk.calc.auc.inf()pk.calc.half.life()pk.calc.cmax()pk.calc.tmax()
- These functions operate directly on vectors of concentration and time.
- They expose the core algorithms used internally by
PKNCA. - However, they do not manage profiles, intervals, or datasets, which is why full analyses typically rely on
pk.nca().
Example Setup
For demonstration, extract one subject profile:
subj1 <- conc_df %>%
filter(ID == 1) %>%
arrange(TIME)
time <- subj1$TIME
conc <- subj1$CONCExample 1: AUC to Last Observation
pk.calc.auc.last(conc, time)[1] 147.2347
This function integrates the concentration-time curve using trapezoidal rules up to the last observation.
Conceptually:
\[ AUC_{0-tlast} \]
is computed from the observed data only.
Example 2: Half-Life and Terminal Slope
hl <- pk.calc.half.life(conc, time)
hl lambda.z r.squared adj.r.squared lambda.z.time.first lambda.z.time.last
1 0.048457 0.9999997 0.9999995 9.05 24.37
lambda.z.n.points clast.pred half.life span.ratio tmax tlast
1 3 3.280146 14.30438 1.071001 1.12 24.37
Half-life is derived from the terminal slope:
\[ t_{1/2} = \frac{\ln(2)}{\lambda_z} \]
The function internally:
- Identifies terminal points
- Fits a log-linear regression
- Computes the slope and derived half-life
Example 3: AUC to Infinity
pk.calc.auc.inf() requires a terminal slope (lambda.z). A practical standalone workflow is to estimate lambda.z first with pk.calc.half.life() and then pass it into pk.calc.auc.inf().
pk.calc.auc.inf(
conc,
time,
lambda.z = hl$lambda.z
)[1] 214.9236
This reflects the dependency:
\[ AUC_{0-\infty} = AUC_{0-tlast} + \frac{C_{last}}{\lambda_z} \]
So AUCinf depends on:
- observed AUC
- estimated terminal slope
Example 4: Cmax and Tmax
pk.calc.cmax(conc)[1] 10.5
pk.calc.tmax(conc, time)[1] 1.12
These are simple functions:
Cmax= maximum observed concentrationTmax= time of that concentration
Comparing with the Structured Workflow
The same metrics can be computed through the standard PKNCA pipeline:
dose_df <- as_tibble(Theoph) %>%
distinct(Subject, Dose) %>%
transmute(ID = Subject, TIME = 0, DOSE = Dose)
conc_obj <- PKNCAconc(CONC ~ TIME | ID, data = conc_df)
dose_obj <- PKNCAdose(DOSE ~ TIME | ID, data = dose_df)
intervals_df <- data.frame(
start = 0,
end = Inf,
auclast = TRUE,
aucinf.obs = TRUE,
half.life = TRUE
)
nca_data <- PKNCAdata(conc_obj, dose_obj, intervals = intervals_df)
results <- pk.nca(nca_data)
as.data.frame(results) %>%
filter(ID == 1) # A tibble: 14 × 6
ID start end PPTESTCD PPORRES exclude
<ord> <dbl> <dbl> <chr> <dbl> <chr>
1 1 0 Inf auclast 147. <NA>
2 1 0 Inf tmax 1.12 <NA>
3 1 0 Inf tlast 24.4 <NA>
4 1 0 Inf clast.obs 3.28 <NA>
5 1 0 Inf lambda.z 0.0485 <NA>
6 1 0 Inf r.squared 1.000 <NA>
7 1 0 Inf adj.r.squared 1.000 <NA>
8 1 0 Inf lambda.z.time.first 9.05 <NA>
9 1 0 Inf lambda.z.time.last 24.4 <NA>
10 1 0 Inf lambda.z.n.points 3 <NA>
11 1 0 Inf clast.pred 3.28 <NA>
12 1 0 Inf half.life 14.3 <NA>
13 1 0 Inf span.ratio 1.07 <NA>
14 1 0 Inf aucinf.obs 215. <NA>
The structured workflow:
- manages multiple profiles
- applies interval definitions
- maintains reproducibility
- generates diagnostic outputs
Strategies
- Use standalone functions to inspect algorithms and debug results.
- Confirm how metrics behave when data change (e.g., BLQ handling).
- Estimate
lambda.zbefore callingpk.calc.auc.inf(). - Use the structured
PKNCAworkflow for full dataset analyses. - Treat standalone functions as tools for understanding calculations, not replacing workflows.
Common Mistakes
- Using standalone functions on multiple profiles without looping.
- Forgetting to sort timepoints before calculations.
- Calling
pk.calc.auc.inf()withoutlambda.z. - Comparing standalone outputs with structured outputs without matching assumptions.
- Treating algorithm inspection tools as production analysis tools.
Practice Problems
Executable
- Extract subject 2 and compute
AUC0-tlastusingpk.calc.auc.last(). - Compute half-life for subject 3 using
pk.calc.half.life(). - Compute
AUCinffor subject 1 by first estimatinglambda.z. - Compare
Cmaxfrompk.calc.cmax()with the value returned bypk.nca().
Conceptual
- Why do standalone functions require sorted timepoints?
- Why is
pk.nca()preferred for multi-subject analyses?
1. Subject 2 AUC:
subj2 <- conc_df %>%
filter(ID == 2) %>%
arrange(TIME)
pk.calc.auc.last(subj2$CONC, subj2$TIME)[1] 88.73128
2. Subject 3 half-life:
subj3 <- conc_df %>%
filter(ID == 3) %>%
arrange(TIME)
pk.calc.half.life(subj3$CONC, subj3$TIME) lambda.z r.squared adj.r.squared lambda.z.time.first lambda.z.time.last
1 0.1024443 0.999325 0.9986499 9 24.17
lambda.z.n.points clast.pred half.life span.ratio tmax tlast
1 3 1.055097 6.766087 2.242064 1.02 24.17
3. Subject 1 AUCinf:
hl1 <- pk.calc.half.life(conc, time)
pk.calc.auc.inf(
conc,
time,
lambda.z = hl1$lambda.z
)[1] 214.9236
4. Compare Cmax:
pk.calc.cmax(conc)[1] 10.5
Then inspect the cmax output from pk.nca().
5. Conceptual:
Integration requires ordered timepoints. If time is not sorted, trapezoidal integration produces incorrect results.
6. Conceptual:
pk.nca() handles profiles, intervals, grouping, and diagnostics automatically, making analyses reproducible and scalable.
Summary
Standalone PKNCA functions reveal the algorithms behind NCA metrics.
pk.calc.*functions compute individual PK metrics.- They operate on vectors of concentration and time.
pk.calc.auc.inf()requires a supplied terminal slope.- They are ideal for learning, debugging, and algorithm inspection.
- Full analyses should usually rely on the structured
PKNCAworkflow.
- Use
pk.calc.*functions to understand how NCA metrics are computed. - Always sort timepoints before applying standalone calculations.
- Estimate
lambda.zbefore callingpk.calc.auc.inf(). - Use
pk.nca()for production analyses. - Treat standalone functions as algorithm inspection tools.