20  Validation and Testing

PKNCA ships with a comprehensive testthat-based test suite that covers the full calculation engine. This page explains how to access and run that suite, how to interpret results, and how to spot-check individual calculations against hand-derived values.


20.1 1. PKNCA’s Validation Approach

The validation strategy is reproducibility-first: re-running the test suite in your local environment and obtaining the same results as the original package development constitutes confirmation that the software is working correctly in your system.

The test suite uses the testthat framework and targets near-complete coverage of every calculation function. Tests range from numerical spot-checks of individual PK parameters to integration tests of the full pk.nca() pipeline.

To check whether tests were installed alongside the package:

system.file("tests", package = "PKNCA")
[1] ""

If this returns an empty string, the tests were not installed. Standard CRAN installs do not include tests by default; see Section 2 for how to install them.


20.2 2. Installing with Tests and Running the Suite

Tests must be explicitly included at install time.

From CRAN (source install with tests):

install.packages(
  pkgs         = "PKNCA",
  INSTALL_opts = "--install-tests",
  type         = "source"
)

From GitHub:

if (!requireNamespace("remotes")) install.packages("remotes")
remotes::install_github(
  "humanpred/pknca",
  INSTALL_opts = "--install-tests"
)

Running the test suite once tests are installed:

if (!requireNamespace("testthat")) install.packages("testthat")

# Option A — via devtools (recommended during development)
devtools::test(system.file(package = "PKNCA"))

# Option B — via testthat directly
testthat::test_package("PKNCA")

# Option C — full R CMD CHECK (most rigorous; runs examples too)
# From a terminal:
# R CMD CHECK $(R -e 'cat(find.package("PKNCA"))')

20.3 3. Interpreting Results

A passing run reports the number of tests, the number of expectations checked, and zero failures. A typical run looks like:

[ FAIL 0 | WARN 0 | SKIP n | PASS N ]
  • FAIL 0 — no calculation errors; the install is valid.
  • SKIP n — some tests are conditionally skipped (e.g., tests that require internet access, or environment-specific checks). Skips are expected and do not indicate a problem.
  • WARN — warnings during tests are expected; the PKNCA validation vignette explicitly notes that warnings appear during testing and are not displayed in the published vignette.

If any failures are reported, the output shows the test file, test name, and the specific expectation that failed — compare the expected and actual values to diagnose whether the issue is a platform difference or a genuine calculation error.


20.4 4. Checking the Installed Version

Before any regulatory analysis, record the exact package version:

packageVersion("PKNCA")
[1] '0.12.2'

Cite the package version in the analysis report:

citation("PKNCA")
To cite package 'PKNCA' in publications use:

  Denney W, Duvvuri S, Buckeridge C (2015). "Simple, Automatic
  Noncompartmental Analysis: The PKNCA R Package." _Journal of
  Pharmacokinetics and Pharmacodynamics_, *42*(1), 11-107,S65. ISSN
  1573-8744. doi:10.1007/s10928-015-9432-2
  <https://doi.org/10.1007/s10928-015-9432-2>. R package version
  0.12.2, <https://github.com/humanpred/pknca>.

