system.file("tests", package = "PKNCA")[1] ""
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.
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:
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.
Tests must be explicitly included at install time.
From CRAN (source install with tests):
From GitHub:
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"))')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 ]
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.
Before any regulatory analysis, record the exact package version:
Cite the package version in the analysis report:
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
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}\]
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 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
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
PKNCA (linear method): 148.923
Difference: 0
To confirm the current method:
sessionInfo()Every validation run should capture the complete session environment. Record this at the end of each analysis script:
[1] "2026-06-10"
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:
For validated regulatory environments, supplement sessionInfo() with an renv lockfile:
Together, packageVersion("PKNCA"), PKNCA.options(), sessionInfo(), and renv::snapshot() provide a complete and reproducible record of the computational environment used in an NCA analysis.
pkgdown reference: PKNCAconc() · PKNCAdose() · PKNCAdata() · pk.nca() · PKNCA.options()
---
title: "Validation and Testing"
---
```{r setup, include=FALSE}
library(PKNCA)
library(dplyr)
library(ggplot2)
conflicted::conflicts_prefer(dplyr::filter, dplyr::select, .quiet = TRUE)
```
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.
---
## 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`](https://testthat.r-lib.org/) 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:
```{r test-location}
system.file("tests", package = "PKNCA")
```
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.
---
## 2. Installing with Tests and Running the Suite
Tests must be explicitly included at install time.
**From CRAN (source install with tests):**
```{r cran-install, eval=FALSE}
install.packages(
pkgs = "PKNCA",
INSTALL_opts = "--install-tests",
type = "source"
)
```
**From GitHub:**
```{r github-install, eval=FALSE}
if (!requireNamespace("remotes")) install.packages("remotes")
remotes::install_github(
"humanpred/pknca",
INSTALL_opts = "--install-tests"
)
```
**Running the test suite** once tests are installed:
```{r run-tests, eval=FALSE}
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"))')
```
---
## 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.
---
## 4. Checking the Installed Version
Before any regulatory analysis, record the exact package version:
```{r version-check}
packageVersion("PKNCA")
```
Cite the package version in the analysis report:
```{r citation}
citation("PKNCA")
```
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
---
## 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}$$
```{r spot-check}
data("Theoph")
# Subject 1 concentration-time data
s1 <- Theoph[Theoph$Subject == "1", c("Time", "conc")]
s1
# 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")
```
Now run `pk.nca()` and extract the same parameter:
```{r pknca-auclast}
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")
cat("Difference (manual - PKNCA):", round(manual_auclast - pknca_auclast, 6), "\n")
```
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"`:
```{r linear-match}
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")
cat("PKNCA (linear method): ", round(pknca_lin, 3), "\n")
cat("Difference: ", round(manual_auclast - pknca_lin, 6), "\n")
```
To confirm the current method:
```{r auc-method}
PKNCA.options("auc.method")
```
---
## 6. Reproducibility Reporting with `sessionInfo()`
Every validation run should capture the complete session environment. Record this at the end of each analysis script:
```{r session-info}
Sys.Date()
sessionInfo()
```
`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:
```{r renv-lock, eval=FALSE}
# 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.
---
::: {.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.options()](https://humanpred.github.io/pknca/reference/PKNCA.options.html)
:::