19  Regulatory and CDISC Context

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.12.2 as.data.frame() can translate parameter codes into CDISC standard codes directly (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.


19.1 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. When a units table is provided, PKNCA additionally fills its own standardized-result column, PPSTRES; the corresponding CDISC SDTM variables are PPSTRESC (character) and PPSTRESN (numeric). PKNCA’s as.data.frame() output follows exactly this long-format convention.

df <- as.data.frame(o_nca)
names(df)
[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 Parameter short name; identical names by convention
PPORRES PPORRES Original numeric result (character in CDISC; numeric here)
start Interval start time; useful for PPRFTDTC derivation
end Interval end time
exclude PPSTAT / PPREASND (or a supplemental qualifier) Non-NA string gives the exclusion reason
Grouping columns (Subject, etc.) USUBJID, PPCAT Pass through unchanged

Units (PPORRESU, PPSTRESU) are available when you configure a units table via pknca_units_table() and pass it to PKNCAdata(). Without a units table, 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.

head(df, 10)
# A tibble: 10 × 7
   Subject start   end PPTESTCD            PPORRES PPANMETH              exclude
   <ord>   <dbl> <dbl> <chr>                 <dbl> <chr>                 <chr>  
 1 1           0    24 auclast             92.4    "AUC: lin up/log dow… <NA>   
 2 1           0   Inf cmax                10.5    ""                    <NA>   
 3 1           0   Inf tmax                 1.12   ""                    <NA>   
 4 1           0   Inf tlast               24.4    ""                    <NA>   
 5 1           0   Inf clast.obs            3.28   ""                    <NA>   
 6 1           0   Inf lambda.z             0.0485 ""                    <NA>   
 7 1           0   Inf r.squared            1.000  ""                    <NA>   
 8 1           0   Inf adj.r.squared        1.000  ""                    <NA>   
 9 1           0   Inf lambda.z.corrxy     -1.000  ""                    <NA>   
10 1           0   Inf lambda.z.time.first  9.05   ""                    <NA>   

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

# Grouping columns appear alongside PPTESTCD and PPORRES
df |>
  select(Subject, start, end, PPTESTCD, PPORRES) |>
  head(6)
# A tibble: 6 × 5
  Subject start   end PPTESTCD  PPORRES
  <ord>   <dbl> <dbl> <chr>       <dbl>
1 1           0    24 auclast   92.4   
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 lambda.z   0.0485

For multi-analyte or multi-period studies, add those variables to the formula in PKNCAconc():

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


19.3 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 columns are already CDISC-named, so no renaming is needed:

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)
# A tibble: 6 × 8
  Subject start   end PPTESTCD  PPORRES PPANMETH               exclude PPSTRESN
  <ord>   <dbl> <dbl> <chr>       <dbl> <chr>                  <chr>      <dbl>
1 1           0    24 auclast   92.4    "AUC: lin up/log down" <NA>     92.4   
2 1           0   Inf cmax      10.5    ""                     <NA>     10.5   
3 1           0   Inf tmax       1.12   ""                     <NA>      1.12  
4 1           0   Inf tlast     24.4    ""                     <NA>     24.4   
5 1           0   Inf clast.obs  3.28   ""                     <NA>      3.28  
6 1           0   Inf lambda.z   0.0485 ""                     <NA>      0.0485

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.


19.4 CDISC output format (≥ 0.12.2)

The long-format extraction above keeps PKNCA’s own parameter codes in PPTESTCDauclast, cmax, lambda.z, and so on. For submission datasets, as.data.frame() can translate those codes for you: out_format = "cdisc" replaces each PPTESTCD with its CDISC submission code (auclast becomes AUCLST, clast.obs becomes CLST, lambda.z becomes LAMZ) and inserts a PPTEST column carrying the standard parameter label. Route-dependent parameters resolve to the correct code based on the route recorded in the dose data.

as.data.frame(o_nca, out_format = "cdisc") |>
  select(Subject, PPTESTCD, PPTEST, PPORRES) |>
  head(8)
  Subject PPTESTCD                    PPTEST    PPORRES
1       1   AUCLST  AUC to Last Nonzero Conc 92.3654416
2       1     CMAX                  Max Conc 10.5000000
3       1     TMAX              Time of CMAX  1.1200000
4       1     TLST Time of Last Nonzero Conc 24.3700000
5       1     CLST         Last Nonzero Conc  3.2800000
6       1     LAMZ                  Lambda z  0.0484570
7       1       R2                 R Squared  0.9999997
8       1    R2ADJ        R Squared Adjusted  0.9999995

When an interval-based parameter is requested (for example aucint.last, whose CDISC code AUCINT contains “INT”), the CDISC output also gains PPSTINT and PPENINT columns giving the interval start and end as ISO 8601 durations relative to the last dose time (e.g. PT0H, PT12H).

Custom parameters registered with add.interval.col() can declare their own CDISC codes through its pptestcd_cdisc and pptest_cdisc arguments; a parameter without a declared code keeps its PKNCA name in the CDISC output. See the Writing Custom Parameter Functions chapter.


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

# How many rows are flagged for exclusion?
table(is.na(df$exclude))

TRUE 
 192 
# Show any excluded rows
df |> filter(!is.na(exclude)) |> head()
# 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.

# 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))
Loading required namespace: testthat
df_excl <- as.data.frame(o_nca_excl)
df_excl |> filter(!is.na(exclude)) |> select(Subject, PPTESTCD, PPORRES, exclude) |> head()
# A tibble: 0 × 4
# ℹ 4 variables: Subject <ord>, PPTESTCD <chr>, PPORRES <dbl>, exclude <chr>

19.6 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):

