4  AUC Types and Integration Methods

4.1 AUC types

PKNCA offers four families of AUC — AUClast, AUCall, AUCinf (in observed and predicted flavors), and partial/interval AUC — each answering a different question. They differ in where integration ends and in how concentrations below the limit of quantification (BLQ) are treated:

Parameter End point BLQ treatment When to use
auclast Last measurable concentration BLQs after last non-BLQ are ignored Default; most studies
aucall Last measured timepoint Post-last-measurable BLQs treated as 0 When you want to include the BLQ tail
aucinf.obs Extrapolated to ∞ (using observed Clast) When half-life is reliable
aucinf.pred Extrapolated to ∞ (using predicted Clast from λz fit) When Clast is noisy
aucint.* Any user-defined time window Partial AUC, bioequivalence

For a worked example computing these AUC flavors on a full dataset, see the AUC Calculation with PKNCA vignette.

4.1.1 AUClast vs AUCall

Subject 1’s profile ends on a measurable sample (t = 24.37), so there is no BLQ tail and auclast equals aucall on the raw data. To show the difference, this demo appends a synthetic BLQ tail (concentrations reported as 0 at t = 26 and 28) to a copy of the data:

# Copy of subject 1's profile with a synthetic BLQ tail (reported as 0)
d_one_blq <- d_one |>
  select(subject, time, conc) |>
  mutate(subject = as.character(subject)) |>
  bind_rows(data.frame(subject = "1", time = c(26, 28), conc = 0))

o_conc_blq <- PKNCAconc(d_one_blq, conc ~ time | subject)

auc_compare <- data.frame(
  start    = 0,
  end      = Inf,
  auclast  = TRUE,
  aucall   = TRUE
)

o_nca_blq <- pk.nca(PKNCAdata(o_conc_blq, o_dose, intervals = auc_compare))

as.data.frame(o_nca_blq) |>
  filter(PPTESTCD %in% c("auclast", "aucall")) |>
  select(PPTESTCD, PPORRES)
# A tibble: 2 × 2
  PPTESTCD PPORRES
  <chr>      <dbl>
1 auclast     147.
2 aucall      150.

aucallauclast when there are post-last-measurable BLQ timepoints, since those are treated as 0 and add a small triangle to the AUC — here the triangle from Clast at t = 24.37 down to 0 at t = 26.


4.2 Partial AUC (AUCint)

For bioequivalence or exposure-in-a-window calculations, use aucint.* parameters. These calculate AUC over an exact time window, interpolating concentrations at the boundaries if needed.

# AUC from 0 to 4h and 0 to 12h on the same profile
partial_intervals <- data.frame(
  start         = c(0,  0),
  end           = c(4, 12),
  aucint.last   = TRUE   # AUC to the last obs within window (or interpolated boundary)
)

o_data_partial <- PKNCAdata(o_conc, o_dose, intervals = partial_intervals)
o_nca_partial  <- pk.nca(o_data_partial)

as.data.frame(o_nca_partial) |>
  filter(PPTESTCD == "aucint.last") |>
  select(start, end, PPTESTCD, PPORRES)
# A tibble: 2 × 4
  start   end PPTESTCD    PPORRES
  <dbl> <dbl> <chr>         <dbl>
1     0     4 aucint.last    33.7
2     0    12 aucint.last    91.7

All partial AUC variants:

Parameter End concentration Dose-aware boundary Extrapolation
aucint.last Last observed in window No None
aucint.all Last observed, BLQs as 0 No None
aucint.inf.obs Extrapolated to ∞, observed Clast No λz-based tail
aucint.inf.pred Extrapolated to ∞, predicted Clast No λz-based tail
aucint.last.dose Same as .last Yes None
aucint.all.dose Same as .all Yes None
aucint.inf.obs.dose Same as .inf.obs Yes λz-based tail
aucint.inf.pred.dose Same as .inf.pred Yes λz-based tail

Note: aucint.inf.pred requires that lambda.z has been estimated (i.e. that the terminal slope is calculable from the data window).

The .dose variants use dose-aware interpolation (interp.extrap.conc.dose()) at the interval start and end. This matters when the interval boundary coincides with a dose time — the .dose variant correctly handles the before/after concentration jump. The full decision rules are in the dose-aware interpolation and extrapolation vignette.

# All partial AUC variants over a single window
all_partial <- data.frame(
  start                 = 0,
  end                   = 8,
  aucint.last           = TRUE,
  aucint.all            = TRUE,
  aucint.inf.obs        = TRUE,
  aucint.inf.pred       = TRUE,
  aucint.last.dose      = TRUE,
  aucint.all.dose       = TRUE,
  aucint.inf.obs.dose   = TRUE,
  aucint.inf.pred.dose  = TRUE
)

