19  Regulatory and CDISC Context

PKNCA does not include a dedicated CDISC submission function. What it does provide is a tidy, long-format result table whose structure maps directly onto the CDISC PP (Pharmacokinetic Parameters) domain. This page documents that mapping and describes how to prepare outputs for regulatory submissions.


19.1 1. CDISC PP Domain Alignment

The CDISC PP domain stores one parameter value per row, with the parameter code in PPTESTCD, the numeric result in PPORRES, and the standardized result in PPSTRES (character) when a units table is provided. 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"  "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 PPEXCLFL / PPDRVFL 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.

head(df, 10)
# A tibble: 10 × 6
   Subject start   end PPTESTCD            PPORRES exclude
   <ord>   <dbl> <dbl> <chr>                 <dbl> <chr>  
 1 1           0    24 auclast             92.4    <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 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 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:

pp_domain_draft <- as.data.frame(o_nca)

# Rename to CDISC column names as needed
pp_cdisc <- pp_domain_draft |>
  rename(
    PPORRES  = PPORRES,    # already CDISC-named
    PPTESTCD = PPTESTCD    # already CDISC-named
  ) |>
  # PPSTRESN is typically the same as PPORRES for numeric results
  mutate(PPSTRESN = PPORRES)

head(pp_cdisc)
# A tibble: 6 × 7
  Subject start   end PPTESTCD  PPORRES exclude PPSTRESN
  <ord>   <dbl> <dbl> <chr>       <dbl> <chr>      <dbl>
1 1           0    24 auclast   92.4    <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 4. 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.

# All possible exclusion reasons in this result
table(is.na(df$exclude))

TRUE 
 192 
# Show any excluded rows
df |> filter(!is.na(exclude)) |> head()
# A tibble: 0 × 6
# ℹ 6 variables: Subject <ord>, start <dbl>, end <dbl>, PPTESTCD <chr>,
#   PPORRES <dbl>, 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(). For regulatory submissions, include all rows in the PP domain — both included and excluded — and map the exclude string to PPEXCLFL = "Y" with the reason preserved in a supplemental qualifier.

# Apply standard exclusion rules and inspect excluded rows
o_nca_excl <- o_nca |>
  exclude(reason = "r.squared < 0.9",   FUN = exclude_nca_min.hl.r.squared(0.9)) |>
  exclude(reason = "%AUCextrap > 20%",  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.5 5. Summary Table for Reports

summary(o_nca) returns a wide-format table suitable for clinical study reports. Each row is a parameter and each column is a summary statistic (geometric mean, CV%, range, etc.):

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 running pk.nca().


19.6 6. 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: use log-linear trapezoid instead of linear-up/log-down
PKNCA.options(auc.method = "lin-log")

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

# Save at the end of the analysis script
sessionInfo()
R version 4.6.0 (2026-04-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] ggplot2_4.0.3 dplyr_1.2.1   PKNCA_0.12.2 

loaded via a namespace (and not attached):
 [1] gtable_0.3.6       jsonlite_2.0.0     compiler_4.6.0     brio_1.1.5        
 [5] tidyselect_1.2.1   Rcpp_1.1.1-1.1     tidyr_1.3.2        scales_1.4.0      
 [9] yaml_2.3.12        fastmap_1.2.0      lattice_0.22-9     R6_2.6.1          
[13] generics_0.1.4     knitr_1.51         backports_1.5.1    htmlwidgets_1.6.4 
[17] conflicted_1.2.0   checkmate_2.3.4    tibble_3.3.1       units_1.0-1       
[21] pillar_1.11.1      RColorBrewer_1.1-3 rlang_1.2.0        testthat_3.3.2    
[25] utf8_1.2.6         cachem_1.1.0       xfun_0.58          S7_0.2.2          
[29] otel_0.2.0         memoise_2.0.1      cli_3.6.6          withr_3.0.2       
[33] magrittr_2.0.5     digest_0.6.39      grid_4.6.0         lifecycle_1.0.5   
[37] nlme_3.1-169       vctrs_0.7.3        evaluate_1.0.5     glue_1.8.1        
[41] farver_2.1.2       purrr_1.2.2        rmarkdown_2.31     tools_4.6.0       
[45] pkgconfig_2.0.3    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.