11  Urine Excretion

11.1 Urine NCA overview

Urine NCA characterizes how much drug is excreted in urine over time. Key parameters:

Parameter Formula Meaning
ae Σ(concentration × volume) Amount excreted in urine (mass)
fe ae / dose Fraction of dose excreted unchanged
clr.obs ae / AUCinf.obs Renal clearance — using observed Clast
clr.pred ae / AUCinf.pred Renal clearance — using predicted Clast
clr.last ae / AUClast Renal clearance — no extrapolation

11.2 Data structure for urine

Urine NCA requires a separate concentration object for urine, with the volume argument to PKNCAconc() specifying the column name that holds the urine volume collected in each interval.

The key distinction from plasma NCA: urine concentrations represent interval averages rather than timepoint measurements. This dataset records each collection’s end time as time — sufficient for amount parameters like ae, which only need each row to fall inside the analysis interval. PKNCA’s full urine convention is time as the collection start plus a duration column giving the collection length; the excretion-rate section below rebuilds the data that way.

# Simulate urine collection for 4 subjects after a 100 mg IV bolus
# Collections at: 0-2h, 2-6h, 6-12h, 12-24h
set.seed(99)

urine_collections <- expand.grid(
  Subject     = factor(1:4),
  end_time    = c(2, 6, 12, 24)
) |>
  mutate(
    # Simulated urine volume (mL) and concentration (mcg/mL)
    volume_mL  = round(runif(n(), 80, 300)),
    # Fraction excreted: ~30% in first 2h, 25% 2-6h, 20% 6-12h, 15% 12-24h
    ae_target  = 100 * c(0.30, 0.25, 0.20, 0.15)[match(end_time, c(2, 6, 12, 24))],
    ae_indiv   = ae_target * exp(rnorm(n(), 0, 0.1)),  # mild IIV
    conc_mcg_mL = ae_indiv / volume_mL * 1000           # conc = amount / volume
  ) |>
  select(Subject, time = end_time, conc = conc_mcg_mL, volume = volume_mL)

head(urine_collections)
  Subject time      conc volume
1       1    2 138.40812    209
2       2    2 251.02891    105
3       3    2 120.53715    231
4       4    2 110.38941    298
5       1    6 136.09722    198
6       2    6  66.39376    293

Renal clearance needs a plasma AUC, so we also simulate matched plasma profiles.

# Plasma concentrations (IV bolus, same subjects)
ke <- 0.1  # h^-1
d_plasma <- expand.grid(
  Subject = factor(1:4),
  time    = c(0.5, 1, 2, 4, 6, 8, 12, 16, 24)
) |>
  mutate(
    ke_i = ke * exp(rnorm(n(), 0, 0.15)),
    conc = 100 / 30 * exp(-ke_i * time)   # dose/V * e^(-ke*t)
  )

d_dose <- data.frame(
  Subject = factor(1:4),
  dose    = 100,    # mg
  time    = 0,
  route   = "intravascular"
)

11.3 Setting up urine PKNCAconc

Pass the urine volume column name via the volume argument; the unit arguments (concu, amountu, timeu, …) are optional and set units. For assigning and converting units, see Unit Assignment and Conversion with PKNCA. The formula uses the same conc ~ time | Subject structure.

o_conc_plasma <- PKNCAconc(d_plasma, conc ~ time | Subject)
o_conc_urine  <- PKNCAconc(
  urine_collections,
  conc ~ time | Subject,
  volume = "volume"    # column with urine volume (mL)
)

o_dose <- PKNCAdose(d_dose, dose ~ time | Subject, route = "intravascular")
Found column named route, using it for the attribute of the same name.

11.4 Separate plasma + urine workflow

Renal clearance combines the urine amount excreted (Ae) with the plasma AUC. Combined plasma+urine analysis in a single PKNCAdata object is limited; the recommended workflow is to run plasma NCA and urine NCA separately and join the results.

This separate-workflow approach works reliably:

# --- Plasma NCA ---
o_plasma_data <- PKNCAdata(
  o_conc_plasma, o_dose,
  intervals = data.frame(start = 0, end = Inf,
                         auclast = TRUE, aucinf.obs = TRUE),
  impute = "start_conc0"
)
o_nca_plasma <- pk.nca(o_plasma_data)

plasma_auc <- as.data.frame(o_nca_plasma) |>
  filter(PPTESTCD %in% c("auclast", "aucinf.obs")) |>
  select(Subject, PPTESTCD, PPORRES) |>
  tidyr::pivot_wider(names_from = PPTESTCD, values_from = PPORRES)