A BibTeX entry for LaTeX users is

  @Article{,
    title = {Simple, Automatic Noncompartmental Analysis: The PKNCA R Package},
    author = {William S. Denney and Sridhar Duvvuri and Clare Buckeridge},
    doi = {10.1007/s10928-015-9432-2},
    journal = {Journal of Pharmacokinetics and Pharmacodynamics},
    pages = {11-107,S65},
    year = {2015},
    volume = {42},
    number = {1},
    issn = {1573-8744},
    url = {https://github.com/humanpred/pknca},
    note = {R package version 0.12.2},
  }

The canonical citation is:

Denney W, Duvvuri S, Buckeridge C (2015). “Simple, Automatic Noncompartmental Analysis: The PKNCA R Package.” Journal of Pharmacokinetics and Pharmacodynamics, 42(1), 11-107,S65. doi:10.1007/s10928-015-9432-2


20.5 5. Spot-Checking a Calculation Against a Known Reference

Running the full test suite confirms the package as a whole. For a targeted validation of a specific parameter, you can compute the value by hand and compare it to pk.nca() output.

Example: manual linear-trapezoid AUClast for Theoph Subject 1

The linear (additive) trapezoid rule is:

\[\text{AUC}_{t_i \to t_{i+1}} = \frac{(t_{i+1} - t_i)(C_i + C_{i+1})}{2}\]

data("Theoph")

# Subject 1 concentration-time data
s1 <- Theoph[Theoph$Subject == "1", c("Time", "conc")]
s1
    Time  conc
1   0.00  0.74
2   0.25  2.84
3   0.57  6.57
4   1.12 10.50
5   2.02  9.66
6   3.82  8.58
7   5.10  8.36
8   7.03  7.47
9   9.05  6.89
10 12.12  5.94
11 24.37  3.28
# Manual linear-trapezoid AUClast
# AUClast uses all data up to and including the last measurable concentration
n <- nrow(s1)
manual_auclast <- sum(
  (s1$Time[2:n] - s1$Time[1:(n - 1)]) *
    (s1$conc[2:n] + s1$conc[1:(n - 1)]) / 2
)
cat("Manual AUClast (linear trap):", round(manual_auclast, 3), "\n")
Manual AUClast (linear trap): 148.923 

Now run pk.nca() and extract the same parameter:

d_conc <- PKNCAconc(Theoph, conc ~ Time | Subject)
d_dose <- PKNCAdose(
  Theoph |> group_by(Subject) |>
    summarise(Dose = Dose[1] * Wt[1], .groups = "drop") |>
    mutate(Time = 0),
  Dose ~ Time | Subject
)

# Use a single interval that spans the full profile
d_data <- PKNCAdata(
  d_conc, d_dose,
  intervals = data.frame(start = 0, end = Inf, auclast = TRUE)
)
suppressMessages(o_nca <- pk.nca(d_data))

pknca_auclast <- as.data.frame(o_nca) |>
  filter(Subject == "1", PPTESTCD == "auclast") |>
  pull(PPORRES)

cat("PKNCA AUClast:              ", round(pknca_auclast, 3), "\n")
PKNCA AUClast:               147.235 
cat("Difference (manual - PKNCA):", round(manual_auclast - pknca_auclast, 6), "\n")
Difference (manual - PKNCA): 1.688301 

The manual calculation above uses the linear trapezoidal rule throughout. PKNCA defaults to lin up / log down (PKNCA.options("auc.method") = "lin up/log down"), which uses log-linear integration on any descending interval — so the two values will differ for a typical PK profile with a descending phase. To make them agree exactly, set auc.method = "linear":

suppressMessages(
  o_nca_lin <- pk.nca(PKNCAdata(d_conc, d_dose,
    intervals = data.frame(start = 0, end = Inf, auclast = TRUE),
    options   = list(auc.method = "linear")))
)
pknca_lin <- as.data.frame(o_nca_lin) |>
  filter(Subject == "1", PPTESTCD == "auclast") |> pull(PPORRES)
cat("Manual (linear trap):   ", round(manual_auclast, 3), "\n")
Manual (linear trap):    148.923 
cat("PKNCA  (linear method): ", round(pknca_lin,       3), "\n")
PKNCA  (linear method):  148.923 
cat("Difference:             ", round(manual_auclast - pknca_lin, 6), "\n")
Difference:              0 

To confirm the current method:

PKNCA.options("auc.method")
[1] "lin up/log down"

20.6 6. Reproducibility Reporting with sessionInfo()

Every validation run should capture the complete session environment. Record this at the end of each analysis script:

Sys.Date()
[1] "2026-06-10"
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     tidyselect_1.2.1  
 [5] Rcpp_1.1.1-1.1     tidyr_1.3.2        scales_1.4.0       yaml_2.3.12       
 [9] fastmap_1.2.0      lattice_0.22-9     R6_2.6.1           generics_0.1.4    
[13] knitr_1.51         backports_1.5.1    htmlwidgets_1.6.4  conflicted_1.2.0  
[17] checkmate_2.3.4    tibble_3.3.1       units_1.0-1        pillar_1.11.1     
[21] RColorBrewer_1.1-3 rlang_1.2.0        cachem_1.1.0       xfun_0.58         
[25] S7_0.2.2           otel_0.2.0         memoise_2.0.1      cli_3.6.6         
[29] withr_3.0.2        magrittr_2.0.5     digest_0.6.39      grid_4.6.0        
[33] lifecycle_1.0.5    nlme_3.1-169       vctrs_0.7.3        evaluate_1.0.5    
[37] glue_1.8.1         farver_2.1.2       purrr_1.2.2        rmarkdown_2.31    
[41] tools_4.6.0        pkgconfig_2.0.3    htmltools_0.5.9   

sessionInfo() captures:

  • R version and platform
  • Locale and timezone
  • All attached and loaded packages with their exact versions
  • The version of PKNCA and all its dependencies

For validated regulatory environments, supplement sessionInfo() with an renv lockfile:

# Capture the environment
renv::snapshot()

# Confirm a clean restore on the validation system
renv::restore()
renv::status()

Together, packageVersion("PKNCA"), PKNCA.options(), sessionInfo(), and renv::snapshot() provide a complete and reproducible record of the computational environment used in an NCA analysis.