summary(o_nca)
 start end  N     auclast        cmax               tmax   half.life aucinf.obs
     0  24 12 74.6 [24.3]           .                  .           .          .
     0 Inf 12           . 8.65 [17.0] 1.14 [0.630, 3.55] 8.18 [2.12] 115 [28.4]

Caption: auclast, cmax, aucinf.obs: geometric mean and geometric coefficient of variation; tmax: median and range; half.life: 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().


19.7 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:

# 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
[1] "lin up/log down"
analysis_options$conc.blq        # BLQ handling
$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:

# Example: force the plain linear trapezoidal rule everywhere
PKNCA.options(auc.method = "linear")

For full reproducibility, record the session environment alongside the option snapshot:

# Save at the end of the analysis script
sessionInfo()
R version 4.6.1 (2026-06-24)
Platform: x86_64-pc-linux-gnu
Running under: Ubuntu 24.04.4 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.9000

loaded via a namespace (and not attached):
 [1] jsonlite_2.0.0    compiler_4.6.1    brio_1.1.5        tidyselect_1.2.1 
 [5] Rcpp_1.1.2        tidyr_1.3.2       yaml_2.3.12       fastmap_1.2.0    
 [9] lattice_0.22-9    R6_2.6.1          generics_0.1.4    knitr_1.51       
[13] htmlwidgets_1.6.4 backports_1.5.1   conflicted_1.2.0  checkmate_2.3.4  
[17] tibble_3.3.1      units_1.0-1       pillar_1.11.1     rlang_1.3.0      
[21] utf8_1.2.6        testthat_3.3.2    cachem_1.1.0      xfun_0.60        
[25] otel_0.2.0        memoise_2.0.1     cli_3.6.6         withr_3.0.3      
[29] magrittr_2.0.5    digest_0.6.39     grid_4.6.1        lifecycle_1.0.5  
[33] nlme_3.1-169      vctrs_0.7.3       evaluate_1.0.5    glue_1.8.1       
[37] rmarkdown_2.31    purrr_1.2.2       tools_4.6.1       pkgconfig_2.0.3  
[41] htmltools_0.5.9  

For validated environments, use renv to lock package versions:

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