o_nca_all_partial <- pk.nca(PKNCAdata(o_conc, o_dose, intervals = all_partial))
as.data.frame(o_nca_all_partial) |>
  filter(grepl("^aucint", PPTESTCD)) |>
  select(PPTESTCD, PPORRES)
# A tibble: 8 × 2
  PPTESTCD             PPORRES
  <chr>                  <dbl>
1 aucint.last             65.3
2 aucint.last.dose        65.3
3 aucint.all              65.3
4 aucint.all.dose         65.3
5 aucint.inf.obs          65.3
6 aucint.inf.obs.dose     65.3
7 aucint.inf.pred         65.3
8 aucint.inf.pred.dose    65.3

4.3 Concentration interpolation at interval boundaries

When a requested interval boundary (e.g. end = 5) falls between two observed timepoints, PKNCA interpolates the concentration at that boundary before computing AUC — handled internally by interp.extrap.conc().

The interpolation method matches the AUC method: - "linear" → linear interpolation - "lin-log" → linear interpolation before Tmax, log-linear after Tmax - "lin up/log down" → log-linear interpolation on the descending phase

# end = 5 falls between observed samples t = 3.82 and t = 5.10 for subject 1
interp_interval <- data.frame(start = 0, end = 5, auclast = TRUE)
o_nca_interp <- pk.nca(PKNCAdata(o_conc, o_dose, intervals = interp_interval))
as.data.frame(o_nca_interp) |>
  filter(PPTESTCD == "auclast") |>
  select(start, end, PPTESTCD, PPORRES)
# A tibble: 1 × 4
  start   end PPTESTCD PPORRES
  <dbl> <dbl> <chr>      <dbl>
1     0     5 auclast     32.1

For boundaries that coincide with a dose time, the .dose variants above use dose-aware interpolation instead — see the Dose-aware interpolation chapter.


4.4 Percent extrapolation (%AUCextrap)

When using aucinf, you should check what fraction was extrapolated beyond the last observation. A high aucpext means the terminal phase was poorly characterized.

pext_interval <- data.frame(
  start        = 0,
  end          = Inf,
  auclast      = TRUE,
  aucinf.obs   = TRUE,
  aucpext.obs  = TRUE,
  aucpext.pred = TRUE
)

o_nca_pext <- pk.nca(PKNCAdata(o_conc, o_dose, intervals = pext_interval))
as.data.frame(o_nca_pext) |>
  filter(grepl("^auc", PPTESTCD)) |>
  select(PPTESTCD, PPORRES)
# A tibble: 5 × 2
  PPTESTCD     PPORRES
  <chr>          <dbl>
1 auclast        147. 
2 aucinf.obs     215. 
3 aucinf.pred    215. 
4 aucpext.obs     31.5
5 aucpext.pred    31.5

Here about 31.5% of aucinf.obs is extrapolated — well above the conventional 20% acceptance limit. Note that pk.nca() computes aucpext but does not flag anything on its own: the exclude column (not shown above) is all NA despite the high extrapolation. Flagging is a manual step with exclude() after pk.nca():

o_nca_pext_excl <- exclude(
  o_nca_pext,
  reason = "aucpext.obs > 20%",
  FUN    = exclude_nca_max.aucinf.pext()
)
Loading required namespace: testthat
# Show the flagged rows
as.data.frame(o_nca_pext_excl) |>
  filter(!is.na(exclude)) |>
  select(PPTESTCD, PPORRES, exclude)
# A tibble: 4 × 3
  PPTESTCD     PPORRES exclude     
  <chr>          <dbl> <chr>       
1 aucinf.obs     215.  aucpext > 20
2 aucinf.pred    215.  aucpext > 20
3 aucpext.obs     31.5 aucpext > 20
4 aucpext.pred    31.5 aucpext > 20

The aucinf results and the parameters derived from them (including aucpext itself) are now flagged. Because FUN supplies its own exclusion text, the recorded reason is the function’s "aucpext > 20" rather than the reason = string.

The 20% default comes from the max.aucinf.pext option — but only the global option: called with no argument, exclude_nca_max.aucinf.pext() reads PKNCA.options("max.aucinf.pext"). A per-object setting such as PKNCAdata(options = list(max.aucinf.pext = 10)) never reaches the exclusion function (and does not make pk.nca() flag anything by itself). To flag at a different threshold, pass it to the function directly: exclude_nca_max.aucinf.pext(10).


4.5 Integration methods

The integration method controls how the area of each trapezoid is computed between consecutive timepoints. Each rule, including the zero-concentration fallbacks, is derived in the AUC integration methods vignette.

# Numeric comparison of the three methods on one subject
methods <- c("linear", "lin-log", "lin up/log down")

