---
title: "Dose-Aware Interpolation and Extrapolation"
---
```{r setup, include=FALSE}
library(PKNCA)
library(dplyr)
library(ggplot2)
conflicted::conflicts_prefer(dplyr::filter, dplyr::select, .quiet = TRUE)
```
## Why dose timing matters for interpolation
Standard concentration interpolation (e.g. `interp.extrap.conc()`) assumes a smooth concentration-time curve with no discontinuities. But when a dose is given **at** or **near** the interpolation target time, the correct value depends critically on whether you are asking about the concentration *before* or *after* that dose event.
**`interp.extrap.conc.dose()`** is the dose-aware version. It accounts for:
- Whether the route is intravascular (bolus causes instantaneous jump) or extravascular (no instantaneous change)
- Whether you want the concentration before (`out.after = FALSE`) or after (`out.after = TRUE`) the dose
- Whether the dose is an infusion with a finite duration
---
## The key argument: `out.after`
| `out.after` | Meaning |
|---|---|
| `FALSE` (default) | Return the concentration just **before** the dose event |
| `TRUE` | Return the concentration just **after** the dose event |
`out.after` has a meaningful effect only at the instant of an IV bolus dose (`route.dose = "intravascular"` with `duration.dose = 0`), where the concentration jumps instantaneously: `FALSE` gives the value from the data before the dose (the trough) and `TRUE` gives the value from the data after the dose. For extravascular doses — and for infusions, which have `duration.dose > 0` — there is no instantaneous jump, so `out.after` makes no difference.
---
## Example data: single oral dose
```{r}
# Single-dose oral profile (absorption + elimination)
conc <- c(0.0, 2.5, 5.0, 4.2, 2.8, 1.5, 0.8)
time <- c(0, 1, 2, 3, 5, 7, 10 )
d <- data.frame(time = time, conc = conc)
ggplot(d, aes(x = time, y = conc)) +
geom_line(colour = "steelblue") +
geom_point(size = 3, colour = "steelblue") +
labs(title = "Example single-dose oral profile",
x = "Time (h)", y = "Concentration (mg/L)") +
theme_minimal()
```
---
## Basic interpolation (no dose at target time)
When the target time is **between** observed samples and does not coincide with a dose, the result equals standard log-linear interpolation.
```{r}
# t = 4 h falls between t = 3 and t = 5
interp.extrap.conc.dose(
conc = conc,
time = time,
time.dose = 0, # dose was given at t=0
route.dose = "extravascular",
time.out = 4
)
```
Compare to dose-unaware interpolation:
```{r}
interp.extrap.conc(conc = conc, time = time, time.out = 4)
```
They agree when no dose occurs at the target time.
---
## At the dose time: oral (extravascular)
For an oral dose, concentration does not change instantaneously at the dose time — absorption is gradual. So `out.after` does not change the interpolated value at the dose time itself.
```{r}
# Oral: t=0, before dose
interp.extrap.conc.dose(
conc = conc, time = time,
time.dose = 0, route.dose = "extravascular",
time.out = 0, out.after = FALSE
)
```
```{r}
# Oral: t=0, after dose (same — no jump)
interp.extrap.conc.dose(
conc = conc, time = time,
time.dose = 0, route.dose = "extravascular",
time.out = 0, out.after = TRUE
)
```
---
## At the dose time: IV bolus (intravascular)
For an IV bolus — indicated by `route.dose = "intravascular"` together with `duration.dose = 0` — the dose causes an instantaneous concentration jump. `out.after = FALSE` returns the pre-dose (trough) concentration; `out.after = TRUE` returns the post-dose concentration.
```{r}
# Two IV bolus doses (t=0 and t=6); the t=6 sample is the pre-dose trough
conc_iv <- c(10, 8, 6, 4, 2.5, 9, 6)
time_iv <- c(0, 1, 2, 4, 6, 7, 9)
# Just before the second dose at t=6: the observed trough
interp.extrap.conc.dose(
conc = conc_iv, time = time_iv,
time.dose = c(0, 6), route.dose = "intravascular", duration.dose = 0,
time.out = 6, out.after = FALSE
)
```
```{r}
# Just after the second dose at t=6: the post-dose concentration
interp.extrap.conc.dose(
conc = conc_iv, time = time_iv,
time.dose = c(0, 6), route.dose = "intravascular", duration.dose = 0,
time.out = 6, out.after = TRUE
)
```
The pre-dose value is the measured trough (2.5). The post-dose value (about 11.0) is estimated the same way as `pk.calc.c0()`: log-slope back-extrapolation from the concentrations measured after the dose.
---
## Multiple dose times
The IV bolus example above already passed two dose times — `time.dose` accepts a vector whenever multiple doses were given during the sampling window. Here is the same two-dose scenario with oral dosing.
```{r}
# Oral doses at t=0 and t=6
conc_md <- c(0, 2.5, 5.0, 4.2, 2.8, 4.5, 6.0, 5.1, 3.5, 2.0)
time_md <- c(0, 1, 2, 3, 5, 6, 7, 8, 10, 12 )
# Concentration just before the second dose at t=6
interp.extrap.conc.dose(
conc = conc_md, time = time_md,
time.dose = c(0, 6), route.dose = "extravascular",
time.out = 6, out.after = FALSE
)
```
```{r}
# Concentration just after the second dose at t=6
interp.extrap.conc.dose(
conc = conc_md, time = time_md,
time.dose = c(0, 6), route.dose = "extravascular",
time.out = 6, out.after = TRUE
)
```
For extravascular doses, before and after are the same (no instantaneous jump).
---
## IV infusion: dose duration
For IV infusions, supply `duration.dose` (the infusion duration in the same time units).
```{r}
# 30-minute (0.5h) infusion starting at t=0
conc_inf <- c(0, 3, 5, 4.5, 3, 2, 1 )
time_inf <- c(0, 0.25, 0.5, 1, 2, 4, 8 )
# During the infusion, between the samples at t=0.25 and t=0.5
interp.extrap.conc.dose(
conc = conc_inf, time = time_inf,
time.dose = 0,
route.dose = "intravascular",
duration.dose = 0.5,
time.out = 0.4, out.after = FALSE
)
```
Because `duration.dose > 0`, the dose is treated as an infusion rather than a bolus: there is no instantaneous jump at t = 0, and concentrations during the infusion are interpolated between the observed samples (here 4.2, between the 3 and 5 measurements).
---
## Extrapolating beyond the last sample
Returning to the single oral-dose profile from the top of the page (last sample at t = 10): extrapolation past the last measured concentration requires an elimination rate. Pass `lambda.z` (forwarded via `...` to the extrapolation functions); without it, the result is `NA`.
```{r}
# t=14 is beyond the last sample (t=10); without lambda.z the result is NA
interp.extrap.conc.dose(
conc = conc, time = time,
time.dose = 0, route.dose = "extravascular",
time.out = 14
)
```
```{r}
# With lambda.z, extrapolation follows clast * exp(-lambda.z * (t - tlast))
interp.extrap.conc.dose(
conc = conc, time = time,
time.dose = 0, route.dose = "extravascular",
time.out = 14, lambda.z = 0.25
)
```
---
## How PKNCA uses `interp.extrap.conc.dose()` internally
PKNCA calls this function automatically whenever an interval boundary (the `start` or `end` of an interval in your intervals data frame) falls at a dose time — see [Selection of Calculation Intervals](https://humanpred.github.io/pknca/articles/v03-selection-of-calculation-intervals.html) for how boundaries are chosen. You normally never call it directly.
Understanding its behavior helps diagnose unusual NCA results when dosing and sampling times coincide — for example, when an interval boundary such as `end = tau` coincides with the next dose (the trough sample time). Every combination of dose events and observations, and the method used for each, is tabulated in the [complete methods table](https://humanpred.github.io/pknca/articles/v21-methods-for-dose-aware-interpolation-and-extrapolation.html#appendix-complete-methods-table) of the dose-aware interpolation vignette.
---
## Argument reference
| Argument | Required | Meaning |
|---|---|---|
| `conc` | Yes | Observed concentrations |
| `time` | Yes | Observation times (same units as `time.dose`) |
| `time.dose` | Yes | Time(s) at which doses were given |
| `route.dose` | No (default `"extravascular"`) | `"extravascular"` or `"intravascular"`; scalar or one value per dose |
| `duration.dose` | No (default `NA`) | Dose duration: `0` marks an IV bolus, a positive value an infusion; scalar or one value per dose |
| `time.out` | Yes | Target time at which to interpolate/extrapolate |
| `out.after` | No (default `FALSE`) | `FALSE` = before dose; `TRUE` = after dose (only meaningful at the instant of an IV bolus) |
| `options` | No | PKNCA options list (controls AUC method / interpolation rule) |
The table is abridged: `interp.extrap.conc.dose()` also accepts `conc.blq` and `conc.na` (BLQ and `NA` concentration handling), `check` (input validation), and `...` (passed through to the interpolation/extrapolation functions, e.g. `lambda.z`).
---
::: {.callout-note icon=false appearance="minimal"}
**pkgdown reference:** [interp.extrap.conc.dose()](https://humanpred.github.io/pknca/reference/interp.extrap.conc.html) · [interp.extrap.conc()](https://humanpred.github.io/pknca/reference/interp.extrap.conc.html) · [pk.calc.c0()](https://humanpred.github.io/pknca/reference/pk.calc.c0.html)
:::