library(tidyverse)
library(PKNCA)
data(Theoph)
theoph_conc <- as_tibble(Theoph) %>%
transmute(ID = Subject, TIME = Time, CONC = conc)
theoph_dose <- as_tibble(Theoph) %>%
distinct(Subject, Dose) %>%
transmute(ID = Subject, TIME = 0, DOSE = Dose)
conc_obj <- PKNCAconc(CONC ~ TIME | ID, data = theoph_conc)
dose_obj <- PKNCAdose(DOSE ~ TIME | ID, data = theoph_dose)
# Be explicit about outputs to reduce version-to-version surprises
intervals_df <- data.frame(
start = 0,
end = Inf,
cmax = TRUE,
auclast = TRUE,
aucinf.obs = TRUE,
half.life = TRUE
)
nca_data <- PKNCAdata(conc_obj, dose_obj, intervals = intervals_df)
results_obj <- pk.nca(nca_data)
raw_df <- as.data.frame(results_obj)Terminal Phase Diagnostics and Half-Life Reliability
Big idea: Half-life is only as good as the terminal phase used to estimate it. Poor terminal slope selection leads to unreliable \(t_{1/2}\) and \(AUC_{0-\infty}\).
Learning Objectives
By the end of this lesson, you will be able to:
- Explain how NCA estimates the terminal slope (\(\lambda_z\)).
- Use simple diagnostics to judge whether half-life is trustworthy.
- Compute and interpret the extrapolated AUC fraction.
- Extract terminal-phase parameters from a
PKNCAresultsobject.
Key Ideas
- \(\lambda_z\) is estimated from the log-linear terminal portion of the profile.
- \[t_{1/2} = \frac{\ln(2)}{\lambda_z}\]
- If terminal sampling is weak, \(\lambda_z\) becomes unstable → half-life becomes unstable.
- \(AUC_{0-\infty}\) depends on extrapolation from the terminal slope; if \(\lambda_z\) is unstable, extrapolated exposure becomes unreliable.
- Always check diagnostics before reporting half-life or \(AUC_{0-\infty}\).
Setup: Run NCA on Theoph
Step 1: See what parameters you actually got
Different PKNCA versions/settings can label parameters slightly differently. Before you hard-code names, quickly inspect what parameters are available:
sort(unique(raw_df$PPTESTCD)) [1] "adj.r.squared" "aucinf.obs" "auclast"
[4] "clast.obs" "clast.pred" "cmax"
[7] "half.life" "lambda.z" "lambda.z.n.points"
[10] "lambda.z.time.first" "lambda.z.time.last" "r.squared"
[13] "span.ratio" "tlast" "tmax"
Keep this in your pocket whenever a parameter “is not found”.
Step 3: Extrapolated AUC fraction
We want:
\[ \text{Extrapolated Fraction} = \frac{AUC_{0-\infty} - AUC_{0-tlast}}{AUC_{0-\infty}} \]
Build a small AUC table
auc_wide <- raw_df %>%
filter(PPTESTCD %in% c("aucinf.obs", "auclast")) %>%
select(ID, PPTESTCD, PPORRES) %>%
pivot_wider(names_from = PPTESTCD, values_from = PPORRES) %>%
mutate(extrap_fraction = (aucinf.obs - auclast) / aucinf.obs)
auc_wide %>% head()# A tibble: 6 × 4
ID auclast aucinf.obs extrap_fraction
<ord> <dbl> <dbl> <dbl>
1 6 71.7 82.2 0.128
2 7 88.0 101. 0.129
3 8 86.8 102. 0.150
4 11 77.9 86.9 0.104
5 3 95.9 106. 0.0966
6 2 88.7 97.4 0.0888
Interpretation: Extrapolated fraction reflects how much of the total exposure estimate depends on extrapolation beyond the last measured sample.
A common rule of thumb is to treat extrapolated fractions above ~20% as a caution flag (context-dependent).
Step 4: Terminal-fit quality
In this PKNCA output, terminal-fit diagnostics appear as:
r.squaredadj.r.squaredlambda.z.n.pointslambda.z.time.firstlambda.z.time.last
We can assemble them into a subject-level diagnostic table:
lambda_diag <- raw_df %>%
filter(PPTESTCD %in% c(
"lambda.z",
"r.squared",
"adj.r.squared",
"lambda.z.n.points",
"lambda.z.time.first",
"lambda.z.time.last",
"half.life"
)) %>%
select(ID, PPTESTCD, PPORRES) %>%
pivot_wider(names_from = PPTESTCD, values_from = PPORRES)
lambda_diag %>%
select(
ID, lambda.z, r.squared, adj.r.squared,
lambda.z.n.points, half.life
) %>%
head(12)# A tibble: 12 × 6
ID lambda.z r.squared adj.r.squared lambda.z.n.points half.life
<ord> <dbl> <dbl> <dbl> <dbl> <dbl>
1 6 0.0878 0.998 0.998 7 7.89
2 7 0.0883 0.999 0.998 4 7.85
3 8 0.0815 0.991 0.989 6 8.51
4 11 0.0955 1.000 1.000 3 7.26
5 3 0.102 0.999 0.999 3 6.77
6 2 0.104 0.997 0.996 4 6.66
7 4 0.0993 0.999 0.998 3 6.98
8 9 0.0825 0.999 0.999 3 8.41
9 12 0.110 0.999 0.999 3 6.29
10 10 0.0750 1.000 0.999 3 9.25
11 1 0.0485 1.000 1.000 3 14.3
12 5 0.0866 0.999 0.998 4 8.00
These diagnostics help answer practical questions:
- Was the terminal regression well fit?
- How many points defined the terminal phase?
- Which portion of the curve was used?
When Half-Life Should Not Be Trusted
- Too few terminal points
- Large extrapolated AUC fraction
- No clear log-linear decline
- Terminal samples too sparse or too noisy
NCA does not “fix” poor sampling design.
Strategies
- Always compute and inspect extrapolated fraction.
- Check terminal-fit diagnostics such as \(R^2\) and number of points.
- Treat half-life as a data-quality-dependent summary.
- Report context: sampling window, terminal points, extrapolation fraction.
Common Mistakes
- Reporting \(AUC_{0-\infty}\) when extrapolated fraction is large.
- Ignoring terminal fit quality.
- Assuming automated selection is always correct.
- Treating half-life as more precise than sampling allows.
Practice Problems
- Executable: List subjects with extrapolated fraction > 0.2.
- Executable: Identify the top 5 largest half-life values.
- Conceptual: Why can sparse terminal sampling inflate half-life?
1. Extrapolated fraction > 0.2:
auc_wide %>%
filter(!is.na(extrap_fraction), extrap_fraction > 0.2)# A tibble: 1 × 4
ID auclast aucinf.obs extrap_fraction
<ord> <dbl> <dbl> <dbl>
1 1 147. 215. 0.315
2. Largest half-life values:
raw_df %>%
filter(PPTESTCD == "half.life") %>%
arrange(desc(PPORRES)) %>%
slice_head(n = 5)# A tibble: 5 × 6
ID start end PPTESTCD PPORRES exclude
<ord> <dbl> <dbl> <chr> <dbl> <chr>
1 1 0 Inf half.life 14.3 <NA>
2 10 0 Inf half.life 9.25 <NA>
3 8 0 Inf half.life 8.51 <NA>
4 9 0 Inf half.life 8.41 <NA>
5 5 0 Inf half.life 8.00 <NA>
3. Conceptual reason:
Sparse terminal points make the log-linear regression unstable. Small changes in which points are included can cause large swings in \(\lambda_z\), which inflates or deflates half-life.
Summary
Half-life reliability depends on:
- Adequate terminal sampling
- Reasonable extrapolated AUC fraction
- Acceptable terminal-fit diagnostics
Never report \(t_{1/2}\) or \(AUC_{0-\infty}\) without reviewing diagnostics.
- Start by checking
unique(PPTESTCD)when something “is not found”. - Extrapolated fraction is often your most practical reliability flag.
- Half-life is not a constant — it is an estimate that depends on data quality.
- Diagnostics first, reporting second.