results <- lapply(methods, function(m) {
  o <- pk.nca(PKNCAdata(o_conc, o_dose,
                        intervals = data.frame(start=0, end=Inf, auclast=TRUE),
                        options   = list(auc.method = m)))
  as.data.frame(o) |>
    filter(PPTESTCD == "auclast") |>
    mutate(method = m)
}) |> bind_rows()

results |> select(method, PPORRES) |>
  rename(auclast = PPORRES)
# A tibble: 3 × 2
  method          auclast
  <chr>             <dbl>
1 linear             149.
2 lin-log            147.
3 lin up/log down    147.

4.5.1 Method details

Linear trapezoidal ("linear")

Uses the standard trapezoidal rule throughout: \[\text{AUC}_{t_i \to t_{i+1}} = \frac{(C_i + C_{i+1})}{2} \times (t_{i+1} - t_i)\]

Overestimates during the descending phase (gives too much weight to the higher concentration).

Lin-log ("lin-log")

Uses linear integration from start up to Tmax, and log-linear integration from Tmax onward: \[\text{AUC}_{t_i \to t_{i+1}} = \frac{(C_i - C_{i+1})}{\ln C_i - \ln C_{i+1}} \times (t_{i+1} - t_i) \quad \text{(post-Tmax)}\]

The switch point is Tmax (not whether concentrations are rising or falling), so any post-Tmax secondary peak is integrated log-linearly even if concentrations are rising. Falls back to linear for any interval where either endpoint is zero. Not recommended — "lin up/log down" is almost always preferable as it switches on actual direction rather than Tmax.

Lin up / log down ("lin up/log down") — default

Hybrid: linear trapezoidal on the ascending phase (when \(C_{i+1} \geq C_i\)), log-linear on the descending phase (when \(C_{i+1} < C_i\)). Best of both worlds for most PK profiles.

# Profile with AUClast shaded
d_plot <- d_one |>
  filter(conc > 0) |>
  arrange(time)

ggplot(d_plot, aes(x = time, y = conc)) +
  geom_line(colour = "steelblue", linewidth = 1) +
  geom_point(size = 3, colour = "steelblue") +
  geom_area(alpha = 0.15, fill = "steelblue") +
  labs(title = "Concentration-time profile (shaded area = AUClast)",
       x = "Time (h)", y = "Concentration (mg/L)") +
  theme_minimal()

4.5.2 When to use which method

Study type Recommended method Notes
Most oral/extravascular "lin up/log down" (default) Linear on ascending phase, log-linear on declining phase
IV bolus "lin up/log down" (default) Profile is entirely declining, so log-linear is used throughout — equivalent to "lin-log" for pure bolus but without the Tmax ambiguity
IV infusion with smooth decline "lin up/log down" Concentrations rise during infusion (linear) then fall after end of infusion (log-linear)
Regulatory (some agencies) "linear" Some health authorities require the linear trapezoidal rule throughout
Highly irregular profiles with zeros "linear" Zeros alone are handled per-segment by the log methods (they fall back to linear for those segments); choose "linear" when you want the same rule everywhere, e.g. for some regulatory templates
Never recommended "lin-log" Switches at Tmax rather than on direction of change — gives wrong method for post-Tmax secondary peaks

4.6 AUMC — area under the first moment curve

The area under the first moment curve (AUMC) weights concentrations by time and is used to compute mean residence time (MRT): \[\text{AUMC} = \int_0^{t_{\mathrm{last}}} t \cdot C(t) \, dt\]

Parameter Meaning
aumclast AUMC from 0 to last measurable concentration
aumcall AUMC from 0 to last, treating BLQ as 0
aumcinf.obs AUMC extrapolated to ∞, observed Clast
aumcinf.pred AUMC extrapolated to ∞, predicted Clast
aumc_interval <- data.frame(
  start         = 0,
  end           = Inf,
  aumclast      = TRUE,
  aumcall       = TRUE,
  aumcinf.obs   = TRUE,
  aumcinf.pred  = TRUE
)

o_nca_aumc <- pk.nca(PKNCAdata(o_conc, o_dose, intervals = aumc_interval))

as.data.frame(o_nca_aumc) |>
  filter(grepl("^aumc", PPTESTCD)) |>
  select(PPTESTCD, PPORRES)
# A tibble: 4 × 2
  PPTESTCD     PPORRES
  <chr>          <dbl>
1 aumclast       1499.
2 aumcall        1499.
3 aumcinf.obs    4546.
4 aumcinf.pred   4546.

4.7 Other interval parameters

These interval parameters are requested the same way as AUCs and often accompany them.

4.7.1 AUC above a threshold (aucabove.*)

aucabove.* computes the area under the curve above a reference concentration — useful for quantifying exposure above a minimum effective concentration (for the duration above it, see time_above).