# --- Urine: compute ae manually ---
# ae = concentration (mcg/mL) × volume (mL) / 1000  →  mg
ae_by_subject <- urine_collections |>
  mutate(ae_interval = conc * volume / 1000) |>  # mcg/mL * mL / 1000 = mg
  group_by(Subject) |>
  summarise(ae_total = sum(ae_interval), .groups = "drop")

# --- Join and compute clr, fe ---
dose_amt <- 100  # mg

results <- plasma_auc |>
  left_join(ae_by_subject, by = "Subject") |>
  mutate(
    fe       = ae_total / dose_amt,
    clr_obs  = ae_total / aucinf.obs,
    clr_last = ae_total / auclast
  )

results
# A tibble: 4 × 7
  Subject auclast aucinf.obs ae_total    fe clr_obs clr_last
  <fct>     <dbl>      <dbl>    <dbl> <dbl>   <dbl>    <dbl>
1 1          29.0       35.2     91.8 0.918    2.61     3.17
2 2          30.8       33.1     78.8 0.788    2.38     2.56
3 3          32.7       36.5     82.2 0.822    2.25     2.51
4 4          30.5       36.1     92.9 0.929    2.57     3.05

clr.pred works identically using AUCinf.pred as the denominator.

For a real IV analysis, back-extrapolate C0 instead — see the Intravascular chapter; conc = 0 at t = 0 is used here only to keep the simulation short.


11.5 Interpreting results

results |>
  mutate(across(where(is.numeric), \(x) round(x, 3))) |>
  select(Subject, ae_total, fe, clr_obs, clr_last) |>
  knitr::kable(
    col.names = c("Subject", "Ae (mg)", "fe", "CLr.obs (L/h)", "CLr.last (L/h)"),
    caption = "Urine NCA results — simulated IV bolus (dose = 100 mg)"
  )
Urine NCA results — simulated IV bolus (dose = 100 mg)
Subject Ae (mg) fe CLr.obs (L/h) CLr.last (L/h)
1 91.844 0.918 2.609 3.172
2 78.781 0.788 2.382 2.558
3 82.223 0.822 2.252 2.511
4 92.938 0.929 2.573 3.049

Interpretation guide:

  • fe near 0.9 means ~90% of the dose is renally excreted unchanged — renal excretion dominates elimination for this simulated drug. A low fe would instead point to metabolism or other elimination routes.
  • clr.obs ≈ GFR (120 mL/min = 7.2 L/h) suggests renal filtration; higher values indicate active secretion.
  • clr.last > clr.obs because the same Ae is divided by the smaller AUClast; the gap grows when sampling misses late elimination.

11.6 Using the PKNCA ae parameter natively

If your data are structured with urine as a separate concentration object and PKNCAconc(..., volume = "vol_col"), PKNCA can compute ae directly:

# o_conc_urine (created above) already carries the volume column
ur_intervals <- data.frame(
  start = 0,
  end   = 24,
  ae    = TRUE
)

o_data_ae <- PKNCAdata(o_conc_urine, o_dose, intervals = ur_intervals)
o_nca_ae  <- pk.nca(o_data_ae)

as.data.frame(o_nca_ae) |>
  filter(PPTESTCD == "ae") |>
  select(Subject, PPORRES) |>
  arrange(Subject)
# A tibble: 4 × 2
  Subject PPORRES
  <fct>     <dbl>
1 1        91844.
2 2        78781.
3 3        82223.
4 4        92938.

Note the units: ae here is in mcg (urine concentration in mcg/mL × volume in mL), so ~90,000 mcg matches the ~90 mg computed manually above.


11.7 New urine parameters (≥ 0.12.2)

11.7.1 volpk — total collection volume

volpk is the sum of urine volumes across all collection periods within the analysis interval. When you pass volume = "volume" to PKNCAconc(), PKNCA can compute this automatically:

vol_interval <- data.frame(
  start  = 0,
  end    = 24,
  ae     = TRUE,
  volpk  = TRUE    # sum of urine volumes over the interval
)

o_nca_vol <- pk.nca(PKNCAdata(o_conc_urine, o_dose, intervals = vol_interval))
as.data.frame(o_nca_vol) |>
  filter(PPTESTCD %in% c("ae", "volpk")) |>
  select(Subject, PPTESTCD, PPORRES)
# A tibble: 8 × 3
  Subject PPTESTCD PPORRES
  <fct>   <chr>      <dbl>
