From 6f54851f0ebf158001b7812b762f5bdc14963a57 Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Thu, 27 Nov 2025 09:25:07 +0100 Subject: [PATCH 01/97] feat: add script template for all operations but slope exclusions & ratio calculations --- inst/shiny/www/templates/script_template.R | 88 ++++++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 inst/shiny/www/templates/script_template.R diff --git a/inst/shiny/www/templates/script_template.R b/inst/shiny/www/templates/script_template.R new file mode 100644 index 000000000..b547fcabd --- /dev/null +++ b/inst/shiny/www/templates/script_template.R @@ -0,0 +1,88 @@ +# Load the package (https://github.com/pharmaverse/aNCA) # +library(aNCA) + +# Load raw data # +data_path <- "inst/shiny/data/DummyRO_ADNCA.csv" +adnca_data <- read_pk(data_path) + +## Preprocess data ######################################## +mapping <- session$userData$mapping +names(mapping) <- gsub("select_", "", names(mapping)) +applied_filters <- session$userData$applied_filters + +preprocessed_adnca <- adnca_data %>% + + # Filter the data + apply_filters(applied_filters) %>% + + # Map columns to their standards + apply_mapping( + mapping = mapping, + desired_order = c( + "STUDYID", "USUBJID", "PARAM", "PCSPEC", "ATPTREF", + "AVAL", "AVALU", "AFRLT", "ARRLT", "NRRLT", "NFRLT", + "RRLTU", "ROUTE", "DOSETRT", "DOSEA", "DOSEU", "ADOSEDUR", + "VOLUME", "VOLUMEU", "TRTRINT", "METABFL" + ), + silent = FALSE + ) %>% + + # Derive METABFL column using PARAM metabolites + create_metabfl(mapping$select_Metabolites) + + +## Setup NCA settings in the PKNCA object ######################## +auc_data <- session$userData$settings$partial_aucs +units_table <- session$userData$units_table + +pknca_obj <- preprocessed_adnca %>% + + # Create from ADNCA the PKNCA object + PKNCA_create_data_object() %>% + + # Setup basic settings + PKNCA_update_data_object( + auc_data = auc_data, + method = session$userData$settings$method, + selected_analytes = session$userData$settings$analyte, + selected_profile = session$userData$settings$profile, + selected_pcspec = session$userData$settings$pcspec, + params = session$userData$settings$parameter_selection, + # hl_adj_rules = RULES + should_impute_c0 = session$userData$settings$data_imputation$impute_c0 + ) %>% + + # Define the desired units for the parameters (PPSTRESU) + { + pknca_obj <- . + pknca_obj[["units"]] <- units_table + pknca_obj + } + +## Run NCA calculations ######################################## +#ratios_table <- session$userData$ratios_table +flag_rules <- session$userData$settings$flags + +pknca_res <- pknca_obj %>% + # Run pk.nca and join subject and dose information to the results + PKNCA_calculate_nca() %>% + + # Add bioavailability results if requested + add_f_to_pknca_results(session$userData$settings$bioavailability) %>% + + # Apply standard CDISC names + mutate(PPTESTCD = translate_terms(PPTESTCD, "PKNCA", "PPTESTCD")) %>% + + # Derive secondary parameters (ratio parameters) + #calculate_table_ratios_app() %>% + + # Flag relevant parameters based on AUCPEO, AUCPEP & lambda span + PKNCA_hl_rules_exclusion( + rules = isolate(flag_rules) |> + purrr::keep(\(x) x$is.checked) |> + purrr::map(\(x) x$threshold) + ) + +## Obtain PP, ADPP, ADNCA & Pivoted results ######################### +cdisc_datasets <- export_cdisc(pknca_res) +pivoted_results <- pivot_wider_pknca_results(pknca_res) \ No newline at end of file From 867957dbacee880fc53d2723c027e8411c64f8d9 Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Thu, 27 Nov 2025 09:25:32 +0100 Subject: [PATCH 02/97] feat: make a function to deparse the fill-in the script template --- R/get_session_script_code.R | 100 ++++++++++++++++++++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100644 R/get_session_script_code.R diff --git a/R/get_session_script_code.R b/R/get_session_script_code.R new file mode 100644 index 000000000..cb19dfa91 --- /dev/null +++ b/R/get_session_script_code.R @@ -0,0 +1,100 @@ + +#' Generate a session script with session$ substitutions +#' +##' @param template_path Path to the R script template (e.g., script_template.R) +##' @param session The session object containing userData, etc. +##' @param output_path Path to write the resulting script file (e.g., "output_script.R") +##' @return The output_path (invisibly) +get_session_script_code <- function(template_path, session, output_path) { + # Helper to get value from session$userData by path (e.g., 'settings$method') + get_session_value <- function(path) { + parts <- strsplit(path, "\\$")[[1]] + obj <- session$userData + for (p in parts) { + if (is.null(obj[[p]])) return(NULL) + obj <- obj[[p]] + } + obj + } + + # Read template + script <- readLines(template_path, warn = FALSE) |> + paste(collapse="\n") + + # Find all session$userData$... or session$userData[[...]] or session$userData$...$... + # Regex for session$userData$foo or session$userData$foo$bar or session$userData[["foo"]] + pattern <- "session\\$userData(\\$[a-zA-Z0-9_]+(\\$[a-zA-Z0-9_]+)*)" + matches <- gregexpr(pattern, script, perl=TRUE)[[1]] + if (matches[1] == -1) return(script_lines) + + # Replace each match with deparsed value + for (i in rev(seq_along(matches))) { + start <- matches[i] + len <- attr(matches, "match.length")[i] + matched <- substr(script, start, start+len-1) + # Extract the path after session$userData$ + path <- sub("^session\\$userData\\$", "", matched) + value <- get_session_value(path) + deparsed <- clean_deparse(value) + script <- paste0( + substr(script, 1, start-1), + deparsed, + substr(script, start+len, nchar(script)) + ) + } + + # Split back into lines + script_lines <- strsplit(script, "\n")[[1]] + writeLines(script_lines, output_path) + invisible(output_path) +} + + +# Helper to cleanly deparse an object (data.frame, list, etc.) +clean_deparse <- function(obj, indent = 0) { + ind <- paste(rep(" ", indent), collapse = "") + if (is.data.frame(obj)) { + # Multi-line, indented, no trailing comma on last column + cols <- lapply(obj, function(col) { + d <- paste(deparse(col, width.cutoff=500), collapse="") + if (grepl("^c\\(", d)) d else paste0("c(", d, ")") + }) + col_strs <- paste0(ind, " ", names(obj), " = ", unlist(cols)) + # Add comma to all but last + if (length(col_strs) > 1) { + col_strs[1:(length(col_strs)-1)] <- paste0(col_strs[1:(length(col_strs)-1)], ",") + } + paste0( + "data.frame(\n", + paste(col_strs, collapse = "\n"), + "\n", ind, ")" + ) + } else if (is.list(obj)) { + # Deparse as list(a = ..., ...) + items <- lapply(names(obj), function(nm) paste0(nm, " = ", clean_deparse(obj[[nm]], indent + 1))) + # Add comma to all but last + if (length(items) > 1) { + items[1:(length(items)-1)] <- paste0(items[1:(length(items)-1)], ",") + } + item_strs <- paste0(ind, " ", items) + paste0( + "list(\n", + paste(item_strs, collapse = "\n"), + "\n", ind, ")" + ) + } else if (is.character(obj)) { + if (length(obj) == 1) paste0('"', obj, '"') + else paste0('c(', paste(sprintf('"%s"', obj), collapse=", "), ')') + } else if (is.numeric(obj) || is.integer(obj)) { + if (length(obj) == 1) as.character(obj) + else paste0('c(', paste(obj, collapse=", "), ')') + } else if (is.logical(obj)) { + if (length(obj) == 1) if (obj) 'TRUE' else 'FALSE' + else paste0('c(', paste(ifelse(obj, 'TRUE', 'FALSE'), collapse=", "), ')') + } else { + paste(deparse(obj, width.cutoff=500), collapse="") + } +} + +## Example usage: +# get_session_script_code(template_path = "inst/shiny/script_template.R", session, output_path = "output_script.R") From 84207cab5b5737dcbb8259a7aef56f23202399eb Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Thu, 27 Nov 2025 15:04:52 +0100 Subject: [PATCH 03/97] improve fun allowing reactives --- R/get_session_script_code.R | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/R/get_session_script_code.R b/R/get_session_script_code.R index cb19dfa91..45623212d 100644 --- a/R/get_session_script_code.R +++ b/R/get_session_script_code.R @@ -11,22 +11,29 @@ get_session_script_code <- function(template_path, session, output_path) { parts <- strsplit(path, "\\$")[[1]] obj <- session$userData for (p in parts) { - if (is.null(obj[[p]])) return(NULL) - obj <- obj[[p]] + if (endsWith(p, "()")) { + p_sub <- substr(p, start = 0, stop = (nchar(p) - 2)) + obj <- obj[[p_sub]]() + } else if (is.null(obj[[p]])){ + return(NULL) + } else { + obj <- obj[[p]] + } } + print(path) obj } # Read template script <- readLines(template_path, warn = FALSE) |> paste(collapse="\n") - + # Find all session$userData$... or session$userData[[...]] or session$userData$...$... # Regex for session$userData$foo or session$userData$foo$bar or session$userData[["foo"]] - pattern <- "session\\$userData(\\$[a-zA-Z0-9_]+(\\$[a-zA-Z0-9_]+)*)" + pattern <- "session\\$userData(\\$[a-zA-Z0-9_]+(\\(\\))?(\\$[a-zA-Z0-9_]+)*)" matches <- gregexpr(pattern, script, perl=TRUE)[[1]] if (matches[1] == -1) return(script_lines) - + # Replace each match with deparsed value for (i in rev(seq_along(matches))) { start <- matches[i] @@ -35,6 +42,7 @@ get_session_script_code <- function(template_path, session, output_path) { # Extract the path after session$userData$ path <- sub("^session\\$userData\\$", "", matched) value <- get_session_value(path) + deparsed <- clean_deparse(value) script <- paste0( substr(script, 1, start-1), @@ -49,7 +57,6 @@ get_session_script_code <- function(template_path, session, output_path) { invisible(output_path) } - # Helper to cleanly deparse an object (data.frame, list, etc.) clean_deparse <- function(obj, indent = 0) { ind <- paste(rep(" ", indent), collapse = "") From 27a933f7b9d7f2901761b597db66b4feb5ffead9 Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Thu, 27 Nov 2025 15:05:07 +0100 Subject: [PATCH 04/97] include reactives in template script --- inst/shiny/www/templates/script_template.R | 26 +++++++++++----------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/inst/shiny/www/templates/script_template.R b/inst/shiny/www/templates/script_template.R index b547fcabd..238b01fde 100644 --- a/inst/shiny/www/templates/script_template.R +++ b/inst/shiny/www/templates/script_template.R @@ -2,7 +2,7 @@ library(aNCA) # Load raw data # -data_path <- "inst/shiny/data/DummyRO_ADNCA.csv" +data_path <- session$userData$data_path adnca_data <- read_pk(data_path) ## Preprocess data ######################################## @@ -32,24 +32,24 @@ preprocessed_adnca <- adnca_data %>% ## Setup NCA settings in the PKNCA object ######################## -auc_data <- session$userData$settings$partial_aucs -units_table <- session$userData$units_table +auc_data <- session$userData$settings()$partial_aucs +units_table <- session$userData$units_table() pknca_obj <- preprocessed_adnca %>% # Create from ADNCA the PKNCA object PKNCA_create_data_object() %>% - + # Setup basic settings PKNCA_update_data_object( auc_data = auc_data, - method = session$userData$settings$method, - selected_analytes = session$userData$settings$analyte, - selected_profile = session$userData$settings$profile, - selected_pcspec = session$userData$settings$pcspec, - params = session$userData$settings$parameter_selection, + method = session$userData$settings()$method, + selected_analytes = session$userData$settings()$analyte, + selected_profile = session$userData$settings()$profile, + selected_pcspec = session$userData$settings()$pcspec, + params = session$userData$settings()$parameter_selection, # hl_adj_rules = RULES - should_impute_c0 = session$userData$settings$data_imputation$impute_c0 + should_impute_c0 = session$userData$settings()$data_imputation$impute_c0 ) %>% # Define the desired units for the parameters (PPSTRESU) @@ -61,14 +61,14 @@ pknca_obj <- preprocessed_adnca %>% ## Run NCA calculations ######################################## #ratios_table <- session$userData$ratios_table -flag_rules <- session$userData$settings$flags +flag_rules <- session$userData$settings()$flags pknca_res <- pknca_obj %>% # Run pk.nca and join subject and dose information to the results PKNCA_calculate_nca() %>% - + # Add bioavailability results if requested - add_f_to_pknca_results(session$userData$settings$bioavailability) %>% + add_f_to_pknca_results(session$userData$settings()$bioavailability) %>% # Apply standard CDISC names mutate(PPTESTCD = translate_terms(PPTESTCD, "PKNCA", "PPTESTCD")) %>% From 8fe876d2300180a79121f87a7423dee6f7e2ce54 Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Thu, 27 Nov 2025 15:05:39 +0100 Subject: [PATCH 05/97] feat: include in zip folder for code script --- inst/shiny/modules/tab_nca/nca_results.R | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/inst/shiny/modules/tab_nca/nca_results.R b/inst/shiny/modules/tab_nca/nca_results.R index b2ac1d8c3..8eb044e50 100644 --- a/inst/shiny/modules/tab_nca/nca_results.R +++ b/inst/shiny/modules/tab_nca/nca_results.R @@ -172,11 +172,27 @@ nca_results_server <- function(id, pknca_data, res_nca, settings, ratio_table, g dir.create(setts_tmpdir, recursive = TRUE) saveRDS(session$userData$settings(), paste0(setts_tmpdir, "/settings.rds")) + # Save a code R script template for the session + script_tmpdir <- file.path(output_tmpdir, "code") + dir.create(script_tmpdir, recursive = TRUE) + browser() + # saveRDS(session, paste0(script_tmpdir, "/session.rds")) + # session2 <- readRDS(paste0(script_tmpdir, "/session.rds")) + + script_template_path <- "www/templates/script_template.R" + get_session_script_code( + template_path = script_template_path, + session = session, + output_path = paste0(script_tmpdir, "/session_code.R") + ) + + # Zip all files in the output folder files <- list.files( output_tmpdir, pattern = paste0( ".(csv)|(rds)|(xpt)|(html)|(rda)", - "|(dose_escalation.pptx)|(dose_escalation.qmd)$" + "|(dose_escalation.pptx)|(dose_escalation.qmd)$", + "|(session_code.R)" ), recursive = TRUE ) From 3b8a051c5677081b38b3f97f334578f50af1fad7 Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Thu, 27 Nov 2025 15:06:15 +0100 Subject: [PATCH 06/97] save filters and mapping for userData --- inst/shiny/modules/tab_data/data_filtering.R | 5 ++++- inst/shiny/modules/tab_data/data_mapping.R | 3 +++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/inst/shiny/modules/tab_data/data_filtering.R b/inst/shiny/modules/tab_data/data_filtering.R index c553ce2e6..2e225f3a6 100644 --- a/inst/shiny/modules/tab_data/data_filtering.R +++ b/inst/shiny/modules/tab_data/data_filtering.R @@ -103,7 +103,7 @@ data_filtering_server <- function(id, raw_adnca_data) { # Extract filters from reactive values applied_filters <- lapply(reactiveValuesToList(filters), function(x) x()) %>% purrr::keep(\(x) !is.null(x)) - + if (length(applied_filters) == 0) return(raw_adnca_data()) applied_filters %>% @@ -111,6 +111,9 @@ data_filtering_server <- function(id, raw_adnca_data) { paste0(collapse = "\n") %>% paste0("Submitting the following filters:\n", .) %>% log_info() + + # Save the filters object + session$userData$applied_filters <- applied_filters # Filter and return data withCallingHandlers({ diff --git a/inst/shiny/modules/tab_data/data_mapping.R b/inst/shiny/modules/tab_data/data_mapping.R index 9cf142321..3c7507680 100644 --- a/inst/shiny/modules/tab_data/data_mapping.R +++ b/inst/shiny/modules/tab_data/data_mapping.R @@ -218,6 +218,9 @@ data_mapping_server <- function(id, adnca_data, trigger) { # Subset the list with the final names mapping_list[names_to_keep] }) + observe({ + session$userData$mapping <- mapping() + }) mapped_data <- reactive({ req(adnca_data()) From 76ebe77a13f91d330f8ef90866b9400763d2edc5 Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Thu, 27 Nov 2025 15:50:37 +0100 Subject: [PATCH 07/97] include in session datapath & final units table --- inst/shiny/modules/tab_data/data_upload.R | 7 +++++++ inst/shiny/modules/tab_nca.R | 5 +++++ inst/shiny/modules/tab_nca/nca_results.R | 5 +---- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/inst/shiny/modules/tab_data/data_upload.R b/inst/shiny/modules/tab_data/data_upload.R index d8bfcaad5..ece45d052 100644 --- a/inst/shiny/modules/tab_data/data_upload.R +++ b/inst/shiny/modules/tab_data/data_upload.R @@ -49,6 +49,13 @@ data_upload_server <- function(id) { }) datapath <- getOption("aNCA.datapath", NULL) + observe({ + if (!is.null(input$data_upload$datapath)) { + session$userData$data_path <- input$data_upload$datapath + } else { + session$userData$data_path <- system.file("shiny/data/Dummy_data.csv", package = "aNCA") + } + }) raw_data <- ( reactive({ diff --git a/inst/shiny/modules/tab_nca.R b/inst/shiny/modules/tab_nca.R index cbd861e8a..4d861ec59 100644 --- a/inst/shiny/modules/tab_nca.R +++ b/inst/shiny/modules/tab_nca.R @@ -189,6 +189,11 @@ tab_nca_server <- function(id, pknca_data, extra_group_vars) { }) %>% bindEvent(input$run_nca) + observe({ + req(res_nca()) + session$userData$final_units <- res_nca()$data$units + }) + #' Show slopes results pivoted_slopes <- reactive({ req(res_nca()) diff --git a/inst/shiny/modules/tab_nca/nca_results.R b/inst/shiny/modules/tab_nca/nca_results.R index 8eb044e50..649247638 100644 --- a/inst/shiny/modules/tab_nca/nca_results.R +++ b/inst/shiny/modules/tab_nca/nca_results.R @@ -175,10 +175,7 @@ nca_results_server <- function(id, pknca_data, res_nca, settings, ratio_table, g # Save a code R script template for the session script_tmpdir <- file.path(output_tmpdir, "code") dir.create(script_tmpdir, recursive = TRUE) - browser() - # saveRDS(session, paste0(script_tmpdir, "/session.rds")) - # session2 <- readRDS(paste0(script_tmpdir, "/session.rds")) - + script_template_path <- "www/templates/script_template.R" get_session_script_code( template_path = script_template_path, From b214e701948e754743a6eb820da39fa14bae754e Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Thu, 27 Nov 2025 15:50:52 +0100 Subject: [PATCH 08/97] adapt script template for final units --- inst/shiny/www/templates/script_template.R | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/inst/shiny/www/templates/script_template.R b/inst/shiny/www/templates/script_template.R index 238b01fde..a5ebbc402 100644 --- a/inst/shiny/www/templates/script_template.R +++ b/inst/shiny/www/templates/script_template.R @@ -30,10 +30,9 @@ preprocessed_adnca <- adnca_data %>% # Derive METABFL column using PARAM metabolites create_metabfl(mapping$select_Metabolites) - ## Setup NCA settings in the PKNCA object ######################## auc_data <- session$userData$settings()$partial_aucs -units_table <- session$userData$units_table() +units_table <- session$userData$final_units pknca_obj <- preprocessed_adnca %>% @@ -78,7 +77,7 @@ pknca_res <- pknca_obj %>% # Flag relevant parameters based on AUCPEO, AUCPEP & lambda span PKNCA_hl_rules_exclusion( - rules = isolate(flag_rules) |> + rules = flag_rules |> purrr::keep(\(x) x$is.checked) |> purrr::map(\(x) x$threshold) ) From 3e6ee2c547a26c978da62659d8db0669325e1d65 Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Thu, 27 Nov 2025 17:12:04 +0100 Subject: [PATCH 09/97] feat: add ratio table to the script --- NAMESPACE | 1 + R/ratio_calculations.R | 45 ++++++++++++++++++++++ inst/shiny/functions/ratio_table.R | 44 --------------------- inst/shiny/modules/tab_nca.R | 5 ++- inst/shiny/modules/tab_nca/setup.R | 1 - inst/shiny/www/templates/script_template.R | 10 ++--- 6 files changed, 55 insertions(+), 51 deletions(-) diff --git a/NAMESPACE b/NAMESPACE index 287851137..b60d8a95c 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -20,6 +20,7 @@ export(apply_mapping) export(calculate_f) export(calculate_ratios) export(calculate_summary_stats) +export(calculate_table_ratios_app) export(check_slope_rule_overlap) export(convert_volume_units) export(create_metabfl) diff --git a/R/ratio_calculations.R b/R/ratio_calculations.R index fdd2d273a..063723399 100644 --- a/R/ratio_calculations.R +++ b/R/ratio_calculations.R @@ -268,3 +268,48 @@ calculate_ratios.PKNCAresults <- function( data$result <- bind_rows(data$result, ratios_result) data } + +#' Apply Ratio Calculations to PKNCAresult Object +#' +#' This function takes a PKNCAresult object and a data.frame specifying ratio calculations +#' +#' @param res A PKNCAresult object. +#' @param ratio_table Data.frame with columns: +#' TestParameter, RefParameter, Reference, Test, AggregateSubject, AdjustingFactor. +#' @returns The updated PKNCAresult object with added rows in the `result` data.frame. +#' @export +calculate_table_ratios_app <- function(res, ratio_table) { + # Make a list to save all results + ratio_results <- vector("list", nrow(ratio_table)) + + # Loop through each row of the ratio_table + for (i in seq_len(nrow(ratio_table))) { + ratio_results[[i]] <- calculate_ratio_app( + res = res, + test_parameter = ratio_table$TestParameter[i], + ref_parameter = ratio_table$RefParameter[i], + test_group = ratio_table$TestGroups[i], + ref_group = ratio_table$RefGroups[i], + aggregate_subject = ratio_table$AggregateSubject[i], + adjusting_factor = as.numeric(ratio_table$AdjustingFactor[i]), + custom_pptestcd = if (ratio_table$PPTESTCD[i] == "") NULL else ratio_table$PPTESTCD[i] + ) + + if (nrow(ratio_results[[i]]) == 0) { + warning( + "Ratio ", ratio_table$PPTESTCD[i], " not computed.", + "No comparable groups found between RefGroups", + " (", ratio_table$RefGroups[i], ")", + "and TestGroups", + " (", ratio_table$TestGroups[i], ")" + ) + } + } + if (!"PPANMETH" %in% names(res$result)) { + res$result$PPANMETH <- "" + } + + # Combine all results into the original PKNCAresult object + res$result <- do.call(rbind, c(list(res$result), ratio_results)) + res +} diff --git a/inst/shiny/functions/ratio_table.R b/inst/shiny/functions/ratio_table.R index b517d7dd0..aecaecc9a 100644 --- a/inst/shiny/functions/ratio_table.R +++ b/inst/shiny/functions/ratio_table.R @@ -99,47 +99,3 @@ calculate_ratio_app <- function( .keep_all = TRUE) } -#' Apply Ratio Calculations to PKNCAresult Object -#' -#' This function takes a PKNCAresult object and a data.frame specifying ratio calculations -#' -#' @param res A PKNCAresult object. -#' @param ratio_table Data.frame with columns: -#' TestParameter, RefParameter, Reference, Test, AggregateSubject, AdjustingFactor. -#' @returns The updated PKNCAresult object with added rows in the `result` data.frame. -#' @export -calculate_table_ratios_app <- function(res, ratio_table) { - # Make a list to save all results - ratio_results <- vector("list", nrow(ratio_table)) - - # Loop through each row of the ratio_table - for (i in seq_len(nrow(ratio_table))) { - ratio_results[[i]] <- calculate_ratio_app( - res = res, - test_parameter = ratio_table$TestParameter[i], - ref_parameter = ratio_table$RefParameter[i], - test_group = ratio_table$TestGroups[i], - ref_group = ratio_table$RefGroups[i], - aggregate_subject = ratio_table$AggregateSubject[i], - adjusting_factor = as.numeric(ratio_table$AdjustingFactor[i]), - custom_pptestcd = if (ratio_table$PPTESTCD[i] == "") NULL else ratio_table$PPTESTCD[i] - ) - - if (nrow(ratio_results[[i]]) == 0) { - warning( - "Ratio ", ratio_table$PPTESTCD[i], " not computed.", - "No comparable groups found between RefGroups", - " (", ratio_table$RefGroups[i], ")", - "and TestGroups", - " (", ratio_table$TestGroups[i], ")" - ) - } - } - if (!"PPANMETH" %in% names(res$result)) { - res$result$PPANMETH <- "" - } - - # Combine all results into the original PKNCAresult object - res$result <- do.call(rbind, c(list(res$result), ratio_results)) - res -} diff --git a/inst/shiny/modules/tab_nca.R b/inst/shiny/modules/tab_nca.R index 4d861ec59..7d5cb7d8d 100644 --- a/inst/shiny/modules/tab_nca.R +++ b/inst/shiny/modules/tab_nca.R @@ -78,10 +78,13 @@ tab_nca_server <- function(id, pknca_data, extra_group_vars) { processed_pknca_data <- nca_setup$processed_pknca_data settings <- nca_setup$settings - session$userData$settings <- settings # This will be saved in the results zip folder ratio_table <- nca_setup$ratio_table slope_rules <- nca_setup$slope_rules + # This will be saved in the results zip folder + session$userData$settings <- settings + session$userData$ratio_table <- ratio_table + reactable_server("manual_slopes", slope_rules$manual_slopes) # List all irrelevant warnings to suppres in the NCA calculation diff --git a/inst/shiny/modules/tab_nca/setup.R b/inst/shiny/modules/tab_nca/setup.R index ecaa904a9..8a4cc1e2a 100644 --- a/inst/shiny/modules/tab_nca/setup.R +++ b/inst/shiny/modules/tab_nca/setup.R @@ -105,7 +105,6 @@ setup_server <- function(id, data, adnca_data) { id = "ratio_calculations_table", adnca_data = processed_pknca_data ) - session$userData$ratio_table <- reactive(ratio_table()) # Automatically update the units table when settings are uploaded. observeEvent(settings_override(), { diff --git a/inst/shiny/www/templates/script_template.R b/inst/shiny/www/templates/script_template.R index a5ebbc402..68f53b40f 100644 --- a/inst/shiny/www/templates/script_template.R +++ b/inst/shiny/www/templates/script_template.R @@ -59,7 +59,7 @@ pknca_obj <- preprocessed_adnca %>% } ## Run NCA calculations ######################################## -#ratios_table <- session$userData$ratios_table +ratio_table <- session$userData$ratio_table() flag_rules <- session$userData$settings()$flags pknca_res <- pknca_obj %>% @@ -72,15 +72,15 @@ pknca_res <- pknca_obj %>% # Apply standard CDISC names mutate(PPTESTCD = translate_terms(PPTESTCD, "PKNCA", "PPTESTCD")) %>% - # Derive secondary parameters (ratio parameters) - #calculate_table_ratios_app() %>% - # Flag relevant parameters based on AUCPEO, AUCPEP & lambda span PKNCA_hl_rules_exclusion( rules = flag_rules |> purrr::keep(\(x) x$is.checked) |> purrr::map(\(x) x$threshold) - ) + ) %>% + + # Derive secondary parameters (ratio parameters) + calculate_table_ratios_app(ratio_table) ## Obtain PP, ADPP, ADNCA & Pivoted results ######################### cdisc_datasets <- export_cdisc(pknca_res) From 07bc425538f9d57309a07ad00f6aefbdf3996d21 Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Thu, 27 Nov 2025 17:28:05 +0100 Subject: [PATCH 10/97] include slope_rules in userData --- inst/shiny/modules/tab_nca.R | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/inst/shiny/modules/tab_nca.R b/inst/shiny/modules/tab_nca.R index 7d5cb7d8d..bcd132744 100644 --- a/inst/shiny/modules/tab_nca.R +++ b/inst/shiny/modules/tab_nca.R @@ -84,7 +84,8 @@ tab_nca_server <- function(id, pknca_data, extra_group_vars) { # This will be saved in the results zip folder session$userData$settings <- settings session$userData$ratio_table <- ratio_table - + session$userData$slope_rules <- slope_rules + reactable_server("manual_slopes", slope_rules$manual_slopes) # List all irrelevant warnings to suppres in the NCA calculation From 0dedfcadcfe3ae83f98a5099cea4922cabbd05e6 Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Fri, 28 Nov 2025 09:44:20 +0100 Subject: [PATCH 11/97] generalize fun --- R/get_session_script_code.R | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/R/get_session_script_code.R b/R/get_session_script_code.R index 45623212d..d8f5eda05 100644 --- a/R/get_session_script_code.R +++ b/R/get_session_script_code.R @@ -11,16 +11,13 @@ get_session_script_code <- function(template_path, session, output_path) { parts <- strsplit(path, "\\$")[[1]] obj <- session$userData for (p in parts) { - if (endsWith(p, "()")) { - p_sub <- substr(p, start = 0, stop = (nchar(p) - 2)) - obj <- obj[[p_sub]]() - } else if (is.null(obj[[p]])){ - return(NULL) + if (inherits(obj[[p]], "reactive")) { + obj <- obj[[p]]() } else { obj <- obj[[p]] } + if (is.null(obj)) return(NULL) } - print(path) obj } From 68ef670d30ea3e1fd6d9a7d633edbc32d9486b6a Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Fri, 28 Nov 2025 09:44:37 +0100 Subject: [PATCH 12/97] integrate slope rules --- inst/shiny/www/templates/script_template.R | 34 +++++++++++++++------- 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/inst/shiny/www/templates/script_template.R b/inst/shiny/www/templates/script_template.R index 68f53b40f..672354ba1 100644 --- a/inst/shiny/www/templates/script_template.R +++ b/inst/shiny/www/templates/script_template.R @@ -31,7 +31,7 @@ preprocessed_adnca <- adnca_data %>% create_metabfl(mapping$select_Metabolites) ## Setup NCA settings in the PKNCA object ######################## -auc_data <- session$userData$settings()$partial_aucs +auc_data <- session$userData$settings$partial_aucs units_table <- session$userData$final_units pknca_obj <- preprocessed_adnca %>% @@ -42,13 +42,13 @@ pknca_obj <- preprocessed_adnca %>% # Setup basic settings PKNCA_update_data_object( auc_data = auc_data, - method = session$userData$settings()$method, - selected_analytes = session$userData$settings()$analyte, - selected_profile = session$userData$settings()$profile, - selected_pcspec = session$userData$settings()$pcspec, - params = session$userData$settings()$parameter_selection, + method = session$userData$settings$method, + selected_analytes = session$userData$settings$analyte, + selected_profile = session$userData$settings$profile, + selected_pcspec = session$userData$settings$pcspec, + params = session$userData$settings$parameter_selection, # hl_adj_rules = RULES - should_impute_c0 = session$userData$settings()$data_imputation$impute_c0 + should_impute_c0 = session$userData$settings$data_imputation$impute_c0 ) %>% # Define the desired units for the parameters (PPSTRESU) @@ -59,15 +59,29 @@ pknca_obj <- preprocessed_adnca %>% } ## Run NCA calculations ######################################## -ratio_table <- session$userData$ratio_table() -flag_rules <- session$userData$settings()$flags +slope_rules <- list( + manual_slopes = session$userData$slope_rules$manual_slopes, + profiles_per_subject = session$userData$slope_rules$profiles_per_subject, + slopes_groups = session$userData$slope_rules$slopes_groups +) +flag_rules <- session$userData$settings$flags +ratio_table <- session$userData$ratio_table pknca_res <- pknca_obj %>% + + # Apply half-life adjustments + filter_slopes( + slope_rules$manual_slopes, + slope_rules$profiles_per_subject, + slope_rules$slopes_groups, + check_reasons = TRUE + ) %>% + # Run pk.nca and join subject and dose information to the results PKNCA_calculate_nca() %>% # Add bioavailability results if requested - add_f_to_pknca_results(session$userData$settings()$bioavailability) %>% + add_f_to_pknca_results(session$userData$settings$bioavailability) %>% # Apply standard CDISC names mutate(PPTESTCD = translate_terms(PPTESTCD, "PKNCA", "PPTESTCD")) %>% From cf2d74bf63a08b54dd2218712a5265e5c403d69f Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Fri, 28 Nov 2025 09:53:16 +0100 Subject: [PATCH 13/97] refactor: lintr --- R/get_session_script_code.R | 38 ++++++++++---------- R/ratio_calculations.R | 6 ++-- inst/shiny/functions/ratio_table.R | 1 - inst/shiny/modules/tab_data/data_filtering.R | 4 +-- inst/shiny/modules/tab_nca/nca_results.R | 2 +- inst/shiny/www/templates/script_template.R | 23 ++++++------ 6 files changed, 37 insertions(+), 37 deletions(-) diff --git a/R/get_session_script_code.R b/R/get_session_script_code.R index d8f5eda05..00cd7faba 100644 --- a/R/get_session_script_code.R +++ b/R/get_session_script_code.R @@ -23,31 +23,31 @@ get_session_script_code <- function(template_path, session, output_path) { # Read template script <- readLines(template_path, warn = FALSE) |> - paste(collapse="\n") + paste(collapse = "\n") # Find all session$userData$... or session$userData[[...]] or session$userData$...$... # Regex for session$userData$foo or session$userData$foo$bar or session$userData[["foo"]] pattern <- "session\\$userData(\\$[a-zA-Z0-9_]+(\\(\\))?(\\$[a-zA-Z0-9_]+)*)" - matches <- gregexpr(pattern, script, perl=TRUE)[[1]] + matches <- gregexpr(pattern, script, perl = TRUE)[[1]] if (matches[1] == -1) return(script_lines) # Replace each match with deparsed value for (i in rev(seq_along(matches))) { start <- matches[i] len <- attr(matches, "match.length")[i] - matched <- substr(script, start, start+len-1) + matched <- substr(script, start, start + len - 1) # Extract the path after session$userData$ path <- sub("^session\\$userData\\$", "", matched) value <- get_session_value(path) deparsed <- clean_deparse(value) script <- paste0( - substr(script, 1, start-1), + substr(script, 1, start - 1), deparsed, - substr(script, start+len, nchar(script)) + substr(script, start + len, nchar(script)) ) } - + # Split back into lines script_lines <- strsplit(script, "\n")[[1]] writeLines(script_lines, output_path) @@ -60,13 +60,13 @@ clean_deparse <- function(obj, indent = 0) { if (is.data.frame(obj)) { # Multi-line, indented, no trailing comma on last column cols <- lapply(obj, function(col) { - d <- paste(deparse(col, width.cutoff=500), collapse="") + d <- paste(deparse(col, width.cutoff = 500), collapse = "") if (grepl("^c\\(", d)) d else paste0("c(", d, ")") }) col_strs <- paste0(ind, " ", names(obj), " = ", unlist(cols)) # Add comma to all but last if (length(col_strs) > 1) { - col_strs[1:(length(col_strs)-1)] <- paste0(col_strs[1:(length(col_strs)-1)], ",") + col_strs[1:(length(col_strs) - 1)] <- paste0(col_strs[1:(length(col_strs) - 1)], ",") } paste0( "data.frame(\n", @@ -75,10 +75,15 @@ clean_deparse <- function(obj, indent = 0) { ) } else if (is.list(obj)) { # Deparse as list(a = ..., ...) - items <- lapply(names(obj), function(nm) paste0(nm, " = ", clean_deparse(obj[[nm]], indent + 1))) + items <- lapply( + names(obj), + function(nm) { + paste0(nm, " = ", clean_deparse(obj[[nm]], indent + 1)) + } + ) # Add comma to all but last if (length(items) > 1) { - items[1:(length(items)-1)] <- paste0(items[1:(length(items)-1)], ",") + items[1:(length(items) - 1)] <- paste0(items[1:(length(items) - 1)], ",") } item_strs <- paste0(ind, " ", items) paste0( @@ -88,17 +93,14 @@ clean_deparse <- function(obj, indent = 0) { ) } else if (is.character(obj)) { if (length(obj) == 1) paste0('"', obj, '"') - else paste0('c(', paste(sprintf('"%s"', obj), collapse=", "), ')') + else paste0("c(", paste(sprintf('"%s"', obj), collapse = ", "), ")") } else if (is.numeric(obj) || is.integer(obj)) { if (length(obj) == 1) as.character(obj) - else paste0('c(', paste(obj, collapse=", "), ')') + else paste0("c(", paste(obj, collapse = ", "), ")") } else if (is.logical(obj)) { - if (length(obj) == 1) if (obj) 'TRUE' else 'FALSE' - else paste0('c(', paste(ifelse(obj, 'TRUE', 'FALSE'), collapse=", "), ')') + if (length(obj) == 1) if (obj) "TRUE" else "FALSE" + else paste0("c(", paste(ifelse(obj, "TRUE", "FALSE"), collapse = ", "), ")") } else { - paste(deparse(obj, width.cutoff=500), collapse="") + paste(deparse(obj, width.cutoff = 500), collapse = "") } } - -## Example usage: -# get_session_script_code(template_path = "inst/shiny/script_template.R", session, output_path = "output_script.R") diff --git a/R/ratio_calculations.R b/R/ratio_calculations.R index 063723399..858464d65 100644 --- a/R/ratio_calculations.R +++ b/R/ratio_calculations.R @@ -281,7 +281,7 @@ calculate_ratios.PKNCAresults <- function( calculate_table_ratios_app <- function(res, ratio_table) { # Make a list to save all results ratio_results <- vector("list", nrow(ratio_table)) - + # Loop through each row of the ratio_table for (i in seq_len(nrow(ratio_table))) { ratio_results[[i]] <- calculate_ratio_app( @@ -294,7 +294,7 @@ calculate_table_ratios_app <- function(res, ratio_table) { adjusting_factor = as.numeric(ratio_table$AdjustingFactor[i]), custom_pptestcd = if (ratio_table$PPTESTCD[i] == "") NULL else ratio_table$PPTESTCD[i] ) - + if (nrow(ratio_results[[i]]) == 0) { warning( "Ratio ", ratio_table$PPTESTCD[i], " not computed.", @@ -308,7 +308,7 @@ calculate_table_ratios_app <- function(res, ratio_table) { if (!"PPANMETH" %in% names(res$result)) { res$result$PPANMETH <- "" } - + # Combine all results into the original PKNCAresult object res$result <- do.call(rbind, c(list(res$result), ratio_results)) res diff --git a/inst/shiny/functions/ratio_table.R b/inst/shiny/functions/ratio_table.R index aecaecc9a..8a46de31a 100644 --- a/inst/shiny/functions/ratio_table.R +++ b/inst/shiny/functions/ratio_table.R @@ -98,4 +98,3 @@ calculate_ratio_app <- function( ), .keep_all = TRUE) } - diff --git a/inst/shiny/modules/tab_data/data_filtering.R b/inst/shiny/modules/tab_data/data_filtering.R index 2e225f3a6..81ac5ba1c 100644 --- a/inst/shiny/modules/tab_data/data_filtering.R +++ b/inst/shiny/modules/tab_data/data_filtering.R @@ -103,7 +103,7 @@ data_filtering_server <- function(id, raw_adnca_data) { # Extract filters from reactive values applied_filters <- lapply(reactiveValuesToList(filters), function(x) x()) %>% purrr::keep(\(x) !is.null(x)) - + if (length(applied_filters) == 0) return(raw_adnca_data()) applied_filters %>% @@ -111,7 +111,7 @@ data_filtering_server <- function(id, raw_adnca_data) { paste0(collapse = "\n") %>% paste0("Submitting the following filters:\n", .) %>% log_info() - + # Save the filters object session$userData$applied_filters <- applied_filters diff --git a/inst/shiny/modules/tab_nca/nca_results.R b/inst/shiny/modules/tab_nca/nca_results.R index 649247638..9fd2d20b6 100644 --- a/inst/shiny/modules/tab_nca/nca_results.R +++ b/inst/shiny/modules/tab_nca/nca_results.R @@ -175,7 +175,7 @@ nca_results_server <- function(id, pknca_data, res_nca, settings, ratio_table, g # Save a code R script template for the session script_tmpdir <- file.path(output_tmpdir, "code") dir.create(script_tmpdir, recursive = TRUE) - + script_template_path <- "www/templates/script_template.R" get_session_script_code( template_path = script_template_path, diff --git a/inst/shiny/www/templates/script_template.R b/inst/shiny/www/templates/script_template.R index 672354ba1..7f526bd7b 100644 --- a/inst/shiny/www/templates/script_template.R +++ b/inst/shiny/www/templates/script_template.R @@ -11,10 +11,10 @@ names(mapping) <- gsub("select_", "", names(mapping)) applied_filters <- session$userData$applied_filters preprocessed_adnca <- adnca_data %>% - + # Filter the data apply_filters(applied_filters) %>% - + # Map columns to their standards apply_mapping( mapping = mapping, @@ -26,7 +26,7 @@ preprocessed_adnca <- adnca_data %>% ), silent = FALSE ) %>% - + # Derive METABFL column using PARAM metabolites create_metabfl(mapping$select_Metabolites) @@ -35,7 +35,7 @@ auc_data <- session$userData$settings$partial_aucs units_table <- session$userData$final_units pknca_obj <- preprocessed_adnca %>% - + # Create from ADNCA the PKNCA object PKNCA_create_data_object() %>% @@ -47,10 +47,9 @@ pknca_obj <- preprocessed_adnca %>% selected_profile = session$userData$settings$profile, selected_pcspec = session$userData$settings$pcspec, params = session$userData$settings$parameter_selection, - # hl_adj_rules = RULES should_impute_c0 = session$userData$settings$data_imputation$impute_c0 - ) %>% - + ) %>% + # Define the desired units for the parameters (PPSTRESU) { pknca_obj <- . @@ -68,7 +67,7 @@ flag_rules <- session$userData$settings$flags ratio_table <- session$userData$ratio_table pknca_res <- pknca_obj %>% - + # Apply half-life adjustments filter_slopes( slope_rules$manual_slopes, @@ -76,16 +75,16 @@ pknca_res <- pknca_obj %>% slope_rules$slopes_groups, check_reasons = TRUE ) %>% - + # Run pk.nca and join subject and dose information to the results PKNCA_calculate_nca() %>% # Add bioavailability results if requested add_f_to_pknca_results(session$userData$settings$bioavailability) %>% - + # Apply standard CDISC names mutate(PPTESTCD = translate_terms(PPTESTCD, "PKNCA", "PPTESTCD")) %>% - + # Flag relevant parameters based on AUCPEO, AUCPEP & lambda span PKNCA_hl_rules_exclusion( rules = flag_rules |> @@ -98,4 +97,4 @@ pknca_res <- pknca_obj %>% ## Obtain PP, ADPP, ADNCA & Pivoted results ######################### cdisc_datasets <- export_cdisc(pknca_res) -pivoted_results <- pivot_wider_pknca_results(pknca_res) \ No newline at end of file +pivoted_results <- pivot_wider_pknca_results(pknca_res) From 2a898fc9e24217d2b6bc83fc737e26d53c8413cd Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Fri, 28 Nov 2025 10:04:05 +0100 Subject: [PATCH 14/97] man: add docs --- man/calculate_table_ratios_app.Rd | 20 ++++++++++++++++++++ man/get_session_script_code.Rd | 21 +++++++++++++++++++++ 2 files changed, 41 insertions(+) create mode 100644 man/calculate_table_ratios_app.Rd create mode 100644 man/get_session_script_code.Rd diff --git a/man/calculate_table_ratios_app.Rd b/man/calculate_table_ratios_app.Rd new file mode 100644 index 000000000..61a6f1567 --- /dev/null +++ b/man/calculate_table_ratios_app.Rd @@ -0,0 +1,20 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/ratio_calculations.R +\name{calculate_table_ratios_app} +\alias{calculate_table_ratios_app} +\title{Apply Ratio Calculations to PKNCAresult Object} +\usage{ +calculate_table_ratios_app(res, ratio_table) +} +\arguments{ +\item{res}{A PKNCAresult object.} + +\item{ratio_table}{Data.frame with columns: +TestParameter, RefParameter, Reference, Test, AggregateSubject, AdjustingFactor.} +} +\value{ +The updated PKNCAresult object with added rows in the \code{result} data.frame. +} +\description{ +This function takes a PKNCAresult object and a data.frame specifying ratio calculations +} diff --git a/man/get_session_script_code.Rd b/man/get_session_script_code.Rd new file mode 100644 index 000000000..79b7379f7 --- /dev/null +++ b/man/get_session_script_code.Rd @@ -0,0 +1,21 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/get_session_script_code.R +\name{get_session_script_code} +\alias{get_session_script_code} +\title{Generate a session script with session$ substitutions} +\usage{ +get_session_script_code(template_path, session, output_path) +} +\arguments{ +\item{template_path}{Path to the R script template (e.g., script_template.R)} + +\item{session}{The session object containing userData, etc.} + +\item{output_path}{Path to write the resulting script file (e.g., "output_script.R")} +} +\value{ +The output_path (invisibly) +} +\description{ +Generate a session script with session$ substitutions +} From 3a2f51df0f5ee64b211930d036fc83ee83426ad7 Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Fri, 28 Nov 2025 10:04:32 +0100 Subject: [PATCH 15/97] update wordlist & global variables --- R/zzz.R | 1 + inst/WORDLIST | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/R/zzz.R b/R/zzz.R index 45684005e..65a2bb741 100644 --- a/R/zzz.R +++ b/R/zzz.R @@ -104,6 +104,7 @@ "aucs", "aucs_extravascular", "aucs_intravascular", + "calculate_ratio_app", "color_var", "combn", "conv_factor", diff --git a/inst/WORDLIST b/inst/WORDLIST index 8c6a28ce3..02e0d86b9 100644 --- a/inst/WORDLIST +++ b/inst/WORDLIST @@ -8,6 +8,8 @@ ATPTREF AUClast AVAL AVALU +AdjustingFactor +AggregateSubject Analyte Buckeridge CDISC @@ -41,6 +43,7 @@ PKNCA PKNCAconc PKNCAdata PKNCAdose +PKNCAresult PKNCAresults PPORRES PPSTRES @@ -54,12 +57,14 @@ Plotly Pre RRLTU RStudio +RefParameter Req STUDYID TLG TLGs TRT TRTRINT +TestParameter Tooltip UI USUBJID @@ -115,6 +120,7 @@ tooltip tooltips ug unmapped +userData utilise visualizable yaml From 09eb1705474523b5230ff0d1b60fc460e7b5bd78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gerardo=20J=2E=20Rodr=C3=ADguez?= <68994823+Gero1999@users.noreply.github.com> Date: Fri, 28 Nov 2025 10:28:02 +0100 Subject: [PATCH 16/97] Apply suggestions from code review apply relevant copilot suggestions Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- R/get_session_script_code.R | 22 +++++++--------------- R/ratio_calculations.R | 2 +- inst/shiny/www/templates/script_template.R | 2 +- 3 files changed, 9 insertions(+), 17 deletions(-) diff --git a/R/get_session_script_code.R b/R/get_session_script_code.R index 00cd7faba..5e2bd1de1 100644 --- a/R/get_session_script_code.R +++ b/R/get_session_script_code.R @@ -31,23 +31,15 @@ get_session_script_code <- function(template_path, session, output_path) { matches <- gregexpr(pattern, script, perl = TRUE)[[1]] if (matches[1] == -1) return(script_lines) - # Replace each match with deparsed value - for (i in rev(seq_along(matches))) { - start <- matches[i] - len <- attr(matches, "match.length")[i] - matched <- substr(script, start, start + len - 1) - # Extract the path after session$userData$ + # Replace all matches with deparsed values in one pass + match_positions <- gregexpr(pattern, script, perl = TRUE)[[1]] + match_strings <- regmatches(script, list(match_positions)) + replacements <- lapply(match_strings, function(matched) { path <- sub("^session\\$userData\\$", "", matched) value <- get_session_value(path) - - deparsed <- clean_deparse(value) - script <- paste0( - substr(script, 1, start - 1), - deparsed, - substr(script, start + len, nchar(script)) - ) - } - + clean_deparse(value) + }) + regmatches(script, list(match_positions)) <- replacements # Split back into lines script_lines <- strsplit(script, "\n")[[1]] writeLines(script_lines, output_path) diff --git a/R/ratio_calculations.R b/R/ratio_calculations.R index 858464d65..d10f92a1d 100644 --- a/R/ratio_calculations.R +++ b/R/ratio_calculations.R @@ -271,7 +271,7 @@ calculate_ratios.PKNCAresults <- function( #' Apply Ratio Calculations to PKNCAresult Object #' -#' This function takes a PKNCAresult object and a data.frame specifying ratio calculations +#' This function takes a PKNCAresult object and a data.frame specifying ratio calculations, and applies each ratio calculation to the results. #' #' @param res A PKNCAresult object. #' @param ratio_table Data.frame with columns: diff --git a/inst/shiny/www/templates/script_template.R b/inst/shiny/www/templates/script_template.R index 7f526bd7b..16b8e5990 100644 --- a/inst/shiny/www/templates/script_template.R +++ b/inst/shiny/www/templates/script_template.R @@ -28,7 +28,7 @@ preprocessed_adnca <- adnca_data %>% ) %>% # Derive METABFL column using PARAM metabolites - create_metabfl(mapping$select_Metabolites) + create_metabfl(mapping$Metabolites) ## Setup NCA settings in the PKNCA object ######################## auc_data <- session$userData$settings$partial_aucs From 4f49a861d4923094be1c6bf195735058fc3e6b22 Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Fri, 28 Nov 2025 10:32:18 +0100 Subject: [PATCH 17/97] refactor: get_session_script_code --- R/get_session_script_code.R | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/R/get_session_script_code.R b/R/get_session_script_code.R index 5e2bd1de1..19381e2d2 100644 --- a/R/get_session_script_code.R +++ b/R/get_session_script_code.R @@ -1,10 +1,9 @@ - -#' Generate a session script with session$ substitutions +#' Generates a session script to replicate the App outputs #' -##' @param template_path Path to the R script template (e.g., script_template.R) -##' @param session The session object containing userData, etc. -##' @param output_path Path to write the resulting script file (e.g., "output_script.R") -##' @return The output_path (invisibly) +#' @param template_path Path to the R script template (e.g., script_template.R) +#' @param session The session object containing userData, etc. +#' @param output_path Path to write the resulting script file (e.g., "output_script.R") +#' @return The output_path (invisibly) get_session_script_code <- function(template_path, session, output_path) { # Helper to get value from session$userData by path (e.g., 'settings$method') get_session_value <- function(path) { @@ -29,7 +28,18 @@ get_session_script_code <- function(template_path, session, output_path) { # Regex for session$userData$foo or session$userData$foo$bar or session$userData[["foo"]] pattern <- "session\\$userData(\\$[a-zA-Z0-9_]+(\\(\\))?(\\$[a-zA-Z0-9_]+)*)" matches <- gregexpr(pattern, script, perl = TRUE)[[1]] - if (matches[1] == -1) return(script_lines) + + # Just write the original template if there are no substitutions needed + # with a warning message + if (matches[1] == -1) { + script_lines <- strsplit(script, "\n")[[1]] + writeLines(script_lines, output_path) + warning( + "Please, the script template should have session$userData calls in order to work. ", + "Did you accidentally modify the original template?" + ) + return(invisible(output_path)) + } # Replace all matches with deparsed values in one pass match_positions <- gregexpr(pattern, script, perl = TRUE)[[1]] From bc3fb4d103f3a9debe46c3c9ae80c4c9836ec094 Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Fri, 28 Nov 2025 11:24:39 +0100 Subject: [PATCH 18/97] refactor: lintr --- R/get_session_script_code.R | 37 +++++++++++++++++-------------------- 1 file changed, 17 insertions(+), 20 deletions(-) diff --git a/R/get_session_script_code.R b/R/get_session_script_code.R index 19381e2d2..8d5ac033c 100644 --- a/R/get_session_script_code.R +++ b/R/get_session_script_code.R @@ -1,4 +1,4 @@ -#' Generates a session script to replicate the App outputs +#' Generate a session script code in R that can replicate the App outputs #' #' @param template_path Path to the R script template (e.g., script_template.R) #' @param session The session object containing userData, etc. @@ -28,28 +28,25 @@ get_session_script_code <- function(template_path, session, output_path) { # Regex for session$userData$foo or session$userData$foo$bar or session$userData[["foo"]] pattern <- "session\\$userData(\\$[a-zA-Z0-9_]+(\\(\\))?(\\$[a-zA-Z0-9_]+)*)" matches <- gregexpr(pattern, script, perl = TRUE)[[1]] - - # Just write the original template if there are no substitutions needed - # with a warning message - if (matches[1] == -1) { - script_lines <- strsplit(script, "\n")[[1]] - writeLines(script_lines, output_path) - warning( - "Please, the script template should have session$userData calls in order to work. ", - "Did you accidentally modify the original template?" - ) - return(invisible(output_path)) - } + if (matches[1] == -1) return(script_lines) - # Replace all matches with deparsed values in one pass - match_positions <- gregexpr(pattern, script, perl = TRUE)[[1]] - match_strings <- regmatches(script, list(match_positions)) - replacements <- lapply(match_strings, function(matched) { + # Replace each match with deparsed value + for (i in rev(seq_along(matches))) { + start <- matches[i] + len <- attr(matches, "match.length")[i] + matched <- substr(script, start, start + len - 1) + # Extract the path after session$userData$ path <- sub("^session\\$userData\\$", "", matched) value <- get_session_value(path) - clean_deparse(value) - }) - regmatches(script, list(match_positions)) <- replacements + + deparsed <- clean_deparse(value) + script <- paste0( + substr(script, 1, start - 1), + deparsed, + substr(script, start + len, nchar(script)) + ) + } + # Split back into lines script_lines <- strsplit(script, "\n")[[1]] writeLines(script_lines, output_path) From bc107241590137cf3ce8c07773270432a509782e Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Fri, 28 Nov 2025 11:37:59 +0100 Subject: [PATCH 19/97] lintr, roxygen2 --- R/ratio_calculations.R | 2 +- man/calculate_table_ratios_app.Rd | 2 +- man/get_session_script_code.Rd | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/R/ratio_calculations.R b/R/ratio_calculations.R index d10f92a1d..54f2d2d5c 100644 --- a/R/ratio_calculations.R +++ b/R/ratio_calculations.R @@ -271,7 +271,7 @@ calculate_ratios.PKNCAresults <- function( #' Apply Ratio Calculations to PKNCAresult Object #' -#' This function takes a PKNCAresult object and a data.frame specifying ratio calculations, and applies each ratio calculation to the results. +#' This function takes a PKNCAresult object and a data.frame specifying ratio calculations. #' #' @param res A PKNCAresult object. #' @param ratio_table Data.frame with columns: diff --git a/man/calculate_table_ratios_app.Rd b/man/calculate_table_ratios_app.Rd index 61a6f1567..38289385d 100644 --- a/man/calculate_table_ratios_app.Rd +++ b/man/calculate_table_ratios_app.Rd @@ -16,5 +16,5 @@ TestParameter, RefParameter, Reference, Test, AggregateSubject, AdjustingFactor. The updated PKNCAresult object with added rows in the \code{result} data.frame. } \description{ -This function takes a PKNCAresult object and a data.frame specifying ratio calculations +This function takes a PKNCAresult object and a data.frame specifying ratio calculations. } diff --git a/man/get_session_script_code.Rd b/man/get_session_script_code.Rd index 79b7379f7..d829c85d7 100644 --- a/man/get_session_script_code.Rd +++ b/man/get_session_script_code.Rd @@ -2,7 +2,7 @@ % Please edit documentation in R/get_session_script_code.R \name{get_session_script_code} \alias{get_session_script_code} -\title{Generate a session script with session$ substitutions} +\title{Generate a session script code in R that can replicate the App outputs} \usage{ get_session_script_code(template_path, session, output_path) } @@ -17,5 +17,5 @@ get_session_script_code(template_path, session, output_path) The output_path (invisibly) } \description{ -Generate a session script with session$ substitutions +Generate a session script code in R that can replicate the App outputs } From 81667bbb48cbea97e7f02b286d0e757d560d6cc7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gerardo=20J=2E=20Rodr=C3=ADguez?= <68994823+Gero1999@users.noreply.github.com> Date: Fri, 28 Nov 2025 12:00:07 +0100 Subject: [PATCH 20/97] Apply suggestions from code review (m-kolomanski) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Mateusz Kołomański <63905560+m-kolomanski@users.noreply.github.com> --- R/get_session_script_code.R | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/R/get_session_script_code.R b/R/get_session_script_code.R index 8d5ac033c..1468e120c 100644 --- a/R/get_session_script_code.R +++ b/R/get_session_script_code.R @@ -59,13 +59,21 @@ clean_deparse <- function(obj, indent = 0) { if (is.data.frame(obj)) { # Multi-line, indented, no trailing comma on last column cols <- lapply(obj, function(col) { - d <- paste(deparse(col, width.cutoff = 500), collapse = "") - if (grepl("^c\\(", d)) d else paste0("c(", d, ")") + d <- col |> + deparse(width.cutoff = 500) |> + paste(collapse = "") + + if (!grepl("^c\\(", d)) { + d <- paste0("c(", d, ")") + } + + d }) col_strs <- paste0(ind, " ", names(obj), " = ", unlist(cols)) # Add comma to all but last if (length(col_strs) > 1) { - col_strs[1:(length(col_strs) - 1)] <- paste0(col_strs[1:(length(col_strs) - 1)], ",") + not_last <- 1:(length(col_strs) - 1) + col_strs[not_last] <- paste0(col_strs[not_last], ",") } paste0( "data.frame(\n", @@ -82,7 +90,8 @@ clean_deparse <- function(obj, indent = 0) { ) # Add comma to all but last if (length(items) > 1) { - items[1:(length(items) - 1)] <- paste0(items[1:(length(items) - 1)], ",") + not_last <- 1:(length(items) - 1) + items[not_last] <- paste0(items[not_last], ",") } item_strs <- paste0(ind, " ", items) paste0( From c825d489e199f47fbe2c9721ae570099cae97f87 Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Fri, 28 Nov 2025 13:42:40 +0100 Subject: [PATCH 21/97] refactor: use switch for clean_deparse --- R/get_session_script_code.R | 113 +++++++++++++++++++----------------- 1 file changed, 61 insertions(+), 52 deletions(-) diff --git a/R/get_session_script_code.R b/R/get_session_script_code.R index 1468e120c..23603dfc8 100644 --- a/R/get_session_script_code.R +++ b/R/get_session_script_code.R @@ -56,59 +56,68 @@ get_session_script_code <- function(template_path, session, output_path) { # Helper to cleanly deparse an object (data.frame, list, etc.) clean_deparse <- function(obj, indent = 0) { ind <- paste(rep(" ", indent), collapse = "") - if (is.data.frame(obj)) { - # Multi-line, indented, no trailing comma on last column - cols <- lapply(obj, function(col) { - d <- col |> - deparse(width.cutoff = 500) |> - paste(collapse = "") - - if (!grepl("^c\\(", d)) { - d <- paste0("c(", d, ")") + obj_class <- class(obj)[1] + + switch( + obj_class, + data.frame = { + # Multi-line, indented representation of a data.frame + cols <- lapply(obj, function(col) { + d <- col |> + deparse(width.cutoff = 500) |> + paste(collapse = "") + if (!grepl("^c\\(", d)) { + d <- paste0("c(", d, ")") + } + d + }) + col_strs <- paste0(ind, " ", names(obj), " = ", unlist(cols)) + if (length(col_strs) > 1) { + not_last <- 1:(length(col_strs) - 1) + col_strs[not_last] <- paste0(col_strs[not_last], ",") } - - d - }) - col_strs <- paste0(ind, " ", names(obj), " = ", unlist(cols)) - # Add comma to all but last - if (length(col_strs) > 1) { - not_last <- 1:(length(col_strs) - 1) - col_strs[not_last] <- paste0(col_strs[not_last], ",") - } - paste0( - "data.frame(\n", - paste(col_strs, collapse = "\n"), - "\n", ind, ")" - ) - } else if (is.list(obj)) { - # Deparse as list(a = ..., ...) - items <- lapply( - names(obj), - function(nm) { - paste0(nm, " = ", clean_deparse(obj[[nm]], indent + 1)) + paste0("data.frame(\n", paste(col_strs, collapse = "\n"), "\n", ind, ")") + }, + + list = { + # Deparse as list(a = ..., ...) and handle unnamed elements by position + n <- length(obj) + nms <- names(obj) + items <- vapply(seq_len(n), FUN.VALUE = "", function(i) { + name <- if (!is.null(nms) && nzchar(nms[i])) nms[i] else paste0("V", i) + val <- obj[[i]] + paste0(name, " = ", clean_deparse(val, indent + 1)) + }) + if (length(items) > 1) { + not_last <- seq_len(length(items) - 1) + items[not_last] <- paste0(items[not_last], ",") } - ) - # Add comma to all but last - if (length(items) > 1) { - not_last <- 1:(length(items) - 1) - items[not_last] <- paste0(items[not_last], ",") + item_strs <- paste0(ind, " ", items) + paste0("list(\n", paste(item_strs, collapse = "\n"), "\n", ind, ")") + }, + + character = { + if (length(obj) == 1) paste0('"', obj, '"') + else paste0("c(", paste(sprintf('"%s"', obj), collapse = ", "), ")") + }, + + numeric = { + if (length(obj) == 1) as.character(obj) + else paste0("c(", paste(obj, collapse = ", "), ")") + }, + integer = { + if (length(obj) == 1) as.character(obj) + else paste0("c(", paste(obj, collapse = ", "), ")") + }, + + logical = { + if (length(obj) == 1) if (obj) "TRUE" else "FALSE" + else paste0("c(", paste(ifelse(obj, "TRUE", "FALSE"), collapse = ", "), ")") + }, + + { + # default: fallback to deparse + paste(deparse(obj, width.cutoff = 500), collapse = "") } - item_strs <- paste0(ind, " ", items) - paste0( - "list(\n", - paste(item_strs, collapse = "\n"), - "\n", ind, ")" - ) - } else if (is.character(obj)) { - if (length(obj) == 1) paste0('"', obj, '"') - else paste0("c(", paste(sprintf('"%s"', obj), collapse = ", "), ")") - } else if (is.numeric(obj) || is.integer(obj)) { - if (length(obj) == 1) as.character(obj) - else paste0("c(", paste(obj, collapse = ", "), ")") - } else if (is.logical(obj)) { - if (length(obj) == 1) if (obj) "TRUE" else "FALSE" - else paste0("c(", paste(ifelse(obj, "TRUE", "FALSE"), collapse = ", "), ")") - } else { - paste(deparse(obj, width.cutoff = 500), collapse = "") - } + ) } From 9c0d99376d1aecdd48d16492ddba92ef18e2fea7 Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Fri, 28 Nov 2025 13:43:12 +0100 Subject: [PATCH 22/97] test: clean_deparse() unit tests --- tests/testthat/test-get_session_script_code.R | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 tests/testthat/test-get_session_script_code.R diff --git a/tests/testthat/test-get_session_script_code.R b/tests/testthat/test-get_session_script_code.R new file mode 100644 index 000000000..d5f6bd8cb --- /dev/null +++ b/tests/testthat/test-get_session_script_code.R @@ -0,0 +1,36 @@ +# Tests for clean_deparse function +describe("clean_deparse()", { + it("formats character single and vector correctly", { + expect_equal(clean_deparse("hello"), '"hello"') + expect_equal(clean_deparse(c("a", "b")), 'c("a", "b")') + }) + + it("formats numeric and integer values correctly", { + expect_equal(clean_deparse(1.23), "1.23") + expect_equal(clean_deparse(c(1, 2)), "c(1, 2)") + expect_equal(clean_deparse(as.integer(3)), "3") + expect_equal(clean_deparse(as.integer(c(4L,5L))), "c(4, 5)") + }) + + it("formats logical values correctly", { + expect_equal(clean_deparse(TRUE), "TRUE") + expect_equal(clean_deparse(c(TRUE, FALSE)), "c(TRUE, FALSE)") + }) + + it("formats named lists correctly", { + l <- list(a = 1, b = "x") + exp_named <- "list(\n a = 1,\n b = \"x\"\n)" + expect_equal(clean_deparse(l), exp_named) + }) + + it("renders data.frame as data.frame(...) with per-column vectors", { + df <- data.frame(x = c(1,2), y = c("a", "b"), stringsAsFactors = FALSE) + exp_df <- paste0( + "data.frame(\n", + " x = c(1, 2),\n", + " y = c(\"a\", \"b\")\n", + ")" + ) + expect_equal(clean_deparse(df), exp_df) + }) +}) From fe89909acc417b985727e137fe1336cb23073f25 Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Fri, 28 Nov 2025 13:46:51 +0100 Subject: [PATCH 23/97] change warning to error in get_session_script_code.R --- R/get_session_script_code.R | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/R/get_session_script_code.R b/R/get_session_script_code.R index 23603dfc8..cf2bb6107 100644 --- a/R/get_session_script_code.R +++ b/R/get_session_script_code.R @@ -28,7 +28,12 @@ get_session_script_code <- function(template_path, session, output_path) { # Regex for session$userData$foo or session$userData$foo$bar or session$userData[["foo"]] pattern <- "session\\$userData(\\$[a-zA-Z0-9_]+(\\(\\))?(\\$[a-zA-Z0-9_]+)*)" matches <- gregexpr(pattern, script, perl = TRUE)[[1]] - if (matches[1] == -1) return(script_lines) + if (matches[1] == -1) { + stop( + "Template has no placeholders *(session$userData...) to substitute.", + "Did you accidentally modify it?" + ) + } # Replace each match with deparsed value for (i in rev(seq_along(matches))) { From 28e44f1f108535686d5ad38f05a9186675bbb833 Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Fri, 28 Nov 2025 13:50:16 +0100 Subject: [PATCH 24/97] refactor: lintr --- tests/testthat/test-get_session_script_code.R | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/testthat/test-get_session_script_code.R b/tests/testthat/test-get_session_script_code.R index d5f6bd8cb..80f506558 100644 --- a/tests/testthat/test-get_session_script_code.R +++ b/tests/testthat/test-get_session_script_code.R @@ -9,7 +9,7 @@ describe("clean_deparse()", { expect_equal(clean_deparse(1.23), "1.23") expect_equal(clean_deparse(c(1, 2)), "c(1, 2)") expect_equal(clean_deparse(as.integer(3)), "3") - expect_equal(clean_deparse(as.integer(c(4L,5L))), "c(4, 5)") + expect_equal(clean_deparse(as.integer(c(4L, 5L))), "c(4, 5)") }) it("formats logical values correctly", { @@ -24,7 +24,7 @@ describe("clean_deparse()", { }) it("renders data.frame as data.frame(...) with per-column vectors", { - df <- data.frame(x = c(1,2), y = c("a", "b"), stringsAsFactors = FALSE) + df <- data.frame(x = c(1, 2), y = c("a", "b"), stringsAsFactors = FALSE) exp_df <- paste0( "data.frame(\n", " x = c(1, 2),\n", From 87a25c8fdf817343e6bca98229865afbca1a9eac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gerardo=20J=2E=20Rodr=C3=ADguez?= <68994823+Gero1999@users.noreply.github.com> Date: Fri, 28 Nov 2025 14:37:49 +0100 Subject: [PATCH 25/97] Apply suggestions from copilot review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- R/get_session_script_code.R | 6 ++++-- R/ratio_calculations.R | 2 +- inst/shiny/modules/tab_nca/nca_results.R | 4 ++-- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/R/get_session_script_code.R b/R/get_session_script_code.R index cf2bb6107..5e57be054 100644 --- a/R/get_session_script_code.R +++ b/R/get_session_script_code.R @@ -4,6 +4,7 @@ #' @param session The session object containing userData, etc. #' @param output_path Path to write the resulting script file (e.g., "output_script.R") #' @return The output_path (invisibly) +#' @export get_session_script_code <- function(template_path, session, output_path) { # Helper to get value from session$userData by path (e.g., 'settings$method') get_session_value <- function(path) { @@ -30,7 +31,7 @@ get_session_script_code <- function(template_path, session, output_path) { matches <- gregexpr(pattern, script, perl = TRUE)[[1]] if (matches[1] == -1) { stop( - "Template has no placeholders *(session$userData...) to substitute.", + "Template has no placeholders (session$userData...) to substitute.", "Did you accidentally modify it?" ) } @@ -71,7 +72,8 @@ clean_deparse <- function(obj, indent = 0) { d <- col |> deparse(width.cutoff = 500) |> paste(collapse = "") - if (!grepl("^c\\(", d)) { + # Only wrap in c(...) if length > 1 + if (length(col) > 1 && !grepl("^c\\(", d)) { d <- paste0("c(", d, ")") } d diff --git a/R/ratio_calculations.R b/R/ratio_calculations.R index 54f2d2d5c..9fac2ec10 100644 --- a/R/ratio_calculations.R +++ b/R/ratio_calculations.R @@ -275,7 +275,7 @@ calculate_ratios.PKNCAresults <- function( #' #' @param res A PKNCAresult object. #' @param ratio_table Data.frame with columns: -#' TestParameter, RefParameter, Reference, Test, AggregateSubject, AdjustingFactor. +#' TestParameter, RefParameter, Reference, Test, AggregateSubject, AdjustingFactor, TestGroups, RefGroups, PPTESTCD. #' @returns The updated PKNCAresult object with added rows in the `result` data.frame. #' @export calculate_table_ratios_app <- function(res, ratio_table) { diff --git a/inst/shiny/modules/tab_nca/nca_results.R b/inst/shiny/modules/tab_nca/nca_results.R index 9fd2d20b6..88219f8d6 100644 --- a/inst/shiny/modules/tab_nca/nca_results.R +++ b/inst/shiny/modules/tab_nca/nca_results.R @@ -188,8 +188,8 @@ nca_results_server <- function(id, pknca_data, res_nca, settings, ratio_table, g output_tmpdir, pattern = paste0( ".(csv)|(rds)|(xpt)|(html)|(rda)", - "|(dose_escalation.pptx)|(dose_escalation.qmd)$", - "|(session_code.R)" + "|(dose_escalation.pptx)|(dose_escalation.qmd)", + "|(session_code.R)$" ), recursive = TRUE ) From 0cf7275acd97dc9885422072a8c8ac6ab8eb5b6a Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Fri, 28 Nov 2025 14:43:33 +0100 Subject: [PATCH 26/97] handle empty cases in clean_deparse --- R/get_session_script_code.R | 3 +++ tests/testthat/test-get_session_script_code.R | 11 +++++++++++ 2 files changed, 14 insertions(+) diff --git a/R/get_session_script_code.R b/R/get_session_script_code.R index 5e57be054..ee43e0809 100644 --- a/R/get_session_script_code.R +++ b/R/get_session_script_code.R @@ -63,6 +63,9 @@ get_session_script_code <- function(template_path, session, output_path) { clean_deparse <- function(obj, indent = 0) { ind <- paste(rep(" ", indent), collapse = "") obj_class <- class(obj)[1] + if (length(obj) == 0) { + return(paste0(obj_class, "()")) + } switch( obj_class, diff --git a/tests/testthat/test-get_session_script_code.R b/tests/testthat/test-get_session_script_code.R index 80f506558..e85d56b6e 100644 --- a/tests/testthat/test-get_session_script_code.R +++ b/tests/testthat/test-get_session_script_code.R @@ -33,4 +33,15 @@ describe("clean_deparse()", { ) expect_equal(clean_deparse(df), exp_df) }) + + it("renders a NULL object correctly", { + expect_equal(clean_deparse(NULL), "NULL") + }) + + it("renders empty classes correctly", { + expect_equal(clean_deparse(list()), "list()") + expect_equal(clean_deparse(data.frame()), "data.frame()") + expect_equal(clean_deparse(character(0)), "character()") + expect_equal(clean_deparse(numeric(0)), "numeric()") + }) }) From 16bf1643a06eb4e397397e8839137bb0df9f0d4a Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Fri, 28 Nov 2025 15:28:42 +0100 Subject: [PATCH 27/97] consider tbl_df option as data.frame for switch --- R/get_session_script_code.R | 7 ++++++- tests/testthat/test-get_session_script_code.R | 11 +++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/R/get_session_script_code.R b/R/get_session_script_code.R index ee43e0809..5900e5caf 100644 --- a/R/get_session_script_code.R +++ b/R/get_session_script_code.R @@ -63,13 +63,18 @@ get_session_script_code <- function(template_path, session, output_path) { clean_deparse <- function(obj, indent = 0) { ind <- paste(rep(" ", indent), collapse = "") obj_class <- class(obj)[1] - if (length(obj) == 0) { + if (length(obj) == 0 && !is.null(obj)) { return(paste0(obj_class, "()")) } switch( obj_class, + "tbl_df" = , data.frame = { + + # If the data.frame has zero rows, return an empty constructor string + if (nrow(obj) == 0) return("data.frame()") + # Multi-line, indented representation of a data.frame cols <- lapply(obj, function(col) { d <- col |> diff --git a/tests/testthat/test-get_session_script_code.R b/tests/testthat/test-get_session_script_code.R index e85d56b6e..24459c6fb 100644 --- a/tests/testthat/test-get_session_script_code.R +++ b/tests/testthat/test-get_session_script_code.R @@ -34,6 +34,17 @@ describe("clean_deparse()", { expect_equal(clean_deparse(df), exp_df) }) + it("renders tbl_df as data.frame(...)", { + df <- dplyr::tibble(x = c(1, 2), y = c("a", "b")) + exp_df <- paste0( + "data.frame(\n", + " x = c(1, 2),\n", + " y = c(\"a\", \"b\")\n", + ")" + ) + expect_equal(clean_deparse(df), exp_df) + }) + it("renders a NULL object correctly", { expect_equal(clean_deparse(NULL), "NULL") }) From 198cd9e8aaaf087359b93faed550c804230344a4 Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Fri, 28 Nov 2025 15:58:35 +0100 Subject: [PATCH 28/97] switch has a high cyclomatic complexity, do instead methods --- R/get_session_script_code.R | 125 ++++++++++++++++++------------------ 1 file changed, 61 insertions(+), 64 deletions(-) diff --git a/R/get_session_script_code.R b/R/get_session_script_code.R index 5900e5caf..7c8bac6e3 100644 --- a/R/get_session_script_code.R +++ b/R/get_session_script_code.R @@ -60,79 +60,76 @@ get_session_script_code <- function(template_path, session, output_path) { } # Helper to cleanly deparse an object (data.frame, list, etc.) +#' @noRd clean_deparse <- function(obj, indent = 0) { - ind <- paste(rep(" ", indent), collapse = "") - obj_class <- class(obj)[1] + # Handle trivial length-0 constructors (character(0), numeric(0), list(), data.frame(), ...) if (length(obj) == 0 && !is.null(obj)) { - return(paste0(obj_class, "()")) + return(paste0(class(obj)[1], "()")) + } + # For single-element atomic numeric/integer/logical return bare representation + if (length(obj) == 1 && class(obj)[1] %in% c("integer", "numeric", "logical")) { + return(as.character(obj)) } + UseMethod("clean_deparse") +} - switch( - obj_class, - "tbl_df" = , - data.frame = { +#' @noRd +clean_deparse.default <- function(obj, indent = 0) { + paste(deparse(obj, width.cutoff = 500), collapse = "") +} - # If the data.frame has zero rows, return an empty constructor string - if (nrow(obj) == 0) return("data.frame()") +#' @noRd +clean_deparse.data.frame <- function(obj, indent = 0) { + ind <- paste(rep(" ", indent), collapse = "") + if (nrow(obj) == 0) return("data.frame()") - # Multi-line, indented representation of a data.frame - cols <- lapply(obj, function(col) { - d <- col |> - deparse(width.cutoff = 500) |> - paste(collapse = "") - # Only wrap in c(...) if length > 1 - if (length(col) > 1 && !grepl("^c\\(", d)) { - d <- paste0("c(", d, ")") - } - d - }) - col_strs <- paste0(ind, " ", names(obj), " = ", unlist(cols)) - if (length(col_strs) > 1) { - not_last <- 1:(length(col_strs) - 1) - col_strs[not_last] <- paste0(col_strs[not_last], ",") - } - paste0("data.frame(\n", paste(col_strs, collapse = "\n"), "\n", ind, ")") - }, + cols <- lapply(obj, function(col) { + d <- deparse(col, width.cutoff = 500) |> paste(collapse = "") + if (length(col) > 1 && !grepl("^c\\(", d)) d <- paste0("c(", d, ")") + d + }) + col_strs <- paste0(ind, " ", names(obj), " = ", unlist(cols)) + if (length(col_strs) > 1) { + not_last <- seq_len(length(col_strs) - 1) + col_strs[not_last] <- paste0(col_strs[not_last], ",") + } + paste0("data.frame(\n", paste(col_strs, collapse = "\n"), "\n", ind, ")") +} - list = { - # Deparse as list(a = ..., ...) and handle unnamed elements by position - n <- length(obj) - nms <- names(obj) - items <- vapply(seq_len(n), FUN.VALUE = "", function(i) { - name <- if (!is.null(nms) && nzchar(nms[i])) nms[i] else paste0("V", i) - val <- obj[[i]] - paste0(name, " = ", clean_deparse(val, indent + 1)) - }) - if (length(items) > 1) { - not_last <- seq_len(length(items) - 1) - items[not_last] <- paste0(items[not_last], ",") - } - item_strs <- paste0(ind, " ", items) - paste0("list(\n", paste(item_strs, collapse = "\n"), "\n", ind, ")") - }, +#' @noRd +clean_deparse.list <- function(obj, indent = 0) { + ind <- paste(rep(" ", indent), collapse = "") + n <- length(obj) + if (n == 0) return("list()") + nms <- names(obj) + items <- vapply(seq_len(n), FUN.VALUE = "", function(i) { + name <- if (!is.null(nms) && nzchar(nms[i])) nms[i] else paste0("V", i) + val <- obj[[i]] + paste0(name, " = ", clean_deparse(val, indent + 1)) + }) + if (length(items) > 1) { + not_last <- seq_len(length(items) - 1) + items[not_last] <- paste0(items[not_last], ",") + } + item_strs <- paste0(ind, " ", items) + paste0("list(\n", paste(item_strs, collapse = "\n"), "\n", ind, ")") +} - character = { - if (length(obj) == 1) paste0('"', obj, '"') - else paste0("c(", paste(sprintf('"%s"', obj), collapse = ", "), ")") - }, +#' @noRd +clean_deparse.character <- function(obj, indent = 0) { + if (length(obj) == 1) return(sprintf('"%s"', obj)) + paste0("c(", paste(sprintf('"%s"', obj), collapse = ", "), ")") +} - numeric = { - if (length(obj) == 1) as.character(obj) - else paste0("c(", paste(obj, collapse = ", "), ")") - }, - integer = { - if (length(obj) == 1) as.character(obj) - else paste0("c(", paste(obj, collapse = ", "), ")") - }, +#' @noRd +clean_deparse.numeric <- function(obj, indent = 0) { + paste0("c(", paste(obj, collapse = ", "), ")") +} - logical = { - if (length(obj) == 1) if (obj) "TRUE" else "FALSE" - else paste0("c(", paste(ifelse(obj, "TRUE", "FALSE"), collapse = ", "), ")") - }, +#' @noRd +clean_deparse.integer <- clean_deparse.numeric - { - # default: fallback to deparse - paste(deparse(obj, width.cutoff = 500), collapse = "") - } - ) +#' @noRd +clean_deparse.logical <- function(obj, indent = 0) { + paste0("c(", paste(ifelse(obj, "TRUE", "FALSE"), collapse = ", "), ")") } From 9555215b76ed23b9b0039f86fea967fda256b42e Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Fri, 28 Nov 2025 16:10:09 +0100 Subject: [PATCH 29/97] man: add docs --- NAMESPACE | 1 + R/get_session_script_code.R | 15 +++++++++++++-- R/ratio_calculations.R | 3 ++- man/calculate_table_ratios_app.Rd | 3 ++- man/clean_deparse.Rd | 24 ++++++++++++++++++++++++ 5 files changed, 42 insertions(+), 4 deletions(-) create mode 100644 man/clean_deparse.Rd diff --git a/NAMESPACE b/NAMESPACE index b60d8a95c..c91050b41 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -43,6 +43,7 @@ export(general_meanplot) export(generate_tooltip_text) export(get_conversion_factor) export(get_label) +export(get_session_script_code) export(interval_add_impute) export(interval_remove_impute) export(l_pkcl01) diff --git a/R/get_session_script_code.R b/R/get_session_script_code.R index 7c8bac6e3..f58147096 100644 --- a/R/get_session_script_code.R +++ b/R/get_session_script_code.R @@ -6,6 +6,7 @@ #' @return The output_path (invisibly) #' @export get_session_script_code <- function(template_path, session, output_path) { + # Helper to get value from session$userData by path (e.g., 'settings$method') get_session_value <- function(path) { parts <- strsplit(path, "\\$")[[1]] @@ -59,8 +60,18 @@ get_session_script_code <- function(template_path, session, output_path) { invisible(output_path) } -# Helper to cleanly deparse an object (data.frame, list, etc.) -#' @noRd +#' Convert R objects into reproducible R code strings (internal) +#' +#' This internal S3 generic converts common R objects (data frames, lists, +#' atomic vectors, etc.) into character strings containing R code that will +#' reconstruct the object. It is used by the app script generator to +#' serialize `session$userData` values into a runnable R script. +#' +#' @param obj An R object to convert to a string of R code. +#' @param indent Integer indentation level for multi-line outputs. +#' @return A single string containing R code that, when evaluated, will +#' reconstruct `obj` (or a close approximation for complex types). +#' @keywords internal clean_deparse <- function(obj, indent = 0) { # Handle trivial length-0 constructors (character(0), numeric(0), list(), data.frame(), ...) if (length(obj) == 0 && !is.null(obj)) { diff --git a/R/ratio_calculations.R b/R/ratio_calculations.R index 9fac2ec10..2991ea836 100644 --- a/R/ratio_calculations.R +++ b/R/ratio_calculations.R @@ -275,7 +275,8 @@ calculate_ratios.PKNCAresults <- function( #' #' @param res A PKNCAresult object. #' @param ratio_table Data.frame with columns: -#' TestParameter, RefParameter, Reference, Test, AggregateSubject, AdjustingFactor, TestGroups, RefGroups, PPTESTCD. +#' TestParameter, RefParameter, Reference, Test, AggregateSubject, +#' AdjustingFactor, TestGroups, RefGroups, PPTESTCD. #' @returns The updated PKNCAresult object with added rows in the `result` data.frame. #' @export calculate_table_ratios_app <- function(res, ratio_table) { diff --git a/man/calculate_table_ratios_app.Rd b/man/calculate_table_ratios_app.Rd index 38289385d..e0bfcb13b 100644 --- a/man/calculate_table_ratios_app.Rd +++ b/man/calculate_table_ratios_app.Rd @@ -10,7 +10,8 @@ calculate_table_ratios_app(res, ratio_table) \item{res}{A PKNCAresult object.} \item{ratio_table}{Data.frame with columns: -TestParameter, RefParameter, Reference, Test, AggregateSubject, AdjustingFactor.} +TestParameter, RefParameter, Reference, Test, AggregateSubject, +AdjustingFactor, TestGroups, RefGroups, PPTESTCD.} } \value{ The updated PKNCAresult object with added rows in the \code{result} data.frame. diff --git a/man/clean_deparse.Rd b/man/clean_deparse.Rd new file mode 100644 index 000000000..210b448d3 --- /dev/null +++ b/man/clean_deparse.Rd @@ -0,0 +1,24 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/get_session_script_code.R +\name{clean_deparse} +\alias{clean_deparse} +\title{Convert R objects into reproducible R code strings (internal)} +\usage{ +clean_deparse(obj, indent = 0) +} +\arguments{ +\item{obj}{An R object to convert to a string of R code.} + +\item{indent}{Integer indentation level for multi-line outputs.} +} +\value{ +A single string containing R code that, when evaluated, will +reconstruct \code{obj} (or a close approximation for complex types). +} +\description{ +This internal S3 generic converts common R objects (data frames, lists, +atomic vectors, etc.) into character strings containing R code that will +reconstruct the object. It is used by the app script generator to +serialize \code{session$userData} values into a runnable R script. +} +\keyword{internal} From a8445168bb8e5afb64ac2c43ccb035d95e68717f Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Fri, 28 Nov 2025 16:37:41 +0100 Subject: [PATCH 30/97] change error msg & change get_session_script_code > get_session_code --- NAMESPACE | 2 +- R/{get_session_script_code.R => get_session_code.R} | 8 ++++---- inst/shiny/modules/tab_nca/nca_results.R | 2 +- man/clean_deparse.Rd | 2 +- man/{get_session_script_code.Rd => get_session_code.Rd} | 8 ++++---- 5 files changed, 11 insertions(+), 11 deletions(-) rename R/{get_session_script_code.R => get_session_code.R} (94%) rename man/{get_session_script_code.Rd => get_session_code.Rd} (73%) diff --git a/NAMESPACE b/NAMESPACE index c91050b41..624d24342 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -43,7 +43,7 @@ export(general_meanplot) export(generate_tooltip_text) export(get_conversion_factor) export(get_label) -export(get_session_script_code) +export(get_session_code) export(interval_add_impute) export(interval_remove_impute) export(l_pkcl01) diff --git a/R/get_session_script_code.R b/R/get_session_code.R similarity index 94% rename from R/get_session_script_code.R rename to R/get_session_code.R index f58147096..6e5023c48 100644 --- a/R/get_session_script_code.R +++ b/R/get_session_code.R @@ -5,7 +5,7 @@ #' @param output_path Path to write the resulting script file (e.g., "output_script.R") #' @return The output_path (invisibly) #' @export -get_session_script_code <- function(template_path, session, output_path) { +get_session_code <- function(template_path, session, output_path) { # Helper to get value from session$userData by path (e.g., 'settings$method') get_session_value <- function(path) { @@ -26,14 +26,14 @@ get_session_script_code <- function(template_path, session, output_path) { script <- readLines(template_path, warn = FALSE) |> paste(collapse = "\n") - # Find all session$userData$... or session$userData[[...]] or session$userData$...$... - # Regex for session$userData$foo or session$userData$foo$bar or session$userData[["foo"]] + # Find all session$userData$... pattern <- "session\\$userData(\\$[a-zA-Z0-9_]+(\\(\\))?(\\$[a-zA-Z0-9_]+)*)" matches <- gregexpr(pattern, script, perl = TRUE)[[1]] if (matches[1] == -1) { stop( "Template has no placeholders (session$userData...) to substitute.", - "Did you accidentally modify it?" + "This may be due to an incorrect file path, a missing template, ", + "or a modified template without placeholders." ) } diff --git a/inst/shiny/modules/tab_nca/nca_results.R b/inst/shiny/modules/tab_nca/nca_results.R index 88219f8d6..440f11494 100644 --- a/inst/shiny/modules/tab_nca/nca_results.R +++ b/inst/shiny/modules/tab_nca/nca_results.R @@ -177,7 +177,7 @@ nca_results_server <- function(id, pknca_data, res_nca, settings, ratio_table, g dir.create(script_tmpdir, recursive = TRUE) script_template_path <- "www/templates/script_template.R" - get_session_script_code( + get_session_code( template_path = script_template_path, session = session, output_path = paste0(script_tmpdir, "/session_code.R") diff --git a/man/clean_deparse.Rd b/man/clean_deparse.Rd index 210b448d3..daff2c15f 100644 --- a/man/clean_deparse.Rd +++ b/man/clean_deparse.Rd @@ -1,5 +1,5 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/get_session_script_code.R +% Please edit documentation in R/get_session_code.R \name{clean_deparse} \alias{clean_deparse} \title{Convert R objects into reproducible R code strings (internal)} diff --git a/man/get_session_script_code.Rd b/man/get_session_code.Rd similarity index 73% rename from man/get_session_script_code.Rd rename to man/get_session_code.Rd index d829c85d7..cb339ee55 100644 --- a/man/get_session_script_code.Rd +++ b/man/get_session_code.Rd @@ -1,10 +1,10 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/get_session_script_code.R -\name{get_session_script_code} -\alias{get_session_script_code} +% Please edit documentation in R/get_session_code.R +\name{get_session_code} +\alias{get_session_code} \title{Generate a session script code in R that can replicate the App outputs} \usage{ -get_session_script_code(template_path, session, output_path) +get_session_code(template_path, session, output_path) } \arguments{ \item{template_path}{Path to the R script template (e.g., script_template.R)} From bd22e67fb3429915e15e5709e6a70dee86a0db23 Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Fri, 28 Nov 2025 16:49:31 +0100 Subject: [PATCH 31/97] spelling: update wordlist --- inst/WORDLIST | 3 +++ 1 file changed, 3 insertions(+) diff --git a/inst/WORDLIST b/inst/WORDLIST index 02e0d86b9..16c1b0956 100644 --- a/inst/WORDLIST +++ b/inst/WORDLIST @@ -57,6 +57,7 @@ Plotly Pre RRLTU RStudio +RefGroups RefParameter Req STUDYID @@ -64,6 +65,7 @@ TLG TLGs TRT TRTRINT +TestGroups TestParameter Tooltip UI @@ -111,6 +113,7 @@ reupload rpptx ruleset rulesets +runnable signif tibble tidyverse From 8d7925b1cd4e3a9535d63988dde82e9427f34280 Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Mon, 1 Dec 2025 08:50:47 +0100 Subject: [PATCH 32/97] refactor script_template --- inst/shiny/modules/tab_nca/nca_results.R | 9 ++++++--- inst/shiny/www/templates/script_template.R | 2 +- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/inst/shiny/modules/tab_nca/nca_results.R b/inst/shiny/modules/tab_nca/nca_results.R index 440f11494..d5d980492 100644 --- a/inst/shiny/modules/tab_nca/nca_results.R +++ b/inst/shiny/modules/tab_nca/nca_results.R @@ -187,9 +187,12 @@ nca_results_server <- function(id, pknca_data, res_nca, settings, ratio_table, g files <- list.files( output_tmpdir, pattern = paste0( - ".(csv)|(rds)|(xpt)|(html)|(rda)", - "|(dose_escalation.pptx)|(dose_escalation.qmd)", - "|(session_code.R)$" + "(", + paste(c( + paste0("\\.", c("csv", "rds", "xpt", "html", "rda")), + "dose_escalation.pptx", "dose_escalation.qmd", + "session_code.R" + ), collapse = "|"), ")" ), recursive = TRUE ) diff --git a/inst/shiny/www/templates/script_template.R b/inst/shiny/www/templates/script_template.R index 16b8e5990..c831f08b9 100644 --- a/inst/shiny/www/templates/script_template.R +++ b/inst/shiny/www/templates/script_template.R @@ -77,7 +77,7 @@ pknca_res <- pknca_obj %>% ) %>% # Run pk.nca and join subject and dose information to the results - PKNCA_calculate_nca() %>% + PKNCA_calculate_nca() %>% # Add bioavailability results if requested add_f_to_pknca_results(session$userData$settings$bioavailability) %>% From 930a02f3d0692069b98f66b703cc4c74229f1055 Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Fri, 5 Dec 2025 11:21:43 +0100 Subject: [PATCH 33/97] change utils::zip > zip::zipr for ZIP file exportation --- inst/shiny/modules/tab_nca/nca_results.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/inst/shiny/modules/tab_nca/nca_results.R b/inst/shiny/modules/tab_nca/nca_results.R index d5d980492..a34e0f252 100644 --- a/inst/shiny/modules/tab_nca/nca_results.R +++ b/inst/shiny/modules/tab_nca/nca_results.R @@ -201,7 +201,7 @@ nca_results_server <- function(id, pknca_data, res_nca, settings, ratio_table, g on.exit(setwd(wd), add = TRUE) # this will reset the wd after the download handler setwd(output_tmpdir) incProgress(0.9) - utils::zip(zipfile = fname, files = files) + zip::zipr(zipfile = fname, files = files, mode = "mirror") incProgress(1) }) } From 163654b85f6191f2f3f29d2a4abc6d8dbd6807d3 Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Wed, 10 Dec 2025 16:13:46 +0100 Subject: [PATCH 34/97] refactor: include in pivot_wider option to locate profile rules --- R/pivot_wider_pknca_results.R | 57 +++++++++++++++++++++++- inst/shiny/modules/tab_nca/nca_results.R | 30 ++----------- 2 files changed, 58 insertions(+), 29 deletions(-) diff --git a/R/pivot_wider_pknca_results.R b/R/pivot_wider_pknca_results.R index 06f45f632..2fbf9971a 100644 --- a/R/pivot_wider_pknca_results.R +++ b/R/pivot_wider_pknca_results.R @@ -26,7 +26,7 @@ #' @importFrom purrr pmap_chr #' @export #' -pivot_wider_pknca_results <- function(myres) { +pivot_wider_pknca_results <- function(myres, flag_rules = NULL) { ############################################################################################ # Derive LAMZNPT & LAMZMTD # ToDo: At some point this will be integrated in PKNCA and will need to be removed//modified @@ -153,7 +153,10 @@ pivot_wider_pknca_results <- function(myres) { ungroup() # Add "label" attribute to columns - add_label_attribute(pivoted_res, myres) + pivoted_res <- add_label_attribute(pivoted_res, myres) + + # Add flagging columns for each rule and a general "flagged" column + .create_flags_for_profiles(final_results = pivoted_res, myres = myres, flag_rules = flag_rules) } #' Helper function to extract exclude values @@ -200,3 +203,53 @@ add_label_attribute <- function(df, myres) { }, df[, mapping_cols], attrs, SIMPLIFY = FALSE)) df } + +.add_units_to_colnames <- function(final_results, myres) { + mapping_vr <- myres$result %>% + mutate( + PPTESTCD_unit = case_when( + type_interval == "manual" ~ paste0( + PPTESTCD, "_", start, "-", end, + ifelse(PPSTRESU != "", paste0("[", PPSTRESU, "]"), "") + ), + PPSTRESU != "" ~ paste0(PPTESTCD, "[", PPSTRESU, "]"), + TRUE ~ PPTESTCD + ), + PPTESTCD_cdisc = translate_terms(PPTESTCD, mapping_col = "PPTESTCD", target_col = "PPTEST") + ) %>% + select(PPTESTCD_cdisc, PPTESTCD_unit) %>% + distinct() %>% + pull(PPTESTCD_unit, PPTESTCD_cdisc) + + mapping_cols <- intersect(names(final_results), names(mapping_vr)) + new_colnames <- unname(mapping_vr[mapping_cols]) + names(final_results)[match(mapping_cols, names(final_results))] <- new_colnames + final_results +} + +.create_flags_for_profiles <- function(final_results, myres, flag_rules) { + + # Add flaging columns in the pivoted results + applied_flags <- purrr::keep(flag_rules, function(x) x$is.checked) + flag_params <- names(flag_rules) + flag_thr <- sapply(flag_rules, FUN = function(x) x$threshold) + flag_rule_msgs <- paste0(flag_params, c(" < ", " > ", " > ", " < "), flag_thr) + flag_cols <- names(final_results)[formatters::var_labels(final_results) + %in% translate_terms(flag_params, "PPTESTCD", "PPTEST")] + + if (length(flag_params) > 0) { + final_results <- final_results %>% + mutate( + flagged = case_when( + rowSums(is.na(select(., any_of(flag_cols)))) > 0 ~ "MISSING", + is.na(Exclude) ~ "ACCEPTED", + any(sapply( + flag_rule_msgs, function(msg) str_detect(Exclude, fixed(msg)) + )) ~ "FLAGGED", + TRUE ~ "ACCEPTED" + ) + ) + } + final_results +} + diff --git a/inst/shiny/modules/tab_nca/nca_results.R b/inst/shiny/modules/tab_nca/nca_results.R index a34e0f252..86d67b40d 100644 --- a/inst/shiny/modules/tab_nca/nca_results.R +++ b/inst/shiny/modules/tab_nca/nca_results.R @@ -80,7 +80,7 @@ nca_results_server <- function(id, pknca_data, res_nca, settings, ratio_table, g results <- res_nca() # Transform results - final_results <- pivot_wider_pknca_results(results) + final_results <- pivot_wider_pknca_results(results, flag_rules = settings()$flags) # Join subject data to allow the user to group by it conc_data_to_join <- res_nca()$data$conc$data %>% @@ -94,32 +94,8 @@ nca_results_server <- function(id, pknca_data, res_nca, settings, ratio_table, g final_results <- final_results %>% inner_join(conc_data_to_join, by = intersect(names(.), names(conc_data_to_join))) %>% - distinct() %>% - mutate( - flagged = "NOT DONE" - ) - - # Add flaging column in the pivoted results - applied_flags <- purrr::keep(settings()$flags, function(x) x$is.checked) - flag_params <- names(settings()$flags) - flag_thr <- sapply(settings()$flags, FUN = function(x) x$threshold) - flag_rule_msgs <- paste0(flag_params, c(" < ", " > ", " > ", " < "), flag_thr) - flag_cols <- names(final_results)[formatters::var_labels(final_results) - %in% translate_terms(flag_params, "PPTESTCD", "PPTEST")] - - if (length(flag_params) > 0) { - final_results <- final_results %>% - mutate( - flagged = case_when( - rowSums(is.na(select(., any_of(flag_cols)))) > 0 ~ "MISSING", - is.na(Exclude) ~ "ACCEPTED", - any(sapply( - flag_rule_msgs, function(msg) str_detect(Exclude, fixed(msg)) - )) ~ "FLAGGED", - TRUE ~ "ACCEPTED" - ) - ) - } + distinct() + final_results }) From af866977863e684ad2c98ba33d390320cb93eaa5 Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Thu, 11 Dec 2025 11:31:40 +0100 Subject: [PATCH 35/97] roxygen: add docs, update man & namespace --- NAMESPACE | 2 ++ R/pivot_wider_pknca_results.R | 33 ++++++++------------------------ man/pivot_wider_pknca_results.Rd | 8 +++++++- 3 files changed, 17 insertions(+), 26 deletions(-) diff --git a/NAMESPACE b/NAMESPACE index 2213ba66a..aebbee820 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -126,6 +126,8 @@ importFrom(stats,median) importFrom(stats,na.omit) importFrom(stats,sd) importFrom(stats,setNames) +importFrom(stringr,fixed) +importFrom(stringr,str_detect) importFrom(stringr,str_glue) importFrom(stringr,str_split) importFrom(stringr,str_trim) diff --git a/R/pivot_wider_pknca_results.R b/R/pivot_wider_pknca_results.R index 2fbf9971a..8a0b67462 100644 --- a/R/pivot_wider_pknca_results.R +++ b/R/pivot_wider_pknca_results.R @@ -15,6 +15,11 @@ #' of the dose, relative to the last reference dose. #' 5) Temporarily: CDISC denomination of PK parameters related to half-life: "LAMZNPT", #' "LAMZLL", "LAMZ" Used to derive `LAMZNPT` and `LAMZMTD`. +#' @param flag_rules A named list of flagging rules to be applied to the results. Each rule +#' should be a list with two elements: `is.checked` (logical) indicating whether the rule +#' should be applied, and `threshold` (numeric) specifying the threshold value for flagging. +#' The name of each rule should correspond to a parameter in the results data.frame as a PPTESTCD +#' (e.g., "R2ADJ", "AUCPEO", "AUCPEP", "LAMZSPN"). #' #' @returns A data frame which provides an easy overview on the results from the NCA #' in each profile/subject and how it was computed lambda (half life) and the results @@ -24,8 +29,10 @@ #' @importFrom dplyr filter slice across where #' @importFrom tidyr pivot_wider pivot_longer #' @importFrom purrr pmap_chr +#' @importFrom stringr str_detect fixed #' @export #' + pivot_wider_pknca_results <- function(myres, flag_rules = NULL) { ############################################################################################ # Derive LAMZNPT & LAMZMTD @@ -155,7 +162,7 @@ pivot_wider_pknca_results <- function(myres, flag_rules = NULL) { # Add "label" attribute to columns pivoted_res <- add_label_attribute(pivoted_res, myres) - # Add flagging columns for each rule and a general "flagged" column + # Add flagging columns for each rule and a general "flagged" column .create_flags_for_profiles(final_results = pivoted_res, myres = myres, flag_rules = flag_rules) } @@ -204,29 +211,6 @@ add_label_attribute <- function(df, myres) { df } -.add_units_to_colnames <- function(final_results, myres) { - mapping_vr <- myres$result %>% - mutate( - PPTESTCD_unit = case_when( - type_interval == "manual" ~ paste0( - PPTESTCD, "_", start, "-", end, - ifelse(PPSTRESU != "", paste0("[", PPSTRESU, "]"), "") - ), - PPSTRESU != "" ~ paste0(PPTESTCD, "[", PPSTRESU, "]"), - TRUE ~ PPTESTCD - ), - PPTESTCD_cdisc = translate_terms(PPTESTCD, mapping_col = "PPTESTCD", target_col = "PPTEST") - ) %>% - select(PPTESTCD_cdisc, PPTESTCD_unit) %>% - distinct() %>% - pull(PPTESTCD_unit, PPTESTCD_cdisc) - - mapping_cols <- intersect(names(final_results), names(mapping_vr)) - new_colnames <- unname(mapping_vr[mapping_cols]) - names(final_results)[match(mapping_cols, names(final_results))] <- new_colnames - final_results -} - .create_flags_for_profiles <- function(final_results, myres, flag_rules) { # Add flaging columns in the pivoted results @@ -252,4 +236,3 @@ add_label_attribute <- function(df, myres) { } final_results } - diff --git a/man/pivot_wider_pknca_results.Rd b/man/pivot_wider_pknca_results.Rd index 23cdd77f8..a9ab5b2ff 100644 --- a/man/pivot_wider_pknca_results.Rd +++ b/man/pivot_wider_pknca_results.Rd @@ -4,7 +4,7 @@ \alias{pivot_wider_pknca_results} \title{Reshape PKNCA Results} \usage{ -pivot_wider_pknca_results(myres) +pivot_wider_pknca_results(myres, flag_rules = NULL) } \arguments{ \item{myres}{The output of PKNCA::pk.nca. It makes some additional assumptions: @@ -18,6 +18,12 @@ of the dose, relative to the last reference dose. \item Temporarily: CDISC denomination of PK parameters related to half-life: "LAMZNPT", "LAMZLL", "LAMZ" Used to derive \code{LAMZNPT} and \code{LAMZMTD}. }} + +\item{flag_rules}{A named list of flagging rules to be applied to the results. Each rule +should be a list with two elements: \code{is.checked} (logical) indicating whether the rule +should be applied, and \code{threshold} (numeric) specifying the threshold value for flagging. +The name of each rule should correspond to a parameter in the results data.frame as a PPTESTCD +(e.g., "R2ADJ", "AUCPEO", "AUCPEP", "LAMZSPN").} } \value{ A data frame which provides an easy overview on the results from the NCA From 94596fec0a2ba653aefbd3fd11736ffea2b53c60 Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Thu, 11 Dec 2025 11:31:57 +0100 Subject: [PATCH 36/97] add tests for new functionality in pivot_wider_pknca_results --- .../testthat/test-pivot_wider_pknca_results.R | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/tests/testthat/test-pivot_wider_pknca_results.R b/tests/testthat/test-pivot_wider_pknca_results.R index bfe1d0964..972b09b6a 100644 --- a/tests/testthat/test-pivot_wider_pknca_results.R +++ b/tests/testthat/test-pivot_wider_pknca_results.R @@ -207,4 +207,37 @@ describe("pivot_wider_pknca_results", { expect_false("PPANMETH" %in% result) }) + + it("includes the flagged column and the flag rule columns when flag_rules are provided", { + + # Prepare a PKNCA results object with the exclusions to flag + flag_rules <- list( + R2ADJ = list(is.checked = TRUE, threshold = 0.99), + R2 = list(is.checked = TRUE, threshold = 0.99) + ) + flag_rules_pknca <- flag_rules %>% + purrr::keep(\(x) x$is.checked) %>% + purrr::map(\(x) x$threshold) + pknca_res <- PKNCA_hl_rules_exclusion(res = pknca_res, rules = flag_rules_pknca) + + # Produce the output to be tested + piv_result <- pivot_wider_pknca_results(pknca_res, flag_rules = flag_rules) + + # Missing results produce "MISSING" flag + na_result <- piv_result %>% + filter(is.na(R2) | is.na(R2ADJ)) + expect_equal(unique(na_result$flagged), "MISSING") + + # Invalid rules produce "FLAGGED" flag + flagged_result <- piv_result %>% + filter(!is.na(R2) & !is.na(R2ADJ)) %>% + filter(R2 < flag_rules$R2$threshold | R2ADJ < flag_rules$R2ADJ$threshold) + expect_equal(unique(flagged_result$flagged), "FLAGGED") + + # Passed rules produce "ACCEPTED" flag + accepted_result <- piv_result %>% + filter(!is.na(R2) & !is.na(R2ADJ)) %>% + filter(R2 >= flag_rules$R2$threshold & R2ADJ >= flag_rules$R2ADJ$threshold) + expect_equal(unique(accepted_result$flagged), "ACCEPTED") + }) }) From e9122bb218f195a89cc8ae99a947d140712ae9c7 Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Thu, 11 Dec 2025 11:32:20 +0100 Subject: [PATCH 37/97] update wordlist --- inst/WORDLIST | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/inst/WORDLIST b/inst/WORDLIST index bcc486e11..2da294868 100644 --- a/inst/WORDLIST +++ b/inst/WORDLIST @@ -4,8 +4,10 @@ ADPC ADPP AFRLT ARRLT -AUCinf ATPTREF +AUCPEO +AUCPEP +AUCinf AUClast AVAL AVALU @@ -22,10 +24,9 @@ DOSETRT DOSEU DOSNOA DTYPE -DataTab -EMA Denney Duvvuri +EMA EVID Extravascular Kezia @@ -33,11 +34,11 @@ Kobana LAMZ LAMZLL LAMZNPT +LAMZSPN Lineplot METABFL MSCW NCA -NCATab NFRLT NRRLT Noncompartmental @@ -76,10 +77,10 @@ STUDYID TLG TLGs TRT -Tmax TRTRINT TestGroups TestParameter +Tmax Tooltip UI USUBJID @@ -98,7 +99,6 @@ bioavailability bioequivalence cdisc cmax -codebase colour concu customizable @@ -109,8 +109,6 @@ doi extravascular ggplot ggplots -github -https intravascular linters lintr @@ -119,12 +117,10 @@ macroparameters md nca oligo -orgs pak pharmacodynamics pharmacokinetic pharmacokinetics -pharmaverse pkcg pknca plotly From 3cbbd232a861b013332cb9381853d20d0c35000b Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Fri, 12 Dec 2025 13:58:44 +0100 Subject: [PATCH 38/97] script: change |> with %>% & add flag_rules to pivot_wider() --- inst/shiny/www/templates/script_template.R | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/inst/shiny/www/templates/script_template.R b/inst/shiny/www/templates/script_template.R index c831f08b9..6d24f44e4 100644 --- a/inst/shiny/www/templates/script_template.R +++ b/inst/shiny/www/templates/script_template.R @@ -1,5 +1,6 @@ # Load the package (https://github.com/pharmaverse/aNCA) # library(aNCA) +library(dplyr) # Load raw data # data_path <- session$userData$data_path @@ -87,8 +88,8 @@ pknca_res <- pknca_obj %>% # Flag relevant parameters based on AUCPEO, AUCPEP & lambda span PKNCA_hl_rules_exclusion( - rules = flag_rules |> - purrr::keep(\(x) x$is.checked) |> + rules = flag_rules %>% + purrr::keep(\(x) x$is.checked) %>% purrr::map(\(x) x$threshold) ) %>% @@ -97,4 +98,4 @@ pknca_res <- pknca_obj %>% ## Obtain PP, ADPP, ADNCA & Pivoted results ######################### cdisc_datasets <- export_cdisc(pknca_res) -pivoted_results <- pivot_wider_pknca_results(pknca_res) +pivoted_results <- pivot_wider_pknca_results(pknca_res, flag_rules) From afc3cd65e9c5a58767622d86eee0e24213160e01 Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Fri, 12 Dec 2025 14:18:00 +0100 Subject: [PATCH 39/97] mv calculate_ratio_app to \R --- R/ratio_calculations.R | 102 +++++++++++++++++++++++++++++ inst/shiny/functions/ratio_table.R | 100 ---------------------------- 2 files changed, 102 insertions(+), 100 deletions(-) delete mode 100644 inst/shiny/functions/ratio_table.R diff --git a/R/ratio_calculations.R b/R/ratio_calculations.R index 2991ea836..43a61c742 100644 --- a/R/ratio_calculations.R +++ b/R/ratio_calculations.R @@ -314,3 +314,105 @@ calculate_table_ratios_app <- function(res, ratio_table) { res$result <- do.call(rbind, c(list(res$result), ratio_results)) res } + +#' Links the table ratio of the App with the ratio calculations via PKNCA results +#' +#' @param res A PKNCAresult object. +#' @param test_parameter Character. The PPTESTCD value to use as test (numerator). +#' @param ref_parameter Character. The PPTESTCD value to use as reference (denominator). +#' Defaults to test_parameter. +#' @param test_group Character. The test group (numerator). Default is "(all other levels)". +#' @param ref_group Character. The reference group (denominator). +#' @param aggregate_subject Character. Aggregation mode: "yes", "no", or "if-needed". +#' @param adjusting_factor Numeric that multiplies the calculated ratio. Default is 1. +#' @param custom_pptestcd Optional character. If provided, will be used as the PPTESTCD value. +#' @returns A data.frame with the calculated ratios for the specified settings. +calculate_ratio_app <- function( + res, + test_parameter, + ref_parameter = test_parameter, + test_group = "(all other levels)", + ref_group = "PARAM: Analyte01", + aggregate_subject = "no", + adjusting_factor = 1, + custom_pptestcd = NULL +) { + reference_colname <- gsub("(.*): (.*)", "\\1", ref_group) + match_cols <- setdiff(unique(c(dplyr::group_vars(res), "start", "end")), reference_colname) + + ########### This is very App specific ############### + if ("ATPTREF" %in% reference_colname) { + match_cols <- setdiff(match_cols, c("start", "end")) + } + if ("ROUTE" %in% reference_colname && aggregate_subject == "no") { + match_cols <- setdiff(match_cols, c("start", "end")) + } + ##################################################### + + if (aggregate_subject == "yes") { + match_cols <- list(setdiff(match_cols, "USUBJID")) + } else if (aggregate_subject == "no") { + if (!"USUBJID" %in% match_cols) { + stop("USUBJID must be included in match_cols when aggregate_subject is 'never'.") + } + match_cols <- list(match_cols) + } else if (aggregate_subject == "if-needed") { + if ("USUBJID" %in% match_cols) { + # Perform both individual & aggregated calculations, then eliminate duplicates + match_cols <- list(match_cols, setdiff(match_cols, "USUBJID")) + } + } + + if (test_group == "(all other levels)") { + test_groups <- NULL + } else { + num_colname <- gsub("(.*): (.*)", "\\1", test_group) + num_value <- gsub("(.*): (.*)", "\\2", test_group) + test_groups <- data.frame( + matrix( + num_value, + nrow = 1, + ncol = length(num_colname), + dimnames = list(NULL, num_colname) + ) + ) + } + + reference_colname <- gsub("(.*): (.*)", "\\1", ref_group) + reference_value <- gsub("(.*): (.*)", "\\2", ref_group) + ref_groups <- data.frame( + matrix( + reference_value, + nrow = 1, + ncol = length(reference_colname), + dimnames = list(NULL, reference_colname) + ) + ) + + + all_ratios <- data.frame() + + for (ix in seq_along(match_cols)) { + ratio_calculations <- calculate_ratios( + data = res$result, + test_parameter = test_parameter, + ref_parameter = ref_parameter, + match_cols = match_cols[[ix]], + ref_groups = ref_groups, + test_groups = test_groups, + adjusting_factor = adjusting_factor, + custom_pptestcd = custom_pptestcd + ) + all_ratios <- bind_rows(all_ratios, ratio_calculations) + } + + # Assuming there cannot be more than 1 reference + PPTESTCD combination for the same group... + # If aggregate_subject = 'if-needed', then this will remove cases when subject is not needed + all_ratios %>% + # Make sure there are no duplicate rows for: parameter, contrast_var, and match_cols + distinct(across( + all_of(c("PPTESTCD", group_vars(res$data), "end")) + ), + .keep_all = TRUE) +} + diff --git a/inst/shiny/functions/ratio_table.R b/inst/shiny/functions/ratio_table.R deleted file mode 100644 index 8a46de31a..000000000 --- a/inst/shiny/functions/ratio_table.R +++ /dev/null @@ -1,100 +0,0 @@ -#' Links the table ratio of the App with the ratio calculations via PKNCA results -#' -#' @param res A PKNCAresult object. -#' @param test_parameter Character. The PPTESTCD value to use as test (numerator). -#' @param ref_parameter Character. The PPTESTCD value to use as reference (denominator). -#' Defaults to test_parameter. -#' @param test_group Character. The test group (numerator). Default is "(all other levels)". -#' @param ref_group Character. The reference group (denominator). -#' @param aggregate_subject Character. Aggregation mode: "yes", "no", or "if-needed". -#' @param adjusting_factor Numeric that multiplies the calculated ratio. Default is 1. -#' @param custom_pptestcd Optional character. If provided, will be used as the PPTESTCD value. -#' @returns A data.frame with the calculated ratios for the specified settings. -calculate_ratio_app <- function( - res, - test_parameter, - ref_parameter = test_parameter, - test_group = "(all other levels)", - ref_group = "PARAM: Analyte01", - aggregate_subject = "no", - adjusting_factor = 1, - custom_pptestcd = NULL -) { - reference_colname <- gsub("(.*): (.*)", "\\1", ref_group) - match_cols <- setdiff(unique(c(dplyr::group_vars(res), "start", "end")), reference_colname) - - ########### This is very App specific ############### - if ("ATPTREF" %in% reference_colname) { - match_cols <- setdiff(match_cols, c("start", "end")) - } - if ("ROUTE" %in% reference_colname && aggregate_subject == "no") { - match_cols <- setdiff(match_cols, c("start", "end")) - } - ##################################################### - - if (aggregate_subject == "yes") { - match_cols <- list(setdiff(match_cols, "USUBJID")) - } else if (aggregate_subject == "no") { - if (!"USUBJID" %in% match_cols) { - stop("USUBJID must be included in match_cols when aggregate_subject is 'never'.") - } - match_cols <- list(match_cols) - } else if (aggregate_subject == "if-needed") { - if ("USUBJID" %in% match_cols) { - # Perform both individual & aggregated calculations, then eliminate duplicates - match_cols <- list(match_cols, setdiff(match_cols, "USUBJID")) - } - } - - if (test_group == "(all other levels)") { - test_groups <- NULL - } else { - num_colname <- gsub("(.*): (.*)", "\\1", test_group) - num_value <- gsub("(.*): (.*)", "\\2", test_group) - test_groups <- data.frame( - matrix( - num_value, - nrow = 1, - ncol = length(num_colname), - dimnames = list(NULL, num_colname) - ) - ) - } - - reference_colname <- gsub("(.*): (.*)", "\\1", ref_group) - reference_value <- gsub("(.*): (.*)", "\\2", ref_group) - ref_groups <- data.frame( - matrix( - reference_value, - nrow = 1, - ncol = length(reference_colname), - dimnames = list(NULL, reference_colname) - ) - ) - - - all_ratios <- data.frame() - - for (ix in seq_along(match_cols)) { - ratio_calculations <- calculate_ratios( - data = res$result, - test_parameter = test_parameter, - ref_parameter = ref_parameter, - match_cols = match_cols[[ix]], - ref_groups = ref_groups, - test_groups = test_groups, - adjusting_factor = adjusting_factor, - custom_pptestcd = custom_pptestcd - ) - all_ratios <- bind_rows(all_ratios, ratio_calculations) - } - - # Assuming there cannot be more than 1 reference + PPTESTCD combination for the same group... - # If aggregate_subject = 'if-needed', then this will remove cases when subject is not needed - all_ratios %>% - # Make sure there are no duplicate rows for: parameter, contrast_var, and match_cols - distinct(across( - all_of(c("PPTESTCD", group_vars(res$data), "end")) - ), - .keep_all = TRUE) -} From 521e856e611e0247fd391ce32d18a7f1433a9c71 Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Fri, 12 Dec 2025 14:35:09 +0100 Subject: [PATCH 40/97] install pkg if missing (fixes #727) --- inst/shiny/www/templates/script_template.R | 1 + 1 file changed, 1 insertion(+) diff --git a/inst/shiny/www/templates/script_template.R b/inst/shiny/www/templates/script_template.R index 6d24f44e4..6af70bc0e 100644 --- a/inst/shiny/www/templates/script_template.R +++ b/inst/shiny/www/templates/script_template.R @@ -1,4 +1,5 @@ # Load the package (https://github.com/pharmaverse/aNCA) # +if (!require("aNCA")) install.packages("aNCA") library(aNCA) library(dplyr) From 06a3860b9d2549a1d5a0925c016b52c505610ffd Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Mon, 15 Dec 2025 09:44:01 +0100 Subject: [PATCH 41/97] merge: this is the conflict solved --- inst/shiny/modules/tab_nca/nca_results.R | 46 ++++++------------------ 1 file changed, 11 insertions(+), 35 deletions(-) diff --git a/inst/shiny/modules/tab_nca/nca_results.R b/inst/shiny/modules/tab_nca/nca_results.R index 19287fa91..3a6e7b66d 100644 --- a/inst/shiny/modules/tab_nca/nca_results.R +++ b/inst/shiny/modules/tab_nca/nca_results.R @@ -149,45 +149,22 @@ nca_results_server <- function(id, pknca_data, res_nca, settings, ratio_table, g setts_tmpdir <- file.path(output_tmpdir, "settings") dir.create(setts_tmpdir, recursive = TRUE) saveRDS(session$userData$settings(), paste0(setts_tmpdir, "/settings.rds")) + + # Save a code R script template for the session + script_tmpdir <- file.path(output_tmpdir, "code") + dir.create(script_tmpdir, recursive = TRUE) + script_template_path <- "www/templates/script_template.R" + get_session_code( + template_path = script_template_path, + session = session, + output_path = paste0(script_tmpdir, "/session_code.R") + ) -<<<<<<< HEAD - # Save a code R script template for the session - script_tmpdir <- file.path(output_tmpdir, "code") - dir.create(script_tmpdir, recursive = TRUE) - - script_template_path <- "www/templates/script_template.R" - get_session_code( - template_path = script_template_path, - session = session, - output_path = paste0(script_tmpdir, "/session_code.R") - ) - - # Zip all files in the output folder - files <- list.files( - output_tmpdir, - pattern = paste0( - "(", - paste(c( - paste0("\\.", c("csv", "rds", "xpt", "html", "rda", "png")), - "dose_escalation.pptx", "dose_escalation.qmd", - "session_code.R" - ), collapse = "|"), ")" - ), - recursive = TRUE - ) - - wd <- getwd() - on.exit(setwd(wd), add = TRUE) # this will reset the wd after the download handler - setwd(output_tmpdir) - incProgress(0.9) - zip::zipr(zipfile = fname, files = files, mode = "mirror") - incProgress(1) -======= files <- list.files( output_tmpdir, pattern = paste0( "(\\.csv)|(\\.rds)|(\\.xpt)|(\\.html)|(\\.rda)|(\\.png)", - "|(results_slides\\.pptx)|(results_slides\\.qmd)$" + "|(results_slides\\.pptx)|(results_slides\\.qmd)|(session_code\\.R)$" ), recursive = TRUE ) @@ -203,7 +180,6 @@ nca_results_server <- function(id, pknca_data, res_nca, settings, ratio_table, g message("Download Error:") message(e$message) stop(e) ->>>>>>> origin/main }) } ) From 37e50093693e98f508e13c49714218a1c5522cd9 Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Mon, 15 Dec 2025 10:05:38 +0100 Subject: [PATCH 42/97] internalize code in nca_results to pivot_Wider_pknca_results.R --- R/pivot_wider_pknca_results.R | 22 ++++++++++++++++++++-- inst/shiny/modules/tab_nca/nca_results.R | 20 +++++--------------- 2 files changed, 25 insertions(+), 17 deletions(-) diff --git a/R/pivot_wider_pknca_results.R b/R/pivot_wider_pknca_results.R index 8a0b67462..defa4e541 100644 --- a/R/pivot_wider_pknca_results.R +++ b/R/pivot_wider_pknca_results.R @@ -20,6 +20,7 @@ #' should be applied, and `threshold` (numeric) specifying the threshold value for flagging. #' The name of each rule should correspond to a parameter in the results data.frame as a PPTESTCD #' (e.g., "R2ADJ", "AUCPEO", "AUCPEP", "LAMZSPN"). +#' @param extra_vars_to_keep Optional character vector of variable names to join from the concentration data to the output. Default is NULL. #' #' @returns A data frame which provides an easy overview on the results from the NCA #' in each profile/subject and how it was computed lambda (half life) and the results @@ -33,7 +34,7 @@ #' @export #' -pivot_wider_pknca_results <- function(myres, flag_rules = NULL) { +pivot_wider_pknca_results <- function(myres, flag_rules = NULL, extra_vars_to_keep = NULL) { ############################################################################################ # Derive LAMZNPT & LAMZMTD # ToDo: At some point this will be integrated in PKNCA and will need to be removed//modified @@ -163,7 +164,24 @@ pivot_wider_pknca_results <- function(myres, flag_rules = NULL) { pivoted_res <- add_label_attribute(pivoted_res, myres) # Add flagging columns for each rule and a general "flagged" column - .create_flags_for_profiles(final_results = pivoted_res, myres = myres, flag_rules = flag_rules) + out <- .create_flags_for_profiles(final_results = pivoted_res, myres = myres, flag_rules = flag_rules) + + # If extra_vars_to_keep is provided, join these variables from the conc data + if (!is.null(extra_vars_to_keep) && length(extra_vars_to_keep) > 0) { + conc_data <- myres$data$conc$data + # Only keep columns that exist in conc_data + vars_to_join <- intersect(extra_vars_to_keep, names(conc_data)) + if (length(vars_to_join) > 0) { + out <- out %>% + dplyr::inner_join( + dplyr::select(conc_data, dplyr::any_of(vars_to_join)), + by = intersect(names(out), vars_to_join) + ) %>% + dplyr::distinct() + } + } + + out } #' Helper function to extract exclude values diff --git a/inst/shiny/modules/tab_nca/nca_results.R b/inst/shiny/modules/tab_nca/nca_results.R index 3a6e7b66d..d7840a34b 100644 --- a/inst/shiny/modules/tab_nca/nca_results.R +++ b/inst/shiny/modules/tab_nca/nca_results.R @@ -80,21 +80,11 @@ nca_results_server <- function(id, pknca_data, res_nca, settings, ratio_table, g results <- res_nca() # Transform results - final_results <- pivot_wider_pknca_results(results, flag_rules = settings()$flags) - - # Join subject data to allow the user to group by it - conc_data_to_join <- res_nca()$data$conc$data %>% - select(any_of(c( - grouping_vars(), - unname(unlist(res_nca()$data$conc$columns$groups)), - "DOSEA", - "ATPTREF", - "ROUTE" - ))) - - final_results <- final_results %>% - inner_join(conc_data_to_join, by = intersect(names(.), names(conc_data_to_join))) %>% - distinct() + final_results <- pivot_wider_pknca_results( + results, + flag_rules = settings()$flags, + extra_vars_to_keep = c(grouping_vars(), "DOSEA", "ATPTREF", "ROUTE") + ) final_results }) From 1aea3b80128571bb58e3cf718a7fe90531048724 Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Mon, 15 Dec 2025 10:05:52 +0100 Subject: [PATCH 43/97] make test for pivot_wider_pknca_result new arg --- tests/testthat/test-pivot_wider_pknca_results.R | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/testthat/test-pivot_wider_pknca_results.R b/tests/testthat/test-pivot_wider_pknca_results.R index 61388873f..11378a653 100644 --- a/tests/testthat/test-pivot_wider_pknca_results.R +++ b/tests/testthat/test-pivot_wider_pknca_results.R @@ -240,4 +240,16 @@ describe("pivot_wider_pknca_results", { filter(R2 >= flag_rules$R2$threshold & R2ADJ >= flag_rules$R2ADJ$threshold) expect_equal(unique(accepted_result$flagged), "ACCEPTED") }) + + it("includes extra_vars_to_keep columns when provided", { + pknca_res <- FIXTURE_PKNCA_RES + extra_vars <- c("ROUTE", "DOSEA", "ATPTREF") + pivoted_res <- pivot_wider_pknca_results(pknca_res, extra_vars_to_keep = extra_vars) + + # All requested extra_vars should be present in the output (if present in conc data) + present_vars <- intersect(extra_vars, names(pknca_res$data$conc$data)) + + # Check all extra_vars present are added in the pivoted result + expect_true(all(present_vars %in% names(pivoted_res))) + }) }) From d518136bf3119a0c4723a1b9bd4a43b7ef1a26e5 Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Mon, 15 Dec 2025 10:18:38 +0100 Subject: [PATCH 44/97] docs: roxygenise --- man/pivot_wider_pknca_results.Rd | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/man/pivot_wider_pknca_results.Rd b/man/pivot_wider_pknca_results.Rd index a9ab5b2ff..1317c0b49 100644 --- a/man/pivot_wider_pknca_results.Rd +++ b/man/pivot_wider_pknca_results.Rd @@ -4,7 +4,7 @@ \alias{pivot_wider_pknca_results} \title{Reshape PKNCA Results} \usage{ -pivot_wider_pknca_results(myres, flag_rules = NULL) +pivot_wider_pknca_results(myres, flag_rules = NULL, extra_vars_to_keep = NULL) } \arguments{ \item{myres}{The output of PKNCA::pk.nca. It makes some additional assumptions: @@ -24,6 +24,8 @@ should be a list with two elements: \code{is.checked} (logical) indicating wheth should be applied, and \code{threshold} (numeric) specifying the threshold value for flagging. The name of each rule should correspond to a parameter in the results data.frame as a PPTESTCD (e.g., "R2ADJ", "AUCPEO", "AUCPEP", "LAMZSPN").} + +\item{extra_vars_to_keep}{Optional character vector of variable names to join from the concentration data to the output. Default is NULL.} } \value{ A data frame which provides an easy overview on the results from the NCA From ecef7420b1c0c9d082aede0381e426640dfb39db Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Mon, 15 Dec 2025 10:19:33 +0100 Subject: [PATCH 45/97] refactor: lintr --- R/get_session_code.R | 4 +-- R/pivot_wider_pknca_results.R | 9 ++++-- R/ratio_calculations.R | 32 +++++++++---------- .../testthat/test-pivot_wider_pknca_results.R | 2 +- 4 files changed, 26 insertions(+), 21 deletions(-) diff --git a/R/get_session_code.R b/R/get_session_code.R index 6e5023c48..a45f3aa6a 100644 --- a/R/get_session_code.R +++ b/R/get_session_code.R @@ -23,7 +23,7 @@ get_session_code <- function(template_path, session, output_path) { } # Read template - script <- readLines(template_path, warn = FALSE) |> + script <- readLines(template_path, warn = FALSE) %>% paste(collapse = "\n") # Find all session$userData$... @@ -95,7 +95,7 @@ clean_deparse.data.frame <- function(obj, indent = 0) { if (nrow(obj) == 0) return("data.frame()") cols <- lapply(obj, function(col) { - d <- deparse(col, width.cutoff = 500) |> paste(collapse = "") + d <- deparse(col, width.cutoff = 500) %>% paste(collapse = "") if (length(col) > 1 && !grepl("^c\\(", d)) d <- paste0("c(", d, ")") d }) diff --git a/R/pivot_wider_pknca_results.R b/R/pivot_wider_pknca_results.R index defa4e541..be4a1f003 100644 --- a/R/pivot_wider_pknca_results.R +++ b/R/pivot_wider_pknca_results.R @@ -20,7 +20,8 @@ #' should be applied, and `threshold` (numeric) specifying the threshold value for flagging. #' The name of each rule should correspond to a parameter in the results data.frame as a PPTESTCD #' (e.g., "R2ADJ", "AUCPEO", "AUCPEP", "LAMZSPN"). -#' @param extra_vars_to_keep Optional character vector of variable names to join from the concentration data to the output. Default is NULL. +#' @param extra_vars_to_keep Optional character vector of variable names to join from the +#' concentration data to the output. Default is NULL. #' #' @returns A data frame which provides an easy overview on the results from the NCA #' in each profile/subject and how it was computed lambda (half life) and the results @@ -164,7 +165,11 @@ pivot_wider_pknca_results <- function(myres, flag_rules = NULL, extra_vars_to_ke pivoted_res <- add_label_attribute(pivoted_res, myres) # Add flagging columns for each rule and a general "flagged" column - out <- .create_flags_for_profiles(final_results = pivoted_res, myres = myres, flag_rules = flag_rules) + out <- .create_flags_for_profiles( + final_results = pivoted_res, + myres = myres, + flag_rules = flag_rules + ) # If extra_vars_to_keep is provided, join these variables from the conc data if (!is.null(extra_vars_to_keep) && length(extra_vars_to_keep) > 0) { diff --git a/R/ratio_calculations.R b/R/ratio_calculations.R index 43a61c742..8647d635c 100644 --- a/R/ratio_calculations.R +++ b/R/ratio_calculations.R @@ -328,18 +328,18 @@ calculate_table_ratios_app <- function(res, ratio_table) { #' @param custom_pptestcd Optional character. If provided, will be used as the PPTESTCD value. #' @returns A data.frame with the calculated ratios for the specified settings. calculate_ratio_app <- function( - res, - test_parameter, - ref_parameter = test_parameter, - test_group = "(all other levels)", - ref_group = "PARAM: Analyte01", - aggregate_subject = "no", - adjusting_factor = 1, - custom_pptestcd = NULL + res, + test_parameter, + ref_parameter = test_parameter, + test_group = "(all other levels)", + ref_group = "PARAM: Analyte01", + aggregate_subject = "no", + adjusting_factor = 1, + custom_pptestcd = NULL ) { reference_colname <- gsub("(.*): (.*)", "\\1", ref_group) match_cols <- setdiff(unique(c(dplyr::group_vars(res), "start", "end")), reference_colname) - + ########### This is very App specific ############### if ("ATPTREF" %in% reference_colname) { match_cols <- setdiff(match_cols, c("start", "end")) @@ -348,7 +348,7 @@ calculate_ratio_app <- function( match_cols <- setdiff(match_cols, c("start", "end")) } ##################################################### - + if (aggregate_subject == "yes") { match_cols <- list(setdiff(match_cols, "USUBJID")) } else if (aggregate_subject == "no") { @@ -362,7 +362,7 @@ calculate_ratio_app <- function( match_cols <- list(match_cols, setdiff(match_cols, "USUBJID")) } } - + if (test_group == "(all other levels)") { test_groups <- NULL } else { @@ -377,7 +377,7 @@ calculate_ratio_app <- function( ) ) } - + reference_colname <- gsub("(.*): (.*)", "\\1", ref_group) reference_value <- gsub("(.*): (.*)", "\\2", ref_group) ref_groups <- data.frame( @@ -388,10 +388,10 @@ calculate_ratio_app <- function( dimnames = list(NULL, reference_colname) ) ) - - + + all_ratios <- data.frame() - + for (ix in seq_along(match_cols)) { ratio_calculations <- calculate_ratios( data = res$result, @@ -405,7 +405,7 @@ calculate_ratio_app <- function( ) all_ratios <- bind_rows(all_ratios, ratio_calculations) } - + # Assuming there cannot be more than 1 reference + PPTESTCD combination for the same group... # If aggregate_subject = 'if-needed', then this will remove cases when subject is not needed all_ratios %>% diff --git a/tests/testthat/test-pivot_wider_pknca_results.R b/tests/testthat/test-pivot_wider_pknca_results.R index 11378a653..ecb60ec76 100644 --- a/tests/testthat/test-pivot_wider_pknca_results.R +++ b/tests/testthat/test-pivot_wider_pknca_results.R @@ -249,7 +249,7 @@ describe("pivot_wider_pknca_results", { # All requested extra_vars should be present in the output (if present in conc data) present_vars <- intersect(extra_vars, names(pknca_res$data$conc$data)) - # Check all extra_vars present are added in the pivoted result + # Check all extra_vars present are added in the pivoted result expect_true(all(present_vars %in% names(pivoted_res))) }) }) From 5ae7cf7c8200490a34c568411eb400398527867e Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Mon, 15 Dec 2025 10:19:53 +0100 Subject: [PATCH 46/97] roxygen: docs --- man/calculate_ratio_app.Rd | 41 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 man/calculate_ratio_app.Rd diff --git a/man/calculate_ratio_app.Rd b/man/calculate_ratio_app.Rd new file mode 100644 index 000000000..55322344e --- /dev/null +++ b/man/calculate_ratio_app.Rd @@ -0,0 +1,41 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/ratio_calculations.R +\name{calculate_ratio_app} +\alias{calculate_ratio_app} +\title{Links the table ratio of the App with the ratio calculations via PKNCA results} +\usage{ +calculate_ratio_app( + res, + test_parameter, + ref_parameter = test_parameter, + test_group = "(all other levels)", + ref_group = "PARAM: Analyte01", + aggregate_subject = "no", + adjusting_factor = 1, + custom_pptestcd = NULL +) +} +\arguments{ +\item{res}{A PKNCAresult object.} + +\item{test_parameter}{Character. The PPTESTCD value to use as test (numerator).} + +\item{ref_parameter}{Character. The PPTESTCD value to use as reference (denominator). +Defaults to test_parameter.} + +\item{test_group}{Character. The test group (numerator). Default is "(all other levels)".} + +\item{ref_group}{Character. The reference group (denominator).} + +\item{aggregate_subject}{Character. Aggregation mode: "yes", "no", or "if-needed".} + +\item{adjusting_factor}{Numeric that multiplies the calculated ratio. Default is 1.} + +\item{custom_pptestcd}{Optional character. If provided, will be used as the PPTESTCD value.} +} +\value{ +A data.frame with the calculated ratios for the specified settings. +} +\description{ +Links the table ratio of the App with the ratio calculations via PKNCA results +} From df923f6844d5ef2cb8f2a81f2bfc2ac7460e980c Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Mon, 15 Dec 2025 11:09:39 +0100 Subject: [PATCH 47/97] refactor: lintr --- R/ratio_calculations.R | 1 - inst/shiny/modules/tab_nca/nca_results.R | 2 +- inst/shiny/www/templates/script_template.R | 4 +++- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/R/ratio_calculations.R b/R/ratio_calculations.R index 8647d635c..1db92a833 100644 --- a/R/ratio_calculations.R +++ b/R/ratio_calculations.R @@ -415,4 +415,3 @@ calculate_ratio_app <- function( ), .keep_all = TRUE) } - diff --git a/inst/shiny/modules/tab_nca/nca_results.R b/inst/shiny/modules/tab_nca/nca_results.R index d7840a34b..16e336c0d 100644 --- a/inst/shiny/modules/tab_nca/nca_results.R +++ b/inst/shiny/modules/tab_nca/nca_results.R @@ -139,7 +139,7 @@ nca_results_server <- function(id, pknca_data, res_nca, settings, ratio_table, g setts_tmpdir <- file.path(output_tmpdir, "settings") dir.create(setts_tmpdir, recursive = TRUE) saveRDS(session$userData$settings(), paste0(setts_tmpdir, "/settings.rds")) - + # Save a code R script template for the session script_tmpdir <- file.path(output_tmpdir, "code") dir.create(script_tmpdir, recursive = TRUE) diff --git a/inst/shiny/www/templates/script_template.R b/inst/shiny/www/templates/script_template.R index 6af70bc0e..860aa817f 100644 --- a/inst/shiny/www/templates/script_template.R +++ b/inst/shiny/www/templates/script_template.R @@ -55,7 +55,9 @@ pknca_obj <- preprocessed_adnca %>% # Define the desired units for the parameters (PPSTRESU) { pknca_obj <- . - pknca_obj[["units"]] <- units_table + if (!is.null(units_table)) { + pknca_obj[["units"]] <- units_table + } pknca_obj } From 4f10ab85728ccc55e40ef5580f2889bac7440bd6 Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Mon, 15 Dec 2025 11:11:53 +0100 Subject: [PATCH 48/97] docs: roxygenise --- man/pivot_wider_pknca_results.Rd | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/man/pivot_wider_pknca_results.Rd b/man/pivot_wider_pknca_results.Rd index 1317c0b49..da37f2f27 100644 --- a/man/pivot_wider_pknca_results.Rd +++ b/man/pivot_wider_pknca_results.Rd @@ -25,7 +25,8 @@ should be applied, and \code{threshold} (numeric) specifying the threshold value The name of each rule should correspond to a parameter in the results data.frame as a PPTESTCD (e.g., "R2ADJ", "AUCPEO", "AUCPEP", "LAMZSPN").} -\item{extra_vars_to_keep}{Optional character vector of variable names to join from the concentration data to the output. Default is NULL.} +\item{extra_vars_to_keep}{Optional character vector of variable names to join from the +concentration data to the output. Default is NULL.} } \value{ A data frame which provides an easy overview on the results from the NCA From 2f7fd2852859347d4670bc1937020c1b72c0a72b Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Mon, 15 Dec 2025 11:28:44 +0100 Subject: [PATCH 49/97] fix: consider data if not present for userData --- inst/shiny/modules/tab_data/data_upload.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/inst/shiny/modules/tab_data/data_upload.R b/inst/shiny/modules/tab_data/data_upload.R index 2cb2c1881..abd24d6ac 100644 --- a/inst/shiny/modules/tab_data/data_upload.R +++ b/inst/shiny/modules/tab_data/data_upload.R @@ -53,7 +53,7 @@ data_upload_server <- function(id) { if (!is.null(input$data_upload$datapath)) { session$userData$data_path <- input$data_upload$datapath } else { - session$userData$data_path <- system.file("shiny/data/Dummy_data.csv", package = "aNCA") + session$userData$data_path <- system.file("shiny/data/example-ADNCA.csv", package = "aNCA") } }) From 69728953f00666436665ac95fc1371f46c835f1f Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Mon, 15 Dec 2025 11:29:43 +0100 Subject: [PATCH 50/97] save extra_vars_to_keep in userData & use in template --- inst/shiny/modules/tab_nca/nca_results.R | 5 ++++- inst/shiny/www/templates/script_template.R | 7 ++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/inst/shiny/modules/tab_nca/nca_results.R b/inst/shiny/modules/tab_nca/nca_results.R index 16e336c0d..756b3405b 100644 --- a/inst/shiny/modules/tab_nca/nca_results.R +++ b/inst/shiny/modules/tab_nca/nca_results.R @@ -80,10 +80,13 @@ nca_results_server <- function(id, pknca_data, res_nca, settings, ratio_table, g results <- res_nca() # Transform results + extra_vars_to_keep <- c(grouping_vars(), "DOSEA", "ATPTREF", "ROUTE") + session$userData$extra_vars_to_keep <- extra_vars_to_keep + final_results <- pivot_wider_pknca_results( results, flag_rules = settings()$flags, - extra_vars_to_keep = c(grouping_vars(), "DOSEA", "ATPTREF", "ROUTE") + extra_vars_to_keep = extra_vars_to_keep ) final_results diff --git a/inst/shiny/www/templates/script_template.R b/inst/shiny/www/templates/script_template.R index 860aa817f..c74b0496b 100644 --- a/inst/shiny/www/templates/script_template.R +++ b/inst/shiny/www/templates/script_template.R @@ -69,6 +69,7 @@ slope_rules <- list( ) flag_rules <- session$userData$settings$flags ratio_table <- session$userData$ratio_table +extra_vars_to_keep <- session$userData$extra_vars_to_keep pknca_res <- pknca_obj %>% @@ -101,4 +102,8 @@ pknca_res <- pknca_obj %>% ## Obtain PP, ADPP, ADNCA & Pivoted results ######################### cdisc_datasets <- export_cdisc(pknca_res) -pivoted_results <- pivot_wider_pknca_results(pknca_res, flag_rules) +pivoted_results <- pivot_wider_pknca_results( + myres = pknca_res, + flag_rules = flag_rules, + extra_vars_to_keep = extra_vars_to_keep +) From 862e2aa7160e2ecb52fcb0af11318a14fdd417bb Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Mon, 15 Dec 2025 12:13:14 +0100 Subject: [PATCH 51/97] bump pkg version --- DESCRIPTION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DESCRIPTION b/DESCRIPTION index f496fa53c..3b9bd85e4 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: aNCA Title: (Pre-)Clinical NCA in a Dynamic Shiny App -Version: 0.1.0.9012 +Version: 0.1.0.9013 Authors@R: c( person("Ercan", "Suekuer", email = "ercan.suekuer@roche.com", role = "aut", comment = c(ORCID = "0009-0001-1626-1526")), From e2884221a30b39731740442aa6ae2d7291397770 Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Mon, 15 Dec 2025 12:13:28 +0100 Subject: [PATCH 52/97] news: add r-script news --- NEWS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/NEWS.md b/NEWS.md index 263dea7c4..3a1917934 100644 --- a/NEWS.md +++ b/NEWS.md @@ -8,6 +8,7 @@ * Interface includes now a color legend next to the pivoted NCA results to indicate missing and flagged parameters (#779) * Enhancements to the slides outputs including grouping by PKNCA groups, dose profile, and additional grouping variables (#791) * Option to include and apply NCA flag rules with reasons (NCAwXRS) as defined by ADNCA standards. Any record populated within these columns will be excluded for the NCA (#752) +* R script exported in ZIP folder to re-run and replicate dataset outputs (#789) ## Bugs fixed * Bug fix for box/violin plots that were crashing when PPSTRES is NA (#785) From d89b4aa70000127181335f1a3e14d7c29a523f59 Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Mon, 15 Dec 2025 14:59:43 +0100 Subject: [PATCH 53/97] rename test file --- .../{test-get_session_script_code.R => test-get_session_code.R} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tests/testthat/{test-get_session_script_code.R => test-get_session_code.R} (100%) diff --git a/tests/testthat/test-get_session_script_code.R b/tests/testthat/test-get_session_code.R similarity index 100% rename from tests/testthat/test-get_session_script_code.R rename to tests/testthat/test-get_session_code.R From 0e224d3de71b17b0885a53d849ba1a733e26ad95 Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Wed, 17 Dec 2025 10:28:52 +0100 Subject: [PATCH 54/97] fix: consider excluded records also in script --- inst/shiny/www/templates/script_template.R | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/inst/shiny/www/templates/script_template.R b/inst/shiny/www/templates/script_template.R index c74b0496b..4d6834164 100644 --- a/inst/shiny/www/templates/script_template.R +++ b/inst/shiny/www/templates/script_template.R @@ -39,7 +39,9 @@ units_table <- session$userData$final_units pknca_obj <- preprocessed_adnca %>% # Create from ADNCA the PKNCA object - PKNCA_create_data_object() %>% + PKNCA_create_data_object( + nca_exclude_reason_columns = c("DTYPE", mapping$NCAwXRS) + ) %>% # Setup basic settings PKNCA_update_data_object( From 19cdcdeb922224b3b25ce593915ced9bb5fd3eb9 Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Wed, 17 Dec 2025 10:31:12 +0100 Subject: [PATCH 55/97] fix: use group_vars to join the pivot_wider extra groups --- R/pivot_wider_pknca_results.R | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/R/pivot_wider_pknca_results.R b/R/pivot_wider_pknca_results.R index be4a1f003..0e4a64149 100644 --- a/R/pivot_wider_pknca_results.R +++ b/R/pivot_wider_pknca_results.R @@ -172,15 +172,16 @@ pivot_wider_pknca_results <- function(myres, flag_rules = NULL, extra_vars_to_ke ) # If extra_vars_to_keep is provided, join these variables from the conc data - if (!is.null(extra_vars_to_keep) && length(extra_vars_to_keep) > 0) { + if (length(extra_vars_to_keep) > 0) { conc_data <- myres$data$conc$data # Only keep columns that exist in conc_data vars_to_join <- intersect(extra_vars_to_keep, names(conc_data)) + group_vars <- group_vars(myres$data$conc) if (length(vars_to_join) > 0) { out <- out %>% dplyr::inner_join( - dplyr::select(conc_data, dplyr::any_of(vars_to_join)), - by = intersect(names(out), vars_to_join) + dplyr::select(conc_data, dplyr::any_of(c(vars_to_join, group_vars))), + by = intersect(names(out), names(c(vars_to_join, group_vars))) ) %>% dplyr::distinct() } From 8e294858a8d61335d43823f85312664f522604dc Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Tue, 23 Dec 2025 15:18:16 +0100 Subject: [PATCH 56/97] fix: issue parsing parameters_types with asyntactic list names --- R/get_session_code.R | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/R/get_session_code.R b/R/get_session_code.R index a45f3aa6a..949b1606e 100644 --- a/R/get_session_code.R +++ b/R/get_session_code.R @@ -21,7 +21,6 @@ get_session_code <- function(template_path, session, output_path) { } obj } - # Read template script <- readLines(template_path, warn = FALSE) %>% paste(collapse = "\n") @@ -115,6 +114,10 @@ clean_deparse.list <- function(obj, indent = 0) { nms <- names(obj) items <- vapply(seq_len(n), FUN.VALUE = "", function(i) { name <- if (!is.null(nms) && nzchar(nms[i])) nms[i] else paste0("V", i) + # Quote name if not a valid R symbol + if (!grepl("^[A-Za-z.][A-Za-z0-9._]*$", name)) { + name <- sprintf('"%s"', name) + } val <- obj[[i]] paste0(name, " = ", clean_deparse(val, indent + 1)) }) From c35f58da6423f96de1a8f1ded92c27ce2624c657 Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Tue, 23 Dec 2025 15:18:50 +0100 Subject: [PATCH 57/97] test: add case for asyntactic list deparsing --- tests/testthat/test-get_session_code.R | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/testthat/test-get_session_code.R b/tests/testthat/test-get_session_code.R index 24459c6fb..35a1e8342 100644 --- a/tests/testthat/test-get_session_code.R +++ b/tests/testthat/test-get_session_code.R @@ -23,6 +23,12 @@ describe("clean_deparse()", { expect_equal(clean_deparse(l), exp_named) }) + it("formats named lists with non-syntactic names correctly", { + l <- list("first item" = 1, "second-item" = "x") + exp_named <- "list(\n \"first item\" = 1,\n \"second-item\" = \"x\"\n)" + expect_equal(clean_deparse(l), exp_named) + }) + it("renders data.frame as data.frame(...) with per-column vectors", { df <- data.frame(x = c(1, 2), y = c("a", "b"), stringsAsFactors = FALSE) exp_df <- paste0( From 75e5fe03239fb663bb8d86726656cf11448bb851 Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Tue, 23 Dec 2025 15:29:13 +0100 Subject: [PATCH 58/97] fix: save parameter setts & adapt script template to new processing --- inst/shiny/modules/tab_nca/setup.R | 7 +++++-- inst/shiny/www/templates/script_template.R | 11 +++++++++-- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/inst/shiny/modules/tab_nca/setup.R b/inst/shiny/modules/tab_nca/setup.R index a0c154b47..7474afa6e 100644 --- a/inst/shiny/modules/tab_nca/setup.R +++ b/inst/shiny/modules/tab_nca/setup.R @@ -104,9 +104,12 @@ setup_server <- function(id, data, adnca_data) { final_settings <- reactive({ req(settings(), parameters_output$selections()) - current_settings <- settings() - current_settings$parameter_selections <- parameters_output$selections() + current_settings$parameters <- list( + selections = parameters_output$selections(), + types_df = parameters_output$types_df() + ) + current_settings }) diff --git a/inst/shiny/www/templates/script_template.R b/inst/shiny/www/templates/script_template.R index 4d6834164..6e924f78e 100644 --- a/inst/shiny/www/templates/script_template.R +++ b/inst/shiny/www/templates/script_template.R @@ -35,6 +35,8 @@ preprocessed_adnca <- adnca_data %>% ## Setup NCA settings in the PKNCA object ######################## auc_data <- session$userData$settings$partial_aucs units_table <- session$userData$final_units +parameters_selected_per_study <- session$userData$settings$parameters$selections +study_types_df <- session$userData$settings$parameters$types_df pknca_obj <- preprocessed_adnca %>% @@ -45,15 +47,20 @@ pknca_obj <- preprocessed_adnca %>% # Setup basic settings PKNCA_update_data_object( - auc_data = auc_data, method = session$userData$settings$method, selected_analytes = session$userData$settings$analyte, selected_profile = session$userData$settings$profile, selected_pcspec = session$userData$settings$pcspec, - params = session$userData$settings$parameter_selection, should_impute_c0 = session$userData$settings$data_imputation$impute_c0 ) %>% + update_main_intervals( + auc_data = auc_data, + parameter_selections = parameters_selected_per_study, + study_types_df = study_types_df, + impute = session$userData$settings$data_imputation$impute_c0 + ) %>% + # Define the desired units for the parameters (PPSTRESU) { pknca_obj <- . From 68717590a9368b55d3c2717c50acc0dc281063a7 Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Tue, 23 Dec 2025 15:35:26 +0100 Subject: [PATCH 59/97] refactor: lintr --- inst/shiny/modules/tab_nca/setup.R | 2 -- 1 file changed, 2 deletions(-) diff --git a/inst/shiny/modules/tab_nca/setup.R b/inst/shiny/modules/tab_nca/setup.R index 7474afa6e..6e11a52dc 100644 --- a/inst/shiny/modules/tab_nca/setup.R +++ b/inst/shiny/modules/tab_nca/setup.R @@ -109,8 +109,6 @@ setup_server <- function(id, data, adnca_data) { selections = parameters_output$selections(), types_df = parameters_output$types_df() ) - - current_settings }) From 2a685625d8372e11ac383ecb38e457994a8ecd21 Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Wed, 24 Dec 2025 11:36:20 +0100 Subject: [PATCH 60/97] combine use of methods and split in-lines when multiple elements --- R/get_session_code.R | 58 ++++++++++++++++++++++++++++++++++---------- 1 file changed, 45 insertions(+), 13 deletions(-) diff --git a/R/get_session_code.R b/R/get_session_code.R index 949b1606e..942522989 100644 --- a/R/get_session_code.R +++ b/R/get_session_code.R @@ -76,10 +76,6 @@ clean_deparse <- function(obj, indent = 0) { if (length(obj) == 0 && !is.null(obj)) { return(paste0(class(obj)[1], "()")) } - # For single-element atomic numeric/integer/logical return bare representation - if (length(obj) == 1 && class(obj)[1] %in% c("integer", "numeric", "logical")) { - return(as.character(obj)) - } UseMethod("clean_deparse") } @@ -94,10 +90,9 @@ clean_deparse.data.frame <- function(obj, indent = 0) { if (nrow(obj) == 0) return("data.frame()") cols <- lapply(obj, function(col) { - d <- deparse(col, width.cutoff = 500) %>% paste(collapse = "") - if (length(col) > 1 && !grepl("^c\\(", d)) d <- paste0("c(", d, ")") - d + clean_deparse(col, indent + 1) }) + col_strs <- paste0(ind, " ", names(obj), " = ", unlist(cols)) if (length(col_strs) > 1) { not_last <- seq_len(length(col_strs) - 1) @@ -110,7 +105,6 @@ clean_deparse.data.frame <- function(obj, indent = 0) { clean_deparse.list <- function(obj, indent = 0) { ind <- paste(rep(" ", indent), collapse = "") n <- length(obj) - if (n == 0) return("list()") nms <- names(obj) items <- vapply(seq_len(n), FUN.VALUE = "", function(i) { name <- if (!is.null(nms) && nzchar(nms[i])) nms[i] else paste0("V", i) @@ -119,7 +113,9 @@ clean_deparse.list <- function(obj, indent = 0) { name <- sprintf('"%s"', name) } val <- obj[[i]] - paste0(name, " = ", clean_deparse(val, indent + 1)) + # Use specialized deparsers for atomic vectors + val_str <- clean_deparse(val, indent + 1) + paste0(name, " = ", val_str) }) if (length(items) > 1) { not_last <- seq_len(length(items) - 1) @@ -130,14 +126,43 @@ clean_deparse.list <- function(obj, indent = 0) { } #' @noRd + clean_deparse.character <- function(obj, indent = 0) { - if (length(obj) == 1) return(sprintf('"%s"', obj)) - paste0("c(", paste(sprintf('"%s"', obj), collapse = ", "), ")") + n <- length(obj) + if (n == 1) { + return(sprintf('"%s"', obj)) + } else if (n > 10) { + ind <- paste(rep(" ", indent), collapse = "") + lines <- split(obj, ceiling(seq_along(obj) / 10)) + line_strs <- vapply(lines, function(x) paste(sprintf('"%s"', x), collapse = ", "), "") + paste0( + "c(\n", + paste0(ind, " ", line_strs, collapse = ",\n"), + "\n", ind, ")" + ) + } else { + paste0("c(", paste(sprintf('"%s"', obj), collapse = ", "), ")") + } } #' @noRd + clean_deparse.numeric <- function(obj, indent = 0) { - paste0("c(", paste(obj, collapse = ", "), ")") + n <- length(obj) + if (n == 1) { + return(paste0(obj)) + } else if (n > 10) { + ind <- paste(rep(" ", indent), collapse = "") + lines <- split(obj, ceiling(seq_along(obj) / 10)) + line_strs <- vapply(lines, function(x) paste(x, collapse = ", "), "") + paste0( + "c(\n", + paste0(ind, " ", line_strs, collapse = ",\n"), + "\n", ind, ")" + ) + } else { + paste0("c(", paste(obj, collapse = ", "), ")") + } } #' @noRd @@ -145,5 +170,12 @@ clean_deparse.integer <- clean_deparse.numeric #' @noRd clean_deparse.logical <- function(obj, indent = 0) { - paste0("c(", paste(ifelse(obj, "TRUE", "FALSE"), collapse = ", "), ")") + n <- length(obj) + if (n == 0) { + return("logical()") + } else if (n == 1) { + return(paste0(obj)) + } else { + paste0("c(", paste(ifelse(obj, "TRUE", "FALSE"), collapse = ", "), ")") + } } From c69e9bc6e975843738908d85e7bdb3131fb7c991 Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Wed, 24 Dec 2025 11:39:27 +0100 Subject: [PATCH 61/97] tests: add case for line-split in clean_deparse --- tests/testthat/test-get_session_code.R | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/testthat/test-get_session_code.R b/tests/testthat/test-get_session_code.R index 35a1e8342..7b9e2d21a 100644 --- a/tests/testthat/test-get_session_code.R +++ b/tests/testthat/test-get_session_code.R @@ -61,4 +61,14 @@ describe("clean_deparse()", { expect_equal(clean_deparse(character(0)), "character()") expect_equal(clean_deparse(numeric(0)), "numeric()") }) + + it("renders data.frame with long columns using newlines and indentation", { + long_vec <- as.character(1:25) + df <- data.frame(x = 1:25, y = long_vec, stringsAsFactors = FALSE) + out <- clean_deparse(df) + expect_true(grepl("c\\(\n", out)) # Should use newlines for long columns + expect_true(grepl(" 1, 2, 3", out)) # Indentation present + expect_true(grepl("x = c\\(", out)) # Column x uses c( ... ) + expect_true(grepl("y = c\\(", out)) # Column y uses c( ... ) + }) }) From a516839e8f7d8cbb87da565174c9520e6f6a9ecd Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Wed, 24 Dec 2025 11:58:49 +0100 Subject: [PATCH 62/97] refactor: add arg max_per_line --- R/get_session_code.R | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/R/get_session_code.R b/R/get_session_code.R index 942522989..fd32e109e 100644 --- a/R/get_session_code.R +++ b/R/get_session_code.R @@ -71,7 +71,7 @@ get_session_code <- function(template_path, session, output_path) { #' @return A single string containing R code that, when evaluated, will #' reconstruct `obj` (or a close approximation for complex types). #' @keywords internal -clean_deparse <- function(obj, indent = 0) { +clean_deparse <- function(obj, indent = 0, max_per_line = 10) { # Handle trivial length-0 constructors (character(0), numeric(0), list(), data.frame(), ...) if (length(obj) == 0 && !is.null(obj)) { return(paste0(class(obj)[1], "()")) @@ -80,17 +80,17 @@ clean_deparse <- function(obj, indent = 0) { } #' @noRd -clean_deparse.default <- function(obj, indent = 0) { +clean_deparse.default <- function(obj, indent = 0, max_per_line = 10) { paste(deparse(obj, width.cutoff = 500), collapse = "") } #' @noRd -clean_deparse.data.frame <- function(obj, indent = 0) { +clean_deparse.data.frame <- function(obj, indent = 0, max_per_line = 10) { ind <- paste(rep(" ", indent), collapse = "") if (nrow(obj) == 0) return("data.frame()") cols <- lapply(obj, function(col) { - clean_deparse(col, indent + 1) + clean_deparse(col, indent + 1, max_per_line = max_per_line) }) col_strs <- paste0(ind, " ", names(obj), " = ", unlist(cols)) @@ -102,7 +102,7 @@ clean_deparse.data.frame <- function(obj, indent = 0) { } #' @noRd -clean_deparse.list <- function(obj, indent = 0) { +clean_deparse.list <- function(obj, indent = 0, max_per_line = 10) { ind <- paste(rep(" ", indent), collapse = "") n <- length(obj) nms <- names(obj) @@ -113,8 +113,7 @@ clean_deparse.list <- function(obj, indent = 0) { name <- sprintf('"%s"', name) } val <- obj[[i]] - # Use specialized deparsers for atomic vectors - val_str <- clean_deparse(val, indent + 1) + val_str <- clean_deparse(val, indent + 1, max_per_line = max_per_line) paste0(name, " = ", val_str) }) if (length(items) > 1) { @@ -127,13 +126,13 @@ clean_deparse.list <- function(obj, indent = 0) { #' @noRd -clean_deparse.character <- function(obj, indent = 0) { +clean_deparse.character <- function(obj, indent = 0, max_per_line = 10) { n <- length(obj) if (n == 1) { return(sprintf('"%s"', obj)) - } else if (n > 10) { + } else if (n > max_per_line) { ind <- paste(rep(" ", indent), collapse = "") - lines <- split(obj, ceiling(seq_along(obj) / 10)) + lines <- split(obj, ceiling(seq_along(obj) / max_per_line)) line_strs <- vapply(lines, function(x) paste(sprintf('"%s"', x), collapse = ", "), "") paste0( "c(\n", @@ -147,13 +146,13 @@ clean_deparse.character <- function(obj, indent = 0) { #' @noRd -clean_deparse.numeric <- function(obj, indent = 0) { +clean_deparse.numeric <- function(obj, indent = 0, max_per_line = 10) { n <- length(obj) if (n == 1) { return(paste0(obj)) - } else if (n > 10) { + } else if (n > max_per_line) { ind <- paste(rep(" ", indent), collapse = "") - lines <- split(obj, ceiling(seq_along(obj) / 10)) + lines <- split(obj, ceiling(seq_along(obj) / max_per_line)) line_strs <- vapply(lines, function(x) paste(x, collapse = ", "), "") paste0( "c(\n", @@ -169,7 +168,7 @@ clean_deparse.numeric <- function(obj, indent = 0) { clean_deparse.integer <- clean_deparse.numeric #' @noRd -clean_deparse.logical <- function(obj, indent = 0) { +clean_deparse.logical <- function(obj, indent = 0, max_per_line = 10) { n <- length(obj) if (n == 0) { return("logical()") From e2e281904336093ca35f17e5d30bc5efee5127a1 Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Wed, 24 Dec 2025 11:59:46 +0100 Subject: [PATCH 63/97] test: add tests for arg max_per_line --- tests/testthat/test-get_session_code.R | 50 ++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/tests/testthat/test-get_session_code.R b/tests/testthat/test-get_session_code.R index 7b9e2d21a..1f8dc1d69 100644 --- a/tests/testthat/test-get_session_code.R +++ b/tests/testthat/test-get_session_code.R @@ -71,4 +71,54 @@ describe("clean_deparse()", { expect_true(grepl("x = c\\(", out)) # Column x uses c( ... ) expect_true(grepl("y = c\\(", out)) # Column y uses c( ... ) }) + + it("respects max_per_line for character vectors", { + v <- as.character(1:6) + out <- clean_deparse(v, max_per_line = 2) + exp_out <- "c(\n \"1\", \"2\",\n \"3\", \"4\",\n \"5\", \"6\"\n)" + expect_equal(out, exp_out) + }) + + it("respects max_per_line for numeric vectors", { + v <- 1:6 + out <- clean_deparse(v, max_per_line = 2) + exp_out <- "c(\n 1, 2,\n 3, 4,\n 5, 6\n)" + expect_equal(out, exp_out) + }) + + it("respects max_per_line for lists of vectors", { + l <- list(a = 1:4, b = letters[1:4]) + out <- clean_deparse(l, max_per_line = 2) + exp_out <- paste0( + "list(\n", + " a = c(\n", + " 1, 2,\n", + " 3, 4\n", + " ),\n", + " b = c(\n", + ' "a", "b",\n', + ' "c", "d"\n', + " )\n", + ")" + ) + expect_equal(out, exp_out) + }) + + it("respects max_per_line for data.frames", { + df <- data.frame(x = 1:4, y = letters[1:4], stringsAsFactors = FALSE) + out <- clean_deparse(df, max_per_line = 2) + exp_out <- paste0( + "data.frame(\n", + " x = c(\n", + " 1, 2,\n", + " 3, 4\n", + " ),\n", + " y = c(\n", + ' "a", "b",\n', + ' "c", "d"\n', + " )\n", + ")" + ) + expect_equal(out, exp_out) + }) }) From dcf98c82bce9961bbc00fbf7616bd42b8bd37662 Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Wed, 24 Dec 2025 12:01:47 +0100 Subject: [PATCH 64/97] man: roxygenise --- man/clean_deparse.Rd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/man/clean_deparse.Rd b/man/clean_deparse.Rd index daff2c15f..22f96c33a 100644 --- a/man/clean_deparse.Rd +++ b/man/clean_deparse.Rd @@ -4,7 +4,7 @@ \alias{clean_deparse} \title{Convert R objects into reproducible R code strings (internal)} \usage{ -clean_deparse(obj, indent = 0) +clean_deparse(obj, indent = 0, max_per_line = 10) } \arguments{ \item{obj}{An R object to convert to a string of R code.} From 3a44f36c1df66d5b79fdc671385b74be457a4489 Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Wed, 24 Dec 2025 12:05:45 +0100 Subject: [PATCH 65/97] refactor: lintr --- R/get_session_code.R | 8 ++++---- tests/testthat/test-get_session_code.R | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/R/get_session_code.R b/R/get_session_code.R index fd32e109e..054568491 100644 --- a/R/get_session_code.R +++ b/R/get_session_code.R @@ -129,7 +129,7 @@ clean_deparse.list <- function(obj, indent = 0, max_per_line = 10) { clean_deparse.character <- function(obj, indent = 0, max_per_line = 10) { n <- length(obj) if (n == 1) { - return(sprintf('"%s"', obj)) + sprintf('"%s"', obj) } else if (n > max_per_line) { ind <- paste(rep(" ", indent), collapse = "") lines <- split(obj, ceiling(seq_along(obj) / max_per_line)) @@ -149,7 +149,7 @@ clean_deparse.character <- function(obj, indent = 0, max_per_line = 10) { clean_deparse.numeric <- function(obj, indent = 0, max_per_line = 10) { n <- length(obj) if (n == 1) { - return(paste0(obj)) + paste0(obj) } else if (n > max_per_line) { ind <- paste(rep(" ", indent), collapse = "") lines <- split(obj, ceiling(seq_along(obj) / max_per_line)) @@ -171,9 +171,9 @@ clean_deparse.integer <- clean_deparse.numeric clean_deparse.logical <- function(obj, indent = 0, max_per_line = 10) { n <- length(obj) if (n == 0) { - return("logical()") + "logical()" } else if (n == 1) { - return(paste0(obj)) + paste0(obj) } else { paste0("c(", paste(ifelse(obj, "TRUE", "FALSE"), collapse = ", "), ")") } diff --git a/tests/testthat/test-get_session_code.R b/tests/testthat/test-get_session_code.R index 1f8dc1d69..c3d84dbcf 100644 --- a/tests/testthat/test-get_session_code.R +++ b/tests/testthat/test-get_session_code.R @@ -71,7 +71,7 @@ describe("clean_deparse()", { expect_true(grepl("x = c\\(", out)) # Column x uses c( ... ) expect_true(grepl("y = c\\(", out)) # Column y uses c( ... ) }) - + it("respects max_per_line for character vectors", { v <- as.character(1:6) out <- clean_deparse(v, max_per_line = 2) From 5efa5050b3f9cc45d0fb1b254e700605a5f55b85 Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Wed, 24 Dec 2025 12:33:29 +0100 Subject: [PATCH 66/97] feat: save input data in ZIP & add relative path in script template --- inst/shiny/modules/tab_nca/nca_results.R | 6 ++++++ inst/shiny/www/templates/script_template.R | 8 ++++++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/inst/shiny/modules/tab_nca/nca_results.R b/inst/shiny/modules/tab_nca/nca_results.R index c6491e96f..c6d44964e 100644 --- a/inst/shiny/modules/tab_nca/nca_results.R +++ b/inst/shiny/modules/tab_nca/nca_results.R @@ -154,6 +154,12 @@ nca_results_server <- function(id, pknca_data, res_nca, settings, ratio_table, g saveRDS(setings_to_save, paste0(setts_tmpdir, "/settings.rds")) + # Save input dataset used + data_tmpdir <- file.path(output_tmpdir, "data") + dir.create(data_tmpdir, recursive = TRUE) + data <- read_pk(session$userData$data_path) + saveRDS(data, paste0(data_tmpdir, "/data.rds")) + # Save a code R script template for the session script_tmpdir <- file.path(output_tmpdir, "code") dir.create(script_tmpdir, recursive = TRUE) diff --git a/inst/shiny/www/templates/script_template.R b/inst/shiny/www/templates/script_template.R index 6e924f78e..d2fe6a1b1 100644 --- a/inst/shiny/www/templates/script_template.R +++ b/inst/shiny/www/templates/script_template.R @@ -1,10 +1,14 @@ -# Load the package (https://github.com/pharmaverse/aNCA) # +# Load the package (https://github.com/pharmaverse/aNCA) # +########################################################### +## Please, set your working directory to this file: +# setwd("path/to/this/file/script_template.R") + if (!require("aNCA")) install.packages("aNCA") library(aNCA) library(dplyr) # Load raw data # -data_path <- session$userData$data_path +data_path <- "../data/data.rds" adnca_data <- read_pk(data_path) ## Preprocess data ######################################## From 80e05ad87b6defa7fcc0008965df8f263385d763 Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Tue, 30 Dec 2025 07:44:28 +0100 Subject: [PATCH 67/97] feat: use rep for clean_deparse --- R/get_session_code.R | 69 +++++++++++++++++++++++++++++++------------- 1 file changed, 49 insertions(+), 20 deletions(-) diff --git a/R/get_session_code.R b/R/get_session_code.R index 054568491..42569ea50 100644 --- a/R/get_session_code.R +++ b/R/get_session_code.R @@ -45,7 +45,7 @@ get_session_code <- function(template_path, session, output_path) { path <- sub("^session\\$userData\\$", "", matched) value <- get_session_value(path) - deparsed <- clean_deparse(value) + deparsed <- clean_deparse(value, max_per_line = 15) script <- paste0( substr(script, 1, start - 1), deparsed, @@ -71,7 +71,7 @@ get_session_code <- function(template_path, session, output_path) { #' @return A single string containing R code that, when evaluated, will #' reconstruct `obj` (or a close approximation for complex types). #' @keywords internal -clean_deparse <- function(obj, indent = 0, max_per_line = 10) { +clean_deparse <- function(obj, indent = 0, max_per_line = 10, simplify_with_rep = TRUE) { # Handle trivial length-0 constructors (character(0), numeric(0), list(), data.frame(), ...) if (length(obj) == 0 && !is.null(obj)) { return(paste0(class(obj)[1], "()")) @@ -85,7 +85,7 @@ clean_deparse.default <- function(obj, indent = 0, max_per_line = 10) { } #' @noRd -clean_deparse.data.frame <- function(obj, indent = 0, max_per_line = 10) { +clean_deparse.data.frame <- function(obj, indent = 0, max_per_line = 10, simplify_with_rep = TRUE) { ind <- paste(rep(" ", indent), collapse = "") if (nrow(obj) == 0) return("data.frame()") @@ -126,46 +126,75 @@ clean_deparse.list <- function(obj, indent = 0, max_per_line = 10) { #' @noRd -clean_deparse.character <- function(obj, indent = 0, max_per_line = 10) { +clean_deparse.character <- function(obj, indent = 0, max_per_line = 10, min_to_rep = max_per_line) { n <- length(obj) + obj <- sprintf('"%s"', obj) if (n == 1) { - sprintf('"%s"', obj) + return(obj) + } else if (n <= max_per_line) { + return(paste0("c(", paste0(obj, collapse = ", "), ")")) } else if (n > max_per_line) { - ind <- paste(rep(" ", indent), collapse = "") - lines <- split(obj, ceiling(seq_along(obj) / max_per_line)) - line_strs <- vapply(lines, function(x) paste(sprintf('"%s"', x), collapse = ", "), "") - paste0( - "c(\n", - paste0(ind, " ", line_strs, collapse = ",\n"), - "\n", ind, ")" - ) - } else { - paste0("c(", paste(sprintf('"%s"', obj), collapse = ", "), ")") + rle_obj <- rle(obj) + lines_obj <- c() + for (i in seq_along(rle_obj$values)) { + val <- rle_obj$values[i] + len <- rle_obj$lengths[i] + + if (len >= min_to_rep) { + rep_obj <- paste0("rep(", val, ", ", len, ")") + lines_obj <- c(lines_obj, rep_obj) + } else { + lines_obj <- c(lines_obj, (rep(val, len))) + } + } } + ind <- paste(rep(" ", indent), collapse = "") + lines <- split(lines_obj, ceiling(seq_along(lines_obj) / max_per_line)) + line_strs <- vapply(lines, function(x) paste(x, collapse = ", "), "") + paste0( + "c(\n", + paste0(ind, " ", line_strs, collapse = ",\n"), + "\n", ind, ")" + ) } #' @noRd -clean_deparse.numeric <- function(obj, indent = 0, max_per_line = 10) { +clean_deparse.numeric <- function(obj, indent = 0, max_per_line = 10, min_to_rep = max_per_line) { n <- length(obj) if (n == 1) { paste0(obj) + } else if (n <= max_per_line) { + paste0("c(", paste0(obj, collapse = ", "), ")") } else if (n > max_per_line) { + rle_obj <- rle(obj) + lines_obj <- c() + for (i in seq_along(rle_obj$lengths)) { + val <- rle_obj$values[i] + len <- rle_obj$lengths[i] + if (len > min_to_rep) { + lines_obj <- c(lines_obj, sprintf('rep(%s, %d)', val, len)) + } else if (len == 1) { + lines_obj <- c(lines_obj, as.character(val)) + } else { + lines_obj <- c(lines_obj, as.character(rep(val, len))) + } + } ind <- paste(rep(" ", indent), collapse = "") - lines <- split(obj, ceiling(seq_along(obj) / max_per_line)) + lines <- split(lines_obj, ceiling(seq_along(lines_obj) / max_per_line)) line_strs <- vapply(lines, function(x) paste(x, collapse = ", "), "") paste0( "c(\n", paste0(ind, " ", line_strs, collapse = ",\n"), "\n", ind, ")" ) - } else { - paste0("c(", paste(obj, collapse = ", "), ")") } } #' @noRd -clean_deparse.integer <- clean_deparse.numeric +clean_deparse.integer <- function(obj, indent = 0, max_per_line = 10, min_to_rep = max_per_line) { + clean_deparse.numeric(obj, indent = indent, max_per_line = max_per_line, min_to_rep = min_to_rep) +} #' @noRd clean_deparse.logical <- function(obj, indent = 0, max_per_line = 10) { From 110595e57be848ce396f4a18dc94abb5537279ad Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Tue, 30 Dec 2025 07:44:45 +0100 Subject: [PATCH 68/97] test: add the rep cases --- tests/testthat/test-get_session_code.R | 39 +++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/tests/testthat/test-get_session_code.R b/tests/testthat/test-get_session_code.R index c3d84dbcf..82880ddde 100644 --- a/tests/testthat/test-get_session_code.R +++ b/tests/testthat/test-get_session_code.R @@ -106,7 +106,7 @@ describe("clean_deparse()", { it("respects max_per_line for data.frames", { df <- data.frame(x = 1:4, y = letters[1:4], stringsAsFactors = FALSE) - out <- clean_deparse(df, max_per_line = 2) + out <- clean_deparse.data.frame(df, max_per_line = 2) exp_out <- paste0( "data.frame(\n", " x = c(\n", @@ -121,4 +121,41 @@ describe("clean_deparse()", { ) expect_equal(out, exp_out) }) + + it("uses rep(...) when values are repeated at least min_to_rep times", { + + # Test character + char_vec <- c(rep("apple", 5), "banana", "cherry", rep("date", 3)) + out_char <- clean_deparse.character(char_vec, max_per_line = 3, min_to_rep = 2) + exp_out_char <- paste0( + "c(\n", + " rep(\"apple\", 5), \"banana\", \"cherry\",\n", + " rep(\"date\", 3)\n", + ")" + ) + expect_equal(out_char, exp_out_char) + + # Test numeric + vec <- c(rep(5, 10), 6, 7, rep(8, 5)) + out_vec <- clean_deparse.numeric(vec, max_per_line = 3, min_to_rep = 2) + exp_out_vec <- paste0( + "c(\n", + " rep(5, 10), 6, 7,\n", + " rep(8, 5)\n", + ")" + ) + expect_equal(out_vec, exp_out_vec) + + # Test min_to_rep greater than any repetition + vec <- c(1, rep(2, 2), rep(3, 3), rep(4, 4)) + + out_rep1234 <- clean_deparse.numeric(vec, max_per_line = 10, min_to_rep = 2) + + out_rep4 <- clean_deparse.numeric(vec, max_per_line = 10, min_to_rep = 3) + exp_out_rep4 <- paste0( + "c(\n", + " 1, 2, 2, 3, 3, 3, rep(4, 4)\n", + ")" + ) + }) }) From 17594ab05924c7bf49f5fbbadd252880c33df06e Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Tue, 30 Dec 2025 08:21:18 +0100 Subject: [PATCH 69/97] fix: ensure USUBJID is always char --- inst/shiny/www/templates/script_template.R | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/inst/shiny/www/templates/script_template.R b/inst/shiny/www/templates/script_template.R index d2fe6a1b1..2d7e2bd89 100644 --- a/inst/shiny/www/templates/script_template.R +++ b/inst/shiny/www/templates/script_template.R @@ -34,7 +34,10 @@ preprocessed_adnca <- adnca_data %>% ) %>% # Derive METABFL column using PARAM metabolites - create_metabfl(mapping$Metabolites) + create_metabfl(mapping$Metabolites) %>% + + # Make sure all variables are in its correct class + adjust_class_and_length(metadata_nca_variables) ## Setup NCA settings in the PKNCA object ######################## auc_data <- session$userData$settings$partial_aucs From e9254c45bf8ee208dd60f6996f37ad47ad1701d9 Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Tue, 30 Dec 2025 08:31:21 +0100 Subject: [PATCH 70/97] refactor & docs --- R/get_session_code.R | 27 ++++++++++++++++---------- tests/testthat/test-get_session_code.R | 1 - 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/R/get_session_code.R b/R/get_session_code.R index 42569ea50..7e392eba4 100644 --- a/R/get_session_code.R +++ b/R/get_session_code.R @@ -6,7 +6,6 @@ #' @return The output_path (invisibly) #' @export get_session_code <- function(template_path, session, output_path) { - # Helper to get value from session$userData by path (e.g., 'settings$method') get_session_value <- function(path) { parts <- strsplit(path, "\\$")[[1]] @@ -17,7 +16,9 @@ get_session_code <- function(template_path, session, output_path) { } else { obj <- obj[[p]] } - if (is.null(obj)) return(NULL) + if (is.null(obj)) { + return(NULL) + } } obj } @@ -67,11 +68,15 @@ get_session_code <- function(template_path, session, output_path) { #' serialize `session$userData` values into a runnable R script. #' #' @param obj An R object to convert to a string of R code. +#' @param max_per_line Maximum number of elements to include per line for +#' long vectors/lists. +#' @param min_to_rep Minimum number of repeated elements to use `rep()` for +#' long vectors/lists. #' @param indent Integer indentation level for multi-line outputs. #' @return A single string containing R code that, when evaluated, will #' reconstruct `obj` (or a close approximation for complex types). #' @keywords internal -clean_deparse <- function(obj, indent = 0, max_per_line = 10, simplify_with_rep = TRUE) { +clean_deparse <- function(obj, indent = 0, max_per_line = 10, min_to_rep = 3) { # Handle trivial length-0 constructors (character(0), numeric(0), list(), data.frame(), ...) if (length(obj) == 0 && !is.null(obj)) { return(paste0(class(obj)[1], "()")) @@ -85,9 +90,11 @@ clean_deparse.default <- function(obj, indent = 0, max_per_line = 10) { } #' @noRd -clean_deparse.data.frame <- function(obj, indent = 0, max_per_line = 10, simplify_with_rep = TRUE) { +clean_deparse.data.frame <- function(obj, indent = 0, max_per_line = 10, min_to_rep = 3) { ind <- paste(rep(" ", indent), collapse = "") - if (nrow(obj) == 0) return("data.frame()") + if (nrow(obj) == 0) { + return("data.frame()") + } cols <- lapply(obj, function(col) { clean_deparse(col, indent + 1, max_per_line = max_per_line) @@ -126,7 +133,7 @@ clean_deparse.list <- function(obj, indent = 0, max_per_line = 10) { #' @noRd -clean_deparse.character <- function(obj, indent = 0, max_per_line = 10, min_to_rep = max_per_line) { +clean_deparse.character <- function(obj, indent = 0, max_per_line = 10, min_to_rep = 3) { n <- length(obj) obj <- sprintf('"%s"', obj) if (n == 1) { @@ -160,7 +167,7 @@ clean_deparse.character <- function(obj, indent = 0, max_per_line = 10, min_to_r #' @noRd -clean_deparse.numeric <- function(obj, indent = 0, max_per_line = 10, min_to_rep = max_per_line) { +clean_deparse.numeric <- function(obj, indent = 0, max_per_line = 10, min_to_rep = 3) { n <- length(obj) if (n == 1) { paste0(obj) @@ -173,11 +180,11 @@ clean_deparse.numeric <- function(obj, indent = 0, max_per_line = 10, min_to_rep val <- rle_obj$values[i] len <- rle_obj$lengths[i] if (len > min_to_rep) { - lines_obj <- c(lines_obj, sprintf('rep(%s, %d)', val, len)) + lines_obj <- c(lines_obj, sprintf("rep(%s, %d)", val, len)) } else if (len == 1) { lines_obj <- c(lines_obj, as.character(val)) } else { - lines_obj <- c(lines_obj, as.character(rep(val, len))) + lines_obj <- c(lines_obj, as.character(rep(val, len))) } } ind <- paste(rep(" ", indent), collapse = "") @@ -192,7 +199,7 @@ clean_deparse.numeric <- function(obj, indent = 0, max_per_line = 10, min_to_rep } #' @noRd -clean_deparse.integer <- function(obj, indent = 0, max_per_line = 10, min_to_rep = max_per_line) { +clean_deparse.integer <- function(obj, indent = 0, max_per_line = 10, min_to_rep = 3) { clean_deparse.numeric(obj, indent = indent, max_per_line = max_per_line, min_to_rep = min_to_rep) } diff --git a/tests/testthat/test-get_session_code.R b/tests/testthat/test-get_session_code.R index 82880ddde..6e91da6a7 100644 --- a/tests/testthat/test-get_session_code.R +++ b/tests/testthat/test-get_session_code.R @@ -123,7 +123,6 @@ describe("clean_deparse()", { }) it("uses rep(...) when values are repeated at least min_to_rep times", { - # Test character char_vec <- c(rep("apple", 5), "banana", "cherry", rep("date", 3)) out_char <- clean_deparse.character(char_vec, max_per_line = 3, min_to_rep = 2) From 3b0a9e4d3ad949ec17f18839dda0e804ca3a7aa7 Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Tue, 30 Dec 2025 08:32:20 +0100 Subject: [PATCH 71/97] man: roxygenise --- man/clean_deparse.Rd | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/man/clean_deparse.Rd b/man/clean_deparse.Rd index 22f96c33a..835abff28 100644 --- a/man/clean_deparse.Rd +++ b/man/clean_deparse.Rd @@ -4,12 +4,18 @@ \alias{clean_deparse} \title{Convert R objects into reproducible R code strings (internal)} \usage{ -clean_deparse(obj, indent = 0, max_per_line = 10) +clean_deparse(obj, indent = 0, max_per_line = 10, min_to_rep = 3) } \arguments{ \item{obj}{An R object to convert to a string of R code.} \item{indent}{Integer indentation level for multi-line outputs.} + +\item{max_per_line}{Maximum number of elements to include per line for +long vectors/lists.} + +\item{min_to_rep}{Minimum number of repeated elements to use \code{rep()} for +long vectors/lists.} } \value{ A single string containing R code that, when evaluated, will From 1ebfeafeb8a2c2eb9064d89c8d917e891ec19577 Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Tue, 30 Dec 2025 08:42:24 +0100 Subject: [PATCH 72/97] lintr: exclude template from check --- .lintr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.lintr b/.lintr index 9a641ba55..a1383cecf 100644 --- a/.lintr +++ b/.lintr @@ -11,4 +11,4 @@ linters: seq_linter = seq_linter() ) encoding: "UTF-8" -exclusions: list("R/PKNCA_extra_parameters.R", "tests/testthat/test-PKNCA_extra_parameters.R") +exclusions: list("R/PKNCA_extra_parameters.R", "tests/testthat/test-PKNCA_extra_parameters.R", "inst/shiny/www/templates/script_template.R") From 0f4acacc312f43984bbc2d9524bb3e5bbb514a4b Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Tue, 30 Dec 2025 08:58:32 +0100 Subject: [PATCH 73/97] fix: min_to_rep --- R/get_session_code.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/get_session_code.R b/R/get_session_code.R index 7e392eba4..827828e65 100644 --- a/R/get_session_code.R +++ b/R/get_session_code.R @@ -179,7 +179,7 @@ clean_deparse.numeric <- function(obj, indent = 0, max_per_line = 10, min_to_rep for (i in seq_along(rle_obj$lengths)) { val <- rle_obj$values[i] len <- rle_obj$lengths[i] - if (len > min_to_rep) { + if (len >= min_to_rep) { lines_obj <- c(lines_obj, sprintf("rep(%s, %d)", val, len)) } else if (len == 1) { lines_obj <- c(lines_obj, as.character(val)) From 28a284188c3d7948641d3510ebe92a35d9308055 Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Tue, 30 Dec 2025 08:58:48 +0100 Subject: [PATCH 74/97] test: check that min_to_rep is accurate --- tests/testthat/test-get_session_code.R | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/tests/testthat/test-get_session_code.R b/tests/testthat/test-get_session_code.R index 6e91da6a7..d51070f6e 100644 --- a/tests/testthat/test-get_session_code.R +++ b/tests/testthat/test-get_session_code.R @@ -148,13 +148,19 @@ describe("clean_deparse()", { # Test min_to_rep greater than any repetition vec <- c(1, rep(2, 2), rep(3, 3), rep(4, 4)) - out_rep1234 <- clean_deparse.numeric(vec, max_per_line = 10, min_to_rep = 2) - - out_rep4 <- clean_deparse.numeric(vec, max_per_line = 10, min_to_rep = 3) - exp_out_rep4 <- paste0( + out_2_to_rep <- clean_deparse(vec, max_per_line = 10, min_to_rep = 2) + out_3_to_rep <- clean_deparse(vec, max_per_line = 10, min_to_rep = 3) + exp_out_2_to_rep <- paste0( + "c(\n", + " 1, rep(2, 2), rep(3, 3), rep(4, 4)\n", + ")" + ) + exp_out_3_to_rep <- paste0( "c(\n", - " 1, 2, 2, 3, 3, 3, rep(4, 4)\n", + " 1, 2, 2, rep(3, 3), rep(4, 4)\n", ")" ) + expect_equal(out_2_to_rep, exp_out_2_to_rep) + expect_equal(out_3_to_rep, exp_out_3_to_rep) }) }) From e150b75eaa6f4aecb81c11306bc17ab4ff35d346 Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Tue, 30 Dec 2025 09:01:24 +0100 Subject: [PATCH 75/97] fix: pivot_wider_pknca_results intersect typpo --- R/pivot_wider_pknca_results.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/pivot_wider_pknca_results.R b/R/pivot_wider_pknca_results.R index 26dfb5f86..4c2469940 100644 --- a/R/pivot_wider_pknca_results.R +++ b/R/pivot_wider_pknca_results.R @@ -181,7 +181,7 @@ pivot_wider_pknca_results <- function(myres, flag_rules = NULL, extra_vars_to_ke out <- out %>% dplyr::inner_join( dplyr::select(conc_data, dplyr::any_of(c(vars_to_join, group_vars))), - by = intersect(names(out), names(c(vars_to_join, group_vars))) + by = intersect(names(out), c(vars_to_join, group_vars)) ) %>% dplyr::distinct() } From 613ece827dbc8fa3df448f8588973124679992c9 Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Tue, 30 Dec 2025 10:55:28 +0100 Subject: [PATCH 76/97] fix: rep for all cases when needed --- R/get_session_code.R | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/R/get_session_code.R b/R/get_session_code.R index 827828e65..633fdfedd 100644 --- a/R/get_session_code.R +++ b/R/get_session_code.R @@ -138,9 +138,7 @@ clean_deparse.character <- function(obj, indent = 0, max_per_line = 10, min_to_r obj <- sprintf('"%s"', obj) if (n == 1) { return(obj) - } else if (n <= max_per_line) { - return(paste0("c(", paste0(obj, collapse = ", "), ")")) - } else if (n > max_per_line) { + } else { rle_obj <- rle(obj) lines_obj <- c() for (i in seq_along(rle_obj$values)) { @@ -158,11 +156,13 @@ clean_deparse.character <- function(obj, indent = 0, max_per_line = 10, min_to_r ind <- paste(rep(" ", indent), collapse = "") lines <- split(lines_obj, ceiling(seq_along(lines_obj) / max_per_line)) line_strs <- vapply(lines, function(x) paste(x, collapse = ", "), "") - paste0( - "c(\n", - paste0(ind, " ", line_strs, collapse = ",\n"), - "\n", ind, ")" - ) + + if (is.list(lines) && length(lines) > 1) { + out <- paste0(ind, " ", line_strs, collapse = ",\n") + paste0("c(\n", out, "\n", ind, ")") + } else { + paste0("c(", paste0(line_strs, collapse = ",\n"), ")") + } } #' @noRd @@ -171,9 +171,7 @@ clean_deparse.numeric <- function(obj, indent = 0, max_per_line = 10, min_to_rep n <- length(obj) if (n == 1) { paste0(obj) - } else if (n <= max_per_line) { - paste0("c(", paste0(obj, collapse = ", "), ")") - } else if (n > max_per_line) { + } else { rle_obj <- rle(obj) lines_obj <- c() for (i in seq_along(rle_obj$lengths)) { @@ -190,11 +188,13 @@ clean_deparse.numeric <- function(obj, indent = 0, max_per_line = 10, min_to_rep ind <- paste(rep(" ", indent), collapse = "") lines <- split(lines_obj, ceiling(seq_along(lines_obj) / max_per_line)) line_strs <- vapply(lines, function(x) paste(x, collapse = ", "), "") - paste0( - "c(\n", - paste0(ind, " ", line_strs, collapse = ",\n"), - "\n", ind, ")" - ) + + if (is.list(lines) && length(lines) > 1) { + out <- paste0(ind, " ", line_strs, collapse = ",\n") + paste0("c(\n", out, "\n", ind, ")") + } else { + paste0("c(", paste0(line_strs, collapse = ",\n"), ")") + } } } From a845355256dddb4f90fdb50111c41581221b0e18 Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Tue, 30 Dec 2025 10:55:44 +0100 Subject: [PATCH 77/97] test: adapt test for new case expected --- tests/testthat/test-get_session_code.R | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/tests/testthat/test-get_session_code.R b/tests/testthat/test-get_session_code.R index d51070f6e..8355084bd 100644 --- a/tests/testthat/test-get_session_code.R +++ b/tests/testthat/test-get_session_code.R @@ -148,19 +148,21 @@ describe("clean_deparse()", { # Test min_to_rep greater than any repetition vec <- c(1, rep(2, 2), rep(3, 3), rep(4, 4)) + out_no_rep <- clean_deparse(vec, max_per_line = 10, min_to_rep = Inf) + out_1_to_rep <- clean_deparse(vec, max_per_line = 10, min_to_rep = 1) out_2_to_rep <- clean_deparse(vec, max_per_line = 10, min_to_rep = 2) out_3_to_rep <- clean_deparse(vec, max_per_line = 10, min_to_rep = 3) - exp_out_2_to_rep <- paste0( - "c(\n", - " 1, rep(2, 2), rep(3, 3), rep(4, 4)\n", - ")" - ) - exp_out_3_to_rep <- paste0( - "c(\n", - " 1, 2, 2, rep(3, 3), rep(4, 4)\n", - ")" - ) + out_4_to_rep <- clean_deparse(vec, max_per_line = 10, min_to_rep = 4) + + exp_out_no_rep <- "c(1, 2, 2, 3, 3, 3, 4, 4, 4, 4)" + exp_out_1_to_rep <- "c(rep(1, 1), rep(2, 2), rep(3, 3), rep(4, 4))" + exp_out_2_to_rep <- "c(1, rep(2, 2), rep(3, 3), rep(4, 4))" + exp_out_3_to_rep <- "c(1, 2, 2, rep(3, 3), rep(4, 4))" + exp_out_4_to_rep <- "c(1, 2, 2, 3, 3, 3, rep(4, 4))" + + expect_equal(out_no_rep, exp_out_no_rep) expect_equal(out_2_to_rep, exp_out_2_to_rep) expect_equal(out_3_to_rep, exp_out_3_to_rep) + expect_equal(out_4_to_rep, exp_out_4_to_rep) }) }) From 70fd838c1cf0981bde915301a57cdc7608d44227 Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Tue, 30 Dec 2025 14:07:37 +0100 Subject: [PATCH 78/97] refactor: simplify code with common fun --- R/get_session_code.R | 84 +++++++++----------------- tests/testthat/test-get_session_code.R | 1 + 2 files changed, 30 insertions(+), 55 deletions(-) diff --git a/R/get_session_code.R b/R/get_session_code.R index 633fdfedd..53e89a09c 100644 --- a/R/get_session_code.R +++ b/R/get_session_code.R @@ -109,7 +109,7 @@ clean_deparse.data.frame <- function(obj, indent = 0, max_per_line = 10, min_to_ } #' @noRd -clean_deparse.list <- function(obj, indent = 0, max_per_line = 10) { +clean_deparse.list <- function(obj, indent = 0, max_per_line = 10, min_to_rep = 3) { ind <- paste(rep(" ", indent), collapse = "") n <- length(obj) nms <- names(obj) @@ -134,8 +134,34 @@ clean_deparse.list <- function(obj, indent = 0, max_per_line = 10) { #' @noRd clean_deparse.character <- function(obj, indent = 0, max_per_line = 10, min_to_rep = 3) { - n <- length(obj) obj <- sprintf('"%s"', obj) + .deparse_vector(obj, indent, max_per_line, min_to_rep) +} + +#' @noRd + +clean_deparse.numeric <- function(obj, indent = 0, max_per_line = 10, min_to_rep = 3) { + obj <- sprintf("%s", obj) + .deparse_vector(obj, indent, max_per_line, min_to_rep) +} + +#' @noRd +clean_deparse.integer <- function(obj, indent = 0, max_per_line = 10, min_to_rep = 3) { + clean_deparse.numeric(obj, indent = indent, max_per_line = max_per_line, min_to_rep = min_to_rep) +} + +#' @noRd +clean_deparse.logical <- function(obj, indent = 0, max_per_line = 10, min_to_rep = 3) { + obj <- as.character(obj) + .deparse_vector(obj, indent, max_per_line, min_to_rep) +} + +#' Internal helper to deparse atomic vectors +#' using repetition simplification (rep) and line splitting +#' +#' @noRd +.deparse_vector <- function(obj, indent = 0, max_per_line = 10, min_to_rep = 3) { + n <- length(obj) if (n == 1) { return(obj) } else { @@ -144,19 +170,17 @@ clean_deparse.character <- function(obj, indent = 0, max_per_line = 10, min_to_r for (i in seq_along(rle_obj$values)) { val <- rle_obj$values[i] len <- rle_obj$lengths[i] - if (len >= min_to_rep) { rep_obj <- paste0("rep(", val, ", ", len, ")") lines_obj <- c(lines_obj, rep_obj) } else { - lines_obj <- c(lines_obj, (rep(val, len))) + lines_obj <- c(lines_obj, rep(val, len)) } } } ind <- paste(rep(" ", indent), collapse = "") lines <- split(lines_obj, ceiling(seq_along(lines_obj) / max_per_line)) line_strs <- vapply(lines, function(x) paste(x, collapse = ", "), "") - if (is.list(lines) && length(lines) > 1) { out <- paste0(ind, " ", line_strs, collapse = ",\n") paste0("c(\n", out, "\n", ind, ")") @@ -164,53 +188,3 @@ clean_deparse.character <- function(obj, indent = 0, max_per_line = 10, min_to_r paste0("c(", paste0(line_strs, collapse = ",\n"), ")") } } - -#' @noRd - -clean_deparse.numeric <- function(obj, indent = 0, max_per_line = 10, min_to_rep = 3) { - n <- length(obj) - if (n == 1) { - paste0(obj) - } else { - rle_obj <- rle(obj) - lines_obj <- c() - for (i in seq_along(rle_obj$lengths)) { - val <- rle_obj$values[i] - len <- rle_obj$lengths[i] - if (len >= min_to_rep) { - lines_obj <- c(lines_obj, sprintf("rep(%s, %d)", val, len)) - } else if (len == 1) { - lines_obj <- c(lines_obj, as.character(val)) - } else { - lines_obj <- c(lines_obj, as.character(rep(val, len))) - } - } - ind <- paste(rep(" ", indent), collapse = "") - lines <- split(lines_obj, ceiling(seq_along(lines_obj) / max_per_line)) - line_strs <- vapply(lines, function(x) paste(x, collapse = ", "), "") - - if (is.list(lines) && length(lines) > 1) { - out <- paste0(ind, " ", line_strs, collapse = ",\n") - paste0("c(\n", out, "\n", ind, ")") - } else { - paste0("c(", paste0(line_strs, collapse = ",\n"), ")") - } - } -} - -#' @noRd -clean_deparse.integer <- function(obj, indent = 0, max_per_line = 10, min_to_rep = 3) { - clean_deparse.numeric(obj, indent = indent, max_per_line = max_per_line, min_to_rep = min_to_rep) -} - -#' @noRd -clean_deparse.logical <- function(obj, indent = 0, max_per_line = 10) { - n <- length(obj) - if (n == 0) { - "logical()" - } else if (n == 1) { - paste0(obj) - } else { - paste0("c(", paste(ifelse(obj, "TRUE", "FALSE"), collapse = ", "), ")") - } -} diff --git a/tests/testthat/test-get_session_code.R b/tests/testthat/test-get_session_code.R index 8355084bd..078509068 100644 --- a/tests/testthat/test-get_session_code.R +++ b/tests/testthat/test-get_session_code.R @@ -15,6 +15,7 @@ describe("clean_deparse()", { it("formats logical values correctly", { expect_equal(clean_deparse(TRUE), "TRUE") expect_equal(clean_deparse(c(TRUE, FALSE)), "c(TRUE, FALSE)") + expect_equal(clean_deparse(logical()), "logical()") }) it("formats named lists correctly", { From 05f2c98876783bf76348d270356a26b9d578e3ed Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Tue, 30 Dec 2025 14:31:16 +0100 Subject: [PATCH 79/97] fix little typpos --- R/get_session_code.R | 2 +- inst/shiny/modules/tab_nca/nca_results.R | 4 ++-- tests/testthat/test-get_session_code.R | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/R/get_session_code.R b/R/get_session_code.R index 53e89a09c..bc805db93 100644 --- a/R/get_session_code.R +++ b/R/get_session_code.R @@ -85,7 +85,7 @@ clean_deparse <- function(obj, indent = 0, max_per_line = 10, min_to_rep = 3) { } #' @noRd -clean_deparse.default <- function(obj, indent = 0, max_per_line = 10) { +clean_deparse.default <- function(obj, indent = 0, max_per_line = 10, min_to_rep = 3) { paste(deparse(obj, width.cutoff = 500), collapse = "") } diff --git a/inst/shiny/modules/tab_nca/nca_results.R b/inst/shiny/modules/tab_nca/nca_results.R index c6d44964e..3b149d9db 100644 --- a/inst/shiny/modules/tab_nca/nca_results.R +++ b/inst/shiny/modules/tab_nca/nca_results.R @@ -143,7 +143,7 @@ nca_results_server <- function(id, pknca_data, res_nca, settings, ratio_table, g setts_tmpdir <- file.path(output_tmpdir, "settings") dir.create(setts_tmpdir, recursive = TRUE) settings_list <- session$userData$settings() - setings_to_save <- list( + settings_to_save <- list( settings = settings_list, slope_rules = list( manual_slopes = session$userData$slope_rules$manual_slopes(), @@ -152,7 +152,7 @@ nca_results_server <- function(id, pknca_data, res_nca, settings, ratio_table, g ) ) - saveRDS(setings_to_save, paste0(setts_tmpdir, "/settings.rds")) + saveRDS(settings_to_save, paste0(setts_tmpdir, "/settings.rds")) # Save input dataset used data_tmpdir <- file.path(output_tmpdir, "data") diff --git a/tests/testthat/test-get_session_code.R b/tests/testthat/test-get_session_code.R index 078509068..c222c6508 100644 --- a/tests/testthat/test-get_session_code.R +++ b/tests/testthat/test-get_session_code.R @@ -137,7 +137,7 @@ describe("clean_deparse()", { # Test numeric vec <- c(rep(5, 10), 6, 7, rep(8, 5)) - out_vec <- clean_deparse.numeric(vec, max_per_line = 3, min_to_rep = 2) + out_vec <- clean_deparse(vec, max_per_line = 3, min_to_rep = 2) exp_out_vec <- paste0( "c(\n", " rep(5, 10), 6, 7,\n", From 9f02bfcb573cd59dbe63a24cf9996f8b10dcd6fd Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Thu, 8 Jan 2026 11:29:19 +0100 Subject: [PATCH 80/97] make fun to filter PPTESTCD or parameters not requested --- R/PKNCA.R | 61 +++++++++++++++++++ inst/shiny/modules/tab_nca.R | 31 +--------- .../modules/tab_nca/parameter_datasets.R | 19 +----- inst/shiny/www/templates/script_template.R | 9 ++- 4 files changed, 72 insertions(+), 48 deletions(-) diff --git a/R/PKNCA.R b/R/PKNCA.R index 8ae726277..a2a8c02f3 100644 --- a/R/PKNCA.R +++ b/R/PKNCA.R @@ -641,3 +641,64 @@ PKNCA_hl_rules_exclusion <- function(res, rules) { # nolint } res } + + +#' Filter Out Parameters Not Requested in PKNCA Results +#' +#' This function removes parameters from the PKNCA results that were not requested by the user, +#' based on the intervals table. It identifies columns in the intervals table that are all NA +#' (i.e., not requested), translates them to PPTESTCD terms, and filters them out from the results. +#' +#' @param pknca_res A PKNCA results object containing at least $data$intervals and $result. +#' +#' @return The PKNCA results object with unrequested parameters removed from $result. +#' +#' @examples +#' # pknca_res <- ... # your PKNCA results object +#' # pknca_res <- filter_parameters_not_requested(pknca_res) +#' +#' @export +remove_parameters_not_requested <- function(pknca_res) { + params_not_requested <- pknca_res$data$intervals %>% + select(any_of(setdiff(names(PKNCA::get.interval.cols()), c("start", "end")))) %>% + # For all logical columns, mutate FALSE to NA + mutate(across(where(is.logical), ~ ifelse(.x, TRUE, NA))) %>% + # Only select columns that are only NA + select(where(~ all(is.na(.x)))) %>% + names() + pknca_res$result <- pknca_res$result %>% + filter(!PPTESTCD %in% translate_terms(params_not_requested, "PKNCA", "PPTESTCD")) + pknca_res +} + + +#' Filter Out Parameters Not Requested in PKNCA Results (Pivot Version) +#' +#' This function removes parameters from the PKNCA results that were not requested by the user, +#' using a pivoted approach that also handles bioavailability settings. +#' +#' @param pknca_res A PKNCA results object containing at least $data$intervals and $result. +#' @return The PKNCA results object with unrequested parameters removed from $result. +#' @export +filter_parameters_not_requested <- function(pknca_res) { + params <- c(setdiff(names(PKNCA::get.interval.cols()), c("start", "end"))) + # Reshape intervals, filter + params_not_requested <- pknca_res$data$intervals %>% + pivot_longer( + cols = (any_of(params)), + names_to = "PPTESTCD", + values_to = "is_requested" + ) %>% + mutate(PPTESTCD = translate_terms(PPTESTCD, "PKNCA", "PPTESTCD")) %>% + group_by(across(c(-impute, -is_requested))) %>% + summarise( + is_requested = any(is_requested), + .groups = "drop" + ) %>% + filter(!is_requested) + + # Filter for requested params based on intervals + pknca_res$result <- pknca_res$result %>% + anti_join(params_not_requested, by = intersect(names(.), names(params_not_requested))) + pknca_res +} diff --git a/inst/shiny/modules/tab_nca.R b/inst/shiny/modules/tab_nca.R index 33733a728..e7e42b0fa 100644 --- a/inst/shiny/modules/tab_nca.R +++ b/inst/shiny/modules/tab_nca.R @@ -152,7 +152,9 @@ tab_nca_server <- function(id, pknca_data, extra_group_vars) { purrr::map(\(x) x$threshold) ) %>% # Add parameter ratio calculations - calculate_table_ratios_app(ratio_table = ratio_table()) + calculate_table_ratios_app(ratio_table = ratio_table()) %>% + # Keep only parameters requested by the user + remove_parameters_not_requested() }, warning = function(w) { if (!grepl(paste(irrelevant_regex_warnings, collapse = "|"), @@ -173,33 +175,6 @@ tab_nca_server <- function(id, pknca_data, extra_group_vars) { log_success("NCA results calculated.") - params_requested <- c(setdiff(names(PKNCA::get.interval.cols()), c("start", "end")), - settings()$bioavailability) - # Reshape intervals, filter - params_not_requested <- res$data$intervals %>% - # add bioavailability if requested - mutate(!!!rlang::set_names(TRUE, settings()$bioavailability)) %>% - # pivot for requested params - pivot_longer( - cols = (any_of(params_requested)), - names_to = "PPTESTCD", - values_to = "is_requested" - ) %>% - # Translate terms - mutate(PPTESTCD = translate_terms(PPTESTCD, "PKNCA", "PPTESTCD")) %>% - # Group by all identifying cols EXCEPT the impute column and the value column - group_by(across(c(-impute, -is_requested))) %>% - # If all are FALSE, any(is_requested) will be FALSE. - summarise( - is_requested = any(is_requested), - .groups = "drop" - ) %>% - filter(!is_requested) - - # Filter for requested params based on intervals - res$result <- res$result %>% - anti_join(params_not_requested, by = intersect(names(.), names(params_not_requested))) - res }, error = function(e) { log_error("Error calculating NCA results:\n{conditionMessage(e)}") diff --git a/inst/shiny/modules/tab_nca/parameter_datasets.R b/inst/shiny/modules/tab_nca/parameter_datasets.R index 41d64806e..903fefb3d 100644 --- a/inst/shiny/modules/tab_nca/parameter_datasets.R +++ b/inst/shiny/modules/tab_nca/parameter_datasets.R @@ -11,24 +11,7 @@ parameter_datasets_server <- function(id, res_nca) { moduleServer(id, function(input, output, session) { CDISC <- reactive({ req(res_nca()) - - # Only select from results the requested parameters by the user - ############################################################################ - # TODO (Gerardo): Once PKNCA non covered parameters start being covered, - # this can be done instead using filter_requested = TRUE - res_nca_req <- res_nca() - params_not_requested <- res_nca_req$data$intervals %>% - select(any_of(setdiff(names(PKNCA::get.interval.cols()), c("start", "end")))) %>% - # For all logical columns, mutate FALSE to NA - mutate(across(where(is.logical), ~ ifelse(.x, TRUE, NA))) %>% - # Only select column that are only NA - select(where(~ all(is.na(.x)))) %>% - names() - res_nca_req$result <- res_nca_req$result %>% - filter(!PPTESTCD %in% translate_terms(params_not_requested, "PKNCA", "PPTESTCD")) - ############################################################################ - - export_cdisc(res_nca_req) + export_cdisc(res_nca()) }) reactable_server( diff --git a/inst/shiny/www/templates/script_template.R b/inst/shiny/www/templates/script_template.R index 2d7e2bd89..c4c7d691d 100644 --- a/inst/shiny/www/templates/script_template.R +++ b/inst/shiny/www/templates/script_template.R @@ -114,10 +114,15 @@ pknca_res <- pknca_obj %>% ) %>% # Derive secondary parameters (ratio parameters) - calculate_table_ratios_app(ratio_table) + calculate_table_ratios_app(ratio_table) %>% + + # Filter only parameters that have been requested + remove_parameters_not_requested() ## Obtain PP, ADPP, ADNCA & Pivoted results ######################### -cdisc_datasets <- export_cdisc(pknca_res) +cdisc_datasets <- pknca_res %>% + export_cdisc() + pivoted_results <- pivot_wider_pknca_results( myres = pknca_res, flag_rules = flag_rules, From 377ed977b1058e15bedd2780dc93a6e417e59e7a Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Thu, 8 Jan 2026 14:30:32 +0100 Subject: [PATCH 81/97] add general_exclusions (recent merge) to script_template.R --- inst/shiny/www/templates/script_template.R | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/inst/shiny/www/templates/script_template.R b/inst/shiny/www/templates/script_template.R index c4c7d691d..2cdd22108 100644 --- a/inst/shiny/www/templates/script_template.R +++ b/inst/shiny/www/templates/script_template.R @@ -58,7 +58,8 @@ pknca_obj <- preprocessed_adnca %>% selected_analytes = session$userData$settings$analyte, selected_profile = session$userData$settings$profile, selected_pcspec = session$userData$settings$pcspec, - should_impute_c0 = session$userData$settings$data_imputation$impute_c0 + should_impute_c0 = session$userData$settings$data_imputation$impute_c0, + exclusion_list = session$userData$settings$general_exclusions$exclusion_list ) %>% update_main_intervals( From f19fc1ade5d94a7084a8d3541046c31ef05d7df7 Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Thu, 8 Jan 2026 16:43:53 +0100 Subject: [PATCH 82/97] man: roxygenise remove_parameters_not_requested --- NAMESPACE | 1 + man/remove_parameters_not_requested.Rd | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+) create mode 100644 man/remove_parameters_not_requested.Rd diff --git a/NAMESPACE b/NAMESPACE index 6796f1cb2..e4e0900fe 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -62,6 +62,7 @@ export(pknca_calculate_f) export(process_data_individual) export(process_data_mean) export(read_pk) +export(remove_parameters_not_requested) export(run_app) export(simplify_unit) export(translate_terms) diff --git a/man/remove_parameters_not_requested.Rd b/man/remove_parameters_not_requested.Rd new file mode 100644 index 000000000..c97b3dac8 --- /dev/null +++ b/man/remove_parameters_not_requested.Rd @@ -0,0 +1,18 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/PKNCA.R +\name{remove_parameters_not_requested} +\alias{remove_parameters_not_requested} +\title{Filter Out Parameters Not Requested in PKNCA Results (Pivot Version)} +\usage{ +remove_parameters_not_requested(pknca_res) +} +\arguments{ +\item{pknca_res}{A PKNCA results object containing at least $data$intervals and $result.} +} +\value{ +The PKNCA results object with unrequested parameters removed from $result. +} +\description{ +This function removes parameters from the PKNCA results that were not requested by the user, +using a pivoted approach that also handles bioavailability settings. +} From d140074fb0050c7190e164f766241f5089c3cce2 Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Thu, 8 Jan 2026 16:46:54 +0100 Subject: [PATCH 83/97] rename: remove_parameters_not_requested > remove_pp_not_requested --- NAMESPACE | 2 +- R/PKNCA.R | 2 +- inst/shiny/modules/tab_nca.R | 2 +- inst/shiny/www/templates/script_template.R | 2 +- ...rameters_not_requested.Rd => remove_pp_not_requested.Rd} | 6 +++--- 5 files changed, 7 insertions(+), 7 deletions(-) rename man/{remove_parameters_not_requested.Rd => remove_pp_not_requested.Rd} (81%) diff --git a/NAMESPACE b/NAMESPACE index e4e0900fe..750ae6809 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -62,7 +62,7 @@ export(pknca_calculate_f) export(process_data_individual) export(process_data_mean) export(read_pk) -export(remove_parameters_not_requested) +export(remove_pp_not_requested) export(run_app) export(simplify_unit) export(translate_terms) diff --git a/R/PKNCA.R b/R/PKNCA.R index 6c7114763..5c805dab4 100644 --- a/R/PKNCA.R +++ b/R/PKNCA.R @@ -658,7 +658,7 @@ PKNCA_hl_rules_exclusion <- function(res, rules) { # nolint #' @param pknca_res A PKNCA results object containing at least $data$intervals and $result. #' @return The PKNCA results object with unrequested parameters removed from $result. #' @export -remove_parameters_not_requested <- function(pknca_res) { +remove_pp_not_requested <- function(pknca_res) { params <- c(setdiff(names(PKNCA::get.interval.cols()), c("start", "end"))) # Reshape intervals, filter params_not_requested <- pknca_res$data$intervals %>% diff --git a/inst/shiny/modules/tab_nca.R b/inst/shiny/modules/tab_nca.R index e7e42b0fa..9c4eeaac4 100644 --- a/inst/shiny/modules/tab_nca.R +++ b/inst/shiny/modules/tab_nca.R @@ -154,7 +154,7 @@ tab_nca_server <- function(id, pknca_data, extra_group_vars) { # Add parameter ratio calculations calculate_table_ratios_app(ratio_table = ratio_table()) %>% # Keep only parameters requested by the user - remove_parameters_not_requested() + remove_pp_not_requested() }, warning = function(w) { if (!grepl(paste(irrelevant_regex_warnings, collapse = "|"), diff --git a/inst/shiny/www/templates/script_template.R b/inst/shiny/www/templates/script_template.R index 2cdd22108..467e5c821 100644 --- a/inst/shiny/www/templates/script_template.R +++ b/inst/shiny/www/templates/script_template.R @@ -118,7 +118,7 @@ pknca_res <- pknca_obj %>% calculate_table_ratios_app(ratio_table) %>% # Filter only parameters that have been requested - remove_parameters_not_requested() + remove_pp_not_requested() ## Obtain PP, ADPP, ADNCA & Pivoted results ######################### cdisc_datasets <- pknca_res %>% diff --git a/man/remove_parameters_not_requested.Rd b/man/remove_pp_not_requested.Rd similarity index 81% rename from man/remove_parameters_not_requested.Rd rename to man/remove_pp_not_requested.Rd index c97b3dac8..146ca5687 100644 --- a/man/remove_parameters_not_requested.Rd +++ b/man/remove_pp_not_requested.Rd @@ -1,10 +1,10 @@ % Generated by roxygen2: do not edit by hand % Please edit documentation in R/PKNCA.R -\name{remove_parameters_not_requested} -\alias{remove_parameters_not_requested} +\name{remove_pp_not_requested} +\alias{remove_pp_not_requested} \title{Filter Out Parameters Not Requested in PKNCA Results (Pivot Version)} \usage{ -remove_parameters_not_requested(pknca_res) +remove_pp_not_requested(pknca_res) } \arguments{ \item{pknca_res}{A PKNCA results object containing at least $data$intervals and $result.} From 1aa17fe764133fd2ff6db0ba80624813fd567f0e Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Fri, 9 Jan 2026 08:45:13 +0100 Subject: [PATCH 84/97] spelling: unrequested --- R/PKNCA.R | 2 +- man/remove_pp_not_requested.Rd | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/R/PKNCA.R b/R/PKNCA.R index 5c805dab4..189aec244 100644 --- a/R/PKNCA.R +++ b/R/PKNCA.R @@ -656,7 +656,7 @@ PKNCA_hl_rules_exclusion <- function(res, rules) { # nolint #' using a pivoted approach that also handles bioavailability settings. #' #' @param pknca_res A PKNCA results object containing at least $data$intervals and $result. -#' @return The PKNCA results object with unrequested parameters removed from $result. +#' @return The PKNCA results object with non requested parameters removed from $result. #' @export remove_pp_not_requested <- function(pknca_res) { params <- c(setdiff(names(PKNCA::get.interval.cols()), c("start", "end"))) diff --git a/man/remove_pp_not_requested.Rd b/man/remove_pp_not_requested.Rd index 146ca5687..36d3c721a 100644 --- a/man/remove_pp_not_requested.Rd +++ b/man/remove_pp_not_requested.Rd @@ -10,7 +10,7 @@ remove_pp_not_requested(pknca_res) \item{pknca_res}{A PKNCA results object containing at least $data$intervals and $result.} } \value{ -The PKNCA results object with unrequested parameters removed from $result. +The PKNCA results object with non requested parameters removed from $result. } \description{ This function removes parameters from the PKNCA results that were not requested by the user, From b5ef41d152ea4fe5845b6b127274fabf6a63a65a Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Fri, 9 Jan 2026 10:56:10 +0100 Subject: [PATCH 85/97] test: todo excluding tmp weird case test --- tests/testthat/test-pivot_wider_pknca_results.R | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/tests/testthat/test-pivot_wider_pknca_results.R b/tests/testthat/test-pivot_wider_pknca_results.R index ecb60ec76..fbdcc5ff8 100644 --- a/tests/testthat/test-pivot_wider_pknca_results.R +++ b/tests/testthat/test-pivot_wider_pknca_results.R @@ -225,7 +225,13 @@ describe("pivot_wider_pknca_results", { # Missing results produce "MISSING" flag na_result <- piv_result %>% - filter(is.na(R2) | is.na(R2ADJ)) + filter(is.na(R2) | is.na(R2ADJ)) %>% + + # TODO (Gerardo): Exclude test on SUBJ 4 because + # is a weird case scenario that PKNCA does not detect + # It is a straight line, R2 = NA, but PKNCA does not flag exclude + filter(USUBJID != 4) + expect_equal(unique(na_result$flagged), "MISSING") # Invalid rules produce "FLAGGED" flag From 1b78da131bfe877920b0017b1b92388844eaa343 Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Fri, 9 Jan 2026 11:12:37 +0100 Subject: [PATCH 86/97] add global vars to zzz.R --- R/zzz.R | 2 ++ 1 file changed, 2 insertions(+) diff --git a/R/zzz.R b/R/zzz.R index 1013bc088..e32e1b3ee 100644 --- a/R/zzz.R +++ b/R/zzz.R @@ -128,6 +128,7 @@ "id_list", "id_plot", "id_variable_col", + "impute", "install.packages", "interval_name", "interval_name_col", @@ -139,6 +140,7 @@ "is_extravascular", "is_metabolite", "is_one_dose", + "is_requested", "legend_group", "log10_CI", "log10_Mean", From a332b90cd9da2c86bba0b468a8f3898a2e515892 Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Fri, 9 Jan 2026 17:05:48 +0100 Subject: [PATCH 87/97] add todo & rm redundant data.frame check --- R/get_session_code.R | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/R/get_session_code.R b/R/get_session_code.R index bc805db93..85d6b77e8 100644 --- a/R/get_session_code.R +++ b/R/get_session_code.R @@ -92,9 +92,6 @@ clean_deparse.default <- function(obj, indent = 0, max_per_line = 10, min_to_rep #' @noRd clean_deparse.data.frame <- function(obj, indent = 0, max_per_line = 10, min_to_rep = 3) { ind <- paste(rep(" ", indent), collapse = "") - if (nrow(obj) == 0) { - return("data.frame()") - } cols <- lapply(obj, function(col) { clean_deparse(col, indent + 1, max_per_line = max_per_line) @@ -188,3 +185,7 @@ clean_deparse.logical <- function(obj, indent = 0, max_per_line = 10, min_to_rep paste0("c(", paste0(line_strs, collapse = ",\n"), ")") } } + +# TODO (Gerardo): Create a linked function +# to obtain the code from a settings file +# (#826) From af3fd7ca6650b4576c770a35f4bf3e485d5f8417 Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Mon, 12 Jan 2026 09:30:27 +0100 Subject: [PATCH 88/97] rename calculate_table_ratios_app > calculate_table ratios --- NAMESPACE | 2 +- R/ratio_calculations.R | 31 +++++++++++----------- inst/shiny/modules/tab_nca.R | 2 +- inst/shiny/www/templates/script_template.R | 2 +- man/calculate_table_ratios_app.Rd | 6 ++--- 5 files changed, 21 insertions(+), 22 deletions(-) diff --git a/NAMESPACE b/NAMESPACE index 750ae6809..2da88ecd1 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -21,7 +21,7 @@ export(apply_mapping) export(calculate_f) export(calculate_ratios) export(calculate_summary_stats) -export(calculate_table_ratios_app) +export(calculate_table_ratios) export(check_slope_rule_overlap) export(convert_volume_units) export(create_metabfl) diff --git a/R/ratio_calculations.R b/R/ratio_calculations.R index 3e787f4d4..dce4da96c 100644 --- a/R/ratio_calculations.R +++ b/R/ratio_calculations.R @@ -286,7 +286,7 @@ calculate_ratios.PKNCAresults <- function( #' AdjustingFactor, TestGroups, RefGroups, PPTESTCD. #' @returns The updated PKNCAresult object with added rows in the `result` data.frame. #' @export -calculate_table_ratios_app <- function(res, ratio_table) { +calculate_table_ratios <- function(res, ratio_table) { # Make a list to save all results ratio_results <- vector("list", nrow(ratio_table)) @@ -397,21 +397,20 @@ calculate_ratio_app <- function( ) - all_ratios <- data.frame() - - for (ix in seq_along(match_cols)) { - ratio_calculations <- calculate_ratios( - data = res$result, - test_parameter = test_parameter, - ref_parameter = ref_parameter, - match_cols = match_cols[[ix]], - ref_groups = ref_groups, - test_groups = test_groups, - adjusting_factor = adjusting_factor, - custom_pptestcd = custom_pptestcd - ) - all_ratios <- bind_rows(all_ratios, ratio_calculations) - } + # Use lapply to calculate all ratios and bind at once + ratio_list <- lapply(seq_along(match_cols), function(ix) { + calculate_ratios( + data = res$result, + test_parameter = test_parameter, + ref_parameter = ref_parameter, + match_cols = match_cols[[ix]], + ref_groups = ref_groups, + test_groups = test_groups, + adjusting_factor = adjusting_factor, + custom_pptestcd = custom_pptestcd + ) + }) + all_ratios <- bind_rows(ratio_list) # Assuming there cannot be more than 1 reference + PPTESTCD combination for the same group... # If aggregate_subject = 'if-needed', then this will remove cases when subject is not needed diff --git a/inst/shiny/modules/tab_nca.R b/inst/shiny/modules/tab_nca.R index 9c4eeaac4..8855409ab 100644 --- a/inst/shiny/modules/tab_nca.R +++ b/inst/shiny/modules/tab_nca.R @@ -152,7 +152,7 @@ tab_nca_server <- function(id, pknca_data, extra_group_vars) { purrr::map(\(x) x$threshold) ) %>% # Add parameter ratio calculations - calculate_table_ratios_app(ratio_table = ratio_table()) %>% + calculate_table_ratios(ratio_table = ratio_table()) %>% # Keep only parameters requested by the user remove_pp_not_requested() }, diff --git a/inst/shiny/www/templates/script_template.R b/inst/shiny/www/templates/script_template.R index 467e5c821..62fef40ec 100644 --- a/inst/shiny/www/templates/script_template.R +++ b/inst/shiny/www/templates/script_template.R @@ -115,7 +115,7 @@ pknca_res <- pknca_obj %>% ) %>% # Derive secondary parameters (ratio parameters) - calculate_table_ratios_app(ratio_table) %>% + calculate_table_ratios(ratio_table) %>% # Filter only parameters that have been requested remove_pp_not_requested() diff --git a/man/calculate_table_ratios_app.Rd b/man/calculate_table_ratios_app.Rd index e0bfcb13b..cd2173407 100644 --- a/man/calculate_table_ratios_app.Rd +++ b/man/calculate_table_ratios_app.Rd @@ -1,10 +1,10 @@ % Generated by roxygen2: do not edit by hand % Please edit documentation in R/ratio_calculations.R -\name{calculate_table_ratios_app} -\alias{calculate_table_ratios_app} +\name{calculate_table_ratios} +\alias{calculate_table_ratios} \title{Apply Ratio Calculations to PKNCAresult Object} \usage{ -calculate_table_ratios_app(res, ratio_table) +calculate_table_ratios(res, ratio_table) } \arguments{ \item{res}{A PKNCAresult object.} From 7144766dd1626185ce95af9a9a9cd65eda512c26 Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Mon, 12 Jan 2026 09:38:52 +0100 Subject: [PATCH 89/97] use apply instead of a for loop in ratio_calculations.R --- R/ratio_calculations.R | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/R/ratio_calculations.R b/R/ratio_calculations.R index dce4da96c..d0b57683c 100644 --- a/R/ratio_calculations.R +++ b/R/ratio_calculations.R @@ -287,12 +287,9 @@ calculate_ratios.PKNCAresults <- function( #' @returns The updated PKNCAresult object with added rows in the `result` data.frame. #' @export calculate_table_ratios <- function(res, ratio_table) { - # Make a list to save all results - ratio_results <- vector("list", nrow(ratio_table)) - - # Loop through each row of the ratio_table - for (i in seq_len(nrow(ratio_table))) { - ratio_results[[i]] <- calculate_ratio_app( + # Use lapply to process each row of ratio_table + ratio_results <- lapply(seq_len(nrow(ratio_table)), function(i) { + result <- calculate_ratio_app( res = res, test_parameter = ratio_table$TestParameter[i], ref_parameter = ratio_table$RefParameter[i], @@ -302,8 +299,7 @@ calculate_table_ratios <- function(res, ratio_table) { adjusting_factor = as.numeric(ratio_table$AdjustingFactor[i]), custom_pptestcd = if (ratio_table$PPTESTCD[i] == "") NULL else ratio_table$PPTESTCD[i] ) - - if (nrow(ratio_results[[i]]) == 0) { + if (nrow(result) == 0) { warning( "Ratio ", ratio_table$PPTESTCD[i], " not computed.", "No comparable groups found between RefGroups", @@ -312,7 +308,8 @@ calculate_table_ratios <- function(res, ratio_table) { " (", ratio_table$TestGroups[i], ")" ) } - } + result + }) if (!"PPANMETH" %in% names(res$result)) { res$result$PPANMETH <- "" } @@ -396,8 +393,6 @@ calculate_ratio_app <- function( ) ) - - # Use lapply to calculate all ratios and bind at once ratio_list <- lapply(seq_along(match_cols), function(ix) { calculate_ratios( data = res$result, From ef71957b242f10356dceadea697008b14b8bb0e1 Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Mon, 12 Jan 2026 09:43:54 +0100 Subject: [PATCH 90/97] man: roxygenise & refactor lintr --- R/ratio_calculations.R | 26 +++++++++---------- ...atios_app.Rd => calculate_table_ratios.Rd} | 0 2 files changed, 13 insertions(+), 13 deletions(-) rename man/{calculate_table_ratios_app.Rd => calculate_table_ratios.Rd} (100%) diff --git a/R/ratio_calculations.R b/R/ratio_calculations.R index d0b57683c..4694662f9 100644 --- a/R/ratio_calculations.R +++ b/R/ratio_calculations.R @@ -393,19 +393,19 @@ calculate_ratio_app <- function( ) ) - ratio_list <- lapply(seq_along(match_cols), function(ix) { - calculate_ratios( - data = res$result, - test_parameter = test_parameter, - ref_parameter = ref_parameter, - match_cols = match_cols[[ix]], - ref_groups = ref_groups, - test_groups = test_groups, - adjusting_factor = adjusting_factor, - custom_pptestcd = custom_pptestcd - ) - }) - all_ratios <- bind_rows(ratio_list) + ratio_list <- lapply(seq_along(match_cols), function(ix) { + calculate_ratios( + data = res$result, + test_parameter = test_parameter, + ref_parameter = ref_parameter, + match_cols = match_cols[[ix]], + ref_groups = ref_groups, + test_groups = test_groups, + adjusting_factor = adjusting_factor, + custom_pptestcd = custom_pptestcd + ) + }) + all_ratios <- bind_rows(ratio_list) # Assuming there cannot be more than 1 reference + PPTESTCD combination for the same group... # If aggregate_subject = 'if-needed', then this will remove cases when subject is not needed diff --git a/man/calculate_table_ratios_app.Rd b/man/calculate_table_ratios.Rd similarity index 100% rename from man/calculate_table_ratios_app.Rd rename to man/calculate_table_ratios.Rd From 892d4d87e9865f6b2e48d8377fbea2c3b356695e Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Fri, 16 Jan 2026 09:31:51 +0100 Subject: [PATCH 91/97] readme: add R script msg --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 9601b03b9..a8df8986a 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,7 @@ This application enables users to upload their datasets and perform Non-Compartm - **Visualize data and results** with interactive boxplots, summary statistic tables and scatter plots - **Produce PP and ADPP** dataset formats of the resulting parameters - **Save your analysis settings** and reupload them later to keep on analysing! +- **Reproduce your NCA outside of the app**, using the R script created ## Installation From b80a38983a156a22993d98b173da9d4a1ea1cb45 Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Fri, 16 Jan 2026 09:33:27 +0100 Subject: [PATCH 92/97] add PR task temporarely until tests are added --- .github/PULL_REQUEST_TEMPLATE.md | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index b64a3a610..ad5f31524 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -21,6 +21,7 @@ How to test features not covered by unit tests. - [ ] New logic is documented - [ ] App or package changes are reflected in NEWS - [ ] Package version is incremented +- [ ] R script works with the new implementation (if applicable) ## Notes to reviewer From 20c47d41240b18b8707090eab32d53bcefe301a3 Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Mon, 19 Jan 2026 12:00:18 +0100 Subject: [PATCH 93/97] rephrase news & bump pkg version --- DESCRIPTION | 2 +- NEWS.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index cc75a6fc3..d994914d9 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: aNCA Title: (Pre-)Clinical NCA in a Dynamic Shiny App -Version: 0.1.0.9035 +Version: 0.1.0.9039 Authors@R: c( person("Ercan", "Suekuer", email = "ercan.suekuer@roche.com", role = "aut", comment = c(ORCID = "0009-0001-1626-1526")), diff --git a/NEWS.md b/NEWS.md index 260723540..8f6e8f914 100644 --- a/NEWS.md +++ b/NEWS.md @@ -6,7 +6,7 @@ * Enhancements to the slides outputs including grouping by PKNCA groups, dose profile, and additional grouping variables (#791) * Option to include and apply NCA flag rules with reasons (NCAwXRS) as defined by ADNCA standards. Any record populated within these columns will be excluded for the NCA (#752) -* R script exported in ZIP folder to re-run and replicate dataset outputs (#789) +* R script exported in ZIP folder to re-run and replicate App outputs (#789) * Individual and Mean plots tabs now created using the same function, so the layout and plot themes are consistent across both plots (#712) * New flagging rule for lambda-z calculations based on r-squared, R2 (#834) * New Parameter Selection section in NCA tab allowing to select parameters by study type (#795) From 9abb49c322d46529a22263138acc05ff5f1effaa Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Fri, 23 Jan 2026 09:10:51 +0100 Subject: [PATCH 94/97] fix: issue with settings change of list structure in zip download --- inst/shiny/modules/tab_nca/zip.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/inst/shiny/modules/tab_nca/zip.R b/inst/shiny/modules/tab_nca/zip.R index 23389fa38..4bddb572c 100644 --- a/inst/shiny/modules/tab_nca/zip.R +++ b/inst/shiny/modules/tab_nca/zip.R @@ -179,7 +179,7 @@ zip_server <- function(id, res_nca, settings, grouping_vars) { dir.create(setts_tmpdir, recursive = TRUE) settings_list <- session$userData$settings() settings_to_save <- list( - settings = settings_list$settings(), + settings = settings_list, slope_rules = list( manual_slopes = session$userData$slope_rules$manual_slopes(), profiles_per_subject = session$userData$slope_rules$profiles_per_subject(), From f8a78efd98ac816a3109239ee5d03d8cf1dde51d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gerardo=20J=2E=20Rodr=C3=ADguez?= <68994823+Gero1999@users.noreply.github.com> Date: Fri, 23 Jan 2026 09:24:35 +0100 Subject: [PATCH 95/97] Apply easy/simple suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Mateusz Kołomański <63905560+m-kolomanski@users.noreply.github.com> --- R/pivot_wider_pknca_results.R | 2 +- R/ratio_calculations.R | 7 +++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/R/pivot_wider_pknca_results.R b/R/pivot_wider_pknca_results.R index 47b2a1242..ab6bada33 100644 --- a/R/pivot_wider_pknca_results.R +++ b/R/pivot_wider_pknca_results.R @@ -240,7 +240,7 @@ add_label_attribute <- function(df, myres) { # Add flaging columns in the pivoted results applied_flags <- purrr::keep(flag_rules, function(x) x$is.checked) flag_params <- names(flag_rules) - flag_thr <- sapply(flag_rules, FUN = function(x) x$threshold) + flag_thr <- sapply(flag_rules, FUN = \(x) x$threshold) flag_rule_msgs <- paste0(flag_params, c(" < ", "<", " > ", " > ", " < "), flag_thr) flag_cols <- names(final_results)[formatters::var_labels(final_results) %in% translate_terms(flag_params, "PPTESTCD", "PPTEST")] diff --git a/R/ratio_calculations.R b/R/ratio_calculations.R index 569ea2f6f..73dafbe05 100644 --- a/R/ratio_calculations.R +++ b/R/ratio_calculations.R @@ -345,10 +345,9 @@ calculate_ratio_app <- function( match_cols <- setdiff(unique(c(dplyr::group_vars(res), "start", "end")), reference_colname) ########### This is very App specific ############### - if ("ATPTREF" %in% reference_colname) { - match_cols <- setdiff(match_cols, c("start", "end")) - } - if ("ROUTE" %in% reference_colname && aggregate_subject == "no") { + atptref_exists <- "ATPTREF" %in% reference_colname + route_and_aggregate <- "ROUTE" %in% reference_colname && aggregate_subject == "no" + if (atptref_exists || route_and_aggregate) { match_cols <- setdiff(match_cols, c("start", "end")) } ##################################################### From bb147e3575b4fa38df12b3f00598728263532b85 Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Fri, 23 Jan 2026 09:51:26 +0100 Subject: [PATCH 96/97] add m-kolomanski refactoring suggestions (ratio_calculations.R, test-get_session_code.R) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Mateusz Kołomański <63905560+m-kolomanski@users.noreply.github.com> --- R/ratio_calculations.R | 33 ++++++++++++++++---------- tests/testthat/test-get_session_code.R | 10 ++++---- 2 files changed, 25 insertions(+), 18 deletions(-) diff --git a/R/ratio_calculations.R b/R/ratio_calculations.R index 73dafbe05..8d6a8f576 100644 --- a/R/ratio_calculations.R +++ b/R/ratio_calculations.R @@ -352,19 +352,26 @@ calculate_ratio_app <- function( } ##################################################### - if (aggregate_subject == "yes") { - match_cols <- list(setdiff(match_cols, "USUBJID")) - } else if (aggregate_subject == "no") { - if (!"USUBJID" %in% match_cols) { - stop("USUBJID must be included in match_cols when aggregate_subject is 'never'.") - } - match_cols <- list(match_cols) - } else if (aggregate_subject == "if-needed") { - if ("USUBJID" %in% match_cols) { - # Perform both individual & aggregated calculations, then eliminate duplicates - match_cols <- list(match_cols, setdiff(match_cols, "USUBJID")) + match_cols <- switch( + aggregate_subject, + "yes" = { + list(setdiff(match_cols, "USUBJID")) + }, + "no" = { + if (!"USUBJID" %in% match_cols) { + stop("USUBJID must be included in match_cols when aggregate_subject is 'never'.") + } + list(match_cols) + }, + "if-needed" = { + match_cols <- list(match_cols) + if ("USUBJID" %in% match_cols) { + # Perform both individual & aggregated calculations, then eliminate duplicates + match_cols <- c(match_cols, list(setdiff(match_cols, "USUBJID"))) + } + match_cols } - } + ) if (test_group == "(all other levels)") { test_groups <- NULL @@ -414,4 +421,4 @@ calculate_ratio_app <- function( all_of(c("PPTESTCD", group_vars(res$data), "end")) ), .keep_all = TRUE) -} + } diff --git a/tests/testthat/test-get_session_code.R b/tests/testthat/test-get_session_code.R index c222c6508..773decb54 100644 --- a/tests/testthat/test-get_session_code.R +++ b/tests/testthat/test-get_session_code.R @@ -67,10 +67,10 @@ describe("clean_deparse()", { long_vec <- as.character(1:25) df <- data.frame(x = 1:25, y = long_vec, stringsAsFactors = FALSE) out <- clean_deparse(df) - expect_true(grepl("c\\(\n", out)) # Should use newlines for long columns - expect_true(grepl(" 1, 2, 3", out)) # Indentation present - expect_true(grepl("x = c\\(", out)) # Column x uses c( ... ) - expect_true(grepl("y = c\\(", out)) # Column y uses c( ... ) + expect_match(out, "c\\(\n") # Should use newlines for long columns + expect_match(out, " 1, 2, 3") # Indentation present + expect_match(out, "x = c\\(") # Column x uses c( ... ) + expect_match(out, "y = c\\(") # Column y uses c( ... ) }) it("respects max_per_line for character vectors", { @@ -107,7 +107,7 @@ describe("clean_deparse()", { it("respects max_per_line for data.frames", { df <- data.frame(x = 1:4, y = letters[1:4], stringsAsFactors = FALSE) - out <- clean_deparse.data.frame(df, max_per_line = 2) + out <- clean_deparse(df, max_per_line = 2) exp_out <- paste0( "data.frame(\n", " x = c(\n", From b6725cda50550c121fea164186a5a14e81203a88 Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Fri, 23 Jan 2026 09:52:56 +0100 Subject: [PATCH 97/97] refactor: lintr --- R/ratio_calculations.R | 85 ++++++++++++++++++++---------------------- 1 file changed, 41 insertions(+), 44 deletions(-) diff --git a/R/ratio_calculations.R b/R/ratio_calculations.R index 8d6a8f576..8b09884be 100644 --- a/R/ratio_calculations.R +++ b/R/ratio_calculations.R @@ -85,30 +85,28 @@ multiple_matrix_ratios <- function(data, matrix_col, conc_col, units_col, #' @export #' @export calculate_ratios <- function( - data, - test_parameter, - ref_parameter = test_parameter, - match_cols, - ref_groups, - test_groups = NULL, - adjusting_factor = 1, - custom_pptestcd = NULL -) { + data, + test_parameter, + ref_parameter = test_parameter, + match_cols, + ref_groups, + test_groups = NULL, + adjusting_factor = 1, + custom_pptestcd = NULL) { UseMethod("calculate_ratios", data) } #' @export #' @export calculate_ratios.data.frame <- function( - data, - test_parameter, - ref_parameter = test_parameter, - match_cols, - ref_groups, - test_groups = NULL, - adjusting_factor = 1, - custom_pptestcd = NULL -) { + data, + test_parameter, + ref_parameter = test_parameter, + match_cols, + ref_groups, + test_groups = NULL, + adjusting_factor = 1, + custom_pptestcd = NULL) { if (!any(data$PPTESTCD == test_parameter)) { warning( paste0( @@ -237,15 +235,14 @@ calculate_ratios.data.frame <- function( #' @export calculate_ratios.PKNCAresults <- function( - data, - test_parameter, - ref_parameter = test_parameter, - match_cols, - ref_groups, - test_groups = NULL, - adjusting_factor = 1, - custom_pptestcd = NULL -) { + data, + test_parameter, + ref_parameter = test_parameter, + match_cols, + ref_groups, + test_groups = NULL, + adjusting_factor = 1, + custom_pptestcd = NULL) { # Check if match_cols and ref_groups are valid group columns # Make checks on the input formats cols_used_for_ratios <- c(match_cols, names(ref_groups), names(test_groups)) @@ -332,15 +329,14 @@ calculate_table_ratios <- function(res, ratio_table) { #' @param custom_pptestcd Optional character. If provided, will be used as the PPTESTCD value. #' @returns A data.frame with the calculated ratios for the specified settings. calculate_ratio_app <- function( - res, - test_parameter, - ref_parameter = test_parameter, - test_group = "(all other levels)", - ref_group = "PARAM: Analyte01", - aggregate_subject = "no", - adjusting_factor = 1, - custom_pptestcd = NULL -) { + res, + test_parameter, + ref_parameter = test_parameter, + test_group = "(all other levels)", + ref_group = "PARAM: Analyte01", + aggregate_subject = "no", + adjusting_factor = 1, + custom_pptestcd = NULL) { reference_colname <- gsub("(.*): (.*)", "\\1", ref_group) match_cols <- setdiff(unique(c(dplyr::group_vars(res), "start", "end")), reference_colname) @@ -352,12 +348,11 @@ calculate_ratio_app <- function( } ##################################################### - match_cols <- switch( - aggregate_subject, + match_cols <- switch(aggregate_subject, "yes" = { list(setdiff(match_cols, "USUBJID")) }, - "no" = { + "no" = { if (!"USUBJID" %in% match_cols) { stop("USUBJID must be included in match_cols when aggregate_subject is 'never'.") } @@ -417,8 +412,10 @@ calculate_ratio_app <- function( # If aggregate_subject = 'if-needed', then this will remove cases when subject is not needed all_ratios %>% # Make sure there are no duplicate rows for: parameter, contrast_var, and match_cols - distinct(across( - all_of(c("PPTESTCD", group_vars(res$data), "end")) - ), - .keep_all = TRUE) - } + distinct( + across( + all_of(c("PPTESTCD", group_vars(res$data), "end")) + ), + .keep_all = TRUE + ) +}