Parameter Reference concentration
aucabove.trough.all Ctrough (concentration at end of interval)
aucabove.predose.all Cpredose (concentration at interval start)
# ctrough requires an observation exactly at the interval end —
# subject 1's last sample is at t = 24.37 (end = 24 would give ctrough = NA)
above_interval <- data.frame(
  start              = 0,
  end                = 24.37,
  aucabove.trough.all  = TRUE,
  aucabove.predose.all = TRUE,
  ctrough              = TRUE,
  cstart               = TRUE
)

o_nca_above <- pk.nca(PKNCAdata(o_conc, o_dose, intervals = above_interval))

as.data.frame(o_nca_above) |>
  filter(PPTESTCD %in% c("aucabove.trough.all", "aucabove.predose.all", "ctrough", "cstart")) |>
  select(PPTESTCD, PPORRES)
# A tibble: 4 × 2
  PPTESTCD             PPORRES
  <chr>                  <dbl>
1 ctrough                 3.28
2 cstart                  0.74
3 aucabove.predose.all  129.  
4 aucabove.trough.all    69.3 

4.7.2 Time above a threshold (time_above)

time_above computes the total duration (in time units) for which the concentration exceeds a specified threshold. The threshold is set via the conc_above column in the intervals data frame.

# Time above 2 mg/L
time_above_interval <- data.frame(
  start      = 0,
  end        = Inf,
  time_above = TRUE,
  conc_above = 2       # threshold concentration (same units as your data)
)

o_nca_ta <- pk.nca(PKNCAdata(o_conc, o_dose, intervals = time_above_interval))

as.data.frame(o_nca_ta) |>
  filter(PPTESTCD == "time_above") |>
  select(PPTESTCD, PPORRES)
# A tibble: 1 × 2
  PPTESTCD   PPORRES
  <chr>        <dbl>
1 time_above    24.2

4.7.3 Total dose (totdose)

totdose is the total amount administered within the analysis interval — it is extracted from the dose object rather than computed from concentrations. Useful as a reference column in your results data frame.

totdose_interval <- data.frame(
  start    = 0,
  end      = Inf,
  totdose  = TRUE,
  auclast  = TRUE
)

o_nca_td <- pk.nca(PKNCAdata(o_conc, o_dose, intervals = totdose_interval))

as.data.frame(o_nca_td) |>
  filter(PPTESTCD %in% c("totdose", "auclast")) |>
  select(PPTESTCD, PPORRES)
# A tibble: 2 × 2
  PPTESTCD PPORRES
  <chr>      <dbl>
1 auclast     147.
2 totdose     320.

4.7.4 Cav over a specific interval (cav.int.*)

cav.int.* computes the average concentration over the requested interval, without requiring a full dosing-interval design. The numerator is the corresponding aucint.* flavor computed over the window; the divisor is always end − start.

Warning: because the divisor is always end − start, requesting any cav.int.* parameter with end = Inf divides a finite AUC by infinity and reports 0 — always use a finite end.

Parameter Numerator (aucint.* flavor over the window)
cav.int.last aucint.last — stops accumulating area at the last measurable concentration
cav.int.all aucint.all — post-Tlast BLQ values treated as 0
cav.int.inf.obs aucint.inf.obs — λz extrapolation beyond Tlast, observed Clast
cav.int.inf.pred aucint.inf.pred — λz extrapolation beyond Tlast, predicted Clast
# Two finite windows: 0–24 h lies inside the observed profile (Tlast = 24.37 h),
# 0–48 h extends past it
cav_interval <- data.frame(
  start             = 0,
  end               = c(24, 48),
  cav.int.last      = TRUE,
  cav.int.all       = TRUE,
  cav.int.inf.obs   = TRUE,
  cav.int.inf.pred  = TRUE
)

o_nca_cav <- pk.nca(PKNCAdata(o_conc, o_dose, intervals = cav_interval))

as.data.frame(o_nca_cav) |>
  filter(grepl("^cav.int", PPTESTCD)) |>
  select(start, end, PPTESTCD, PPORRES)
# A tibble: 8 × 4
  start   end PPTESTCD         PPORRES
  <dbl> <dbl> <chr>              <dbl>
1     0    24 cav.int.last        6.08
2     0    24 cav.int.all         6.08
3     0    24 cav.int.inf.obs     6.08
4     0    24 cav.int.inf.pred    6.08
5     0    48 cav.int.last        3.07
6     0    48 cav.int.all         3.07
7     0    48 cav.int.inf.obs     4.03
8     0    48 cav.int.inf.pred    4.03

Over 0–24 h all four flavors integrate the same observed curve, so they coincide. Over 0–48 h they separate: the .inf forms extrapolate the tail beyond Tlast with λz (differing from each other only through the observed vs. predicted Clast — nearly identical here), while .last and .all stop accumulating area at Tlast yet still divide by the full 48 h, which is why their average drops from 6.08 to 3.07 mg/L.