---
title: "Units Assignment and Conversion"
---
```{r setup, include=FALSE}
library(PKNCA)
library(dplyr)
library(ggplot2)
conflicted::conflicts_prefer(dplyr::filter, dplyr::select, .quiet = TRUE)
```
## Why units matter
By default PKNCA returns bare numbers. When you provide units, every row in
`as.data.frame()` gains columns that mirror the CDISC SDTM (Study Data
Tabulation Model) PP (PK Parameters) domain:
| Column | Meaning |
|--------|---------|
| `PPORRESU` | Original reporting unit (the unit the result is expressed in) |
| `PPSTRESU` | Standardized unit (populated when a conversion is requested) |
| `PPSTRES` | Result expressed in the standardized unit |
This makes the output submission-ready and prevents silent unit mismatches when
combining results from multiple studies or analytes. See also the package
vignette [Unit Assignment and Conversion with PKNCA](https://humanpred.github.io/pknca/articles/v07-unit-conversion.html).
---
## Assigning units at object creation
Units can be supplied directly to `PKNCAconc()` and `PKNCAdose()` as string
literals or as the name of a column in your data frame that holds the unit
string per row.
### Arguments in `PKNCAconc()`
You can list the full argument set (the unit arguments are at the end) with:
```{r}
formals(getS3method("PKNCAconc", "data.frame")) |> names()
```
The unit-related arguments are:
- `concu` — concentration unit (e.g. `"mg/L"`)
- `timeu` — time unit (e.g. `"h"`)
- `amountu` — amount unit, used for urine/fecal data (e.g. `"mg"`)
- `concu_pref` — preferred output unit for concentration (triggers conversion)
- `timeu_pref` — preferred output unit for time (triggers conversion)
- `amountu_pref` — preferred output unit for amount (triggers conversion)
### Arguments in `PKNCAdose()`
- `doseu` — dose unit (e.g. `"mg"`); like the `PKNCAconc()` unit arguments, it also accepts the name of a column in the dose data holding per-row unit strings
- `doseu_pref` — preferred output unit for dose (triggers conversion)
### Example: units as string literals
```{r}
Theoph_df <- as.data.frame(Theoph)
d_dose <- Theoph_df |>
filter(Time == 0) |>
mutate(amt = Dose * Wt)
o_conc <- PKNCAconc(Theoph_df, conc ~ Time | Subject,
concu = "mg/L", timeu = "h")
o_dose <- PKNCAdose(d_dose, amt ~ Time | Subject,
doseu = "mg")
```
### Example: units from a data column (≥ 0.12.0)
```{r}
Theoph_units <- Theoph_df |>
mutate(conc_unit = "mg/L", time_unit = "h")
o_conc_col <- PKNCAconc(Theoph_units, conc ~ Time | Subject,
concu = "conc_unit", timeu = "time_unit")
```
---
## `pknca_units_table()`
This function builds a complete mapping from every NCA parameter to its
reporting unit, derived from the four base units you supply.
In PKNCA ≥ 0.12.2, `pknca_units_table()` is an **S3 generic**. The most useful new method is `pknca_units_table.PKNCAdata()`, which extracts units directly from a fully assembled `PKNCAdata` object — useful when units are already embedded in `PKNCAconc` and `PKNCAdose`:
```{r}
# Units already embedded in the data objects
o_conc_u <- PKNCAconc(as.data.frame(Theoph) |> rename(time=Time, subject=Subject),
conc ~ time | subject, concu = "mg/L", timeu = "h")
o_dose_u <- PKNCAdose(
as.data.frame(Theoph) |>
group_by(Subject) |>
summarise(dose = Dose[1]*Wt[1], .groups="drop") |>
rename(subject=Subject) |>
mutate(time = 0),
dose ~ time | subject, route = "extravascular",
doseu = "mg"
)
o_data_u <- PKNCAdata(o_conc_u, o_dose_u)
# Extract units from the PKNCAdata object (≥ 0.12.2)
u_from_data <- pknca_units_table(o_data_u)
head(u_from_data, 6)
```
### Arguments (default method)
```{r}
args(getS3method("pknca_units_table", "default"))
```
All four of `concu`, `doseu`, `amountu`, `timeu` are required. PKNCA derives
composite units automatically (e.g. `auclast` gets `h*mg/L` from `timeu="h"`
and `concu="mg/L"`).
### Output columns
The returned data frame has exactly two columns when no preferred units are
requested:
| Column | Content |
|--------|---------|
| `PPORRESU` | Reporting unit for this parameter |
| `PPTESTCD` | Parameter code (e.g. `"cmax"`, `"auclast"`, `"tmax"`) |
When preferred units are requested, two additional columns appear:
`PPSTRESU` (target unit) and `conversion_factor`.
```{r}
u <- pknca_units_table(concu = "mg/L", timeu = "h",
doseu = "mg", amountu = "mg")
# The table covers all NCA parameters
nrow(u)
# Sample rows
u[u$PPTESTCD %in% c("cmax", "auclast", "tmax", "cl.obs", "vz.obs"), ]
```
---
## Passing the units table to `PKNCAdata()`
Pass the table via the `units` argument, reusing `u` from above. If `units` is
provided, it takes precedence: any units given directly to `PKNCAconc()` or
`PKNCAdose()` are ignored. The example below sets a deliberately different
concentration unit (`ng/mL`) on the `PKNCAconc` object to demonstrate this —
the results report `mg/L` from the table.
```{r}
# Units set on the objects are superseded by the units table passed to PKNCAdata()
o_conc2 <- PKNCAconc(Theoph_df, conc ~ Time | Subject,
concu = "ng/mL", timeu = "h")
o_dose2 <- PKNCAdose(d_dose, amt ~ Time | Subject, doseu = "mg")
o_data <- PKNCAdata(o_conc2, o_dose2, units = u) # u built in the section above
# Run NCA
res <- pk.nca(o_data)
# Units appear in the PPORRESU column: mg/L from the table,
# not the ng/mL set on the object
df <- as.data.frame(res)
stopifnot("PPORRESU" %in% names(df)) # unit column present on every row
df |>
filter(Subject == "1",
PPTESTCD %in% c("auclast", "cmax", "tmax")) |>
select(PPTESTCD, PPORRES, PPORRESU)
```
Whenever a units table is in effect, `PPORRESU` is populated for every result
row. The standardized-unit columns `PPSTRESU` and `PPSTRES` appear only when
preferred or custom conversions are requested, as the next sections show.
---
## Preferred units
Setting `concu_pref`, `timeu_pref`, `doseu_pref`, or `amountu_pref` in
`pknca_units_table()` tells PKNCA to convert results to those preferred units.
PKNCA calculates the conversion factor automatically for metric prefixes and
time unit changes.
When preferred units are active the results data frame gains:
- `PPSTRESU` — the preferred unit
- `PPSTRES` — the result in the preferred unit
```{r}
u_pref <- pknca_units_table(
concu = "mg/L", timeu = "h",
doseu = "mg", amountu = "mg",
concu_pref = "ug/L", # mg/L → ug/L (× 1000)
timeu_pref = "day" # h → day (× 1/24)
)
# Inspect what the table says for key parameters
u_pref[u_pref$PPTESTCD %in% c("cmax", "auclast", "tmax"), ]
```
```{r}
o_data_pref <- PKNCAdata(o_conc2, o_dose2, units = u_pref)
res_pref <- pk.nca(o_data_pref)
as.data.frame(res_pref) |>
filter(Subject == "1",
PPTESTCD %in% c("auclast", "cmax", "tmax")) |>
select(PPTESTCD, PPORRES, PPORRESU, PPSTRES, PPSTRESU)
```
`PPORRES`/`PPORRESU` hold the original value and unit; `PPSTRES`/`PPSTRESU`
hold the converted value and unit.
---
## Custom unit conversions
For conversions PKNCA cannot derive automatically (e.g. mass ↔ molar, or a
non-standard reporting preference), pass a `conversions` data frame to
`pknca_units_table()`. For a molar-conversion example and per-analyte unit
tables, see [the vignette's analyte section](https://humanpred.github.io/pknca/articles/v07-unit-conversion.html#how-do-i-add-different-unit-conversions-for-different-analytes).
### Required columns
| Column | Type | Meaning |
|--------|------|---------|
| `PPORRESU` | character | The original unit to match |
| `PPSTRESU` | character | The target unit |
| `conversion_factor` | numeric | Multiplier (original × factor = target). Use `NA` to ask PKNCA to compute it. |
### Example: report concentration as ug/mL instead of mg/L
`mg/L` and `ug/mL` are numerically equal (factor = 1), but differ in label.
```{r}
my_conversions <- data.frame(
PPORRESU = c("mg/L", "h*mg/L"),
PPSTRESU = c("ug/mL", "h*ug/mL"),
conversion_factor = c(1, 1)
)
u_custom <- pknca_units_table(
concu = "mg/L", timeu = "h",
doseu = "mg", amountu = "mg",
conversions = my_conversions
)
o_data_custom <- PKNCAdata(o_conc2, o_dose2, units = u_custom)
res_custom <- pk.nca(o_data_custom)
as.data.frame(res_custom) |>
filter(Subject == "1",
PPTESTCD %in% c("auclast", "cmax")) |>
select(PPTESTCD, PPORRES, PPORRESU, PPSTRES, PPSTRESU)
```
---
## Allowing partial units (≥ 0.12.0)
`pknca_units_table()` always returns a complete table, but a hand-edited or
reused units table can end up missing entries for some of the parameters an
analysis requests. By default PKNCA treats that as an error when `pk.nca()`
runs, so no partially-unitless results are produced by accident. Here the
`half.life` and `tmax` rows are removed from the table built above:
```{r}
u_partial <- u |> filter(!(PPTESTCD %in% c("half.life", "tmax")))
o_data_partial <- PKNCAdata(o_conc2, o_dose2, units = u_partial)
# The missing entries are an error when the calculation runs
tryCatch(
pk.nca(o_data_partial),
error = function(e) cat("Error:", conditionMessage(e), "\n")
)
```
Setting `allow_partial_missing_units = TRUE` converts that error to a warning
so the run completes. It can be set per-analysis through the `options` argument
of `PKNCAdata()` (as below) or globally with
`PKNCA.options(allow_partial_missing_units = TRUE)`:
```{r}
o_data_partial_ok <- PKNCAdata(
o_conc2, o_dose2, units = u_partial,
options = list(allow_partial_missing_units = TRUE)
)
res_partial <- pk.nca(o_data_partial_ok)
as.data.frame(res_partial) |>
filter(Subject == "1",
PPTESTCD %in% c("cmax", "tmax", "half.life")) |>
select(PPTESTCD, PPORRES, PPORRESU)
```
The warning above names the parameters that lack units; it is expected here and
should be reviewed, not silenced. The affected parameters (`tmax`,
`half.life`) are still calculated but reported without a unit — their
`PPORRESU` is `NA` — while parameters present in the table (`cmax`) keep
theirs.
---
## Summary
| Goal | How |
|------|-----|
| Assign units at data creation | `concu`, `timeu`, `doseu` args in `PKNCAconc()` / `PKNCAdose()` |
| Build a complete unit table | `pknca_units_table(concu, doseu, amountu, timeu)` |
| Pass units to the analysis | `PKNCAdata(..., units = u)` |
| Convert to preferred output units | Add `concu_pref`, `timeu_pref`, etc. to `pknca_units_table()` |
| Custom / non-metric conversions | `conversions` argument in `pknca_units_table()` |
| Inspect units in results | `as.data.frame(res)$PPORRESU` |
| Extract units from a PKNCAdata object (≥ 0.12.2) | `pknca_units_table(o_data)` |
| Allow some parameters without units (≥ 0.12.0) | `PKNCA.options(allow_partial_missing_units = TRUE)` |
---
::: {.callout-note icon=false appearance="minimal"}
**pkgdown reference:** [PKNCAconc()](https://humanpred.github.io/pknca/reference/PKNCAconc.html) · [PKNCAdose()](https://humanpred.github.io/pknca/reference/PKNCAdose.html) · [PKNCAdata()](https://humanpred.github.io/pknca/reference/PKNCAdata.html) · [pk.nca()](https://humanpred.github.io/pknca/reference/pk.nca.html) · [pknca_units_table()](https://humanpred.github.io/pknca/reference/pknca_units_table.html) · [PKNCA.options()](https://humanpred.github.io/pknca/reference/PKNCA.options.html)
:::