df <- as.data.frame(o_nca)
names(df)[1] "Subject" "start" "end" "PPTESTCD" "PPORRES" "PPANMETH" "exclude"
PKNCA does not build a complete submission dataset for you, but it comes close: its tidy, long-format result table maps directly onto the CDISC PP (Pharmacokinetic Parameters) domain, and as of version 0.13.0 as.data.frame(out_format = "cdisc") translates parameter codes into CDISC standard codes and adds the SDTM time point reference, method, and group identifier columns (see the CDISC output format section below). This page documents that mapping and describes how to prepare outputs for regulatory submissions. For PKNCA’s regulator-facing overview, see PKNCA – an R package for noncompartmental analysis.
The CDISC PP domain stores one parameter value per row, with the parameter code in PPTESTCD and the original result in PPORRES. With any units table, PKNCA adds the original-unit column PPORRESU; when preferred units or custom conversions are requested, it also fills its standardized-result columns, PPSTRES and PPSTRESU (the corresponding CDISC SDTM variables are PPSTRESC (character), PPSTRESN (numeric), and PPSTRESU). PKNCA’s as.data.frame() output follows exactly this long-format convention.
[1] "Subject" "start" "end" "PPTESTCD" "PPORRES" "PPANMETH" "exclude"
The columns map to CDISC PP variables as follows:
| PKNCA column | CDISC PP variable | Notes |
|---|---|---|
PPTESTCD |
PPTESTCD |
PKNCA parameter name (auclast); translated to the CDISC code (AUCLST) by out_format = "cdisc" |
PPORRES |
PPORRES |
Original numeric result (character in CDISC; numeric here) |
PPANMETH |
PPANMETH |
Analysis method (≥ 0.13.0): imputation and the AUC and half-life methods used |
start |
— | Interval start, relative to the first dose; out_format = "cdisc" adds PPSTINT relative to the dose that starts the interval |
end |
— | Interval end, relative to the first dose; out_format = "cdisc" adds PPENINT |
exclude |
PPSTAT / PPREASND (or a supplemental qualifier) |
Non-NA string gives the exclusion reason |
Grouping columns (Subject, etc.) |
USUBJID, PPCAT |
Pass through unchanged |
The PPORRESU column is added whenever the data have units: either unit arguments (concu, timeu, doseu, …) given to PKNCAconc() and PKNCAdose(), from which PKNCAdata() builds the units table automatically (≥ 0.13.0), or a table from pknca_units_table() passed to PKNCAdata(). PPSTRES and PPSTRESU are added only when the units table requests preferred units (concu_pref, timeu_pref, …) or custom conversions. Without units, PPORRES carries the raw numeric value and unit derivation must be done externally. For the full units workflow, see the Units chapter and Unit Assignment and Conversion with PKNCA.
# A tibble: 10 × 7
Subject start end PPTESTCD PPORRES PPANMETH exclude
<ord> <dbl> <dbl> <chr> <dbl> <chr> <chr>
1 1 0 Inf auclast 147. Imputation: start_predose… <NA>
2 1 0 Inf cmax 10.5 Imputation: start_predose… <NA>
3 1 0 Inf tmax 1.12 Imputation: start_predose… <NA>
4 1 0 Inf tlast 24.4 Imputation: start_predose… <NA>
5 1 0 Inf clast.obs 3.28 Imputation: start_predose… <NA>
6 1 0 Inf tlag 0 Imputation: start_predose… <NA>
7 1 0 Inf count_conc 11 Imputation: start_predose… <NA>
8 1 0 Inf lambda.z 0.0485 Imputation: start_predose… <NA>
9 1 0 Inf r.squared 1.000 Imputation: start_predose… <NA>
10 1 0 Inf adj.r.squared 1.000 Imputation: start_predose… <NA>
Whatever grouping structure you define in PKNCAconc() — subject ID, analyte, treatment, period — those column names carry through unchanged into as.data.frame(o_nca). They become the basis for USUBJID, PPCAT (analyte/treatment), and other PP domain identifiers.
# A tibble: 6 × 5
Subject start end PPTESTCD PPORRES
<ord> <dbl> <dbl> <chr> <dbl>
1 1 0 Inf auclast 147.
2 1 0 Inf cmax 10.5
3 1 0 Inf tmax 1.12
4 1 0 Inf tlast 24.4
5 1 0 Inf clast.obs 3.28
6 1 0 Inf tlag 0
For multi-analyte or multi-period studies, add those variables to the formula in PKNCAconc():
All grouping variables will appear as columns in the long-format result, ready to be mapped to their CDISC equivalents.
as.data.frame(o_nca) returns the individual (subject-level) long-format table. This is the primary output for PP domain construction. The PPTESTCD and PPORRES column names are already the CDISC variable names, so no renaming is needed; the PPTESTCD values are PKNCA’s parameter names until they are translated with out_format = "cdisc" (next section):
# A tibble: 6 × 8
Subject start end PPTESTCD PPORRES PPANMETH exclude PPSTRESN
<ord> <dbl> <dbl> <chr> <dbl> <chr> <chr> <dbl>
1 1 0 Inf auclast 147. Imputation: start_pred… <NA> 147.
2 1 0 Inf cmax 10.5 Imputation: start_pred… <NA> 10.5
3 1 0 Inf tmax 1.12 Imputation: start_pred… <NA> 1.12
4 1 0 Inf tlast 24.4 Imputation: start_pred… <NA> 24.4
5 1 0 Inf clast.obs 3.28 Imputation: start_pred… <NA> 3.28
6 1 0 Inf tlag 0 Imputation: start_pred… <NA> 0
The result is in tidy long format: one row per subject per parameter per interval, which is the structure expected by the CDISC PP domain.
The long-format extraction above keeps PKNCA’s own parameter codes in PPTESTCD — auclast, cmax, lambda.z, and so on. For submission datasets, as.data.frame(out_format = "cdisc") translates them and adds the SDTM PP columns that PKNCA can fill:
PPTESTCD holds the CDISC submission code (auclast becomes AUCLST, clast.obs becomes CLST, lambda.z becomes LAMZ) and a new PPTEST column holds the standard parameter name.pknca_parameter column, placed just before PPTESTCD, keeps the original PKNCA parameter name.PPSTINT, PPENINT, PPTPTREF, and (for date-time data) PPRFTDTC give every row its time point reference.PPGRPID gives the record group identifier. [1] "Subject" "start" "end" "pknca_parameter"
[5] "PPTESTCD" "PPTEST" "PPORRES" "PPANMETH"
[9] "exclude" "PPSTINT" "PPENINT" "PPTPTREF"
[13] "PPGRPID"
Subject pknca_parameter PPTESTCD PPTEST PPORRES
1 1 auclast AUCLST AUC to Last Nonzero Conc 147.234749
2 1 cmax CMAX Max Conc 10.500000
3 1 tmax TMAX Time of CMAX Observation 1.120000
4 1 tlast TLST Time of Last Nonzero Conc 24.370000
5 1 clast.obs CLST Last Nonzero Conc 3.280000
6 1 tlag TLAG Time Until First Nonzero Conc 0.000000
7 1 count_conc CNTCONC Concentration count 11.000000
8 1 lambda.z LAMZ Lambda z 0.048457
pknca_parameter columnThe translation is many-to-one. CDISC has one “AUC from T1 to T2” code regardless of how the region after Tlast is handled, so aucint.last and aucint.inf.obs both become AUCINT. The pknca_parameter column is then the only column that identifies which PKNCA calculation produced a row, and PPANMETH records the method. The column name is lowercase so that it cannot be mistaken for an SDTM PP variable; drop it before submission.
pknca_parameter PPTESTCD PPORRES
1 aucint.last AUCINT 147.2347
2 aucint.inf.obs AUCINT 193.3842
PPANMETH
1 AUC: lin up/log down. Interpolation: dose-aware. Extrapolation: AUClast
2 AUC: lin up/log down. Interpolation: dose-aware. Extrapolation: half-life
Route-dependent parameters resolve to the code for the route recorded in the dose data: cl.obs is CLFO (“Total CL Obs by F”) after an extravascular dose and CLO (“Total CL Obs”) after an intravascular one. The same applies to the volume and MRT parameters.
pknca_cdisc_codes() (≥ 0.13.0) lists every registered code, one row per route or dense/sparse variant, generated from the parameter registry at call time (so it includes parameters registered by other packages):
[1] 247
parameter tier variant pptestcd_cdisc pptest_cdisc
1 auclast common single AUCLST AUC to Last Nonzero Conc
2 aucint.last common single AUCINT AUC from T1 to T2
3 count_conc common single CNTCONC Concentration count
4 aucint.inf.obs common single AUCINT AUC from T1 to T2
5 cl.obs common extravascular CLFO Total CL Obs by F
6 cl.obs common intravascular CLO Total CL Obs
in_ct
1 TRUE
2 TRUE
3 FALSE
4 TRUE
5 TRUE
6 TRUE
The in_ct column says whether each code is a term of the CDISC PKPARMCD codelist. It is checked against the cdiscdata package (a suggested dependency, ≥ 0.2.0); when cdiscdata is not installed, in_ct is NA for every row, with a message. Every "common" tier parameter has a real PKPARMCD code except count_conc, sparse_auc_se, and cl.int.inf.obs, which have no CDISC PP code; a parameter with no CDISC equivalent keeps a sponsor-defined code.
PPANMETH)Every pk.nca() result has a PPANMETH column (≥ 0.13.0) describing the analysis methods used for that parameter: the imputation, the AUC integration method, and, for aucint and aumcint parameters, whether the calculation was dose-aware and how the region after Tlast was handled (in the example above, Extrapolation: AUClast for aucint.last, which stops at Tlast, and Extrapolation: half-life for aucint.inf.obs). A parameter with no method of its own, such as cmax, shows only the imputation, or nothing when no imputation was used. When a calculation function sets no method at run time, PPANMETH falls back to the parameter’s registered formula_note.
Every row of the CDISC output, not only interval-based parameters, gets PPSTINT and PPENINT (the interval start and end as ISO 8601 durations, such as "PT0H" and "PT24H") relative to the reference named in PPTPTREF. The reference is the last dose at or before the interval start ("LAST DOSE PRIOR TO INTERVAL"), or the first concentration for a subject without a dose ("FIRST OBSERVATION"). PPENINT is NA for an interval to infinity, as in this single-dose analysis:
Subject start end PPTESTCD PPSTINT PPENINT PPTPTREF
1 1 0 Inf AUCLST PT0H <NA> LAST DOSE PRIOR TO INTERVAL
2 1 0 Inf CMAX PT0H <NA> LAST DOSE PRIOR TO INTERVAL
3 1 0 Inf TMAX PT0H <NA> LAST DOSE PRIOR TO INTERVAL
When the concentration and dose times are date-times, PPRFTDTC gives the reference’s date-time, so it no longer has to be derived outside PKNCA. The Date-Time Data and Time References chapter shows these columns for a multiple-dose study with date-time data.
PPGRPID)PPGRPID identifies the rows of one interval: "<prefix1><value1>.<prefix2><value2>.I<nn>", built from the grouping columns named in grpid_cols and the number of the row’s interval within each combination of the subject, the analyte, and those columns. Without grpid_cols it is just the interval number ("I01" above). grpid_cols is a named character vector of PKNCAconc() grouping columns (other than the subject and analyte), each with the text written before its value; grpid_numeric names the columns whose values are whole numbers, such as the period, to be written as that number. Both can be given to PKNCAdata(), as the default for every output of one analysis, or to as.data.frame().
For a two-period crossover (built here by repeating the Theoph data as a second period), with two intervals per period:
d_conc_xover <- bind_rows(
as.data.frame(Theoph) |> mutate(Period = 1),
as.data.frame(Theoph) |> mutate(Period = 2)
)
d_dose_xover <- d_conc_xover |>
group_by(Period, Subject) |>
summarise(Dose = Dose[1] * Wt[1], Time = 0, .groups = "drop")
o_data_xover <- PKNCAdata(
PKNCAconc(d_conc_xover, conc ~ Time | Period + Subject),
PKNCAdose(d_dose_xover, Dose ~ Time | Period + Subject),
intervals = data.frame(start = 0, end = c(24, Inf), auclast = TRUE),
grpid_cols = c(Period = "P"),
grpid_numeric = "Period"
)
as.data.frame(pk.nca(o_data_xover), out_format = "cdisc") |>
filter(Subject == "1") |>
select(Period, Subject, start, end, PPTESTCD, PPSTINT, PPENINT, PPGRPID) Period Subject start end PPTESTCD PPSTINT PPENINT PPGRPID
1 1 1 0 24 AUCLST PT0H PT24H P1.I01
2 1 1 0 Inf AUCLST PT0H <NA> P1.I02
3 2 1 0 24 AUCLST PT0H PT24H P2.I01
4 2 1 0 Inf AUCLST PT0H <NA> P2.I02
Within each subject and period, the intervals are numbered from 1 by start and then end, and every parameter of one interval shares the number. The number is written with at least two digits (more when needed) so that the text sorts in time order. grpid_cols = character() in as.data.frame() gives the interval-only identifier regardless of the PKNCAdata() default.
Custom parameters registered with add.interval.col() declare their CDISC codes through its pptestcd_cdisc and pptest_cdisc arguments. Each is a character string; a list with one element named route holding a named list of codes per route (for route-dependent parameters such as CL, VZ, MRT, and VSS); or a list of exactly the elements dense and sparse. A parameter without a declared code keeps its PKNCA name in the CDISC output. See the Writing Custom Parameter Functions chapter.
exclude columnThe exclude column records whether a result was flagged for exclusion and why. When exclude is NA, the result is valid. When it is a non-NA character string, it contains a human-readable exclusion reason.
TRUE
240
# A tibble: 0 × 7
# ℹ 7 variables: Subject <ord>, start <dbl>, end <dbl>, PPTESTCD <chr>,
# PPORRES <dbl>, PPANMETH <chr>, exclude <chr>
Exclusions are set programmatically via the exclude_nca_* family of functions (e.g. exclude_nca_max.aucinf.pext(), exclude_nca_min.hl.r.squared()), or manually via exclude(). The rule functions supply their own exclusion reason text (e.g. exclude_nca_min.hl.r.squared(0.9) records r.squared < 0.9), so no reason= argument is needed. For regulatory submissions, include all rows in the PP domain — both included and excluded. Exclusion is typically represented via PPSTAT/PPREASND (status and reason not done) or a supplemental qualifier; PKNCA’s exclude column provides the source text for whichever convention your submission uses. Exclusion workflows are covered in the Post-Processing chapter and vignette, and data-quality rules such as Tmax coverage and missing samples in the Data Quality Checks chapter.
# Apply standard exclusion rules and inspect excluded rows
o_nca_excl <- o_nca |>
exclude(FUN = exclude_nca_min.hl.r.squared(0.9)) |>
exclude(FUN = exclude_nca_max.aucinf.pext(20))
df_excl <- as.data.frame(o_nca_excl)
df_excl |> filter(!is.na(exclude)) |> select(Subject, PPTESTCD, PPORRES, exclude) |> head()# A tibble: 3 × 4
Subject PPTESTCD PPORRES exclude
<ord> <chr> <dbl> <chr>
1 1 aucinf.obs 215. aucpext > 20
2 1 aucpext.obs 31.5 aucpext > 20
3 1 cl.obs 1.49 aucpext > 20
summary(o_nca) returns a wide-format table suitable for clinical study reports. Each row is a group/interval and each parameter gets a column, with cells formatted as “point [spread]” (geometric mean [CV%] by default):
start end N auclast cmax tmax tlag
0 Inf 12 98.7 [22.5] 8.65 [17.0] 1.14 [0.630, 3.55] 0.000 [0.000, 0.000]
count_conc half.life aucinf.obs aucpext.obs cl.obs
11.0 [11.0, 11.0] 8.18 [2.12] 115 [28.4] 13.8 [6.34] 2.74 [27.9]
Caption: auclast, cmax, aucinf.obs, cl.obs: geometric mean and geometric coefficient of variation; tmax, tlag, count_conc: median and range; half.life, aucpext.obs: arithmetic mean and standard deviation; N: number of subjects
The summary uses the PKNCA.set.summary() configuration for each parameter type. You can customize which statistics are reported and their formatting by calling PKNCA.set.summary() before calling summary().
For regulatory analyses, the PKNCA option set used during the run must be documented. PKNCA.options() with no arguments returns the full current option set:
[1] "lin up/log down"
$first
[1] "keep"
$middle
[1] "drop"
$last
[1] "keep"
Set any non-default options at the top of the analysis script, before any data is processed:
For full reproducibility, record the session environment alongside the option snapshot:
R version 4.6.1 (2026-06-24)
Platform: x86_64-pc-linux-gnu
Running under: Ubuntu 24.04.5 LTS
Matrix products: default
BLAS: /usr/lib/x86_64-linux-gnu/openblas-pthread/libblas.so.3
LAPACK: /usr/lib/x86_64-linux-gnu/openblas-pthread/libopenblasp-r0.3.26.so; LAPACK version 3.12.0
locale:
[1] LC_CTYPE=C.UTF-8 LC_NUMERIC=C LC_TIME=C.UTF-8
[4] LC_COLLATE=C.UTF-8 LC_MONETARY=C.UTF-8 LC_MESSAGES=C.UTF-8
[7] LC_PAPER=C.UTF-8 LC_NAME=C LC_ADDRESS=C
[10] LC_TELEPHONE=C LC_MEASUREMENT=C.UTF-8 LC_IDENTIFICATION=C
time zone: UTC
tzcode source: system (glibc)
attached base packages:
[1] stats graphics grDevices utils datasets methods base
other attached packages:
[1] dplyr_1.2.1 PKNCA_0.12.1.9002
loaded via a namespace (and not attached):
[1] jsonlite_2.0.0 compiler_4.6.1 tidyselect_1.2.1 Rcpp_1.1.2
[5] tidyr_1.3.2 yaml_2.3.12 fastmap_1.2.0 lattice_0.22-9
[9] R6_2.6.1 generics_0.1.4 knitr_1.52 htmlwidgets_1.6.4
[13] backports_1.5.1 conflicted_1.2.0 checkmate_2.3.4 tibble_3.3.1
[17] units_1.0-1 pillar_1.11.1 rlang_1.3.0 utf8_1.2.6
[21] cachem_1.1.0 cdiscdata_0.2.0 xfun_0.62 otel_0.2.0
[25] memoise_2.0.1 cli_3.6.6 withr_3.0.3 magrittr_2.0.5
[29] digest_0.6.39 grid_4.6.1 lifecycle_1.0.5 nlme_3.1-169
[33] vctrs_0.7.3 evaluate_1.0.5 glue_1.8.1 rmarkdown_2.32
[37] purrr_1.2.2 tools_4.6.1 pkgconfig_2.0.3 htmltools_0.5.9
For validated environments, use renv to lock package versions:
Together, PKNCA.options(), sessionInfo(), and renv provide the three layers of reproducibility documentation expected for regulatory NCA submissions.
PKNCA objects carry a provenance record (a hash of the object, the creation time, and the R and PKNCA versions), and checkProvenance() confirms that an object is unchanged since PKNCA created it:
A PKNCAresults object changed after the run by filter(), mutate(), group_by(), ungroup(), the joins, exclude(), or normalize() no longer vouches for itself (≥ 0.13.0): its provenance becomes a marker such as "filtered from <hash>" and checkProvenance() returns FALSE. A call that leaves the object identical keeps its provenance.
For a submission, keep the unmodified PKNCAresults object with the analysis outputs, so that the reported numbers can be traced to an intact run, and record the exclusions applied to it in the analysis script.
pkgdown reference: PKNCAconc() · PKNCAdose() · PKNCAdata() · pk.nca() · as.data.frame.PKNCAresults() · pknca_cdisc_codes() · add.interval.col() · checkProvenance() · exclude() · exclude_nca_*() · summary() · PKNCA.options()
---
title: "Regulatory and CDISC Context"
---
```{r setup, include=FALSE}
library(PKNCA)
library(dplyr)
conflicted::conflicts_prefer(dplyr::filter, dplyr::select, .quiet = TRUE)
```
PKNCA does not build a complete submission dataset for you, but it comes close: its tidy, long-format result table maps directly onto the CDISC PP (Pharmacokinetic Parameters) domain, and as of version 0.13.0 `as.data.frame(out_format = "cdisc")` translates parameter codes into CDISC standard codes and adds the SDTM time point reference, method, and group identifier columns (see the CDISC output format section below). This page documents that mapping and describes how to prepare outputs for regulatory submissions. For PKNCA's regulator-facing overview, see [PKNCA – an R package for noncompartmental analysis](https://humanpred.github.io/pknca/articles/v31-FDA-introduction.html).
```{r build-result, include=FALSE}
data("Theoph")
o_conc <- PKNCAconc(Theoph, conc ~ Time | Subject)
o_dose <- PKNCAdose(
Theoph |> group_by(Subject) |>
summarise(Dose = Dose[1] * Wt[1], .groups = "drop") |>
mutate(Time = 0),
Dose ~ Time | Subject
)
o_data <- PKNCAdata(o_conc, o_dose)
o_nca <- pk.nca(o_data)
```
---
## CDISC PP domain alignment
The CDISC PP domain stores one parameter value per row, with the parameter code in `PPTESTCD` and the original result in `PPORRES`. With any units table, PKNCA adds the original-unit column `PPORRESU`; when preferred units or custom conversions are requested, it also fills its standardized-result columns, `PPSTRES` and `PPSTRESU` (the corresponding CDISC SDTM variables are `PPSTRESC` (character), `PPSTRESN` (numeric), and `PPSTRESU`). PKNCA's `as.data.frame()` output follows exactly this long-format convention.
```{r result-columns}
df <- as.data.frame(o_nca)
names(df)
```
The columns map to CDISC PP variables as follows:
| PKNCA column | CDISC PP variable | Notes |
|--------------|-------------------|--------------------------------------------------------|
| `PPTESTCD` | `PPTESTCD` | PKNCA parameter name (`auclast`); translated to the CDISC code (`AUCLST`) by `out_format = "cdisc"` |
| `PPORRES` | `PPORRES` | Original numeric result (character in CDISC; numeric here) |
| `PPANMETH` | `PPANMETH` | Analysis method (≥ 0.13.0): imputation and the AUC and half-life methods used |
| `start` | — | Interval start, relative to the first dose; `out_format = "cdisc"` adds `PPSTINT` relative to the dose that starts the interval |
| `end` | — | Interval end, relative to the first dose; `out_format = "cdisc"` adds `PPENINT` |
| `exclude` | `PPSTAT` / `PPREASND` (or a supplemental qualifier) | Non-`NA` string gives the exclusion reason |
| Grouping columns (`Subject`, etc.) | `USUBJID`, `PPCAT` | Pass through unchanged |
The `PPORRESU` column is added whenever the data have units: either unit arguments (`concu`, `timeu`, `doseu`, ...) given to `PKNCAconc()` and `PKNCAdose()`, from which `PKNCAdata()` builds the units table automatically (≥ 0.13.0), or a table from `pknca_units_table()` passed to `PKNCAdata()`. `PPSTRES` and `PPSTRESU` are added only when the units table requests preferred units (`concu_pref`, `timeu_pref`, ...) or custom conversions. Without units, `PPORRES` carries the raw numeric value and unit derivation must be done externally. For the full units workflow, see the [Units chapter](units.qmd) and [Unit Assignment and Conversion with PKNCA](https://humanpred.github.io/pknca/articles/v07-unit-conversion.html).
```{r result-head}
head(df, 10)
```
---
## Grouping columns as CDISC identifiers
Whatever grouping structure you define in `PKNCAconc()` — subject ID, analyte, treatment, period — those column names carry through unchanged into `as.data.frame(o_nca)`. They become the basis for `USUBJID`, `PPCAT` (analyte/treatment), and other PP domain identifiers.
```{r grouping-cols}
# Grouping columns appear alongside PPTESTCD and PPORRES
df |>
select(Subject, start, end, PPTESTCD, PPORRES) |>
head(6)
```
For multi-analyte or multi-period studies, add those variables to the formula in `PKNCAconc()`:
```{r multi-group-example, eval=FALSE}
# Example: analyte + period grouping
d_conc_multi <- PKNCAconc(
my_data,
conc ~ Time | USUBJID / ANALYTE / PERIOD
)
```
All grouping variables will appear as columns in the long-format result, ready to be mapped to their CDISC equivalents.
---
## Extracting results for submission
`as.data.frame(o_nca)` returns the individual (subject-level) long-format table. This is the primary output for PP domain construction. The `PPTESTCD` and `PPORRES` column names are already the CDISC variable names, so no renaming is needed; the `PPTESTCD` values are PKNCA's parameter names until they are translated with `out_format = "cdisc"` (next section):
```{r extract-individual}
pp_domain_draft <- as.data.frame(o_nca)
# PPSTRESN is typically the same as PPORRES for numeric results
pp_cdisc <- pp_domain_draft |>
mutate(PPSTRESN = PPORRES)
head(pp_cdisc)
```
The result is in tidy long format: one row per subject per parameter per interval, which is the structure expected by the CDISC PP domain.
---
## CDISC output format (≥ 0.13.0)
The long-format extraction above keeps PKNCA's own parameter codes in `PPTESTCD` — `auclast`, `cmax`, `lambda.z`, and so on. For submission datasets, `as.data.frame(out_format = "cdisc")` translates them and adds the SDTM PP columns that PKNCA can fill:
- `PPTESTCD` holds the CDISC submission code (`auclast` becomes `AUCLST`, `clast.obs` becomes `CLST`, `lambda.z` becomes `LAMZ`) and a new `PPTEST` column holds the standard parameter name.
- A new `pknca_parameter` column, placed just before `PPTESTCD`, keeps the original PKNCA parameter name.
- `PPSTINT`, `PPENINT`, `PPTPTREF`, and (for date-time data) `PPRFTDTC` give every row its time point reference.
- `PPGRPID` gives the record group identifier.
```{r cdisc-format}
df_cdisc <- as.data.frame(o_nca, out_format = "cdisc")
names(df_cdisc)
df_cdisc |>
select(Subject, pknca_parameter, PPTESTCD, PPTEST, PPORRES) |>
head(8)
```
### Parameter codes and the `pknca_parameter` column
The translation is many-to-one. CDISC has one "AUC from T1 to T2" code regardless of how the region after Tlast is handled, so `aucint.last` and `aucint.inf.obs` both become `AUCINT`. The `pknca_parameter` column is then the only column that identifies which PKNCA calculation produced a row, and `PPANMETH` records the method. The column name is lowercase so that it cannot be mistaken for an SDTM PP variable; drop it before submission.
```{r cdisc-many-to-one}
o_nca_aucint <- pk.nca(PKNCAdata(
o_conc, o_dose,
intervals = data.frame(start = 0, end = 48, aucint.last = TRUE, aucint.inf.obs = TRUE)
))
as.data.frame(o_nca_aucint, out_format = "cdisc", filter_requested = TRUE) |>
filter(Subject == "1") |>
select(pknca_parameter, PPTESTCD, PPORRES, PPANMETH)
```
Route-dependent parameters resolve to the code for the route recorded in the dose data: `cl.obs` is `CLFO` ("Total CL Obs by F") after an extravascular dose and `CLO` ("Total CL Obs") after an intravascular one. The same applies to the volume and MRT parameters.
`pknca_cdisc_codes()` (≥ 0.13.0) lists every registered code, one row per route or dense/sparse variant, generated from the parameter registry at call time (so it includes parameters registered by other packages):
```{r cdisc-codes}
d_codes <- pknca_cdisc_codes()
nrow(d_codes)
d_codes |>
filter(parameter %in% c("auclast", "aucint.last", "aucint.inf.obs", "cl.obs", "count_conc"))
```
The `in_ct` column says whether each code is a term of the CDISC PKPARMCD codelist. It is checked against the cdiscdata package (a suggested dependency, ≥ 0.2.0); when cdiscdata is not installed, `in_ct` is `NA` for every row, with a message. Every `"common"` tier parameter has a real PKPARMCD code except `count_conc`, `sparse_auc_se`, and `cl.int.inf.obs`, which have no CDISC PP code; a parameter with no CDISC equivalent keeps a sponsor-defined code.
### The method column (`PPANMETH`)
Every `pk.nca()` result has a `PPANMETH` column (≥ 0.13.0) describing the analysis methods used for that parameter: the imputation, the AUC integration method, and, for `aucint` and `aumcint` parameters, whether the calculation was dose-aware and how the region after Tlast was handled (in the example above, `Extrapolation: AUClast` for `aucint.last`, which stops at Tlast, and `Extrapolation: half-life` for `aucint.inf.obs`). A parameter with no method of its own, such as `cmax`, shows only the imputation, or nothing when no imputation was used. When a calculation function sets no method at run time, `PPANMETH` falls back to the parameter's registered `formula_note`.
### Time point references
Every row of the CDISC output, not only interval-based parameters, gets `PPSTINT` and `PPENINT` (the interval start and end as ISO 8601 durations, such as `"PT0H"` and `"PT24H"`) relative to the reference named in `PPTPTREF`. The reference is the last dose at or before the interval start (`"LAST DOSE PRIOR TO INTERVAL"`), or the first concentration for a subject without a dose (`"FIRST OBSERVATION"`). `PPENINT` is `NA` for an interval to infinity, as in this single-dose analysis:
```{r cdisc-time}
df_cdisc |>
select(Subject, start, end, PPTESTCD, PPSTINT, PPENINT, PPTPTREF) |>
head(3)
```
When the concentration and dose times are date-times, `PPRFTDTC` gives the reference's date-time, so it no longer has to be derived outside PKNCA. The [Date-Time Data and Time References chapter](datetime.qmd) shows these columns for a multiple-dose study with date-time data.
### Record group identifier (`PPGRPID`)
`PPGRPID` identifies the rows of one interval: `"<prefix1><value1>.<prefix2><value2>.I<nn>"`, built from the grouping columns named in `grpid_cols` and the number of the row's interval within each combination of the subject, the analyte, and those columns. Without `grpid_cols` it is just the interval number (`"I01"` above). `grpid_cols` is a named character vector of `PKNCAconc()` grouping columns (other than the subject and analyte), each with the text written before its value; `grpid_numeric` names the columns whose values are whole numbers, such as the period, to be written as that number. Both can be given to `PKNCAdata()`, as the default for every output of one analysis, or to `as.data.frame()`.
For a two-period crossover (built here by repeating the Theoph data as a second period), with two intervals per period:
```{r cdisc-grpid}
d_conc_xover <- bind_rows(
as.data.frame(Theoph) |> mutate(Period = 1),
as.data.frame(Theoph) |> mutate(Period = 2)
)
d_dose_xover <- d_conc_xover |>
group_by(Period, Subject) |>
summarise(Dose = Dose[1] * Wt[1], Time = 0, .groups = "drop")
o_data_xover <- PKNCAdata(
PKNCAconc(d_conc_xover, conc ~ Time | Period + Subject),
PKNCAdose(d_dose_xover, Dose ~ Time | Period + Subject),
intervals = data.frame(start = 0, end = c(24, Inf), auclast = TRUE),
grpid_cols = c(Period = "P"),
grpid_numeric = "Period"
)
as.data.frame(pk.nca(o_data_xover), out_format = "cdisc") |>
filter(Subject == "1") |>
select(Period, Subject, start, end, PPTESTCD, PPSTINT, PPENINT, PPGRPID)
```
Within each subject and period, the intervals are numbered from 1 by start and then end, and every parameter of one interval shares the number. The number is written with at least two digits (more when needed) so that the text sorts in time order. `grpid_cols = character()` in `as.data.frame()` gives the interval-only identifier regardless of the `PKNCAdata()` default.
### Codes for custom parameters
Custom parameters registered with `add.interval.col()` declare their CDISC codes through its `pptestcd_cdisc` and `pptest_cdisc` arguments. Each is a character string; a list with one element named `route` holding a named list of codes per route (for route-dependent parameters such as CL, VZ, MRT, and VSS); or a list of exactly the elements `dense` and `sparse`. A parameter without a declared code keeps its PKNCA name in the CDISC output. See the [Writing Custom Parameter Functions chapter](custom-parameters.qmd).
---
## The `exclude` column
The `exclude` column records whether a result was flagged for exclusion and why. When `exclude` is `NA`, the result is valid. When it is a non-`NA` character string, it contains a human-readable exclusion reason.
```{r exclude-col}
# How many rows are flagged for exclusion?
table(is.na(df$exclude))
# Show any excluded rows
df |> filter(!is.na(exclude)) |> head()
```
Exclusions are set programmatically via the `exclude_nca_*` family of functions (e.g. `exclude_nca_max.aucinf.pext()`, `exclude_nca_min.hl.r.squared()`), or manually via `exclude()`. The rule functions supply their own exclusion reason text (e.g. `exclude_nca_min.hl.r.squared(0.9)` records `r.squared < 0.9`), so no `reason=` argument is needed. For regulatory submissions, include all rows in the PP domain — both included and excluded. Exclusion is typically represented via `PPSTAT`/`PPREASND` (status and reason not done) or a supplemental qualifier; PKNCA's `exclude` column provides the source text for whichever convention your submission uses. Exclusion workflows are covered in the [Post-Processing chapter](postprocessing.qmd) and [vignette](https://humanpred.github.io/pknca/articles/v07-post-processing.html), and data-quality rules such as Tmax coverage and missing samples in the [Data Quality Checks chapter](data-quality.qmd).
```{r exclude-nca-example}
# Apply standard exclusion rules and inspect excluded rows
o_nca_excl <- o_nca |>
exclude(FUN = exclude_nca_min.hl.r.squared(0.9)) |>
exclude(FUN = exclude_nca_max.aucinf.pext(20))
df_excl <- as.data.frame(o_nca_excl)
df_excl |> filter(!is.na(exclude)) |> select(Subject, PPTESTCD, PPORRES, exclude) |> head()
```
---
## Summary table for reports
`summary(o_nca)` returns a wide-format table suitable for clinical study reports. Each row is a group/interval and each parameter gets a column, with cells formatted as "point [spread]" (geometric mean [CV%] by default):
```{r summary-table}
summary(o_nca)
```
The summary uses the `PKNCA.set.summary()` configuration for each parameter type. You can customize which statistics are reported and their formatting by calling `PKNCA.set.summary()` before calling `summary()`.
---
## Reproducibility: pinning options and environment
For regulatory analyses, the PKNCA option set used during the run must be documented. `PKNCA.options()` with no arguments returns the full current option set:
```{r options-snapshot}
# Capture the full option set at the start of an analysis script
analysis_options <- PKNCA.options()
# Confirm a key option
analysis_options$auc.method # trapezoidal method
analysis_options$conc.blq # BLQ handling
```
Set any non-default options at the top of the analysis script, before any data is processed:
```{r set-options, eval=FALSE}
# Example: force the plain linear trapezoidal rule everywhere
PKNCA.options(auc.method = "linear")
```
For full reproducibility, record the session environment alongside the option snapshot:
```{r session-info}
# Save at the end of the analysis script
sessionInfo()
```
For validated environments, use `renv` to lock package versions:
```{r renv-example, eval=FALSE}
# Lock the current environment
renv::snapshot()
# Restore a locked environment on a validation system
renv::restore()
```
Together, `PKNCA.options()`, `sessionInfo()`, and `renv` provide the three layers of reproducibility documentation expected for regulatory NCA submissions.
### Provenance of results
PKNCA objects carry a provenance record (a hash of the object, the creation time, and the R and PKNCA versions), and `checkProvenance()` confirms that an object is unchanged since PKNCA created it:
```{r provenance}
checkProvenance(o_nca)
```
A `PKNCAresults` object changed after the run by `filter()`, `mutate()`, `group_by()`, `ungroup()`, the joins, `exclude()`, or `normalize()` no longer vouches for itself (≥ 0.13.0): its provenance becomes a marker such as `"filtered from <hash>"` and `checkProvenance()` returns `FALSE`. A call that leaves the object identical keeps its provenance.
```{r provenance-modified}
checkProvenance(o_nca_excl)
```
For a submission, keep the unmodified `PKNCAresults` object with the analysis outputs, so that the reported numbers can be traced to an intact run, and record the exclusions applied to it in the analysis script.
---
::: {.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) · [as.data.frame.PKNCAresults()](https://humanpred.github.io/pknca/reference/as.data.frame.PKNCAresults.html) · [pknca_cdisc_codes()](https://humanpred.github.io/pknca/reference/pknca_cdisc_codes.html) · [add.interval.col()](https://humanpred.github.io/pknca/reference/add.interval.col.html) · [checkProvenance()](https://humanpred.github.io/pknca/reference/checkProvenance.html) · [exclude()](https://humanpred.github.io/pknca/reference/exclude.html) · [exclude_nca_*()](https://humanpred.github.io/pknca/reference/exclude_nca.html) · [summary()](https://humanpred.github.io/pknca/reference/summary.PKNCAresults.html) · [PKNCA.options()](https://humanpred.github.io/pknca/reference/PKNCA.options.html)
:::