Running NCA with aNCA: R Script Walkthrough
Source:vignettes/example_r_script.Rmd
example_r_script.RmdIntroduction
The aNCA Shiny app exports a settings YAML and an
R script that together reproduce the analysis outside
the app. This vignette walks through that R script step by step, using
the built-in adnca_example dataset with concrete values for
every setting.
By the end you will have: - A PKNCAdata object with mapped columns, filters and intervals - NCA results with flag rules, interval parameters and ratio calculations applied - CDISC-compliant PP, ADPP, and ADNCA datasets - A pivoted results table ready for review
Start here: choose your workflow
Use this section to decide how you want to run NCA, based on your background and preferred workflow.
| If you are… | Best path | What you get |
|---|---|---|
| Comfortable running scripted analyses in R | Path A: run the full script directly | Maximum control and visibility over each step |
| Working from settings YAML files (e.g., from app exports or versioned config files) | Path B: generate the same script from YAML | Reproducible script generation with
get_settings_code()
|
| Prefer playing with an App and an interface | Path C: generate from the app | Build settings interactively, then export YAML and script |
Install aNCA
Install the package from GitHub (the CRAN version still does not have the features shown here):
# Option 1
remotes::install_github("pharmaverse/aNCA")
# Option 2
pak::pak("pharmaverse/aNCA")Then restart your R session and confirm the installed version:
packageVersion("aNCA")Path A: run the full script directly
1. Load the package and data
library(aNCA)
library(dplyr)
# The example dataset ships with the package.
# In practice this would be: adnca_data <- read_pk("path/to/your/data.csv")
adnca_data <- adnca_example
head(adnca_data)adnca_example is a 76-row ADNCA dataset with two
analytes (DrugA and its metabolite Metab-DrugA), two specimen types
(SERUM and URINE), and two dosing periods (DOSE 1, DOSE 2). Subjects
received different dose levels via EXTRAVASCULAR and INTRAVASCULAR
routes.
2. Define the column mapping
The mapping tells aNCA which columns in your dataset correspond to the expected CDISC variables. Keys match the standard ADNCA column names; values are the actual column names in your data.
mapping <- list(
STUDYID = "STUDYID",
USUBJID = "USUBJID",
DOSEA = "DOSEA",
DOSEU = "DOSEU",
DOSETRT = "DOSETRT",
PARAM = "PARAM",
Metabolites = "Metab-DrugA",
ARRLT = "ARRLT",
NRRLT = "NRRLT",
AFRLT = "AFRLT",
NCAwXRS = c("NCA1XRS", "NCA2XRS"),
NFRLT = "NFRLT",
PCSPEC = "PCSPEC",
ROUTE = "ROUTE",
TRTRINT = "TRTRINT",
ADOSEDUR = "ADOSEDUR",
Grouping_Variables = c("TRT01A", "RACE", "SEX"),
RRLTU = "RRLTU",
VOLUME = "VOLUME",
VOLUMEU = "VOLUMEU",
AVAL = "AVAL",
AVALU = "AVALU",
ATPTREF = "ATPTREF"
)A few notes: - Metabolites: The PARAM
value(s) that represent metabolites. aNCA uses this to set the
METABFL flag. - NCAwXRS: One
or more columns containing NCA exclusion reason codes. Subjects flagged
here are excluded from calculations. -
Grouping_Variables: Columns carried
through to results and used for grouping in descriptive statistics
(e.g., treatment arm, demographics).
3. Set up filters
Filters restrict the data before analysis. Each filter specifies a column and the values to keep. Here we keep only the parent drug for serum specimens:
applied_filters <- list(
list(column = "PARAM", value = c("DrugA")),
list(column = "PCSPEC", value = c("SERUM"))
)Pass NULL if no filters are needed.
4. Define NCA settings
4.1 Partial interval calculations
Interval parameters compute AUC or average concentration over custom time windows. Each row specifies the parameter code, start time and end time:
int_parameters <- data.frame(
parameter = c("AUCINT", "CAVGINT"),
start_auc = c(0, 0),
end_auc = c(12, 12)
)This calculates for each dose AUCs from 0–12 h and average concentration from 0–12 h.
4.2 Parameter selections
Select which NCA parameters to compute, grouped by study type. The names must match the study types that PKNCA assigns based on route and dosing:
4.3 Custom units
Override the default units for specific parameters. Each row maps a PPTESTCD to the desired output unit:
units_table <- data.frame(
PPTESTCD = c("CMAX", "TMAX", "AUCLAST"),
PPSTRESU = c("ng/mL", "h", "ng*h/mL")
)Pass NULL to use the units derived from the data.
4.4 Flag rules
Flag rules define quality thresholds. Parameters that violate a
checked rule are flagged and excluded from descriptive statistics. Each
rule has a checkbox (is.checked) and a numeric
threshold:
flag_rules <- list(
R2ADJ = list(is.checked = TRUE, threshold = 0.7),
R2 = list(is.checked = FALSE, threshold = 0.7),
AUCPEO = list(is.checked = TRUE, threshold = 20),
AUCPEP = list(is.checked = TRUE, threshold = 20),
LAMZSPN = list(is.checked = TRUE, threshold = 2)
)- R2ADJ ≥ 0.7: Minimum adjusted R² for the terminal elimination regression.
- AUCPEO ≤ 20: Maximum % AUC extrapolated (observed).
- AUCPEP ≤ 20: Maximum % AUC extrapolated (predicted).
- LAMZSPN ≥ 2: Minimum half-life span ratio.
4.5 Slope rules
Slope rules let you manually override which points PKNCA uses for the terminal elimination (lambda-z) regression. This is useful when the automatic best-fit selects an inappropriate range — for example, when a secondary absorption peak or an outlier distorts the slope.
Each row targets one subject/profile combination:
-
USUBJID: Subject identifier. -
ATPTREF: Dosing period (e.g.,"DOSE 1"). -
DOSNOA: Dose number within the period (use"*"for all doses). -
TYPE: Either"points"(select individual time points) or"range"(select a start–end window). -
RANGE: The time points or window, e.g.,"4, 8, 12, 24"for points or"4-24"for a range. -
REASON: Free-text justification for the override.
# Example: force lambda-z to use 4–24 h for subject S1-001 in DOSE 1
slope_rules <- data.frame(
USUBJID = "S1-001",
ATPTREF = "DOSE 1",
DOSNOA = "*",
TYPE = "range",
RANGE = "4-24",
REASON = "Exclude early absorption phase from terminal slope"
)
# If no manual adjustments are needed, pass an empty data frame:
# slope_rules <- data.frame(
# USUBJID = character(0), ATPTREF = character(0),
# DOSNOA = character(0), TYPE = character(0),
# RANGE = character(0), REASON = character(0)
# )4.6 Extra variables to keep
These columns are carried through from the input data to the final
results. They always include DOSEA, ATPTREF,
and ROUTE, plus whatever you selected as
Grouping_Variables in the mapping:
extra_vars_to_keep <- c(
mapping$Grouping_Variables, "DOSEA", "ATPTREF", "ROUTE"
)5. Create the PKNCA data object
PKNCA_create_data_object() applies the column mapping,
filters, and duplicate handling to produce a PKNCA-ready data
object:
pknca_obj <- adnca_data %>%
PKNCA_create_data_object(
mapping = mapping,
applied_filters = applied_filters,
time_duplicate_rows = NULL
)time_duplicate_rows is a character vector of column
names used to identify duplicate time points. Pass NULL to
skip duplicate handling.
6. Configure the data object
PKNCA_update_data_object() sets the NCA method, selects
the analyte/profile/ specimen, configures intervals, parameters, and
units:
pknca_obj <- pknca_obj %>%
PKNCA_update_data_object(
method = "lin up/log down",
selected_analytes = "DrugA",
selected_profile = "DOSE 1",
selected_pcspec = "SERUM",
start_impute = "yes",
exclusion_list = list(),
hl_adj_rules = slope_rules,
keep_interval_cols = setdiff(extra_vars_to_keep, c("DOSEA", "ATPTREF", "ROUTE")),
min_hl_points = 3,
parameter_selections = parameters_selected_per_study,
int_parameters = int_parameters,
custom_units_table = units_table
)Key arguments: - method: AUC
calculation method. Options: "linear",
"lin up/log down", "linear up/log down". -
selected_analytes: Which PARAM value(s) to
analyze. - selected_profile: Which ATPTREF
(dosing period) to analyze. -
selected_pcspec: Which specimen type to
use. - start_impute: Whether to impute C0
("yes" or "no"). -
exclusion_list: Subject-level exclusions.
Pass a list of USUBJID values to drop specific subjects before NCA
calculations, e.g., list("S1-003", "S1-007"). Pass an empty
list() to include all subjects. -
min_hl_points: Minimum number of points
for lambda-z regression. -
keep_interval_cols: Extra columns to carry
into PKNCA intervals (the grouping variables, minus the three that are
always included).
7. Run NCA calculations
pknca_res <- pknca_obj %>%
# Run PKNCA and join subject/dose information to results
PKNCA_calculate_nca(
blq_rule = list(first = "keep", middle = "keep", last = "keep")
) %>%
# Add bioavailability if requested (NULL = skip)
add_f_to_pknca_results(NULL) %>%
# Translate PKNCA parameter names to CDISC PPTESTCD codes
mutate(PPTESTCD = translate_terms(PPTESTCD, "PKNCA", "PPTESTCD"))-
blq_rule: How to handle BLQ (below limit of quantification) values at different positions in the concentration-time profile. Options for each position:"keep","0","loq/2". -
add_f_to_pknca_results(): Pass a bioavailability AUC type (e.g.,"f_aucinf.obs") to compute F, orNULLto skip.
8. Apply flag rules
Flag rules mark parameters that fail quality thresholds. Flagged parameters get an exclusion reason and are dropped from descriptive statistics:
pknca_res <- pknca_res %>%
PKNCA_hl_rules_exclusion(
rules = flag_rules %>%
purrr::keep(\(x) x$is.checked) %>%
purrr::map(\(x) x$threshold)
)This filters flag_rules to only the checked rules,
extracts their thresholds, and passes them to PKNCA’s exclusion
logic.
9. Calculate ratio parameters
The ratio table derives secondary parameters by dividing one NCA result by another across groups. Here we compute a metabolite-to-parent Cmax ratio:
ratio_table <- data.frame(
TestParameter = "CMAX",
RefParameter = "CMAX",
RefGroups = "PARAM: DrugA",
TestGroups = "(all other levels)",
AggregateSubject = "no",
AdjustingFactor = 1,
PPTESTCD = "MRCMAX"
)
pknca_res <- pknca_res %>%
calculate_table_ratios(ratio_table)Pass NULL or an empty data frame if no ratios are
needed.
10. Filter to requested parameters
Remove any parameters that were computed by PKNCA but not explicitly
selected in parameters_selected_per_study:
pknca_res <- pknca_res %>%
remove_pp_not_requested()11. Export CDISC datasets
export_cdisc() produces PP, ADPP, and ADNCA datasets
from the NCA results. Flag rules are formatted into
CRITy/CRITyFL/PPSUMFL columns in ADPP:
# Build flag rule messages for ADPP criterion columns
flag_operators <- c(
R2ADJ = " < ", R2 = " < ", AUCPEO = " > ", AUCPEP = " > ", LAMZSPN = " < "
)
checked_flags <- purrr::keep(flag_rules, function(x) x$is.checked)
flag_rule_msgs <- if (length(checked_flags) > 0) {
vapply(names(checked_flags), function(nm) {
paste0(nm, flag_operators[nm], checked_flags[[nm]]$threshold)
}, character(1), USE.NAMES = FALSE)
} else {
NULL
}
cdisc_datasets <- pknca_res %>%
export_cdisc(
grouping_vars = extra_vars_to_keep,
flag_rules = flag_rule_msgs
)cdisc_datasets is a named list with three data frames: -
cdisc_datasets$pp: PP domain — one row per
parameter per subject. -
cdisc_datasets$adpp: ADPP — PP with
ADaM-style flags and criterion columns (CRITy, CRITyFL, PPSUMFL,
PPSUMRSN). - cdisc_datasets$adnca: ADNCA —
the full analysis dataset with results joined back to the
concentration-time data.
12. Pivot results
pivot_wider_pknca_results() reshapes the long-format NCA
results into a wide table with one row per subject and one column per
parameter — the same view shown in the app’s Results tab:
pivoted_results <- pivot_wider_pknca_results(
myres = pknca_res,
flag_rules = flag_rules,
extra_vars_to_keep = extra_vars_to_keep
)Flagged parameters (those excluded by flag rules) are marked in the output and excluded from summary statistics.
Path B: generate the same script from YAML with get_settings_code()
If you prefer managing configuration as YAML, you can generate the
same runnable R script with get_settings_code().
Example YAML (same analysis setup)
Save this as example_settings.yaml, and feel free to
edit based on preferences:
mapping:
STUDYID: STUDYID
USUBJID: USUBJID
DOSEA: DOSEA
DOSEU: DOSEU
DOSETRT: DOSETRT
PARAM: PARAM
Metabolites: Metab-DrugA
ARRLT: ARRLT
NRRLT: NRRLT
AFRLT: AFRLT
NCAwXRS: [NCA1XRS, NCA2XRS]
NFRLT: NFRLT
PCSPEC: PCSPEC
ROUTE: ROUTE
TRTRINT: TRTRINT
ADOSEDUR: ADOSEDUR
Grouping_Variables: [TRT01A, RACE, SEX]
RRLTU: RRLTU
VOLUME: VOLUME
VOLUMEU: VOLUMEU
AVAL: AVAL
AVALU: AVALU
ATPTREF: ATPTREF
filters:
- column: PARAM
value: [DrugA]
- column: PCSPEC
value: [SERUM]
settings:
method: lin up/log down
analyte: DrugA
profile: DOSE 1
pcspec: SERUM
start_impute: yes
min_hl_points: 3
int_parameters:
parameter: [AUCINT, CAVGINT]
start_auc: [0, 0]
end_auc: [12, 12]
ratio_table:
TestParameter: [CMAX]
RefParameter: [CMAX]
RefGroups: ["PARAM: DrugA"]
TestGroups: ["(all other levels)"]
AggregateSubject: [no]
AdjustingFactor: [1]
PPTESTCD: [MRCMAX]
units:
PPTESTCD: [CMAX, TMAX, AUCLAST]
PPSTRESU: [ng/mL, h, ng*h/mL]
slope_rules:
USUBJID: []
ATPTREF: []
DOSNOA: []
TYPE: []
RANGE: []
REASON: []
time_duplicate_keys: nullGenerate the script from YAML
library(aNCA)
get_settings_code(
settings_file_path = "example_settings.yaml",
data_path = "adnca_example.csv",
output_path = "settings_code.R"
)This writes a runnable script to settings_code.R using
your YAML settings (check the Full script
section below). It will have some comments explaining each step, but for
more details refer back to Path A: run the full script
directly.
Path C: generate from the aNCA app
If you prefer a guided UI workflow, it is very simple:
aNCA::run_app()From the app, configure mapping, filters, and NCA settings, then
export pressing the Save button: - Settings YAML file -
Reproducible R script - All the generated outputs of your interest
For a YAML-driven reproducible workflow after app export, see Path B: generate the same script from YAML with get_settings_code().
Full script
Below is the complete script assembled from the steps above. This is what the app generates when you export settings — with all placeholders filled in.
Show full reproducible script
# Load the package
library(aNCA)
library(dplyr)
# 1. Load data
adnca_data <- adnca_example
# 2. Column mapping
mapping <- list(
STUDYID = "STUDYID",
USUBJID = "USUBJID",
DOSEA = "DOSEA",
DOSEU = "DOSEU",
DOSETRT = "DOSETRT",
PARAM = "PARAM",
Metabolites = "Metab-DrugA",
ARRLT = "ARRLT",
NRRLT = "NRRLT",
AFRLT = "AFRLT",
NCAwXRS = c("NCA1XRS", "NCA2XRS"),
NFRLT = "NFRLT",
PCSPEC = "PCSPEC",
ROUTE = "ROUTE",
TRTRINT = "TRTRINT",
ADOSEDUR = "ADOSEDUR",
Grouping_Variables = c("TRT01A", "RACE", "SEX"),
RRLTU = "RRLTU",
VOLUME = "VOLUME",
VOLUMEU = "VOLUMEU",
AVAL = "AVAL",
AVALU = "AVALU",
ATPTREF = "ATPTREF"
)
# 3. Filters
applied_filters <- list(
list(column = "PARAM", value = c("DrugA")),
list(column = "PCSPEC", value = c("SERUM"))
)
# 4. NCA settings
int_parameters <- data.frame(
parameter = c("AUCINT", "CAVGINT"),
start_auc = c(0, 0),
end_auc = c(12, 12)
)
parameters_selected_per_study <- list(
`Single Extravascular` = c(
"auclast", "cmax", "tmax", "half.life", "aucinf.obs",
"vz.obs", "cl.obs", "aumclast"
),
`Single IV Bolus` = c(
"auclast", "cmax", "half.life", "aucinf.obs",
"vz.obs", "cl.obs"
)
)
units_table <- data.frame(
PPTESTCD = c("CMAX", "TMAX", "AUCLAST"),
PPSTRESU = c("ng/mL", "h", "ng*h/mL")
)
flag_rules <- list(
R2ADJ = list(is.checked = TRUE, threshold = 0.7),
R2 = list(is.checked = FALSE, threshold = 0.7),
AUCPEO = list(is.checked = TRUE, threshold = 20),
AUCPEP = list(is.checked = TRUE, threshold = 20),
LAMZSPN = list(is.checked = TRUE, threshold = 2)
)
# Slope rules: manual lambda-z overrides (empty = no overrides)
slope_rules <- data.frame(
USUBJID = "S1-001",
ATPTREF = "DOSE 1",
DOSNOA = "*",
TYPE = "range",
RANGE = "4-24",
REASON = "Exclude early absorption phase from terminal slope"
)
extra_vars_to_keep <- c(
mapping$Grouping_Variables, "DOSEA", "ATPTREF", "ROUTE"
)
ratio_table <- data.frame(
TestParameter = "CMAX",
RefParameter = "CMAX",
RefGroups = "PARAM: DrugA",
TestGroups = "(all other levels)",
AggregateSubject = "no",
AdjustingFactor = 1,
PPTESTCD = "MRCMAX"
)
# 5. Create PKNCA data object
pknca_obj <- adnca_data %>%
PKNCA_create_data_object(
mapping = mapping,
applied_filters = applied_filters,
time_duplicate_rows = NULL
)
# 6. Configure intervals, parameters, and units
pknca_obj <- pknca_obj %>%
PKNCA_update_data_object(
method = "lin up/log down",
selected_analytes = "DrugA",
selected_profile = "DOSE 1",
selected_pcspec = "SERUM",
start_impute = "yes",
exclusion_list = list(),
hl_adj_rules = slope_rules,
keep_interval_cols = setdiff(extra_vars_to_keep, c("DOSEA", "ATPTREF", "ROUTE")),
min_hl_points = 3,
parameter_selections = parameters_selected_per_study,
int_parameters = int_parameters,
custom_units_table = units_table
)
# 7. Run NCA
pknca_res <- pknca_obj %>%
PKNCA_calculate_nca(
blq_rule = list(first = "keep", middle = "keep", last = "keep")
) %>%
add_f_to_pknca_results(NULL) %>%
mutate(PPTESTCD = translate_terms(PPTESTCD, "PKNCA", "PPTESTCD"))
# 8. Apply flag rules
pknca_res <- pknca_res %>%
PKNCA_hl_rules_exclusion(
rules = flag_rules %>%
purrr::keep(\(x) x$is.checked) %>%
purrr::map(\(x) x$threshold)
)
# 9. Calculate ratios
pknca_res <- pknca_res %>%
calculate_table_ratios(ratio_table)
# 10. Filter to requested parameters
pknca_res <- pknca_res %>%
remove_pp_not_requested()
# 11. Export CDISC datasets
flag_operators <- c(
R2ADJ = " < ", R2 = " < ", AUCPEO = " > ", AUCPEP = " > ", LAMZSPN = " < "
)
checked_flags <- purrr::keep(flag_rules, function(x) x$is.checked)
flag_rule_msgs <- if (length(checked_flags) > 0) {
vapply(names(checked_flags), function(nm) {
paste0(nm, flag_operators[nm], checked_flags[[nm]]$threshold)
}, character(1), USE.NAMES = FALSE)
} else {
NULL
}
cdisc_datasets <- pknca_res %>%
export_cdisc(
grouping_vars = extra_vars_to_keep,
flag_rules = flag_rule_msgs
)
# 12. Pivot results
pivoted_results <- pivot_wider_pknca_results(
myres = pknca_res,
flag_rules = flag_rules,
extra_vars_to_keep = extra_vars_to_keep
)