16  Dose-Aware Interpolation and Extrapolation

16.1 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

16.2 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.


16.3 Example data: single oral dose

# 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()


16.4 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.

# 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
)
[1] 3.429286
attr(,"Method")
[1] "Interpolation"

Compare to dose-unaware interpolation:

interp.extrap.conc(conc = conc, time = time, time.out = 4)
[1] 3.429286

They agree when no dose occurs at the target time.


16.5 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.

# 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
)
[1] 0
attr(,"Method")
[1] "Observed concentration"
# 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
)
[1] 0
attr(,"Method")
[1] "Interpolation"

16.6 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.

# 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
)
[1] 2.5
attr(,"Method")
[1] "Observed concentration"
# 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
)
[1] 11.0227
attr(,"Method")
[1] "Immediately after an IV bolus with a concentration next"

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.


16.7 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.

# 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
)
[1] 4.5
attr(,"Method")
[1] "Observed concentration"
# 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
)
[1] 4.5
attr(,"Method")
[1] "Interpolation"

For extravascular doses, before and after are the same (no instantaneous jump).


16.8 IV infusion: dose duration

For IV infusions, supply duration.dose (the infusion duration in the same time units).

# 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
)
[1] 4.2
attr(,"Method")
[1] "Interpolation"

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).


16.9 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.

# 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
)
[1] NA
attr(,"Method")
[1] "Extrapolation"
# 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
)
[1] 0.2943036
attr(,"Method")
[1] "Extrapolation"

16.10 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 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 of the dose-aware interpolation vignette.


16.11 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).