1 1       volpk       689 
2 1       ae        91844.
3 2       volpk       737 
4 2       ae        78781.
5 3       volpk       891 
6 3       ae        82223.
7 4       volpk       855 
8 4       ae        92938.

11.7.2 Excretion rate parameters

Parameter Description
ermax Maximum excretion rate (amount/time) within the interval
ertmax Midpoint collection time of the maximum excretion rate period
ertlst Midpoint collection time of the last measurable excretion rate

These mirror the plasma-side cmax / tmax / tlast but apply to the excretion rate (amount excreted per collection period divided by collection duration).

# Excretion rate needs each collection's duration, and PKNCA takes urine `time` as
# the collection START with `duration` extending forward — rebuild the object that way
urine_er <- urine_collections |>
  mutate(
    start_t  = c(0, 2, 6, 12)[match(time, c(2, 6, 12, 24))],
    duration = time - start_t
  ) |>
  transmute(Subject, time = start_t, conc, volume, duration)

o_conc_er <- PKNCAconc(urine_er, conc ~ time | Subject,
                       volume = "volume", duration = "duration")

er_interval <- data.frame(
  start  = 0,
  end    = 24,
  ae     = TRUE,
  ermax  = TRUE,    # maximum excretion rate
  ertmax = TRUE,    # midpoint time of the maximum-rate collection
  ertlst = TRUE     # midpoint time of the last measurable-rate collection
)

o_nca_er <- pk.nca(PKNCAdata(o_conc_er, o_dose, intervals = er_interval))
as.data.frame(o_nca_er) |>
  filter(PPTESTCD %in% c("ae", "ermax", "ertmax", "ertlst")) |>
  select(Subject, PPTESTCD, PPORRES)
# A tibble: 16 × 3
   Subject PPTESTCD PPORRES
   <fct>   <chr>      <dbl>
 1 1       ae        91844.
 2 1       ertlst       18 
 3 1       ermax     14464.
 4 1       ertmax        1 
 5 2       ae        78781.
 6 2       ertlst       18 
 7 2       ermax     13179.
 8 2       ertmax        1 
 9 3       ae        82223.
10 3       ertlst       18 
11 3       ermax     13922.
12 3       ertmax        1 
13 4       ae        92938.
14 4       ertlst       18 
15 4       ermax     16448.
16 4       ertmax        1 

11.7.3 Dose-normalized renal clearance

Dose-normalized renal clearance variants are now available in the parameter registry:

Parameter Definition
clr.last.dn clr.last / dose
clr.obs.dn clr.obs / dose
clr.pred.dn clr.pred / dose

Renal clearance divides the urine Ae by the plasma AUC — two different concentration objects — so a single pk.nca() call cannot compute clr.* directly. Compute each piece with pk.nca() and combine them, with pk.calc.dn() applying the dose normalization:

o_nca_ae_dn <- pk.nca(PKNCAdata(o_conc_urine, o_dose,
                                intervals = data.frame(start = 0, end = 24, ae = TRUE)))

clr_dn <- plasma_auc |>   # plasma pk.nca() results from the separate workflow above
  left_join(
    as.data.frame(o_nca_ae_dn) |>
      filter(PPTESTCD == "ae") |>
      select(Subject, ae = PPORRES),
    by = "Subject"
  ) |>
  mutate(
    ae          = ae / 1000,                  # native ae is in mcg (mcg/mL * mL); convert to mg to match the plasma AUC
    clr.last    = ae / auclast,
    clr.obs     = ae / aucinf.obs,
    clr.last.dn = pk.calc.dn(clr.last, 100),  # dose = 100 mg
    clr.obs.dn  = pk.calc.dn(clr.obs, 100)
  )

clr_dn
# A tibble: 4 × 8
  Subject auclast aucinf.obs    ae clr.last clr.obs clr.last.dn clr.obs.dn
  <fct>     <dbl>      <dbl> <dbl>    <dbl>   <dbl>       <dbl>      <dbl>
1 1          29.0       35.2  91.8     3.17    2.61      0.0317     0.0261
2 2          30.8       33.1  78.8     2.56    2.38      0.0256     0.0238
3 3          32.7       36.5  82.2     2.51    2.25      0.0251     0.0225
4 4          30.5       36.1  92.9     3.05    2.57      0.0305     0.0257

pkgdown reference: PKNCAconc() · PKNCAdose() · PKNCAdata() · pk.nca() · pk.calc.dn()