d_conc <- as.data.frame(Theoph) |> rename(time = Time, subject = Subject)
d_dose <- Theoph |> as.data.frame() |>
group_by(Subject) |>
summarise(dose = Dose[1] * Wt[1], weight = Wt[1], .groups = "drop") |>
rename(subject = Subject) |>
mutate(time = 0)
o_conc <- PKNCAconc(d_conc, conc ~ time | subject)
o_dose <- PKNCAdose(d_dose, dose ~ time | subject, route = "extravascular")
o_data <- PKNCAdata(o_conc, o_dose, intervals = data.frame(
start = 0, end = Inf,
auclast = TRUE, aucinf.obs = TRUE, cmax = TRUE,
half.life = TRUE, span.ratio = TRUE, adj.r.squared = TRUE,
aucpext.obs = TRUE
))
o_nca <- pk.nca(o_data)18 Post-Processing Results
18.1 Why post-process?
After pk.nca() you may need to:
- Extract results as a tidy data frame for further analysis
- Exclude specific results that failed QC (poor half-life fit, protocol deviation)
- Normalize parameters by dose (built-in
.dnvariants) - Summarize with custom statistics, respecting exclusions
- Report with the
business.*summary helper functions - Inspect which concentrations each λz regression used, and the fitted line
Data-quality checks that run before or alongside exclusion (missing samples, Tmax coverage, input validation) are covered in the Data Quality Checks chapter.
Most of this operates on the PKNCAresults object without rerunning the analysis (requesting .dn parameters is the exception — they are computed by pk.nca()). The package’s own Post-Processing vignette walks the same workflow.
18.2 Example data and analysis
Every section below post-processes the same PKNCAresults object, built from the Theoph dataset. The interval requests the headline parameters plus the regression- and extrapolation-quality metrics that the exclusion demos rely on (span.ratio, adj.r.squared, aucpext.obs):
18.3 Extracting results
as.data.frame() returns the full tidy result including excluded rows:
df <- as.data.frame(o_nca)
# Columns:
# subject — group identifier
# start/end — interval boundaries
# PPTESTCD — parameter code
# PPORRES — numeric result
# exclude — NA if valid; character reason if excluded
df |>
filter(PPTESTCD == "auclast") |>
select(subject, PPORRES, exclude) |>
arrange(subject)# A tibble: 12 × 3
subject PPORRES exclude
<ord> <dbl> <chr>
1 6 71.7 <NA>
2 7 88.0 <NA>
3 8 86.8 <NA>
4 11 77.9 <NA>
5 3 95.9 <NA>
6 2 88.7 <NA>
7 4 103. <NA>
8 9 83.9 <NA>
9 12 115. <NA>
10 10 136. <NA>
11 1 147. <NA>
12 5 118. <NA>
18.4 Excluding results
18.4.1 Manual exclusion with exclude()
exclude() takes your results object plus a reason string and either a logical mask or a rule FUN. A manual mask excludes exactly the rows you flag — it does not propagate to other parameters. Rule-based exclusion (next section) does propagate: each rule excludes its affected parameters and the parameters that depend on them.
# Exclude subject 3's half-life manually (e.g. failed QC review)
o_nca_excl <- exclude(
o_nca,
reason = "half-life failed manual QC",
mask = as.data.frame(o_nca)$subject == "3" &
as.data.frame(o_nca)$PPTESTCD == "half.life"
)
# Only the masked half.life row is excluded
as.data.frame(o_nca_excl) |>
filter(subject == "3", !is.na(exclude)) |>
select(subject, PPTESTCD, PPORRES, exclude)# A tibble: 1 × 4
subject PPTESTCD PPORRES exclude
<ord> <chr> <dbl> <chr>
1 3 half.life 6.77 half-life failed manual QC
The function given as FUN (next section) is called once per group of results and must return either one value or one value per result row of the group; anything else is the pknca_error_exclude_fun_length error (≥ 0.13.0). A warning that the function signals reaches the caller with its class and fields intact (≥ 0.13.0), so it can be caught with withCallingHandlers().
18.4.2 Rule-based exclusion functions
PKNCA provides ready-made rule functions to pass to exclude(FUN=...). Unlike a manual mask, rule-based exclusion propagates: each rule excludes the parameters it affects (for the half-life rules, the whole λz family) and the parameters that depend on them, such as aucinf.obs and aucpext.obs. The built-in rules also supply their own reason text, which overrides the reason argument in the exclude column.
| Function | Excludes when… |
|---|---|
exclude_nca_span.ratio(min.span.ratio) |
span.ratio < min.span.ratio (default from PKNCA.options("min.span.ratio")) |
exclude_nca_min.hl.r.squared(min.hl.r.squared) |
half-life unadjusted R² < min.hl.r.squared (default from PKNCA.options("min.hl.r.squared")) |
exclude_nca_min.hl.adj.r.squared(min.hl.adj.r.squared = 0.9) |
half-life adjusted R² < min.hl.adj.r.squared (stricter: adjusted for the number of points) |
exclude_nca_max.aucinf.pext(max.aucinf.pext) |
%AUCextrap > max.aucinf.pext (default from PKNCA.options("max.aucinf.pext")) |
exclude_nca_tmax_0() |
Tmax at or before 0 (tmax <= 0); excludes every parameter of the interval. Use this, not exclude_nca_tmax_early(0), to catch a Tmax of exactly 0 |
exclude_nca_tmax_early(tmax_early = 0) |
Tmax strictly before tmax_early (tmax < tmax_early); excludes every parameter of the interval. With the default 0, only a negative Tmax is excluded |
exclude_nca_count_conc_measured(min_count, exclude_param_pattern = ...) |
fewer than min_count measured concentrations; excludes the AUC-family parameters matching exclude_param_pattern |
exclude_nca_by_param(parameter, min_thr = NULL, max_thr = NULL, affected_parameters = parameter) (≥ 0.12.1) |
the value of parameter falls outside [min_thr, max_thr]; excludes affected_parameters (default: parameter itself) |
exclude_nca_tmax_coverage(min_subjects = 4, k_warn = 1.5, k_exclude = 3) (≥ 0.13.0) |
the subject has no sample within Tukey’s outer fences around its group’s Tmax values; excludes every result of the interval (subjects outside only the inner fences get a warning) |
pknca_exclude_rules() (≥ 0.13.0) lists these rules with their arguments, defaults, the options they use, and the parameters each can exclude. The Tmax-coverage rule and the rule listing are demonstrated in the Data Quality Checks chapter.
# Exclude half-lives where span ratio < 2
o_nca_span <- exclude(
o_nca,
reason = "span.ratio < 2",
FUN = exclude_nca_span.ratio(2)
)
as.data.frame(o_nca_span) |>
filter(PPTESTCD == "half.life") |>
select(subject, PPORRES, exclude) |>
arrange(subject)# A tibble: 12 × 3
subject PPORRES exclude
<ord> <dbl> <chr>
1 6 7.89 <NA>
2 7 7.85 <NA>
3 8 8.51 <NA>
4 11 7.26 <NA>
5 3 6.77 <NA>
6 2 6.66 <NA>
7 4 6.98 <NA>
8 9 8.41 span.ratio < 2
9 12 6.29 <NA>
10 10 9.25 span.ratio < 2
11 1 14.3 span.ratio < 2
12 5 8.00 <NA>
# Propagation: subject 9 fails the span-ratio rule, so the entire lambda.z
# family and the parameters that depend on it are excluded together
as.data.frame(o_nca_span) |>
filter(subject == "9", !is.na(exclude)) |>
select(subject, PPTESTCD, exclude)# A tibble: 12 × 3
subject PPTESTCD exclude
<ord> <chr> <chr>
1 9 lambda.z span.ratio < 2
2 9 r.squared span.ratio < 2
3 9 adj.r.squared span.ratio < 2
4 9 lambda.z.corrxy span.ratio < 2
5 9 lambda.z.time.first span.ratio < 2
6 9 lambda.z.time.last span.ratio < 2
7 9 lambda.z.n.points span.ratio < 2
8 9 clast.pred span.ratio < 2
9 9 half.life span.ratio < 2
10 9 span.ratio span.ratio < 2
11 9 aucinf.obs span.ratio < 2
12 9 aucpext.obs span.ratio < 2
# Chain multiple exclusion rules
o_nca_multi <- o_nca |>
exclude(reason = "span.ratio < 2", FUN = exclude_nca_span.ratio(2)) |>
exclude(reason = "%AUCextrap > 20%", FUN = exclude_nca_max.aucinf.pext(20))
as.data.frame(o_nca_multi) |>
filter(!is.na(exclude)) |>
select(subject, PPTESTCD, exclude) |>
arrange(subject, PPTESTCD)# A tibble: 36 × 3
subject PPTESTCD exclude
<ord> <chr> <chr>
1 9 adj.r.squared span.ratio < 2
2 9 aucinf.obs span.ratio < 2
3 9 aucpext.obs span.ratio < 2
4 9 clast.pred span.ratio < 2
5 9 half.life span.ratio < 2
6 9 lambda.z span.ratio < 2
7 9 lambda.z.corrxy span.ratio < 2
8 9 lambda.z.n.points span.ratio < 2
9 9 lambda.z.time.first span.ratio < 2
10 9 lambda.z.time.last span.ratio < 2
# ℹ 26 more rows
18.4.3 Exclusions are respected in summary
Excluded values are dropped from the summary statistics: the parameters affected by the span-ratio rule are summarized over fewer subjects, and the caption defines the resulting n= annotation as the number of measurements included in the summary.
summary(o_nca_span) start end N auclast cmax half.life adj.r.squared
0 Inf 12 98.7 [22.5] 8.65 [17.0] 7.36 [0.742], n=9 0.997 [0.00331], n=9
span.ratio aucinf.obs aucpext.obs
2.34 [9.28], n=9 105 [16.4], n=9 11.3 [2.28], n=9
Caption: auclast, cmax, span.ratio, aucinf.obs: geometric mean and geometric coefficient of variation; half.life, adj.r.squared, aucpext.obs: arithmetic mean and standard deviation; N: number of subjects; n: number of measurements included in summary
18.5 Dose-normalized parameters
PKNCA has built-in dose-normalized variants for several parameters. Request them in the interval — they are automatically computed as parameter / dose.
All available dose-normalized parameters, retrieved dynamically from the interval-column registry (get.interval.cols()):
cols <- get.interval.cols()
dn_params <- names(cols)[endsWith(names(cols), ".dn")]
data.frame(
parameter = dn_params,
description = sapply(cols[dn_params], function(x) x$desc),
normalized_from = sub("\\.dn$", "", dn_params)
) |> knitr::kable()| parameter | description | normalized_from | |
|---|---|---|---|
| auclast.dn | auclast.dn | Dose-normalized auclast | auclast |
| aucall.dn | aucall.dn | Dose-normalized aucall | aucall |
| aucinf.obs.dn | aucinf.obs.dn | Dose-normalized aucinf.obs | aucinf.obs |
| aucinf.pred.dn | aucinf.pred.dn | Dose-normalized aucinf.pred | aucinf.pred |
| aumclast.dn | aumclast.dn | Dose-normalized aumclast | aumclast |
| aumcall.dn | aumcall.dn | Dose-normalized aumcall | aumcall |
| aumcinf.obs.dn | aumcinf.obs.dn | Dose-normalized aumcinf.obs | aumcinf.obs |
| aumcinf.pred.dn | aumcinf.pred.dn | Dose-normalized aumcinf.pred | aumcinf.pred |
| cmax.dn | cmax.dn | Dose-normalized cmax | cmax |
| cmin.dn | cmin.dn | Dose-normalized cmin | cmin |
| clast.obs.dn | clast.obs.dn | Dose-normalized clast.obs | clast.obs |
| clast.pred.dn | clast.pred.dn | Dose-normalized clast.pred | clast.pred |
| cav.dn | cav.dn | Dose-normalized cav | cav |
| ctrough.dn | ctrough.dn | Dose-normalized ctrough | ctrough |
| clr.last.dn | clr.last.dn | Dose-normalized clr.last | clr.last |
| clr.obs.dn | clr.obs.dn | Dose-normalized clr.obs | clr.obs |
| clr.pred.dn | clr.pred.dn | Dose-normalized clr.pred | clr.pred |
Each .dn parameter is computed as parameter / dose where dose is the dose from the PKNCAdose object for that interval. In PKNCA ≥ 0.13.0 this includes renal clearance variants (clr.last.dn, clr.obs.dn, clr.pred.dn).
Request the .dn variants in the interval like any other parameter:
dn_interval <- data.frame(
start = 0,
end = Inf,
auclast = TRUE,
auclast.dn = TRUE, # AUClast / dose
aucinf.obs = TRUE,
aucinf.obs.dn = TRUE, # AUCinf.obs / dose
aucinf.pred.dn = TRUE, # AUCinf.pred / dose
cmax = TRUE,
cmax.dn = TRUE, # Cmax / dose
cmin.dn = TRUE, # Cmin / dose
aumclast.dn = TRUE, # AUMClast / dose
clast.obs.dn = TRUE, # Clast.obs / dose
cav.dn = TRUE, # Cav / dose
half.life = TRUE # needed for aucinf.pred
)
o_nca_dn <- pk.nca(PKNCAdata(o_conc, o_dose, intervals = dn_interval))
as.data.frame(o_nca_dn) |>
filter(PPTESTCD %in% c("auclast", "auclast.dn", "cmax", "cmax.dn",
"aucinf.obs", "aucinf.obs.dn")) |>
select(subject, PPTESTCD, PPORRES) |>
tidyr::pivot_wider(names_from = PPTESTCD, values_from = PPORRES) |>
arrange(subject)# A tibble: 12 × 7
subject auclast cmax aucinf.obs auclast.dn aucinf.obs.dn cmax.dn
<ord> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
1 6 71.7 6.44 82.2 0.224 0.257 0.0201
2 7 88.0 7.09 101. 0.275 0.316 0.0222
3 8 86.8 7.56 102. 0.272 0.320 0.0237
4 11 77.9 8 86.9 0.244 0.272 0.0250
5 3 95.9 8.2 106. 0.300 0.332 0.0257
6 2 88.7 8.33 97.4 0.279 0.306 0.0261
7 4 103. 8.6 114. 0.321 0.357 0.0269
8 9 83.9 9.03 97.5 0.313 0.364 0.0337
9 12 115. 9.75 126. 0.359 0.392 0.0304
10 10 136. 10.2 168. 0.424 0.524 0.0319
11 1 147. 10.5 215. 0.460 0.672 0.0328
12 5 118. 11.4 136. 0.369 0.426 0.0356
18.5.1 Custom normalization with normalize() (≥ 0.13.0)
For normalization by columns other than dose (e.g. body weight, body surface area), PKNCA 0.13.0 adds normalize(). You supply a norm_table data frame containing the grouping columns, a normalization value, and a unit for each group. With grouped data, every group of the results must be in norm_table, so no result rows are dropped silently; with ungrouped data, norm_table must have exactly one row.
# Normalize AUClast and Cmax by body weight
norm_tbl <- as.data.frame(Theoph) |>
group_by(Subject) |>
summarise(normalization = Wt[1], unit = "kg", .groups = "drop") |>
rename(subject = Subject)
df_results <- as.data.frame(o_nca)
df_bw_norm <- normalize(
df_results,
norm_table = norm_tbl,
parameters = c("auclast", "cmax"),
suffix = ".bw"
)
df_bw_norm |>
select(subject, PPTESTCD, PPORRES) |>
arrange(subject, PPTESTCD) |>
head(8)# A tibble: 8 × 3
subject PPTESTCD PPORRES
<ord> <chr> <dbl>
1 6 auclast.bw 0.896
2 6 cmax.bw 0.0805
3 7 auclast.bw 1.36
4 7 cmax.bw 0.110
5 8 auclast.bw 1.23
6 8 cmax.bw 0.107
7 11 auclast.bw 1.20
8 11 cmax.bw 0.123
normalize()returns only the normalized rows, under new parameter names —auclastbecomesauclast.bw,cmaxbecomescmax.bw. To keep both original and normalized values, bind the normalized rows onto the original results:bind_rows(df_results, df_bw_norm).
normalize() also accepts the PKNCAresults object itself. It then adds the normalized rows to the results and returns a PKNCAresults object holding both the original and the normalized parameters (marked as modified; see the provenance note in the Regulatory chapter).
18.5.2 Normalizing by a column of the concentration data (≥ 0.13.0)
When the normalizing value is already a column of the concentration data, normalize_by_col() builds the normalization table from it. It takes a PKNCAresults object, the column name, and the unit (a fixed string or the name of a unit column), and returns a PKNCAresults object with the normalized parameters added. The Theoph concentration data carry body weight in Wt:
o_nca_bw <- normalize_by_col(
o_nca,
col = "Wt",
unit = "kg",
parameters = c("auclast", "cmax"),
suffix = ".bw"
)
as.data.frame(o_nca_bw) |>
filter(subject %in% c("1", "2"), PPTESTCD %in% c("auclast", "auclast.bw")) |>
select(subject, PPTESTCD, PPORRES) |>
arrange(subject, PPTESTCD)# A tibble: 4 × 3
subject PPTESTCD PPORRES
<ord> <chr> <dbl>
1 2 auclast 88.7
2 2 auclast.bw 1.23
3 1 auclast 147.
4 1 auclast.bw 1.85
Subject 1 weighs 79.6 kg, so its auclast.bw is its auclast divided by 79.6. The column must have one value per concentration group; a column whose value changes within a group (for example, a weight recorded at several visits within one profile) is the pknca_error_norm_multiple_values error.
18.6 Filtering and grouping results
dplyr verbs work on PKNCAresults objects. filter() with expressions that use only group columns (the columns after the | in the PKNCAconc() formula) keeps the concentration data, dose data, and intervals in $data consistent with the remaining results (≥ 0.13.0); filters on other result columns (PPTESTCD, PPORRES, exclude) filter only the result table:
o_nca_12 <- filter(o_nca, subject %in% c("1", "2"))
nrow(as.data.frame(o_nca_12))[1] 34
unique(as.character(o_nca_12$data$conc$data$subject))[1] "1" "2"
group_vars() on a PKNCAresults object includes the interval start and end with the group columns (≥ 0.13.0), so code that groups or joins results by group_vars() also matches on the interval:
group_vars(o_nca)[1] "start" "end" "subject"
A results object changed by filter(), mutate(), exclude(), or normalize() no longer passes checkProvenance(); see the Regulatory chapter.
18.7 Controlling the summary table
summary() arguments control which groups and parameters appear and how gaps are shown:
| Argument | Default | Effect |
|---|---|---|
drop_group |
the subject column | Group columns summarized over (removed from the rows of the table) |
drop_param |
none | Parameters left out of the table |
summarize_n |
NA (add N when there is a subject column) |
Add a column N with the number of subjects |
not_requested |
"." |
Text for a parameter not requested in an interval |
not_calculated |
"NC" |
Text for a requested parameter whose summary is NA |
summary(o_nca, drop_param = c("adj.r.squared", "span.ratio"), summarize_n = FALSE) start end auclast cmax half.life aucinf.obs aucpext.obs
0 Inf 98.7 [22.5] 8.65 [17.0] 8.18 [2.12] 115 [28.4] 13.8 [6.34]
Caption: auclast, cmax, aucinf.obs: geometric mean and geometric coefficient of variation; half.life, aucpext.obs: arithmetic mean and standard deviation
When the summary shows the not-calculated text, the caption explains it (≥ 0.13.0). Here, a span-ratio threshold of 20 excludes every half-life, so the λz-dependent parameters are not calculated:
summary(exclude(o_nca, FUN = exclude_nca_span.ratio(20))) start end N auclast cmax half.life adj.r.squared span.ratio
0 Inf 12 98.7 [22.5] 8.65 [17.0] NC NC NC
aucinf.obs aucpext.obs
NC NC
Caption: auclast, cmax, span.ratio, aucinf.obs: geometric mean and geometric coefficient of variation; half.life, adj.r.squared, aucpext.obs: arithmetic mean and standard deviation; N: number of subjects; NC: not calculated
18.8 Custom summary statistics
PKNCA.set.summary() controls how each parameter is presented in summary().
# Arithmetic mean ± SD for AUClast
PKNCA.set.summary(
"auclast",
description = "mean ± SD",
point = mean,
spread = sd
)
# Median [min, max] for Tmax
# spread must return a 2-element numeric vector
PKNCA.set.summary(
"tmax",
description = "median [min, max]",
point = median,
spread = function(x) c(min(x), max(x))
)
summary(o_nca) start end N auclast cmax half.life adj.r.squared span.ratio
0 Inf 12 101 [23.5] 8.65 [17.0] 8.18 [2.12] 0.998 [0.00301] 2.07 [26.2]
aucinf.obs aucpext.obs
115 [28.4] 13.8 [6.34]
Caption: auclast: mean ± SD; cmax, span.ratio, aucinf.obs: geometric mean and geometric coefficient of variation; half.life, adj.r.squared, aucpext.obs: arithmetic mean and standard deviation; N: number of subjects
PKNCA.set.summary() also has a spread_for argument (≥ 0.13.0) that summarizes one parameter with the spread taken from another parameter’s results; PKNCA uses it to show a sparse AUClast as the estimate with its standard error (see the Sparse NCA chapter). PKNCA.set.summary(reset = TRUE) removes every summary instruction and, since that can break summary(), warns (≥ 0.13.0); restore individual parameters as below instead.
Restore the defaults — business.geomean/business.geocv for auclast and business.median/business.range for tmax (the business.* helpers are covered in the next section):
PKNCA.set.summary(
"auclast",
description = "geometric mean and geometric coefficient of variation",
point = business.geomean,
spread = business.geocv
)
PKNCA.set.summary(
"tmax",
description = "median and range",
point = business.median,
spread = business.range
)18.9 Business summary helpers
PKNCA exports the full set of business.* summary functions. These are the defaults summary() uses for most parameters, and they are also available directly for custom reporting pipelines.
| Function | Returns | Description |
|---|---|---|
business.geomean(x) |
scalar | Geometric mean: exp(mean(log(x))) |
business.geocv(x) |
scalar | Geometric CV%: sqrt(exp(var(log(x)))−1) × 100 |
business.mean(x) |
scalar | Arithmetic mean |
business.sd(x) |
scalar | Standard deviation |
business.cv(x) |
scalar | Coefficient of variation %: sd/mean × 100 |
business.median(x) |
scalar | Median |
business.min(x) |
scalar | Minimum |
business.max(x) |
scalar | Maximum |
business.range(x) |
length-2 vector | c(min, max) |
x <- c(10.2, 11.8, 9.6, 12.1, 10.5)
data.frame(
statistic = c("geomean", "geocv", "mean", "sd", "cv%", "median", "min", "max"),
value = c(
business.geomean(x), business.geocv(x),
business.mean(x), business.sd(x), business.cv(x),
business.median(x), business.min(x), business.max(x)
)
) statistic value
1 geomean 10.798057
2 geocv 9.859166
3 mean 10.840000
4 sd 1.069112
5 cv% 9.862655
6 median 10.500000
7 min 9.600000
8 max 12.100000
The business.* functions return NA for an empty input (≥ 0.13.0), rather than an error, so a summary group with no included values gives NC instead of stopping:
business.geomean(numeric(0))[1] NA
Use these as point and spread arguments to PKNCA.set.summary() — rerunning summary() shows the new cmax presentation:
# Arithmetic mean ± CV% for Cmax
PKNCA.set.summary(
"cmax",
description = "mean [CV%]",
point = business.mean,
spread = business.cv
)
summary(o_nca) start end N auclast cmax half.life adj.r.squared span.ratio
0 Inf 12 98.7 [22.5] 8.76 [16.8] 8.18 [2.12] 0.998 [0.00301] 2.07 [26.2]
aucinf.obs aucpext.obs
115 [28.4] 13.8 [6.34]
Caption: auclast, span.ratio, aucinf.obs: geometric mean and geometric coefficient of variation; cmax: mean [CV%]; half.life, adj.r.squared, aucpext.obs: arithmetic mean and standard deviation; N: number of subjects
18.10 Inspecting the terminal regression window
get_halflife_points() returns a logical vector aligned with the original concentration data — TRUE if that observation was used in the λz regression, FALSE if it was available but excluded, NA if half-life was not computed for that interval. In PKNCA ≥ 0.13.0 it also accepts a PKNCAdata object directly, handles start ≠ 0 intervals correctly, and sets points outside any interval to NA. For how PKNCA selects the regression points, see the Half-Life Calculation vignette.
Because the example analysis requested half.life, o_nca already carries the λz fits:
# Logical vector (TRUE = used in lambda.z fit), aligned with the rows of d_conc
hl_used <- get_halflife_points(o_nca)
table(hl_used, useNA = "always")hl_used
FALSE TRUE <NA>
86 46 0
# >= 0.13.0: a PKNCAdata object is accepted directly (no need to run pk.nca first)
head(get_halflife_points(o_data))[1] FALSE FALSE FALSE FALSE FALSE FALSE
get_halflife_fit() (≥ 0.13.0) returns the fitted terminal line of each interval (intercept and slope on the natural-log scale, and the first and last times used), and get_halflife_curve() (≥ 0.13.0) returns predicted concentrations along it, by default n = 50 points from the first time used, extrapolated to the end of the interval’s data (extrapolate_later = TRUE):
get_halflife_fit(o_nca) |> filter(subject == "1") subject start end intercept slope time_first time_last
1 1 0 Inf 2.368785 -0.048457 9.05 24.37
d_hl_curve <- get_halflife_curve(o_nca)
head(d_hl_curve |> filter(subject == "1"), 3) subject start end time conc
1 1 0 Inf 9.050000 6.891228
2 1 0 Inf 9.362653 6.787611
3 1 0 Inf 9.675306 6.685552
# Attach to concentration data for plotting
d_conc_annotated <- d_conc |> mutate(in_lambda_z = hl_used)
subj1_conc <- d_conc_annotated |> filter(subject == "1")
ggplot(subj1_conc, aes(x = time, y = conc, colour = in_lambda_z)) +
geom_line(colour = "grey70") +
geom_line(data = d_hl_curve |> filter(subject == "1"), colour = "firebrick",
linetype = "dashed") +
geom_point(size = 3) +
scale_colour_manual(values = c("FALSE" = "grey50", "TRUE" = "firebrick"),
labels = c("not used", "used in lambda.z"), name = NULL,
na.value = "grey80") +
scale_y_log10() +
labs(title = "Subject 1 - points used in lambda.z regression (red) and fitted line",
x = "Time (h)", y = "Concentration (mg/L) [log scale]") +
theme_minimal()
18.11 Imputation methods
Imputation is a pre-calculation step, not a post-processing step. It is specified before pk.nca() runs via the impute argument to PKNCAdata() — either a method name applied to every interval, or the name of a column in the intervals data frame holding per-interval methods. See the Concentration Imputation chapter for the built-in strategies, chaining, custom methods, and a side-by-side comparison.