From 4f7a2e83eb05978a31f5eff17baef0993e36f654 Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Mon, 25 Aug 2025 16:27:08 +0200 Subject: [PATCH 001/113] draft: start developing a new lambda_slope_plot fun --- R/lambda_slope_plot.R | 150 ++++++++++++++++++ .../modules/tab_nca/setup/slope_selector.R | 2 +- inst/shiny/o_nca | Bin 0 -> 366470 bytes 3 files changed, 151 insertions(+), 1 deletion(-) create mode 100644 inst/shiny/o_nca diff --git a/R/lambda_slope_plot.R b/R/lambda_slope_plot.R index 4f2e08f04..45273966f 100644 --- a/R/lambda_slope_plot.R +++ b/R/lambda_slope_plot.R @@ -255,3 +255,153 @@ lambda_slope_plot <- function( showlegend = FALSE ) } + + +get_halflife_plot <- function(o_nca, add_annotations = TRUE) { + # Ensure result columns are present + if (!"PPSTRES" %in% names(o_nca$result)) { + o_nca$result$PPSTRES <- o_nca$result$PPORRES + if ("PPORRESU" %in% names(o_nca$result)) { + o_nca$result$PPSTRESU <- o_nca$result$PPORRESU + } + } + + # Get grouping structure for lambda.z + groups <- getGroups(o_nca %>% dplyr::filter(PPTESTCD == "lambda.z")) %>% unique() + groups <- o_nca$result %>% + select(any_of(c(group_vars(o_nca), "start", "end", "PPTESTCD"))) %>% + dplyr::filter(PPTESTCD == "lambda.z") %>% + select(-PPTESTCD) %>% + unique() + + plots <- vector("list", nrow(groups)) + + for (i in seq_len(nrow(groups))) { + group <- groups[i, ] + group_vars <- setdiff(names(group), c("start", "end")) + # Subset data for this group + df <- merge(o_nca$data$conc$data, group[, group_vars, drop = FALSE]) + + # Column names + time_col <- o_nca$data$conc$columns$time + conc_col <- o_nca$data$conc$columns$concentration + timeu_col <- o_nca$data$conc$columns$timeu + concu_col <- o_nca$data$conc$columns$concu + exclude_hl_col <- o_nca$data$conc$columns$exclude_half.life + if (is.null(exclude_hl_col)) { + o_nca$data$conc$data[["exclude_half.life"]] <- FALSE + exclude_hl_col <- "exclude_half.life" + } + + # Filter and order by time + df <- df[df[[time_col]] >= group$start & df[[time_col]] <= group$end, ] + df[["ROWID"]] <- seq_len(nrow(df)) + df <- df[order(df[[time_col]]), ] + df$IX <- seq_len(nrow(df)) + + # Prepare NCA object for this group + group_nca <- o_nca + group_nca$data$conc$data <- df + group_nca$result <- merge(group_nca$result, group[, group_vars, drop = FALSE]) + is_lz_used <- get_halflife_points(group_nca) + df_fit <- df[is_lz_used, ] + + # Fit log-linear model to green points + fit <- lm(as.formula(paste0("log10(", conc_col,") ~ ", time_col)), df_fit) + + # Extract NCA results for annotation + get_res <- function(testcd) group_nca$result$PPORRES[group_nca$result$PPTESTCD == testcd] + get_unit <- function(testcd) group_nca$result$PPSTRESU[group_nca$result$PPTESTCD == testcd] + tlast <- get_res("tlast") + half_life <- get_res("half.life") + adj.r.squared <- get_res("adj.r.squared") + lz_time_first <- get_res("lambda.z.time.first") + lz_time_last <- get_res("lambda.z.time.last") + time_span <- lz_time_last - lz_time_first + + # Prepare fit line (on log scale, then back-transform) + fit_line_data <- data.frame(x = c(lz_time_first, tlast)) + colnames(fit_line_data) <- time_col + fit_line_data$y <- predict(fit, fit_line_data) + + # Plot data + plot_data <- df + plot_data$color <- ifelse(is_lz_used, "green", "red") + title <- paste0(paste0(group_vars, ": "), group[, group_vars, drop = FALSE], collapse = ", ") + xlab <- if (!is.null(timeu_col)) paste0(time_col, " (", unique(plot_data[[timeu_col]]), ")") else time_col + ylab <- if (!is.null(concu_col)) paste0(conc_col, " (", unique(plot_data[[concu_col]]), ")") else conc_col + subtitle_text <- paste0( + "R2adj = ", signif(adj.r.squared, 2), + " ", + "ln(2)/ \u03BBz = ", signif(half_life, 2), " ", get_unit("half.life"), + " ", + "(T", df$IX[which(df[[time_col]] == lz_time_first)], + " - T", df$IX[which(df[[time_col]] == lz_time_last)], ")/2 = ", + " ", + signif(time_span / 2, 2), " ", get_unit("lambda.z.time.first") + ) + + # Build plotly object + p <- plotly::plot_ly() %>% + plotly::add_lines( + data = fit_line_data, + x = ~get(time_col), + y = ~10^y, + line = list(color = "green", width = 2), + name = "Fit", + inherit = FALSE, + showlegend = TRUE + ) %>% + plotly::layout( + title = title, + xaxis = list( + title = xlab, + linecolor = "black", + gridcolor = "white", + zeroline = FALSE + ), + yaxis = list( + title = ylab, + type = "log", + tickformat = "f", + linecolor = "black", + gridcolor = "white", + zeroline = FALSE + ), + annotations = if (add_annotations) list( + text = subtitle_text, + showarrow = FALSE, + xref = "paper", + yref = "paper", + y = 1 + ) else NULL + ) %>% + plotly::add_trace( + data = plot_data, + x = ~plot_data[[time_col]], + y = ~plot_data[[conc_col]], + text = ~paste0( + "Data Point: ", plot_data[["IX"]], "\n", + "(", + get(time_col), + ", ", + signif(get(conc_col), 3), + ")" + ), + hoverinfo = "text", + showlegend = FALSE, + type = "scatter", + mode = "markers", + marker = list( + color = plot_data$color, + size = 15, + symbol = ifelse(plot_data[[exclude_hl_col]], "x", "circle"), + size = 20 + ), + customdata = ~plot_data[["ROWID"]] # Returns the row number in the object + ) + plots[[i]] <- p + names(plots)[i] <- title + } + return(plots) +} diff --git a/inst/shiny/modules/tab_nca/setup/slope_selector.R b/inst/shiny/modules/tab_nca/setup/slope_selector.R index 692f62d2a..7b951551c 100644 --- a/inst/shiny/modules/tab_nca/setup/slope_selector.R +++ b/inst/shiny/modules/tab_nca/setup/slope_selector.R @@ -243,7 +243,7 @@ slope_selector_server <- function( # nolint output$page_number <- renderUI(num_pages) plots_to_render <- slice(ungroup(subject_profile_plot_ids), page_start:page_end) - +browser() plot_outputs <- apply(plots_to_render, 1, function(row) { lambda_slope_plot( diff --git a/inst/shiny/o_nca b/inst/shiny/o_nca new file mode 100644 index 0000000000000000000000000000000000000000..a460e4ba16c52d164ea6a7ddbf3f606e739facb1 GIT binary patch literal 366470 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 y8X$g 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

?$4j< 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 _3U;S9)MpA96_4GjZr7@##tyR8>`;fEbg;ryQ1 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

!Bm~GmMau%6JE3l0-E(ixyY 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 z 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೩TS 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 Date: Wed, 27 Aug 2025 14:55:30 +0200 Subject: [PATCH 002/113] fix: loop difficulties and still problems with start != 0 --- R/lambda_slope_plot.R | 95 +++++++++++++++++++++++++++++++------------ 1 file changed, 70 insertions(+), 25 deletions(-) diff --git a/R/lambda_slope_plot.R b/R/lambda_slope_plot.R index 45273966f..274d9265d 100644 --- a/R/lambda_slope_plot.R +++ b/R/lambda_slope_plot.R @@ -265,7 +265,7 @@ get_halflife_plot <- function(o_nca, add_annotations = TRUE) { o_nca$result$PPSTRESU <- o_nca$result$PPORRESU } } - + # Get grouping structure for lambda.z groups <- getGroups(o_nca %>% dplyr::filter(PPTESTCD == "lambda.z")) %>% unique() groups <- o_nca$result %>% @@ -275,7 +275,7 @@ get_halflife_plot <- function(o_nca, add_annotations = TRUE) { unique() plots <- vector("list", nrow(groups)) - + for (i in seq_len(nrow(groups))) { group <- groups[i, ] group_vars <- setdiff(names(group), c("start", "end")) @@ -292,7 +292,11 @@ get_halflife_plot <- function(o_nca, add_annotations = TRUE) { o_nca$data$conc$data[["exclude_half.life"]] <- FALSE exclude_hl_col <- "exclude_half.life" } - + exclude_msg_col <- o_nca$data$conc$columns$exclude + if (is.null(exclude_msg_col)) { + exclude_msg_col <- "exclude" + } + # Filter and order by time df <- df[df[[time_col]] >= group$start & df[[time_col]] <= group$end, ] df[["ROWID"]] <- seq_len(nrow(df)) @@ -303,44 +307,84 @@ get_halflife_plot <- function(o_nca, add_annotations = TRUE) { group_nca <- o_nca group_nca$data$conc$data <- df group_nca$result <- merge(group_nca$result, group[, group_vars, drop = FALSE]) - is_lz_used <- get_halflife_points(group_nca) - df_fit <- df[is_lz_used, ] - - # Fit log-linear model to green points - fit <- lm(as.formula(paste0("log10(", conc_col,") ~ ", time_col)), df_fit) - + # Extract NCA results for annotation get_res <- function(testcd) group_nca$result$PPORRES[group_nca$result$PPTESTCD == testcd] get_unit <- function(testcd) group_nca$result$PPSTRESU[group_nca$result$PPTESTCD == testcd] + start <- unique(group_nca$result$start) # this has to have a better way tlast <- get_res("tlast") half_life <- get_res("half.life") adj.r.squared <- get_res("adj.r.squared") lz_time_first <- get_res("lambda.z.time.first") lz_time_last <- get_res("lambda.z.time.last") time_span <- lz_time_last - lz_time_first - - # Prepare fit line (on log scale, then back-transform) - fit_line_data <- data.frame(x = c(lz_time_first, tlast)) - colnames(fit_line_data) <- time_col - fit_line_data$y <- predict(fit, fit_line_data) - + span_ratio <- get_res("span.ratio") + exclude_calc_reason <- group_nca$result$exclude[group_nca$result$PPTESTCD == "half.life"] + + ######################################################### + # TODO (Gerardo): This complication in the code comes from a PKNCA issue + # Once solved in a new version this can be simplified + group_nca_for_fun <- group_nca + group_nca_for_fun$result <- group_nca_for_fun$result %>% + mutate( + PPORRES = ifelse( + PPTESTCD %in% c("lambda.z.time.first", "lambda.z.time.last", "tlast"), + PPORRES + start, + PPORRES + ), + PPSTRES = ifelse( + PPTESTCD %in% c("lambda.z.time.first", "lambda.z.time.last", "tlast"), + PPSTRES + start, + PPSTRES + ) + ) + if (!is.na(half_life)) { + is_lz_used <- get_halflife_points(group_nca_for_fun) + } else { + is_lz_used <- rep(NA_real_, nrow(df)) + } + ######################################################### + + # Compute the points to depict the lambda fit line (if there is) + if (!is.na(half_life)) { + df_fit <- df[is_lz_used, ] + fit <- lm(as.formula(paste0("log10(", conc_col,") ~ ", time_col)), df_fit) + fit_line_data <- data.frame(x = c(lz_time_first + start, tlast + start)) + colnames(fit_line_data) <- time_col + fit_line_data$y <- predict(fit, fit_line_data) + } else { + fit_line_data <- data.frame( + x = c(start, start), + y = c(0, 0) + ) + colnames(fit_line_data)[1] <- time_col + } + # Plot data plot_data <- df - plot_data$color <- ifelse(is_lz_used, "green", "red") + plot_data$color <- case_when( + is.na(is_lz_used) ~ "black", + !is.na(is_lz_used) & is_lz_used ~ "green", + !is.na(is_lz_used) & !is_lz_used ~ "red", + TRUE ~ "black" + ) title <- paste0(paste0(group_vars, ": "), group[, group_vars, drop = FALSE], collapse = ", ") xlab <- if (!is.null(timeu_col)) paste0(time_col, " (", unique(plot_data[[timeu_col]]), ")") else time_col ylab <- if (!is.null(concu_col)) paste0(conc_col, " (", unique(plot_data[[concu_col]]), ")") else conc_col subtitle_text <- paste0( "R2adj = ", signif(adj.r.squared, 2), - " ", - "ln(2)/ \u03BBz = ", signif(half_life, 2), " ", get_unit("half.life"), - " ", - "(T", df$IX[which(df[[time_col]] == lz_time_first)], - " - T", df$IX[which(df[[time_col]] == lz_time_last)], ")/2 = ", - " ", - signif(time_span / 2, 2), " ", get_unit("lambda.z.time.first") + "    ", + #"ln(2)/ \u03BBz = ", signif(half_life, 2), " ", get_unit("half.life"), + #" ", + #"(T", df$IX[which(df[[time_col]] == lz_time_first)], + #" - T", df$IX[which(df[[time_col]] == lz_time_last)], ")/(ln(2)/ \u03BBz) =", + "span ratio = ", + signif(span_ratio, 2) ) - + if (is.na(half_life)) { + subtitle_text <- exclude_calc_reason + } + # Build plotly object p <- plotly::plot_ly() %>% plotly::add_lines( @@ -400,7 +444,8 @@ get_halflife_plot <- function(o_nca, add_annotations = TRUE) { ), customdata = ~plot_data[["ROWID"]] # Returns the row number in the object ) - plots[[i]] <- p + + plots[[i]] <- plotly_build(p) names(plots)[i] <- title } return(plots) From c0ef0204a5aa79d09180503d1d0cd56d830c8447 Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Wed, 27 Aug 2025 17:13:43 +0200 Subject: [PATCH 003/113] draft: customdata to use --- R/lambda_slope_plot.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/lambda_slope_plot.R b/R/lambda_slope_plot.R index 274d9265d..bb29562e1 100644 --- a/R/lambda_slope_plot.R +++ b/R/lambda_slope_plot.R @@ -442,7 +442,7 @@ get_halflife_plot <- function(o_nca, add_annotations = TRUE) { symbol = ifelse(plot_data[[exclude_hl_col]], "x", "circle"), size = 20 ), - customdata = ~plot_data[["ROWID"]] # Returns the row number in the object + customdata = ~df[, c(group_vars, time_col)] # Returns the row number in the object ) plots[[i]] <- plotly_build(p) From cabdc38af459c91743ce9506ca0cf61f9fbd9a3e Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Thu, 28 Aug 2025 11:56:19 +0200 Subject: [PATCH 004/113] fix: get_res uses PPSTRES instead of PPORRES --- R/lambda_slope_plot.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/lambda_slope_plot.R b/R/lambda_slope_plot.R index bb29562e1..d93d60c83 100644 --- a/R/lambda_slope_plot.R +++ b/R/lambda_slope_plot.R @@ -309,7 +309,7 @@ get_halflife_plot <- function(o_nca, add_annotations = TRUE) { group_nca$result <- merge(group_nca$result, group[, group_vars, drop = FALSE]) # Extract NCA results for annotation - get_res <- function(testcd) group_nca$result$PPORRES[group_nca$result$PPTESTCD == testcd] + get_res <- function(testcd) group_nca$result$PPSTRES[group_nca$result$PPTESTCD == testcd] get_unit <- function(testcd) group_nca$result$PPSTRESU[group_nca$result$PPTESTCD == testcd] start <- unique(group_nca$result$start) # this has to have a better way tlast <- get_res("tlast") From 76338b8c5d3d6feedfaa24ef58848b362774f230 Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Thu, 28 Aug 2025 14:40:24 +0200 Subject: [PATCH 005/113] feat: input for plots is pknca_data & namespace PKNCA funs --- R/lambda_slope_plot.R | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/R/lambda_slope_plot.R b/R/lambda_slope_plot.R index d93d60c83..d418480d4 100644 --- a/R/lambda_slope_plot.R +++ b/R/lambda_slope_plot.R @@ -257,7 +257,11 @@ lambda_slope_plot <- function( } -get_halflife_plot <- function(o_nca, add_annotations = TRUE) { +get_halflife_plot <- function(pknca_data, add_annotations = TRUE) { + + # Obtain the results + o_nca <- PKNCA::pk.nca(pknca_data) + # Ensure result columns are present if (!"PPSTRES" %in% names(o_nca$result)) { o_nca$result$PPSTRES <- o_nca$result$PPORRES @@ -265,9 +269,9 @@ get_halflife_plot <- function(o_nca, add_annotations = TRUE) { o_nca$result$PPSTRESU <- o_nca$result$PPORRESU } } - + # Get grouping structure for lambda.z - groups <- getGroups(o_nca %>% dplyr::filter(PPTESTCD == "lambda.z")) %>% unique() + groups <- PKNCA::getGroups(o_nca %>% dplyr::filter(PPTESTCD == "lambda.z")) %>% unique() groups <- o_nca$result %>% select(any_of(c(group_vars(o_nca), "start", "end", "PPTESTCD"))) %>% dplyr::filter(PPTESTCD == "lambda.z") %>% @@ -339,7 +343,7 @@ get_halflife_plot <- function(o_nca, add_annotations = TRUE) { ) ) if (!is.na(half_life)) { - is_lz_used <- get_halflife_points(group_nca_for_fun) + is_lz_used <- PKNCA::get_halflife_points(group_nca_for_fun) } else { is_lz_used <- rep(NA_real_, nrow(df)) } From daec41052abc15b28af8ec234fc3fbd69e03fcf7 Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Thu, 28 Aug 2025 16:55:40 +0200 Subject: [PATCH 006/113] draft: start trying to include get_halflife_plots in the server logic --- .../modules/tab_nca/setup/slope_selector.R | 130 ++++++------------ 1 file changed, 43 insertions(+), 87 deletions(-) diff --git a/inst/shiny/modules/tab_nca/setup/slope_selector.R b/inst/shiny/modules/tab_nca/setup/slope_selector.R index 7b951551c..7bce5b520 100644 --- a/inst/shiny/modules/tab_nca/setup/slope_selector.R +++ b/inst/shiny/modules/tab_nca/setup/slope_selector.R @@ -12,7 +12,7 @@ #' @returns List with reactive expressions: #' * manual_slopes - Data frame containing inclusions / exclusions. #' * profiles_per_subject - Grouping for each subject. -#' * slopes_groups - Grouping for the slopes, in accordance to the settings. +#' * slopes_pknca_groups - Grouping for the slopes, in accordance to the settings. slope_selector_ui <- function(id) { ns <- NS(id) @@ -128,9 +128,22 @@ slope_selector_server <- function( # nolint ns <- session$ns + slopes_pknca_data <- reactive({ + req(pknca_data()) + browser() + pknca_data <- pknca_data() + pknca_data$intervals <- pknca_data$intervals %>% + mutate( + half.life = TRUE + ) + pknca_data + }) + #Get grouping columns for plots and tables - slopes_groups <- reactive({ + slopes_pknca_groups <- reactive({ req(pknca_data()) + browser() + get_groups(pknca_data()) pknca_data()$conc$columns$groups %>% purrr::list_c() %>% @@ -161,69 +174,38 @@ slope_selector_server <- function( # nolint #' Plot data is a local reactive copy of full data. The purpose is to display data that #' is already adjusted with the applied rules, so that the user can verify added selections #' and exclusions before applying them to the actual dataset. - plot_data <- reactive({ - req(pknca_data(), manual_slopes(), profiles_per_subject()) - filter_slopes(pknca_data(), manual_slopes(), profiles_per_subject(), slopes_groups()) - }) %>% - shiny::debounce(750) - - # Generate dynamically the minimum results you need for the lambda plots - lambdas_res <- reactive({ - req(plot_data()) - if (!"type_interval" %in% names(plot_data()$intervals)) { - NULL - } else if (all(!unlist(plot_data()$intervals[sapply(plot_data()$intervals, is.logical)]))) { - NULL - } else { - result_obj <- suppressWarnings(PKNCA::pk.nca(data = plot_data(), verbose = FALSE)) - result_obj$result <- result_obj$result %>% - mutate(start_dose = start, end_dose = end) - - result_obj - } - }) - - # Profiles per Patient ---- - # Define the profiles per patient - profiles_per_subject <- reactive({ - req(pknca_data()) - - pknca_data()$intervals %>% - mutate(USUBJID = as.character(USUBJID), - NCA_PROFILE = as.character(NCA_PROFILE), - DOSNOA = as.character(DOSNOA)) %>% - group_by(!!!syms(c(unname(unlist(pknca_data()$conc$columns$groups)), "DOSNOA"))) %>% - summarise(NCA_PROFILE = unique(NCA_PROFILE), .groups = "drop") %>% - unnest(NCA_PROFILE) # Convert lists into individual rows - }) + # plot_data <- reactive({ + # req(pknca_data(), manual_slopes(), profiles_per_subject()) + # filter_slopes(pknca_data(), manual_slopes(), profiles_per_subject(), slopes_pknca_groups()) + # }) %>% + # shiny::debounce(750) #' Updating plot outputUI, dictating which plots get displayed to the user. #' Scans for any related reactives (page number, subject filter etc) and updates the plot output #' UI to have only plotlyOutput elements for desired plots. observeEvent(list( - plot_data(), lambdas_res(), input$plots_per_page, input$search_subject, current_page() + pknca_data(), input$plots_per_page, input$search_subject, current_page() ), { - req(lambdas_res()) + req(pknca_data()) log_trace("{id}: Updating displayed plots") - +browser() + # Decide # Make sure the search_subject input is not NULL search_subject <- { if (is.null(input$search_subject) || length(input$search_subject) == 0) { - unique(lambdas_res()$result$USUBJID) + subject_col <- pknca_data()$conc$columns$subject + unique( + slopes_pknca_data()$intervals %>% + filter(half.life) %>% + .[[subject_col]] + ) } else { input$search_subject } } - # create plot ids based on available data # - subject_profile_plot_ids <- pknca_data()$intervals %>% - select(any_of(c(unname(unlist(pknca_data()$dose$columns$groups)), - unname(unlist(pknca_data()$conc$columns$groups)), - "NCA_PROFILE", "DOSNOA"))) %>% - filter(USUBJID %in% search_subject) %>% - select(slopes_groups(), USUBJID, DOSNOA) %>% - unique() %>% - arrange(USUBJID) + # Get all lambda z slope plots + plot_outputs <- get_halflife_plot(pknca_data()) # find which plots should be displayed based on page # num_plots <- nrow(subject_profile_plot_ids) @@ -241,34 +223,12 @@ slope_selector_server <- function( # nolint # update page number display # output$page_number <- renderUI(num_pages) - - plots_to_render <- slice(ungroup(subject_profile_plot_ids), page_start:page_end) browser() - plot_outputs <- apply(plots_to_render, 1, function(row) { - - lambda_slope_plot( - conc_pknca_df = plot_data()$conc$data, - row_values = as.list(row), - myres = lambdas_res(), - r2adj_threshold = 0.7, - time_column = pknca_data()$conc$columns$time - ) |> - htmlwidgets::onRender( - # nolint start - "function(el, x) { - const plotlyElements = $('.slope-selector-module .plotly.html-widget.html-fill-item.html-widget-static-bound.js-plotly-plot'); - plotlyElements.css('height', '100%'); - plotlyElements.css('aspect-ratio', '1'); - window.dispatchEvent(new Event('resize')); - }" - # nolint end - ) - }) - + # Render only the plots requested by the user output$slope_plots_ui <- renderUI({ shinyjs::enable(selector = ".btn-page") - plot_outputs + plot_outputs[page_start:page_end] }) # update jump to page selector # @@ -292,23 +252,20 @@ browser() }) #' Rendering slope plots based on nca data. - observeEvent(lambdas_res(), { - req( - lambdas_res(), - profiles_per_subject() - ) + observeEvent(pknca_data(), { + req(pknca_data()) log_trace("{id}: Rendering plots") # Update the subject search input to make available choices for the user updateSelectInput( session = session, inputId = "search_subject", label = "Search Subject", - choices = unique(lambdas_res()$result$USUBJID) + choices = unique(pknca_data()$intervals$USUBJID) ) }) slopes_table <- manual_slopes_table_server("manual_slopes", pknca_data, - profiles_per_subject, slopes_groups) + profiles_per_subject, slopes_pknca_groups) manual_slopes <- slopes_table$manual_slopes refresh_reactable <- slopes_table$refresh_reactable @@ -316,9 +273,9 @@ browser() # Define the click events for the point exclusion and selection in the slope plots last_click_data <- reactiveValues() - observeEvent(slopes_groups(), { - # Reinitialize dynamic columns when slopes_groups changes - for (col in tolower(slopes_groups())) { + observeEvent(slopes_pknca_groups(), { + # Reinitialize dynamic columns when slopes_pknca_groups changes + for (col in tolower(slopes_pknca_groups())) { last_click_data[[col]] <- "" } last_click_data$idx_pnt <- "" @@ -329,7 +286,7 @@ browser() result <- handle_plotly_click(last_click_data, manual_slopes, - slopes_groups(), + slopes_pknca_groups(), event_data("plotly_click")) # Update reactive values in the observer last_click_data <- result$last_click_data @@ -373,8 +330,7 @@ browser() #' return reactive with slope exclusions data to be displayed in Results -> Exclusions tab list( manual_slopes = manual_slopes, - profiles_per_subject = profiles_per_subject, - slopes_groups = slopes_groups + slopes_pknca_groups = slopes_pknca_groups ) }) } From 08ef8d6ef0681a14d01c0d8ae59df103652c8371 Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Fri, 29 Aug 2025 10:51:10 +0200 Subject: [PATCH 007/113] fix: adapt modules slope_selector, manual_slopes_table --- .../tab_nca/setup/manual_slopes_table.R | 50 +++++++++---------- .../modules/tab_nca/setup/slope_selector.R | 36 +++++++------ 2 files changed, 42 insertions(+), 44 deletions(-) diff --git a/inst/shiny/modules/tab_nca/setup/manual_slopes_table.R b/inst/shiny/modules/tab_nca/setup/manual_slopes_table.R index 9ca905e30..88b9c9711 100644 --- a/inst/shiny/modules/tab_nca/setup/manual_slopes_table.R +++ b/inst/shiny/modules/tab_nca/setup/manual_slopes_table.R @@ -20,7 +20,7 @@ manual_slopes_table_ui <- function(id) { manual_slopes_table_server <- function( - id, mydata, profiles_per_subject, slopes_groups + id, mydata, slopes_groups ) { moduleServer(id, function(input, output, session) { @@ -29,7 +29,8 @@ manual_slopes_table_server <- function( # Reactive for Slope selector columns slope_selector_columns <- reactive({ req(slopes_groups()) - c(slopes_groups(), "TYPE", "RANGE", "REASON") + + c(names(slopes_groups()), "TYPE", "RANGE", "REASON") }) #' Object for storing exclusion and selection data for lambda slope calculation @@ -43,16 +44,16 @@ manual_slopes_table_server <- function( }) observeEvent(mydata(), { - current_slopes <- manual_slopes() + # Add missing dynamic columns with default values (e.g., NA_character_) - for (col in slopes_groups()) { - if (!col %in% colnames(current_slopes)) { - current_slopes[[col]] <- character() - } + missing_cols <- setdiff(colnames(slopes_groups()), colnames(current_slopes)) + for (missing_col in missing_cols) { + current_slopes[[missing_col]] <- character() } + # Define the desired column order - ordered_cols <- c(slopes_groups(), "TYPE", "RANGE", "REASON") + ordered_cols <- c(colnames(slopes_groups()), "TYPE", "RANGE", "REASON") current_slopes <- current_slopes[, ordered_cols, drop = FALSE] # Update the reactive Val @@ -61,23 +62,19 @@ manual_slopes_table_server <- function( #' Adds new row to the selection/exclusion datatable observeEvent(input$add_rule, { + log_trace("{id}: adding manual slopes row") - # Create a named list for dynamic columns based on `profiles_per_subject` - dynamic_values <- lapply(slopes_groups(), function(col) { - value <- as.character(unique(profiles_per_subject()[[col]])) - if (length(value) > 0) value[1] else NA_character_ # Handle empty or NULL cases - }) - - names(dynamic_values) <- slopes_groups() # Create the new row with both fixed and dynamic columns - new_row <- as.data.frame(c( - dynamic_values, - TYPE = "Selection", - RANGE = "1:3", - REASON = "" - ), stringsAsFactors = FALSE) + new_row <- cbind( + slopes_groups()[1, ], + data.frame( + TYPE = "Selection", + RANGE = "1:3", + REASON = "" + ) + ) updated_data <- as.data.frame(rbind(manual_slopes(), new_row), stringsAsFactors = FALSE) manual_slopes(updated_data) @@ -87,6 +84,7 @@ manual_slopes_table_server <- function( #' Removes selected row observeEvent(input$remove_rule, { + log_trace("{id}: removing manual slopes row") selected <- getReactableState("manual_slopes", "selected") @@ -128,19 +126,19 @@ manual_slopes_table_server <- function( width = 400 ) ) - +browser() # Dynamic group column definitions - dynamic_columns <- lapply(slopes_groups(), function(col) { + dynamic_columns <- lapply(colnames(slopes_groups()), function(col) { colDef( cell = dropdown_extra( id = ns(paste0("edit_", col)), - choices = unique(profiles_per_subject()[[col]]), # Dynamically set choices + choices = unique(slopes_groups()[[col]]), # Dynamically set choices class = "dropdown-extra" ), width = 150 ) }) - names(dynamic_columns) <- slopes_groups() + names(dynamic_columns) <- colnames(slopes_groups()) # Combine columns in the desired order all_columns <- c( @@ -173,6 +171,7 @@ manual_slopes_table_server <- function( #' rows, plots selection, edits). This needs to be separate call, since simply re-rendering #' the table would mean losing focus on text inputs when entering values. observeEvent(manual_slopes(), { + req(manual_slopes()) reactable::updateReactable( outputId = "manual_slopes", @@ -184,6 +183,7 @@ manual_slopes_table_server <- function( #' edits for that column made in the reactable. observe({ req(slope_selector_columns()) + # Dynamically attach observers for each column purrr::walk(slope_selector_columns(), \(colname) { observeEvent(input[[paste0("edit_", colname)]], { diff --git a/inst/shiny/modules/tab_nca/setup/slope_selector.R b/inst/shiny/modules/tab_nca/setup/slope_selector.R index 7bce5b520..2f99dc4dd 100644 --- a/inst/shiny/modules/tab_nca/setup/slope_selector.R +++ b/inst/shiny/modules/tab_nca/setup/slope_selector.R @@ -130,7 +130,7 @@ slope_selector_server <- function( # nolint slopes_pknca_data <- reactive({ req(pknca_data()) - browser() + pknca_data <- pknca_data() pknca_data$intervals <- pknca_data$intervals %>% mutate( @@ -142,20 +142,17 @@ slope_selector_server <- function( # nolint #Get grouping columns for plots and tables slopes_pknca_groups <- reactive({ req(pknca_data()) - browser() - get_groups(pknca_data()) - - pknca_data()$conc$columns$groups %>% - purrr::list_c() %>% - append(c("NCA_PROFILE", "DOSNOA")) %>% - purrr::keep(\(col) { - !is.null(col) && col != "DRUG" && length(unique(pknca_data()$conc$data[[col]])) > 1 - }) + + pknca_data()$intervals %>% + select( + any_of( + c(group_vars(pknca_data()), "start", "end", "NCA_PROFILE") + ) + ) }) # HACK: workaround to avoid plotly_click not being registered warning session$userData$plotlyShinyEventIDs <- "plotly_click-A" - current_page <- reactiveVal(1) #' updating current page based on user input @@ -188,7 +185,7 @@ slope_selector_server <- function( # nolint ), { req(pknca_data()) log_trace("{id}: Updating displayed plots") -browser() + # Decide # Make sure the search_subject input is not NULL search_subject <- { @@ -208,10 +205,10 @@ browser() plot_outputs <- get_halflife_plot(pknca_data()) # find which plots should be displayed based on page # - num_plots <- nrow(subject_profile_plot_ids) + num_plots <- nrow(slopes_pknca_groups()) plots_per_page <- as.numeric(input$plots_per_page) num_pages <- ceiling(num_plots / plots_per_page) - + if (current_page() > num_pages) { current_page(current_page() - 1) return(NULL) @@ -223,7 +220,6 @@ browser() # update page number display # output$page_number <- renderUI(num_pages) -browser() # Render only the plots requested by the user output$slope_plots_ui <- renderUI({ @@ -254,6 +250,7 @@ browser() #' Rendering slope plots based on nca data. observeEvent(pknca_data(), { req(pknca_data()) + log_trace("{id}: Rendering plots") # Update the subject search input to make available choices for the user updateSelectInput( @@ -265,7 +262,7 @@ browser() }) slopes_table <- manual_slopes_table_server("manual_slopes", pknca_data, - profiles_per_subject, slopes_pknca_groups) + slopes_pknca_groups) manual_slopes <- slopes_table$manual_slopes refresh_reactable <- slopes_table$refresh_reactable @@ -283,7 +280,7 @@ browser() observeEvent(event_data("plotly_click", priority = "event"), { log_trace("slope_selector: plotly click detected") - + result <- handle_plotly_click(last_click_data, manual_slopes, slopes_pknca_groups(), @@ -299,7 +296,7 @@ browser() #' If any settings are uploaded by the user, overwrite current rules observeEvent(manual_slopes_override(), { req(manual_slopes_override()) - + if (nrow(manual_slopes_override()) == 0) return(NULL) log_debug_list("Manual slopes override:", manual_slopes_override()) @@ -318,6 +315,7 @@ browser() all() if (!override_valid) { + msg <- "Manual slopes not compatible with current data, leaving as default." log_warn(msg) showNotification(msg, type = "warning", duration = 5) @@ -330,7 +328,7 @@ browser() #' return reactive with slope exclusions data to be displayed in Results -> Exclusions tab list( manual_slopes = manual_slopes, - slopes_pknca_groups = slopes_pknca_groups + slopes_groups = slopes_pknca_groups ) }) } From 862ad2f018a886c8bd501cc105cb5219bb5b92b7 Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Fri, 29 Aug 2025 11:28:06 +0200 Subject: [PATCH 008/113] optimize: generation reactivity & reduce edit-table columns --- .../tab_nca/setup/manual_slopes_table.R | 20 ++++++++-------- .../modules/tab_nca/setup/slope_selector.R | 23 +++++++++++++------ 2 files changed, 26 insertions(+), 17 deletions(-) diff --git a/inst/shiny/modules/tab_nca/setup/manual_slopes_table.R b/inst/shiny/modules/tab_nca/setup/manual_slopes_table.R index 88b9c9711..c5fbeee23 100644 --- a/inst/shiny/modules/tab_nca/setup/manual_slopes_table.R +++ b/inst/shiny/modules/tab_nca/setup/manual_slopes_table.R @@ -20,7 +20,7 @@ manual_slopes_table_ui <- function(id) { manual_slopes_table_server <- function( - id, mydata, slopes_groups + id, mydata, slopes_pknca_groups ) { moduleServer(id, function(input, output, session) { @@ -28,9 +28,9 @@ manual_slopes_table_server <- function( # Reactive for Slope selector columns slope_selector_columns <- reactive({ - req(slopes_groups()) + req(slopes_pknca_groups()) - c(names(slopes_groups()), "TYPE", "RANGE", "REASON") + c(names(slopes_pknca_groups()), "TYPE", "RANGE", "REASON") }) #' Object for storing exclusion and selection data for lambda slope calculation @@ -47,13 +47,13 @@ manual_slopes_table_server <- function( current_slopes <- manual_slopes() # Add missing dynamic columns with default values (e.g., NA_character_) - missing_cols <- setdiff(colnames(slopes_groups()), colnames(current_slopes)) + missing_cols <- setdiff(colnames(slopes_pknca_groups()), colnames(current_slopes)) for (missing_col in missing_cols) { current_slopes[[missing_col]] <- character() } # Define the desired column order - ordered_cols <- c(colnames(slopes_groups()), "TYPE", "RANGE", "REASON") + ordered_cols <- c(colnames(slopes_pknca_groups()), "TYPE", "RANGE", "REASON") current_slopes <- current_slopes[, ordered_cols, drop = FALSE] # Update the reactive Val @@ -68,7 +68,7 @@ manual_slopes_table_server <- function( # Create the new row with both fixed and dynamic columns new_row <- cbind( - slopes_groups()[1, ], + slopes_pknca_groups()[1, ], data.frame( TYPE = "Selection", RANGE = "1:3", @@ -126,19 +126,19 @@ manual_slopes_table_server <- function( width = 400 ) ) -browser() + # Dynamic group column definitions - dynamic_columns <- lapply(colnames(slopes_groups()), function(col) { + dynamic_columns <- lapply(colnames(slopes_pknca_groups()), function(col) { colDef( cell = dropdown_extra( id = ns(paste0("edit_", col)), - choices = unique(slopes_groups()[[col]]), # Dynamically set choices + choices = unique(slopes_pknca_groups()[[col]]), # Dynamically set choices class = "dropdown-extra" ), width = 150 ) }) - names(dynamic_columns) <- colnames(slopes_groups()) + names(dynamic_columns) <- colnames(slopes_pknca_groups()) # Combine columns in the desired order all_columns <- c( diff --git a/inst/shiny/modules/tab_nca/setup/slope_selector.R b/inst/shiny/modules/tab_nca/setup/slope_selector.R index 2f99dc4dd..aed66c607 100644 --- a/inst/shiny/modules/tab_nca/setup/slope_selector.R +++ b/inst/shiny/modules/tab_nca/setup/slope_selector.R @@ -148,7 +148,19 @@ slope_selector_server <- function( # nolint any_of( c(group_vars(pknca_data()), "start", "end", "NCA_PROFILE") ) - ) + ) %>% + # Select only the columns that are strictly needed to identify each group + # That way the display for the user won't be saturated + mutate(GROUPID = 1:n()) %>% + select_minimal_grouping_cols(strata_cols = "GROUPID") %>% + select(-GROUPID) + }) + + # Get all lambda z slope plots + plot_outputs <- reactiveVal(NULL) + observeEvent(slopes_pknca_data(), { + plot_outputs <- get_halflife_plot(slopes_pknca_data()) + plot_outputs(plot_outputs) }) # HACK: workaround to avoid plotly_click not being registered warning @@ -181,9 +193,9 @@ slope_selector_server <- function( # nolint #' Scans for any related reactives (page number, subject filter etc) and updates the plot output #' UI to have only plotlyOutput elements for desired plots. observeEvent(list( - pknca_data(), input$plots_per_page, input$search_subject, current_page() + plot_outputs(), input$plots_per_page, input$search_subject, current_page() ), { - req(pknca_data()) + req(plot_outputs()) log_trace("{id}: Updating displayed plots") # Decide @@ -201,9 +213,6 @@ slope_selector_server <- function( # nolint } } - # Get all lambda z slope plots - plot_outputs <- get_halflife_plot(pknca_data()) - # find which plots should be displayed based on page # num_plots <- nrow(slopes_pknca_groups()) plots_per_page <- as.numeric(input$plots_per_page) @@ -224,7 +233,7 @@ slope_selector_server <- function( # nolint # Render only the plots requested by the user output$slope_plots_ui <- renderUI({ shinyjs::enable(selector = ".btn-page") - plot_outputs[page_start:page_end] + plot_outputs()[page_start:page_end] }) # update jump to page selector # From 99c195d5eba8db460445d4dcc70c1c80df6ca234 Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Fri, 29 Aug 2025 17:05:10 +0200 Subject: [PATCH 009/113] draft: try to change customdata for a more efficient way (not working) --- R/lambda_slope_plot.R | 10 ++++-- inst/shiny/functions/handle_plotly_click.R | 2 +- inst/shiny/modules/tab_nca/setup.R | 17 ++++++---- .../modules/tab_nca/setup/slope_selector.R | 33 ++++++++++++------- 4 files changed, 41 insertions(+), 21 deletions(-) diff --git a/R/lambda_slope_plot.R b/R/lambda_slope_plot.R index d418480d4..0e7a72f5d 100644 --- a/R/lambda_slope_plot.R +++ b/R/lambda_slope_plot.R @@ -259,6 +259,9 @@ lambda_slope_plot <- function( get_halflife_plot <- function(pknca_data, add_annotations = TRUE) { + # Add a ROWID in the data to track the points later + pknca_data$conc$data$ROWID <- seq_len(nrow(pknca_data$conc$data)) + # Obtain the results o_nca <- PKNCA::pk.nca(pknca_data) @@ -303,7 +306,6 @@ get_halflife_plot <- function(pknca_data, add_annotations = TRUE) { # Filter and order by time df <- df[df[[time_col]] >= group$start & df[[time_col]] <= group$end, ] - df[["ROWID"]] <- seq_len(nrow(df)) df <- df[order(df[[time_col]]), ] df$IX <- seq_len(nrow(df)) @@ -446,7 +448,11 @@ get_halflife_plot <- function(pknca_data, add_annotations = TRUE) { symbol = ifelse(plot_data[[exclude_hl_col]], "x", "circle"), size = 20 ), - customdata = ~df[, c(group_vars, time_col)] # Returns the row number in the object + customdata = apply( + plot_data[, c(group_vars, "ROWID", "IX"), drop = FALSE], + 1, + function(row) as.list(set_names(row, c(group_vars, "ROWID", "IX"))) + ) ) plots[[i]] <- plotly_build(p) diff --git a/inst/shiny/functions/handle_plotly_click.R b/inst/shiny/functions/handle_plotly_click.R index aca9ad98c..86fb43092 100644 --- a/inst/shiny/functions/handle_plotly_click.R +++ b/inst/shiny/functions/handle_plotly_click.R @@ -25,7 +25,7 @@ handle_plotly_click <- function(last_click_data, manual_slopes, slopes_groups, c ) # Extract additional information for idx_pnt - idx_pnt <- identifiers$IX + idx_pnt <- identifiers$ROWID # Create a copy of last_click_data updated_click_data <- last_click_data diff --git a/inst/shiny/modules/tab_nca/setup.R b/inst/shiny/modules/tab_nca/setup.R index 8dd28aef6..5c95d43cc 100644 --- a/inst/shiny/modules/tab_nca/setup.R +++ b/inst/shiny/modules/tab_nca/setup.R @@ -123,17 +123,22 @@ setup_server <- function(id, data, adnca_data) { req(adnca_data(), settings(), settings()$profile, settings()$analyte, settings()$pcspec) log_trace("Updating PKNCA::data object for slopes.") - +browser() PKNCA_update_data_object( adnca_data = adnca_data(), - auc_data = settings()$partial_aucs, - method = settings()$method, selected_analytes = settings()$analyte, selected_profile = settings()$profile, selected_pcspec = settings()$pcspec, - params = c("lambda.z.n.points", "lambda.z.time.first", - "r.squared", "adj.r.squared", "tmax"), - should_impute_c0 = settings()$data_imputation$impute_c0 + params = "half.life", + # The next parameters should not matter for the calculations + # So reactivity should not be involved + auc_data = data.frame( + start_auc = NA_real_, + end_auc = NA_real_ + ), + method = "lin up/log down", + # TODO (Gerardo): This would better be FALSE, but start changes... + should_impute_c0 = TRUE ) }) diff --git a/inst/shiny/modules/tab_nca/setup/slope_selector.R b/inst/shiny/modules/tab_nca/setup/slope_selector.R index aed66c607..77b1962a0 100644 --- a/inst/shiny/modules/tab_nca/setup/slope_selector.R +++ b/inst/shiny/modules/tab_nca/setup/slope_selector.R @@ -142,20 +142,23 @@ slope_selector_server <- function( # nolint #Get grouping columns for plots and tables slopes_pknca_groups <- reactive({ req(pknca_data()) - - pknca_data()$intervals %>% + + browser() + relevant_group_cols <- pknca_data()$conc$data %>% select( any_of( - c(group_vars(pknca_data()), "start", "end", "NCA_PROFILE") + c(group_vars(pknca_data()), "NCA_PROFILE") ) ) %>% - # Select only the columns that are strictly needed to identify each group - # That way the display for the user won't be saturated - mutate(GROUPID = 1:n()) %>% - select_minimal_grouping_cols(strata_cols = "GROUPID") %>% - select(-GROUPID) + # TODO (Gerardo): Include a better new version of select_minimal_grouping_cols + # Select columns that have more than 1 unique value + select(where(~ n_distinct(.) > 1)) %>% + colnames() + + pknca_data()$intervals %>% + select(any_of(relevant_group_cols)) }) - + # Get all lambda z slope plots plot_outputs <- reactiveVal(NULL) observeEvent(slopes_pknca_data(), { @@ -213,8 +216,13 @@ slope_selector_server <- function( # nolint } } + has_plot_subject <- grepl( + paste0("USUBJID: ", "(", paste0(search_subject, collapse = ")|("), ")"), + names(plot_outputs()) + ) + # find which plots should be displayed based on page # - num_plots <- nrow(slopes_pknca_groups()) + num_plots <- sum(has_plot_subject) plots_per_page <- as.numeric(input$plots_per_page) num_pages <- ceiling(num_plots / plots_per_page) @@ -233,7 +241,7 @@ slope_selector_server <- function( # nolint # Render only the plots requested by the user output$slope_plots_ui <- renderUI({ shinyjs::enable(selector = ".btn-page") - plot_outputs()[page_start:page_end] + plot_outputs()[has_plot_subject][page_start:page_end] }) # update jump to page selector # @@ -288,8 +296,9 @@ slope_selector_server <- function( # nolint }) observeEvent(event_data("plotly_click", priority = "event"), { + browser() log_trace("slope_selector: plotly click detected") - + result <- handle_plotly_click(last_click_data, manual_slopes, slopes_pknca_groups(), From 7e4ab7044c15b13de5a8509dc825a9eeed276f88 Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Wed, 3 Sep 2025 07:19:44 +0200 Subject: [PATCH 010/113] feat: make plots interactive with themselves and the table --- R/utils-slope_selector.R | 33 +++-- inst/shiny/functions/handle_plotly_click.R | 126 ++++++++++-------- .../modules/tab_nca/setup/slope_selector.R | 24 ++-- 3 files changed, 100 insertions(+), 83 deletions(-) diff --git a/R/utils-slope_selector.R b/R/utils-slope_selector.R index e2549fc5f..56b1eacbd 100644 --- a/R/utils-slope_selector.R +++ b/R/utils-slope_selector.R @@ -94,7 +94,8 @@ filter_slopes <- function(data, slopes, profiles, slope_groups, check_reasons = #' If TRUE, in that case full range will be kept. #' @returns Data frame with full ruleset, adjusted for new rules. #' @export -check_slope_rule_overlap <- function(existing, new, slope_groups, .keep = FALSE) { +check_slope_rule_overlap <- function(existing, new, .keep = FALSE) { + slope_groups <- setdiff(names(new), c("TYPE", "RANGE", "REASON")) # check if any rule already exists for specific subject and profile # existing_index <- which( @@ -141,29 +142,35 @@ check_slope_rule_overlap <- function(existing, new, slope_groups, .keep = FALSE) #' #' @returns description The modified `data` object with updated inclusion/exclusion flags #' and reasons in `data$conc$data`. -.apply_slope_rules <- function(data, slopes, slope_groups) { - - conc_data <- data$conc$data %>% - group_by(!!!syms(slope_groups)) %>% - mutate(index = seq_len(n())) %>% - ungroup() +.apply_slope_rules <- function(data, slopes) { + slope_groups <- group_vars(data) + time_col <- data$conc$columns$time + exclude_hl_col <- data$conc$columns$exclude_half.life + include_hl_col <- data$conc$columns$include_half.life for (i in seq_len(nrow(slopes))) { - # Build the condition dynamically for group columns + # Determine the time range for the points adjusted + range <- strsplit(as.character(slopes$RANGE[i]), ":")[[1]] + if (length(range) == 1) range <- rep(range, 2) + + # Build the condition dynamically for group columns and time range selection_index <- which( Reduce(`&`, lapply(slope_groups, function(col) { - conc_data[[col]] == slopes[[col]][i] + data$conc$data[[col]] == slopes[[col]][i] })) & - conc_data$index %in% .eval_range(slopes$RANGE[i]) + between(data$conc$data[[time_col]], as.numeric(range[[1]]), as.numeric(range[[2]])) ) if (slopes$TYPE[i] == "Selection") { - data$conc$data$is.included.hl[selection_index] <- TRUE + data$conc$data[[include_hl_col]][selection_index] <- TRUE } else { - data$conc$data$is.excluded.hl[selection_index] <- TRUE + data$conc$data[[exclude_hl_col]][selection_index] <- TRUE } - data$conc$data$REASON[selection_index] <- slopes$REASON[i] + data$conc$data$REASON[selection_index] <- paste0( + data$conc$data$REASON[selection_index], + slopes$REASON[i] + ) } data diff --git a/inst/shiny/functions/handle_plotly_click.R b/inst/shiny/functions/handle_plotly_click.R index 86fb43092..f3d1dd92b 100644 --- a/inst/shiny/functions/handle_plotly_click.R +++ b/inst/shiny/functions/handle_plotly_click.R @@ -5,72 +5,90 @@ #' #' @param last_click_data A reactive Values object storing the last clicked data. #' @param manual_slopes A reactive Values object storing the manually added slope rules. -#' @param slopes_groups A character vector of slope grouping column names. #' @param click_data A list containing the custom data from the plotly click event. #' #' @returns Returns a list with updated `last_click_data` and `manual_slopes`. #' -handle_plotly_click <- function(last_click_data, manual_slopes, slopes_groups, click_data) { +handle_plotly_click <- function(last_click_data, manual_slopes, click_data, pknca_data, plot_outputs) { req(click_data, click_data$customdata) - - identifiers <- click_data$customdata - if (!all(names(identifiers) %in% c(slopes_groups, "IX", "DOSNOA"))) { - stop("Error: Missing expected keys in customdata") + print("current click:") + print(click_data) + print("last click:") + print(last_click_data) + # If there is no previous click to this one, store it and do nothing else + if (is.null(last_click_data())) { + return(list( + last_click_data = click_data, + manual_slopes = manual_slopes(), + plot_outputs = plot_outputs + )) } - # Map identifiers dynamically - dynamic_values <- setNames( - lapply(slopes_groups, function(col) identifiers[[col]]), - slopes_groups - ) - - # Extract additional information for idx_pnt - idx_pnt <- identifiers$ROWID - - # Create a copy of last_click_data - updated_click_data <- last_click_data - - # Check if the selection has changed - updated <- any( - sapply( - slopes_groups, - function(col) dynamic_values[[col]] != updated_click_data[[tolower(col)]] - ) - ) + # Extract additional information of the point selected + idx_pnt <- as.numeric(click_data$customdata$ROWID) + time_pnt <- as.numeric(click_data$x) + row_pnt <- pknca_data$conc$data[idx_pnt, ] + int_pnt <- pknca_data$intervals %>% + merge(row_pnt, by = intersect(names(.), names(row_pnt))) %>% + filter(start <= row_pnt[[pknca_data$conc$columns$time]] & + end >= row_pnt[[pknca_data$conc$columns$time]]) %>% + select(any_of(names(pknca_data$intervals))) + group_pnt <- int_pnt %>% + select(any_of(c(group_vars(pknca_data), "NCA_PROFILE"))) + + # Do the same for the previous click + last_click_data <- last_click_data() + idx_lstpnt <- as.numeric(last_click_data$customdata$ROWID) + time_lstpnt <- as.numeric(last_click_data$x) + row_lstpnt <- pknca_data$conc$data[idx_lstpnt, ] + int_lstpnt <- pknca_data$intervals %>% + merge(row_lstpnt, by = intersect(names(.), names(row_lstpnt))) %>% + filter(start <= row_lstpnt[[pknca_data$conc$columns$time]] & + end >= row_lstpnt[[pknca_data$conc$columns$time]]) %>% + select(any_of(names(pknca_data$intervals))) + group_lstpnt <- int_lstpnt %>% + select(any_of(c(group_vars(pknca_data), "NCA_PROFILE"))) + + # Get relevant columns from data + excl_hl_col <- pknca_data$conc$columns$exclude_half.life + incl_hl_col <- pknca_data$conc$columns$include_half.life + time_col <- pknca_data$conc$columns$time - if (updated) { - for (col in slopes_groups) { - updated_click_data[[tolower(col)]] <- dynamic_values[[col]] - } - updated_click_data$idx_pnt <- idx_pnt + # Depending on the last click decide if it is an exclusion or inclusion of the point + new_rule <- group_pnt + if (idx_pnt == idx_lstpnt) { + new_rule$TYPE <- "Exclusion" + new_rule$RANGE <- time_pnt + new_rule$REASON <- "" - # Return updated last_click_data, but do not modify global state - return(list(last_click_data = updated_click_data, manual_slopes = manual_slopes())) + } else if (int_pnt == int_lstpnt) { + new_rule$TYPE <- "Inclusion" + new_rule$RANGE <- paste0(sort(c(time_pnt, time_lstpnt)), collapse = ":") + new_rule$REASON <- "" + } else { + # Do nothing + return(list( + last_click_data = click_data, + manual_slopes = manual_slopes(), + plot_outputs = plot_outputs + )) } - # Create new rule as a local object - new_rule <- as.data.frame( - lapply( - c( - dynamic_values, - TYPE = if (idx_pnt != updated_click_data$idx_pnt) "Selection" else "Exclusion", - RANGE = paste0(updated_click_data$idx_pnt, ":", idx_pnt), - REASON = "" - ), - as.character # Convert everything to character - ), - stringsAsFactors = FALSE - ) - + # Update the concentration data to add manual half life adjustments + updated_pknca <- .apply_slope_rules(pknca_data, new_rule) + + # Update the specific plot affected + updated_pknca$intervals <- int_pnt + updated_plot <- suppressWarnings(get_halflife_plot(updated_pknca)) + plot_outputs[names(plot_outputs) %in% names(updated_plot)] <- updated_plot + browser() # Update manual_slopes without modifying it globally - updated_slopes <- check_slope_rule_overlap(manual_slopes(), new_rule, slopes_groups) - - # Reset last_click_data dynamically - for (col in names(dynamic_values)) { - updated_click_data[[tolower(col)]] <- "" - } - updated_click_data$idx_pnt <- "" + updated_slopes <- check_slope_rule_overlap(manual_slopes(), new_rule) # Return updated values - list(last_click_data = updated_click_data, manual_slopes = updated_slopes) + list( + last_click_data = NULL, # Action was finished and has to be updated + manual_slopes = updated_slopes, + plot_outputs = plot_outputs + ) } diff --git a/inst/shiny/modules/tab_nca/setup/slope_selector.R b/inst/shiny/modules/tab_nca/setup/slope_selector.R index 77b1962a0..6faf78c77 100644 --- a/inst/shiny/modules/tab_nca/setup/slope_selector.R +++ b/inst/shiny/modules/tab_nca/setup/slope_selector.R @@ -142,8 +142,7 @@ slope_selector_server <- function( # nolint #Get grouping columns for plots and tables slopes_pknca_groups <- reactive({ req(pknca_data()) - - browser() + relevant_group_cols <- pknca_data()$conc$data %>% select( any_of( @@ -161,7 +160,7 @@ slope_selector_server <- function( # nolint # Get all lambda z slope plots plot_outputs <- reactiveVal(NULL) - observeEvent(slopes_pknca_data(), { + observeEvent(slopes_pknca_data(), { # Maybe use directly pknca_data plot_outputs <- get_halflife_plot(slopes_pknca_data()) plot_outputs(plot_outputs) }) @@ -285,26 +284,19 @@ slope_selector_server <- function( # nolint refresh_reactable <- slopes_table$refresh_reactable # Define the click events for the point exclusion and selection in the slope plots - last_click_data <- reactiveValues() - - observeEvent(slopes_pknca_groups(), { - # Reinitialize dynamic columns when slopes_pknca_groups changes - for (col in tolower(slopes_pknca_groups())) { - last_click_data[[col]] <- "" - } - last_click_data$idx_pnt <- "" - }) + last_click_data <- reactiveVal(NULL) observeEvent(event_data("plotly_click", priority = "event"), { - browser() log_trace("slope_selector: plotly click detected") result <- handle_plotly_click(last_click_data, manual_slopes, - slopes_pknca_groups(), - event_data("plotly_click")) + event_data("plotly_click"), + pknca_data(), + plot_outputs()) # Update reactive values in the observer - last_click_data <- result$last_click_data + last_click_data(result$last_click_data) + plot_outputs(result$plot_outputs) manual_slopes(result$manual_slopes) # render rectable anew # shinyjs::runjs("memory = {};") # needed to properly reset reactable.extras widgets From c8b08e0a5486a8c58c73b2be4e72864797e13f27 Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Wed, 3 Sep 2025 10:41:23 +0200 Subject: [PATCH 011/113] test push --- inst/shiny/modules/tab_nca/setup/slope_selector.R | 1 + 1 file changed, 1 insertion(+) diff --git a/inst/shiny/modules/tab_nca/setup/slope_selector.R b/inst/shiny/modules/tab_nca/setup/slope_selector.R index 6faf78c77..604003b4a 100644 --- a/inst/shiny/modules/tab_nca/setup/slope_selector.R +++ b/inst/shiny/modules/tab_nca/setup/slope_selector.R @@ -131,6 +131,7 @@ slope_selector_server <- function( # nolint slopes_pknca_data <- reactive({ req(pknca_data()) + pknca_data <- pknca_data() pknca_data$intervals <- pknca_data$intervals %>% mutate( From 3676fe2916263cc6b1e6620e6d587ab5650771f2 Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Wed, 3 Sep 2025 11:28:30 +0200 Subject: [PATCH 012/113] refactor: handle_plotly_click --- inst/shiny/functions/handle_plotly_click.R | 68 ++++++++++--------- .../tab_nca/setup/manual_slopes_table.R | 9 +-- 2 files changed, 40 insertions(+), 37 deletions(-) diff --git a/inst/shiny/functions/handle_plotly_click.R b/inst/shiny/functions/handle_plotly_click.R index f3d1dd92b..c1cc4b43e 100644 --- a/inst/shiny/functions/handle_plotly_click.R +++ b/inst/shiny/functions/handle_plotly_click.R @@ -6,6 +6,8 @@ #' @param last_click_data A reactive Values object storing the last clicked data. #' @param manual_slopes A reactive Values object storing the manually added slope rules. #' @param click_data A list containing the custom data from the plotly click event. +#' @param pknca_data A PKNCA data object containing concentration data and intervals. +#' @param plot_outputs A list of current plot outputs to be updated if needed. #' #' @returns Returns a list with updated `last_click_data` and `manual_slopes`. #' @@ -24,46 +26,27 @@ handle_plotly_click <- function(last_click_data, manual_slopes, click_data, pknc )) } - # Extract additional information of the point selected - idx_pnt <- as.numeric(click_data$customdata$ROWID) - time_pnt <- as.numeric(click_data$x) - row_pnt <- pknca_data$conc$data[idx_pnt, ] - int_pnt <- pknca_data$intervals %>% - merge(row_pnt, by = intersect(names(.), names(row_pnt))) %>% - filter(start <= row_pnt[[pknca_data$conc$columns$time]] & - end >= row_pnt[[pknca_data$conc$columns$time]]) %>% - select(any_of(names(pknca_data$intervals))) - group_pnt <- int_pnt %>% - select(any_of(c(group_vars(pknca_data), "NCA_PROFILE"))) - - # Do the same for the previous click - last_click_data <- last_click_data() - idx_lstpnt <- as.numeric(last_click_data$customdata$ROWID) - time_lstpnt <- as.numeric(last_click_data$x) - row_lstpnt <- pknca_data$conc$data[idx_lstpnt, ] - int_lstpnt <- pknca_data$intervals %>% - merge(row_lstpnt, by = intersect(names(.), names(row_lstpnt))) %>% - filter(start <= row_lstpnt[[pknca_data$conc$columns$time]] & - end >= row_lstpnt[[pknca_data$conc$columns$time]]) %>% - select(any_of(names(pknca_data$intervals))) - group_lstpnt <- int_lstpnt %>% - select(any_of(c(group_vars(pknca_data), "NCA_PROFILE"))) - + # Extract the information on the last and current points clicked + pnt <- .extract_click_info(click_data, pknca_data) + lstpnt <- .extract_click_info(last_click_data(), pknca_data) + # Get relevant columns from data excl_hl_col <- pknca_data$conc$columns$exclude_half.life incl_hl_col <- pknca_data$conc$columns$include_half.life time_col <- pknca_data$conc$columns$time - # Depending on the last click decide if it is an exclusion or inclusion of the point - new_rule <- group_pnt - if (idx_pnt == idx_lstpnt) { + # Depending on the last click decide what half life adjustment rule to apply + new_rule <- pnt$group + # If it is the same point, consider the point excluded + if (pnt$idx == lstpnt$idx) { new_rule$TYPE <- "Exclusion" - new_rule$RANGE <- time_pnt + new_rule$RANGE <- pnt$time new_rule$REASON <- "" - } else if (int_pnt == int_lstpnt) { + # If it is in the same plot (interval), consider all points in the time range included + } else if (pnt$int == lstpnt$int) { new_rule$TYPE <- "Inclusion" - new_rule$RANGE <- paste0(sort(c(time_pnt, time_lstpnt)), collapse = ":") + new_rule$RANGE <- paste0(sort(c(pnt$time, lstpnt$time)), collapse = ":") new_rule$REASON <- "" } else { # Do nothing @@ -76,12 +59,12 @@ handle_plotly_click <- function(last_click_data, manual_slopes, click_data, pknc # Update the concentration data to add manual half life adjustments updated_pknca <- .apply_slope_rules(pknca_data, new_rule) - + # Update the specific plot affected updated_pknca$intervals <- int_pnt updated_plot <- suppressWarnings(get_halflife_plot(updated_pknca)) plot_outputs[names(plot_outputs) %in% names(updated_plot)] <- updated_plot - browser() + # Update manual_slopes without modifying it globally updated_slopes <- check_slope_rule_overlap(manual_slopes(), new_rule) @@ -92,3 +75,22 @@ handle_plotly_click <- function(last_click_data, manual_slopes, click_data, pknc plot_outputs = plot_outputs ) } + + #' Helper to extract click info for handle_plotly_click + #' + #' @param click_data List from plotly click event + #' @param pknca_data PKNCA data object + #' @return List with idx, time, row, int, group + .extract_click_info <- function(click_data, pknca_data) { + idx <- as.numeric(click_data$customdata$ROWID) + time <- as.numeric(click_data$x) + row <- pknca_data$conc$data[idx, ] + int <- pknca_data$intervals %>% + merge(row, by = intersect(names(.), names(row))) %>% + filter(start <= row[[pknca_data$conc$columns$time]] & + end >= row[[pknca_data$conc$columns$time]]) %>% + select(any_of(names(pknca_data$intervals))) + group <- int %>% + select(any_of(c(group_vars(pknca_data), "NCA_PROFILE"))) + list(idx = idx, time = time, row = row, int = int, group = group) + } diff --git a/inst/shiny/modules/tab_nca/setup/manual_slopes_table.R b/inst/shiny/modules/tab_nca/setup/manual_slopes_table.R index c5fbeee23..06e1192c1 100644 --- a/inst/shiny/modules/tab_nca/setup/manual_slopes_table.R +++ b/inst/shiny/modules/tab_nca/setup/manual_slopes_table.R @@ -29,7 +29,7 @@ manual_slopes_table_server <- function( # Reactive for Slope selector columns slope_selector_columns <- reactive({ req(slopes_pknca_groups()) - + c(names(slopes_pknca_groups()), "TYPE", "RANGE", "REASON") }) @@ -64,7 +64,7 @@ manual_slopes_table_server <- function( observeEvent(input$add_rule, { log_trace("{id}: adding manual slopes row") - +browser() # Create the new row with both fixed and dynamic columns new_row <- cbind( @@ -84,7 +84,7 @@ manual_slopes_table_server <- function( #' Removes selected row observeEvent(input$remove_rule, { - +browser() log_trace("{id}: removing manual slopes row") selected <- getReactableState("manual_slopes", "selected") @@ -183,10 +183,11 @@ manual_slopes_table_server <- function( #' edits for that column made in the reactable. observe({ req(slope_selector_columns()) - + # Dynamically attach observers for each column purrr::walk(slope_selector_columns(), \(colname) { observeEvent(input[[paste0("edit_", colname)]], { + browser() edit <- input[[paste0("edit_", colname)]] edited_slopes <- manual_slopes() edited_slopes[edit$row, edit$column] <- edit$value From 02b5f3edb2308ca12b70fdfe43ca898a2bd1179a Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Wed, 3 Sep 2025 17:12:03 +0200 Subject: [PATCH 013/113] draft: selections still not working well --- R/utils-slope_selector.R | 36 ++++++++--- inst/shiny/functions/handle_plotly_click.R | 14 ++--- inst/shiny/modules/tab_nca/setup.R | 2 +- .../modules/tab_nca/setup/slope_selector.R | 61 +++++++++++-------- 4 files changed, 73 insertions(+), 40 deletions(-) diff --git a/R/utils-slope_selector.R b/R/utils-slope_selector.R index 56b1eacbd..a58d8b4fc 100644 --- a/R/utils-slope_selector.R +++ b/R/utils-slope_selector.R @@ -16,6 +16,7 @@ #' @importFrom dplyr filter group_by mutate select all_of #' @export filter_slopes <- function(data, slopes, profiles, slope_groups, check_reasons = FALSE) { + print("filter_slopes") if (is.null(data) || is.null(data$conc) || is.null(data$conc$data)) stop("Please provide valid data.") @@ -61,7 +62,7 @@ filter_slopes <- function(data, slopes, profiles, slope_groups, check_reasons = } # Update the exclusion/selection data for Lambda based on the current exc/sel table - data <- .apply_slope_rules(data, slopes, slope_groups) + data <- .update_pknca_with_rules(data, slopes, slope_groups) data$conc$data <- data$conc$data %>% group_by(!!!syms(slope_groups)) %>% @@ -95,6 +96,7 @@ filter_slopes <- function(data, slopes, profiles, slope_groups, check_reasons = #' @returns Data frame with full ruleset, adjusted for new rules. #' @export check_slope_rule_overlap <- function(existing, new, .keep = FALSE) { + print("check_slope_rule_overlap") slope_groups <- setdiff(names(new), c("TYPE", "RANGE", "REASON")) # check if any rule already exists for specific subject and profile # @@ -142,7 +144,9 @@ check_slope_rule_overlap <- function(existing, new, .keep = FALSE) { #' #' @returns description The modified `data` object with updated inclusion/exclusion flags #' and reasons in `data$conc$data`. -.apply_slope_rules <- function(data, slopes) { +.update_pknca_with_rules <- function(data, slopes) { + print(".update_pknca_with_rules") + browser() slope_groups <- group_vars(data) time_col <- data$conc$columns$time exclude_hl_col <- data$conc$columns$exclude_half.life @@ -154,7 +158,7 @@ check_slope_rule_overlap <- function(existing, new, .keep = FALSE) { if (length(range) == 1) range <- rep(range, 2) # Build the condition dynamically for group columns and time range - selection_index <- which( + pnt_idx <- which( Reduce(`&`, lapply(slope_groups, function(col) { data$conc$data[[col]] == slopes[[col]][i] })) & @@ -162,16 +166,32 @@ check_slope_rule_overlap <- function(existing, new, .keep = FALSE) { ) if (slopes$TYPE[i] == "Selection") { - data$conc$data[[include_hl_col]][selection_index] <- TRUE + data$conc$data[[include_hl_col]][pnt_idx] <- TRUE } else { - data$conc$data[[exclude_hl_col]][selection_index] <- TRUE + data$conc$data[[exclude_hl_col]][pnt_idx] <- TRUE } - data$conc$data$REASON[selection_index] <- paste0( - data$conc$data$REASON[selection_index], - slopes$REASON[i] + data$conc$data$REASON[pnt_idx] <- paste0( + data$conc$data$REASON[pnt_idx], + rep(slopes$REASON[i], length(pnt_idx)) ) + print(".update_pknca_with_rules") } data } + +.update_plots_with_rules <- function(pknca_data, manual_slopes, plot_outputs) { + print(".update_plots_with_rules") + pknca_for_plots <- .update_pknca_with_rules(pknca_data, manual_slopes) + browser() + pknca_for_plots$intervals <- inner_join( + manual_slopes %>% select(any_of(c(group_vars(pknca_for_plots), "NCA_PROFILE"))), + pknca_for_plots$intervals, + by = c(group_vars(pknca_for_plots), "NCA_PROFILE") + ) + updated_plots <- suppressWarnings(get_halflife_plot(pknca_for_plots)) + plot_outputs[names(plot_outputs) %in% names(updated_plots)] <- updated_plots + plot_outputs + print(".update_plots_with_rules") +} diff --git a/inst/shiny/functions/handle_plotly_click.R b/inst/shiny/functions/handle_plotly_click.R index c1cc4b43e..f67a8b349 100644 --- a/inst/shiny/functions/handle_plotly_click.R +++ b/inst/shiny/functions/handle_plotly_click.R @@ -44,7 +44,7 @@ handle_plotly_click <- function(last_click_data, manual_slopes, click_data, pknc new_rule$REASON <- "" # If it is in the same plot (interval), consider all points in the time range included - } else if (pnt$int == lstpnt$int) { + } else if (all.equal(pnt$int, lstpnt$int)) { new_rule$TYPE <- "Inclusion" new_rule$RANGE <- paste0(sort(c(pnt$time, lstpnt$time)), collapse = ":") new_rule$REASON <- "" @@ -58,12 +58,12 @@ handle_plotly_click <- function(last_click_data, manual_slopes, click_data, pknc } # Update the concentration data to add manual half life adjustments - updated_pknca <- .apply_slope_rules(pknca_data, new_rule) + updated_pknca <- .update_pknca_with_rules(pknca_data, new_rule) - # Update the specific plot affected - updated_pknca$intervals <- int_pnt - updated_plot <- suppressWarnings(get_halflife_plot(updated_pknca)) - plot_outputs[names(plot_outputs) %in% names(updated_plot)] <- updated_plot + # # Update the specific plot (interval) affected + # updated_pknca$intervals <- pnt$int + # updated_plot <- suppressWarnings(get_halflife_plot(updated_pknca)) + # plot_outputs[names(plot_outputs) %in% names(updated_plot)] <- updated_plot # Update manual_slopes without modifying it globally updated_slopes <- check_slope_rule_overlap(manual_slopes(), new_rule) @@ -72,7 +72,7 @@ handle_plotly_click <- function(last_click_data, manual_slopes, click_data, pknc list( last_click_data = NULL, # Action was finished and has to be updated manual_slopes = updated_slopes, - plot_outputs = plot_outputs + plot_outputs = plot_outputs # TODO: NOT NEEDED ANYMORE ) } diff --git a/inst/shiny/modules/tab_nca/setup.R b/inst/shiny/modules/tab_nca/setup.R index 5c95d43cc..fcf01678f 100644 --- a/inst/shiny/modules/tab_nca/setup.R +++ b/inst/shiny/modules/tab_nca/setup.R @@ -123,7 +123,7 @@ setup_server <- function(id, data, adnca_data) { req(adnca_data(), settings(), settings()$profile, settings()$analyte, settings()$pcspec) log_trace("Updating PKNCA::data object for slopes.") -browser() + PKNCA_update_data_object( adnca_data = adnca_data(), selected_analytes = settings()$analyte, diff --git a/inst/shiny/modules/tab_nca/setup/slope_selector.R b/inst/shiny/modules/tab_nca/setup/slope_selector.R index 604003b4a..da686992c 100644 --- a/inst/shiny/modules/tab_nca/setup/slope_selector.R +++ b/inst/shiny/modules/tab_nca/setup/slope_selector.R @@ -140,23 +140,26 @@ slope_selector_server <- function( # nolint pknca_data }) - #Get grouping columns for plots and tables + # Get grouping columns for plots and tables slopes_pknca_groups <- reactive({ req(pknca_data()) - relevant_group_cols <- pknca_data()$conc$data %>% - select( - any_of( - c(group_vars(pknca_data()), "NCA_PROFILE") - ) - ) %>% - # TODO (Gerardo): Include a better new version of select_minimal_grouping_cols - # Select columns that have more than 1 unique value - select(where(~ n_distinct(.) > 1)) %>% - colnames() - pknca_data()$intervals %>% - select(any_of(relevant_group_cols)) + select(any_of(c(group_vars(pknca_data()), "NCA_PROFILE"))) + + # relevant_group_cols <- pknca_data()$conc$data %>% + # select( + # any_of( + # c(group_vars(pknca_data()), "NCA_PROFILE") + # ) + # ) %>% + # # TODO (Gerardo): Include a better new version of select_minimal_grouping_cols + # # Select columns that have more than 1 unique value + # select(where(~ n_distinct(.) > 1)) %>% + # colnames() + + # pknca_data()$intervals %>% + # select(any_of(relevant_group_cols)) }) # Get all lambda z slope plots @@ -183,15 +186,6 @@ slope_selector_server <- function( # nolint observeEvent(input$select_page, current_page(as.numeric(input$select_page))) observeEvent(list(input$plots_per_page, input$search_subject), current_page(1)) - #' Plot data is a local reactive copy of full data. The purpose is to display data that - #' is already adjusted with the applied rules, so that the user can verify added selections - #' and exclusions before applying them to the actual dataset. - # plot_data <- reactive({ - # req(pknca_data(), manual_slopes(), profiles_per_subject()) - # filter_slopes(pknca_data(), manual_slopes(), profiles_per_subject(), slopes_pknca_groups()) - # }) %>% - # shiny::debounce(750) - #' Updating plot outputUI, dictating which plots get displayed to the user. #' Scans for any related reactives (page number, subject filter etc) and updates the plot output #' UI to have only plotlyOutput elements for desired plots. @@ -225,7 +219,7 @@ slope_selector_server <- function( # nolint num_plots <- sum(has_plot_subject) plots_per_page <- as.numeric(input$plots_per_page) num_pages <- ceiling(num_plots / plots_per_page) - + if (current_page() > num_pages) { current_page(current_page() - 1) return(NULL) @@ -283,7 +277,7 @@ slope_selector_server <- function( # nolint manual_slopes <- slopes_table$manual_slopes refresh_reactable <- slopes_table$refresh_reactable - + # Define the click events for the point exclusion and selection in the slope plots last_click_data <- reactiveVal(NULL) @@ -304,6 +298,25 @@ slope_selector_server <- function( # nolint refresh_reactable(refresh_reactable() + 1) }) + #' Separate event handling updating displayed reactable upon every change (adding and removing + #' rows, plots selection, edits). This needs to be separate call, since simply re-rendering + #' the table would mean losing focus on text inputs when entering values. + observeEvent(manual_slopes(), { + req(manual_slopes()) + print("observeEvent manual_slopes()") + reactable::updateReactable( + outputId = "manual_slopes", + data = manual_slopes() + ) + + if (nrow(manual_slopes())) { + plot_outputs( + .update_plots_with_rules(pknca_data(), manual_slopes(), plot_outputs()) + ) + } + }) + + #' If any settings are uploaded by the user, overwrite current rules observeEvent(manual_slopes_override(), { req(manual_slopes_override()) From 85ea16783b60e006b456636da3b202ac77d204cb Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Wed, 3 Sep 2025 17:12:59 +0200 Subject: [PATCH 014/113] draft: manual exclusions still not workng --- .../tab_nca/setup/manual_slopes_table.R | 28 ++++++++++--------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/inst/shiny/modules/tab_nca/setup/manual_slopes_table.R b/inst/shiny/modules/tab_nca/setup/manual_slopes_table.R index 06e1192c1..7a66bc372 100644 --- a/inst/shiny/modules/tab_nca/setup/manual_slopes_table.R +++ b/inst/shiny/modules/tab_nca/setup/manual_slopes_table.R @@ -20,7 +20,7 @@ manual_slopes_table_ui <- function(id) { manual_slopes_table_server <- function( - id, mydata, slopes_pknca_groups + id, mydata, slopes_pknca_groups ) { moduleServer(id, function(input, output, session) { @@ -167,22 +167,24 @@ browser() }) %>% shiny::bindEvent(refresh_reactable()) - #' Separate event handling updating displayed reactable upon every change (adding and removing - #' rows, plots selection, edits). This needs to be separate call, since simply re-rendering - #' the table would mean losing focus on text inputs when entering values. - observeEvent(manual_slopes(), { - req(manual_slopes()) - - reactable::updateReactable( - outputId = "manual_slopes", - data = manual_slopes() - ) - }) + #' #' Separate event handling updating displayed reactable upon every change (adding and removing + #' #' rows, plots selection, edits). This needs to be separate call, since simply re-rendering + #' #' the table would mean losing focus on text inputs when entering values. + #' observeEvent(manual_slopes(), { + #' req(manual_slopes()) + #' + #' reactable::updateReactable( + #' outputId = "manual_slopes", + #' data = manual_slopes() + #' ) + #' + #' update_plots <- function(pknca_data, pnew_rules) + #' }) #' For each of the columns in slope selector data frame, attach an event that will read #' edits for that column made in the reactable. observe({ - req(slope_selector_columns()) + req(slope_selector_columns(), manual_slopes()) # Dynamically attach observers for each column purrr::walk(slope_selector_columns(), \(colname) { From 16f55dbddf4102bbd082be5b61b17a0ab65acc96 Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Thu, 4 Sep 2025 16:46:31 +0200 Subject: [PATCH 015/113] rm slopes_pknca_groups as input for modules, only change affected plots TODO: mydata is affected by parameters and is affecting manual_slopes! --- R/utils-slope_selector.R | 27 ++++-- inst/shiny/functions/handle_plotly_click.R | 7 +- .../tab_nca/setup/manual_slopes_table.R | 88 ++++++------------- .../modules/tab_nca/setup/slope_selector.R | 73 ++++++++++----- 4 files changed, 100 insertions(+), 95 deletions(-) diff --git a/R/utils-slope_selector.R b/R/utils-slope_selector.R index a58d8b4fc..6187898ab 100644 --- a/R/utils-slope_selector.R +++ b/R/utils-slope_selector.R @@ -146,12 +146,13 @@ check_slope_rule_overlap <- function(existing, new, .keep = FALSE) { #' and reasons in `data$conc$data`. .update_pknca_with_rules <- function(data, slopes) { print(".update_pknca_with_rules") - browser() - slope_groups <- group_vars(data) + #browser() + slope_groups <- c(group_vars(data), "NCA_PROFILE") time_col <- data$conc$columns$time exclude_hl_col <- data$conc$columns$exclude_half.life include_hl_col <- data$conc$columns$include_half.life + # Apply each rule action for (i in seq_len(nrow(slopes))) { # Determine the time range for the points adjusted range <- strsplit(as.character(slopes$RANGE[i]), ":")[[1]] @@ -167,8 +168,10 @@ check_slope_rule_overlap <- function(existing, new, .keep = FALSE) { if (slopes$TYPE[i] == "Selection") { data$conc$data[[include_hl_col]][pnt_idx] <- TRUE - } else { + } else if (slopes$TYPE[i] == "Exclusion") { data$conc$data[[exclude_hl_col]][pnt_idx] <- TRUE + } else { + stop("Unknown TYPE in slopes: ", slopes$TYPE[i]) } data$conc$data$REASON[pnt_idx] <- paste0( @@ -181,17 +184,27 @@ check_slope_rule_overlap <- function(existing, new, .keep = FALSE) { data } -.update_plots_with_rules <- function(pknca_data, manual_slopes, plot_outputs) { +.update_plots_with_rules <- function(pknca_data, manual_slopes, plot_outputs, slopes_to_update = NULL) { print(".update_plots_with_rules") pknca_for_plots <- .update_pknca_with_rules(pknca_data, manual_slopes) - browser() + #browser() + + # If the user does not specify which plots to update, update all plots in the manual slopes table + if (is.null(slopes_to_update)) { + slopes_to_update <- manual_slopes %>% + select(any_of(c(group_vars(pknca_for_plots), "NCA_PROFILE"))) %>% + distinct() + print(names(plot_outputs)) + if (nrow(slopes_to_update) == 0) return(plot_outputs) + } + + # Get the intervals of the plots affected by the current rules pknca_for_plots$intervals <- inner_join( - manual_slopes %>% select(any_of(c(group_vars(pknca_for_plots), "NCA_PROFILE"))), + slopes_to_update, pknca_for_plots$intervals, by = c(group_vars(pknca_for_plots), "NCA_PROFILE") ) updated_plots <- suppressWarnings(get_halflife_plot(pknca_for_plots)) plot_outputs[names(plot_outputs) %in% names(updated_plots)] <- updated_plots plot_outputs - print(".update_plots_with_rules") } diff --git a/inst/shiny/functions/handle_plotly_click.R b/inst/shiny/functions/handle_plotly_click.R index f67a8b349..8499c6ed3 100644 --- a/inst/shiny/functions/handle_plotly_click.R +++ b/inst/shiny/functions/handle_plotly_click.R @@ -40,12 +40,12 @@ handle_plotly_click <- function(last_click_data, manual_slopes, click_data, pknc # If it is the same point, consider the point excluded if (pnt$idx == lstpnt$idx) { new_rule$TYPE <- "Exclusion" - new_rule$RANGE <- pnt$time + new_rule$RANGE <- paste0(pnt$time) new_rule$REASON <- "" # If it is in the same plot (interval), consider all points in the time range included } else if (all.equal(pnt$int, lstpnt$int)) { - new_rule$TYPE <- "Inclusion" + new_rule$TYPE <- "Selection" new_rule$RANGE <- paste0(sort(c(pnt$time, lstpnt$time)), collapse = ":") new_rule$REASON <- "" } else { @@ -57,9 +57,6 @@ handle_plotly_click <- function(last_click_data, manual_slopes, click_data, pknc )) } - # Update the concentration data to add manual half life adjustments - updated_pknca <- .update_pknca_with_rules(pknca_data, new_rule) - # # Update the specific plot (interval) affected # updated_pknca$intervals <- pnt$int # updated_plot <- suppressWarnings(get_halflife_plot(updated_pknca)) diff --git a/inst/shiny/modules/tab_nca/setup/manual_slopes_table.R b/inst/shiny/modules/tab_nca/setup/manual_slopes_table.R index 7a66bc372..1d37945a8 100644 --- a/inst/shiny/modules/tab_nca/setup/manual_slopes_table.R +++ b/inst/shiny/modules/tab_nca/setup/manual_slopes_table.R @@ -20,58 +20,46 @@ manual_slopes_table_ui <- function(id) { manual_slopes_table_server <- function( - id, mydata, slopes_pknca_groups + id, mydata ) { moduleServer(id, function(input, output, session) { ns <- session$ns - - # Reactive for Slope selector columns - slope_selector_columns <- reactive({ - req(slopes_pknca_groups()) - - c(names(slopes_pknca_groups()), "TYPE", "RANGE", "REASON") + slopes_pknca_groups <- reactive({ + req(mydata()) + mydata()$conc$data %>% + select(any_of(c(group_vars(mydata()), "NCA_PROFILE"))) }) #' Object for storing exclusion and selection data for lambda slope calculation - manual_slopes <- reactiveVal({ - data.frame( - TYPE = character(), - RANGE = character(), - REASON = character(), - stringsAsFactors = FALSE - ) - }) - + # TODO (Gerardo): Parameter selection is still affecting mydata() and re-creating the manual slopes! + manual_slopes <- reactiveVal({NULL}) observeEvent(mydata(), { - current_slopes <- manual_slopes() - - # Add missing dynamic columns with default values (e.g., NA_character_) - missing_cols <- setdiff(colnames(slopes_pknca_groups()), colnames(current_slopes)) - for (missing_col in missing_cols) { - current_slopes[[missing_col]] <- character() - } - - # Define the desired column order - ordered_cols <- c(colnames(slopes_pknca_groups()), "TYPE", "RANGE", "REASON") - current_slopes <- current_slopes[, ordered_cols, drop = FALSE] + #browser() + req(slopes_pknca_groups()) + ms_colnames <- c(colnames(slopes_pknca_groups()), c("TYPE", "RANGE", "REASON")) + initial_manual_slopes <- data.frame( + matrix(character(), ncol = length(ms_colnames), nrow = 0, dimnames = list(character(), ms_colnames)) + ) # Update the reactive Val - manual_slopes(current_slopes) + manual_slopes(initial_manual_slopes) }) #' Adds new row to the selection/exclusion datatable observeEvent(input$add_rule, { - + #browser() log_trace("{id}: adding manual slopes row") -browser() # Create the new row with both fixed and dynamic columns + first_group <- slopes_pknca_groups()[1, ] new_row <- cbind( - slopes_pknca_groups()[1, ], + first_group, data.frame( - TYPE = "Selection", - RANGE = "1:3", + TYPE = "Exclusion", + RANGE = paste0( + inner_join(slopes_pknca_groups()[1, ], mydata()$conc$data)[[mydata()$conc$columns$time]][2] + ), REASON = "" ) ) @@ -84,7 +72,7 @@ browser() #' Removes selected row observeEvent(input$remove_rule, { -browser() +#browser() log_trace("{id}: removing manual slopes row") selected <- getReactableState("manual_slopes", "selected") @@ -98,6 +86,7 @@ browser() #' Render manual slopes table refresh_reactable <- reactiveVal(1) output$manual_slopes <- renderReactable({ + req(manual_slopes()) log_trace("{id}: rendering slope edit data table") # Isolate to prevent unnecessary re-renders on every edit isolate({ @@ -141,14 +130,7 @@ browser() names(dynamic_columns) <- colnames(slopes_pknca_groups()) # Combine columns in the desired order - all_columns <- c( - dynamic_columns, - list( - TYPE = fixed_columns$TYPE, - RANGE = fixed_columns$RANGE, - REASON = fixed_columns$REASON - ) - ) + all_columns <- c(dynamic_columns, fixed_columns) # Render reactable reactable( @@ -167,29 +149,11 @@ browser() }) %>% shiny::bindEvent(refresh_reactable()) - #' #' Separate event handling updating displayed reactable upon every change (adding and removing - #' #' rows, plots selection, edits). This needs to be separate call, since simply re-rendering - #' #' the table would mean losing focus on text inputs when entering values. - #' observeEvent(manual_slopes(), { - #' req(manual_slopes()) - #' - #' reactable::updateReactable( - #' outputId = "manual_slopes", - #' data = manual_slopes() - #' ) - #' - #' update_plots <- function(pknca_data, pnew_rules) - #' }) - - #' For each of the columns in slope selector data frame, attach an event that will read - #' edits for that column made in the reactable. observe({ - req(slope_selector_columns(), manual_slopes()) - + req(manual_slopes()) # Dynamically attach observers for each column - purrr::walk(slope_selector_columns(), \(colname) { + purrr::walk(colnames(manual_slopes()), \(colname) { observeEvent(input[[paste0("edit_", colname)]], { - browser() edit <- input[[paste0("edit_", colname)]] edited_slopes <- manual_slopes() edited_slopes[edit$row, edit$column] <- edit$value diff --git a/inst/shiny/modules/tab_nca/setup/slope_selector.R b/inst/shiny/modules/tab_nca/setup/slope_selector.R index da686992c..ecc69cea5 100644 --- a/inst/shiny/modules/tab_nca/setup/slope_selector.R +++ b/inst/shiny/modules/tab_nca/setup/slope_selector.R @@ -119,7 +119,6 @@ slope_selector_ui <- function(id) { ) } - slope_selector_server <- function( # nolint id, pknca_data, manual_slopes_override ) { @@ -130,8 +129,6 @@ slope_selector_server <- function( # nolint slopes_pknca_data <- reactive({ req(pknca_data()) - - pknca_data <- pknca_data() pknca_data$intervals <- pknca_data$intervals %>% mutate( @@ -232,6 +229,7 @@ slope_selector_server <- function( # nolint # update page number display # output$page_number <- renderUI(num_pages) + print(names(plot_outputs())) # Render only the plots requested by the user output$slope_plots_ui <- renderUI({ shinyjs::enable(selector = ".btn-page") @@ -261,7 +259,7 @@ slope_selector_server <- function( # nolint #' Rendering slope plots based on nca data. observeEvent(pknca_data(), { req(pknca_data()) - + log_trace("{id}: Rendering plots") # Update the subject search input to make available choices for the user updateSelectInput( @@ -272,23 +270,25 @@ slope_selector_server <- function( # nolint ) }) - slopes_table <- manual_slopes_table_server("manual_slopes", pknca_data, - slopes_pknca_groups) - + # Creates an initial version of the manual slope adjustments table with pknca_data + # and handles the addition and deletion of rows through the UI + slopes_table <- manual_slopes_table_server("manual_slopes", pknca_data) manual_slopes <- slopes_table$manual_slopes refresh_reactable <- slopes_table$refresh_reactable - + # Define the click events for the point exclusion and selection in the slope plots last_click_data <- reactiveVal(NULL) observeEvent(event_data("plotly_click", priority = "event"), { log_trace("slope_selector: plotly click detected") - result <- handle_plotly_click(last_click_data, - manual_slopes, - event_data("plotly_click"), - pknca_data(), - plot_outputs()) + result <- handle_plotly_click( + last_click_data, + manual_slopes, + event_data("plotly_click"), + pknca_data(), + plot_outputs() + ) # Update reactive values in the observer last_click_data(result$last_click_data) plot_outputs(result$plot_outputs) @@ -301,26 +301,58 @@ slope_selector_server <- function( # nolint #' Separate event handling updating displayed reactable upon every change (adding and removing #' rows, plots selection, edits). This needs to be separate call, since simply re-rendering #' the table would mean losing focus on text inputs when entering values. + manual_slopes_version <- reactiveValues(lst = NULL, current = NULL) observeEvent(manual_slopes(), { req(manual_slopes()) print("observeEvent manual_slopes()") + + # Update reactable with rules reactable::updateReactable( outputId = "manual_slopes", data = manual_slopes() ) - if (nrow(manual_slopes())) { - plot_outputs( - .update_plots_with_rules(pknca_data(), manual_slopes(), plot_outputs()) + # Update slopes version + manual_slopes_version$lst <- manual_slopes_version$current + manual_slopes_version$current <- manual_slopes() + + # Update only the plots affected by the changed rules + req(plot_outputs()) + if (!is.null(manual_slopes_version$lst)) { + browser() + print(manual_slopes_version$current) + print(manual_slopes_version$lst) + rules_added <- anti_join( + manual_slopes_version$current, + manual_slopes_version$lst, + by = names(manual_slopes()) ) + + rules_removed <- anti_join( + manual_slopes_version$lst, + manual_slopes_version$current, + by = names(manual_slopes()) + ) + + slopes_to_update <- bind_rows(rules_added, rules_removed) %>% + select(any_of(c(group_vars(pknca_data()), "NCA_PROFILE"))) %>% + distinct() + } else { + # This will do the default udpate (all slopes in the manual slopes table) + slopes_to_update <- NULL } - }) + plot_outputs( + .update_plots_with_rules(pknca_data(), manual_slopes(), plot_outputs(), slopes_to_update) + ) + print("updated plots") + print(names(plot_outputs())) + }) #' If any settings are uploaded by the user, overwrite current rules observeEvent(manual_slopes_override(), { req(manual_slopes_override()) - + if (nrow(manual_slopes_override()) == 0) return(NULL) log_debug_list("Manual slopes override:", manual_slopes_override()) @@ -339,7 +371,7 @@ slope_selector_server <- function( # nolint all() if (!override_valid) { - + msg <- "Manual slopes not compatible with current data, leaving as default." log_warn(msg) showNotification(msg, type = "warning", duration = 5) @@ -351,8 +383,7 @@ slope_selector_server <- function( # nolint #' return reactive with slope exclusions data to be displayed in Results -> Exclusions tab list( - manual_slopes = manual_slopes, - slopes_groups = slopes_pknca_groups + manual_slopes = manual_slopes ) }) } From d83e95ce89537da8a012e1d35b8c321d338fcca1 Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Fri, 5 Sep 2025 15:52:54 +0200 Subject: [PATCH 016/113] enhance: keep in plot manual changes when user selects more analytes, profile, pcspec --- R/PKNCA.R | 27 +++++++--- inst/shiny/modules/tab_nca/setup.R | 35 +++--------- .../modules/tab_nca/setup/slope_selector.R | 54 ++++++++++++------- 3 files changed, 60 insertions(+), 56 deletions(-) diff --git a/R/PKNCA.R b/R/PKNCA.R index 49aaa0fba..a614d7cb3 100644 --- a/R/PKNCA.R +++ b/R/PKNCA.R @@ -118,7 +118,7 @@ PKNCA_create_data_object <- function(adnca_data) { # nolint: object_name_linter # Set default settings df_conc$is.excluded.hl <- FALSE df_conc$is.included.hl <- FALSE - df_conc$REASON <- NA + df_conc$REASON <- "" df_conc$exclude_half.life <- FALSE # Create PKNCA conc object @@ -224,7 +224,8 @@ PKNCA_update_data_object <- function( # nolint: object_name_linter selected_profile, selected_pcspec, params, - should_impute_c0 = TRUE + should_impute_c0 = TRUE, + hl_adj_rules = NULL ) { data <- adnca_data @@ -316,6 +317,11 @@ PKNCA_update_data_object <- function( # nolint: object_name_linter }, all_impute_methods, init = data$intervals) } + # Update concentration data to indicate points excluded / selected manually for half-life + if (!is.null(hl_adj_rules)) { + data <- .update_pknca_with_rules(data, hl_adj_rules) + } + data } @@ -672,19 +678,24 @@ select_minimal_grouping_cols <- function(df, strata_cols) { #' @importFrom PKNCA exclude #' @export PKNCA_hl_rules_exclusion <- function(res, rules) { # nolint - for (param in names(rules)) { - if (startsWith(param, "aucpext")) { - exc_fun <- exclude_nca_by_param( + if (startsWith(param, "AUCPE")) { + exc_fun <- PKNCA::exclude_nca_by_param( param, max_thr = rules[[param]], - affected_parameters = PKNCA::get.parameter.deps(gsub("pext", "inf", param)) + affected_parameters = translate_terms( + PKNCA::get.parameter.deps( + translate_terms(gsub("PE", "IF", param), "PPTESTCD", "PKNCA") + ) + ) ) } else { - exc_fun <- exclude_nca_by_param( + exc_fun <- PKNCA::exclude_nca_by_param( param, min_thr = rules[[param]], - affected_parameters = PKNCA::get.parameter.deps("half.life") + affected_parameters = translate_terms( + PKNCA::get.parameter.deps("half.life") + ) ) } res <- PKNCA::exclude(res, FUN = exc_fun) diff --git a/inst/shiny/modules/tab_nca/setup.R b/inst/shiny/modules/tab_nca/setup.R index fcf01678f..be117de67 100644 --- a/inst/shiny/modules/tab_nca/setup.R +++ b/inst/shiny/modules/tab_nca/setup.R @@ -80,7 +80,8 @@ setup_server <- function(id, data, adnca_data) { selected_profile = settings()$profile, selected_pcspec = settings()$pcspec, params = settings()$parameter_selection, - should_impute_c0 = settings()$data_imputation$impute_c0 + should_impute_c0 = settings()$data_imputation$impute_c0, + hl_adj_rules = slope_rules$manual_slopes() ) # Show bioavailability widget if it is possible to calculate @@ -116,35 +117,13 @@ setup_server <- function(id, data, adnca_data) { # Parameter unit changes option: Opens a modal message with a units table to edit units_table_server("units_table", processed_pknca_data) - # Create version for slope plots - # Only parameters required for the slope plots are set in intervals - # NCA dynamic changes/filters based on user selections - slopes_pknca_data <- reactive({ - req(adnca_data(), settings(), settings()$profile, - settings()$analyte, settings()$pcspec) - log_trace("Updating PKNCA::data object for slopes.") - - PKNCA_update_data_object( - adnca_data = adnca_data(), - selected_analytes = settings()$analyte, - selected_profile = settings()$profile, - selected_pcspec = settings()$pcspec, - params = "half.life", - # The next parameters should not matter for the calculations - # So reactivity should not be involved - auc_data = data.frame( - start_auc = NA_real_, - end_auc = NA_real_ - ), - method = "lin up/log down", - # TODO (Gerardo): This would better be FALSE, but start changes... - should_impute_c0 = TRUE - ) - }) - + # Collect all half life manual adjustments done in the `Slope Selector` section slope_rules <- slope_selector_server( "slope_selector", - slopes_pknca_data, + adnca_data, + settings()$analyte, + settings()$profile, + settings()$pcspec, manual_slopes_override ) diff --git a/inst/shiny/modules/tab_nca/setup/slope_selector.R b/inst/shiny/modules/tab_nca/setup/slope_selector.R index ecc69cea5..a46b056eb 100644 --- a/inst/shiny/modules/tab_nca/setup/slope_selector.R +++ b/inst/shiny/modules/tab_nca/setup/slope_selector.R @@ -120,21 +120,43 @@ slope_selector_ui <- function(id) { } slope_selector_server <- function( # nolint - id, pknca_data, manual_slopes_override + id, adnca_data, selected_analytes, selected_profile, selected_pcspec, manual_slopes_override ) { moduleServer(id, function(input, output, session) { log_trace("{id}: Attaching server") ns <- session$ns + + pknca_data <- reactiveVal(NULL) + plot_outputs <- reactiveVal(NULL) + observeEvent(list(adnca_data(), selected_analytes, selected_profile, selected_pcspec), { + req(adnca_data(), selected_analytes, selected_pcspec, selected_profile) + + browser() + + # Prepare a standard PKNCA object with only the basic adjustments for the half-life plots + # and only over the user selected analytes, profiles and specimens + pknca_data <- PKNCA_update_data_object( + adnca_data = adnca_data(), + selected_analytes = selected_analytes, + selected_profile = selected_profile, + selected_pcspec = selected_pcspec, + params = "half.life", + # The next parameters should not matter for the calculations + # So reactivity should not be involved + auc_data = data.frame( + start_auc = NA_real_, + end_auc = NA_real_ + ), + method = "lin up/log down", + # TODO (Gerardo): This would better be FALSE, but start changes... + should_impute_c0 = TRUE, + hl_adj_rules = manual_slopes() + ) + pknca_data(pknca_data) - slopes_pknca_data <- reactive({ - req(pknca_data()) - pknca_data <- pknca_data() - pknca_data$intervals <- pknca_data$intervals %>% - mutate( - half.life = TRUE - ) - pknca_data + # Prepare a default list with the half life plots + plot_outputs(get_halflife_plot(pknca_data())) }) # Get grouping columns for plots and tables @@ -159,13 +181,6 @@ slope_selector_server <- function( # nolint # select(any_of(relevant_group_cols)) }) - # Get all lambda z slope plots - plot_outputs <- reactiveVal(NULL) - observeEvent(slopes_pknca_data(), { # Maybe use directly pknca_data - plot_outputs <- get_halflife_plot(slopes_pknca_data()) - plot_outputs(plot_outputs) - }) - # HACK: workaround to avoid plotly_click not being registered warning session$userData$plotlyShinyEventIDs <- "plotly_click-A" current_page <- reactiveVal(1) @@ -198,7 +213,7 @@ slope_selector_server <- function( # nolint if (is.null(input$search_subject) || length(input$search_subject) == 0) { subject_col <- pknca_data()$conc$columns$subject unique( - slopes_pknca_data()$intervals %>% + pknca_data()$intervals %>% filter(half.life) %>% .[[subject_col]] ) @@ -229,7 +244,6 @@ slope_selector_server <- function( # nolint # update page number display # output$page_number <- renderUI(num_pages) - print(names(plot_outputs())) # Render only the plots requested by the user output$slope_plots_ui <- renderUI({ shinyjs::enable(selector = ".btn-page") @@ -327,13 +341,13 @@ slope_selector_server <- function( # nolint manual_slopes_version$lst, by = names(manual_slopes()) ) - + rules_removed <- anti_join( manual_slopes_version$lst, manual_slopes_version$current, by = names(manual_slopes()) ) - + slopes_to_update <- bind_rows(rules_added, rules_removed) %>% select(any_of(c(group_vars(pknca_data()), "NCA_PROFILE"))) %>% distinct() From e0dcd1213f0f37b13d004b5253e96db5ccd95c6f Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Fri, 5 Sep 2025 15:58:25 +0200 Subject: [PATCH 017/113] rm filter_slopes, apply instead manual_slopes in PKNCA_update_data_obj --- R/utils-slope_selector.R | 94 +++--------------------------------- inst/shiny/modules/tab_nca.R | 6 --- 2 files changed, 6 insertions(+), 94 deletions(-) diff --git a/R/utils-slope_selector.R b/R/utils-slope_selector.R index 6187898ab..3bcfb3599 100644 --- a/R/utils-slope_selector.R +++ b/R/utils-slope_selector.R @@ -1,87 +1,3 @@ -#' Filter dataset based on slope selections and exclusions -#' -#' This function filters main dataset based on provided slope selections an exclusions. -#' -#' @param data Data to filter. Must be `PKNCAdata` list, containing the `conc` element with -#' `PKNCAconc` list and appropriate data frame included under data. -#' @param slopes A data frame containing slope rules, including `TYPE`, `RANGE`, -#' and `REASON` columns. May also have grouping columns (expected to match slope_groups) -#' @param profiles List with available profiles for each `SUBJECT`. -#' @param slope_groups List with column names that define the groups. -#' @param check_reasons Whether to check if all selections have REASONS stated. If this is `TRUE` -#' and not all selections have a reason provided, an error will be thrown. -#' -#' @returns Original dataset, with `is.included.hl`, `is.excluded.hl` and `exclude_half.life` -#' columns modified in accordance to the provided slope filters. -#' @importFrom dplyr filter group_by mutate select all_of -#' @export -filter_slopes <- function(data, slopes, profiles, slope_groups, check_reasons = FALSE) { - print("filter_slopes") - if (is.null(data) || is.null(data$conc) || is.null(data$conc$data)) - stop("Please provide valid data.") - - # If there is no specification there is nothing to save # - if (is.null(slopes) || nrow(slopes) == 0) { - return(data) - } - - if (check_reasons) { - exclusions <- filter(slopes, TYPE == "Exclusion") - if (any(exclusions$REASON == "")) { - missing_reasons <- filter(exclusions, REASON == "") %>% - select(-REASON) %>% - apply(1, \(x) paste0(x, collapse = " ")) - - stop( - "No reason provided for the following exclusions:\n", - missing_reasons - ) - } - } - - # Reset to 0 all previous (if done) changes # - data$conc$data$is.included.hl <- FALSE - data$conc$data$is.excluded.hl <- FALSE - data$conc$data$exclude_half.life <- FALSE - data$conc$data$include_half.life <- NA - - # Eliminate all rows with conflicting or blank values - slopes <- slopes %>% - semi_join( - profiles, - by = slope_groups - ) %>% - filter(all(!is.na(sapply(RANGE, .eval_range)))) - - if (nrow(slopes) != 0) { - # Go over all rules and check if there is no overlap - if there is, edit accordingly - slopes <- purrr::reduce( - split(slopes, seq_len(nrow(slopes))), - .f = ~ check_slope_rule_overlap(.x, .y, slope_groups, .keep = TRUE) - ) - } - - # Update the exclusion/selection data for Lambda based on the current exc/sel table - data <- .update_pknca_with_rules(data, slopes, slope_groups) - - data$conc$data <- data$conc$data %>% - group_by(!!!syms(slope_groups)) %>% - mutate(exclude_half.life = { - if (any(is.included.hl)) { - is.excluded.hl | !is.included.hl - } else { - is.excluded.hl - } - }, - include_half.life = case_when( - is.included.hl ~ TRUE, - TRUE ~ NA - )) %>% - ungroup() - - data -} - #' Check overlap between existing and new slope rulesets #' #' Takes in tables with existing and incoming selections and exclusions, finds any overlap and @@ -146,7 +62,8 @@ check_slope_rule_overlap <- function(existing, new, .keep = FALSE) { #' and reasons in `data$conc$data`. .update_pknca_with_rules <- function(data, slopes) { print(".update_pknca_with_rules") - #browser() +browser() + # TODO (Gerardo): If we keep RANGE as a time, we don't need NCA_PROFILE slope_groups <- c(group_vars(data), "NCA_PROFILE") time_col <- data$conc$columns$time exclude_hl_col <- data$conc$columns$exclude_half.life @@ -155,15 +72,16 @@ check_slope_rule_overlap <- function(existing, new, .keep = FALSE) { # Apply each rule action for (i in seq_len(nrow(slopes))) { # Determine the time range for the points adjusted - range <- strsplit(as.character(slopes$RANGE[i]), ":")[[1]] - if (length(range) == 1) range <- rep(range, 2) + range <- strsplit(as.character(slopes$RANGE[i]), ":")[[1]] %>% + as.numeric() %>% + range() # Build the condition dynamically for group columns and time range pnt_idx <- which( Reduce(`&`, lapply(slope_groups, function(col) { data$conc$data[[col]] == slopes[[col]][i] })) & - between(data$conc$data[[time_col]], as.numeric(range[[1]]), as.numeric(range[[2]])) + between(data$conc$data[[time_col]], range[[1]], range[[2]]) ) if (slopes$TYPE[i] == "Selection") { diff --git a/inst/shiny/modules/tab_nca.R b/inst/shiny/modules/tab_nca.R index a077ee054..a5ec0034a 100644 --- a/inst/shiny/modules/tab_nca.R +++ b/inst/shiny/modules/tab_nca.R @@ -133,12 +133,6 @@ tab_nca_server <- function(id, adnca_data, grouping_vars) { #' Calculate results res <- withCallingHandlers({ processed_pknca_data %>% - filter_slopes( - slope_rules$manual_slopes(), - slope_rules$profiles_per_subject(), - slope_rules$slopes_groups(), - check_reasons = TRUE - ) %>% PKNCA_calculate_nca() %>% # Add bioavailability results if requested add_f_to_pknca_results(settings()$bioavailability) %>% From bed581c6bed82ac9a58d4988af07b72e7bb2980f Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Tue, 9 Sep 2025 16:04:20 +0200 Subject: [PATCH 018/113] fix: issue with MD due to result merge without start/end --- R/lambda_slope_plot.R | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/R/lambda_slope_plot.R b/R/lambda_slope_plot.R index 0e7a72f5d..658ff966d 100644 --- a/R/lambda_slope_plot.R +++ b/R/lambda_slope_plot.R @@ -258,12 +258,12 @@ lambda_slope_plot <- function( get_halflife_plot <- function(pknca_data, add_annotations = TRUE) { - +# browser() # Add a ROWID in the data to track the points later - pknca_data$conc$data$ROWID <- seq_len(nrow(pknca_data$conc$data)) + pknca_data$conc$data$ROWID <- seq_len(nrow(pknca_data$conc$data)) # Obtain the results - o_nca <- PKNCA::pk.nca(pknca_data) + o_nca <- suppressWarnings(PKNCA::pk.nca(pknca_data)) # Ensure result columns are present if (!"PPSTRES" %in% names(o_nca$result)) { @@ -284,6 +284,7 @@ get_halflife_plot <- function(pknca_data, add_annotations = TRUE) { plots <- vector("list", nrow(groups)) for (i in seq_len(nrow(groups))) { + group <- groups[i, ] group_vars <- setdiff(names(group), c("start", "end")) # Subset data for this group @@ -308,11 +309,11 @@ get_halflife_plot <- function(pknca_data, add_annotations = TRUE) { df <- df[df[[time_col]] >= group$start & df[[time_col]] <= group$end, ] df <- df[order(df[[time_col]]), ] df$IX <- seq_len(nrow(df)) - + # Prepare NCA object for this group group_nca <- o_nca group_nca$data$conc$data <- df - group_nca$result <- merge(group_nca$result, group[, group_vars, drop = FALSE]) + group_nca$result <- merge(group_nca$result, group) # Extract NCA results for annotation get_res <- function(testcd) group_nca$result$PPSTRES[group_nca$result$PPTESTCD == testcd] @@ -456,7 +457,8 @@ get_halflife_plot <- function(pknca_data, add_annotations = TRUE) { ) plots[[i]] <- plotly_build(p) - names(plots)[i] <- title + names(plots)[i] <- paste0(title, ", start: ", group$start, ", end: ", group$end) } + return(plots) } From c67b0a33c2ed4d85cc5f41f7fb543fc2f6b0efb2 Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Tue, 9 Sep 2025 16:05:39 +0200 Subject: [PATCH 019/113] optimize speed fun, sacrifice clarity (main problems: PKNCA funs) --- R/lambda_slope_plot.R | 737 +++++++++++++++++------------------------- 1 file changed, 296 insertions(+), 441 deletions(-) diff --git a/R/lambda_slope_plot.R b/R/lambda_slope_plot.R index 658ff966d..aa23339b1 100644 --- a/R/lambda_slope_plot.R +++ b/R/lambda_slope_plot.R @@ -1,464 +1,319 @@ -#' Generate a Lambda Slope Plot -#' -#' This function generates a lambda slope plot using pharmacokinetic data. It calculates relevant -#' lambda parameters and visualizes the data points used for lambda calculation, along with -#' a linear regression line and additional plot annotations. -#' -#' @param conc_pknca_df Data frame containing the concentration data -#' (default is `mydata$conc$data`). -#' @param row_values A list containing the values for the column_names used for filtering. -#' @param myres A PKNCAresults object containing the results of the NCA analysis -#' @param r2adj_threshold Numeric value representing the R-squared adjusted threshold for -#' determining the subtitle color (default is 0.7). -#' @param time_column The name of the time column in the concentration data frame. -#' (default is "AFRLT"). -#' -#' @return A plotly object representing the lambda slope plot. -#' -#' @details -#' The function performs the following steps: -#' \itemize{ -#' \item{Creates duplicates of the pre-dose and last doses of concentration data.} -#' \item{Filters and arranges the input data to obtain relevant lambda calculation information.} -#' \item{Identifies the data points used for lambda calculation.} -#' \item{Calculates the fitness, intercept, and time span of the half-life estimate.} -#' \item{ -#' Determines the subtitle color based on the R-squared adjusted value and half-life estimate. -#' } -#' \item{ -#' Generates a ggplot object with the relevant data points, -#' linear regression line, and annotations. -#' } -#' \item{Converts the ggplot object to a plotly object for interactive visualization.} -#' } -#' -#' @examples -#' \dontrun{ -#' # Example usage: -#' plot <- lambda_slope_plot(conc_pknca_df = mydata$conc$data, -#' row_values = list(USUBJID = "001", STUDYID = "A", DOSENO = 1), -#' myres = res_nca, -#' r2adj_threshold = 0.7) -#' plot -#' } -#' -#' @import dplyr -#' @import ggplot2 -#' @importFrom plotly ggplotly layout config style add_trace -#' @importFrom rlang set_names -#' @export -lambda_slope_plot <- function( - conc_pknca_df, - row_values, - myres = myres, - r2adj_threshold = 0.7, - time_column = "AFRLT" -) { - - column_names <- names(row_values) - grouping_names <- setdiff(column_names, "NCA_PROFILE") - #Create duplicates for predose and last dose points per profile - conc_pknca_df <- dose_profile_duplicates(conc_pknca_df, grouping_names) - #Obtain values for slopes selection - lambda_res <- myres$result %>% - filter(if_all(all_of(column_names), ~ .x == row_values[[deparse(substitute(.x))]])) %>% - arrange(across(all_of(column_names)), start_dose, desc(end_dose)) %>% - filter(!duplicated(paste0(!!!rlang::syms(column_names), PPTESTCD))) - - lambda_z_n_points <- as.numeric(lambda_res$PPSTRES[lambda_res$PPTESTCD == "lambda.z.n.points"]) - if (is.na(lambda_z_n_points)) lambda_z_n_points <- 0 - - grouping_values <- row_values[grouping_names] - - lambda_z_ix_rows <- conc_pknca_df %>% - ungroup() %>% - filter( - if_all(all_of(grouping_names), ~ .x == row_values[[deparse(substitute(.x))]]), - !exclude_half.life, - !!sym(time_column) >= sum( - subset( - lambda_res, - lambda_res$PPTESTCD == "lambda.z.time.first", - select = c("start", "PPSTRES") - ) - ) - ) %>% - arrange(IX) %>% - slice(0:lambda_z_n_points) - - #Obtain parameter values for subtitle - r2_value <- signif(as.numeric(lambda_res$PPSTRES[lambda_res$PPTESTCD == "r.squared"]), 3) - r2adj_value <- signif(as.numeric(lambda_res$PPSTRES[lambda_res$PPTESTCD == "adj.r.squared"]), 3) - half_life_value <- signif(as.numeric(lambda_res$PPSTRES[lambda_res$PPTESTCD == "half.life"]), 3) - time_span <- signif( - abs(dplyr::last(lambda_z_ix_rows[[time_column]]) - - dplyr::first(lambda_z_ix_rows[[time_column]])), 3 - ) - - subtitle_color <- ifelse( - r2adj_value < r2adj_threshold | half_life_value > (time_span / 2), - "red", - "black" - ) - subtitle_text <- paste0( - " R2adj: ", r2adj_value, - " HL \u03BBz = ", half_life_value, " ", - lambda_res$PPSTRESU[lambda_res$PPTESTCD == "half.life"], - " (T", lambda_z_ix_rows$IX[nrow(lambda_z_ix_rows)], " - T", - lambda_z_ix_rows$IX[1], ")/2 = ", time_span / 2, " ", - lambda_res$PPSTRESU[lambda_res$PPTESTCD == "half.life"] - ) - - #Create error text if Cmax used in calculation - cmax_error_text <- NULL - tmax_value <- lambda_res$PPSTRES[lambda_res$PPTESTCD == "tmax"] - lower_limit <- lambda_res$PPSTRES[lambda_res$PPTESTCD == "lambda.z.time.first"] - if (!is.na(tmax_value) && !is.na(lower_limit) && tmax_value >= lower_limit) { - subtitle_color <- "red" - cmax_error_text <- list( - text = "Cmax should not be included in lambda calculation", - font = list(size = 15, color = "red", family = "times"), - x = 1, - y = 1, - xref = "paper", - yref = "paper", - xanchor = "right", - yanchor = "top", - showarrow = FALSE - ) - } - - # Create the title - title_text <- paste( - paste0(column_names, ": ", sapply(column_names, function(col) row_values[[col]])), - collapse = ", " - ) - - #Filter the data set for subject profile - plot_data <- conc_pknca_df %>% - ungroup() %>% - filter( - if_all(all_of(grouping_names), ~ .x == grouping_values[[deparse(substitute(.x))]]) - ) %>% - arrange(IX) %>% - mutate( - IX_shape = ifelse(is.excluded.hl, "excluded", "included"), - IX_stroke = ifelse(is.excluded.hl, 4, 1), - IX_color = case_when( - is.excluded.hl ~ "excluded", - IX %in% lambda_z_ix_rows$IX ~ "hl.included", - TRUE ~ "hl.excluded" - ) - ) %>% - filter(AVAL > 0) %>% - as.data.frame() - - if (nrow(plot_data) == 0) { - warning("Not enough data for plotting. Returning empty plot.") - return(.plotly_empty_plot("No valid lambda calculations available")) - } - - #Create initial plot - ggplot_obj <- plot_data %>% - ggplot(aes(x = ARRLT, y = AVAL)) + - geom_line(color = "gray70", linetype = "solid", linewidth = 1) + - geom_smooth( - data = subset(plot_data, IX_color == "hl.included"), - method = "lm", - se = FALSE, - formula = y ~ x, - color = "green3", - linetype = "solid", - linewidth = 1 - ) + - geom_point( - data = plot_data, - aes(shape = IX_shape, color = IX_color, stroke = IX_stroke), - size = 5 - ) + - labs( - title = title_text, - y = paste0("Log10 Concentration (", conc_pknca_df $PCSTRESU[1], ")"), - x = paste0("Actual Time Post Dose (", conc_pknca_df $RRLTU[1], ")") - ) + - theme_bw() + - theme( - plot.title = element_text(hjust = 0.5, face = "bold", size = 15, family = "serif"), - legend.position = "none", - axis.text = element_text(size = 15), - axis.title.x = element_text(size = 15, family = "serif", margin = margin(t = 0)), - axis.title.y = element_text(size = 15, family = "serif", margin = margin(r = 10)), - panel.border = element_rect(colour = "gray35", fill = NA, linewidth = 1), - panel.grid.major = element_line(colour = "gray90"), - plot.margin = margin(t = 5, r = 5, b = 35, l = 5) - ) + - scale_shape_manual(values = c("included" = 16, "excluded" = 3)) + - scale_color_manual(values = c( - "hl.included" = "green4", "hl.excluded" = "black", "excluded" = "red3" - )) + - scale_y_log10() - - plotly_obj <- ggplotly(ggplot_obj) %>% - layout( - margin = list(t = 80), - annotations = list( - list( - text = subtitle_text, - showarrow = TRUE, - arrowcolor = "transparent", - xref = "paper", - yref = "paper", - xanchor = "right", - yanchor = "top", - font = list(size = 15, color = subtitle_color, family = "times"), - x = 1, - y = 1 - ), - cmax_error_text - ), - hoverlabel = list(font = list(family = "times", size = 20)) - ) %>% - config(mathjax = "cdn") - - num_traces <- length(plotly_obj$x$data) - - for (i in seq_len(num_traces)) { - plotly_obj <- plotly_obj %>% - style(hovertext = ~paste0("Data Point: ", IX), hoverinfo = "none", traces = i) - } - - customdata <- apply( - plot_data[, c(column_names, "IX"), drop = FALSE], - 1, - function(row) as.list(set_names(row, c(column_names, "IX"))) - ) - - # Add tracing for interactive plots - plotly_obj %>% - add_trace( - plot_data, - x = ~ARRLT, y = ~log10(AVAL), - customdata = customdata, - text = ~paste0( - "Data Point: ", IX, "\n", - "(", round(ARRLT, 1), " , ", signif(AVAL, 3), ")" - ), - type = "scatter", - mode = "markers", - name = "Data Points", - hoverinfo = "text", - marker = list(color = case_when( - plot_data$is.excluded.hl ~ "red", - plot_data$IX %in% lambda_z_ix_rows$IX ~ "green", - TRUE ~ "black" - ), size = 12, opacity = 0), # Make points semi-transparent - showlegend = FALSE - ) -} - get_halflife_plot <- function(pknca_data, add_annotations = TRUE) { -# browser() - # Add a ROWID in the data to track the points later pknca_data$conc$data$ROWID <- seq_len(nrow(pknca_data$conc$data)) - - # Obtain the results o_nca <- suppressWarnings(PKNCA::pk.nca(pknca_data)) - - # Ensure result columns are present if (!"PPSTRES" %in% names(o_nca$result)) { o_nca$result$PPSTRES <- o_nca$result$PPORRES if ("PPORRESU" %in% names(o_nca$result)) { o_nca$result$PPSTRESU <- o_nca$result$PPORRESU } } - - # Get grouping structure for lambda.z - groups <- PKNCA::getGroups(o_nca %>% dplyr::filter(PPTESTCD == "lambda.z")) %>% unique() + o_nca$result <- o_nca$result %>% + mutate( + PPORRES = ifelse( + PPTESTCD %in% c("lambda.z.time.first", "lambda.z.time.last", "tlast"), + PPORRES + start, + PPORRES + ), + PPSTRES = ifelse( + PPTESTCD %in% c("lambda.z.time.first", "lambda.z.time.last", "tlast"), + PPSTRES + start, + PPSTRES + ) + ) + groups <- o_nca$result %>% select(any_of(c(group_vars(o_nca), "start", "end", "PPTESTCD"))) %>% dplyr::filter(PPTESTCD == "lambda.z") %>% select(-PPTESTCD) %>% unique() - - plots <- vector("list", nrow(groups)) - - for (i in seq_len(nrow(groups))) { - - group <- groups[i, ] - group_vars <- setdiff(names(group), c("start", "end")) - # Subset data for this group - df <- merge(o_nca$data$conc$data, group[, group_vars, drop = FALSE]) - - # Column names - time_col <- o_nca$data$conc$columns$time - conc_col <- o_nca$data$conc$columns$concentration - timeu_col <- o_nca$data$conc$columns$timeu - concu_col <- o_nca$data$conc$columns$concu - exclude_hl_col <- o_nca$data$conc$columns$exclude_half.life - if (is.null(exclude_hl_col)) { - o_nca$data$conc$data[["exclude_half.life"]] <- FALSE - exclude_hl_col <- "exclude_half.life" - } - exclude_msg_col <- o_nca$data$conc$columns$exclude - if (is.null(exclude_msg_col)) { - exclude_msg_col <- "exclude" - } - - # Filter and order by time - df <- df[df[[time_col]] >= group$start & df[[time_col]] <= group$end, ] - df <- df[order(df[[time_col]]), ] - df$IX <- seq_len(nrow(df)) - - # Prepare NCA object for this group - group_nca <- o_nca - group_nca$data$conc$data <- df - group_nca$result <- merge(group_nca$result, group) - - # Extract NCA results for annotation - get_res <- function(testcd) group_nca$result$PPSTRES[group_nca$result$PPTESTCD == testcd] - get_unit <- function(testcd) group_nca$result$PPSTRESU[group_nca$result$PPTESTCD == testcd] - start <- unique(group_nca$result$start) # this has to have a better way - tlast <- get_res("tlast") - half_life <- get_res("half.life") - adj.r.squared <- get_res("adj.r.squared") - lz_time_first <- get_res("lambda.z.time.first") - lz_time_last <- get_res("lambda.z.time.last") - time_span <- lz_time_last - lz_time_first - span_ratio <- get_res("span.ratio") - exclude_calc_reason <- group_nca$result$exclude[group_nca$result$PPTESTCD == "half.life"] - - ######################################################### - # TODO (Gerardo): This complication in the code comes from a PKNCA issue - # Once solved in a new version this can be simplified - group_nca_for_fun <- group_nca - group_nca_for_fun$result <- group_nca_for_fun$result %>% - mutate( - PPORRES = ifelse( - PPTESTCD %in% c("lambda.z.time.first", "lambda.z.time.last", "tlast"), - PPORRES + start, - PPORRES - ), - PPSTRES = ifelse( - PPTESTCD %in% c("lambda.z.time.first", "lambda.z.time.last", "tlast"), - PPSTRES + start, - PPSTRES + n_groups <- nrow(groups) + plots <- vector("list", n_groups) + if (n_groups == 0) { + print(difftime(Sys.time() - time0)) + return(plots) + } + # Precompute column names and helper functions + time_col <- o_nca$data$conc$columns$time + conc_col <- o_nca$data$conc$columns$concentration + timeu_col <- o_nca$data$conc$columns$timeu + concu_col <- o_nca$data$conc$columns$concu + exclude_hl_col <- o_nca$data$conc$columns$exclude_half.life + if (is.null(exclude_hl_col)) { + o_nca$data$conc$data[["exclude_half.life"]] <- FALSE + exclude_hl_col <- "exclude_half.life" + } + + include_hl_col <- o_nca$data$conc$columns$exclude_half.life + if (is.null(include_hl_col)) { + o_nca$data$conc$data[["include_half.life"]] <- FALSE + exclude_hl_col <- "include_half.life" + } + + + # Helper functions for extracting results + get_res_fun <- function(result_df, testcd) result_df$PPSTRES[result_df$PPTESTCD == testcd] + get_unit_fun <- function(result_df, testcd) result_df$PPSTRESU[result_df$PPTESTCD == testcd] + # 1. Pre-split data by group (avoid repeated merges) + group_vars_all <- setdiff(names(groups), c("start", "end")) + # Create a key for each row in conc data and in groups + conc_data <- o_nca$data$conc$data + if (length(group_vars_all) > 0) { + conc_data$.__groupkey__ <- apply(conc_data[, group_vars_all, drop = FALSE], 1, function(row) paste(row, collapse = "|")) + groups$.__groupkey__ <- apply(groups[, group_vars_all, drop = FALSE], 1, function(row) paste(row, collapse = "|")) + } else { + conc_data$.__groupkey__ <- "__all__" + groups$.__groupkey__ <- "__all__" + } + # Pre-split conc_data by group key + conc_data_split <- split(conc_data, conc_data$.__groupkey__) + + + # 2. Build the first plot fully + i <- 1 + group <- groups[i, ] + group_vars <- group_vars_all + group_key <- group$.__groupkey__ + df <- conc_data_split[[group_key]] + df <- df[df[[time_col]] >= group$start & df[[time_col]] <= group$end, ] + df <- df[order(df[[time_col]]), ] + df$IX <- seq_len(nrow(df)) + group_nca <- o_nca + group_nca$data$conc$data <- df + group_nca$result <- merge(group_nca$result, group) + get_res <- function(testcd) get_res_fun(group_nca$result, testcd) + get_unit <- function(testcd) get_unit_fun(group_nca$result, testcd) + start <- unique(group_nca$result$start) + tlast <- get_res("tlast") + half_life <- get_res("half.life") + adj.r.squared <- get_res("adj.r.squared") + lz_time_first <- get_res("lambda.z.time.first") + lz_time_last <- get_res("lambda.z.time.last") + time_span <- lz_time_last - lz_time_first + span_ratio <- get_res("span.ratio") + exclude_calc_reason <- group_nca$result$exclude[group_nca$result$PPTESTCD == "half.life"] + + if (!is.na(half_life)) { + is_lz_used <- get_halflife_points2( + group_nca$data$conc$data, + include_hl_col, + time_col, + lz_time_first, + lz_time_last, + exclude_hl_col + ) + df_fit <- df[is_lz_used, ] + fit <- lm(as.formula(paste0("log10(", conc_col,") ~ ", time_col)), df_fit) + fit_line_data <- data.frame(x = c(lz_time_first + start, tlast + start)) + colnames(fit_line_data) <- time_col + fit_line_data$y <- predict(fit, fit_line_data) + } else { + is_lz_used <- rep(NA_real_, nrow(df)) + fit_line_data <- data.frame( + x = c(start, start), + y = c(0, 0) + ) + colnames(fit_line_data)[1] <- time_col + } + + plot_data <- df + # 4. Vectorize color, symbol, text assignment + plot_data$color <- ifelse(is.na(is_lz_used), "black", + ifelse(is_lz_used, "green", "red")) + plot_data$symbol <- ifelse(plot_data[[exclude_hl_col]], "x", "circle") + plot_data$text <- paste0( + "Data Point: ", plot_data[["IX"]], "\n", + "(", + plot_data[[time_col]], + ", ", + signif(plot_data[[conc_col]], 3), + ")" + ) + title <- paste0(paste0(group_vars, ": "), group[, group_vars, drop = FALSE], collapse = ", ") + xlab <- if (!is.null(timeu_col)) paste0(time_col, " (", unique(plot_data[[timeu_col]]), ")") else time_col + ylab <- if (!is.null(concu_col)) paste0(conc_col, " (", unique(plot_data[[concu_col]]), ")") else conc_col + subtitle_text <- paste0( + "R2adj = ", signif(adj.r.squared, 2), + "    ", + "span ratio = ", + signif(span_ratio, 2) + ) + if (is.na(half_life)) { + subtitle_text <- exclude_calc_reason + } + p <- plotly::plot_ly() %>% + plotly::add_lines( + data = fit_line_data, + x = ~get(time_col), + y = ~10^y, + line = list(color = "green", width = 2), + name = "Fit", + inherit = FALSE, + showlegend = TRUE + ) %>% + plotly::layout( + title = title, + xaxis = list( + title = xlab, + linecolor = "black", + gridcolor = "white", + zeroline = FALSE + ), + yaxis = list( + title = ylab, + type = "log", + tickformat = "f", + linecolor = "black", + gridcolor = "white", + zeroline = FALSE + ), + annotations = if (add_annotations) list( + text = subtitle_text, + showarrow = FALSE, + xref = "paper", + yref = "paper", + y = 1 + ) else NULL + ) %>% + plotly::add_trace( + data = plot_data, + x = ~plot_data[[time_col]], + y = ~plot_data[[conc_col]], + text = ~plot_data$text, + hoverinfo = "text", + showlegend = FALSE, + type = "scatter", + mode = "markers", + marker = list( + color = plot_data$color, + size = 15, + symbol = plot_data$symbol, + size = 20 + ), + customdata = apply( + plot_data[, c(group_vars, "ROWID", "IX"), drop = FALSE], + 1, + function(row) as.list(set_names(row, c(group_vars, "ROWID", "IX"))) + ) + ) + plots[[1]] <- plotly::plotly_build(p) + names(plots)[1] <- paste0(title, ", start: ", group$start, ", end: ", group$end) + # For the rest, update the plotly object + # Clone the first plot and update data/layout + p_i <- plots[[1]] + if (n_groups > 1) { + for (i in 2:n_groups) { + group <- groups[i, ] + group_vars <- group_vars_all + group_key <- group$.__groupkey__ + df <- conc_data_split[[group_key]] + df <- df[df[[time_col]] >= group$start & df[[time_col]] <= group$end, ] + df <- df[order(df[[time_col]]), ] + df$IX <- seq_len(nrow(df)) + group_nca <- o_nca + group_nca$data$conc$data <- df + group_nca$result <- merge(group_nca$result, group) + + start <- unique(group_nca$result$start) + tlast <- get_res("tlast") + half_life <- get_res("half.life") + adj.r.squared <- get_res("adj.r.squared") + lz_time_first <- get_res("lambda.z.time.first") + lz_time_last <- get_res("lambda.z.time.last") + time_span <- lz_time_last - lz_time_first + span_ratio <- get_res("span.ratio") + exclude_calc_reason <- group_nca$result$exclude[group_nca$result$PPTESTCD == "half.life"] + + if (!is.na(half_life)) { + is_lz_used <- get_halflife_points2( + group_nca$data$conc$data, + include_hl_col, + time_col, + lz_time_first, + lz_time_last, + exclude_hl_col + ) + df_fit <- df[is_lz_used, ] + fit <- lm(as.formula(paste0("log10(", conc_col,") ~ ", time_col)), df_fit) + fit_line_data <- data.frame(x = c(lz_time_first + start, tlast + start)) + colnames(fit_line_data) <- time_col + fit_line_data$y <- predict(fit, fit_line_data) + } else { + is_lz_used <- rep(NA_real_, nrow(df)) + fit_line_data <- data.frame( + x = c(start, start), + y = c(0, 0) ) + colnames(fit_line_data)[1] <- time_col + } + # Vectorize color, symbol, text assignment + plot_data <- df + plot_data$color <- ifelse(is.na(is_lz_used), "black", + ifelse(is_lz_used, "green", "red")) + plot_data$symbol <- ifelse(plot_data[[exclude_hl_col]], "x", "circle") + plot_data$text <- paste0( + "Data Point: ", plot_data[["IX"]], "\n", + "(", + plot_data[[time_col]], + ", ", + signif(plot_data[[conc_col]], 3), + ")" ) - if (!is.na(half_life)) { - is_lz_used <- PKNCA::get_halflife_points(group_nca_for_fun) - } else { - is_lz_used <- rep(NA_real_, nrow(df)) - } - ######################################################### - - # Compute the points to depict the lambda fit line (if there is) - if (!is.na(half_life)) { - df_fit <- df[is_lz_used, ] - fit <- lm(as.formula(paste0("log10(", conc_col,") ~ ", time_col)), df_fit) - fit_line_data <- data.frame(x = c(lz_time_first + start, tlast + start)) - colnames(fit_line_data) <- time_col - fit_line_data$y <- predict(fit, fit_line_data) - } else { - fit_line_data <- data.frame( - x = c(start, start), - y = c(0, 0) + title <- paste0(paste0(group_vars, ": "), group[, group_vars, drop = FALSE], collapse = ", ") + xlab <- if (!is.null(timeu_col)) paste0(time_col, " (", unique(plot_data[[timeu_col]]), ")") else time_col + ylab <- if (!is.null(concu_col)) paste0(conc_col, " (", unique(plot_data[[concu_col]]), ")") else conc_col + subtitle_text <- paste0( + "R2adj = ", signif(adj.r.squared, 2), + "    ", + "span ratio = ", + signif(span_ratio, 2) ) - colnames(fit_line_data)[1] <- time_col - } - - # Plot data - plot_data <- df - plot_data$color <- case_when( - is.na(is_lz_used) ~ "black", - !is.na(is_lz_used) & is_lz_used ~ "green", - !is.na(is_lz_used) & !is_lz_used ~ "red", - TRUE ~ "black" - ) - title <- paste0(paste0(group_vars, ": "), group[, group_vars, drop = FALSE], collapse = ", ") - xlab <- if (!is.null(timeu_col)) paste0(time_col, " (", unique(plot_data[[timeu_col]]), ")") else time_col - ylab <- if (!is.null(concu_col)) paste0(conc_col, " (", unique(plot_data[[concu_col]]), ")") else conc_col - subtitle_text <- paste0( - "R2adj = ", signif(adj.r.squared, 2), - "    ", - #"ln(2)/ \u03BBz = ", signif(half_life, 2), " ", get_unit("half.life"), - #" ", - #"(T", df$IX[which(df[[time_col]] == lz_time_first)], - #" - T", df$IX[which(df[[time_col]] == lz_time_last)], ")/(ln(2)/ \u03BBz) =", - "span ratio = ", - signif(span_ratio, 2) - ) - if (is.na(half_life)) { - subtitle_text <- exclude_calc_reason + if (is.na(half_life)) { + subtitle_text <- exclude_calc_reason + } + # Update traces + # 1: fit line, 2: scatter + p_i$x$data[[1]]$x <- fit_line_data[[time_col]] + p_i$x$data[[1]]$y <- 10^fit_line_data$y + p_i$x$data[[2]]$x <- plot_data[[time_col]] + p_i$x$data[[2]]$y <- plot_data[[conc_col]] + p_i$x$data[[2]]$marker$color <- plot_data$color + p_i$x$data[[2]]$marker$symbol <- plot_data$symbol + p_i$x$data[[2]]$text <- plot_data$text + p_i$x$data[[2]]$customdata <- apply( + plot_data[, c(group_vars, "ROWID", "IX"), drop = FALSE], + 1, + function(row) as.list(set_names(row, c(group_vars, "ROWID", "IX"))) + ) + # Update layout + p_i$x$layout$title <- title + p_i$x$layout$xaxis$title <- xlab + p_i$x$layout$yaxis$title <- ylab + if (add_annotations) { + p_i$x$layout$annotations[[1]]$text <- subtitle_text + } + plots[[i]] <- p_i + names(plots)[i] <- paste0(title, ", start: ", group$start, ", end: ", group$end) } + } + + return(plots) +} - # Build plotly object - p <- plotly::plot_ly() %>% - plotly::add_lines( - data = fit_line_data, - x = ~get(time_col), - y = ~10^y, - line = list(color = "green", width = 2), - name = "Fit", - inherit = FALSE, - showlegend = TRUE - ) %>% - plotly::layout( - title = title, - xaxis = list( - title = xlab, - linecolor = "black", - gridcolor = "white", - zeroline = FALSE - ), - yaxis = list( - title = ylab, - type = "log", - tickformat = "f", - linecolor = "black", - gridcolor = "white", - zeroline = FALSE - ), - annotations = if (add_annotations) list( - text = subtitle_text, - showarrow = FALSE, - xref = "paper", - yref = "paper", - y = 1 - ) else NULL - ) %>% - plotly::add_trace( - data = plot_data, - x = ~plot_data[[time_col]], - y = ~plot_data[[conc_col]], - text = ~paste0( - "Data Point: ", plot_data[["IX"]], "\n", - "(", - get(time_col), - ", ", - signif(get(conc_col), 3), - ")" - ), - hoverinfo = "text", - showlegend = FALSE, - type = "scatter", - mode = "markers", - marker = list( - color = plot_data$color, - size = 15, - symbol = ifelse(plot_data[[exclude_hl_col]], "x", "circle"), - size = 20 - ), - customdata = apply( - plot_data[, c(group_vars, "ROWID", "IX"), drop = FALSE], - 1, - function(row) as.list(set_names(row, c(group_vars, "ROWID", "IX"))) - ) - ) - plots[[i]] <- plotly_build(p) - names(plots)[i] <- paste0(title, ", start: ", group$start, ", end: ", group$end) +#' Custom fast version of get_halflife_points +#' @param data concentration data.frame +#' @param include_hl_col column name for include_half.life +#' @param time_col column name for time +#' @param lz_time_first numeric +#' @param lz_time_last numeric +#' @param exclude_hl_col column name for exclude_half.life +#' @return logical vector +get_halflife_points2 <- function(data, include_hl_col, time_col, lz_time_first, lz_time_last, exclude_hl_col) { + if (any(data[[include_hl_col]])) { + data[[include_hl_col]] + } else { + data[[time_col]] >= lz_time_first & data[[time_col]] <= lz_time_last & !data[[exclude_hl_col]] } - - return(plots) } + From 84cb3076374cb97fb07de4d4a941e61c44fe2878 Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Tue, 9 Sep 2025 18:59:29 +0200 Subject: [PATCH 020/113] fix: fun in prev change has already considered start for line drawing --- R/lambda_slope_plot.R | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/R/lambda_slope_plot.R b/R/lambda_slope_plot.R index aa23339b1..cf37b5210 100644 --- a/R/lambda_slope_plot.R +++ b/R/lambda_slope_plot.R @@ -9,6 +9,7 @@ get_halflife_plot <- function(pknca_data, add_annotations = TRUE) { } } o_nca$result <- o_nca$result %>% + unique() %>% mutate( PPORRES = ifelse( PPTESTCD %in% c("lambda.z.time.first", "lambda.z.time.last", "tlast"), @@ -104,7 +105,7 @@ get_halflife_plot <- function(pknca_data, add_annotations = TRUE) { ) df_fit <- df[is_lz_used, ] fit <- lm(as.formula(paste0("log10(", conc_col,") ~ ", time_col)), df_fit) - fit_line_data <- data.frame(x = c(lz_time_first + start, tlast + start)) + fit_line_data <- data.frame(x = c(lz_time_first, tlast)) colnames(fit_line_data) <- time_col fit_line_data$y <- predict(fit, fit_line_data) } else { @@ -223,7 +224,7 @@ get_halflife_plot <- function(pknca_data, add_annotations = TRUE) { time_span <- lz_time_last - lz_time_first span_ratio <- get_res("span.ratio") exclude_calc_reason <- group_nca$result$exclude[group_nca$result$PPTESTCD == "half.life"] - + if (!is.na(half_life)) { is_lz_used <- get_halflife_points2( group_nca$data$conc$data, @@ -235,7 +236,7 @@ get_halflife_plot <- function(pknca_data, add_annotations = TRUE) { ) df_fit <- df[is_lz_used, ] fit <- lm(as.formula(paste0("log10(", conc_col,") ~ ", time_col)), df_fit) - fit_line_data <- data.frame(x = c(lz_time_first + start, tlast + start)) + fit_line_data <- data.frame(x = c(lz_time_first, tlast)) colnames(fit_line_data) <- time_col fit_line_data$y <- predict(fit, fit_line_data) } else { From 0bee541713db6ca0e2a42c9a07f139ee0d46330e Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Tue, 9 Sep 2025 19:00:34 +0200 Subject: [PATCH 021/113] draft: start working in changing reactivity to processed_pknca_data obj --- R/utils-slope_selector.R | 4 +- inst/shiny/functions/handle_plotly_click.R | 4 +- inst/shiny/modules/tab_nca/setup.R | 6 +-- .../modules/tab_nca/setup/slope_selector.R | 47 +++++++++---------- 4 files changed, 27 insertions(+), 34 deletions(-) diff --git a/R/utils-slope_selector.R b/R/utils-slope_selector.R index 3bcfb3599..27a819e90 100644 --- a/R/utils-slope_selector.R +++ b/R/utils-slope_selector.R @@ -105,7 +105,7 @@ browser() .update_plots_with_rules <- function(pknca_data, manual_slopes, plot_outputs, slopes_to_update = NULL) { print(".update_plots_with_rules") pknca_for_plots <- .update_pknca_with_rules(pknca_data, manual_slopes) - #browser() + browser() # If the user does not specify which plots to update, update all plots in the manual slopes table if (is.null(slopes_to_update)) { @@ -123,6 +123,6 @@ browser() by = c(group_vars(pknca_for_plots), "NCA_PROFILE") ) updated_plots <- suppressWarnings(get_halflife_plot(pknca_for_plots)) - plot_outputs[names(plot_outputs) %in% names(updated_plots)] <- updated_plots + plot_outputs[names(updated_plots)] <- updated_plots plot_outputs } diff --git a/inst/shiny/functions/handle_plotly_click.R b/inst/shiny/functions/handle_plotly_click.R index 8499c6ed3..09e6d2d8e 100644 --- a/inst/shiny/functions/handle_plotly_click.R +++ b/inst/shiny/functions/handle_plotly_click.R @@ -34,7 +34,7 @@ handle_plotly_click <- function(last_click_data, manual_slopes, click_data, pknc excl_hl_col <- pknca_data$conc$columns$exclude_half.life incl_hl_col <- pknca_data$conc$columns$include_half.life time_col <- pknca_data$conc$columns$time - +browser() # Depending on the last click decide what half life adjustment rule to apply new_rule <- pnt$group # If it is the same point, consider the point excluded @@ -83,7 +83,7 @@ handle_plotly_click <- function(last_click_data, manual_slopes, click_data, pknc time <- as.numeric(click_data$x) row <- pknca_data$conc$data[idx, ] int <- pknca_data$intervals %>% - merge(row, by = intersect(names(.), names(row))) %>% + merge(row, by = c(group_vars(pknca_data), "NCA_PROFILE")) %>% filter(start <= row[[pknca_data$conc$columns$time]] & end >= row[[pknca_data$conc$columns$time]]) %>% select(any_of(names(pknca_data$intervals))) diff --git a/inst/shiny/modules/tab_nca/setup.R b/inst/shiny/modules/tab_nca/setup.R index be117de67..f4d5ce087 100644 --- a/inst/shiny/modules/tab_nca/setup.R +++ b/inst/shiny/modules/tab_nca/setup.R @@ -118,12 +118,10 @@ setup_server <- function(id, data, adnca_data) { units_table_server("units_table", processed_pknca_data) # Collect all half life manual adjustments done in the `Slope Selector` section + # and controls the half life plots that are displayed slope_rules <- slope_selector_server( "slope_selector", - adnca_data, - settings()$analyte, - settings()$profile, - settings()$pcspec, + processed_pknca_data, manual_slopes_override ) diff --git a/inst/shiny/modules/tab_nca/setup/slope_selector.R b/inst/shiny/modules/tab_nca/setup/slope_selector.R index a46b056eb..fa177db71 100644 --- a/inst/shiny/modules/tab_nca/setup/slope_selector.R +++ b/inst/shiny/modules/tab_nca/setup/slope_selector.R @@ -120,7 +120,7 @@ slope_selector_ui <- function(id) { } slope_selector_server <- function( # nolint - id, adnca_data, selected_analytes, selected_profile, selected_pcspec, manual_slopes_override + id, processed_pknca_data, manual_slopes_override ) { moduleServer(id, function(input, output, session) { log_trace("{id}: Attaching server") @@ -129,34 +129,29 @@ slope_selector_server <- function( # nolint pknca_data <- reactiveVal(NULL) plot_outputs <- reactiveVal(NULL) - observeEvent(list(adnca_data(), selected_analytes, selected_profile, selected_pcspec), { - req(adnca_data(), selected_analytes, selected_pcspec, selected_profile) - - browser() - + observeEvent(list(processed_pknca_data()), { + req(processed_pknca_data()) +browser() # Prepare a standard PKNCA object with only the basic adjustments for the half-life plots # and only over the user selected analytes, profiles and specimens - pknca_data <- PKNCA_update_data_object( - adnca_data = adnca_data(), - selected_analytes = selected_analytes, - selected_profile = selected_profile, - selected_pcspec = selected_pcspec, - params = "half.life", - # The next parameters should not matter for the calculations - # So reactivity should not be involved - auc_data = data.frame( - start_auc = NA_real_, - end_auc = NA_real_ - ), - method = "lin up/log down", - # TODO (Gerardo): This would better be FALSE, but start changes... - should_impute_c0 = TRUE, - hl_adj_rules = manual_slopes() - ) - pknca_data(pknca_data) + new_pknca_data <- processed_pknca_data() + new_pknca_data$intervals <- new_pknca_data$intervals %>% + filter(type_interval == "main", half.life) %>% + unique() + + if (!is.null(new_pknca_data$conc$data) && is.null(pknca_data()$conc$data)) { + # Prepare a default list with the half life plots + plot_outputs(get_halflife_plot(new_pknca_data)) + } else if (!isTRUE(all.equal(new_pknca_data$conc$data, pknca_data()$conc$data))) { + browser() + .update_plots_with_rules(new_pknca_data, NULL, plot_outputs) + } else if (!isTRUE(all.equal(pknca_data$intervals, pknca_data()$intervals))) { + browser() + new_intervals <- anti_join(pknca_data$intervals, pknca_data()$intervals) + } - # Prepare a default list with the half life plots - plot_outputs(get_halflife_plot(pknca_data())) + # Update the object + pknca_data(new_pknca_data) }) # Get grouping columns for plots and tables From 882d4b95bd860261550396d2075c99cc4f72f4e1 Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Wed, 10 Sep 2025 08:06:03 +0200 Subject: [PATCH 022/113] handle new rules in plot update based on processed_pknca_data --- .../modules/tab_nca/setup/slope_selector.R | 76 ++++++++++++++++--- 1 file changed, 67 insertions(+), 9 deletions(-) diff --git a/inst/shiny/modules/tab_nca/setup/slope_selector.R b/inst/shiny/modules/tab_nca/setup/slope_selector.R index fa177db71..88f984dc7 100644 --- a/inst/shiny/modules/tab_nca/setup/slope_selector.R +++ b/inst/shiny/modules/tab_nca/setup/slope_selector.R @@ -131,25 +131,83 @@ slope_selector_server <- function( # nolint plot_outputs <- reactiveVal(NULL) observeEvent(list(processed_pknca_data()), { req(processed_pknca_data()) -browser() + # Prepare a standard PKNCA object with only the basic adjustments for the half-life plots # and only over the user selected analytes, profiles and specimens new_pknca_data <- processed_pknca_data() new_pknca_data$intervals <- new_pknca_data$intervals %>% filter(type_interval == "main", half.life) %>% unique() + excl_hl_col <- new_pknca_data$conc$columns$exclude_half.life + incl_hl_col <- new_pknca_data$conc$columns$include_half.life + is_new_data <- !is.null(new_pknca_data$conc$data) && is.null(pknca_data()$conc$data) - if (!is.null(new_pknca_data$conc$data) && is.null(pknca_data()$conc$data)) { + if (is_new_data) { # Prepare a default list with the half life plots - plot_outputs(get_halflife_plot(new_pknca_data)) - } else if (!isTRUE(all.equal(new_pknca_data$conc$data, pknca_data()$conc$data))) { - browser() - .update_plots_with_rules(new_pknca_data, NULL, plot_outputs) - } else if (!isTRUE(all.equal(pknca_data$intervals, pknca_data()$intervals))) { browser() - new_intervals <- anti_join(pknca_data$intervals, pknca_data()$intervals) + plot_outputs(get_halflife_plot(new_pknca_data)) + } else { + is_change_in_conc_data <- !isTRUE( + all.equal( + dplyr::select(new_pknca_data$conc$data, -any_of(c(excl_hl_col, incl_hl_col))), + dplyr::select(pknca_data()$conc$data, -any_of(c(excl_hl_col, incl_hl_col))) + ) + ) + is_change_in_hl_adj <- !isTRUE( + all.equal( + dplyr::select(new_pknca_data$conc$data, any_of(c(excl_hl_col, incl_hl_col))), + dplyr::select(pknca_data()$conc$data, any_of(c(excl_hl_col, incl_hl_col))) + ) + ) + is_change_in_selected_intervals <- !isTRUE( + all.equal(new_pknca_data$intervals, pknca_data()$intervals) + ) + + if (is_change_in_conc_data) { + plot_outputs(get_halflife_plot(new_pknca_data)) + } else if (is_change_in_hl_adj) { + browser() + affected_groups <- anti_join( + dplyr::select(new_pknca_data$conc$data, any_of(c(group_vars(new_pknca_data()), "NCA_PROFILE", excl_hl_col, incl_hl_col))), + dplyr::select(pknca_data()$conc$data, any_of(c(group_vars(pknca_data()), "NCA_PROFILE", excl_hl_col, incl_hl_col))), + by = c(group_vars(new_pknca_data()), "NCA_PROFILE") + ) %>% + select(any_of(c(group_vars(new_pknca_data()), "NCA_PROFILE"))) %>% + distinct() + .update_plots_with_rules(new_pknca_data, data.frame(), plot_outputs, affected_groups) + } else if (is_change_in_selected_intervals) { + browser() + new_intervals <- anti_join(new_pknca_data$intervals, pknca_data()$intervals) + rm_intervals <- anti_join(pknca_data()$intervals, new_pknca_data$intervals) + if (nrow(new_intervals) > 0) { + affected_groups <- new_intervals %>% + select(any_of(c(group_vars(new_pknca_data), "NCA_PROFILE"))) %>% + distinct() + new_plots <- .update_plots_with_rules(new_pknca_data, data.frame(), plot_outputs(), affected_groups) + plot_outputs(new_plots) + } + if (nrow(rm_intervals) > 0) { + rm_plot_names <- rm_intervals %>% + select(any_of(c(group_vars(new_pknca_data), "start", "end"))) %>% + # Create a column that is just a pasted character with the name and value of each column in the row + distinct() %>% + mutate(across(everything(), as.character)) %>% + # Create a column that is just a pasted character with the name and value of each column in the row + mutate(id = purrr::pmap_chr( + ., + function(...) { + vals <- list(...) + paste0(names(vals), ": ", vals, collapse = ", ") + } + )) %>% + pull(id) + + remaining_plots <- plot_outputs()[!names(plot_outputs()) %in% rm_plot_names] + plot_outputs(remaining_plots) + } + } } - + # Update the object pknca_data(new_pknca_data) }) From f8fd6ad0835f02a4a0c0f170fcd8ac6fbe02c7c2 Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Wed, 10 Sep 2025 15:31:29 +0200 Subject: [PATCH 023/113] refactor: create new .update_pknca_with_rules --- R/utils-slope_selector.R | 244 ++++++++++++++++++++++----------------- 1 file changed, 138 insertions(+), 106 deletions(-) diff --git a/R/utils-slope_selector.R b/R/utils-slope_selector.R index 27a819e90..fba217ac2 100644 --- a/R/utils-slope_selector.R +++ b/R/utils-slope_selector.R @@ -14,115 +14,147 @@ check_slope_rule_overlap <- function(existing, new, .keep = FALSE) { print("check_slope_rule_overlap") slope_groups <- setdiff(names(new), c("TYPE", "RANGE", "REASON")) - - # check if any rule already exists for specific subject and profile # - existing_index <- which( - existing$TYPE == new$TYPE & - Reduce(`&`, lapply(slope_groups, function(col) { - existing[[col]] == new[[col]] - })) - ) - - if (length(existing_index) != 1) { - if (length(existing_index) > 1) - warning("More than one range for single subject, profile and rule type detected.") - return(rbind(existing, new)) - } - - existing_range <- .eval_range(existing$RANGE[existing_index]) - new_range <- .eval_range(new$RANGE) - - is_inter <- length(intersect(existing_range, new_range)) != 0 - is_diff <- length(setdiff(new_range, existing_range)) != 0 - - if (is_diff || .keep) { - existing$RANGE[existing_index] <- unique(c(existing_range, new_range)) %>% - .compress_range() - - } else if (is_inter) { - existing$RANGE[existing_index] <- setdiff(existing_range, new_range) %>% - .compress_range() - } - - dplyr::filter(existing, !is.na(RANGE)) -} - -#' Apply Slope Rules to Update Data -#' -#' This function iterates over the given slopes and updates the `data$conc$data` object -#' by setting inclusion or exclusion flags based on the slope conditions. -#' -#' @param data A list containing concentration data (`data$conc$data`) with columns that -#' need to be updated based on the slope rules. -#' @param slopes A data frame containing slope rules, including `TYPE`, `RANGE`, -#' and `REASON` columns. May also have grouping columns (expected to match slope_groups) -#' @param slope_groups A character vector specifying the group columns used for filtering. -#' -#' @returns description The modified `data` object with updated inclusion/exclusion flags -#' and reasons in `data$conc$data`. -.update_pknca_with_rules <- function(data, slopes) { - print(".update_pknca_with_rules") -browser() - # TODO (Gerardo): If we keep RANGE as a time, we don't need NCA_PROFILE - slope_groups <- c(group_vars(data), "NCA_PROFILE") - time_col <- data$conc$columns$time - exclude_hl_col <- data$conc$columns$exclude_half.life - include_hl_col <- data$conc$columns$include_half.life - - # Apply each rule action - for (i in seq_len(nrow(slopes))) { - # Determine the time range for the points adjusted - range <- strsplit(as.character(slopes$RANGE[i]), ":")[[1]] %>% - as.numeric() %>% - range() - - # Build the condition dynamically for group columns and time range - pnt_idx <- which( - Reduce(`&`, lapply(slope_groups, function(col) { - data$conc$data[[col]] == slopes[[col]][i] - })) & - between(data$conc$data[[time_col]], range[[1]], range[[2]]) + #' Check overlap between existing and new slope rulesets + #' + #' Takes in tables with existing and incoming selections and exclusions, finds any overlap and + #' differences, edits the ruleset table accordingly. + #' + #' @param existing Data frame with existing selections and exclusions. + #' @param new Data frame with new rule to be added or removed. + #' @param slope_groups List with column names that define the groups. + #' @param .keep Whether to force keep fully overlapping rulesets. If FALSE, it will be assumed + #' that the user wants to remove rule if new range already exists in the dataset. + #' If TRUE, in that case full range will be kept. + #' @returns Data frame with full ruleset, adjusted for new rules. + #' @export + check_slope_rule_overlap <- function(existing, new, .keep = FALSE) { + print("check_slope_rule_overlap") + slope_groups <- setdiff(names(new), c("TYPE", "RANGE", "REASON")) + + # check if any rule already exists for specific subject and profile # + existing_index <- which( + existing$TYPE == new$TYPE & + Reduce(`&`, lapply(slope_groups, function(col) { + existing[[col]] == new[[col]] + })) ) - - if (slopes$TYPE[i] == "Selection") { - data$conc$data[[include_hl_col]][pnt_idx] <- TRUE - } else if (slopes$TYPE[i] == "Exclusion") { - data$conc$data[[exclude_hl_col]][pnt_idx] <- TRUE - } else { - stop("Unknown TYPE in slopes: ", slopes$TYPE[i]) + + if (length(existing_index) != 1) { + if (length(existing_index) > 1) + warning("More than one range for single subject, profile and rule type detected.") + return(rbind(existing, new)) } - - data$conc$data$REASON[pnt_idx] <- paste0( - data$conc$data$REASON[pnt_idx], - rep(slopes$REASON[i], length(pnt_idx)) - ) + + existing_range <- .eval_range(existing$RANGE[existing_index]) + new_range <- .eval_range(new$RANGE) + + is_inter <- length(intersect(existing_range, new_range)) != 0 + is_diff <- length(setdiff(new_range, existing_range)) != 0 + + if (is_diff || .keep) { + existing$RANGE[existing_index] <- unique(c(existing_range, new_range)) %>% + .compress_range() + + } else if (is_inter) { + existing$RANGE[existing_index] <- setdiff(existing_range, new_range) %>% + .compress_range() + } + + dplyr::filter(existing, !is.na(RANGE)) + } + + #' Apply Slope Rules to Update Data + #' + #' This function iterates over the given slopes and updates the `data$conc$data` object + #' by setting inclusion or exclusion flags based on the slope conditions. + #' + #' @param data A list containing concentration data (`data$conc$data`) with columns that + #' need to be updated based on the slope rules. + #' @param slopes A data frame containing slope rules, including `TYPE`, `RANGE`, + #' and `REASON` columns. May also have grouping columns (expected to match slope_groups) + #' @param slope_groups A character vector specifying the group columns used for filtering. + #' + #' @returns description The modified `data` object with updated inclusion/exclusion flags + #' and reasons in `data$conc$data`. + .update_pknca_with_rules <- function(data, slopes) { print(".update_pknca_with_rules") + + # TODO (Gerardo): If we keep RANGE as a time, we don't need NCA_PROFILE + slope_groups <- c(group_vars(data), "NCA_PROFILE") + time_col <- data$conc$columns$time + exclude_hl_col <- data$conc$columns$exclude_half.life + include_hl_col <- data$conc$columns$include_half.life + + # Apply each rule action + for (i in seq_len(nrow(slopes))) { + # Determine the time range for the points adjusted + range <- strsplit(as.character(slopes$RANGE[i]), ":")[[1]] %>% + as.numeric() %>% + range() + + # Build the condition dynamically for group columns and time range + pnt_idx <- which( + Reduce(`&`, lapply(slope_groups, function(col) { + data$conc$data[[col]] == slopes[[col]][i] + })) & + between(data$conc$data[[time_col]], range[[1]], range[[2]]) + ) + + if (slopes$TYPE[i] == "Selection") { + data$conc$data[[include_hl_col]][pnt_idx] <- TRUE + } else if (slopes$TYPE[i] == "Exclusion") { + data$conc$data[[exclude_hl_col]][pnt_idx] <- TRUE + } else { + stop("Unknown TYPE in slopes: ", slopes$TYPE[i]) + } + + data$conc$data$REASON[pnt_idx] <- paste0( + data$conc$data$REASON[pnt_idx], + rep(slopes$REASON[i], length(pnt_idx)) + ) + print(".update_pknca_with_rules") + } + + data } - - data -} - -.update_plots_with_rules <- function(pknca_data, manual_slopes, plot_outputs, slopes_to_update = NULL) { - print(".update_plots_with_rules") - pknca_for_plots <- .update_pknca_with_rules(pknca_data, manual_slopes) - browser() - - # If the user does not specify which plots to update, update all plots in the manual slopes table - if (is.null(slopes_to_update)) { - slopes_to_update <- manual_slopes %>% - select(any_of(c(group_vars(pknca_for_plots), "NCA_PROFILE"))) %>% - distinct() - print(names(plot_outputs)) + + # Refactored: .update_plots_with_rules now uses .update_pknca_with_rules and delegates to .update_plots_with_pknca + .update_plots_with_rules <- function(pknca_data, manual_slopes, plot_outputs, slopes_to_update = NULL) { + print(".update_plots_with_rules (refactored)") + # Update the PKNCA object with manual slopes + pknca_for_plots <- .update_pknca_with_rules(pknca_data, manual_slopes) + # If the user does not specify which plots to update (NULL), update all plots in the manual slopes table + if (is.null(slopes_to_update)) { + slopes_to_update <- manual_slopes %>% + select(any_of(c(group_vars(pknca_for_plots), "NCA_PROFILE"))) %>% + distinct() + print(names(plot_outputs)) + } + # If the user specify that there are no plots to update (empty data.frame), then do not update if (nrow(slopes_to_update) == 0) return(plot_outputs) + # Delegate to .update_plots_with_pknca for actual plot updating + .update_plots_with_pknca(pknca_for_plots, plot_outputs, intervals_to_update = slopes_to_update) + } + + .update_plots_with_pknca <- function(pknca_data, plot_outputs, intervals_to_update = NULL) { + print(".update_plots_with_pknca") + # If the user does not specify which plots to update (NULL), update all plots in the manual slopes table + if (is.null(intervals_to_update)) { + intervals_to_update <- pknca_data$intervals %>% + select(any_of(c(group_vars(pknca_data), "NCA_PROFILE"))) %>% + distinct() + print(names(plot_outputs)) + } + # If the user specify that there are no plots to update (empty data.frame), then do not update + if (nrow(intervals_to_update) == 0) return(plot_outputs) + + # Get the intervals of the plots affected by the current rules + pknca_data$intervals <- inner_join( + intervals_to_update, + pknca_data$intervals, + by = c(group_vars(pknca_data), "NCA_PROFILE") + ) + updated_plots <- suppressWarnings(get_halflife_plot(pknca_data)) + plot_outputs[names(updated_plots)] <- updated_plots + plot_outputs } - - # Get the intervals of the plots affected by the current rules - pknca_for_plots$intervals <- inner_join( - slopes_to_update, - pknca_for_plots$intervals, - by = c(group_vars(pknca_for_plots), "NCA_PROFILE") - ) - updated_plots <- suppressWarnings(get_halflife_plot(pknca_for_plots)) - plot_outputs[names(updated_plots)] <- updated_plots - plot_outputs -} From 847032de88378a8719cf7eabb2441686b8f26466 Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Wed, 10 Sep 2025 15:31:50 +0200 Subject: [PATCH 024/113] disconsider is.excluded.hl, is.included.hl --- 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 06f45f632..088458db6 100644 --- a/R/pivot_wider_pknca_results.R +++ b/R/pivot_wider_pknca_results.R @@ -53,7 +53,7 @@ pivot_wider_pknca_results <- function(myres) { group_by(!!!syms(conc_groups), DOSNOA) %>% # Derive LAMZMTD: was lambda.z manually customized? mutate(LAMZMTD = ifelse( - any(is.excluded.hl) | any(is.included.hl), "Manual", "Best slope" + any(exclude_half.life) | any(include_half.life), "Manual", "Best slope" )) %>% filter(!exclude_half.life | is.na(LAMZLL) | is.na(LAMZNPT)) %>% filter(!!sym(time_col) >= (LAMZLL + start) | is.na(LAMZLL)) %>% From 921a7a1628155884bba2ccd9039880c139d11728 Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Wed, 10 Sep 2025 15:35:21 +0200 Subject: [PATCH 025/113] fix: allow all plots to keep interactivity (get_halflife_plot) --- R/lambda_slope_plot.R | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/R/lambda_slope_plot.R b/R/lambda_slope_plot.R index cf37b5210..45e0c78fb 100644 --- a/R/lambda_slope_plot.R +++ b/R/lambda_slope_plot.R @@ -285,7 +285,7 @@ get_halflife_plot <- function(pknca_data, add_annotations = TRUE) { plot_data[, c(group_vars, "ROWID", "IX"), drop = FALSE], 1, function(row) as.list(set_names(row, c(group_vars, "ROWID", "IX"))) - ) + ) %>% unname() # Update layout p_i$x$layout$title <- title p_i$x$layout$xaxis$title <- xlab @@ -293,6 +293,7 @@ get_halflife_plot <- function(pknca_data, add_annotations = TRUE) { if (add_annotations) { p_i$x$layout$annotations[[1]]$text <- subtitle_text } + plots[[i]] <- p_i names(plots)[i] <- paste0(title, ", start: ", group$start, ", end: ", group$end) } From 1444a06a1c4f39d2bcd87d0da1016458e157b33b Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Thu, 11 Sep 2025 05:31:34 +0200 Subject: [PATCH 026/113] fix: issues with efficient render --- R/utils-slope_selector.R | 16 ---- .../tab_nca/setup/manual_slopes_table.R | 1 + .../modules/tab_nca/setup/slope_selector.R | 85 ++++++++++--------- 3 files changed, 44 insertions(+), 58 deletions(-) diff --git a/R/utils-slope_selector.R b/R/utils-slope_selector.R index fba217ac2..93e235172 100644 --- a/R/utils-slope_selector.R +++ b/R/utils-slope_selector.R @@ -1,19 +1,3 @@ -#' Check overlap between existing and new slope rulesets -#' -#' Takes in tables with existing and incoming selections and exclusions, finds any overlap and -#' differences, edits the ruleset table accordingly. -#' -#' @param existing Data frame with existing selections and exclusions. -#' @param new Data frame with new rule to be added or removed. -#' @param slope_groups List with column names that define the groups. -#' @param .keep Whether to force keep fully overlapping rulesets. If FALSE, it will be assumed -#' that the user wants to remove rule if new range already exists in the dataset. -#' If TRUE, in that case full range will be kept. -#' @returns Data frame with full ruleset, adjusted for new rules. -#' @export -check_slope_rule_overlap <- function(existing, new, .keep = FALSE) { - print("check_slope_rule_overlap") - slope_groups <- setdiff(names(new), c("TYPE", "RANGE", "REASON")) #' Check overlap between existing and new slope rulesets #' #' Takes in tables with existing and incoming selections and exclusions, finds any overlap and diff --git a/inst/shiny/modules/tab_nca/setup/manual_slopes_table.R b/inst/shiny/modules/tab_nca/setup/manual_slopes_table.R index 1d37945a8..c73699e74 100644 --- a/inst/shiny/modules/tab_nca/setup/manual_slopes_table.R +++ b/inst/shiny/modules/tab_nca/setup/manual_slopes_table.R @@ -36,6 +36,7 @@ manual_slopes_table_server <- function( manual_slopes <- reactiveVal({NULL}) observeEvent(mydata(), { #browser() + req(is.null(manual_slopes())) req(slopes_pknca_groups()) ms_colnames <- c(colnames(slopes_pknca_groups()), c("TYPE", "RANGE", "REASON")) initial_manual_slopes <- data.frame( diff --git a/inst/shiny/modules/tab_nca/setup/slope_selector.R b/inst/shiny/modules/tab_nca/setup/slope_selector.R index 88f984dc7..b2c445f83 100644 --- a/inst/shiny/modules/tab_nca/setup/slope_selector.R +++ b/inst/shiny/modules/tab_nca/setup/slope_selector.R @@ -168,13 +168,14 @@ slope_selector_server <- function( # nolint } else if (is_change_in_hl_adj) { browser() affected_groups <- anti_join( - dplyr::select(new_pknca_data$conc$data, any_of(c(group_vars(new_pknca_data()), "NCA_PROFILE", excl_hl_col, incl_hl_col))), + dplyr::select(new_pknca_data$conc$data, any_of(c(group_vars(new_pknca_data), "NCA_PROFILE", excl_hl_col, incl_hl_col))), dplyr::select(pknca_data()$conc$data, any_of(c(group_vars(pknca_data()), "NCA_PROFILE", excl_hl_col, incl_hl_col))), - by = c(group_vars(new_pknca_data()), "NCA_PROFILE") + by = c(group_vars(new_pknca_data), "NCA_PROFILE", excl_hl_col, incl_hl_col) ) %>% - select(any_of(c(group_vars(new_pknca_data()), "NCA_PROFILE"))) %>% + select(any_of(c(group_vars(new_pknca_data), "NCA_PROFILE"))) %>% distinct() - .update_plots_with_rules(new_pknca_data, data.frame(), plot_outputs, affected_groups) + new_plots <- .update_plots_with_pknca(new_pknca_data, plot_outputs(), affected_groups) + plot_outputs(new_plots) } else if (is_change_in_selected_intervals) { browser() new_intervals <- anti_join(new_pknca_data$intervals, pknca_data()$intervals) @@ -183,7 +184,7 @@ slope_selector_server <- function( # nolint affected_groups <- new_intervals %>% select(any_of(c(group_vars(new_pknca_data), "NCA_PROFILE"))) %>% distinct() - new_plots <- .update_plots_with_rules(new_pknca_data, data.frame(), plot_outputs(), affected_groups) + new_plots <- .update_plots_with_pknca(new_pknca_data, plot_outputs(), affected_groups) plot_outputs(new_plots) } if (nrow(rm_intervals) > 0) { @@ -207,7 +208,7 @@ slope_selector_server <- function( # nolint } } } - + # Update the object pknca_data(new_pknca_data) }) @@ -378,42 +379,42 @@ slope_selector_server <- function( # nolint outputId = "manual_slopes", data = manual_slopes() ) - - # Update slopes version - manual_slopes_version$lst <- manual_slopes_version$current - manual_slopes_version$current <- manual_slopes() - - # Update only the plots affected by the changed rules - req(plot_outputs()) - if (!is.null(manual_slopes_version$lst)) { - browser() - print(manual_slopes_version$current) - print(manual_slopes_version$lst) - rules_added <- anti_join( - manual_slopes_version$current, - manual_slopes_version$lst, - by = names(manual_slopes()) - ) - - rules_removed <- anti_join( - manual_slopes_version$lst, - manual_slopes_version$current, - by = names(manual_slopes()) - ) - - slopes_to_update <- bind_rows(rules_added, rules_removed) %>% - select(any_of(c(group_vars(pknca_data()), "NCA_PROFILE"))) %>% - distinct() - } else { - # This will do the default udpate (all slopes in the manual slopes table) - slopes_to_update <- NULL - } - - plot_outputs( - .update_plots_with_rules(pknca_data(), manual_slopes(), plot_outputs(), slopes_to_update) - ) - print("updated plots") - print(names(plot_outputs())) +# +# # Update slopes version +# manual_slopes_version$lst <- manual_slopes_version$current +# manual_slopes_version$current <- manual_slopes() +# +# # Update only the plots affected by the changed rules +# req(plot_outputs()) +# if (!is.null(manual_slopes_version$lst)) { +# browser() +# print(manual_slopes_version$current) +# print(manual_slopes_version$lst) +# rules_added <- anti_join( +# manual_slopes_version$current, +# manual_slopes_version$lst, +# by = names(manual_slopes()) +# ) +# +# rules_removed <- anti_join( +# manual_slopes_version$lst, +# manual_slopes_version$current, +# by = names(manual_slopes()) +# ) +# +# slopes_to_update <- bind_rows(rules_added, rules_removed) %>% +# select(any_of(c(group_vars(pknca_data()), "NCA_PROFILE"))) %>% +# distinct() +# } else { +# # This will do the default udpate (all slopes in the manual slopes table) +# slopes_to_update <- NULL +# } +# +# plot_outputs( +# .update_plots_with_rules(pknca_data(), manual_slopes(), plot_outputs(), slopes_to_update) +# ) +# print("updated plots") +# print(names(plot_outputs())) }) #' If any settings are uploaded by the user, overwrite current rules From 85960fccc9216c0a5f55fca448b91e864aabe421 Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Thu, 11 Sep 2025 07:27:05 +0200 Subject: [PATCH 027/113] fix: udpate search_subject choices only when data changes --- .../modules/tab_nca/setup/slope_selector.R | 30 ++++++++----------- 1 file changed, 13 insertions(+), 17 deletions(-) diff --git a/inst/shiny/modules/tab_nca/setup/slope_selector.R b/inst/shiny/modules/tab_nca/setup/slope_selector.R index b2c445f83..78d928776 100644 --- a/inst/shiny/modules/tab_nca/setup/slope_selector.R +++ b/inst/shiny/modules/tab_nca/setup/slope_selector.R @@ -141,7 +141,7 @@ slope_selector_server <- function( # nolint excl_hl_col <- new_pknca_data$conc$columns$exclude_half.life incl_hl_col <- new_pknca_data$conc$columns$include_half.life is_new_data <- !is.null(new_pknca_data$conc$data) && is.null(pknca_data()$conc$data) - +browser() if (is_new_data) { # Prepare a default list with the half life plots browser() @@ -162,7 +162,7 @@ slope_selector_server <- function( # nolint is_change_in_selected_intervals <- !isTRUE( all.equal(new_pknca_data$intervals, pknca_data()$intervals) ) - + if (is_change_in_conc_data) { plot_outputs(get_halflife_plot(new_pknca_data)) } else if (is_change_in_hl_adj) { @@ -195,7 +195,7 @@ slope_selector_server <- function( # nolint mutate(across(everything(), as.character)) %>% # Create a column that is just a pasted character with the name and value of each column in the row mutate(id = purrr::pmap_chr( - ., + ., function(...) { vals <- list(...) paste0(names(vals), ": ", vals, collapse = ", ") @@ -209,6 +209,16 @@ slope_selector_server <- function( # nolint } } + # Update the search options if the options actually changed + if (is_new_data || is_change_in_conc_data || is_change_in_selected_intervals) { + updateSelectInput( + session = session, + inputId = "search_subject", + label = "Search Subject", + choices = unique(new_pknca_data$intervals$USUBJID) + ) + } + # Update the object pknca_data(new_pknca_data) }) @@ -324,20 +334,6 @@ slope_selector_server <- function( # nolint shinyjs::toggleState(id = ns("next_page"), condition = current_page() == num_pages) }) - #' Rendering slope plots based on nca data. - observeEvent(pknca_data(), { - req(pknca_data()) - - log_trace("{id}: Rendering plots") - # Update the subject search input to make available choices for the user - updateSelectInput( - session = session, - inputId = "search_subject", - label = "Search Subject", - choices = unique(pknca_data()$intervals$USUBJID) - ) - }) - # Creates an initial version of the manual slope adjustments table with pknca_data # and handles the addition and deletion of rows through the UI slopes_table <- manual_slopes_table_server("manual_slopes", pknca_data) From d26ad1060baedaad217e99556b3eedc967b4ecfa Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Thu, 11 Sep 2025 07:28:35 +0200 Subject: [PATCH 028/113] dont use NCA_PROFILE, some points will not work if nominally classified (e.g subj 3, time point 5 and 6) --- R/utils-slope_selector.R | 10 +++++----- inst/shiny/functions/handle_plotly_click.R | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/R/utils-slope_selector.R b/R/utils-slope_selector.R index 93e235172..24f4f7bb9 100644 --- a/R/utils-slope_selector.R +++ b/R/utils-slope_selector.R @@ -62,13 +62,13 @@ #' and reasons in `data$conc$data`. .update_pknca_with_rules <- function(data, slopes) { print(".update_pknca_with_rules") - + # TODO (Gerardo): If we keep RANGE as a time, we don't need NCA_PROFILE - slope_groups <- c(group_vars(data), "NCA_PROFILE") + slope_groups <- group_vars(data) time_col <- data$conc$columns$time exclude_hl_col <- data$conc$columns$exclude_half.life include_hl_col <- data$conc$columns$include_half.life - + browser() # Apply each rule action for (i in seq_len(nrow(slopes))) { # Determine the time range for the points adjusted @@ -91,7 +91,7 @@ } else { stop("Unknown TYPE in slopes: ", slopes$TYPE[i]) } - + data$conc$data$REASON[pnt_idx] <- paste0( data$conc$data$REASON[pnt_idx], rep(slopes$REASON[i], length(pnt_idx)) @@ -101,7 +101,7 @@ data } - + # Refactored: .update_plots_with_rules now uses .update_pknca_with_rules and delegates to .update_plots_with_pknca .update_plots_with_rules <- function(pknca_data, manual_slopes, plot_outputs, slopes_to_update = NULL) { print(".update_plots_with_rules (refactored)") diff --git a/inst/shiny/functions/handle_plotly_click.R b/inst/shiny/functions/handle_plotly_click.R index 09e6d2d8e..aa54852a0 100644 --- a/inst/shiny/functions/handle_plotly_click.R +++ b/inst/shiny/functions/handle_plotly_click.R @@ -83,7 +83,7 @@ browser() time <- as.numeric(click_data$x) row <- pknca_data$conc$data[idx, ] int <- pknca_data$intervals %>% - merge(row, by = c(group_vars(pknca_data), "NCA_PROFILE")) %>% + merge(row, by = c(group_vars(pknca_data))) %>% filter(start <= row[[pknca_data$conc$columns$time]] & end >= row[[pknca_data$conc$columns$time]]) %>% select(any_of(names(pknca_data$intervals))) From 88a89918efa432d83c137e739482180616fc5049 Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Thu, 11 Sep 2025 09:45:23 +0200 Subject: [PATCH 029/113] fix: issue with update of plots for exclusions --- R/lambda_slope_plot.R | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/R/lambda_slope_plot.R b/R/lambda_slope_plot.R index 45e0c78fb..ea3498356 100644 --- a/R/lambda_slope_plot.R +++ b/R/lambda_slope_plot.R @@ -31,7 +31,6 @@ get_halflife_plot <- function(pknca_data, add_annotations = TRUE) { n_groups <- nrow(groups) plots <- vector("list", n_groups) if (n_groups == 0) { - print(difftime(Sys.time() - time0)) return(plots) } # Precompute column names and helper functions @@ -45,13 +44,12 @@ get_halflife_plot <- function(pknca_data, add_annotations = TRUE) { exclude_hl_col <- "exclude_half.life" } - include_hl_col <- o_nca$data$conc$columns$exclude_half.life + include_hl_col <- o_nca$data$conc$columns$include_half.life if (is.null(include_hl_col)) { o_nca$data$conc$data[["include_half.life"]] <- FALSE exclude_hl_col <- "include_half.life" } - - + # Helper functions for extracting results get_res_fun <- function(result_df, testcd) result_df$PPSTRES[result_df$PPTESTCD == testcd] get_unit_fun <- function(result_df, testcd) result_df$PPSTRESU[result_df$PPTESTCD == testcd] @@ -312,7 +310,7 @@ get_halflife_plot <- function(pknca_data, add_annotations = TRUE) { #' @param exclude_hl_col column name for exclude_half.life #' @return logical vector get_halflife_points2 <- function(data, include_hl_col, time_col, lz_time_first, lz_time_last, exclude_hl_col) { - if (any(data[[include_hl_col]])) { + if (any(!is.na(data[[include_hl_col]]) & data[[include_hl_col]])) { data[[include_hl_col]] } else { data[[time_col]] >= lz_time_first & data[[time_col]] <= lz_time_last & !data[[exclude_hl_col]] From 1c1ad55e7bfafc9c7e4181fad1fa1372e5835238 Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Thu, 11 Sep 2025 11:17:31 +0200 Subject: [PATCH 030/113] refactor: create a module page_and_searcher.R --- .../modules/tab_nca/setup/page_and_searcher.R | 123 +++++++++++++++++ .../modules/tab_nca/setup/slope_selector.R | 127 +++--------------- 2 files changed, 140 insertions(+), 110 deletions(-) create mode 100644 inst/shiny/modules/tab_nca/setup/page_and_searcher.R diff --git a/inst/shiny/modules/tab_nca/setup/page_and_searcher.R b/inst/shiny/modules/tab_nca/setup/page_and_searcher.R new file mode 100644 index 000000000..8f6c67bc0 --- /dev/null +++ b/inst/shiny/modules/tab_nca/setup/page_and_searcher.R @@ -0,0 +1,123 @@ +#' Page and Searcher UI (pagination controls only) +#' +#' This UI module provides the page navigation controls (previous, next, page selector, page number display). +#' The search_subject input remains outside for now. +page_and_searcher_page_ui <- function(id) { + ns <- NS(id) + fluidRow( + class = "plot-widgets-container-2", + div( + class = "plot-widget-group", + actionButton( + ns("previous_page"), + "Previous Page", + class = "btn-page" + ) + ), + div( + class = "plot-widget-group", + tags$span("Page "), + pickerInput(ns("select_page"), "", choices = c(), width = "100px"), + tags$span("of "), + uiOutput(ns("page_number"), inline = TRUE) + ), + div( + class = "plot-widget-group", + actionButton( + ns("next_page"), + "Next Page", + class = "btn-page" + ) + ) + ) +} + +#' Page and Searcher Server +#' +#' Handles pagination and subject search logic for displaying plots. +#' Inputs: current_page (reactive), input$search_subject, plot_outputs (reactive), input$plots_per_page +#' Outputs: list of reactives: page_start, page_end, has_plot_subject, num_pages +page_and_searcher_server <- function(id, search_subject, plot_outputs, plots_per_page, ns_parent) { + moduleServer(id, function(input, output, session) { + ns <- session$ns + + # Internalize current page state + current_page <- reactiveVal(1) + + # Calculate has_plot_subject + has_plot_subject <- reactive({ + req(plot_outputs()) + search_val <- search_subject() + if (is.null(search_val) || length(search_val) == 0) { + rep(TRUE, length(names(plot_outputs()))) + } else { + grepl( + paste0("USUBJID: (", paste0(search_val, collapse = ")|("), ")"), + names(plot_outputs()) + ) + } + }) + + num_plots <- reactive({ sum(has_plot_subject()) }) + plots_per_page_num <- reactive({ as.numeric(plots_per_page()) }) + num_pages <- reactive({ ceiling(num_plots() / plots_per_page_num()) }) + + # Navigation events + observeEvent(input$next_page, { + if (current_page() < num_pages()) { + current_page(current_page() + 1) + } + shinyjs::disable(selector = ".btn-page") + }) + observeEvent(input$previous_page, { + if (current_page() > 1) { + current_page(current_page() - 1) + } + shinyjs::disable(selector = ".btn-page") + }) + observeEvent(input$select_page, { + val <- as.numeric(input$select_page) + if (!is.na(val)) current_page(val) + }) + observeEvent(list(plots_per_page(), search_subject()), { + current_page(1) + }) + + page_end <- reactive({ + min(current_page() * plots_per_page_num(), num_plots()) + }) + page_start <- reactive({ + max(page_end() - plots_per_page_num() + 1, 1) + }) + + # UI outputs for page number and page selector + output$page_number <- renderUI(num_pages()) + observe({ + updatePickerInput( + session = session, + inputId = "select_page", + choices = 1:num_pages(), + selected = current_page() + ) + }) + observe({ + shinyjs::toggleState(id = "previous_page", condition = current_page() == 1) + shinyjs::toggleState(id = "next_page", condition = current_page() == num_pages()) + }) + observe({ + shinyjs::toggleClass( + selector = ".slope-plots-container", + class = "multiple", + condition = plots_per_page_num() != 1 + ) + }) + + list( + page_start = page_start, + page_end = page_end, + has_plot_subject = has_plot_subject, + num_pages = num_pages, + current_page = current_page + ) + }) +} diff --git a/inst/shiny/modules/tab_nca/setup/slope_selector.R b/inst/shiny/modules/tab_nca/setup/slope_selector.R index 78d928776..56eef44fe 100644 --- a/inst/shiny/modules/tab_nca/setup/slope_selector.R +++ b/inst/shiny/modules/tab_nca/setup/slope_selector.R @@ -61,7 +61,6 @@ slope_selector_ui <- function(id) { # Widgets for manipulating plots display # fluidRow( class = "plot-widgets-container", - div( class = "plot-widget-group", selectInput( @@ -71,7 +70,6 @@ slope_selector_ui <- function(id) { selected = 1 ) ), - div( class = "plot-widget-group", selectInput( @@ -86,35 +84,8 @@ slope_selector_ui <- function(id) { # Plots display # uiOutput(ns("slope_plots_ui"), class = "slope-plots-container"), br(), - fluidRow( - class = "plot-widgets-container-2", - div( - class = "plot-widget-group", - actionButton( - ns("previous_page"), - "Previous Page", - class = "btn-page" - ) - ), - - div( - class = "plot-widget-group", - tags$span("Page "), - pickerInput(ns("select_page"), "", choices = c(), - width = "100px"), - tags$span("of "), - uiOutput(ns("page_number"), inline = TRUE) - ), - - div( - class = "plot-widget-group", - actionButton( - ns("next_page"), - "Next Page", - class = "btn-page" - ) - ) - ), + # Use the new pagination UI module + page_and_searcher_page_ui(ns("page_and_searcher")), br() ) } @@ -126,7 +97,7 @@ slope_selector_server <- function( # nolint log_trace("{id}: Attaching server") ns <- session$ns - + pknca_data <- reactiveVal(NULL) plot_outputs <- reactiveVal(NULL) observeEvent(list(processed_pknca_data()), { @@ -247,91 +218,27 @@ browser() # HACK: workaround to avoid plotly_click not being registered warning session$userData$plotlyShinyEventIDs <- "plotly_click-A" - current_page <- reactiveVal(1) - #' updating current page based on user input - observeEvent(input$next_page, { - current_page(current_page() + 1) - shinyjs::disable(selector = ".btn-page") - }) - observeEvent(input$previous_page, { - if (current_page() == 1) return(NULL) - current_page(current_page() - 1) - shinyjs::disable(selector = ".btn-page") - }) - observeEvent(input$select_page, current_page(as.numeric(input$select_page))) - observeEvent(list(input$plots_per_page, input$search_subject), current_page(1)) - - #' Updating plot outputUI, dictating which plots get displayed to the user. - #' Scans for any related reactives (page number, subject filter etc) and updates the plot output - #' UI to have only plotlyOutput elements for desired plots. - observeEvent(list( - plot_outputs(), input$plots_per_page, input$search_subject, current_page() - ), { - req(plot_outputs()) - log_trace("{id}: Updating displayed plots") - - # Decide - # Make sure the search_subject input is not NULL - search_subject <- { - if (is.null(input$search_subject) || length(input$search_subject) == 0) { - subject_col <- pknca_data()$conc$columns$subject - unique( - pknca_data()$intervals %>% - filter(half.life) %>% - .[[subject_col]] - ) - } else { - input$search_subject - } - } - - has_plot_subject <- grepl( - paste0("USUBJID: ", "(", paste0(search_subject, collapse = ")|("), ")"), - names(plot_outputs()) - ) - - # find which plots should be displayed based on page # - num_plots <- sum(has_plot_subject) - plots_per_page <- as.numeric(input$plots_per_page) - num_pages <- ceiling(num_plots / plots_per_page) - - if (current_page() > num_pages) { - current_page(current_page() - 1) - return(NULL) - } - page_end <- current_page() * plots_per_page - page_start <- page_end - plots_per_page + 1 - if (page_end > num_plots) page_end <- num_plots + # --- Pagination and search logic using the new module --- + search_subject_r <- reactive({ input$search_subject }) + plots_per_page_r <- reactive({ input$plots_per_page }) - # update page number display # - output$page_number <- renderUI(num_pages) + page_search <- page_and_searcher_server( + id = "page_and_searcher", + search_subject = search_subject_r, + plot_outputs = plot_outputs, + plots_per_page = plots_per_page_r, + ns_parent = ns + ) - # Render only the plots requested by the user + # Render only the plots requested by the user (using the module's outputs) + observe({ + req(plot_outputs()) output$slope_plots_ui <- renderUI({ shinyjs::enable(selector = ".btn-page") - plot_outputs()[has_plot_subject][page_start:page_end] + plot_outputs()[page_search$has_plot_subject()][page_search$page_start():page_search$page_end()] }) - - # update jump to page selector # - updatePickerInput( - session = session, - inputId = "select_page", - choices = 1:num_pages, - selected = current_page() - ) - - # update plot display # - shinyjs::toggleClass( - selector = ".slope-plots-container", - class = "multiple", - condition = plots_per_page != 1 - ) - - # disable buttons if necessary # - shinyjs::toggleState(id = ns("previous_page"), condition = current_page() == 1) - shinyjs::toggleState(id = ns("next_page"), condition = current_page() == num_pages) }) # Creates an initial version of the manual slope adjustments table with pknca_data From 0e9a1cddd164a4eb781578633b7fc01acbbc6f36 Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Thu, 11 Sep 2025 11:36:53 +0200 Subject: [PATCH 031/113] refactor: rm slopes_pknca_groups from slope_selector.R & reduce outputs in page_and_searcher --- .../modules/tab_nca/setup/page_and_searcher.R | 12 ++-- .../modules/tab_nca/setup/slope_selector.R | 69 ++----------------- 2 files changed, 11 insertions(+), 70 deletions(-) diff --git a/inst/shiny/modules/tab_nca/setup/page_and_searcher.R b/inst/shiny/modules/tab_nca/setup/page_and_searcher.R index 8f6c67bc0..3adb171a4 100644 --- a/inst/shiny/modules/tab_nca/setup/page_and_searcher.R +++ b/inst/shiny/modules/tab_nca/setup/page_and_searcher.R @@ -36,7 +36,7 @@ page_and_searcher_page_ui <- function(id) { #' #' Handles pagination and subject search logic for displaying plots. #' Inputs: current_page (reactive), input$search_subject, plot_outputs (reactive), input$plots_per_page -#' Outputs: list of reactives: page_start, page_end, has_plot_subject, num_pages +#' Outputs: list of reactives: page_start, page_end, is_plot_searched, num_pages page_and_searcher_server <- function(id, search_subject, plot_outputs, plots_per_page, ns_parent) { moduleServer(id, function(input, output, session) { ns <- session$ns @@ -44,8 +44,8 @@ page_and_searcher_server <- function(id, search_subject, plot_outputs, plots_per # Internalize current page state current_page <- reactiveVal(1) - # Calculate has_plot_subject - has_plot_subject <- reactive({ + # Calculate is_plot_searched + is_plot_searched <- reactive({ req(plot_outputs()) search_val <- search_subject() if (is.null(search_val) || length(search_val) == 0) { @@ -58,7 +58,7 @@ page_and_searcher_server <- function(id, search_subject, plot_outputs, plots_per } }) - num_plots <- reactive({ sum(has_plot_subject()) }) + num_plots <- reactive({ sum(is_plot_searched()) }) plots_per_page_num <- reactive({ as.numeric(plots_per_page()) }) num_pages <- reactive({ ceiling(num_plots() / plots_per_page_num()) }) @@ -115,9 +115,7 @@ page_and_searcher_server <- function(id, search_subject, plot_outputs, plots_per list( page_start = page_start, page_end = page_end, - has_plot_subject = has_plot_subject, - num_pages = num_pages, - current_page = current_page + is_plot_searched = is_plot_searched ) }) } diff --git a/inst/shiny/modules/tab_nca/setup/slope_selector.R b/inst/shiny/modules/tab_nca/setup/slope_selector.R index 56eef44fe..af95036c8 100644 --- a/inst/shiny/modules/tab_nca/setup/slope_selector.R +++ b/inst/shiny/modules/tab_nca/setup/slope_selector.R @@ -11,8 +11,6 @@ #' #' @returns List with reactive expressions: #' * manual_slopes - Data frame containing inclusions / exclusions. -#' * profiles_per_subject - Grouping for each subject. -#' * slopes_pknca_groups - Grouping for the slopes, in accordance to the settings. slope_selector_ui <- function(id) { ns <- NS(id) @@ -194,36 +192,17 @@ browser() pknca_data(new_pknca_data) }) - # Get grouping columns for plots and tables - slopes_pknca_groups <- reactive({ - req(pknca_data()) - - pknca_data()$intervals %>% - select(any_of(c(group_vars(pknca_data()), "NCA_PROFILE"))) - - # relevant_group_cols <- pknca_data()$conc$data %>% - # select( - # any_of( - # c(group_vars(pknca_data()), "NCA_PROFILE") - # ) - # ) %>% - # # TODO (Gerardo): Include a better new version of select_minimal_grouping_cols - # # Select columns that have more than 1 unique value - # select(where(~ n_distinct(.) > 1)) %>% - # colnames() - - # pknca_data()$intervals %>% - # select(any_of(relevant_group_cols)) - }) - # HACK: workaround to avoid plotly_click not being registered warning session$userData$plotlyShinyEventIDs <- "plotly_click-A" - # --- Pagination and search logic using the new module --- + # --- Pagination and search logic --- search_subject_r <- reactive({ input$search_subject }) plots_per_page_r <- reactive({ input$plots_per_page }) + # Call the pagination/searcher module to: + # - Providing indices of plots for the selected subject(s) + # - Providing indices for which plots to display based on pagination page_search <- page_and_searcher_server( id = "page_and_searcher", search_subject = search_subject_r, @@ -232,12 +211,12 @@ browser() ns_parent = ns ) - # Render only the plots requested by the user (using the module's outputs) + # Render only the plots requested by the user (using the subject searcher and pagination) observe({ req(plot_outputs()) output$slope_plots_ui <- renderUI({ shinyjs::enable(selector = ".btn-page") - plot_outputs()[page_search$has_plot_subject()][page_search$page_start():page_search$page_end()] + plot_outputs()[page_search$is_plot_searched()][page_search$page_start():page_search$page_end()] }) }) @@ -282,42 +261,6 @@ browser() outputId = "manual_slopes", data = manual_slopes() ) -# -# # Update slopes version -# manual_slopes_version$lst <- manual_slopes_version$current -# manual_slopes_version$current <- manual_slopes() -# -# # Update only the plots affected by the changed rules -# req(plot_outputs()) -# if (!is.null(manual_slopes_version$lst)) { -# browser() -# print(manual_slopes_version$current) -# print(manual_slopes_version$lst) -# rules_added <- anti_join( -# manual_slopes_version$current, -# manual_slopes_version$lst, -# by = names(manual_slopes()) -# ) -# -# rules_removed <- anti_join( -# manual_slopes_version$lst, -# manual_slopes_version$current, -# by = names(manual_slopes()) -# ) -# -# slopes_to_update <- bind_rows(rules_added, rules_removed) %>% -# select(any_of(c(group_vars(pknca_data()), "NCA_PROFILE"))) %>% -# distinct() -# } else { -# # This will do the default udpate (all slopes in the manual slopes table) -# slopes_to_update <- NULL -# } -# -# plot_outputs( -# .update_plots_with_rules(pknca_data(), manual_slopes(), plot_outputs(), slopes_to_update) -# ) -# print("updated plots") -# print(names(plot_outputs())) }) #' If any settings are uploaded by the user, overwrite current rules From 78ec71dfd7f18005dc5d736e6c3b10ebad766dc2 Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Thu, 11 Sep 2025 14:55:57 +0200 Subject: [PATCH 032/113] refactor: logic to handle plot generation in slope_selector.R --- R/utils-slope_selector.R | 56 +++++++++++ inst/shiny/functions/handle_plotly_click.R | 5 - .../modules/tab_nca/setup/slope_selector.R | 97 +++++-------------- 3 files changed, 78 insertions(+), 80 deletions(-) diff --git a/R/utils-slope_selector.R b/R/utils-slope_selector.R index 24f4f7bb9..326f2f4e9 100644 --- a/R/utils-slope_selector.R +++ b/R/utils-slope_selector.R @@ -1,3 +1,59 @@ +## Functions to handle plot generation based on PKNCA data changes +# --- Helper: Detect changes between old and new PKNCA data --- +detect_pknca_data_changes <- function(old, new, excl_hl_col, incl_hl_col) { + browser() + list( + in_data = if (is.null(old) & !is.null(new)) TRUE else { + !identical( + dplyr::select(old$conc$data, -any_of(c(excl_hl_col, incl_hl_col))), + dplyr::select(new$conc$data, -any_of(c(excl_hl_col, incl_hl_col))) + ) + }, + in_hl_adj = !identical(old$conc$data[[excl_hl_col]], new$conc$data[[excl_hl_col]]) | + !identical(old$conc$data[[incl_hl_col]], new$conc$data[[incl_hl_col]]), + in_selected_intervals = !identical(new$intervals, old$intervals) + ) +} + +handle_hl_adj <- function(new_pknca_data, old_pknca_data, plot_outputs) { + excl_hl_col <- new_pknca_data$conc$columns$exclude_half.life + incl_hl_col <- new_pknca_data$conc$columns$include_half.life + affected_groups <- anti_join( + dplyr::select(new_pknca_data$conc$data, any_of(c(group_vars(new_pknca_data), "NCA_PROFILE", excl_hl_col, incl_hl_col))), + dplyr::select(old_pknca_data$conc$data, any_of(c(group_vars(old_pknca_data), "NCA_PROFILE", excl_hl_col, incl_hl_col))), + by = c(group_vars(new_pknca_data), "NCA_PROFILE", excl_hl_col, incl_hl_col) + ) %>% + select(any_of(c(group_vars(new_pknca_data), "NCA_PROFILE"))) %>% + distinct() + .update_plots_with_pknca(new_pknca_data, plot_outputs, affected_groups) +} + +handle_interval_change <- function(new_pknca_data, old_pknca_data, plot_outputs) { + new_intervals <- anti_join(new_pknca_data$intervals, old_pknca_data$intervals) + rm_intervals <- anti_join(old_pknca_data$intervals, new_pknca_data$intervals) + if (nrow(new_intervals) > 0) { + affected_groups <- new_intervals %>% + select(any_of(c(group_vars(new_pknca_data), "NCA_PROFILE"))) %>% + distinct() + .update_plots_with_pknca(new_pknca_data, plot_outputs(), affected_groups) + } + if (nrow(rm_intervals) > 0) { + rm_plot_names <- rm_intervals %>% + select(any_of(c(group_vars(new_pknca_data), "start", "end"))) %>% + distinct() %>% + mutate(across(everything(), as.character)) %>% + mutate(id = purrr::pmap_chr( + ., + function(...) { + vals <- list(...) + paste0(names(vals), ": ", vals, collapse = ", ") + } + )) %>% + pull(id) + plot_outputs[!names(plot_outputs()) %in% rm_plot_names] + } +} + #' Check overlap between existing and new slope rulesets #' #' Takes in tables with existing and incoming selections and exclusions, finds any overlap and diff --git a/inst/shiny/functions/handle_plotly_click.R b/inst/shiny/functions/handle_plotly_click.R index aa54852a0..129bae954 100644 --- a/inst/shiny/functions/handle_plotly_click.R +++ b/inst/shiny/functions/handle_plotly_click.R @@ -57,11 +57,6 @@ browser() )) } - # # Update the specific plot (interval) affected - # updated_pknca$intervals <- pnt$int - # updated_plot <- suppressWarnings(get_halflife_plot(updated_pknca)) - # plot_outputs[names(plot_outputs) %in% names(updated_plot)] <- updated_plot - # Update manual_slopes without modifying it globally updated_slopes <- check_slope_rule_overlap(manual_slopes(), new_rule) diff --git a/inst/shiny/modules/tab_nca/setup/slope_selector.R b/inst/shiny/modules/tab_nca/setup/slope_selector.R index af95036c8..ba56a568c 100644 --- a/inst/shiny/modules/tab_nca/setup/slope_selector.R +++ b/inst/shiny/modules/tab_nca/setup/slope_selector.R @@ -98,88 +98,38 @@ slope_selector_server <- function( # nolint pknca_data <- reactiveVal(NULL) plot_outputs <- reactiveVal(NULL) - observeEvent(list(processed_pknca_data()), { + + observeEvent(processed_pknca_data(), { req(processed_pknca_data()) - # Prepare a standard PKNCA object with only the basic adjustments for the half-life plots - # and only over the user selected analytes, profiles and specimens new_pknca_data <- processed_pknca_data() new_pknca_data$intervals <- new_pknca_data$intervals %>% filter(type_interval == "main", half.life) %>% unique() - excl_hl_col <- new_pknca_data$conc$columns$exclude_half.life - incl_hl_col <- new_pknca_data$conc$columns$include_half.life - is_new_data <- !is.null(new_pknca_data$conc$data) && is.null(pknca_data()$conc$data) + browser() - if (is_new_data) { - # Prepare a default list with the half life plots - browser() + changes <- detect_pknca_data_changes( + old = pknca_data(), + new = new_pknca_data, + excl_hl_col = new_pknca_data$conc$columns$exclude_half.life, + incl_hl_col = new_pknca_data$conc$columns$include_half.life + ) + # Regenerate plots if a dataset is submitted or it was changed + # (update of adnca_data in setup.R) + if (changes$in_data) { plot_outputs(get_halflife_plot(new_pknca_data)) - } else { - is_change_in_conc_data <- !isTRUE( - all.equal( - dplyr::select(new_pknca_data$conc$data, -any_of(c(excl_hl_col, incl_hl_col))), - dplyr::select(pknca_data()$conc$data, -any_of(c(excl_hl_col, incl_hl_col))) - ) - ) - is_change_in_hl_adj <- !isTRUE( - all.equal( - dplyr::select(new_pknca_data$conc$data, any_of(c(excl_hl_col, incl_hl_col))), - dplyr::select(pknca_data()$conc$data, any_of(c(excl_hl_col, incl_hl_col))) - ) - ) - is_change_in_selected_intervals <- !isTRUE( - all.equal(new_pknca_data$intervals, pknca_data()$intervals) - ) - - if (is_change_in_conc_data) { - plot_outputs(get_halflife_plot(new_pknca_data)) - } else if (is_change_in_hl_adj) { - browser() - affected_groups <- anti_join( - dplyr::select(new_pknca_data$conc$data, any_of(c(group_vars(new_pknca_data), "NCA_PROFILE", excl_hl_col, incl_hl_col))), - dplyr::select(pknca_data()$conc$data, any_of(c(group_vars(pknca_data()), "NCA_PROFILE", excl_hl_col, incl_hl_col))), - by = c(group_vars(new_pknca_data), "NCA_PROFILE", excl_hl_col, incl_hl_col) - ) %>% - select(any_of(c(group_vars(new_pknca_data), "NCA_PROFILE"))) %>% - distinct() - new_plots <- .update_plots_with_pknca(new_pknca_data, plot_outputs(), affected_groups) - plot_outputs(new_plots) - } else if (is_change_in_selected_intervals) { - browser() - new_intervals <- anti_join(new_pknca_data$intervals, pknca_data()$intervals) - rm_intervals <- anti_join(pknca_data()$intervals, new_pknca_data$intervals) - if (nrow(new_intervals) > 0) { - affected_groups <- new_intervals %>% - select(any_of(c(group_vars(new_pknca_data), "NCA_PROFILE"))) %>% - distinct() - new_plots <- .update_plots_with_pknca(new_pknca_data, plot_outputs(), affected_groups) - plot_outputs(new_plots) - } - if (nrow(rm_intervals) > 0) { - rm_plot_names <- rm_intervals %>% - select(any_of(c(group_vars(new_pknca_data), "start", "end"))) %>% - # Create a column that is just a pasted character with the name and value of each column in the row - distinct() %>% - mutate(across(everything(), as.character)) %>% - # Create a column that is just a pasted character with the name and value of each column in the row - mutate(id = purrr::pmap_chr( - ., - function(...) { - vals <- list(...) - paste0(names(vals), ": ", vals, collapse = ", ") - } - )) %>% - pull(id) - - remaining_plots <- plot_outputs()[!names(plot_outputs()) %in% rm_plot_names] - plot_outputs(remaining_plots) - } - } + # If relevant, modify plots that had new half-life adjustments + # (inclusions/exclusions from manual_slope_table) + } else if (changes$in_hl_adj) { + plot_outputs(handle_hl_adj(new_pknca_data, pknca_data(), plot_outputs())) + # If relevant, modify plots that had interval changes + # (analyte, profile, specimen selection from setup.R settings) + } else if (changes$in_selected_intervals) { + plot_outputs(handle_interval_change(new_pknca_data, pknca_data(), plot_outputs())) } - # Update the search options if the options actually changed - if (is_new_data || is_change_in_conc_data || is_change_in_selected_intervals) { + # Update the searching widget choices based on the new data + if (changes$in_data | changes$in_selected_intervals) { updateSelectInput( session = session, inputId = "search_subject", @@ -195,7 +145,6 @@ browser() # HACK: workaround to avoid plotly_click not being registered warning session$userData$plotlyShinyEventIDs <- "plotly_click-A" - # --- Pagination and search logic --- search_subject_r <- reactive({ input$search_subject }) plots_per_page_r <- reactive({ input$plots_per_page }) @@ -285,13 +234,11 @@ browser() all() if (!override_valid) { - msg <- "Manual slopes not compatible with current data, leaving as default." log_warn(msg) showNotification(msg, type = "warning", duration = 5) return(NULL) } - manual_slopes(manual_slopes_override()) }) From 80d66b7e7d680241b5a77a6aaad688c20e099a5e Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Thu, 11 Sep 2025 15:22:38 +0200 Subject: [PATCH 033/113] refactor: manual_slopes_override inside manual_slopes_table_server.R --- .../tab_nca/setup/manual_slopes_table.R | 35 ++++++++++++++- .../modules/tab_nca/setup/slope_selector.R | 44 +++---------------- 2 files changed, 41 insertions(+), 38 deletions(-) diff --git a/inst/shiny/modules/tab_nca/setup/manual_slopes_table.R b/inst/shiny/modules/tab_nca/setup/manual_slopes_table.R index c73699e74..965406b64 100644 --- a/inst/shiny/modules/tab_nca/setup/manual_slopes_table.R +++ b/inst/shiny/modules/tab_nca/setup/manual_slopes_table.R @@ -20,7 +20,7 @@ manual_slopes_table_ui <- function(id) { manual_slopes_table_server <- function( - id, mydata + id, mydata, manual_slopes_override = NULL ) { moduleServer(id, function(input, output, session) { @@ -163,6 +163,39 @@ manual_slopes_table_server <- function( }) }) + # --- Manual slopes override logic (moved from slope_selector_server) --- + if (!is.null(manual_slopes_override)) { + observeEvent(manual_slopes_override(), { + req(manual_slopes_override()) + + if (nrow(manual_slopes_override()) == 0) return(NULL) + + log_debug_list("Manual slopes override:", manual_slopes_override()) + + # Use mydata() for validation + override_valid <- apply(manual_slopes_override(), 1, function(r) { + dplyr::filter( + mydata()$conc$data, + PCSPEC == r["PCSPEC"], + USUBJID == r["USUBJID"], + PARAM == r["PARAM"], + NCA_PROFILE == r["NCA_PROFILE"], + DOSNOA == r["DOSNOA"] + ) |> + NROW() != 0 + }) |> + all() + + if (!override_valid) { + msg <- "Manual slopes not compatible with current data, leaving as default." + log_warn(msg) + showNotification(msg, type = "warning", duration = 5) + return(NULL) + } + manual_slopes(manual_slopes_override()) + }) + } + list( manual_slopes = manual_slopes, refresh_reactable = refresh_reactable diff --git a/inst/shiny/modules/tab_nca/setup/slope_selector.R b/inst/shiny/modules/tab_nca/setup/slope_selector.R index ba56a568c..54eff8e81 100644 --- a/inst/shiny/modules/tab_nca/setup/slope_selector.R +++ b/inst/shiny/modules/tab_nca/setup/slope_selector.R @@ -106,14 +106,13 @@ slope_selector_server <- function( # nolint new_pknca_data$intervals <- new_pknca_data$intervals %>% filter(type_interval == "main", half.life) %>% unique() - -browser() changes <- detect_pknca_data_changes( old = pknca_data(), new = new_pknca_data, excl_hl_col = new_pknca_data$conc$columns$exclude_half.life, incl_hl_col = new_pknca_data$conc$columns$include_half.life ) + # Regenerate plots if a dataset is submitted or it was changed # (update of adnca_data in setup.R) if (changes$in_data) { @@ -169,11 +168,11 @@ browser() }) }) - # Creates an initial version of the manual slope adjustments table with pknca_data - # and handles the addition and deletion of rows through the UI - slopes_table <- manual_slopes_table_server("manual_slopes", pknca_data) - manual_slopes <- slopes_table$manual_slopes - refresh_reactable <- slopes_table$refresh_reactable + # Creates an initial version of the manual slope adjustments table with pknca_data + # and handles the addition and deletion of rows through the UI + slopes_table <- manual_slopes_table_server("manual_slopes", pknca_data, manual_slopes_override) + manual_slopes <- slopes_table$manual_slopes + refresh_reactable <- slopes_table$refresh_reactable # Define the click events for the point exclusion and selection in the slope plots last_click_data <- reactiveVal(NULL) @@ -200,7 +199,6 @@ browser() #' Separate event handling updating displayed reactable upon every change (adding and removing #' rows, plots selection, edits). This needs to be separate call, since simply re-rendering #' the table would mean losing focus on text inputs when entering values. - manual_slopes_version <- reactiveValues(lst = NULL, current = NULL) observeEvent(manual_slopes(), { req(manual_slopes()) print("observeEvent manual_slopes()") @@ -212,35 +210,7 @@ browser() ) }) - #' If any settings are uploaded by the user, overwrite current rules - observeEvent(manual_slopes_override(), { - req(manual_slopes_override()) - - if (nrow(manual_slopes_override()) == 0) return(NULL) - - log_debug_list("Manual slopes override:", manual_slopes_override()) - - override_valid <- apply(manual_slopes_override(), 1, function(r) { - dplyr::filter( - plot_data()$conc$data, - PCSPEC == r["PCSPEC"], - USUBJID == r["USUBJID"], - PARAM == r["PARAM"], - NCA_PROFILE == r["NCA_PROFILE"], - DOSNOA == r["DOSNOA"] - ) |> - NROW() != 0 - }) |> - all() - - if (!override_valid) { - msg <- "Manual slopes not compatible with current data, leaving as default." - log_warn(msg) - showNotification(msg, type = "warning", duration = 5) - return(NULL) - } - manual_slopes(manual_slopes_override()) - }) + # Manual slopes override logic moved to manual_slopes_table_server #' return reactive with slope exclusions data to be displayed in Results -> Exclusions tab list( From dddc976097f3e03f3d757f88bb4ab8aaea2d9184 Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Thu, 11 Sep 2025 16:08:21 +0200 Subject: [PATCH 034/113] refactor: simplify all submodule out & renaming manual_slopes_table --- R/utils-slope_selector.R | 2 +- inst/shiny/functions/handle_plotly_click.R | 14 ++++----- ...l_slopes_table.R => handle_slopes_table.R} | 4 +-- .../modules/tab_nca/setup/page_and_searcher.R | 2 +- .../modules/tab_nca/setup/slope_selector.R | 29 +++++++++---------- 5 files changed, 22 insertions(+), 29 deletions(-) rename inst/shiny/modules/tab_nca/setup/{manual_slopes_table.R => handle_slopes_table.R} (98%) diff --git a/R/utils-slope_selector.R b/R/utils-slope_selector.R index 326f2f4e9..72ab8b178 100644 --- a/R/utils-slope_selector.R +++ b/R/utils-slope_selector.R @@ -175,7 +175,7 @@ handle_interval_change <- function(new_pknca_data, old_pknca_data, plot_outputs) # Delegate to .update_plots_with_pknca for actual plot updating .update_plots_with_pknca(pknca_for_plots, plot_outputs, intervals_to_update = slopes_to_update) } - + .update_plots_with_pknca <- function(pknca_data, plot_outputs, intervals_to_update = NULL) { print(".update_plots_with_pknca") # If the user does not specify which plots to update (NULL), update all plots in the manual slopes table diff --git a/inst/shiny/functions/handle_plotly_click.R b/inst/shiny/functions/handle_plotly_click.R index 129bae954..39c56a84b 100644 --- a/inst/shiny/functions/handle_plotly_click.R +++ b/inst/shiny/functions/handle_plotly_click.R @@ -7,11 +7,10 @@ #' @param manual_slopes A reactive Values object storing the manually added slope rules. #' @param click_data A list containing the custom data from the plotly click event. #' @param pknca_data A PKNCA data object containing concentration data and intervals. -#' @param plot_outputs A list of current plot outputs to be updated if needed. #' #' @returns Returns a list with updated `last_click_data` and `manual_slopes`. #' -handle_plotly_click <- function(last_click_data, manual_slopes, click_data, pknca_data, plot_outputs) { +handle_plotly_click <- function(last_click_data, manual_slopes, click_data, pknca_data) { req(click_data, click_data$customdata) print("current click:") print(click_data) @@ -21,8 +20,7 @@ handle_plotly_click <- function(last_click_data, manual_slopes, click_data, pknc if (is.null(last_click_data())) { return(list( last_click_data = click_data, - manual_slopes = manual_slopes(), - plot_outputs = plot_outputs + manual_slopes = manual_slopes() )) } @@ -44,7 +42,7 @@ browser() new_rule$REASON <- "" # If it is in the same plot (interval), consider all points in the time range included - } else if (all.equal(pnt$int, lstpnt$int)) { + } else if (identical(pnt$int, lstpnt$int)) { new_rule$TYPE <- "Selection" new_rule$RANGE <- paste0(sort(c(pnt$time, lstpnt$time)), collapse = ":") new_rule$REASON <- "" @@ -52,8 +50,7 @@ browser() # Do nothing return(list( last_click_data = click_data, - manual_slopes = manual_slopes(), - plot_outputs = plot_outputs + manual_slopes = manual_slopes() )) } @@ -63,8 +60,7 @@ browser() # Return updated values list( last_click_data = NULL, # Action was finished and has to be updated - manual_slopes = updated_slopes, - plot_outputs = plot_outputs # TODO: NOT NEEDED ANYMORE + manual_slopes = updated_slopes ) } diff --git a/inst/shiny/modules/tab_nca/setup/manual_slopes_table.R b/inst/shiny/modules/tab_nca/setup/handle_slopes_table.R similarity index 98% rename from inst/shiny/modules/tab_nca/setup/manual_slopes_table.R rename to inst/shiny/modules/tab_nca/setup/handle_slopes_table.R index 965406b64..5baaf479c 100644 --- a/inst/shiny/modules/tab_nca/setup/manual_slopes_table.R +++ b/inst/shiny/modules/tab_nca/setup/handle_slopes_table.R @@ -1,4 +1,4 @@ -manual_slopes_table_ui <- function(id) { +handle_slopes_table_ui <- function(id) { ns <- NS(id) fluidRow( @@ -19,7 +19,7 @@ manual_slopes_table_ui <- function(id) { } -manual_slopes_table_server <- function( +handle_slopes_table_server <- function( id, mydata, manual_slopes_override = NULL ) { moduleServer(id, function(input, output, session) { diff --git a/inst/shiny/modules/tab_nca/setup/page_and_searcher.R b/inst/shiny/modules/tab_nca/setup/page_and_searcher.R index 3adb171a4..fd615013f 100644 --- a/inst/shiny/modules/tab_nca/setup/page_and_searcher.R +++ b/inst/shiny/modules/tab_nca/setup/page_and_searcher.R @@ -37,7 +37,7 @@ page_and_searcher_page_ui <- function(id) { #' Handles pagination and subject search logic for displaying plots. #' Inputs: current_page (reactive), input$search_subject, plot_outputs (reactive), input$plots_per_page #' Outputs: list of reactives: page_start, page_end, is_plot_searched, num_pages -page_and_searcher_server <- function(id, search_subject, plot_outputs, plots_per_page, ns_parent) { +page_and_searcher_server <- function(id, search_subject, plot_outputs, plots_per_page) { moduleServer(id, function(input, output, session) { ns <- session$ns diff --git a/inst/shiny/modules/tab_nca/setup/slope_selector.R b/inst/shiny/modules/tab_nca/setup/slope_selector.R index 54eff8e81..f3da00af4 100644 --- a/inst/shiny/modules/tab_nca/setup/slope_selector.R +++ b/inst/shiny/modules/tab_nca/setup/slope_selector.R @@ -18,7 +18,7 @@ slope_selector_ui <- function(id) { div( class = "slope-selector-module", - manual_slopes_table_ui(ns("manual_slopes")), + handle_slopes_table_ui(ns("manual_slopes")), # Help widget # dropdown( div( @@ -155,8 +155,7 @@ slope_selector_server <- function( # nolint id = "page_and_searcher", search_subject = search_subject_r, plot_outputs = plot_outputs, - plots_per_page = plots_per_page_r, - ns_parent = ns + plots_per_page = plots_per_page_r ) # Render only the plots requested by the user (using the subject searcher and pagination) @@ -168,29 +167,27 @@ slope_selector_server <- function( # nolint }) }) - # Creates an initial version of the manual slope adjustments table with pknca_data - # and handles the addition and deletion of rows through the UI - slopes_table <- manual_slopes_table_server("manual_slopes", pknca_data, manual_slopes_override) - manual_slopes <- slopes_table$manual_slopes - refresh_reactable <- slopes_table$refresh_reactable + # Creates an initial version of the manual slope adjustments table with pknca_data + # and handles the addition and deletion of rows through the UI + slopes_table <- handle_slopes_table_server("manual_slopes", pknca_data, manual_slopes_override) + manual_slopes <- slopes_table$manual_slopes + refresh_reactable <- slopes_table$refresh_reactable # Define the click events for the point exclusion and selection in the slope plots last_click_data <- reactiveVal(NULL) - observeEvent(event_data("plotly_click", priority = "event"), { log_trace("slope_selector: plotly click detected") - result <- handle_plotly_click( + click_result <- handle_plotly_click( last_click_data, manual_slopes, event_data("plotly_click"), - pknca_data(), - plot_outputs() + pknca_data() ) # Update reactive values in the observer - last_click_data(result$last_click_data) - plot_outputs(result$plot_outputs) - manual_slopes(result$manual_slopes) + last_click_data(click_result$last_click_data) + manual_slopes(click_result$manual_slopes) + # render rectable anew # shinyjs::runjs("memory = {};") # needed to properly reset reactable.extras widgets refresh_reactable(refresh_reactable() + 1) @@ -210,7 +207,7 @@ slope_selector_server <- function( # nolint ) }) - # Manual slopes override logic moved to manual_slopes_table_server + # Manual slopes override logic moved to handle_slopes_table_server #' return reactive with slope exclusions data to be displayed in Results -> Exclusions tab list( From f4317e2a31ac0fdffb5ec0c0c3f027de49e11780 Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Thu, 11 Sep 2025 16:27:47 +0200 Subject: [PATCH 035/113] fix: issue when changing intervals and plot_outputs() --- R/utils-slope_selector.R | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/R/utils-slope_selector.R b/R/utils-slope_selector.R index 72ab8b178..5891846a6 100644 --- a/R/utils-slope_selector.R +++ b/R/utils-slope_selector.R @@ -29,13 +29,19 @@ handle_hl_adj <- function(new_pknca_data, old_pknca_data, plot_outputs) { } handle_interval_change <- function(new_pknca_data, old_pknca_data, plot_outputs) { - new_intervals <- anti_join(new_pknca_data$intervals, old_pknca_data$intervals) - rm_intervals <- anti_join(old_pknca_data$intervals, new_pknca_data$intervals) + new_intervals <- anti_join( + new_pknca_data$intervals, old_pknca_data$intervals, + by = intersect(names(new_pknca_data$intervals), names(old_pknca_data$intervals)) + ) + rm_intervals <- anti_join( + old_pknca_data$intervals, new_pknca_data$intervals, + by = intersect(names(new_pknca_data$intervals), names(old_pknca_data$intervals)) + ) if (nrow(new_intervals) > 0) { affected_groups <- new_intervals %>% select(any_of(c(group_vars(new_pknca_data), "NCA_PROFILE"))) %>% distinct() - .update_plots_with_pknca(new_pknca_data, plot_outputs(), affected_groups) + .update_plots_with_pknca(new_pknca_data, plot_outputs, affected_groups) } if (nrow(rm_intervals) > 0) { rm_plot_names <- rm_intervals %>% @@ -50,7 +56,7 @@ handle_interval_change <- function(new_pknca_data, old_pknca_data, plot_outputs) } )) %>% pull(id) - plot_outputs[!names(plot_outputs()) %in% rm_plot_names] + plot_outputs[!names(plot_outputs) %in% rm_plot_names] } } From 1f425f769c7022fdc4a78c638ed0c6a4fe93a040 Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Thu, 11 Sep 2025 17:35:27 +0200 Subject: [PATCH 036/113] refactor: add more docs, comments & rename hadnle_slopes_table > handle_table_edits --- R/utils-slope_selector.R | 282 +++++++++--------- inst/shiny/functions/handle_plotly_click.R | 95 +++--- ...le_slopes_table.R => handle_table_edits.R} | 70 +++-- .../modules/tab_nca/setup/slope_selector.R | 42 ++- 4 files changed, 254 insertions(+), 235 deletions(-) rename inst/shiny/modules/tab_nca/setup/{handle_slopes_table.R => handle_table_edits.R} (73%) diff --git a/R/utils-slope_selector.R b/R/utils-slope_selector.R index 5891846a6..e0963d698 100644 --- a/R/utils-slope_selector.R +++ b/R/utils-slope_selector.R @@ -1,7 +1,18 @@ -## Functions to handle plot generation based on PKNCA data changes -# --- Helper: Detect changes between old and new PKNCA data --- +#' Slope Selector Utility Functions +#' +#' These helpers support the slope selection workflow by detecting changes in PKNCA data, +#' updating plots, and managing slope rule logic. Used internally by the slope selector module. + +#' Detect changes between old and new PKNCA data +#' +#' Compares two PKNCA data objects to determine if the underlying data, half-life adjustment columns, +#' or selected intervals have changed. Used to decide when to update plots. +#' @param old Previous PKNCA data object +#' @param new New PKNCA data object +#' @param excl_hl_col Name of exclusion column +#' @param incl_hl_col Name of inclusion column +#' @return List with logicals: in_data, in_hl_adj, in_selected_intervals detect_pknca_data_changes <- function(old, new, excl_hl_col, incl_hl_col) { - browser() list( in_data = if (is.null(old) & !is.null(new)) TRUE else { !identical( @@ -15,6 +26,14 @@ detect_pknca_data_changes <- function(old, new, excl_hl_col, incl_hl_col) { ) } + +#' Handle half-life adjustment changes +#' +#' Updates only the plots affected by changes in half-life inclusion/exclusion columns. +#' @param new_pknca_data New PKNCA data object +#' @param old_pknca_data Previous PKNCA data object +#' @param plot_outputs Current plot outputs (named list) +#' @return Updated plot_outputs (named list) handle_hl_adj <- function(new_pknca_data, old_pknca_data, plot_outputs) { excl_hl_col <- new_pknca_data$conc$columns$exclude_half.life incl_hl_col <- new_pknca_data$conc$columns$include_half.life @@ -28,6 +47,14 @@ handle_hl_adj <- function(new_pknca_data, old_pknca_data, plot_outputs) { .update_plots_with_pknca(new_pknca_data, plot_outputs, affected_groups) } + +#' Handle interval changes +#' +#' Updates plots when the set of selected intervals changes (e.g., analyte/profile selection). +#' @param new_pknca_data New PKNCA data object +#' @param old_pknca_data Previous PKNCA data object +#' @param plot_outputs Current plot outputs (named list) +#' @return Updated plot_outputs (named list) handle_interval_change <- function(new_pknca_data, old_pknca_data, plot_outputs) { new_intervals <- anti_join( new_pknca_data$intervals, old_pknca_data$intervals, @@ -60,147 +87,126 @@ handle_interval_change <- function(new_pknca_data, old_pknca_data, plot_outputs) } } - #' Check overlap between existing and new slope rulesets - #' - #' Takes in tables with existing and incoming selections and exclusions, finds any overlap and - #' differences, edits the ruleset table accordingly. - #' - #' @param existing Data frame with existing selections and exclusions. - #' @param new Data frame with new rule to be added or removed. - #' @param slope_groups List with column names that define the groups. - #' @param .keep Whether to force keep fully overlapping rulesets. If FALSE, it will be assumed - #' that the user wants to remove rule if new range already exists in the dataset. - #' If TRUE, in that case full range will be kept. - #' @returns Data frame with full ruleset, adjusted for new rules. - #' @export - check_slope_rule_overlap <- function(existing, new, .keep = FALSE) { - print("check_slope_rule_overlap") - slope_groups <- setdiff(names(new), c("TYPE", "RANGE", "REASON")) - - # check if any rule already exists for specific subject and profile # - existing_index <- which( - existing$TYPE == new$TYPE & - Reduce(`&`, lapply(slope_groups, function(col) { - existing[[col]] == new[[col]] - })) - ) - - if (length(existing_index) != 1) { - if (length(existing_index) > 1) - warning("More than one range for single subject, profile and rule type detected.") - return(rbind(existing, new)) - } - - existing_range <- .eval_range(existing$RANGE[existing_index]) - new_range <- .eval_range(new$RANGE) - - is_inter <- length(intersect(existing_range, new_range)) != 0 - is_diff <- length(setdiff(new_range, existing_range)) != 0 - - if (is_diff || .keep) { - existing$RANGE[existing_index] <- unique(c(existing_range, new_range)) %>% - .compress_range() - - } else if (is_inter) { - existing$RANGE[existing_index] <- setdiff(existing_range, new_range) %>% - .compress_range() - } - - dplyr::filter(existing, !is.na(RANGE)) + +#' Check overlap between existing and new slope rulesets +#' +#' Takes in tables with existing and incoming selections and exclusions, finds any overlap and +#' differences, edits the ruleset table accordingly. +#' @param existing Data frame with existing selections and exclusions. +#' @param new Data frame with new rule to be added or removed. +#' @param .keep Whether to force keep fully overlapping rulesets. If FALSE, it will be assumed +#' that the user wants to remove rule if new range already exists in the dataset. +#' If TRUE, in that case full range will be kept. +#' @return Data frame with full ruleset, adjusted for new rules. +#' @export +check_slope_rule_overlap <- function(existing, new, .keep = FALSE) { + slope_groups <- setdiff(names(new), c("TYPE", "RANGE", "REASON")) + # check if any rule already exists for specific subject and profile + existing_index <- which( + existing$TYPE == new$TYPE & + Reduce(`&`, lapply(slope_groups, function(col) { + existing[[col]] == new[[col]] + })) + ) + if (length(existing_index) != 1) { + if (length(existing_index) > 1) + warning("More than one range for single subject, profile and rule type detected.") + return(rbind(existing, new)) + } + existing_range <- .eval_range(existing$RANGE[existing_index]) + new_range <- .eval_range(new$RANGE) + is_inter <- length(intersect(existing_range, new_range)) != 0 + is_diff <- length(setdiff(new_range, existing_range)) != 0 + if (is_diff || .keep) { + existing$RANGE[existing_index] <- unique(c(existing_range, new_range)) %>% + .compress_range() + } else if (is_inter) { + existing$RANGE[existing_index] <- setdiff(existing_range, new_range) %>% + .compress_range() } + dplyr::filter(existing, !is.na(RANGE)) +} - #' Apply Slope Rules to Update Data - #' - #' This function iterates over the given slopes and updates the `data$conc$data` object - #' by setting inclusion or exclusion flags based on the slope conditions. - #' - #' @param data A list containing concentration data (`data$conc$data`) with columns that - #' need to be updated based on the slope rules. - #' @param slopes A data frame containing slope rules, including `TYPE`, `RANGE`, - #' and `REASON` columns. May also have grouping columns (expected to match slope_groups) - #' @param slope_groups A character vector specifying the group columns used for filtering. - #' - #' @returns description The modified `data` object with updated inclusion/exclusion flags - #' and reasons in `data$conc$data`. - .update_pknca_with_rules <- function(data, slopes) { - print(".update_pknca_with_rules") - # TODO (Gerardo): If we keep RANGE as a time, we don't need NCA_PROFILE - slope_groups <- group_vars(data) - time_col <- data$conc$columns$time - exclude_hl_col <- data$conc$columns$exclude_half.life - include_hl_col <- data$conc$columns$include_half.life - browser() - # Apply each rule action - for (i in seq_len(nrow(slopes))) { - # Determine the time range for the points adjusted - range <- strsplit(as.character(slopes$RANGE[i]), ":")[[1]] %>% - as.numeric() %>% - range() - - # Build the condition dynamically for group columns and time range - pnt_idx <- which( - Reduce(`&`, lapply(slope_groups, function(col) { - data$conc$data[[col]] == slopes[[col]][i] - })) & - between(data$conc$data[[time_col]], range[[1]], range[[2]]) - ) - - if (slopes$TYPE[i] == "Selection") { - data$conc$data[[include_hl_col]][pnt_idx] <- TRUE - } else if (slopes$TYPE[i] == "Exclusion") { - data$conc$data[[exclude_hl_col]][pnt_idx] <- TRUE - } else { - stop("Unknown TYPE in slopes: ", slopes$TYPE[i]) - } - - data$conc$data$REASON[pnt_idx] <- paste0( - data$conc$data$REASON[pnt_idx], - rep(slopes$REASON[i], length(pnt_idx)) - ) - print(".update_pknca_with_rules") +#' Apply Slope Rules to Update Data +#' +#' Iterates over the given slopes and updates the data$conc$data object by setting inclusion/exclusion flags. +#' @param data PKNCA data object +#' @param slopes Data frame of slope rules (TYPE, RANGE, REASON, group columns) +#' @return Modified data object with updated flags +.update_pknca_with_rules <- function(data, slopes) { + slope_groups <- group_vars(data) + time_col <- data$conc$columns$time + exclude_hl_col <- data$conc$columns$exclude_half.life + include_hl_col <- data$conc$columns$include_half.life + for (i in seq_len(nrow(slopes))) { + # Determine the time range for the points adjusted + range <- strsplit(as.character(slopes$RANGE[i]), ":")[[1]] %>% + as.numeric() %>% + range() + # Build the condition dynamically for group columns and time range + pnt_idx <- which( + Reduce(`&`, lapply(slope_groups, function(col) { + data$conc$data[[col]] == slopes[[col]][i] + })) & + between(data$conc$data[[time_col]], range[[1]], range[[2]]) + ) + if (slopes$TYPE[i] == "Selection") { + data$conc$data[[include_hl_col]][pnt_idx] <- TRUE + } else if (slopes$TYPE[i] == "Exclusion") { + data$conc$data[[exclude_hl_col]][pnt_idx] <- TRUE + } else { + stop("Unknown TYPE in slopes: ", slopes$TYPE[i]) } - - data + data$conc$data$REASON[pnt_idx] <- paste0( + data$conc$data$REASON[pnt_idx], + rep(slopes$REASON[i], length(pnt_idx)) + ) } + data +} - # Refactored: .update_plots_with_rules now uses .update_pknca_with_rules and delegates to .update_plots_with_pknca - .update_plots_with_rules <- function(pknca_data, manual_slopes, plot_outputs, slopes_to_update = NULL) { - print(".update_plots_with_rules (refactored)") - # Update the PKNCA object with manual slopes - pknca_for_plots <- .update_pknca_with_rules(pknca_data, manual_slopes) - # If the user does not specify which plots to update (NULL), update all plots in the manual slopes table - if (is.null(slopes_to_update)) { - slopes_to_update <- manual_slopes %>% - select(any_of(c(group_vars(pknca_for_plots), "NCA_PROFILE"))) %>% - distinct() - print(names(plot_outputs)) - } - # If the user specify that there are no plots to update (empty data.frame), then do not update - if (nrow(slopes_to_update) == 0) return(plot_outputs) - # Delegate to .update_plots_with_pknca for actual plot updating - .update_plots_with_pknca(pknca_for_plots, plot_outputs, intervals_to_update = slopes_to_update) + +#' Update plots with manual slope rules +#' +#' Updates the plot_outputs list by applying manual slopes to the PKNCA data and regenerating affected plots. +#' @param pknca_data PKNCA data object +#' @param manual_slopes Data frame of manual slope rules +#' @param plot_outputs Named list of current plot outputs +#' @param slopes_to_update Optional: data frame of intervals to update (default: all in manual_slopes) +#' @return Updated plot_outputs (named list) +.update_plots_with_rules <- function(pknca_data, manual_slopes, plot_outputs, slopes_to_update = NULL) { + pknca_for_plots <- .update_pknca_with_rules(pknca_data, manual_slopes) + if (is.null(slopes_to_update)) { + slopes_to_update <- manual_slopes %>% + select(any_of(c(group_vars(pknca_for_plots), "NCA_PROFILE"))) %>% + distinct() } + if (nrow(slopes_to_update) == 0) return(plot_outputs) + .update_plots_with_pknca(pknca_for_plots, plot_outputs, intervals_to_update = slopes_to_update) +} - .update_plots_with_pknca <- function(pknca_data, plot_outputs, intervals_to_update = NULL) { - print(".update_plots_with_pknca") - # If the user does not specify which plots to update (NULL), update all plots in the manual slopes table - if (is.null(intervals_to_update)) { - intervals_to_update <- pknca_data$intervals %>% - select(any_of(c(group_vars(pknca_data), "NCA_PROFILE"))) %>% - distinct() - print(names(plot_outputs)) - } - # If the user specify that there are no plots to update (empty data.frame), then do not update - if (nrow(intervals_to_update) == 0) return(plot_outputs) - - # Get the intervals of the plots affected by the current rules - pknca_data$intervals <- inner_join( - intervals_to_update, - pknca_data$intervals, - by = c(group_vars(pknca_data), "NCA_PROFILE") - ) - updated_plots <- suppressWarnings(get_halflife_plot(pknca_data)) - plot_outputs[names(updated_plots)] <- updated_plots - plot_outputs +#' Update plots with PKNCA data (for affected intervals) +#' +#' Regenerates plots for the specified intervals in the plot_outputs list. +#' @param pknca_data PKNCA data object +#' @param plot_outputs Named list of current plot outputs +#' @param intervals_to_update Data frame of intervals to update (default: all in pknca_data) +#' @return Updated plot_outputs (named list) +.update_plots_with_pknca <- function(pknca_data, plot_outputs, intervals_to_update = NULL) { + if (is.null(intervals_to_update)) { + intervals_to_update <- pknca_data$intervals %>% + select(any_of(c(group_vars(pknca_data), "NCA_PROFILE"))) %>% + distinct() } + if (nrow(intervals_to_update) == 0) return(plot_outputs) + # Get the intervals of the plots affected by the current rules + pknca_data$intervals <- inner_join( + intervals_to_update, + pknca_data$intervals, + by = c(group_vars(pknca_data), "NCA_PROFILE") + ) + updated_plots <- suppressWarnings(get_halflife_plot(pknca_data)) + plot_outputs[names(updated_plots)] <- updated_plots + plot_outputs +} diff --git a/inst/shiny/functions/handle_plotly_click.R b/inst/shiny/functions/handle_plotly_click.R index 39c56a84b..d3877556a 100644 --- a/inst/shiny/functions/handle_plotly_click.R +++ b/inst/shiny/functions/handle_plotly_click.R @@ -1,22 +1,23 @@ -#' Update Last Click Data for Slope Selection -#' -#' Checks if the user clicked on a different plot or dataset and updates -#' `last_click_data` accordingly. If an update is needed, the function exits early. + +#' Handle Plotly Click for Slope Selection #' -#' @param last_click_data A reactive Values object storing the last clicked data. -#' @param manual_slopes A reactive Values object storing the manually added slope rules. -#' @param click_data A list containing the custom data from the plotly click event. -#' @param pknca_data A PKNCA data object containing concentration data and intervals. +#' This function processes a plotly click event in the slope selection UI. It determines whether the click +#' should add a new exclusion or selection rule to the manual slopes table, based on the user's interaction +#' (same point = exclusion, two points in same plot = selection). It updates the manual_slopes table accordingly. #' -#' @returns Returns a list with updated `last_click_data` and `manual_slopes`. +#' @param last_click_data A reactiveVal storing the last clicked plotly data (or NULL). +#' @param manual_slopes A reactiveVal storing the current manual slopes table (data.frame). +#' @param click_data The new plotly click event data (list with customdata). +#' @param pknca_data The current PKNCA data object (for context and group info). +#' @return List with updated last_click_data and manual_slopes. #' +#' @details +#' - If the user clicks the same point twice, an exclusion rule is added for that point. +#' - If the user clicks two points in the same interval, a selection rule is added for the range. +#' - Otherwise, no rule is added and the click is just stored. handle_plotly_click <- function(last_click_data, manual_slopes, click_data, pknca_data) { req(click_data, click_data$customdata) - print("current click:") - print(click_data) - print("last click:") - print(last_click_data) - # If there is no previous click to this one, store it and do nothing else + # If there is no previous click, store this click and do nothing else if (is.null(last_click_data())) { return(list( last_click_data = click_data, @@ -24,61 +25,63 @@ handle_plotly_click <- function(last_click_data, manual_slopes, click_data, pknc )) } - # Extract the information on the last and current points clicked + # Extract info for current and last click (group, interval, time, etc.) pnt <- .extract_click_info(click_data, pknca_data) lstpnt <- .extract_click_info(last_click_data(), pknca_data) - # Get relevant columns from data - excl_hl_col <- pknca_data$conc$columns$exclude_half.life - incl_hl_col <- pknca_data$conc$columns$include_half.life - time_col <- pknca_data$conc$columns$time -browser() - # Depending on the last click decide what half life adjustment rule to apply + # Decide what rule to add based on click context new_rule <- pnt$group - # If it is the same point, consider the point excluded if (pnt$idx == lstpnt$idx) { + # Same point clicked twice: add exclusion rule for that point new_rule$TYPE <- "Exclusion" new_rule$RANGE <- paste0(pnt$time) new_rule$REASON <- "" - - # If it is in the same plot (interval), consider all points in the time range included } else if (identical(pnt$int, lstpnt$int)) { + # Two points in same interval: add selection rule for the range new_rule$TYPE <- "Selection" new_rule$RANGE <- paste0(sort(c(pnt$time, lstpnt$time)), collapse = ":") new_rule$REASON <- "" } else { - # Do nothing + # Clicks not in same interval: just update last_click_data, no rule return(list( last_click_data = click_data, manual_slopes = manual_slopes() )) } - # Update manual_slopes without modifying it globally + # Add or update the rule in the manual_slopes table updated_slopes <- check_slope_rule_overlap(manual_slopes(), new_rule) - # Return updated values + # Return updated values (reset last_click_data to NULL to await next click) list( - last_click_data = NULL, # Action was finished and has to be updated + last_click_data = NULL, manual_slopes = updated_slopes ) } - #' Helper to extract click info for handle_plotly_click - #' - #' @param click_data List from plotly click event - #' @param pknca_data PKNCA data object - #' @return List with idx, time, row, int, group - .extract_click_info <- function(click_data, pknca_data) { - idx <- as.numeric(click_data$customdata$ROWID) - time <- as.numeric(click_data$x) - row <- pknca_data$conc$data[idx, ] - int <- pknca_data$intervals %>% - merge(row, by = c(group_vars(pknca_data))) %>% - filter(start <= row[[pknca_data$conc$columns$time]] & - end >= row[[pknca_data$conc$columns$time]]) %>% - select(any_of(names(pknca_data$intervals))) - group <- int %>% - select(any_of(c(group_vars(pknca_data), "NCA_PROFILE"))) - list(idx = idx, time = time, row = row, int = int, group = group) - } + +#' Extract Click Info for Slope Selection +#' +#' Helper for handle_plotly_click. Given plotly click data and PKNCA data, returns a list with: +#' - idx: row index in conc data +#' - time: time value of click +#' - row: full row from conc data +#' - int: interval(s) in which the point falls +#' - group: grouping columns for the interval +#' +#' @param click_data List from plotly click event +#' @param pknca_data PKNCA data object +#' @return List with idx, time, row, int, group +.extract_click_info <- function(click_data, pknca_data) { + idx <- as.numeric(click_data$customdata$ROWID) + time <- as.numeric(click_data$x) + row <- pknca_data$conc$data[idx, ] + int <- pknca_data$intervals %>% + merge(row, by = c(group_vars(pknca_data))) %>% + filter(start <= row[[pknca_data$conc$columns$time]] & + end >= row[[pknca_data$conc$columns$time]]) %>% + select(any_of(names(pknca_data$intervals))) + group <- int %>% + select(any_of(c(group_vars(pknca_data), "NCA_PROFILE"))) + list(idx = idx, time = time, row = row, int = int, group = group) +} diff --git a/inst/shiny/modules/tab_nca/setup/handle_slopes_table.R b/inst/shiny/modules/tab_nca/setup/handle_table_edits.R similarity index 73% rename from inst/shiny/modules/tab_nca/setup/handle_slopes_table.R rename to inst/shiny/modules/tab_nca/setup/handle_table_edits.R index 5baaf479c..9a4b3cbfb 100644 --- a/inst/shiny/modules/tab_nca/setup/handle_slopes_table.R +++ b/inst/shiny/modules/tab_nca/setup/handle_table_edits.R @@ -1,4 +1,12 @@ -handle_slopes_table_ui <- function(id) { + +#' Manual Slopes Table UI for Slope Selection +#' +#' UI module for displaying and editing the manual slopes table (inclusion/exclusion rules) in the slope selector workflow. +#' Provides buttons to add/remove rules and a reactable table for editing. +#' +#' @param id Shiny module id +#' @return Shiny UI element (fluidRow) +handle_table_edits_ui <- function(id) { ns <- NS(id) fluidRow( @@ -19,40 +27,47 @@ handle_slopes_table_ui <- function(id) { } -handle_slopes_table_server <- function( + +#' Manual Slopes Table Server for Slope Selection +#' +#' Server module for managing the manual slopes table (inclusion/exclusion rules) in the slope selector workflow. +#' Handles adding/removing/editing rules, table reactivity, and optional override logic. +#' +#' @param id Shiny module id +#' @param mydata Reactive providing the current PKNCA data object +#' @param manual_slopes_override Optional reactive providing a table to override manual slopes +#' @return List with: +#' - manual_slopes: reactiveVal containing the current manual slopes table +#' - refresh_reactable: reactiveVal for triggering table re-render +handle_table_edits_server <- function( id, mydata, manual_slopes_override = NULL ) { moduleServer(id, function(input, output, session) { ns <- session$ns + # Get group columns for the current PKNCA data (for table structure) slopes_pknca_groups <- reactive({ req(mydata()) mydata()$conc$data %>% select(any_of(c(group_vars(mydata()), "NCA_PROFILE"))) }) - #' Object for storing exclusion and selection data for lambda slope calculation - # TODO (Gerardo): Parameter selection is still affecting mydata() and re-creating the manual slopes! + # manual_slopes: stores the current table of user rules (inclusion/exclusion) manual_slopes <- reactiveVal({NULL}) + # When mydata() changes, reset the manual_slopes table to empty with correct columns observeEvent(mydata(), { - #browser() req(is.null(manual_slopes())) req(slopes_pknca_groups()) ms_colnames <- c(colnames(slopes_pknca_groups()), c("TYPE", "RANGE", "REASON")) initial_manual_slopes <- data.frame( matrix(character(), ncol = length(ms_colnames), nrow = 0, dimnames = list(character(), ms_colnames)) ) - - # Update the reactive Val manual_slopes(initial_manual_slopes) }) - #' Adds new row to the selection/exclusion datatable + # Add a new row to the table when the user clicks the add button observeEvent(input$add_rule, { - #browser() log_trace("{id}: adding manual slopes row") - - # Create the new row with both fixed and dynamic columns first_group <- slopes_pknca_groups()[1, ] new_row <- cbind( first_group, @@ -64,18 +79,15 @@ handle_slopes_table_server <- function( REASON = "" ) ) - updated_data <- as.data.frame(rbind(manual_slopes(), new_row), stringsAsFactors = FALSE) manual_slopes(updated_data) reset_reactable_memory() refresh_reactable(refresh_reactable() + 1) }) - #' Removes selected row + # Remove selected rows from the table when the user clicks the remove button observeEvent(input$remove_rule, { -#browser() log_trace("{id}: removing manual slopes row") - selected <- getReactableState("manual_slopes", "selected") req(selected) edited_slopes <- manual_slopes()[-selected, ] @@ -84,17 +96,15 @@ handle_slopes_table_server <- function( refresh_reactable(refresh_reactable() + 1) }) - #' Render manual slopes table + # Render the manual slopes table (reactable) refresh_reactable <- reactiveVal(1) output$manual_slopes <- renderReactable({ req(manual_slopes()) log_trace("{id}: rendering slope edit data table") - # Isolate to prevent unnecessary re-renders on every edit isolate({ data <- manual_slopes() }) - - # Fixed columns (TYPE, RANGE, REASON) + # Define columns: group columns (dynamic), then TYPE/RANGE/REASON (fixed) fixed_columns <- list( TYPE = colDef( cell = dropdown_extra( @@ -116,29 +126,21 @@ handle_slopes_table_server <- function( width = 400 ) ) - - # Dynamic group column definitions dynamic_columns <- lapply(colnames(slopes_pknca_groups()), function(col) { colDef( cell = dropdown_extra( id = ns(paste0("edit_", col)), - choices = unique(slopes_pknca_groups()[[col]]), # Dynamically set choices + choices = unique(slopes_pknca_groups()[[col]]), class = "dropdown-extra" ), width = 150 ) }) names(dynamic_columns) <- colnames(slopes_pknca_groups()) - - # Combine columns in the desired order all_columns <- c(dynamic_columns, fixed_columns) - - # Render reactable reactable( data = data, - defaultColDef = colDef( - align = "center" - ), + defaultColDef = colDef(align = "center"), columns = all_columns, selection = "multiple", defaultExpanded = TRUE, @@ -150,10 +152,10 @@ handle_slopes_table_server <- function( }) %>% shiny::bindEvent(refresh_reactable()) + # Dynamically attach observers for each editable column in the table observe({ req(manual_slopes()) - # Dynamically attach observers for each column - purrr::walk(colnames(manual_slopes()), \(colname) { + purrr::walk(colnames(manual_slopes()), function(colname) { observeEvent(input[[paste0("edit_", colname)]], { edit <- input[[paste0("edit_", colname)]] edited_slopes <- manual_slopes() @@ -167,12 +169,8 @@ handle_slopes_table_server <- function( if (!is.null(manual_slopes_override)) { observeEvent(manual_slopes_override(), { req(manual_slopes_override()) - if (nrow(manual_slopes_override()) == 0) return(NULL) - log_debug_list("Manual slopes override:", manual_slopes_override()) - - # Use mydata() for validation override_valid <- apply(manual_slopes_override(), 1, function(r) { dplyr::filter( mydata()$conc$data, @@ -185,7 +183,6 @@ handle_slopes_table_server <- function( NROW() != 0 }) |> all() - if (!override_valid) { msg <- "Manual slopes not compatible with current data, leaving as default." log_warn(msg) @@ -196,6 +193,7 @@ handle_slopes_table_server <- function( }) } + # Output: manual_slopes (reactiveVal) and refresh_reactable (for UI updates) list( manual_slopes = manual_slopes, refresh_reactable = refresh_reactable diff --git a/inst/shiny/modules/tab_nca/setup/slope_selector.R b/inst/shiny/modules/tab_nca/setup/slope_selector.R index f3da00af4..11520d25c 100644 --- a/inst/shiny/modules/tab_nca/setup/slope_selector.R +++ b/inst/shiny/modules/tab_nca/setup/slope_selector.R @@ -1,16 +1,29 @@ -#' NCA Slope selector module handling slope selection via interactive plots and tables. + +#' Slope Selector Module (Server/UI) +#' +#' This module manages the interactive selection and exclusion of NCA slope intervals via plots and a table UI. +#' It coordinates the display and update of plots, the manual slopes table, and the propagation of user changes. +#' +#' --- Reactivity/Data Flow Schema --- #' -#' Generates appropriate interface and gathers user input from the table, as well as interactive -#' plotly plots, with with user can define inclusions and exclusions for the data. +#' processed_pknca_data (input, from parent) +#' └─> slope_selector_server +#' ├──> plot_outputs (reactive, updated by processed_pknca_data changes) +#' └──> ├> handle_plotly_click (updates manual_slopes on plot click) +#' â””> handle_table_edits_server (updates manual_slopes on user actions & setting overrides) +#' └─> manual_slopes (output, used by parent to update processed_pknca_data) #' -#' @param id ID of the module. -#' @param pknca_data `PKNCAdata` object with data to base the plots on. -#' @param manual_slopes_override Reactive experssion with override for the manual slopes selection. -#' If changes are detected, current settings will be overwritten with -#' values from that reactive. #' -#' @returns List with reactive expressions: -#' * manual_slopes - Data frame containing inclusions / exclusions. +#' @param id Character. Shiny module id. +#' @param processed_pknca_data Reactive. PKNCAdata object for plotting and table context. +#' @param manual_slopes_override Reactive. Optional custom settings override for the slopes table. +#' @return manual_slopes (data.frame of user slope inclusions/exclusions) +#' +#' @details +#' - The module's main output is the manual_slopes table, which is updated by user actions in the table UI (handle_table_edits) or by clicking on plots (handle_plotly_click). +#' - The parent module (setup.R) should use manual_slopes to update processed_pknca_data, which is then fed back into this module to update plots and table context. +#' - All plot and table reactivity is encapsulated here; parent modules only need to provide processed_pknca_data and consume manual_slopes. + slope_selector_ui <- function(id) { ns <- NS(id) @@ -18,7 +31,7 @@ slope_selector_ui <- function(id) { div( class = "slope-selector-module", - handle_slopes_table_ui(ns("manual_slopes")), + handle_table_edits_ui(ns("manual_slopes")), # Help widget # dropdown( div( @@ -169,7 +182,7 @@ slope_selector_server <- function( # nolint # Creates an initial version of the manual slope adjustments table with pknca_data # and handles the addition and deletion of rows through the UI - slopes_table <- handle_slopes_table_server("manual_slopes", pknca_data, manual_slopes_override) + slopes_table <- handle_table_edits_server("manual_slopes", pknca_data, manual_slopes_override) manual_slopes <- slopes_table$manual_slopes refresh_reactable <- slopes_table$refresh_reactable @@ -184,7 +197,7 @@ slope_selector_server <- function( # nolint event_data("plotly_click"), pknca_data() ) - # Update reactive values in the observer + # Update reactive values: last click & manual slopes table last_click_data(click_result$last_click_data) manual_slopes(click_result$manual_slopes) @@ -198,7 +211,6 @@ slope_selector_server <- function( # nolint #' the table would mean losing focus on text inputs when entering values. observeEvent(manual_slopes(), { req(manual_slopes()) - print("observeEvent manual_slopes()") # Update reactable with rules reactable::updateReactable( @@ -207,7 +219,7 @@ slope_selector_server <- function( # nolint ) }) - # Manual slopes override logic moved to handle_slopes_table_server + # Manual slopes override logic moved to handle_table_edits_server #' return reactive with slope exclusions data to be displayed in Results -> Exclusions tab list( From 569402e0acb14fc606ee67d16aa57c4dc8fa5232 Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Thu, 11 Sep 2025 17:58:43 +0200 Subject: [PATCH 037/113] fix: issue with interval change when removing --- R/utils-slope_selector.R | 13 +++++++------ inst/shiny/modules/tab_nca/setup/slope_selector.R | 2 +- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/R/utils-slope_selector.R b/R/utils-slope_selector.R index e0963d698..7c7178c08 100644 --- a/R/utils-slope_selector.R +++ b/R/utils-slope_selector.R @@ -34,7 +34,7 @@ detect_pknca_data_changes <- function(old, new, excl_hl_col, incl_hl_col) { #' @param old_pknca_data Previous PKNCA data object #' @param plot_outputs Current plot outputs (named list) #' @return Updated plot_outputs (named list) -handle_hl_adj <- function(new_pknca_data, old_pknca_data, plot_outputs) { +handle_hl_adj_change <- function(new_pknca_data, old_pknca_data, plot_outputs) { excl_hl_col <- new_pknca_data$conc$columns$exclude_half.life incl_hl_col <- new_pknca_data$conc$columns$include_half.life affected_groups <- anti_join( @@ -66,9 +66,9 @@ handle_interval_change <- function(new_pknca_data, old_pknca_data, plot_outputs) ) if (nrow(new_intervals) > 0) { affected_groups <- new_intervals %>% - select(any_of(c(group_vars(new_pknca_data), "NCA_PROFILE"))) %>% + select(any_of(c(group_vars(new_pknca_data), "start", "end"))) %>% distinct() - .update_plots_with_pknca(new_pknca_data, plot_outputs, affected_groups) + plot_outputs <- .update_plots_with_pknca(new_pknca_data, plot_outputs, affected_groups) } if (nrow(rm_intervals) > 0) { rm_plot_names <- rm_intervals %>% @@ -83,8 +83,9 @@ handle_interval_change <- function(new_pknca_data, old_pknca_data, plot_outputs) } )) %>% pull(id) - plot_outputs[!names(plot_outputs) %in% rm_plot_names] + plot_outputs <- plot_outputs[!names(plot_outputs) %in% rm_plot_names] } + plot_outputs } @@ -196,7 +197,7 @@ check_slope_rule_overlap <- function(existing, new, .keep = FALSE) { .update_plots_with_pknca <- function(pknca_data, plot_outputs, intervals_to_update = NULL) { if (is.null(intervals_to_update)) { intervals_to_update <- pknca_data$intervals %>% - select(any_of(c(group_vars(pknca_data), "NCA_PROFILE"))) %>% + select(any_of(c(group_vars(pknca_data), "start", "end"))) %>% distinct() } if (nrow(intervals_to_update) == 0) return(plot_outputs) @@ -204,7 +205,7 @@ check_slope_rule_overlap <- function(existing, new, .keep = FALSE) { pknca_data$intervals <- inner_join( intervals_to_update, pknca_data$intervals, - by = c(group_vars(pknca_data), "NCA_PROFILE") + by = c(group_vars(pknca_data), "start", "end") ) updated_plots <- suppressWarnings(get_halflife_plot(pknca_data)) plot_outputs[names(updated_plots)] <- updated_plots diff --git a/inst/shiny/modules/tab_nca/setup/slope_selector.R b/inst/shiny/modules/tab_nca/setup/slope_selector.R index 11520d25c..a8217f49a 100644 --- a/inst/shiny/modules/tab_nca/setup/slope_selector.R +++ b/inst/shiny/modules/tab_nca/setup/slope_selector.R @@ -133,7 +133,7 @@ slope_selector_server <- function( # nolint # If relevant, modify plots that had new half-life adjustments # (inclusions/exclusions from manual_slope_table) } else if (changes$in_hl_adj) { - plot_outputs(handle_hl_adj(new_pknca_data, pknca_data(), plot_outputs())) + plot_outputs(handle_hl_adj_change(new_pknca_data, pknca_data(), plot_outputs())) # If relevant, modify plots that had interval changes # (analyte, profile, specimen selection from setup.R settings) } else if (changes$in_selected_intervals) { From e7af2a738435dd93166aed6ed380f65cc39ee805 Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Thu, 11 Sep 2025 18:14:20 +0200 Subject: [PATCH 038/113] fix: issue when joining intervals with fixed names --- R/utils-slope_selector.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/utils-slope_selector.R b/R/utils-slope_selector.R index 7c7178c08..5fd2d0d98 100644 --- a/R/utils-slope_selector.R +++ b/R/utils-slope_selector.R @@ -205,7 +205,7 @@ check_slope_rule_overlap <- function(existing, new, .keep = FALSE) { pknca_data$intervals <- inner_join( intervals_to_update, pknca_data$intervals, - by = c(group_vars(pknca_data), "start", "end") + by = intersect(names(intervals_to_update), names(pknca_data$intervals)) ) updated_plots <- suppressWarnings(get_halflife_plot(pknca_data)) plot_outputs[names(updated_plots)] <- updated_plots From b2b5a6455e798c039686416771c194744edf850e Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Thu, 11 Sep 2025 18:14:56 +0200 Subject: [PATCH 039/113] draft: try to incorporate slope plots in zip file (error due to names) --- inst/shiny/modules/tab_nca/setup/slope_selector.R | 1 + 1 file changed, 1 insertion(+) diff --git a/inst/shiny/modules/tab_nca/setup/slope_selector.R b/inst/shiny/modules/tab_nca/setup/slope_selector.R index a8217f49a..a16ee0e9d 100644 --- a/inst/shiny/modules/tab_nca/setup/slope_selector.R +++ b/inst/shiny/modules/tab_nca/setup/slope_selector.R @@ -178,6 +178,7 @@ slope_selector_server <- function( # nolint shinyjs::enable(selector = ".btn-page") plot_outputs()[page_search$is_plot_searched()][page_search$page_start():page_search$page_end()] }) + session$userData$results$slope_selector <- plot_outputs() }) # Creates an initial version of the manual slope adjustments table with pknca_data From f3f226914a24f4c29b83448c39c491ca9117c55c Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Fri, 12 Sep 2025 07:07:07 +0200 Subject: [PATCH 040/113] feat: include a group order for the plots --- R/utils-slope_selector.R | 30 ++++++++++ .../modules/tab_nca/setup/slope_selector.R | 56 +++++++++++++++++-- 2 files changed, 82 insertions(+), 4 deletions(-) diff --git a/R/utils-slope_selector.R b/R/utils-slope_selector.R index 5fd2d0d98..0d75fa0e5 100644 --- a/R/utils-slope_selector.R +++ b/R/utils-slope_selector.R @@ -88,6 +88,36 @@ handle_interval_change <- function(new_pknca_data, old_pknca_data, plot_outputs) plot_outputs } +parse_plot_names_to_df <- function(named_list) { + plot_names <- names(named_list) + parsed <- lapply(plot_names, function(x) { + pairs <- strsplit(x, ",\\s*")[[1]] + kv <- strsplit(pairs, ":\\s*") + setNames( + vapply(kv, function(y) y[2], character(1)), + vapply(kv, function(y) y[1], character(1)) + ) + }) + as.data.frame(do.call(rbind, parsed), stringsAsFactors = FALSE) %>% + mutate(PLOTID = names(named_list)) +} + +arrange_plots_by_groups <- function(named_list, group_cols) { + plot_df <- parse_plot_names_to_df(named_list) + arranged_df <- plot_df %>% + arrange(across(all_of(group_cols))) + named_list[arranged_df$PLOTID] +} + +# prepare_virtual_select_df <- function(pknca_data) { +# pknca_data$intervals %>% +# select(any_of(c(group_vars(pknca_data)))) %>% +# pivot_longer(cols = group_vars(pknca_data), names_to = "var") %>% +# distinct() %>% +# mutate(var_and_value = paste0(var, ": ", value)) %>% +# distinct() +# } + #' Check overlap between existing and new slope rulesets #' diff --git a/inst/shiny/modules/tab_nca/setup/slope_selector.R b/inst/shiny/modules/tab_nca/setup/slope_selector.R index a16ee0e9d..458b14bac 100644 --- a/inst/shiny/modules/tab_nca/setup/slope_selector.R +++ b/inst/shiny/modules/tab_nca/setup/slope_selector.R @@ -88,7 +88,23 @@ slope_selector_ui <- function(id) { label = "Search Subject", choices = NULL, multiple = TRUE - ) + ), + # shinyWidgets::virtualSelectInput( + # ns("search_group"), + # label = "Search Group", + # choices = NULL, + # multiple = TRUE, + # search = TRUE, + # placeholder = "Select group(s)...", + # noOfDisplayValues = 3 + # ), + ) + ), + fluidRow( + orderInput( + ns("order_groups"), + label = "Order plots by:", + items = NULL ) ), br(), @@ -148,6 +164,27 @@ slope_selector_server <- function( # nolint label = "Search Subject", choices = unique(new_pknca_data$intervals$USUBJID) ) + #group_options_df <- prepare_virtual_select_df(new_pknca_data) + # shinyWidgets::updateVirtualSelect( + # session = session, + # inputId = "search_group", + # label = "Search Group", + # choices = shinyWidgets::prepare_choices( + # group_options_df, + # label = value, + # value = var_and_value, + # alias = value, + # group_by = var + # ), + # selected = group_options_df$var_and_value + # ) + } + if (changes$in_data) { + updateOrderInput( + session = session, + inputId = "order_groups", + items = group_vars(new_pknca_data) + ) } # Update the object @@ -171,14 +208,25 @@ slope_selector_server <- function( # nolint plots_per_page = plots_per_page_r ) - # Render only the plots requested by the user (using the subject searcher and pagination) + # Render only the plots requested by the user observe({ req(plot_outputs()) + # browser() + # prepare_virtual_select_df(pknca_data()) %>% + # filter(var_and_value %in% input$search_group) %>% + # pull() + # sapply(input$search_group, \(x) grep(x, names(plot_outputs()), value = TRUE)) output$slope_plots_ui <- renderUI({ shinyjs::enable(selector = ".btn-page") - plot_outputs()[page_search$is_plot_searched()][page_search$page_start():page_search$page_end()] + plot_outputs() %>% + # Filter plots based on user search + .[page_search$is_plot_searched()] %>% + # Arrange plots by the specified group order + arrange_plots_by_groups(input$order_groups) %>% + # Display only the plots for the current page + .[page_search$page_start():page_search$page_end()] }) - session$userData$results$slope_selector <- plot_outputs() +# session$userData$results$slope_selector <- plot_outputs() }) # Creates an initial version of the manual slope adjustments table with pknca_data From 7b1e69a67229623c1d25cce14d3cf2fa5ab73c0b Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Fri, 12 Sep 2025 08:34:29 +0200 Subject: [PATCH 041/113] refactor: lintr --- R/lambda_slope_plot.R | 102 ++++++++++----- R/utils-slope_selector.R | 123 ++++++++++++------ inst/shiny/functions/handle_plotly_click.R | 17 ++- .../modules/tab_nca/descriptive_statistics.R | 1 - .../tab_nca/setup/handle_table_edits.R | 32 +++-- .../modules/tab_nca/setup/page_and_searcher.R | 11 +- .../modules/tab_nca/setup/slope_selector.R | 62 ++------- 7 files changed, 210 insertions(+), 138 deletions(-) diff --git a/R/lambda_slope_plot.R b/R/lambda_slope_plot.R index ea3498356..935243f3d 100644 --- a/R/lambda_slope_plot.R +++ b/R/lambda_slope_plot.R @@ -1,5 +1,5 @@ -get_halflife_plot <- function(pknca_data, add_annotations = TRUE) { +get_halflife_plot <- function(pknca_data, add_annotations = TRUE) { #nolint pknca_data$conc$data$ROWID <- seq_len(nrow(pknca_data$conc$data)) o_nca <- suppressWarnings(PKNCA::pk.nca(pknca_data)) if (!"PPSTRES" %in% names(o_nca$result)) { @@ -12,17 +12,21 @@ get_halflife_plot <- function(pknca_data, add_annotations = TRUE) { unique() %>% mutate( PPORRES = ifelse( - PPTESTCD %in% c("lambda.z.time.first", "lambda.z.time.last", "tlast"), + PPTESTCD %in% c( + "lambda.z.time.first", "lambda.z.time.last", "tlast" + ), PPORRES + start, PPORRES ), PPSTRES = ifelse( - PPTESTCD %in% c("lambda.z.time.first", "lambda.z.time.last", "tlast"), + PPTESTCD %in% c( + "lambda.z.time.first", "lambda.z.time.last", "tlast" + ), PPSTRES + start, PPSTRES ) ) - + groups <- o_nca$result %>% select(any_of(c(group_vars(o_nca), "start", "end", "PPTESTCD"))) %>% dplyr::filter(PPTESTCD == "lambda.z") %>% @@ -43,7 +47,7 @@ get_halflife_plot <- function(pknca_data, add_annotations = TRUE) { o_nca$data$conc$data[["exclude_half.life"]] <- FALSE exclude_hl_col <- "exclude_half.life" } - + include_hl_col <- o_nca$data$conc$columns$include_half.life if (is.null(include_hl_col)) { o_nca$data$conc$data[["include_half.life"]] <- FALSE @@ -58,16 +62,20 @@ get_halflife_plot <- function(pknca_data, add_annotations = TRUE) { # Create a key for each row in conc data and in groups conc_data <- o_nca$data$conc$data if (length(group_vars_all) > 0) { - conc_data$.__groupkey__ <- apply(conc_data[, group_vars_all, drop = FALSE], 1, function(row) paste(row, collapse = "|")) - groups$.__groupkey__ <- apply(groups[, group_vars_all, drop = FALSE], 1, function(row) paste(row, collapse = "|")) + conc_data$.__groupkey__ <- apply( + conc_data[, group_vars_all, drop = FALSE], 1, function(row) paste(row, collapse = "|") + ) + groups$.__groupkey__ <- apply( + groups[, group_vars_all, drop = FALSE], 1, function(row) paste(row, collapse = "|") + ) } else { conc_data$.__groupkey__ <- "__all__" groups$.__groupkey__ <- "__all__" } # Pre-split conc_data by group key conc_data_split <- split(conc_data, conc_data$.__groupkey__) - - + + # 2. Build the first plot fully i <- 1 group <- groups[i, ] @@ -85,13 +93,13 @@ get_halflife_plot <- function(pknca_data, add_annotations = TRUE) { start <- unique(group_nca$result$start) tlast <- get_res("tlast") half_life <- get_res("half.life") - adj.r.squared <- get_res("adj.r.squared") + adj_r2 <- get_res("adj.r.squared") lz_time_first <- get_res("lambda.z.time.first") lz_time_last <- get_res("lambda.z.time.last") time_span <- lz_time_last - lz_time_first span_ratio <- get_res("span.ratio") exclude_calc_reason <- group_nca$result$exclude[group_nca$result$PPTESTCD == "half.life"] - + if (!is.na(half_life)) { is_lz_used <- get_halflife_points2( group_nca$data$conc$data, @@ -102,7 +110,7 @@ get_halflife_plot <- function(pknca_data, add_annotations = TRUE) { exclude_hl_col ) df_fit <- df[is_lz_used, ] - fit <- lm(as.formula(paste0("log10(", conc_col,") ~ ", time_col)), df_fit) + fit <- lm(as.formula(paste0("log10(", conc_col, ") ~ ", time_col)), df_fit) fit_line_data <- data.frame(x = c(lz_time_first, tlast)) colnames(fit_line_data) <- time_col fit_line_data$y <- predict(fit, fit_line_data) @@ -114,7 +122,7 @@ get_halflife_plot <- function(pknca_data, add_annotations = TRUE) { ) colnames(fit_line_data)[1] <- time_col } - + plot_data <- df # 4. Vectorize color, symbol, text assignment plot_data$color <- ifelse(is.na(is_lz_used), "black", @@ -128,11 +136,24 @@ get_halflife_plot <- function(pknca_data, add_annotations = TRUE) { signif(plot_data[[conc_col]], 3), ")" ) - title <- paste0(paste0(group_vars, ": "), group[, group_vars, drop = FALSE], collapse = ", ") - xlab <- if (!is.null(timeu_col)) paste0(time_col, " (", unique(plot_data[[timeu_col]]), ")") else time_col - ylab <- if (!is.null(concu_col)) paste0(conc_col, " (", unique(plot_data[[concu_col]]), ")") else conc_col + title <- paste0( + paste0(group_vars, ": "), + group[, group_vars, drop = FALSE], + collapse = ", " + ) + xlab <- if (!is.null(timeu_col)) { + paste0(time_col, " (", unique(plot_data[[timeu_col]]), ")") + } else { + time_col + } + ylab <- if (!is.null(concu_col)) { + paste0(conc_col, " (", unique(plot_data[[concu_col]]), ")") + } else { + conc_col + } subtitle_text <- paste0( - "R2adj = ", signif(adj.r.squared, 2), + "R2adj = ", + signif(adj_r2, 2), "    ", "span ratio = ", signif(span_ratio, 2) @@ -196,7 +217,11 @@ get_halflife_plot <- function(pknca_data, add_annotations = TRUE) { ) ) plots[[1]] <- plotly::plotly_build(p) - names(plots)[1] <- paste0(title, ", start: ", group$start, ", end: ", group$end) + names(plots)[1] <- paste0( + title, + ", start: ", group$start, + ", end: ", group$end + ) # For the rest, update the plotly object # Clone the first plot and update data/layout p_i <- plots[[1]] @@ -212,11 +237,11 @@ get_halflife_plot <- function(pknca_data, add_annotations = TRUE) { group_nca <- o_nca group_nca$data$conc$data <- df group_nca$result <- merge(group_nca$result, group) - + start <- unique(group_nca$result$start) tlast <- get_res("tlast") half_life <- get_res("half.life") - adj.r.squared <- get_res("adj.r.squared") + adj_r2 <- get_res("adj.r.squared") lz_time_first <- get_res("lambda.z.time.first") lz_time_last <- get_res("lambda.z.time.last") time_span <- lz_time_last - lz_time_first @@ -233,7 +258,7 @@ get_halflife_plot <- function(pknca_data, add_annotations = TRUE) { exclude_hl_col ) df_fit <- df[is_lz_used, ] - fit <- lm(as.formula(paste0("log10(", conc_col,") ~ ", time_col)), df_fit) + fit <- lm(as.formula(paste0("log10(", conc_col, ") ~ ", time_col)), df_fit) fit_line_data <- data.frame(x = c(lz_time_first, tlast)) colnames(fit_line_data) <- time_col fit_line_data$y <- predict(fit, fit_line_data) @@ -258,11 +283,24 @@ get_halflife_plot <- function(pknca_data, add_annotations = TRUE) { signif(plot_data[[conc_col]], 3), ")" ) - title <- paste0(paste0(group_vars, ": "), group[, group_vars, drop = FALSE], collapse = ", ") - xlab <- if (!is.null(timeu_col)) paste0(time_col, " (", unique(plot_data[[timeu_col]]), ")") else time_col - ylab <- if (!is.null(concu_col)) paste0(conc_col, " (", unique(plot_data[[concu_col]]), ")") else conc_col + title <- paste0( + paste0(group_vars, ": "), + group[, group_vars, drop = FALSE], + collapse = ", " + ) + xlab <- if (!is.null(timeu_col)) { + paste0(time_col, " (", unique(plot_data[[timeu_col]]), ")") + } else { + time_col + } + ylab <- if (!is.null(concu_col)) { + paste0(conc_col, " (", unique(plot_data[[concu_col]]), ")") + } else { + conc_col + } subtitle_text <- paste0( - "R2adj = ", signif(adj.r.squared, 2), + "R2adj = ", + signif(adj_r2, 2), "    ", "span ratio = ", signif(span_ratio, 2) @@ -293,14 +331,17 @@ get_halflife_plot <- function(pknca_data, add_annotations = TRUE) { } plots[[i]] <- p_i - names(plots)[i] <- paste0(title, ", start: ", group$start, ", end: ", group$end) + names(plots)[i] <- paste0( + title, + ", start: ", group$start, + ", end: ", group$end + ) } } - + return(plots) } - #' Custom fast version of get_halflife_points #' @param data concentration data.frame #' @param include_hl_col column name for include_half.life @@ -309,11 +350,12 @@ get_halflife_plot <- function(pknca_data, add_annotations = TRUE) { #' @param lz_time_last numeric #' @param exclude_hl_col column name for exclude_half.life #' @return logical vector -get_halflife_points2 <- function(data, include_hl_col, time_col, lz_time_first, lz_time_last, exclude_hl_col) { +get_halflife_points2 <- function( + data, include_hl_col, time_col, lz_time_first, lz_time_last, exclude_hl_col +) { if (any(!is.na(data[[include_hl_col]]) & data[[include_hl_col]])) { data[[include_hl_col]] } else { data[[time_col]] >= lz_time_first & data[[time_col]] <= lz_time_last & !data[[exclude_hl_col]] } } - diff --git a/R/utils-slope_selector.R b/R/utils-slope_selector.R index 0d75fa0e5..6bc25a5ab 100644 --- a/R/utils-slope_selector.R +++ b/R/utils-slope_selector.R @@ -5,7 +5,7 @@ #' Detect changes between old and new PKNCA data #' -#' Compares two PKNCA data objects to determine if the underlying data, half-life adjustment columns, +#' Compares two PKNCA data objects to determine if the underlying data, half-life adjustments, #' or selected intervals have changed. Used to decide when to update plots. #' @param old Previous PKNCA data object #' @param new New PKNCA data object @@ -14,14 +14,28 @@ #' @return List with logicals: in_data, in_hl_adj, in_selected_intervals detect_pknca_data_changes <- function(old, new, excl_hl_col, incl_hl_col) { list( - in_data = if (is.null(old) & !is.null(new)) TRUE else { + in_data = if (is.null(old) & !is.null(new)) { + TRUE + } else { !identical( - dplyr::select(old$conc$data, -any_of(c(excl_hl_col, incl_hl_col))), - dplyr::select(new$conc$data, -any_of(c(excl_hl_col, incl_hl_col))) + dplyr::select( + old$conc$data, + -any_of(c(excl_hl_col, incl_hl_col)) + ), + dplyr::select( + new$conc$data, + -any_of(c(excl_hl_col, incl_hl_col)) + ) ) }, - in_hl_adj = !identical(old$conc$data[[excl_hl_col]], new$conc$data[[excl_hl_col]]) | - !identical(old$conc$data[[incl_hl_col]], new$conc$data[[incl_hl_col]]), + in_hl_adj = !identical( + old$conc$data[[excl_hl_col]], + new$conc$data[[excl_hl_col]] + ) | + !identical( + old$conc$data[[incl_hl_col]], + new$conc$data[[incl_hl_col]] + ), in_selected_intervals = !identical(new$intervals, old$intervals) ) } @@ -38,8 +52,14 @@ handle_hl_adj_change <- function(new_pknca_data, old_pknca_data, plot_outputs) { excl_hl_col <- new_pknca_data$conc$columns$exclude_half.life incl_hl_col <- new_pknca_data$conc$columns$include_half.life affected_groups <- anti_join( - dplyr::select(new_pknca_data$conc$data, any_of(c(group_vars(new_pknca_data), "NCA_PROFILE", excl_hl_col, incl_hl_col))), - dplyr::select(old_pknca_data$conc$data, any_of(c(group_vars(old_pknca_data), "NCA_PROFILE", excl_hl_col, incl_hl_col))), + dplyr::select( + new_pknca_data$conc$data, + any_of(c(group_vars(new_pknca_data), "NCA_PROFILE", excl_hl_col, incl_hl_col)) + ), + dplyr::select( + old_pknca_data$conc$data, + any_of(c(group_vars(old_pknca_data), "NCA_PROFILE", excl_hl_col, incl_hl_col)) + ), by = c(group_vars(new_pknca_data), "NCA_PROFILE", excl_hl_col, incl_hl_col) ) %>% select(any_of(c(group_vars(new_pknca_data), "NCA_PROFILE"))) %>% @@ -57,18 +77,30 @@ handle_hl_adj_change <- function(new_pknca_data, old_pknca_data, plot_outputs) { #' @return Updated plot_outputs (named list) handle_interval_change <- function(new_pknca_data, old_pknca_data, plot_outputs) { new_intervals <- anti_join( - new_pknca_data$intervals, old_pknca_data$intervals, - by = intersect(names(new_pknca_data$intervals), names(old_pknca_data$intervals)) + new_pknca_data$intervals, + old_pknca_data$intervals, + by = intersect( + names(new_pknca_data$intervals), + names(old_pknca_data$intervals) + ) ) rm_intervals <- anti_join( - old_pknca_data$intervals, new_pknca_data$intervals, - by = intersect(names(new_pknca_data$intervals), names(old_pknca_data$intervals)) + old_pknca_data$intervals, + new_pknca_data$intervals, + by = intersect( + names(new_pknca_data$intervals), + names(old_pknca_data$intervals) + ) ) if (nrow(new_intervals) > 0) { affected_groups <- new_intervals %>% select(any_of(c(group_vars(new_pknca_data), "start", "end"))) %>% distinct() - plot_outputs <- .update_plots_with_pknca(new_pknca_data, plot_outputs, affected_groups) + plot_outputs <- .update_plots_with_pknca( + new_pknca_data, + plot_outputs, + affected_groups + ) } if (nrow(rm_intervals) > 0) { rm_plot_names <- rm_intervals %>% @@ -88,6 +120,14 @@ handle_interval_change <- function(new_pknca_data, old_pknca_data, plot_outputs) plot_outputs } + +#' Parse Plot Names to Data Frame +#' +#' Converts a named list of plots (with names in the format 'col1: val1, col2: val2, ...') +#' into a data frame with one row per plot and columns for each key. +#' +#' @param named_list A named list or vector, where names are key-value pairs separated by commas. +#' @return A data frame with columns for each key and a PLOTID column with the original names. parse_plot_names_to_df <- function(named_list) { plot_names <- names(named_list) parsed <- lapply(plot_names, function(x) { @@ -98,10 +138,22 @@ parse_plot_names_to_df <- function(named_list) { vapply(kv, function(y) y[1], character(1)) ) }) - as.data.frame(do.call(rbind, parsed), stringsAsFactors = FALSE) %>% + as.data.frame( + do.call(rbind, parsed), + stringsAsFactors = FALSE + ) %>% mutate(PLOTID = names(named_list)) } + +#' Arrange Plots by Group Columns +#' +#' Orders a named list of plots according to specified grouping columns. +#' Assumes a specific naming format (i.e, 'col1: val1, col2: val2, ...'). +#' +#' @param named_list A named list of plots, with names in the format 'col1: val1, col2: val2, ...'. +#' @param group_cols Character vector of column names to sort by. +#' @return A named list of plots, ordered by the specified group columns. arrange_plots_by_groups <- function(named_list, group_cols) { plot_df <- parse_plot_names_to_df(named_list) arranged_df <- plot_df %>% @@ -109,16 +161,6 @@ arrange_plots_by_groups <- function(named_list, group_cols) { named_list[arranged_df$PLOTID] } -# prepare_virtual_select_df <- function(pknca_data) { -# pknca_data$intervals %>% -# select(any_of(c(group_vars(pknca_data)))) %>% -# pivot_longer(cols = group_vars(pknca_data), names_to = "var") %>% -# distinct() %>% -# mutate(var_and_value = paste0(var, ": ", value)) %>% -# distinct() -# } - - #' Check overlap between existing and new slope rulesets #' #' Takes in tables with existing and incoming selections and exclusions, finds any overlap and @@ -135,13 +177,18 @@ check_slope_rule_overlap <- function(existing, new, .keep = FALSE) { # check if any rule already exists for specific subject and profile existing_index <- which( existing$TYPE == new$TYPE & - Reduce(`&`, lapply(slope_groups, function(col) { - existing[[col]] == new[[col]] - })) + Reduce( + `&`, + lapply(slope_groups, function(col) { + existing[[col]] == new[[col]] + }) + ) ) if (length(existing_index) != 1) { if (length(existing_index) > 1) - warning("More than one range for single subject, profile and rule type detected.") + warning( + "More than one range for single subject, profile and rule type detected." + ) return(rbind(existing, new)) } existing_range <- .eval_range(existing$RANGE[existing_index]) @@ -157,11 +204,10 @@ check_slope_rule_overlap <- function(existing, new, .keep = FALSE) { } dplyr::filter(existing, !is.na(RANGE)) } - #' Apply Slope Rules to Update Data #' -#' Iterates over the given slopes and updates the data$conc$data object by setting inclusion/exclusion flags. +#' Iterates over the given rules and updates the PKNCA object setting inclusion/exclusion flags. #' @param data PKNCA data object #' @param slopes Data frame of slope rules (TYPE, RANGE, REASON, group columns) #' @return Modified data object with updated flags @@ -197,24 +243,25 @@ check_slope_rule_overlap <- function(existing, new, .keep = FALSE) { data } - #' Update plots with manual slope rules #' -#' Updates the plot_outputs list by applying manual slopes to the PKNCA data and regenerating affected plots. +#' Updates the plot_outputs list by applying manual slopes. #' @param pknca_data PKNCA data object #' @param manual_slopes Data frame of manual slope rules #' @param plot_outputs Named list of current plot outputs -#' @param slopes_to_update Optional: data frame of intervals to update (default: all in manual_slopes) +#' @param groups_to_update Optional: data frame of intervals to update (default: all) #' @return Updated plot_outputs (named list) -.update_plots_with_rules <- function(pknca_data, manual_slopes, plot_outputs, slopes_to_update = NULL) { +.update_plots_with_rules <- function( + pknca_data, manual_slopes, plot_outputs, groups_to_update = NULL +) { pknca_for_plots <- .update_pknca_with_rules(pknca_data, manual_slopes) - if (is.null(slopes_to_update)) { - slopes_to_update <- manual_slopes %>% + if (is.null(groups_to_update)) { + groups_to_update <- manual_slopes %>% select(any_of(c(group_vars(pknca_for_plots), "NCA_PROFILE"))) %>% distinct() } - if (nrow(slopes_to_update) == 0) return(plot_outputs) - .update_plots_with_pknca(pknca_for_plots, plot_outputs, intervals_to_update = slopes_to_update) + if (nrow(groups_to_update) == 0) return(plot_outputs) + .update_plots_with_pknca(pknca_for_plots, plot_outputs, intervals_to_update = groups_to_update) } #' Update plots with PKNCA data (for affected intervals) diff --git a/inst/shiny/functions/handle_plotly_click.R b/inst/shiny/functions/handle_plotly_click.R index d3877556a..9209bbe05 100644 --- a/inst/shiny/functions/handle_plotly_click.R +++ b/inst/shiny/functions/handle_plotly_click.R @@ -1,9 +1,9 @@ #' Handle Plotly Click for Slope Selection #' -#' This function processes a plotly click event in the slope selection UI. It determines whether the click -#' should add a new exclusion or selection rule to the manual slopes table, based on the user's interaction -#' (same point = exclusion, two points in same plot = selection). It updates the manual_slopes table accordingly. +#' This function processes a plotly click event in the slope selection UI. It determines whether +#' the double click should add a new exclusion or selection rule to the manual slopes table +#' (same point = exclusion, two points in same plot = selection). #' #' @param last_click_data A reactiveVal storing the last clicked plotly data (or NULL). #' @param manual_slopes A reactiveVal storing the current manual slopes table (data.frame). @@ -39,7 +39,10 @@ handle_plotly_click <- function(last_click_data, manual_slopes, click_data, pknc } else if (identical(pnt$int, lstpnt$int)) { # Two points in same interval: add selection rule for the range new_rule$TYPE <- "Selection" - new_rule$RANGE <- paste0(sort(c(pnt$time, lstpnt$time)), collapse = ":") + new_rule$RANGE <- paste0( + sort(c(pnt$time, lstpnt$time)), + collapse = ":" + ) new_rule$REASON <- "" } else { # Clicks not in same interval: just update last_click_data, no rule @@ -78,8 +81,10 @@ handle_plotly_click <- function(last_click_data, manual_slopes, click_data, pknc row <- pknca_data$conc$data[idx, ] int <- pknca_data$intervals %>% merge(row, by = c(group_vars(pknca_data))) %>% - filter(start <= row[[pknca_data$conc$columns$time]] & - end >= row[[pknca_data$conc$columns$time]]) %>% + filter( + start <= row[[pknca_data$conc$columns$time]] & + end >= row[[pknca_data$conc$columns$time]] + ) %>% select(any_of(names(pknca_data$intervals))) group <- int %>% select(any_of(c(group_vars(pknca_data), "NCA_PROFILE"))) diff --git a/inst/shiny/modules/tab_nca/descriptive_statistics.R b/inst/shiny/modules/tab_nca/descriptive_statistics.R index e9ef8b7b3..1cf2f25f9 100644 --- a/inst/shiny/modules/tab_nca/descriptive_statistics.R +++ b/inst/shiny/modules/tab_nca/descriptive_statistics.R @@ -156,6 +156,5 @@ descriptive_statistics_server <- function(id, res_nca, grouping_vars) { write.csv(summary_stats_filtered(), file) } ) - }) } diff --git a/inst/shiny/modules/tab_nca/setup/handle_table_edits.R b/inst/shiny/modules/tab_nca/setup/handle_table_edits.R index 9a4b3cbfb..b4cf0825f 100644 --- a/inst/shiny/modules/tab_nca/setup/handle_table_edits.R +++ b/inst/shiny/modules/tab_nca/setup/handle_table_edits.R @@ -1,7 +1,7 @@ #' Manual Slopes Table UI for Slope Selection #' -#' UI module for displaying and editing the manual slopes table (inclusion/exclusion rules) in the slope selector workflow. +#' UI module for displaying and editing the manual slopes table (inclusion/exclusion rules). #' Provides buttons to add/remove rules and a reactable table for editing. #' #' @param id Shiny module id @@ -30,8 +30,8 @@ handle_table_edits_ui <- function(id) { #' Manual Slopes Table Server for Slope Selection #' -#' Server module for managing the manual slopes table (inclusion/exclusion rules) in the slope selector workflow. -#' Handles adding/removing/editing rules, table reactivity, and optional override logic. +#' Server module for managing the manual slopes table (inclusion/exclusion rules). +#' Handles adding/removing/editing rules, table's reactivity, and optional override logic. #' #' @param id Shiny module id #' @param mydata Reactive providing the current PKNCA data object @@ -40,7 +40,7 @@ handle_table_edits_ui <- function(id) { #' - manual_slopes: reactiveVal containing the current manual slopes table #' - refresh_reactable: reactiveVal for triggering table re-render handle_table_edits_server <- function( - id, mydata, manual_slopes_override = NULL + id, mydata, manual_slopes_override = NULL ) { moduleServer(id, function(input, output, session) { @@ -53,14 +53,19 @@ handle_table_edits_server <- function( }) # manual_slopes: stores the current table of user rules (inclusion/exclusion) - manual_slopes <- reactiveVal({NULL}) + manual_slopes <- reactiveVal(NULL) # When mydata() changes, reset the manual_slopes table to empty with correct columns observeEvent(mydata(), { req(is.null(manual_slopes())) req(slopes_pknca_groups()) ms_colnames <- c(colnames(slopes_pknca_groups()), c("TYPE", "RANGE", "REASON")) initial_manual_slopes <- data.frame( - matrix(character(), ncol = length(ms_colnames), nrow = 0, dimnames = list(character(), ms_colnames)) + matrix( + character(), + ncol = length(ms_colnames), + nrow = 0, + dimnames = list(character(), ms_colnames) + ) ) manual_slopes(initial_manual_slopes) }) @@ -74,12 +79,18 @@ handle_table_edits_server <- function( data.frame( TYPE = "Exclusion", RANGE = paste0( - inner_join(slopes_pknca_groups()[1, ], mydata()$conc$data)[[mydata()$conc$columns$time]][2] + inner_join( + slopes_pknca_groups()[1, ], + mydata()$conc$data + )[[mydata()$conc$columns$time]][2] ), REASON = "" ) ) - updated_data <- as.data.frame(rbind(manual_slopes(), new_row), stringsAsFactors = FALSE) + updated_data <- as.data.frame( + rbind(manual_slopes(), new_row), + stringsAsFactors = FALSE + ) manual_slopes(updated_data) reset_reactable_memory() refresh_reactable(refresh_reactable() + 1) @@ -146,7 +157,10 @@ handle_table_edits_server <- function( defaultExpanded = TRUE, borderless = TRUE, theme = reactableTheme( - rowSelectedStyle = list(backgroundColor = "#eee", boxShadow = "inset 2px 0 0 0 #ffa62d") + rowSelectedStyle = list( + backgroundColor = "#eee", + boxShadow = "inset 2px 0 0 0 #ffa62d" + ) ) ) }) %>% diff --git a/inst/shiny/modules/tab_nca/setup/page_and_searcher.R b/inst/shiny/modules/tab_nca/setup/page_and_searcher.R index fd615013f..d0eb469f6 100644 --- a/inst/shiny/modules/tab_nca/setup/page_and_searcher.R +++ b/inst/shiny/modules/tab_nca/setup/page_and_searcher.R @@ -1,7 +1,7 @@ #' Page and Searcher UI (pagination controls only) #' -#' This UI module provides the page navigation controls (previous, next, page selector, page number display). -#' The search_subject input remains outside for now. +#' This UI module provides the page navigation controls (previous, next, selector, number). +#' The search_subject input remains outside for now in the parent (slope_selector.R) page_and_searcher_page_ui <- function(id) { ns <- NS(id) fluidRow( @@ -35,7 +35,6 @@ page_and_searcher_page_ui <- function(id) { #' Page and Searcher Server #' #' Handles pagination and subject search logic for displaying plots. -#' Inputs: current_page (reactive), input$search_subject, plot_outputs (reactive), input$plots_per_page #' Outputs: list of reactives: page_start, page_end, is_plot_searched, num_pages page_and_searcher_server <- function(id, search_subject, plot_outputs, plots_per_page) { moduleServer(id, function(input, output, session) { @@ -58,9 +57,9 @@ page_and_searcher_server <- function(id, search_subject, plot_outputs, plots_per } }) - num_plots <- reactive({ sum(is_plot_searched()) }) - plots_per_page_num <- reactive({ as.numeric(plots_per_page()) }) - num_pages <- reactive({ ceiling(num_plots() / plots_per_page_num()) }) + num_plots <- reactive(sum(is_plot_searched())) + plots_per_page_num <- reactive(as.numeric(plots_per_page())) + num_pages <- reactive(ceiling(num_plots() / plots_per_page_num())) # Navigation events observeEvent(input$next_page, { diff --git a/inst/shiny/modules/tab_nca/setup/slope_selector.R b/inst/shiny/modules/tab_nca/setup/slope_selector.R index 458b14bac..4ee7a9acf 100644 --- a/inst/shiny/modules/tab_nca/setup/slope_selector.R +++ b/inst/shiny/modules/tab_nca/setup/slope_selector.R @@ -1,16 +1,16 @@ #' Slope Selector Module (Server/UI) #' -#' This module manages the interactive selection and exclusion of NCA slope intervals via plots and a table UI. -#' It coordinates the display and update of plots, the manual slopes table, and the propagation of user changes. +#' This module manages the half life point selection or exclusion via plots and a table UI. +#' It coordinates the display and update of plots and the manual slopes table. #' -#' --- Reactivity/Data Flow Schema --- +#' --- Reactivity Flow Schema --- #' #' processed_pknca_data (input, from parent) #' └─> slope_selector_server #' ├──> plot_outputs (reactive, updated by processed_pknca_data changes) #' └──> ├> handle_plotly_click (updates manual_slopes on plot click) -#' â””> handle_table_edits_server (updates manual_slopes on user actions & setting overrides) +#' â””> handle_table_edits (updates manual_slopes on user edits & setting overrides) #' └─> manual_slopes (output, used by parent to update processed_pknca_data) #' #' @@ -20,10 +20,10 @@ #' @return manual_slopes (data.frame of user slope inclusions/exclusions) #' #' @details -#' - The module's main output is the manual_slopes table, which is updated by user actions in the table UI (handle_table_edits) or by clicking on plots (handle_plotly_click). -#' - The parent module (setup.R) should use manual_slopes to update processed_pknca_data, which is then fed back into this module to update plots and table context. -#' - All plot and table reactivity is encapsulated here; parent modules only need to provide processed_pknca_data and consume manual_slopes. - +#' - The module's main output is the manual_slopes table, which is updated by user +#' edits in the table UI (handle_table_edits) or by plot clicking (handle_plotly_click). +#' - The parent module (setup.R) uses manual_slopes to update processed_pknca_data, +#' which is then fed back in this module to update plots. slope_selector_ui <- function(id) { ns <- NS(id) @@ -89,15 +89,6 @@ slope_selector_ui <- function(id) { choices = NULL, multiple = TRUE ), - # shinyWidgets::virtualSelectInput( - # ns("search_group"), - # label = "Search Group", - # choices = NULL, - # multiple = TRUE, - # search = TRUE, - # placeholder = "Select group(s)...", - # noOfDisplayValues = 3 - # ), ) ), fluidRow( @@ -142,17 +133,14 @@ slope_selector_server <- function( # nolint incl_hl_col = new_pknca_data$conc$columns$include_half.life ) - # Regenerate plots if a dataset is submitted or it was changed - # (update of adnca_data in setup.R) if (changes$in_data) { + # New data or major changes: regenerate all plots plot_outputs(get_halflife_plot(new_pknca_data)) - # If relevant, modify plots that had new half-life adjustments - # (inclusions/exclusions from manual_slope_table) } else if (changes$in_hl_adj) { + # Modify plots that had new half-life adjustments (inclusions/exclusions) plot_outputs(handle_hl_adj_change(new_pknca_data, pknca_data(), plot_outputs())) - # If relevant, modify plots that had interval changes - # (analyte, profile, specimen selection from setup.R settings) } else if (changes$in_selected_intervals) { + # Modify plots that had interval changes (analyte, profile, specimen selection from setup.R) plot_outputs(handle_interval_change(new_pknca_data, pknca_data(), plot_outputs())) } @@ -164,20 +152,6 @@ slope_selector_server <- function( # nolint label = "Search Subject", choices = unique(new_pknca_data$intervals$USUBJID) ) - #group_options_df <- prepare_virtual_select_df(new_pknca_data) - # shinyWidgets::updateVirtualSelect( - # session = session, - # inputId = "search_group", - # label = "Search Group", - # choices = shinyWidgets::prepare_choices( - # group_options_df, - # label = value, - # value = var_and_value, - # alias = value, - # group_by = var - # ), - # selected = group_options_df$var_and_value - # ) } if (changes$in_data) { updateOrderInput( @@ -195,8 +169,8 @@ slope_selector_server <- function( # nolint session$userData$plotlyShinyEventIDs <- "plotly_click-A" # --- Pagination and search logic --- - search_subject_r <- reactive({ input$search_subject }) - plots_per_page_r <- reactive({ input$plots_per_page }) + search_subject_r <- reactive(input$search_subject) + plots_per_page_r <- reactive(input$plots_per_page) # Call the pagination/searcher module to: # - Providing indices of plots for the selected subject(s) @@ -211,11 +185,6 @@ slope_selector_server <- function( # nolint # Render only the plots requested by the user observe({ req(plot_outputs()) - # browser() - # prepare_virtual_select_df(pknca_data()) %>% - # filter(var_and_value %in% input$search_group) %>% - # pull() - # sapply(input$search_group, \(x) grep(x, names(plot_outputs()), value = TRUE)) output$slope_plots_ui <- renderUI({ shinyjs::enable(selector = ".btn-page") plot_outputs() %>% @@ -267,10 +236,7 @@ slope_selector_server <- function( # nolint data = manual_slopes() ) }) - - # Manual slopes override logic moved to handle_table_edits_server - - #' return reactive with slope exclusions data to be displayed in Results -> Exclusions tab + #' returns half life adjustments rules to update processed_pknca_data in setup.R list( manual_slopes = manual_slopes ) From 5fb2953cfa58514952174a9c8e0c7dc47e77a426 Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Fri, 12 Sep 2025 08:49:59 +0200 Subject: [PATCH 042/113] refactor: rm return for implicit output --- R/lambda_slope_plot.R | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/R/lambda_slope_plot.R b/R/lambda_slope_plot.R index 935243f3d..ac0e26a88 100644 --- a/R/lambda_slope_plot.R +++ b/R/lambda_slope_plot.R @@ -338,8 +338,7 @@ get_halflife_plot <- function(pknca_data, add_annotations = TRUE) { #nolint ) } } - - return(plots) + plots } #' Custom fast version of get_halflife_points From dc227e8ddfeb3daab07f2c0aedd362f51ae2a26b Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Fri, 12 Sep 2025 09:53:21 +0200 Subject: [PATCH 043/113] rename: lambda_slope_plot.R > get_halflife_plot.R --- R/{lambda_slope_plot.R => get_halflife_plot.R} | 0 .../{test-lambda_slope_plot.R => test-get_halflife_plot.R} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename R/{lambda_slope_plot.R => get_halflife_plot.R} (100%) rename tests/testthat/{test-lambda_slope_plot.R => test-get_halflife_plot.R} (100%) diff --git a/R/lambda_slope_plot.R b/R/get_halflife_plot.R similarity index 100% rename from R/lambda_slope_plot.R rename to R/get_halflife_plot.R diff --git a/tests/testthat/test-lambda_slope_plot.R b/tests/testthat/test-get_halflife_plot.R similarity index 100% rename from tests/testthat/test-lambda_slope_plot.R rename to tests/testthat/test-get_halflife_plot.R From c39ac39e1a2e78db297024cd3ac235a53a24ce3f Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Fri, 12 Sep 2025 11:29:19 +0200 Subject: [PATCH 044/113] fix: typpo in get_halflife_plot.R --- R/get_halflife_plot.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/get_halflife_plot.R b/R/get_halflife_plot.R index ac0e26a88..4c63a3d41 100644 --- a/R/get_halflife_plot.R +++ b/R/get_halflife_plot.R @@ -51,7 +51,7 @@ get_halflife_plot <- function(pknca_data, add_annotations = TRUE) { #nolint include_hl_col <- o_nca$data$conc$columns$include_half.life if (is.null(include_hl_col)) { o_nca$data$conc$data[["include_half.life"]] <- FALSE - exclude_hl_col <- "include_half.life" + include_hl_col <- "include_half.life" } # Helper functions for extracting results From f9ba3660db502fe01f6569fd4e79ef97d3e38db6 Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Fri, 12 Sep 2025 11:42:46 +0200 Subject: [PATCH 045/113] draft: new tests for get_halflife_plot --- tests/testthat/test-get_halflife_plot.R | 75 +++++++++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/tests/testthat/test-get_halflife_plot.R b/tests/testthat/test-get_halflife_plot.R index 813f443dc..28e097969 100644 --- a/tests/testthat/test-get_halflife_plot.R +++ b/tests/testthat/test-get_halflife_plot.R @@ -142,3 +142,78 @@ describe("lambda_slope_plot", { }) }) + + +test_that("get_halflife_plot returns a list of plotly objects with valid input", { + # Use the fixture data as input + pknca_data <- FIXTURE_PKNCA_DATA + plots <- get_halflife_plot(pknca_data) + expect_type(plots, "list") + expect_true(length(plots) >= 1) + expect_s3_class(plots[[1]], "plotly") + expect_true("layout" %in% names(plots[[1]]$x)) +}) + +test_that("get_halflife_plot warns and returns empty list when no groups present", { + # Remove all lambda.z rows from result to simulate no groups + pknca_data <- FIXTURE_PKNCA_DATA + pknca_data$conc$data <- pknca_data$conc$data[0, ] + plots <- get_halflife_plot(pknca_data) + expect_type(plots, "list") + expect_length(plots, 0) +}) + + +# Marker color/shape tests for get_halflife_plot +test_that("get_halflife_plot: marker colors and shapes - no exclusion/inclusion", { + pknca_data <- FIXTURE_PKNCA_DATA + # Remove exclude/include columns if present, or set all to FALSE + pknca_data$conc$data$exclude_half.life <- FALSE + pknca_data$conc$data$include_half.life <- FALSE + plots <- get_halflife_plot(pknca_data) + expect_true(length(plots) >= 1) + plot_data <- plots[[1]]$x$data[[2]] + # All points should be black (no inclusion/exclusion) + expect_true(all(plot_data$marker$color == "black")) + # All points should be circles (no exclusion) + expect_true(all(plot_data$marker$symbol == "circle")) +}) + +test_that("get_halflife_plot: marker colors and shapes - exclusion of a lambda.z point", { + pknca_data <- FIXTURE_PKNCA_DATA + pknca_data$intervals <- pknca_data$intervals[2,] + + # Exclude a point in the lambda.z calculation + pknca_data_with_excl <- pknca_data$conc$data + pknca_data_with_excl$conc$data <- pknca_data$conc$data %>% + mutate( + exclude_half.life = ifelse( + USUBJID == USUBJID[2] & AFRLT == 2.5, + TRUE, + FALSE + ) + ) + plots <- get_halflife_plot(pknca_data) + expect_true(length(plots) >= 1) + plot_data <- plots[[1]]$x$data[[2]] + # At least one point should be red (excluded) + expect_true(any(plot_data$marker$color == "red")) + # At least one point should be an 'x' (excluded) + expect_true(any(plot_data$marker$symbol == "x")) +}) + +test_that("get_halflife_plot: marker colors and shapes - inclusion of lambda.z points", { + pknca_data <- FIXTURE_PKNCA_DATA + # Mark first two points as included in lambda.z + pknca_data$conc$data$exclude_half.life <- FALSE + pknca_data$conc$data$include_half.life <- FALSE + pknca_data$conc$data$include_half.life[1:2] <- TRUE + plots <- get_halflife_plot(pknca_data) + expect_true(length(plots) >= 1) + plot_data <- plots[[1]]$x$data[[2]] + # At least two points should be green (included in lambda.z) + expect_true(sum(plot_data$marker$color == "green") >= 2) + # Included points should be circles + expect_true(all(plot_data$marker$symbol[plot_data$marker$color == "green"] == "circle")) +}) + From efa0065808b1c2c50cd2ffc5fa126a166fbabf59 Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Fri, 12 Sep 2025 17:07:24 +0200 Subject: [PATCH 046/113] draft: keep going with tests --- tests/testthat/test-get_halflife_plot.R | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/tests/testthat/test-get_halflife_plot.R b/tests/testthat/test-get_halflife_plot.R index 28e097969..9ce179158 100644 --- a/tests/testthat/test-get_halflife_plot.R +++ b/tests/testthat/test-get_halflife_plot.R @@ -163,7 +163,6 @@ test_that("get_halflife_plot warns and returns empty list when no groups present expect_length(plots, 0) }) - # Marker color/shape tests for get_halflife_plot test_that("get_halflife_plot: marker colors and shapes - no exclusion/inclusion", { pknca_data <- FIXTURE_PKNCA_DATA @@ -184,7 +183,7 @@ test_that("get_halflife_plot: marker colors and shapes - exclusion of a lambda.z pknca_data$intervals <- pknca_data$intervals[2,] # Exclude a point in the lambda.z calculation - pknca_data_with_excl <- pknca_data$conc$data + pknca_data_with_excl <- pknca_data pknca_data_with_excl$conc$data <- pknca_data$conc$data %>% mutate( exclude_half.life = ifelse( @@ -194,6 +193,24 @@ test_that("get_halflife_plot: marker colors and shapes - exclusion of a lambda.z ) ) plots <- get_halflife_plot(pknca_data) + plots_with_excl <- get_halflife_plot(pknca_data_with_excl) + + plots_details <- plots[[1]]$x$data[[2]]$marker + expected_plots_details <- list( + color = c("red", "red", "green", "green", "green"), + size = 15, + symbol = c("circle", "circle", "circle", "circle", "circle"), + line = list(color = "rgba(255,127,14,1)") + ) + plots_with_excl_details <- plots_with_excl[[1]]$x$data[[2]]$marker + expected_plots_details_with_excl <- list( + color = c("red", "black", "green", "green", "green"), + size = 15, + symbol = c("x", "circle", "circle", "circle", "circle"), + line = list(color = "rgba(255,127,14,1)") + ) + + expect_true(length(plots) >= 1) plot_data <- plots[[1]]$x$data[[2]] # At least one point should be red (excluded) From 94b373de8eab62989b5d9ae305a8c2a6a5f6c69e Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Mon, 15 Sep 2025 07:48:59 +0200 Subject: [PATCH 047/113] draft: change fun for get_halflife_plots --- R/get_halflife_plot.R | 483 +++++++----------- R/utils-slope_selector.R | 2 +- .../modules/tab_nca/setup/slope_selector.R | 6 +- tests/testthat/test-get_halflife_plot.R | 12 +- 4 files changed, 195 insertions(+), 308 deletions(-) diff --git a/R/get_halflife_plot.R b/R/get_halflife_plot.R index 4c63a3d41..9a9312cef 100644 --- a/R/get_halflife_plot.R +++ b/R/get_halflife_plot.R @@ -1,167 +1,43 @@ - -get_halflife_plot <- function(pknca_data, add_annotations = TRUE) { #nolint - pknca_data$conc$data$ROWID <- seq_len(nrow(pknca_data$conc$data)) - o_nca <- suppressWarnings(PKNCA::pk.nca(pknca_data)) - if (!"PPSTRES" %in% names(o_nca$result)) { - o_nca$result$PPSTRES <- o_nca$result$PPORRES - if ("PPORRESU" %in% names(o_nca$result)) { - o_nca$result$PPSTRESU <- o_nca$result$PPORRESU - } - } - o_nca$result <- o_nca$result %>% - unique() %>% - mutate( - PPORRES = ifelse( - PPTESTCD %in% c( - "lambda.z.time.first", "lambda.z.time.last", "tlast" - ), - PPORRES + start, - PPORRES - ), - PPSTRES = ifelse( - PPTESTCD %in% c( - "lambda.z.time.first", "lambda.z.time.last", "tlast" - ), - PPSTRES + start, - PPSTRES - ) - ) - - groups <- o_nca$result %>% - select(any_of(c(group_vars(o_nca), "start", "end", "PPTESTCD"))) %>% - dplyr::filter(PPTESTCD == "lambda.z") %>% - select(-PPTESTCD) %>% - unique() - n_groups <- nrow(groups) - plots <- vector("list", n_groups) - if (n_groups == 0) { - return(plots) - } - # Precompute column names and helper functions - time_col <- o_nca$data$conc$columns$time - conc_col <- o_nca$data$conc$columns$concentration - timeu_col <- o_nca$data$conc$columns$timeu - concu_col <- o_nca$data$conc$columns$concu - exclude_hl_col <- o_nca$data$conc$columns$exclude_half.life - if (is.null(exclude_hl_col)) { - o_nca$data$conc$data[["exclude_half.life"]] <- FALSE - exclude_hl_col <- "exclude_half.life" - } - - include_hl_col <- o_nca$data$conc$columns$include_half.life - if (is.null(include_hl_col)) { - o_nca$data$conc$data[["include_half.life"]] <- FALSE - include_hl_col <- "include_half.life" - } - - # Helper functions for extracting results - get_res_fun <- function(result_df, testcd) result_df$PPSTRES[result_df$PPTESTCD == testcd] - get_unit_fun <- function(result_df, testcd) result_df$PPSTRESU[result_df$PPTESTCD == testcd] - # 1. Pre-split data by group (avoid repeated merges) - group_vars_all <- setdiff(names(groups), c("start", "end")) - # Create a key for each row in conc data and in groups - conc_data <- o_nca$data$conc$data - if (length(group_vars_all) > 0) { - conc_data$.__groupkey__ <- apply( - conc_data[, group_vars_all, drop = FALSE], 1, function(row) paste(row, collapse = "|") - ) - groups$.__groupkey__ <- apply( - groups[, group_vars_all, drop = FALSE], 1, function(row) paste(row, collapse = "|") - ) - } else { - conc_data$.__groupkey__ <- "__all__" - groups$.__groupkey__ <- "__all__" - } - # Pre-split conc_data by group key - conc_data_split <- split(conc_data, conc_data$.__groupkey__) - - - # 2. Build the first plot fully - i <- 1 - group <- groups[i, ] - group_vars <- group_vars_all - group_key <- group$.__groupkey__ - df <- conc_data_split[[group_key]] - df <- df[df[[time_col]] >= group$start & df[[time_col]] <= group$end, ] - df <- df[order(df[[time_col]]), ] - df$IX <- seq_len(nrow(df)) - group_nca <- o_nca - group_nca$data$conc$data <- df - group_nca$result <- merge(group_nca$result, group) - get_res <- function(testcd) get_res_fun(group_nca$result, testcd) - get_unit <- function(testcd) get_unit_fun(group_nca$result, testcd) - start <- unique(group_nca$result$start) - tlast <- get_res("tlast") - half_life <- get_res("half.life") - adj_r2 <- get_res("adj.r.squared") - lz_time_first <- get_res("lambda.z.time.first") - lz_time_last <- get_res("lambda.z.time.last") - time_span <- lz_time_last - lz_time_first - span_ratio <- get_res("span.ratio") - exclude_calc_reason <- group_nca$result$exclude[group_nca$result$PPTESTCD == "half.life"] - - if (!is.na(half_life)) { - is_lz_used <- get_halflife_points2( - group_nca$data$conc$data, - include_hl_col, - time_col, - lz_time_first, - lz_time_last, - exclude_hl_col - ) - df_fit <- df[is_lz_used, ] - fit <- lm(as.formula(paste0("log10(", conc_col, ") ~ ", time_col)), df_fit) - fit_line_data <- data.frame(x = c(lz_time_first, tlast)) - colnames(fit_line_data) <- time_col - fit_line_data$y <- predict(fit, fit_line_data) - } else { - is_lz_used <- rep(NA_real_, nrow(df)) - fit_line_data <- data.frame( - x = c(start, start), - y = c(0, 0) +#' Create a Plotly Half-life Plot +#' +#' Generates a plotly plot for NCA half-life visualization, with a fit line and scatter points. +#' +#' @param fit_line_data Data frame for the fit line (must have columns for time and y) +#' @param plot_data Data frame for the scatter points +#' @param time_col Name of the time column (string) +#' @param conc_col Name of the concentration column (string) +#' @param title Plot title +#' @param xlab X axis label +#' @param ylab Y axis label +#' @param subtitle_text Subtitle/annotation (HTML allowed) +#' @param color Vector of colors for points (same length as plot_data) +#' @param symbol Vector of marker symbols for points (same length as plot_data) +#' @param group_vars Character vector of grouping variable names (for customdata) +#' @param add_annotations Logical, whether to add the subtitle annotation +#' @param text Optional vector of hover text for points (same length as plot_data) +#' @return A plotly object +get_halflife_plots_single <- function( + fit_line_data, + plot_data, + time_col, + conc_col, + title, + xlab, + ylab, + subtitle_text, + color, + symbol, + group_vars, + add_annotations = TRUE, + text = NULL +) { + if (is.null(text)) { + text <- paste0( + "Data Point: ", seq_len(nrow(plot_data)), "\n(", + plot_data[[time_col]], ", ", signif(plot_data[[conc_col]], 3), ")" ) - colnames(fit_line_data)[1] <- time_col - } - - plot_data <- df - # 4. Vectorize color, symbol, text assignment - plot_data$color <- ifelse(is.na(is_lz_used), "black", - ifelse(is_lz_used, "green", "red")) - plot_data$symbol <- ifelse(plot_data[[exclude_hl_col]], "x", "circle") - plot_data$text <- paste0( - "Data Point: ", plot_data[["IX"]], "\n", - "(", - plot_data[[time_col]], - ", ", - signif(plot_data[[conc_col]], 3), - ")" - ) - title <- paste0( - paste0(group_vars, ": "), - group[, group_vars, drop = FALSE], - collapse = ", " - ) - xlab <- if (!is.null(timeu_col)) { - paste0(time_col, " (", unique(plot_data[[timeu_col]]), ")") - } else { - time_col - } - ylab <- if (!is.null(concu_col)) { - paste0(conc_col, " (", unique(plot_data[[concu_col]]), ")") - } else { - conc_col } - subtitle_text <- paste0( - "R2adj = ", - signif(adj_r2, 2), - "    ", - "span ratio = ", - signif(span_ratio, 2) - ) - if (is.na(half_life)) { - subtitle_text <- exclude_calc_reason - } - p <- plotly::plot_ly() %>% + plotly::plot_ly() %>% plotly::add_lines( data = fit_line_data, x = ~get(time_col), @@ -187,174 +63,185 @@ get_halflife_plot <- function(pknca_data, add_annotations = TRUE) { #nolint gridcolor = "white", zeroline = FALSE ), - annotations = if (add_annotations) list( + annotations = list( text = subtitle_text, showarrow = FALSE, xref = "paper", yref = "paper", y = 1 - ) else NULL + ) ) %>% plotly::add_trace( data = plot_data, x = ~plot_data[[time_col]], y = ~plot_data[[conc_col]], - text = ~plot_data$text, + text = text, hoverinfo = "text", showlegend = FALSE, type = "scatter", mode = "markers", marker = list( - color = plot_data$color, + color = color, size = 15, - symbol = plot_data$symbol, + symbol = symbol, size = 20 ), customdata = apply( - plot_data[, c(group_vars, "ROWID", "IX"), drop = FALSE], + plot_data[, c(group_vars, "ROWID"), drop = FALSE], 1, - function(row) as.list(set_names(row, c(group_vars, "ROWID", "IX"))) + function(row) as.list(set_names(row, c(group_vars, "ROWID"))) ) - ) - plots[[1]] <- plotly::plotly_build(p) - names(plots)[1] <- paste0( - title, - ", start: ", group$start, - ", end: ", group$end - ) - # For the rest, update the plotly object - # Clone the first plot and update data/layout - p_i <- plots[[1]] - if (n_groups > 1) { - for (i in 2:n_groups) { - group <- groups[i, ] - group_vars <- group_vars_all - group_key <- group$.__groupkey__ - df <- conc_data_split[[group_key]] - df <- df[df[[time_col]] >= group$start & df[[time_col]] <= group$end, ] - df <- df[order(df[[time_col]]), ] - df$IX <- seq_len(nrow(df)) - group_nca <- o_nca - group_nca$data$conc$data <- df - group_nca$result <- merge(group_nca$result, group) + ) %>% + plotly::plotly_build() +} + +get_halflife_plots <- function(pknca_data, add_annotations = TRUE) { + + # Adjust the input to compute half-life & show original row number + pknca_data$conc$data$ROWID <- seq_len(nrow(pknca_data$conc$data)) + pknca_data$intervals <- pknca_data$intervals %>% + filter(type_interval == "main", half.life) %>% + distinct(!!!syms(group_vars(pknca_data)), .keep_all = TRUE) %>% + unique() + o_nca <- suppressWarnings(PKNCA::pk.nca(pknca_data)) + + # Precompute column names and helper functions + time_col <- o_nca$data$conc$columns$time + conc_col <- o_nca$data$conc$columns$concentration + timeu_col <- o_nca$data$conc$columns$timeu + concu_col <- o_nca$data$conc$columns$concu + exclude_hl_col <- o_nca$data$conc$columns$exclude_half.life + if (is.null(exclude_hl_col)) { + o_nca$data$conc$data[["exclude_half.life"]] <- FALSE + exclude_hl_col <- "exclude_half.life" + } + + if (!"PPSTRES" %in% names(o_nca$result)) { + o_nca$result$PPSTRES <- o_nca$result$PPORRES + if ("PPORRESU" %in% names(o_nca$result)) { + o_nca$result$PPSTRESU <- o_nca$result$PPORRESU + } + } - start <- unique(group_nca$result$start) - tlast <- get_res("tlast") - half_life <- get_res("half.life") - adj_r2 <- get_res("adj.r.squared") - lz_time_first <- get_res("lambda.z.time.first") - lz_time_last <- get_res("lambda.z.time.last") - time_span <- lz_time_last - lz_time_first - span_ratio <- get_res("span.ratio") - exclude_calc_reason <- group_nca$result$exclude[group_nca$result$PPTESTCD == "half.life"] + # Prepare an object with all plot information + wide_output <- o_nca + wide_output$result <- wide_output$result %>% + filter(PPTESTCD %in% c("lambda.z.time.first", "lambda.z.time.last", "lambda.z", "adj.r.squared", "span.ratio")) %>% + select(-any_of(c("PPORRESU", "PPSTRESU", "PPSTRES"))) + wide_output <- as.data.frame(wide_output, out_format = "wide") %>% + unique() - if (!is.na(half_life)) { - is_lz_used <- get_halflife_points2( - group_nca$data$conc$data, - include_hl_col, - time_col, - lz_time_first, - lz_time_last, - exclude_hl_col + d_conc_with_res <- merge( + pknca_data$conc$data %>% + select(!!!syms(c(group_vars(pknca_data), time_col, conc_col, timeu_col, concu_col, exclude_hl_col, "ROWID"))), + wide_output, + all.x = TRUE, + by = c(group_vars(pknca_data)) + ) %>% + dplyr::filter(.[[time_col]] >= start & .[[time_col]] <= end) + + # Mark points used in half-life calculation + info_per_plot_list <- d_conc_with_res %>% + # Indicate plot details + dplyr::mutate( + subtitle = ifelse( + is.na(lambda.z), + exclude, + paste0( + "R2adj = ", + signif(adj.r.squared, 2), + "    ", + "span ratio = ", + signif(span.ratio, 2) ) - df_fit <- df[is_lz_used, ] - fit <- lm(as.formula(paste0("log10(", conc_col, ") ~ ", time_col)), df_fit) - fit_line_data <- data.frame(x = c(lz_time_first, tlast)) - colnames(fit_line_data) <- time_col - fit_line_data$y <- predict(fit, fit_line_data) + ), + xlab = ifelse( + !is.null(timeu_col), + paste0(time_col, " (", unique(.[[timeu_col]]), ")"), + time_col + ), + ylab = ifelse( + !is.null(concu_col), + paste0(conc_col, " (", unique(.[[concu_col]]), ")"), + conc_col + ) + ) %>% + # Mark points used in half-life calculation + mutate( + lambda.z.time.first = lambda.z.time.first + start, + lambda.z.time.last = lambda.z.time.last + start, + is_halflife_used = .[[time_col]] >= lambda.z.time.first & + .[[time_col]] <= lambda.z.time.last & + !.[[exclude_hl_col]] + ) %>% + group_by(!!!syms(c(group_vars(pknca_data), "start", "end"))) %>% + mutate( + is_halflife_used = if (any(is.na(lambda.z.time.first))) { + NA + } else { + is_halflife_used + } + ) %>% + ungroup() + + info_per_plot_list <- info_per_plot_list %>% + mutate( + color = ifelse(is.na(is_halflife_used), "black", + ifelse(is_halflife_used, "green", "red")), + symbol = ifelse(.[[exclude_hl_col]], "x", "circle") + ) %>% + group_by(!!!syms(c(group_vars(pknca_data), "start", "end"))) %>% + group_split() + + plot_list <- list() + data_list <- list() + for (i in seq_len(length(info_per_plot_list))) { + df <- info_per_plot_list[[i]] + + # Create line data + if (any(!is.na(df$is_halflife_used))) { + df_fit <- df[df$is_halflife_used, ] + fit <- lm(as.formula(paste0("log10(", conc_col, ") ~ ", time_col)), df_fit) + fit_line_data <- data.frame(x = c(df$lambda.z.time.first[1], max(df[[time_col]]))) + colnames(fit_line_data) <- time_col + fit_line_data$y <- predict(fit, fit_line_data) } else { - is_lz_used <- rep(NA_real_, nrow(df)) fit_line_data <- data.frame( - x = c(start, start), + x = c(df$start[1], df$start[1]), y = c(0, 0) ) colnames(fit_line_data)[1] <- time_col } - # Vectorize color, symbol, text assignment - plot_data <- df - plot_data$color <- ifelse(is.na(is_lz_used), "black", - ifelse(is_lz_used, "green", "red")) - plot_data$symbol <- ifelse(plot_data[[exclude_hl_col]], "x", "circle") - plot_data$text <- paste0( - "Data Point: ", plot_data[["IX"]], "\n", - "(", - plot_data[[time_col]], - ", ", - signif(plot_data[[conc_col]], 3), - ")" - ) - title <- paste0( - paste0(group_vars, ": "), - group[, group_vars, drop = FALSE], - collapse = ", " - ) - xlab <- if (!is.null(timeu_col)) { - paste0(time_col, " (", unique(plot_data[[timeu_col]]), ")") - } else { - time_col - } - ylab <- if (!is.null(concu_col)) { - paste0(conc_col, " (", unique(plot_data[[concu_col]]), ")") - } else { - conc_col - } - subtitle_text <- paste0( - "R2adj = ", - signif(adj_r2, 2), - "    ", - "span ratio = ", - signif(span_ratio, 2) + + # Unique plot ID based on grouping variables and interval times + plotid_vars <- c(group_vars(pknca_data), "start", "end") + plotid <- paste0( + paste0(plotid_vars, ": ", + df[1, plotid_vars, drop = FALSE], + collapse = ", " + ) ) - if (is.na(half_life)) { - subtitle_text <- exclude_calc_reason - } - # Update traces - # 1: fit line, 2: scatter - p_i$x$data[[1]]$x <- fit_line_data[[time_col]] - p_i$x$data[[1]]$y <- 10^fit_line_data$y - p_i$x$data[[2]]$x <- plot_data[[time_col]] - p_i$x$data[[2]]$y <- plot_data[[conc_col]] - p_i$x$data[[2]]$marker$color <- plot_data$color - p_i$x$data[[2]]$marker$symbol <- plot_data$symbol - p_i$x$data[[2]]$text <- plot_data$text - p_i$x$data[[2]]$customdata <- apply( - plot_data[, c(group_vars, "ROWID", "IX"), drop = FALSE], - 1, - function(row) as.list(set_names(row, c(group_vars, "ROWID", "IX"))) - ) %>% unname() - # Update layout - p_i$x$layout$title <- title - p_i$x$layout$xaxis$title <- xlab - p_i$x$layout$yaxis$title <- ylab - if (add_annotations) { - p_i$x$layout$annotations[[1]]$text <- subtitle_text - } - plots[[i]] <- p_i - names(plots)[i] <- paste0( - title, - ", start: ", group$start, - ", end: ", group$end + # Create the plot + plot_list[[plotid]] <- get_halflife_plots_single( + fit_line_data = fit_line_data, + plot_data = df, + time_col = time_col, + conc_col = conc_col, + title = paste0( + paste0(group_vars(pknca_data), ": "), + df[1, group_vars(pknca_data), drop = FALSE], + collapse = ", " + ), + xlab = df$xlab[1], + ylab = df$ylab[1], + subtitle_text = df$subtitle[1], + color = df$color, + symbol = df$symbol, + group_vars = group_vars(pknca_data), + add_annotations = add_annotations ) + data_list[[plotid]] <- df } - } - plots -} - -#' Custom fast version of get_halflife_points -#' @param data concentration data.frame -#' @param include_hl_col column name for include_half.life -#' @param time_col column name for time -#' @param lz_time_first numeric -#' @param lz_time_last numeric -#' @param exclude_hl_col column name for exclude_half.life -#' @return logical vector -get_halflife_points2 <- function( - data, include_hl_col, time_col, lz_time_first, lz_time_last, exclude_hl_col -) { - if (any(!is.na(data[[include_hl_col]]) & data[[include_hl_col]])) { - data[[include_hl_col]] - } else { - data[[time_col]] >= lz_time_first & data[[time_col]] <= lz_time_last & !data[[exclude_hl_col]] - } + return(list(plots = plot_list, data = data_list)) } diff --git a/R/utils-slope_selector.R b/R/utils-slope_selector.R index 6bc25a5ab..2c20c87cb 100644 --- a/R/utils-slope_selector.R +++ b/R/utils-slope_selector.R @@ -284,7 +284,7 @@ check_slope_rule_overlap <- function(existing, new, .keep = FALSE) { pknca_data$intervals, by = intersect(names(intervals_to_update), names(pknca_data$intervals)) ) - updated_plots <- suppressWarnings(get_halflife_plot(pknca_data)) + updated_plots <- suppressWarnings(get_halflife_plots(pknca_data)[["plots"]]) plot_outputs[names(updated_plots)] <- updated_plots plot_outputs } diff --git a/inst/shiny/modules/tab_nca/setup/slope_selector.R b/inst/shiny/modules/tab_nca/setup/slope_selector.R index 4ee7a9acf..9f7ddff9d 100644 --- a/inst/shiny/modules/tab_nca/setup/slope_selector.R +++ b/inst/shiny/modules/tab_nca/setup/slope_selector.R @@ -135,12 +135,12 @@ slope_selector_server <- function( # nolint if (changes$in_data) { # New data or major changes: regenerate all plots - plot_outputs(get_halflife_plot(new_pknca_data)) + plot_outputs(get_halflife_plots(new_pknca_data)[["plots"]]) } else if (changes$in_hl_adj) { - # Modify plots that had new half-life adjustments (inclusions/exclusions) + # Modify plots with new half-life adjustments (inclusions/exclusions) plot_outputs(handle_hl_adj_change(new_pknca_data, pknca_data(), plot_outputs())) } else if (changes$in_selected_intervals) { - # Modify plots that had interval changes (analyte, profile, specimen selection from setup.R) + # Add/remove plots based on interval changes (analyte, profile, specimen selection from setup.R) plot_outputs(handle_interval_change(new_pknca_data, pknca_data(), plot_outputs())) } diff --git a/tests/testthat/test-get_halflife_plot.R b/tests/testthat/test-get_halflife_plot.R index 9ce179158..a99393376 100644 --- a/tests/testthat/test-get_halflife_plot.R +++ b/tests/testthat/test-get_halflife_plot.R @@ -147,7 +147,7 @@ describe("lambda_slope_plot", { test_that("get_halflife_plot returns a list of plotly objects with valid input", { # Use the fixture data as input pknca_data <- FIXTURE_PKNCA_DATA - plots <- get_halflife_plot(pknca_data) + plots <- get_halflife_plots(pknca_data)[["plots"]] expect_type(plots, "list") expect_true(length(plots) >= 1) expect_s3_class(plots[[1]], "plotly") @@ -158,7 +158,7 @@ test_that("get_halflife_plot warns and returns empty list when no groups present # Remove all lambda.z rows from result to simulate no groups pknca_data <- FIXTURE_PKNCA_DATA pknca_data$conc$data <- pknca_data$conc$data[0, ] - plots <- get_halflife_plot(pknca_data) + plots <- get_halflife_plots(pknca_data)[["plots"]] expect_type(plots, "list") expect_length(plots, 0) }) @@ -169,7 +169,7 @@ test_that("get_halflife_plot: marker colors and shapes - no exclusion/inclusion" # Remove exclude/include columns if present, or set all to FALSE pknca_data$conc$data$exclude_half.life <- FALSE pknca_data$conc$data$include_half.life <- FALSE - plots <- get_halflife_plot(pknca_data) + plots <- get_halflife_plots(pknca_data)[["plots"]] expect_true(length(plots) >= 1) plot_data <- plots[[1]]$x$data[[2]] # All points should be black (no inclusion/exclusion) @@ -192,8 +192,8 @@ test_that("get_halflife_plot: marker colors and shapes - exclusion of a lambda.z FALSE ) ) - plots <- get_halflife_plot(pknca_data) - plots_with_excl <- get_halflife_plot(pknca_data_with_excl) + plots <- get_halflife_plots(pknca_data)[["plots"]] + plots_with_excl <- get_halflife_plots(pknca_data_with_excl) plots_details <- plots[[1]]$x$data[[2]]$marker expected_plots_details <- list( @@ -225,7 +225,7 @@ test_that("get_halflife_plot: marker colors and shapes - inclusion of lambda.z p pknca_data$conc$data$exclude_half.life <- FALSE pknca_data$conc$data$include_half.life <- FALSE pknca_data$conc$data$include_half.life[1:2] <- TRUE - plots <- get_halflife_plot(pknca_data) + plots <- get_halflife_plots(pknca_data)[["plots"]] expect_true(length(plots) >= 1) plot_data <- plots[[1]]$x$data[[2]] # At least two points should be green (included in lambda.z) From 1d9ad3645100ffeeb3b28a588f7cebff518ddac5 Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Mon, 15 Sep 2025 11:46:56 +0200 Subject: [PATCH 048/113] fix: make reading of columns before & solve issue with distinct --- R/get_halflife_plot.R | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/R/get_halflife_plot.R b/R/get_halflife_plot.R index 9a9312cef..0a9b2cbfd 100644 --- a/R/get_halflife_plot.R +++ b/R/get_halflife_plot.R @@ -97,25 +97,31 @@ get_halflife_plots_single <- function( get_halflife_plots <- function(pknca_data, add_annotations = TRUE) { + # If the input has empty concentration or intervals, just return an empty list + if (nrow(pknca_data$conc$data) == 0 || nrow(pknca_data$intervals) == 0) { + return(list(plots = list(), data = list())) + } + + # Identify column names + time_col <- pknca_data$conc$columns$time + conc_col <- pknca_data$conc$columns$concentration + timeu_col <- pknca_data$conc$columns$timeu + concu_col <- pknca_data$conc$columns$concu + exclude_hl_col <- pknca_data$conc$columns$exclude_half.life + + # Make sure to create a default exclude half life column if it does not exist + if (is.null(exclude_hl_col)) { + pknca_data$conc$data[["exclude_half.life"]] <- FALSE + exclude_hl_col <- "exclude_half.life" + } + # Adjust the input to compute half-life & show original row number pknca_data$conc$data$ROWID <- seq_len(nrow(pknca_data$conc$data)) pknca_data$intervals <- pknca_data$intervals %>% filter(type_interval == "main", half.life) %>% - distinct(!!!syms(group_vars(pknca_data)), .keep_all = TRUE) %>% unique() o_nca <- suppressWarnings(PKNCA::pk.nca(pknca_data)) - # Precompute column names and helper functions - time_col <- o_nca$data$conc$columns$time - conc_col <- o_nca$data$conc$columns$concentration - timeu_col <- o_nca$data$conc$columns$timeu - concu_col <- o_nca$data$conc$columns$concu - exclude_hl_col <- o_nca$data$conc$columns$exclude_half.life - if (is.null(exclude_hl_col)) { - o_nca$data$conc$data[["exclude_half.life"]] <- FALSE - exclude_hl_col <- "exclude_half.life" - } - if (!"PPSTRES" %in% names(o_nca$result)) { o_nca$result$PPSTRES <- o_nca$result$PPORRES if ("PPORRESU" %in% names(o_nca$result)) { From a339f9883b1a297622db8eb253625c640da49f53 Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Mon, 15 Sep 2025 11:48:13 +0200 Subject: [PATCH 049/113] test: reach 100% cov & add in setup exclude/include cols for PKNCA --- tests/testthat/setup.R | 4 +- tests/testthat/test-get_halflife_plot.R | 222 ++++++------------------ 2 files changed, 60 insertions(+), 166 deletions(-) diff --git a/tests/testthat/setup.R b/tests/testthat/setup.R index a7ce1f748..b78a3d222 100644 --- a/tests/testthat/setup.R +++ b/tests/testthat/setup.R @@ -344,7 +344,9 @@ base::local({ FIXTURE_PKNCA_DATA <<- PKNCA::PKNCAdata( data.conc = PKNCA::PKNCAconc(FIXTURE_CONC_DATA, AVAL ~ AFRLT | PCSPEC + USUBJID / PARAM, - concu = "AVALU", timeu = "RRLTU"), + concu = "AVALU", timeu = "RRLTU", + exclude_half.life = "exclude_half.life", + include_half.life = "include_half.life"), data.dose = PKNCA::PKNCAdose(FIXTURE_DOSE_DATA, DOSEA ~ AFRLT | USUBJID, route = "ROUTE", duration = "ADOSEDUR"), units = units_table diff --git a/tests/testthat/test-get_halflife_plot.R b/tests/testthat/test-get_halflife_plot.R index a99393376..d965ae213 100644 --- a/tests/testthat/test-get_halflife_plot.R +++ b/tests/testthat/test-get_halflife_plot.R @@ -1,149 +1,3 @@ -describe("lambda_slope_plot", { - - conc_pknca_df <- FIXTURE_PKNCA_DATA$conc$data %>% - # ToDo: The currerent lambda_slope_plot - # has additional non-neccesary assumptions - mutate(TIME = AFRLT, - PCSTRESU = AVALU) - - myres <- FIXTURE_PKNCA_RES - myres$result <- myres$result %>% - mutate(PPTESTCD = translate_terms( - PPTESTCD, "PPTESTCD", "PKNCA" - )) - - row_values <- myres$data$intervals %>% - filter(half.life) %>% - select(any_of(c( - unname(unlist(myres$data$conc$columns$groups)), - "NCA_PROFILE", "DOSNOA" - ))) %>% - filter(USUBJID == 5) - - it("returns a plotly object with valid input", { - plotly_output <- lambda_slope_plot( - conc_pknca_df = conc_pknca_df, - row_values = row_values, - myres = myres, - r2adj_threshold = 0.7 - ) - - expect_s3_class(plotly_output, "plotly") - expect_true("layout" %in% names(plotly_output$x)) - }) - - it("handles NA in lambda.z.n.points gracefully", { - myres_mod <- myres - - # Modify lambda.z.n.points to NA for the target row - myres_mod$result <- myres_mod$result %>% - mutate( - PPSTRES = ifelse( - PPTESTCD == "lambda.z.n.points" & - USUBJID == row_values$USUBJID, - NA_real_, - PPSTRES - ) - ) - - plotly_output <- lambda_slope_plot( - conc_pknca_df = conc_pknca_df, - row_values = row_values, - myres = myres_mod - ) - - expect_s3_class(plotly_output, "plotly") - }) - - it("warns and returns empty plot when AVAL <= 0", { - conc_modified <- conc_pknca_df - conc_modified$AVAL <- -1 - - expect_warning({ - empty_plot <- lambda_slope_plot( - conc_pknca_df = conc_modified, - row_values = row_values, - myres = myres - ) - expect_s3_class(empty_plot, "plotly") - }, "Not enough data for plotting") - }) - - it("returns without error and gives expected warning when plot_data has 0 rows", { - conc_modified <- conc_pknca_df %>% - mutate( - AVAL = ifelse( - USUBJID == row_values$USUBJID, - -1, - AVAL - ) - ) - - expect_warning( - lambda_slope_plot( - conc_pknca_df = conc_modified, - row_values = row_values, - myres = myres - ), - "Not enough data for plotting" - ) - }) - - it("shows warning when Cmax is included in lambda estimation", { - # Copy inputs - conc_modified <- conc_pknca_df - myres_modified <- myres - - # Use the same subject for consistency - test_id <- myres$data$intervals %>% - filter(half.life) %>% - select(any_of(c( - unname(unlist(myres$data$conc$columns$groups)), - "NCA_PROFILE", "DOSNOA" - ))) %>% - filter(USUBJID == 5) - - row_values <- test_id %>% as.list() - - # Get the corresponding Tmax value - tmax_value <- myres_modified$result %>% - filter(PPTESTCD == "tmax") %>% - filter( - USUBJID == row_values$USUBJID - ) %>% - pull(PPSTRES) - - # Force lambda.z.time.first to be *equal* to tmax, triggering Cmax inclusion - myres_modified$result <- myres_modified$result %>% - mutate( - PPSTRES = ifelse( - PPTESTCD == "lambda.z.time.first" & - USUBJID == row_values$USUBJID, - tmax_value, - PPSTRES - ) - ) - - # Run plot function - plotly_output <- lambda_slope_plot( - conc_pknca_df = conc_modified, - row_values = row_values, - myres = myres_modified - ) - - # Check for Cmax warning text - annotations <- plotly_output$x$layout$annotations - cmax_warn <- any(sapply(annotations, function(a) { - is.list(a) && !is.null(a$text) && - grepl("Cmax should not be included in lambda calculation", a$text) - })) - - expect_true(cmax_warn) - }) - -}) - - test_that("get_halflife_plot returns a list of plotly objects with valid input", { # Use the fixture data as input pknca_data <- FIXTURE_PKNCA_DATA @@ -187,13 +41,13 @@ test_that("get_halflife_plot: marker colors and shapes - exclusion of a lambda.z pknca_data_with_excl$conc$data <- pknca_data$conc$data %>% mutate( exclude_half.life = ifelse( - USUBJID == USUBJID[2] & AFRLT == 2.5, + USUBJID == unique(USUBJID)[2] & AFRLT == 2.5, TRUE, FALSE ) ) plots <- get_halflife_plots(pknca_data)[["plots"]] - plots_with_excl <- get_halflife_plots(pknca_data_with_excl) + plots_with_excl <- get_halflife_plots(pknca_data_with_excl)[["plots"]] plots_details <- plots[[1]]$x$data[[2]]$marker expected_plots_details <- list( @@ -204,33 +58,71 @@ test_that("get_halflife_plot: marker colors and shapes - exclusion of a lambda.z ) plots_with_excl_details <- plots_with_excl[[1]]$x$data[[2]]$marker expected_plots_details_with_excl <- list( - color = c("red", "black", "green", "green", "green"), + color = c("red", "red", "red", "green", "green"), size = 15, - symbol = c("x", "circle", "circle", "circle", "circle"), + symbol = c("circle", "circle", "x", "circle", "circle"), line = list(color = "rgba(255,127,14,1)") ) - - expect_true(length(plots) >= 1) - plot_data <- plots[[1]]$x$data[[2]] - # At least one point should be red (excluded) - expect_true(any(plot_data$marker$color == "red")) - # At least one point should be an 'x' (excluded) - expect_true(any(plot_data$marker$symbol == "x")) + expect_equal(plots_details, expected_plots_details, ignore_attr = TRUE) + expect_equal(plots_with_excl_details, expected_plots_details_with_excl, ignore_attr = TRUE) }) test_that("get_halflife_plot: marker colors and shapes - inclusion of lambda.z points", { pknca_data <- FIXTURE_PKNCA_DATA + pknca_data$intervals <- pknca_data$intervals[3,] + # Mark first two points as included in lambda.z pknca_data$conc$data$exclude_half.life <- FALSE - pknca_data$conc$data$include_half.life <- FALSE - pknca_data$conc$data$include_half.life[1:2] <- TRUE + pknca_data$conc$data$include_half.life <- NA + pknca_data_with_incl <- pknca_data + pknca_data_with_incl$conc$data <- pknca_data$conc$data %>% + mutate( + include_half.life = ifelse( + USUBJID == unique(USUBJID)[3] & AFRLT >= 0.5, + TRUE, + FALSE + ) + ) plots <- get_halflife_plots(pknca_data)[["plots"]] - expect_true(length(plots) >= 1) - plot_data <- plots[[1]]$x$data[[2]] - # At least two points should be green (included in lambda.z) - expect_true(sum(plot_data$marker$color == "green") >= 2) - # Included points should be circles - expect_true(all(plot_data$marker$symbol[plot_data$marker$color == "green"] == "circle")) + plots_with_incl <- get_halflife_plots(pknca_data_with_incl)[["plots"]] + + plots_details <- plots[[1]]$x$data[[2]]$marker + expected_plots_details <- list( + color = c("red", "red", "green", "green", "green"), + size = 15, + symbol = c("circle", "circle", "circle", "circle", "circle"), + line = list(color = "rgba(255,127,14,1)") + ) + plots_with_incl_details <- plots_with_incl[[1]]$x$data[[2]]$marker + expected_plots_details_with_incl <- list( + color = c("green", "green", "green", "green", "green"), + size = 15, + symbol = c("circle", "circle", "circle", "circle", "circle"), + line = list(color = "rgba(255,127,14,1)") + ) + + expect_equal(plots_details, expected_plots_details, ignore_attr = TRUE) + expect_equal(plots_with_incl_details, expected_plots_details_with_incl, ignore_attr = TRUE) }) +# Test when there is no exclusion column specified +test_that("get_halflife_plot: marker colors and shapes - exclusion column missing still works", { + pknca_data <- FIXTURE_PKNCA_DATA + pknca_data$intervals <- pknca_data$intervals[2,] + + # Remove the exclude_half.life column + pknca_data$conc$data$exclude_half.life <- NULL + pknca_data$conc$columns$exclude_half.life <- NULL + plots <- get_halflife_plots(pknca_data)[["plots"]] + + plots_details <- plots[[1]]$x$data[[2]]$marker + expected_plots_details <- list( + color = c("red", "red", "green", "green", "green"), + size = 15, + symbol = c("circle", "circle", "circle", "circle", "circle"), + line = list(color = "rgba(255,127,14,1)") + ) + + expect_equal(plots_details, expected_plots_details, ignore_attr = TRUE) +}) \ No newline at end of file From 420930179d17e4fa482ca31b52215646f96aae1a Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Mon, 15 Sep 2025 12:04:23 +0200 Subject: [PATCH 050/113] rename: to get_halflife_plots & refactor with describe/it the tests --- ...t_halflife_plot.R => get_halflife_plots.R} | 0 tests/testthat/test-get_halflife_plot.R | 128 ------------------ tests/testthat/test-get_halflife_plots.R | 118 ++++++++++++++++ 3 files changed, 118 insertions(+), 128 deletions(-) rename R/{get_halflife_plot.R => get_halflife_plots.R} (100%) delete mode 100644 tests/testthat/test-get_halflife_plot.R create mode 100644 tests/testthat/test-get_halflife_plots.R diff --git a/R/get_halflife_plot.R b/R/get_halflife_plots.R similarity index 100% rename from R/get_halflife_plot.R rename to R/get_halflife_plots.R diff --git a/tests/testthat/test-get_halflife_plot.R b/tests/testthat/test-get_halflife_plot.R deleted file mode 100644 index d965ae213..000000000 --- a/tests/testthat/test-get_halflife_plot.R +++ /dev/null @@ -1,128 +0,0 @@ -test_that("get_halflife_plot returns a list of plotly objects with valid input", { - # Use the fixture data as input - pknca_data <- FIXTURE_PKNCA_DATA - plots <- get_halflife_plots(pknca_data)[["plots"]] - expect_type(plots, "list") - expect_true(length(plots) >= 1) - expect_s3_class(plots[[1]], "plotly") - expect_true("layout" %in% names(plots[[1]]$x)) -}) - -test_that("get_halflife_plot warns and returns empty list when no groups present", { - # Remove all lambda.z rows from result to simulate no groups - pknca_data <- FIXTURE_PKNCA_DATA - pknca_data$conc$data <- pknca_data$conc$data[0, ] - plots <- get_halflife_plots(pknca_data)[["plots"]] - expect_type(plots, "list") - expect_length(plots, 0) -}) - -# Marker color/shape tests for get_halflife_plot -test_that("get_halflife_plot: marker colors and shapes - no exclusion/inclusion", { - pknca_data <- FIXTURE_PKNCA_DATA - # Remove exclude/include columns if present, or set all to FALSE - pknca_data$conc$data$exclude_half.life <- FALSE - pknca_data$conc$data$include_half.life <- FALSE - plots <- get_halflife_plots(pknca_data)[["plots"]] - expect_true(length(plots) >= 1) - plot_data <- plots[[1]]$x$data[[2]] - # All points should be black (no inclusion/exclusion) - expect_true(all(plot_data$marker$color == "black")) - # All points should be circles (no exclusion) - expect_true(all(plot_data$marker$symbol == "circle")) -}) - -test_that("get_halflife_plot: marker colors and shapes - exclusion of a lambda.z point", { - pknca_data <- FIXTURE_PKNCA_DATA - pknca_data$intervals <- pknca_data$intervals[2,] - - # Exclude a point in the lambda.z calculation - pknca_data_with_excl <- pknca_data - pknca_data_with_excl$conc$data <- pknca_data$conc$data %>% - mutate( - exclude_half.life = ifelse( - USUBJID == unique(USUBJID)[2] & AFRLT == 2.5, - TRUE, - FALSE - ) - ) - plots <- get_halflife_plots(pknca_data)[["plots"]] - plots_with_excl <- get_halflife_plots(pknca_data_with_excl)[["plots"]] - - plots_details <- plots[[1]]$x$data[[2]]$marker - expected_plots_details <- list( - color = c("red", "red", "green", "green", "green"), - size = 15, - symbol = c("circle", "circle", "circle", "circle", "circle"), - line = list(color = "rgba(255,127,14,1)") - ) - plots_with_excl_details <- plots_with_excl[[1]]$x$data[[2]]$marker - expected_plots_details_with_excl <- list( - color = c("red", "red", "red", "green", "green"), - size = 15, - symbol = c("circle", "circle", "x", "circle", "circle"), - line = list(color = "rgba(255,127,14,1)") - ) - - expect_equal(plots_details, expected_plots_details, ignore_attr = TRUE) - expect_equal(plots_with_excl_details, expected_plots_details_with_excl, ignore_attr = TRUE) -}) - -test_that("get_halflife_plot: marker colors and shapes - inclusion of lambda.z points", { - pknca_data <- FIXTURE_PKNCA_DATA - pknca_data$intervals <- pknca_data$intervals[3,] - - # Mark first two points as included in lambda.z - pknca_data$conc$data$exclude_half.life <- FALSE - pknca_data$conc$data$include_half.life <- NA - pknca_data_with_incl <- pknca_data - pknca_data_with_incl$conc$data <- pknca_data$conc$data %>% - mutate( - include_half.life = ifelse( - USUBJID == unique(USUBJID)[3] & AFRLT >= 0.5, - TRUE, - FALSE - ) - ) - plots <- get_halflife_plots(pknca_data)[["plots"]] - plots_with_incl <- get_halflife_plots(pknca_data_with_incl)[["plots"]] - - plots_details <- plots[[1]]$x$data[[2]]$marker - expected_plots_details <- list( - color = c("red", "red", "green", "green", "green"), - size = 15, - symbol = c("circle", "circle", "circle", "circle", "circle"), - line = list(color = "rgba(255,127,14,1)") - ) - plots_with_incl_details <- plots_with_incl[[1]]$x$data[[2]]$marker - expected_plots_details_with_incl <- list( - color = c("green", "green", "green", "green", "green"), - size = 15, - symbol = c("circle", "circle", "circle", "circle", "circle"), - line = list(color = "rgba(255,127,14,1)") - ) - - expect_equal(plots_details, expected_plots_details, ignore_attr = TRUE) - expect_equal(plots_with_incl_details, expected_plots_details_with_incl, ignore_attr = TRUE) -}) - -# Test when there is no exclusion column specified -test_that("get_halflife_plot: marker colors and shapes - exclusion column missing still works", { - pknca_data <- FIXTURE_PKNCA_DATA - pknca_data$intervals <- pknca_data$intervals[2,] - - # Remove the exclude_half.life column - pknca_data$conc$data$exclude_half.life <- NULL - pknca_data$conc$columns$exclude_half.life <- NULL - plots <- get_halflife_plots(pknca_data)[["plots"]] - - plots_details <- plots[[1]]$x$data[[2]]$marker - expected_plots_details <- list( - color = c("red", "red", "green", "green", "green"), - size = 15, - symbol = c("circle", "circle", "circle", "circle", "circle"), - line = list(color = "rgba(255,127,14,1)") - ) - - expect_equal(plots_details, expected_plots_details, ignore_attr = TRUE) -}) \ No newline at end of file diff --git a/tests/testthat/test-get_halflife_plots.R b/tests/testthat/test-get_halflife_plots.R new file mode 100644 index 000000000..ec557bf09 --- /dev/null +++ b/tests/testthat/test-get_halflife_plots.R @@ -0,0 +1,118 @@ +describe("get_halflife_plot", { + + it("returns a list of plotly objects with valid input", { + pknca_data <- FIXTURE_PKNCA_DATA + plots <- get_halflife_plots(pknca_data)[["plots"]] + expect_type(plots, "list") + expect_true(length(plots) >= 1) + expect_s3_class(plots[[1]], "plotly") + expect_true("layout" %in% names(plots[[1]]$x)) + }) + + it("warns and returns empty list when no groups present", { + pknca_data <- FIXTURE_PKNCA_DATA + pknca_data$conc$data <- pknca_data$conc$data[0, ] + plots <- get_halflife_plots(pknca_data)[["plots"]] + expect_type(plots, "list") + expect_length(plots, 0) + }) + + it("marker colors and shapes - no exclusion/inclusion", { + pknca_data <- FIXTURE_PKNCA_DATA + pknca_data$conc$data$exclude_half.life <- FALSE + pknca_data$conc$data$include_half.life <- FALSE + plots <- get_halflife_plots(pknca_data)[["plots"]] + expect_true(length(plots) >= 1) + plot_data <- plots[[1]]$x$data[[2]] + expect_true(all(plot_data$marker$color == "black")) + expect_true(all(plot_data$marker$symbol == "circle")) + }) + + it("marker colors and shapes - exclusion of a lambda.z point", { + pknca_data <- FIXTURE_PKNCA_DATA + pknca_data$intervals <- pknca_data$intervals[2,] + pknca_data_with_excl <- pknca_data + pknca_data_with_excl$conc$data <- pknca_data$conc$data %>% + mutate( + exclude_half.life = ifelse( + USUBJID == unique(USUBJID)[2] & AFRLT == 2.5, + TRUE, + FALSE + ) + ) + plots <- get_halflife_plots(pknca_data)[["plots"]] + plots_with_excl <- get_halflife_plots(pknca_data_with_excl)[["plots"]] + + plots_details <- plots[[1]]$x$data[[2]]$marker + expected_plots_details <- list( + color = c("red", "red", "green", "green", "green"), + size = 15, + symbol = c("circle", "circle", "circle", "circle", "circle"), + line = list(color = "rgba(255,127,14,1)") + ) + plots_with_excl_details <- plots_with_excl[[1]]$x$data[[2]]$marker + expected_plots_details_with_excl <- list( + color = c("red", "red", "red", "green", "green"), + size = 15, + symbol = c("circle", "circle", "x", "circle", "circle"), + line = list(color = "rgba(255,127,14,1)") + ) + + expect_equal(plots_details, expected_plots_details, ignore_attr = TRUE) + expect_equal(plots_with_excl_details, expected_plots_details_with_excl, ignore_attr = TRUE) + }) + + it("marker colors and shapes - inclusion of lambda.z points", { + pknca_data <- FIXTURE_PKNCA_DATA + pknca_data$intervals <- pknca_data$intervals[3,] + pknca_data$conc$data$exclude_half.life <- FALSE + pknca_data$conc$data$include_half.life <- NA + pknca_data_with_incl <- pknca_data + pknca_data_with_incl$conc$data <- pknca_data$conc$data %>% + mutate( + include_half.life = ifelse( + USUBJID == unique(USUBJID)[3] & AFRLT >= 0.5, + TRUE, + FALSE + ) + ) + plots <- get_halflife_plots(pknca_data)[["plots"]] + plots_with_incl <- get_halflife_plots(pknca_data_with_incl)[["plots"]] + + plots_details <- plots[[1]]$x$data[[2]]$marker + expected_plots_details <- list( + color = c("red", "red", "green", "green", "green"), + size = 15, + symbol = c("circle", "circle", "circle", "circle", "circle"), + line = list(color = "rgba(255,127,14,1)") + ) + plots_with_incl_details <- plots_with_incl[[1]]$x$data[[2]]$marker + expected_plots_details_with_incl <- list( + color = c("green", "green", "green", "green", "green"), + size = 15, + symbol = c("circle", "circle", "circle", "circle", "circle"), + line = list(color = "rgba(255,127,14,1)") + ) + + expect_equal(plots_details, expected_plots_details, ignore_attr = TRUE) + expect_equal(plots_with_incl_details, expected_plots_details_with_incl, ignore_attr = TRUE) + }) + + it("marker colors and shapes - exclusion column missing still works", { + pknca_data <- FIXTURE_PKNCA_DATA + pknca_data$intervals <- pknca_data$intervals[2,] + pknca_data$conc$data$exclude_half.life <- NULL + pknca_data$conc$columns$exclude_half.life <- NULL + plots <- get_halflife_plots(pknca_data)[["plots"]] + + plots_details <- plots[[1]]$x$data[[2]]$marker + expected_plots_details <- list( + color = c("red", "red", "green", "green", "green"), + size = 15, + symbol = c("circle", "circle", "circle", "circle", "circle"), + line = list(color = "rgba(255,127,14,1)") + ) + expect_equal(plots_details, expected_plots_details, ignore_attr = TRUE) + }) + +}) \ No newline at end of file From 95136dab9c56c3fdcef14271b446b38fb39475c2 Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Mon, 15 Sep 2025 15:22:01 +0200 Subject: [PATCH 051/113] draft: new tests for utils-slope_selector.R --- tests/testthat/test-utils-slope_selector.R | 172 +++++++++++---------- 1 file changed, 94 insertions(+), 78 deletions(-) diff --git a/tests/testthat/test-utils-slope_selector.R b/tests/testthat/test-utils-slope_selector.R index cf70ace71..434b387af 100644 --- a/tests/testthat/test-utils-slope_selector.R +++ b/tests/testthat/test-utils-slope_selector.R @@ -24,77 +24,7 @@ DOSNOS_FIXTURE <- data.frame( slope_groups <- c("USUBJID", "PARAM", "PCSPEC", "NCA_PROFILE") -describe(".filter_slopes", { - it("should handle slope selection", { - selection <- data.frame( - TYPE = rep("Selection", 2), - USUBJID = c(1, 3), - NCA_PROFILE = c(1, 1), - PARAM = c("A", "A"), - PCSPEC = c(1, 1), - RANGE = c("1:3", "2:4"), - REASON = "Test selection" - ) - - res <- filter_slopes(DATA_FIXTURE, selection, DOSNOS_FIXTURE, slope_groups) - - expect_true(all(res$is.included.hl[c(1:3, 6:8)])) - expect_true(all(res$REASON[c(1:3, 6:8)] == "Test selection")) - }) - - it("should handle slope exclusion", { - exclusion <- data.frame( - TYPE = rep("Exclusion", 2), - USUBJID = c(2, 4), - NCA_PROFILE = c(1, 1), - PARAM = c("A", "A"), - PCSPEC = c(1, 1), - RANGE = c("1:2", "2:3"), - REASON = "Test exclusion" - ) - - res <- filter_slopes(DATA_FIXTURE, exclusion, DOSNOS_FIXTURE, slope_groups) - - expect_true(all(res$is.excluded.hl[c(5, 6, 14, 15)])) - expect_true(all(res$REASON[c(5, 6, 14, 15)] == "Test exclusion")) - }) - - it("should throw an error for invalid data", { - - expect_error(filter_slopes(NULL, NULL, DOSNOS_FIXTURE), "Please provide valid data.") - expect_error(filter_slopes(list(), NULL, DOSNOS_FIXTURE), "Please provide valid data.") - expect_error( - filter_slopes(list(conc = list()), NULL, DOSNOS_FIXTURE), "Please provide valid data." - ) - expect_error( - filter_slopes(list(conc = list()), NULL, DOSNOS_FIXTURE), "Please provide valid data." - ) - }) - - it("should throw an error if reasons are missing", { - selection <- data.frame( - TYPE = rep("Exclusion", 2), - USUBJID = c(1, 3), - NCA_PROFILE = c(1, 1), - PARAM = c("A", "A"), - PCSPEC = c(1, 1), - RANGE = c("1:3", "2:4"), - REASON = "" - ) - - expect_error( - filter_slopes(DATA_FIXTURE, selection, DOSNOS_FIXTURE, slope_groups, TRUE), - "^No reason provided for the following exclusions*" - ) - }) - it("should return data unchanged if no slopes are provided", { - res_null <- filter_slopes(DATA_FIXTURE, NULL, DOSNOS_FIXTURE, slope_groups) - res_empty <- filter_slopes(DATA_FIXTURE, data.frame(), DOSNOS_FIXTURE, slope_groups) - expect_equal(res_null, DATA_FIXTURE) - expect_equal(res_empty, DATA_FIXTURE) - }) -}) EXISTING_FIXTURE <- data.frame( TYPE = "Exclusion", @@ -117,7 +47,7 @@ describe("check_slope_rule_overlap", { RANGE = "1:3" ) - expect_equal(nrow(check_slope_rule_overlap(EXISTING_FIXTURE, NEW, slope_groups)), 2) + expect_equal(nrow(check_slope_rule_overlap(EXISTING_FIXTURE, NEW)), 2) # different USUBJID # NEW <- data.frame( @@ -129,7 +59,7 @@ describe("check_slope_rule_overlap", { RANGE = "1:3" ) - expect_equal(nrow(check_slope_rule_overlap(EXISTING_FIXTURE, NEW, slope_groups)), 2) + expect_equal(nrow(check_slope_rule_overlap(EXISTING_FIXTURE, NEW)), 2) # different NCA_PROFILE # NEW <- data.frame( @@ -141,7 +71,7 @@ describe("check_slope_rule_overlap", { RANGE = "1:3" ) - expect_equal(nrow(check_slope_rule_overlap(EXISTING_FIXTURE, NEW, slope_groups)), 2) + expect_equal(nrow(check_slope_rule_overlap(EXISTING_FIXTURE, NEW)), 2) }) @@ -155,7 +85,7 @@ describe("check_slope_rule_overlap", { RANGE = "4:5" ) - expect_equal(check_slope_rule_overlap(EXISTING_FIXTURE, NEW, slope_groups)$RANGE, "3,6") + expect_equal(check_slope_rule_overlap(EXISTING_FIXTURE, NEW)$RANGE, "3,6") NEW <- data.frame( TYPE = "Exclusion", @@ -166,7 +96,7 @@ describe("check_slope_rule_overlap", { RANGE = "3:4" ) - expect_equal(check_slope_rule_overlap(EXISTING_FIXTURE, NEW, slope_groups)$RANGE, "5:6") + expect_equal(check_slope_rule_overlap(EXISTING_FIXTURE, NEW)$RANGE, "5:6") }) @@ -180,7 +110,7 @@ describe("check_slope_rule_overlap", { RANGE = "4:9" ) - expect_equal(check_slope_rule_overlap(EXISTING_FIXTURE, NEW, slope_groups)$RANGE, "3:9") + expect_equal(check_slope_rule_overlap(EXISTING_FIXTURE, NEW)$RANGE, "3:9") }) @@ -194,7 +124,7 @@ describe("check_slope_rule_overlap", { RANGE = "3:6" ) - expect_equal(nrow(check_slope_rule_overlap(EXISTING_FIXTURE, NEW, slope_groups)), 0) + expect_equal(nrow(check_slope_rule_overlap(EXISTING_FIXTURE, NEW)), 0) }) @@ -214,8 +144,94 @@ describe("check_slope_rule_overlap", { ) expect_warning( - check_slope_rule_overlap(rbind(EXISTING, DUPLICATE), DUPLICATE, slope_groups), + check_slope_rule_overlap(rbind(EXISTING, DUPLICATE), DUPLICATE), "More than one range for single subject, profile and rule type detected." ) }) }) + +describe("detect_pknca_data_changes", { + it("detects changes in data, hl_adj, and intervals", { + # Setup minimal old and new objects + + old <- FIXTURE_PKNCA_DATA + + # No change + new <- old + res <- detect_pknca_data_changes(old, new, "exclude_half.life", "include_half.life") + expect_false(res$in_data) + expect_false(res$in_hl_adj) + expect_false(res$in_selected_intervals) + # Change in data + new2 <- new + new2$conc$data$VAL[1] <- 99 + res2 <- detect_pknca_data_changes(old, new2, "exclude_half.life", "include_half.life") + expect_true(res2$in_data) + # Change in hl_adj + new3 <- new + new3$conc$data$exclude_half.life[2] <- TRUE + res3 <- detect_pknca_data_changes(old, new3, "exclude_half.life", "include_half.life") + expect_true(res3$in_hl_adj) + # Change in intervals + new4 <- new + new4$intervals <- data.frame(ID = 1:4) + res4 <- detect_pknca_data_changes(old, new4, "exclude_half.life", "include_half.life") + expect_true(res4$in_selected_intervals) + }) +}) + +describe("handle_hl_adj_change", { + it("updates only affected groups in plot_outputs", { + # Setup dummy PKNCA data objects and plot_outputs + old_pknca_data <- FIXTURE_PKNCA_DATA + new_pknca_data <- old_pknca_data + new_pknca_data$conc$data <- new_pknca_data$conc$data %>% + mutate( + exclude_half.life = ifelse( + USUBJID == unique(USUBJID)[3] & AFRLT == 4.5, + TRUE, + exclude_half.life + ) + ) + + old_plots <- get_halflife_plots(old_pknca_data)$plots + + # Patch .update_plots_with_pknca to just return a marker + new_plots <- handle_hl_adj_change(new_pknca_data, old_pknca_data, old_plots) + + ix_unchanged_plots <- setdiff(seq_len(length(new_plots)), 4) + expect_equal(new_plots[ix_unchanged_plots], old_plots[ix_unchanged_plots]) + + old_plots_exp_details <- list( + color = c("red", "red", "green", "green", "green"), + size = 15, + symbol = c("circle", "circle", "circle", "circle", "circle"), + line = list(color = "rgba(255,127,14,1)") + ) + + new_plots_exp_details <- list( + color = c("red", "green", "green", "red", "green"), + size = 15, + symbol = c("circle", "circle", "circle", "x", "circle"), + line = list(color = "rgba(255,127,14,1)") + ) + + expect_equal(old_plots[[4]]$x$data[[2]]$marker, old_plots_exp_details, ignore_attr = TRUE) + expect_equal(new_plots[[4]]$x$data[[2]]$marker, new_plots_exp_details, ignore_attr = TRUE) + }) +}) + +describe("handle_interval_change", { + it("updates and removes plots based on interval changes", { + new_pknca_data <- FIXTURE_PKNCA_DATA + new_pknca_data$intervals <- new_pknca_data$intervals[2:3, ] + old_pknca_data <- FIXTURE_PKNCA_DATA + + old_plots <- get_halflife_plots(old_pknca_data)$plots + new_plots <- handle_interval_change(new_pknca_data, old_pknca_data, old_plots) + + expect_equal(length(old_plots), 13) + expect_equal(length(new_plots), 2) + expect_equal(old_plots[2:3], new_plots) + }) +}) From 8f639527049f61cd6b7ca2675efd60748744d28c Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Mon, 15 Sep 2025 15:51:18 +0200 Subject: [PATCH 052/113] man: add new manuals for the processing --- man/arrange_plots_by_groups.Rd | 20 +++++++++++ man/detect_pknca_data_changes.Rd | 29 ++++++++++++++++ man/dot-update_pknca_with_rules.Rd | 19 +++++++++++ man/dot-update_plots_with_pknca.Rd | 21 ++++++++++++ man/dot-update_plots_with_rules.Rd | 28 +++++++++++++++ man/get_halflife_plots_single.Rd | 55 ++++++++++++++++++++++++++++++ man/handle_hl_adj_change.Rd | 21 ++++++++++++ man/handle_interval_change.Rd | 21 ++++++++++++ man/parse_plot_names_to_df.Rd | 18 ++++++++++ 9 files changed, 232 insertions(+) create mode 100644 man/arrange_plots_by_groups.Rd create mode 100644 man/detect_pknca_data_changes.Rd create mode 100644 man/dot-update_pknca_with_rules.Rd create mode 100644 man/dot-update_plots_with_pknca.Rd create mode 100644 man/dot-update_plots_with_rules.Rd create mode 100644 man/get_halflife_plots_single.Rd create mode 100644 man/handle_hl_adj_change.Rd create mode 100644 man/handle_interval_change.Rd create mode 100644 man/parse_plot_names_to_df.Rd diff --git a/man/arrange_plots_by_groups.Rd b/man/arrange_plots_by_groups.Rd new file mode 100644 index 000000000..46131216c --- /dev/null +++ b/man/arrange_plots_by_groups.Rd @@ -0,0 +1,20 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/utils-slope_selector.R +\name{arrange_plots_by_groups} +\alias{arrange_plots_by_groups} +\title{Arrange Plots by Group Columns} +\usage{ +arrange_plots_by_groups(named_list, group_cols) +} +\arguments{ +\item{named_list}{A named list of plots, with names in the format 'col1: val1, col2: val2, ...'.} + +\item{group_cols}{Character vector of column names to sort by.} +} +\value{ +A named list of plots, ordered by the specified group columns. +} +\description{ +Orders a named list of plots according to specified grouping columns. +Assumes a specific naming format (i.e, 'col1: val1, col2: val2, ...'). +} diff --git a/man/detect_pknca_data_changes.Rd b/man/detect_pknca_data_changes.Rd new file mode 100644 index 000000000..1a85198b1 --- /dev/null +++ b/man/detect_pknca_data_changes.Rd @@ -0,0 +1,29 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/utils-slope_selector.R +\name{detect_pknca_data_changes} +\alias{detect_pknca_data_changes} +\title{Slope Selector Utility Functions} +\usage{ +detect_pknca_data_changes(old, new, excl_hl_col, incl_hl_col) +} +\arguments{ +\item{old}{Previous PKNCA data object} + +\item{new}{New PKNCA data object} + +\item{excl_hl_col}{Name of exclusion column} + +\item{incl_hl_col}{Name of inclusion column} +} +\value{ +List with logicals: in_data, in_hl_adj, in_selected_intervals +} +\description{ +These helpers support the slope selection workflow by detecting changes in PKNCA data, +updating plots, and managing slope rule logic. Used internally by the slope selector module. +Detect changes between old and new PKNCA data +} +\details{ +Compares two PKNCA data objects to determine if the underlying data, half-life adjustments, +or selected intervals have changed. Used to decide when to update plots. +} diff --git a/man/dot-update_pknca_with_rules.Rd b/man/dot-update_pknca_with_rules.Rd new file mode 100644 index 000000000..427506fd0 --- /dev/null +++ b/man/dot-update_pknca_with_rules.Rd @@ -0,0 +1,19 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/utils-slope_selector.R +\name{.update_pknca_with_rules} +\alias{.update_pknca_with_rules} +\title{Apply Slope Rules to Update Data} +\usage{ +.update_pknca_with_rules(data, slopes) +} +\arguments{ +\item{data}{PKNCA data object} + +\item{slopes}{Data frame of slope rules (TYPE, RANGE, REASON, group columns)} +} +\value{ +Modified data object with updated flags +} +\description{ +Iterates over the given rules and updates the PKNCA object setting inclusion/exclusion flags. +} diff --git a/man/dot-update_plots_with_pknca.Rd b/man/dot-update_plots_with_pknca.Rd new file mode 100644 index 000000000..b487a2f70 --- /dev/null +++ b/man/dot-update_plots_with_pknca.Rd @@ -0,0 +1,21 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/utils-slope_selector.R +\name{.update_plots_with_pknca} +\alias{.update_plots_with_pknca} +\title{Update plots with PKNCA data (for affected intervals)} +\usage{ +.update_plots_with_pknca(pknca_data, plot_outputs, intervals_to_update = NULL) +} +\arguments{ +\item{pknca_data}{PKNCA data object} + +\item{plot_outputs}{Named list of current plot outputs} + +\item{intervals_to_update}{Data frame of intervals to update (default: all in pknca_data)} +} +\value{ +Updated plot_outputs (named list) +} +\description{ +Regenerates plots for the specified intervals in the plot_outputs list. +} diff --git a/man/dot-update_plots_with_rules.Rd b/man/dot-update_plots_with_rules.Rd new file mode 100644 index 000000000..7bc6b83de --- /dev/null +++ b/man/dot-update_plots_with_rules.Rd @@ -0,0 +1,28 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/utils-slope_selector.R +\name{.update_plots_with_rules} +\alias{.update_plots_with_rules} +\title{Update plots with manual slope rules} +\usage{ +.update_plots_with_rules( + pknca_data, + manual_slopes, + plot_outputs, + groups_to_update = NULL +) +} +\arguments{ +\item{pknca_data}{PKNCA data object} + +\item{manual_slopes}{Data frame of manual slope rules} + +\item{plot_outputs}{Named list of current plot outputs} + +\item{groups_to_update}{Optional: data frame of intervals to update (default: all)} +} +\value{ +Updated plot_outputs (named list) +} +\description{ +Updates the plot_outputs list by applying manual slopes. +} diff --git a/man/get_halflife_plots_single.Rd b/man/get_halflife_plots_single.Rd new file mode 100644 index 000000000..7c324a3e5 --- /dev/null +++ b/man/get_halflife_plots_single.Rd @@ -0,0 +1,55 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/get_halflife_plots.R +\name{get_halflife_plots_single} +\alias{get_halflife_plots_single} +\title{Create a Plotly Half-life Plot} +\usage{ +get_halflife_plots_single( + fit_line_data, + plot_data, + time_col, + conc_col, + title, + xlab, + ylab, + subtitle_text, + color, + symbol, + group_vars, + add_annotations = TRUE, + text = NULL +) +} +\arguments{ +\item{fit_line_data}{Data frame for the fit line (must have columns for time and y)} + +\item{plot_data}{Data frame for the scatter points} + +\item{time_col}{Name of the time column (string)} + +\item{conc_col}{Name of the concentration column (string)} + +\item{title}{Plot title} + +\item{xlab}{X axis label} + +\item{ylab}{Y axis label} + +\item{subtitle_text}{Subtitle/annotation (HTML allowed)} + +\item{color}{Vector of colors for points (same length as plot_data)} + +\item{symbol}{Vector of marker symbols for points (same length as plot_data)} + +\item{group_vars}{Character vector of grouping variable names (for customdata)} + +\item{add_annotations}{Logical, whether to add the subtitle annotation} + +\item{text}{Optional vector of hover text for points (same length as plot_data)} +} +\value{ +A plotly object +} +\description{ +Generates a plotly plot for NCA half-life visualization, with a fit line and scatter points. +} diff --git a/man/handle_hl_adj_change.Rd b/man/handle_hl_adj_change.Rd new file mode 100644 index 000000000..31a447e6c --- /dev/null +++ b/man/handle_hl_adj_change.Rd @@ -0,0 +1,21 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/utils-slope_selector.R +\name{handle_hl_adj_change} +\alias{handle_hl_adj_change} +\title{Handle half-life adjustment changes} +\usage{ +handle_hl_adj_change(new_pknca_data, old_pknca_data, plot_outputs) +} +\arguments{ +\item{new_pknca_data}{New PKNCA data object} + +\item{old_pknca_data}{Previous PKNCA data object} + +\item{plot_outputs}{Current plot outputs (named list)} +} +\value{ +Updated plot_outputs (named list) +} +\description{ +Updates only the plots affected by changes in half-life inclusion/exclusion columns. +} diff --git a/man/handle_interval_change.Rd b/man/handle_interval_change.Rd new file mode 100644 index 000000000..948371293 --- /dev/null +++ b/man/handle_interval_change.Rd @@ -0,0 +1,21 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/utils-slope_selector.R +\name{handle_interval_change} +\alias{handle_interval_change} +\title{Handle interval changes} +\usage{ +handle_interval_change(new_pknca_data, old_pknca_data, plot_outputs) +} +\arguments{ +\item{new_pknca_data}{New PKNCA data object} + +\item{old_pknca_data}{Previous PKNCA data object} + +\item{plot_outputs}{Current plot outputs (named list)} +} +\value{ +Updated plot_outputs (named list) +} +\description{ +Updates plots when the set of selected intervals changes (e.g., analyte/profile selection). +} diff --git a/man/parse_plot_names_to_df.Rd b/man/parse_plot_names_to_df.Rd new file mode 100644 index 000000000..03df052cb --- /dev/null +++ b/man/parse_plot_names_to_df.Rd @@ -0,0 +1,18 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/utils-slope_selector.R +\name{parse_plot_names_to_df} +\alias{parse_plot_names_to_df} +\title{Parse Plot Names to Data Frame} +\usage{ +parse_plot_names_to_df(named_list) +} +\arguments{ +\item{named_list}{A named list or vector, where names are key-value pairs separated by commas.} +} +\value{ +A data frame with columns for each key and a PLOTID column with the original names. +} +\description{ +Converts a named list of plots (with names in the format 'col1: val1, col2: val2, ...') +into a data frame with one row per plot and columns for each key. +} From b97b2acb3a0b4837ce6e53e283830feb8b13432d Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Mon, 15 Sep 2025 16:15:21 +0200 Subject: [PATCH 053/113] man: roxygenise --- NAMESPACE | 9 ----- man/PKNCA_update_data_object.Rd | 3 +- man/check_slope_rule_overlap.Rd | 4 +-- man/dot-apply_slope_rules.Rd | 25 ------------- man/filter_slopes.Rd | 29 --------------- man/lambda_slope_plot.Rd | 64 --------------------------------- 6 files changed, 3 insertions(+), 131 deletions(-) delete mode 100644 man/dot-apply_slope_rules.Rd delete mode 100644 man/filter_slopes.Rd delete mode 100644 man/lambda_slope_plot.Rd diff --git a/NAMESPACE b/NAMESPACE index f9d698556..fce7e58da 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -27,7 +27,6 @@ export(dose_profile_duplicates) export(export_cdisc) export(faceted_qc_plot) export(filter_breaks) -export(filter_slopes) export(flexible_violinboxplot) export(format_pkncaconc_data) export(format_pkncadata_intervals) @@ -44,7 +43,6 @@ export(get_label) export(interval_add_impute) export(interval_remove_impute) export(l_pkcl01) -export(lambda_slope_plot) export(multiple_matrix_ratios) export(parse_annotation) export(pivot_wider_pknca_results) @@ -67,7 +65,6 @@ importFrom(dplyr,"%>%") importFrom(dplyr,`%>%`) importFrom(dplyr,across) importFrom(dplyr,add_count) -importFrom(dplyr,all_of) importFrom(dplyr,any_of) importFrom(dplyr,arrange) importFrom(dplyr,bind_rows) @@ -96,16 +93,10 @@ importFrom(ggplot2,scale_x_continuous) importFrom(grid,convertUnit) importFrom(magrittr,`%>%`) importFrom(methods,is) -importFrom(plotly,add_trace) -importFrom(plotly,config) -importFrom(plotly,ggplotly) -importFrom(plotly,layout) importFrom(plotly,plot_ly) -importFrom(plotly,style) importFrom(purrr,map_chr) importFrom(purrr,pmap) importFrom(purrr,pmap_chr) -importFrom(rlang,set_names) importFrom(rlang,sym) importFrom(rlang,syms) importFrom(stats,as.formula) diff --git a/man/PKNCA_update_data_object.Rd b/man/PKNCA_update_data_object.Rd index 2d8a5c566..f1697d24d 100644 --- a/man/PKNCA_update_data_object.Rd +++ b/man/PKNCA_update_data_object.Rd @@ -12,7 +12,8 @@ PKNCA_update_data_object( selected_profile, selected_pcspec, params, - should_impute_c0 = TRUE + should_impute_c0 = TRUE, + hl_adj_rules = NULL ) } \arguments{ diff --git a/man/check_slope_rule_overlap.Rd b/man/check_slope_rule_overlap.Rd index c6a2d01d5..a6188071d 100644 --- a/man/check_slope_rule_overlap.Rd +++ b/man/check_slope_rule_overlap.Rd @@ -4,15 +4,13 @@ \alias{check_slope_rule_overlap} \title{Check overlap between existing and new slope rulesets} \usage{ -check_slope_rule_overlap(existing, new, slope_groups, .keep = FALSE) +check_slope_rule_overlap(existing, new, .keep = FALSE) } \arguments{ \item{existing}{Data frame with existing selections and exclusions.} \item{new}{Data frame with new rule to be added or removed.} -\item{slope_groups}{List with column names that define the groups.} - \item{.keep}{Whether to force keep fully overlapping rulesets. If FALSE, it will be assumed that the user wants to remove rule if new range already exists in the dataset. If TRUE, in that case full range will be kept.} diff --git a/man/dot-apply_slope_rules.Rd b/man/dot-apply_slope_rules.Rd deleted file mode 100644 index 78d862412..000000000 --- a/man/dot-apply_slope_rules.Rd +++ /dev/null @@ -1,25 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/utils-slope_selector.R -\name{.apply_slope_rules} -\alias{.apply_slope_rules} -\title{Apply Slope Rules to Update Data} -\usage{ -.apply_slope_rules(data, slopes, slope_groups) -} -\arguments{ -\item{data}{A list containing concentration data (\code{data$conc$data}) with columns that -need to be updated based on the slope rules.} - -\item{slopes}{A data frame containing slope rules, including \code{TYPE}, \code{RANGE}, -and \code{REASON} columns. May also have grouping columns (expected to match slope_groups)} - -\item{slope_groups}{A character vector specifying the group columns used for filtering.} -} -\value{ -description The modified \code{data} object with updated inclusion/exclusion flags -and reasons in \code{data$conc$data}. -} -\description{ -This function iterates over the given slopes and updates the \code{data$conc$data} object -by setting inclusion or exclusion flags based on the slope conditions. -} diff --git a/man/filter_slopes.Rd b/man/filter_slopes.Rd deleted file mode 100644 index 5475e0482..000000000 --- a/man/filter_slopes.Rd +++ /dev/null @@ -1,29 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/utils-slope_selector.R -\name{filter_slopes} -\alias{filter_slopes} -\title{Filter dataset based on slope selections and exclusions} -\usage{ -filter_slopes(data, slopes, profiles, slope_groups, check_reasons = FALSE) -} -\arguments{ -\item{data}{Data to filter. Must be \code{PKNCAdata} list, containing the \code{conc} element with -\code{PKNCAconc} list and appropriate data frame included under data.} - -\item{slopes}{A data frame containing slope rules, including \code{TYPE}, \code{RANGE}, -and \code{REASON} columns. May also have grouping columns (expected to match slope_groups)} - -\item{profiles}{List with available profiles for each \code{SUBJECT}.} - -\item{slope_groups}{List with column names that define the groups.} - -\item{check_reasons}{Whether to check if all selections have REASONS stated. If this is \code{TRUE} -and not all selections have a reason provided, an error will be thrown.} -} -\value{ -Original dataset, with \code{is.included.hl}, \code{is.excluded.hl} and \code{exclude_half.life} -columns modified in accordance to the provided slope filters. -} -\description{ -This function filters main dataset based on provided slope selections an exclusions. -} diff --git a/man/lambda_slope_plot.Rd b/man/lambda_slope_plot.Rd deleted file mode 100644 index ed3997d4b..000000000 --- a/man/lambda_slope_plot.Rd +++ /dev/null @@ -1,64 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/lambda_slope_plot.R -\name{lambda_slope_plot} -\alias{lambda_slope_plot} -\title{Generate a Lambda Slope Plot} -\usage{ -lambda_slope_plot( - conc_pknca_df, - row_values, - myres = myres, - r2adj_threshold = 0.7, - time_column = "AFRLT" -) -} -\arguments{ -\item{conc_pknca_df}{Data frame containing the concentration data -(default is \code{mydata$conc$data}).} - -\item{row_values}{A list containing the values for the column_names used for filtering.} - -\item{myres}{A PKNCAresults object containing the results of the NCA analysis} - -\item{r2adj_threshold}{Numeric value representing the R-squared adjusted threshold for -determining the subtitle color (default is 0.7).} - -\item{time_column}{The name of the time column in the concentration data frame. -(default is "AFRLT").} -} -\value{ -A plotly object representing the lambda slope plot. -} -\description{ -This function generates a lambda slope plot using pharmacokinetic data. It calculates relevant -lambda parameters and visualizes the data points used for lambda calculation, along with -a linear regression line and additional plot annotations. -} -\details{ -The function performs the following steps: -\itemize{ -\item{Creates duplicates of the pre-dose and last doses of concentration data.} -\item{Filters and arranges the input data to obtain relevant lambda calculation information.} -\item{Identifies the data points used for lambda calculation.} -\item{Calculates the fitness, intercept, and time span of the half-life estimate.} -\item{ -Determines the subtitle color based on the R-squared adjusted value and half-life estimate. -} -\item{ -Generates a ggplot object with the relevant data points, -linear regression line, and annotations. -} -\item{Converts the ggplot object to a plotly object for interactive visualization.} -} -} -\examples{ -\dontrun{ - # Example usage: - plot <- lambda_slope_plot(conc_pknca_df = mydata$conc$data, - row_values = list(USUBJID = "001", STUDYID = "A", DOSENO = 1), - myres = res_nca, - r2adj_threshold = 0.7) - plot -} - -} From 313511638d4706f34bd5cfc5dadd2714b8d7e330 Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Mon, 15 Sep 2025 16:37:39 +0200 Subject: [PATCH 054/113] fix: handle_interval_change when intervals do not have all group_vars --- R/utils-slope_selector.R | 4 ++++ tests/testthat/test-utils-slope_selector.R | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/R/utils-slope_selector.R b/R/utils-slope_selector.R index 2c20c87cb..40a8724ad 100644 --- a/R/utils-slope_selector.R +++ b/R/utils-slope_selector.R @@ -94,6 +94,8 @@ handle_interval_change <- function(new_pknca_data, old_pknca_data, plot_outputs) ) if (nrow(new_intervals) > 0) { affected_groups <- new_intervals %>% + select(any_of(c(group_vars(new_pknca_data), "start", "end"))) %>% + merge(unique(PKNCA::getGroups(new_pknca_data$conc)), all.x = TRUE) %>% select(any_of(c(group_vars(new_pknca_data), "start", "end"))) %>% distinct() plot_outputs <- .update_plots_with_pknca( @@ -104,6 +106,8 @@ handle_interval_change <- function(new_pknca_data, old_pknca_data, plot_outputs) } if (nrow(rm_intervals) > 0) { rm_plot_names <- rm_intervals %>% + select(any_of(c(group_vars(new_pknca_data), "start", "end"))) %>% + merge(unique(PKNCA::getGroups(new_pknca_data$conc)), all.x = TRUE) %>% select(any_of(c(group_vars(new_pknca_data), "start", "end"))) %>% distinct() %>% mutate(across(everything(), as.character)) %>% diff --git a/tests/testthat/test-utils-slope_selector.R b/tests/testthat/test-utils-slope_selector.R index 434b387af..789ddf7ed 100644 --- a/tests/testthat/test-utils-slope_selector.R +++ b/tests/testthat/test-utils-slope_selector.R @@ -232,6 +232,6 @@ describe("handle_interval_change", { expect_equal(length(old_plots), 13) expect_equal(length(new_plots), 2) - expect_equal(old_plots[2:3], new_plots) + expect_equal(old_plots[names(new_plots)], new_plots) }) }) From 20999aec0e3b17f3cdb7aa870910b4224719d4e2 Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Mon, 15 Sep 2025 17:36:19 +0200 Subject: [PATCH 055/113] fix: .update_pknca_with_rules when not all group columns are in input --- R/utils-slope_selector.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/utils-slope_selector.R b/R/utils-slope_selector.R index 40a8724ad..9020660bb 100644 --- a/R/utils-slope_selector.R +++ b/R/utils-slope_selector.R @@ -216,7 +216,7 @@ check_slope_rule_overlap <- function(existing, new, .keep = FALSE) { #' @param slopes Data frame of slope rules (TYPE, RANGE, REASON, group columns) #' @return Modified data object with updated flags .update_pknca_with_rules <- function(data, slopes) { - slope_groups <- group_vars(data) + slope_groups <- intersect(group_vars(data), names(slopes)) time_col <- data$conc$columns$time exclude_hl_col <- data$conc$columns$exclude_half.life include_hl_col <- data$conc$columns$include_half.life From 6c8e920fbc3c04d6aee73fc25c6991e69928e1ca Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Mon, 15 Sep 2025 17:36:45 +0200 Subject: [PATCH 056/113] test, draft: continue working in test-utils_slope_selector.R --- tests/testthat/test-utils-slope_selector.R | 159 ++++++++++++++++++++- 1 file changed, 156 insertions(+), 3 deletions(-) diff --git a/tests/testthat/test-utils-slope_selector.R b/tests/testthat/test-utils-slope_selector.R index 789ddf7ed..a8cc44dee 100644 --- a/tests/testthat/test-utils-slope_selector.R +++ b/tests/testthat/test-utils-slope_selector.R @@ -156,27 +156,35 @@ describe("detect_pknca_data_changes", { old <- FIXTURE_PKNCA_DATA - # No change + # No previous object new <- old - res <- detect_pknca_data_changes(old, new, "exclude_half.life", "include_half.life") + res <- detect_pknca_data_changes(NULL, new, "exclude_half.life", "include_half.life") + expect_true(res$in_data) + + # No change + res <- detect_pknca_data_changes(old, old, "exclude_half.life", "include_half.life") expect_false(res$in_data) expect_false(res$in_hl_adj) expect_false(res$in_selected_intervals) + # Change in data new2 <- new new2$conc$data$VAL[1] <- 99 res2 <- detect_pknca_data_changes(old, new2, "exclude_half.life", "include_half.life") expect_true(res2$in_data) + # Change in hl_adj new3 <- new new3$conc$data$exclude_half.life[2] <- TRUE res3 <- detect_pknca_data_changes(old, new3, "exclude_half.life", "include_half.life") expect_true(res3$in_hl_adj) + # Change in intervals new4 <- new new4$intervals <- data.frame(ID = 1:4) res4 <- detect_pknca_data_changes(old, new4, "exclude_half.life", "include_half.life") expect_true(res4$in_selected_intervals) + }) }) @@ -222,7 +230,7 @@ describe("handle_hl_adj_change", { }) describe("handle_interval_change", { - it("updates and removes plots based on interval changes", { + it("removes plots when intervals are removed", { new_pknca_data <- FIXTURE_PKNCA_DATA new_pknca_data$intervals <- new_pknca_data$intervals[2:3, ] old_pknca_data <- FIXTURE_PKNCA_DATA @@ -234,4 +242,149 @@ describe("handle_interval_change", { expect_equal(length(new_plots), 2) expect_equal(old_plots[names(new_plots)], new_plots) }) + + it("adds plots when intervals are added", { + old_pknca_data <- FIXTURE_PKNCA_DATA + old_pknca_data$intervals <- old_pknca_data$intervals[2:3, ] + new_pknca_data <- FIXTURE_PKNCA_DATA + + old_plots <- get_halflife_plots(old_pknca_data)$plots + new_plots <- handle_interval_change(new_pknca_data, old_pknca_data, old_plots) + + expect_equal(length(old_plots), 2) + expect_equal(length(new_plots), 13) + expect_equal(new_plots[names(old_plots)], old_plots) + }) + +}) + +describe("arrange_plots_by_groups", { + it("orders plots by specified group columns", { + named_list <- list( + "B: 2, A: 1" = "plot1", + "B: 1, A: 2" = "plot2", + "B: 1, A: 1" = "plot3" + ) + # Should order by B then A + ordered <- arrange_plots_by_groups(named_list, c("B", "A")) + expect_equal(names(ordered), c("B: 1, A: 1", "B: 1, A: 2", "B: 2, A: 1")) + # Should order by A then B + ordered2 <- arrange_plots_by_groups(named_list, c("A", "B")) + expect_equal(names(ordered2), c("B: 2, A: 1", "B: 1, A: 1", "B: 1, A: 2")) + }) +}) + +describe(".update_pknca_with_rules", { + it("applies selection and exclusion rules to data", { + # Minimal PKNCA data object + data <- list( + conc = list( + data = data.frame(ID = 1:5, TIME = 1:5, exclude_half.life = FALSE, include_half.life = FALSE, REASON = ""), + columns = list(time = "TIME", exclude_half.life = "exclude_half.life", include_half.life = "include_half.life") + ) + ) + attr(data, "groups") <- c("ID") + group_vars <- function(x) "ID" + assignInNamespace("group_vars", group_vars, ns = "globalenv") + # Selection rule + slopes <- data.frame(TYPE = "Selection", ID = 1, RANGE = "2:4", REASON = "sel") + data2 <- .update_pknca_with_rules(data, slopes) + expect_true(all(data2$conc$data$include_half.life[2:4])) + # Exclusion rule + slopes2 <- data.frame(TYPE = "Exclusion", ID = 1, RANGE = "3:5", REASON = "exc") + data3 <- .update_pknca_with_rules(data2, slopes2) + expect_true(all(data3$conc$data$exclude_half.life[3:5])) + # REASON is appended + expect_true(grepl("sel", data3$conc$data$REASON[2])) + expect_true(grepl("exc", data3$conc$data$REASON[3])) + }) +}) + +describe(".update_plots_with_pknca", { + it("updates plots for affected intervals", { + # Minimal PKNCA data object + data <- list( + conc = list( + data = data.frame(ID = 1:3, TIME = 1:3), + columns = list() + ), + intervals = data.frame(ID = 1:3, start = 1:3, end = 2:4) + ) + group_vars <- function(x) "ID" + assignInNamespace("group_vars", group_vars, ns = "globalenv") + get_halflife_plots <- function(p) list(plots = list("ID: 1, start: 1, end: 2" = "plot1", "ID: 2, start: 2, end: 3" = "plot2", "ID: 3, start: 3, end: 4" = "plot3")) + assignInNamespace("get_halflife_plots", get_halflife_plots, ns = "globalenv") + plot_outputs <- list("ID: 1, start: 1, end: 2" = "oldplot1", "ID: 2, start: 2, end: 3" = "oldplot2") + intervals_to_update <- data.frame(ID = 2:3, start = 2:3, end = 3:4) + updated <- .update_plots_with_pknca(data, plot_outputs, intervals_to_update) + expect_equal(updated[["ID: 2, start: 2, end: 3"]], "plot2") + expect_equal(updated[["ID: 3, start: 3, end: 4"]], "plot3") + expect_equal(updated[["ID: 1, start: 1, end: 2"]], "oldplot1") + }) +}) + +describe("arrange_plots_by_groups", { + it("orders plots by specified group columns", { + named_list <- list( + "B: 2, A: 1" = "plot1", + "B: 1, A: 2" = "plot2", + "B: 1, A: 1" = "plot3" + ) + # Should order by B then A + ordered <- arrange_plots_by_groups(named_list, c("B", "A")) + expect_equal(names(ordered), c("B: 1, A: 1", "B: 1, A: 2", "B: 2, A: 1")) + # Should order by A then B + ordered2 <- arrange_plots_by_groups(named_list, c("A", "B")) + expect_equal(names(ordered2), c("B: 1, A: 1", "B: 2, A: 1", "B: 1, A: 2")) + }) +}) + +describe(".update_pknca_with_rules", { + it("applies selection and exclusion rules to data", { + # Minimal PKNCA data object + old <- FIXTURE_PKNCA_DATA + group1 <- data$intervals %>% select(any_of(c(group_vars(old), "start", "end"))) %>% .[1,] + + # Selection rule + slopes_incl <- cbind( + data.frame(TYPE = "Selection", ID = 1, RANGE = "2:4", REASON = "because I want to test it :)"), + group1 + ) + slopes_excl <- cbind( + data.frame(TYPE = "Exclusion", ID = 1, RANGE = "2:4", REASON = "there are always good reasons"), + group1 + ) + new_with_incl <- .update_pknca_with_rules(old, slopes_incl) + new_with_excl <- .update_pknca_with_rules(old, slopes_excl) + + old_have_points_na <- all(is.na(old$conc$data %>% + filter(USUBJID == group1$USUBJID, AFRLT >= 2, AFRLT <= 4) %>% + pull(include_half.life)) + ) + new_have_points_incl <- all(new_with_incl$conc$data %>% + filter(USUBJID == group1$USUBJID, AFRLT >= 2, AFRLT <= 4) %>% + pull(include_half.life)) + + new_have_points_excl <- all(new_with_excl$conc$data %>% + filter(USUBJID == group1$USUBJID, AFRLT >= 2, AFRLT <= 4) %>% + pull(exclude_half.life)) + + expect_true(all(old_have_points_na, new_have_points_incl, new_have_points_excl)) + }) +}) + +describe(".update_plots_with_pknca", { + it("updates plots for affected intervals", { + data <- FIXTURE_PKNCA_DATA + # group_vars <- function(x) "ID" + # assignInNamespace("group_vars", group_vars, ns = "globalenv") + # get_halflife_plots <- function(p) list(plots = list("ID: 1, start: 1, end: 2" = "plot1", "ID: 2, start: 2, end: 3" = "plot2", "ID: 3, start: 3, end: 4" = "plot3")) + # assignInNamespace("get_halflife_plots", get_halflife_plots, ns = "globalenv") + # plot_outputs <- list("ID: 1, start: 1, end: 2" = "oldplot1", "ID: 2, start: 2, end: 3" = "oldplot2") + # intervals_to_update <- data.frame(ID = 2:3, start = 2:3, end = 3:4) + # updated <- .update_plots_with_pknca(data, plot_outputs, intervals_to_update) + # expect_equal(updated[["ID: 2, start: 2, end: 3"]], "plot2") + # expect_equal(updated[["ID: 3, start: 3, end: 4"]], "plot3") + # expect_equal(updated[["ID: 1, start: 1, end: 2"]], "oldplot1") + }) }) From 6ab37f27797633fb59f841a192f3b53165e527e0 Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Mon, 15 Sep 2025 17:47:20 +0200 Subject: [PATCH 057/113] fix: typpo get_halflife_plots --- R/get_halflife_plots.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/get_halflife_plots.R b/R/get_halflife_plots.R index 0a9b2cbfd..a1675ae7d 100644 --- a/R/get_halflife_plots.R +++ b/R/get_halflife_plots.R @@ -89,7 +89,7 @@ get_halflife_plots_single <- function( customdata = apply( plot_data[, c(group_vars, "ROWID"), drop = FALSE], 1, - function(row) as.list(set_names(row, c(group_vars, "ROWID"))) + function(row) as.list(setNames(row, c(group_vars, "ROWID"))) ) ) %>% plotly::plotly_build() From 38f5dbf9980627003b4588656de2a9b25c7f3c19 Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Mon, 15 Sep 2025 17:47:45 +0200 Subject: [PATCH 058/113] rm duplicated test & make new one for exp error --- tests/testthat/test-utils-slope_selector.R | 80 +++------------------- 1 file changed, 11 insertions(+), 69 deletions(-) diff --git a/tests/testthat/test-utils-slope_selector.R b/tests/testthat/test-utils-slope_selector.R index a8cc44dee..f37e8c109 100644 --- a/tests/testthat/test-utils-slope_selector.R +++ b/tests/testthat/test-utils-slope_selector.R @@ -258,71 +258,6 @@ describe("handle_interval_change", { }) -describe("arrange_plots_by_groups", { - it("orders plots by specified group columns", { - named_list <- list( - "B: 2, A: 1" = "plot1", - "B: 1, A: 2" = "plot2", - "B: 1, A: 1" = "plot3" - ) - # Should order by B then A - ordered <- arrange_plots_by_groups(named_list, c("B", "A")) - expect_equal(names(ordered), c("B: 1, A: 1", "B: 1, A: 2", "B: 2, A: 1")) - # Should order by A then B - ordered2 <- arrange_plots_by_groups(named_list, c("A", "B")) - expect_equal(names(ordered2), c("B: 2, A: 1", "B: 1, A: 1", "B: 1, A: 2")) - }) -}) - -describe(".update_pknca_with_rules", { - it("applies selection and exclusion rules to data", { - # Minimal PKNCA data object - data <- list( - conc = list( - data = data.frame(ID = 1:5, TIME = 1:5, exclude_half.life = FALSE, include_half.life = FALSE, REASON = ""), - columns = list(time = "TIME", exclude_half.life = "exclude_half.life", include_half.life = "include_half.life") - ) - ) - attr(data, "groups") <- c("ID") - group_vars <- function(x) "ID" - assignInNamespace("group_vars", group_vars, ns = "globalenv") - # Selection rule - slopes <- data.frame(TYPE = "Selection", ID = 1, RANGE = "2:4", REASON = "sel") - data2 <- .update_pknca_with_rules(data, slopes) - expect_true(all(data2$conc$data$include_half.life[2:4])) - # Exclusion rule - slopes2 <- data.frame(TYPE = "Exclusion", ID = 1, RANGE = "3:5", REASON = "exc") - data3 <- .update_pknca_with_rules(data2, slopes2) - expect_true(all(data3$conc$data$exclude_half.life[3:5])) - # REASON is appended - expect_true(grepl("sel", data3$conc$data$REASON[2])) - expect_true(grepl("exc", data3$conc$data$REASON[3])) - }) -}) - -describe(".update_plots_with_pknca", { - it("updates plots for affected intervals", { - # Minimal PKNCA data object - data <- list( - conc = list( - data = data.frame(ID = 1:3, TIME = 1:3), - columns = list() - ), - intervals = data.frame(ID = 1:3, start = 1:3, end = 2:4) - ) - group_vars <- function(x) "ID" - assignInNamespace("group_vars", group_vars, ns = "globalenv") - get_halflife_plots <- function(p) list(plots = list("ID: 1, start: 1, end: 2" = "plot1", "ID: 2, start: 2, end: 3" = "plot2", "ID: 3, start: 3, end: 4" = "plot3")) - assignInNamespace("get_halflife_plots", get_halflife_plots, ns = "globalenv") - plot_outputs <- list("ID: 1, start: 1, end: 2" = "oldplot1", "ID: 2, start: 2, end: 3" = "oldplot2") - intervals_to_update <- data.frame(ID = 2:3, start = 2:3, end = 3:4) - updated <- .update_plots_with_pknca(data, plot_outputs, intervals_to_update) - expect_equal(updated[["ID: 2, start: 2, end: 3"]], "plot2") - expect_equal(updated[["ID: 3, start: 3, end: 4"]], "plot3") - expect_equal(updated[["ID: 1, start: 1, end: 2"]], "oldplot1") - }) -}) - describe("arrange_plots_by_groups", { it("orders plots by specified group columns", { named_list <- list( @@ -340,11 +275,10 @@ describe("arrange_plots_by_groups", { }) describe(".update_pknca_with_rules", { - it("applies selection and exclusion rules to data", { - # Minimal PKNCA data object - old <- FIXTURE_PKNCA_DATA - group1 <- data$intervals %>% select(any_of(c(group_vars(old), "start", "end"))) %>% .[1,] + old <- FIXTURE_PKNCA_DATA + group1 <- old$intervals %>% select(any_of(c(group_vars(old), "start", "end"))) %>% .[1,] + it("applies selection and exclusion rules to data", { # Selection rule slopes_incl <- cbind( data.frame(TYPE = "Selection", ID = 1, RANGE = "2:4", REASON = "because I want to test it :)"), @@ -371,6 +305,14 @@ describe(".update_pknca_with_rules", { expect_true(all(old_have_points_na, new_have_points_incl, new_have_points_excl)) }) + it("returns an error for invalid rule types", { + # Invalid rule will throw an error + slopes_invalid <- cbind( + data.frame(TYPE = "Invalid", ID = 1, RANGE = "2:4", REASON = "invalid type"), + group1 + ) + expect_error(.update_pknca_with_rules(old, slopes_invalid)) + }) }) describe(".update_plots_with_pknca", { From 2066d87832b4cd468e26f8ea76e5a7cc15712b61 Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Tue, 16 Sep 2025 06:44:31 +0200 Subject: [PATCH 059/113] rm unused fun in utils-slope_selector (update_plots_with_rules) --- R/utils-slope_selector.R | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/R/utils-slope_selector.R b/R/utils-slope_selector.R index 9020660bb..45757f2d6 100644 --- a/R/utils-slope_selector.R +++ b/R/utils-slope_selector.R @@ -247,27 +247,6 @@ check_slope_rule_overlap <- function(existing, new, .keep = FALSE) { data } -#' Update plots with manual slope rules -#' -#' Updates the plot_outputs list by applying manual slopes. -#' @param pknca_data PKNCA data object -#' @param manual_slopes Data frame of manual slope rules -#' @param plot_outputs Named list of current plot outputs -#' @param groups_to_update Optional: data frame of intervals to update (default: all) -#' @return Updated plot_outputs (named list) -.update_plots_with_rules <- function( - pknca_data, manual_slopes, plot_outputs, groups_to_update = NULL -) { - pknca_for_plots <- .update_pknca_with_rules(pknca_data, manual_slopes) - if (is.null(groups_to_update)) { - groups_to_update <- manual_slopes %>% - select(any_of(c(group_vars(pknca_for_plots), "NCA_PROFILE"))) %>% - distinct() - } - if (nrow(groups_to_update) == 0) return(plot_outputs) - .update_plots_with_pknca(pknca_for_plots, plot_outputs, intervals_to_update = groups_to_update) -} - #' Update plots with PKNCA data (for affected intervals) #' #' Regenerates plots for the specified intervals in the plot_outputs list. From 7989d78365b74e8f7ab10be141717316a8c508df Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Tue, 16 Sep 2025 06:46:33 +0200 Subject: [PATCH 060/113] feat: include slope plots in zip folder --- inst/shiny/modules/tab_nca/setup/slope_selector.R | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/inst/shiny/modules/tab_nca/setup/slope_selector.R b/inst/shiny/modules/tab_nca/setup/slope_selector.R index 9f7ddff9d..803ed2259 100644 --- a/inst/shiny/modules/tab_nca/setup/slope_selector.R +++ b/inst/shiny/modules/tab_nca/setup/slope_selector.R @@ -160,8 +160,10 @@ slope_selector_server <- function( # nolint items = group_vars(new_pknca_data) ) } + # Save the plots for the zip download (nca_results.R) + session$userData$results$slope_selector <- plot_outputs() - # Update the object + # Update the object for future comparisons pknca_data(new_pknca_data) }) From 6881e54a069bbd6078016f62f7725ecd11b955e9 Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Tue, 16 Sep 2025 06:46:57 +0200 Subject: [PATCH 061/113] refactor: tests utils-slope_selector logic --- tests/testthat/test-utils-slope_selector.R | 244 +++++++++------------ 1 file changed, 107 insertions(+), 137 deletions(-) diff --git a/tests/testthat/test-utils-slope_selector.R b/tests/testthat/test-utils-slope_selector.R index f37e8c109..7bb1fe0ef 100644 --- a/tests/testthat/test-utils-slope_selector.R +++ b/tests/testthat/test-utils-slope_selector.R @@ -1,31 +1,3 @@ -DATA_FIXTURE <- list( - conc = list( - data = data.frame( - STUDYID = 1, - PCSPEC = 1, - USUBJID = rep(1:4, each = 4), - NCA_PROFILE = 1, - IX = rep(1:4, times = 4), - PARAM = rep("A", 16), - is.included.hl = FALSE, - is.excluded.hl = FALSE, - exclude_half.life = FALSE, - REASON = "" - ) - ) -) - -DOSNOS_FIXTURE <- data.frame( - USUBJID = rep(1:4, each = 1), - PARAM = rep("A", 4), - PCSPEC = rep(1, 4), - NCA_PROFILE = rep(1, 4) -) - -slope_groups <- c("USUBJID", "PARAM", "PCSPEC", "NCA_PROFILE") - - - EXISTING_FIXTURE <- data.frame( TYPE = "Exclusion", USUBJID = 1, @@ -46,9 +18,9 @@ describe("check_slope_rule_overlap", { PCSPEC = 1, RANGE = "1:3" ) - + expect_equal(nrow(check_slope_rule_overlap(EXISTING_FIXTURE, NEW)), 2) - + # different USUBJID # NEW <- data.frame( TYPE = "Exclusion", @@ -58,9 +30,9 @@ describe("check_slope_rule_overlap", { PCSPEC = 1, RANGE = "1:3" ) - + expect_equal(nrow(check_slope_rule_overlap(EXISTING_FIXTURE, NEW)), 2) - + # different NCA_PROFILE # NEW <- data.frame( TYPE = "Exclusion", @@ -70,11 +42,11 @@ describe("check_slope_rule_overlap", { PCSPEC = 1, RANGE = "1:3" ) - + expect_equal(nrow(check_slope_rule_overlap(EXISTING_FIXTURE, NEW)), 2) - + }) - + it("should remove overlapping points if no new points are detected", { NEW <- data.frame( TYPE = "Exclusion", @@ -84,9 +56,9 @@ describe("check_slope_rule_overlap", { PCSPEC = 1, RANGE = "4:5" ) - + expect_equal(check_slope_rule_overlap(EXISTING_FIXTURE, NEW)$RANGE, "3,6") - + NEW <- data.frame( TYPE = "Exclusion", USUBJID = 1, @@ -95,11 +67,11 @@ describe("check_slope_rule_overlap", { PCSPEC = 1, RANGE = "3:4" ) - + expect_equal(check_slope_rule_overlap(EXISTING_FIXTURE, NEW)$RANGE, "5:6") - + }) - + it("should add new points of partial overlap is detected", { NEW <- data.frame( TYPE = "Exclusion", @@ -109,11 +81,11 @@ describe("check_slope_rule_overlap", { PCSPEC = 1, RANGE = "4:9" ) - + expect_equal(check_slope_rule_overlap(EXISTING_FIXTURE, NEW)$RANGE, "3:9") - + }) - + it("should remove full row if full range of rule is removed", { NEW <- data.frame( TYPE = "Exclusion", @@ -123,11 +95,11 @@ describe("check_slope_rule_overlap", { PCSPEC = 1, RANGE = "3:6" ) - + expect_equal(nrow(check_slope_rule_overlap(EXISTING_FIXTURE, NEW)), 0) - + }) - + it("should warn if more than one range for single subject, profile and rule type is detected", { EXISTING <- data.frame( TYPE = "Exclusion", @@ -137,12 +109,12 @@ describe("check_slope_rule_overlap", { PCSPEC = 1, RANGE = "3:6" ) - + DUPLICATE <- EXISTING %>% mutate( RANGE = "4:7" ) - + expect_warning( check_slope_rule_overlap(rbind(EXISTING, DUPLICATE), DUPLICATE), "More than one range for single subject, profile and rule type detected." @@ -151,49 +123,50 @@ describe("check_slope_rule_overlap", { }) describe("detect_pknca_data_changes", { - it("detects changes in data, hl_adj, and intervals", { - # Setup minimal old and new objects - - old <- FIXTURE_PKNCA_DATA - - # No previous object - new <- old - res <- detect_pknca_data_changes(NULL, new, "exclude_half.life", "include_half.life") + + old_data <- FIXTURE_PKNCA_DATA + + it("detects change in data when there is no previous object", { + new_data <- old_data + res <- detect_pknca_data_changes(NULL, new_data, "exclude_half.life", "include_half.life") expect_true(res$in_data) - - # No change - res <- detect_pknca_data_changes(old, old, "exclude_half.life", "include_half.life") + }) + + it("detects no change when old and new are identical", { + res <- detect_pknca_data_changes(old_data, old_data, "exclude_half.life", "include_half.life") expect_false(res$in_data) expect_false(res$in_hl_adj) expect_false(res$in_selected_intervals) - - # Change in data - new2 <- new - new2$conc$data$VAL[1] <- 99 - res2 <- detect_pknca_data_changes(old, new2, "exclude_half.life", "include_half.life") - expect_true(res2$in_data) - - # Change in hl_adj - new3 <- new - new3$conc$data$exclude_half.life[2] <- TRUE - res3 <- detect_pknca_data_changes(old, new3, "exclude_half.life", "include_half.life") - expect_true(res3$in_hl_adj) - - # Change in intervals - new4 <- new - new4$intervals <- data.frame(ID = 1:4) - res4 <- detect_pknca_data_changes(old, new4, "exclude_half.life", "include_half.life") - expect_true(res4$in_selected_intervals) - + }) + + it("detects change in data", { + changed_data <- old_data + changed_data$conc$data$VAL[1] <- 99 + res <- detect_pknca_data_changes(old_data, changed_data, "exclude_half.life", "include_half.life") + expect_true(res$in_data) + }) + + it("detects change in hl_adj", { + changed_hl <- old_data + changed_hl$conc$data$exclude_half.life[2] <- TRUE + res <- detect_pknca_data_changes(old_data, changed_hl, "exclude_half.life", "include_half.life") + expect_true(res$in_hl_adj) + }) + + it("detects change in intervals", { + changed_intervals <- old_data + changed_intervals$intervals <- data.frame(ID = 1:4) + res <- detect_pknca_data_changes(old_data, changed_intervals, "exclude_half.life", "include_half.life") + expect_true(res$in_selected_intervals) }) }) describe("handle_hl_adj_change", { it("updates only affected groups in plot_outputs", { # Setup dummy PKNCA data objects and plot_outputs - old_pknca_data <- FIXTURE_PKNCA_DATA - new_pknca_data <- old_pknca_data - new_pknca_data$conc$data <- new_pknca_data$conc$data %>% + old_data <- FIXTURE_PKNCA_DATA + new_data <- old_data + new_data$conc$data <- new_data$conc$data %>% mutate( exclude_half.life = ifelse( USUBJID == unique(USUBJID)[3] & AFRLT == 4.5, @@ -201,61 +174,58 @@ describe("handle_hl_adj_change", { exclude_half.life ) ) - - old_plots <- get_halflife_plots(old_pknca_data)$plots - - # Patch .update_plots_with_pknca to just return a marker - new_plots <- handle_hl_adj_change(new_pknca_data, old_pknca_data, old_plots) + # Create plots using the original and updated PKNCA data + old_plots <- get_halflife_plots(old_data)$plots + new_plots <- handle_hl_adj_change(new_data, old_data, old_plots) + + # Check that the plots for other groups remain unchanged ix_unchanged_plots <- setdiff(seq_len(length(new_plots)), 4) expect_equal(new_plots[ix_unchanged_plots], old_plots[ix_unchanged_plots]) + # Define the expected differences in the original and updated plots old_plots_exp_details <- list( color = c("red", "red", "green", "green", "green"), size = 15, symbol = c("circle", "circle", "circle", "circle", "circle"), line = list(color = "rgba(255,127,14,1)") ) - new_plots_exp_details <- list( color = c("red", "green", "green", "red", "green"), size = 15, symbol = c("circle", "circle", "circle", "x", "circle"), line = list(color = "rgba(255,127,14,1)") ) - + # Check that the affected plot has been updated correctly expect_equal(old_plots[[4]]$x$data[[2]]$marker, old_plots_exp_details, ignore_attr = TRUE) expect_equal(new_plots[[4]]$x$data[[2]]$marker, new_plots_exp_details, ignore_attr = TRUE) }) }) describe("handle_interval_change", { - it("removes plots when intervals are removed", { - new_pknca_data <- FIXTURE_PKNCA_DATA - new_pknca_data$intervals <- new_pknca_data$intervals[2:3, ] - old_pknca_data <- FIXTURE_PKNCA_DATA - - old_plots <- get_halflife_plots(old_pknca_data)$plots - new_plots <- handle_interval_change(new_pknca_data, old_pknca_data, old_plots) + old_data <- FIXTURE_PKNCA_DATA + old_data$intervals <- old_data$intervals[1:3, ] + old_plots <- get_halflife_plots(old_data)$plots - expect_equal(length(old_plots), 13) - expect_equal(length(new_plots), 2) + it("removes plots when intervals are removed", { + new_data <- FIXTURE_PKNCA_DATA + new_data$intervals <- new_data$intervals[1, ] + new_plots <- handle_interval_change(new_data, old_data, old_plots) + + expect_equal(length(old_plots), 3) + expect_equal(length(new_plots), 1) expect_equal(old_plots[names(new_plots)], new_plots) }) - + it("adds plots when intervals are added", { - old_pknca_data <- FIXTURE_PKNCA_DATA - old_pknca_data$intervals <- old_pknca_data$intervals[2:3, ] - new_pknca_data <- FIXTURE_PKNCA_DATA - - old_plots <- get_halflife_plots(old_pknca_data)$plots - new_plots <- handle_interval_change(new_pknca_data, old_pknca_data, old_plots) - - expect_equal(length(old_plots), 2) - expect_equal(length(new_plots), 13) + new_data <- FIXTURE_PKNCA_DATA + new_data$intervals <- new_data$intervals[1:4, ] + new_plots <- handle_interval_change(new_data, old_data, old_plots) + + expect_equal(length(old_plots), 3) + expect_equal(length(new_plots), 4) expect_equal(new_plots[names(old_plots)], old_plots) }) - }) describe("arrange_plots_by_groups", { @@ -265,21 +235,18 @@ describe("arrange_plots_by_groups", { "B: 1, A: 2" = "plot2", "B: 1, A: 1" = "plot3" ) - # Should order by B then A ordered <- arrange_plots_by_groups(named_list, c("B", "A")) expect_equal(names(ordered), c("B: 1, A: 1", "B: 1, A: 2", "B: 2, A: 1")) - # Should order by A then B ordered2 <- arrange_plots_by_groups(named_list, c("A", "B")) expect_equal(names(ordered2), c("B: 1, A: 1", "B: 2, A: 1", "B: 1, A: 2")) }) }) describe(".update_pknca_with_rules", { - old <- FIXTURE_PKNCA_DATA - group1 <- old$intervals %>% select(any_of(c(group_vars(old), "start", "end"))) %>% .[1,] + old_data <- FIXTURE_PKNCA_DATA + group1 <- old_data$intervals %>% select(any_of(c(group_vars(old_data), "start", "end"))) %>% .[1,] it("applies selection and exclusion rules to data", { - # Selection rule slopes_incl <- cbind( data.frame(TYPE = "Selection", ID = 1, RANGE = "2:4", REASON = "because I want to test it :)"), group1 @@ -288,25 +255,25 @@ describe(".update_pknca_with_rules", { data.frame(TYPE = "Exclusion", ID = 1, RANGE = "2:4", REASON = "there are always good reasons"), group1 ) - new_with_incl <- .update_pknca_with_rules(old, slopes_incl) - new_with_excl <- .update_pknca_with_rules(old, slopes_excl) - - old_have_points_na <- all(is.na(old$conc$data %>% + + new_with_incl <- .update_pknca_with_rules(old_data, slopes_incl) + new_with_excl <- .update_pknca_with_rules(old_data, slopes_excl) + + old_have_points_na <- all(is.na(old_data$conc$data %>% filter(USUBJID == group1$USUBJID, AFRLT >= 2, AFRLT <= 4) %>% pull(include_half.life)) - ) + ) new_have_points_incl <- all(new_with_incl$conc$data %>% - filter(USUBJID == group1$USUBJID, AFRLT >= 2, AFRLT <= 4) %>% - pull(include_half.life)) - + filter(USUBJID == group1$USUBJID, AFRLT >= 2, AFRLT <= 4) %>% + pull(include_half.life)) + new_have_points_excl <- all(new_with_excl$conc$data %>% - filter(USUBJID == group1$USUBJID, AFRLT >= 2, AFRLT <= 4) %>% - pull(exclude_half.life)) - + filter(USUBJID == group1$USUBJID, AFRLT >= 2, AFRLT <= 4) %>% + pull(exclude_half.life)) + expect_true(all(old_have_points_na, new_have_points_incl, new_have_points_excl)) }) it("returns an error for invalid rule types", { - # Invalid rule will throw an error slopes_invalid <- cbind( data.frame(TYPE = "Invalid", ID = 1, RANGE = "2:4", REASON = "invalid type"), group1 @@ -316,17 +283,20 @@ describe(".update_pknca_with_rules", { }) describe(".update_plots_with_pknca", { - it("updates plots for affected intervals", { - data <- FIXTURE_PKNCA_DATA - # group_vars <- function(x) "ID" - # assignInNamespace("group_vars", group_vars, ns = "globalenv") - # get_halflife_plots <- function(p) list(plots = list("ID: 1, start: 1, end: 2" = "plot1", "ID: 2, start: 2, end: 3" = "plot2", "ID: 3, start: 3, end: 4" = "plot3")) - # assignInNamespace("get_halflife_plots", get_halflife_plots, ns = "globalenv") - # plot_outputs <- list("ID: 1, start: 1, end: 2" = "oldplot1", "ID: 2, start: 2, end: 3" = "oldplot2") - # intervals_to_update <- data.frame(ID = 2:3, start = 2:3, end = 3:4) - # updated <- .update_plots_with_pknca(data, plot_outputs, intervals_to_update) - # expect_equal(updated[["ID: 2, start: 2, end: 3"]], "plot2") - # expect_equal(updated[["ID: 3, start: 3, end: 4"]], "plot3") - # expect_equal(updated[["ID: 1, start: 1, end: 2"]], "oldplot1") + old_data <- FIXTURE_PKNCA_DATA + old_data$intervals <- old_data$intervals[1:2, ] + old_plots <- get_halflife_plots(old_data)$plots + + new_data <- old_data + new_data$conc$data$exclude_half.life <- TRUE + + it("updates all plots when no intervals are specified", { + updated_plots <- .update_plots_with_pknca(new_data, old_plots, NULL) + expect_equal(updated_plots[[1]]$x$data[[2]]$marker$symbol, rep("x", 5), ignore_attr = TRUE) + expect_equal(old_plots[[2]]$x$data[[2]]$marker$symbol, rep("circle", 5), ignore_attr = TRUE) + }) + it("does not update any plot when the specified intervals are an empty dataframe", { + updated_plots <- .update_plots_with_pknca(new_data, old_plots, data.frame()) + expect_equal(updated_plots, old_plots) }) }) From ae64689f1f82deb08fd2a774fc71b6d40b098eba Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Tue, 16 Sep 2025 07:48:21 +0200 Subject: [PATCH 062/113] refactor: tests in get_halflife_plots --- tests/testthat/test-get_halflife_plots.R | 59 ++++++++++++------------ 1 file changed, 30 insertions(+), 29 deletions(-) diff --git a/tests/testthat/test-get_halflife_plots.R b/tests/testthat/test-get_halflife_plots.R index ec557bf09..8c5fd0393 100644 --- a/tests/testthat/test-get_halflife_plots.R +++ b/tests/testthat/test-get_halflife_plots.R @@ -1,8 +1,9 @@ -describe("get_halflife_plot", { +# Shared fixture for all tests +base_pknca <- FIXTURE_PKNCA_DATA +describe("get_halflife_plot", { it("returns a list of plotly objects with valid input", { - pknca_data <- FIXTURE_PKNCA_DATA - plots <- get_halflife_plots(pknca_data)[["plots"]] + plots <- get_halflife_plots(base_pknca)[["plots"]] expect_type(plots, "list") expect_true(length(plots) >= 1) expect_s3_class(plots[[1]], "plotly") @@ -10,18 +11,18 @@ describe("get_halflife_plot", { }) it("warns and returns empty list when no groups present", { - pknca_data <- FIXTURE_PKNCA_DATA - pknca_data$conc$data <- pknca_data$conc$data[0, ] - plots <- get_halflife_plots(pknca_data)[["plots"]] + pknca_no_groups <- base_pknca + pknca_no_groups$conc$data <- pknca_no_groups$conc$data[0, ] + plots <- get_halflife_plots(pknca_no_groups)[["plots"]] expect_type(plots, "list") expect_length(plots, 0) }) it("marker colors and shapes - no exclusion/inclusion", { - pknca_data <- FIXTURE_PKNCA_DATA - pknca_data$conc$data$exclude_half.life <- FALSE - pknca_data$conc$data$include_half.life <- FALSE - plots <- get_halflife_plots(pknca_data)[["plots"]] + pknca_no_excl_incl <- base_pknca + pknca_no_excl_incl$conc$data$exclude_half.life <- FALSE + pknca_no_excl_incl$conc$data$include_half.life <- FALSE + plots <- get_halflife_plots(pknca_no_excl_incl)[["plots"]] expect_true(length(plots) >= 1) plot_data <- plots[[1]]$x$data[[2]] expect_true(all(plot_data$marker$color == "black")) @@ -29,10 +30,10 @@ describe("get_halflife_plot", { }) it("marker colors and shapes - exclusion of a lambda.z point", { - pknca_data <- FIXTURE_PKNCA_DATA - pknca_data$intervals <- pknca_data$intervals[2,] - pknca_data_with_excl <- pknca_data - pknca_data_with_excl$conc$data <- pknca_data$conc$data %>% + pknca_excl <- base_pknca + pknca_excl$intervals <- pknca_excl$intervals[2,] + pknca_excl_with_excl <- pknca_excl + pknca_excl_with_excl$conc$data <- pknca_excl$conc$data %>% mutate( exclude_half.life = ifelse( USUBJID == unique(USUBJID)[2] & AFRLT == 2.5, @@ -40,8 +41,8 @@ describe("get_halflife_plot", { FALSE ) ) - plots <- get_halflife_plots(pknca_data)[["plots"]] - plots_with_excl <- get_halflife_plots(pknca_data_with_excl)[["plots"]] + plots <- get_halflife_plots(pknca_excl)[["plots"]] + plots_with_excl <- get_halflife_plots(pknca_excl_with_excl)[["plots"]] plots_details <- plots[[1]]$x$data[[2]]$marker expected_plots_details <- list( @@ -63,12 +64,12 @@ describe("get_halflife_plot", { }) it("marker colors and shapes - inclusion of lambda.z points", { - pknca_data <- FIXTURE_PKNCA_DATA - pknca_data$intervals <- pknca_data$intervals[3,] - pknca_data$conc$data$exclude_half.life <- FALSE - pknca_data$conc$data$include_half.life <- NA - pknca_data_with_incl <- pknca_data - pknca_data_with_incl$conc$data <- pknca_data$conc$data %>% + pknca_incl <- base_pknca + pknca_incl$intervals <- pknca_incl$intervals[3,] + pknca_incl$conc$data$exclude_half.life <- FALSE + pknca_incl$conc$data$include_half.life <- NA + pknca_incl_with_incl <- pknca_incl + pknca_incl_with_incl$conc$data <- pknca_incl$conc$data %>% mutate( include_half.life = ifelse( USUBJID == unique(USUBJID)[3] & AFRLT >= 0.5, @@ -76,8 +77,8 @@ describe("get_halflife_plot", { FALSE ) ) - plots <- get_halflife_plots(pknca_data)[["plots"]] - plots_with_incl <- get_halflife_plots(pknca_data_with_incl)[["plots"]] + plots <- get_halflife_plots(pknca_incl)[["plots"]] + plots_with_incl <- get_halflife_plots(pknca_incl_with_incl)[["plots"]] plots_details <- plots[[1]]$x$data[[2]]$marker expected_plots_details <- list( @@ -99,11 +100,11 @@ describe("get_halflife_plot", { }) it("marker colors and shapes - exclusion column missing still works", { - pknca_data <- FIXTURE_PKNCA_DATA - pknca_data$intervals <- pknca_data$intervals[2,] - pknca_data$conc$data$exclude_half.life <- NULL - pknca_data$conc$columns$exclude_half.life <- NULL - plots <- get_halflife_plots(pknca_data)[["plots"]] + pknca_no_excl_col <- base_pknca + pknca_no_excl_col$intervals <- pknca_no_excl_col$intervals[2,] + pknca_no_excl_col$conc$data$exclude_half.life <- NULL + pknca_no_excl_col$conc$columns$exclude_half.life <- NULL + plots <- get_halflife_plots(pknca_no_excl_col)[["plots"]] plots_details <- plots[[1]]$x$data[[2]]$marker expected_plots_details <- list( From df8a87d1f4730f3ba08a9fd7f38ea750fb1de027 Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Tue, 16 Sep 2025 08:11:10 +0200 Subject: [PATCH 063/113] rm: arg unneded from detect_pknca_data_changes (hl_cols) --- R/utils-slope_selector.R | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/R/utils-slope_selector.R b/R/utils-slope_selector.R index 45757f2d6..a42d5a8c8 100644 --- a/R/utils-slope_selector.R +++ b/R/utils-slope_selector.R @@ -9,10 +9,10 @@ #' or selected intervals have changed. Used to decide when to update plots. #' @param old Previous PKNCA data object #' @param new New PKNCA data object -#' @param excl_hl_col Name of exclusion column -#' @param incl_hl_col Name of inclusion column #' @return List with logicals: in_data, in_hl_adj, in_selected_intervals detect_pknca_data_changes <- function(old, new, excl_hl_col, incl_hl_col) { + excl_hl_col <- new$conc$columns$exclude_half.life + incl_hl_col <- new$conc$columns$include_half.life list( in_data = if (is.null(old) & !is.null(new)) { TRUE From e0f350a1906482be438a07b28121213f55127ed4 Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Tue, 16 Sep 2025 08:11:30 +0200 Subject: [PATCH 064/113] refactor: lintr --- R/get_halflife_plots.R | 110 +++++++++--------- .../modules/tab_nca/setup/slope_selector.R | 3 +- tests/testthat/test-get_halflife_plots.R | 28 ++--- tests/testthat/test-utils-slope_selector.R | 94 +++++++-------- 4 files changed, 121 insertions(+), 114 deletions(-) diff --git a/R/get_halflife_plots.R b/R/get_halflife_plots.R index a1675ae7d..1c2664cda 100644 --- a/R/get_halflife_plots.R +++ b/R/get_halflife_plots.R @@ -132,14 +132,20 @@ get_halflife_plots <- function(pknca_data, add_annotations = TRUE) { # Prepare an object with all plot information wide_output <- o_nca wide_output$result <- wide_output$result %>% - filter(PPTESTCD %in% c("lambda.z.time.first", "lambda.z.time.last", "lambda.z", "adj.r.squared", "span.ratio")) %>% + filter( + PPTESTCD %in% c("lambda.z.time.first", "lambda.z.time.last", + "lambda.z", "adj.r.squared", "span.ratio") + ) %>% select(-any_of(c("PPORRESU", "PPSTRESU", "PPSTRES"))) wide_output <- as.data.frame(wide_output, out_format = "wide") %>% unique() d_conc_with_res <- merge( pknca_data$conc$data %>% - select(!!!syms(c(group_vars(pknca_data), time_col, conc_col, timeu_col, concu_col, exclude_hl_col, "ROWID"))), + select( + !!!syms(c(group_vars(pknca_data), time_col, conc_col, + timeu_col, concu_col, exclude_hl_col, "ROWID")) + ), wide_output, all.x = TRUE, by = c(group_vars(pknca_data)) @@ -190,64 +196,64 @@ get_halflife_plots <- function(pknca_data, add_annotations = TRUE) { ) %>% ungroup() - info_per_plot_list <- info_per_plot_list %>% - mutate( - color = ifelse(is.na(is_halflife_used), "black", - ifelse(is_halflife_used, "green", "red")), - symbol = ifelse(.[[exclude_hl_col]], "x", "circle") - ) %>% - group_by(!!!syms(c(group_vars(pknca_data), "start", "end"))) %>% - group_split() + info_per_plot_list <- info_per_plot_list %>% + mutate( + color = ifelse(is.na(is_halflife_used), "black", + ifelse(is_halflife_used, "green", "red")), + symbol = ifelse(.[[exclude_hl_col]], "x", "circle") + ) %>% + group_by(!!!syms(c(group_vars(pknca_data), "start", "end"))) %>% + group_split() - plot_list <- list() - data_list <- list() - for (i in seq_len(length(info_per_plot_list))) { - df <- info_per_plot_list[[i]] + plot_list <- list() + data_list <- list() + for (i in seq_len(length(info_per_plot_list))) { + df <- info_per_plot_list[[i]] - # Create line data - if (any(!is.na(df$is_halflife_used))) { + # Create line data + if (any(!is.na(df$is_halflife_used))) { df_fit <- df[df$is_halflife_used, ] fit <- lm(as.formula(paste0("log10(", conc_col, ") ~ ", time_col)), df_fit) fit_line_data <- data.frame(x = c(df$lambda.z.time.first[1], max(df[[time_col]]))) colnames(fit_line_data) <- time_col fit_line_data$y <- predict(fit, fit_line_data) - } else { - fit_line_data <- data.frame( - x = c(df$start[1], df$start[1]), - y = c(0, 0) - ) - colnames(fit_line_data)[1] <- time_col - } - - # Unique plot ID based on grouping variables and interval times - plotid_vars <- c(group_vars(pknca_data), "start", "end") - plotid <- paste0( - paste0(plotid_vars, ": ", - df[1, plotid_vars, drop = FALSE], - collapse = ", " - ) + } else { + fit_line_data <- data.frame( + x = c(df$start[1], df$start[1]), + y = c(0, 0) ) + colnames(fit_line_data)[1] <- time_col + } - # Create the plot - plot_list[[plotid]] <- get_halflife_plots_single( - fit_line_data = fit_line_data, - plot_data = df, - time_col = time_col, - conc_col = conc_col, - title = paste0( - paste0(group_vars(pknca_data), ": "), - df[1, group_vars(pknca_data), drop = FALSE], - collapse = ", " - ), - xlab = df$xlab[1], - ylab = df$ylab[1], - subtitle_text = df$subtitle[1], - color = df$color, - symbol = df$symbol, - group_vars = group_vars(pknca_data), - add_annotations = add_annotations + # Unique plot ID based on grouping variables and interval times + plotid_vars <- c(group_vars(pknca_data), "start", "end") + plotid <- paste0( + paste0( + plotid_vars, ": ", df[1, plotid_vars, drop = FALSE], + collapse = ", " ) - data_list[[plotid]] <- df - } - return(list(plots = plot_list, data = data_list)) + ) + + # Create the plot + plot_list[[plotid]] <- get_halflife_plots_single( + fit_line_data = fit_line_data, + plot_data = df, + time_col = time_col, + conc_col = conc_col, + title = paste0( + paste0(group_vars(pknca_data), ": "), + df[1, group_vars(pknca_data), drop = FALSE], + collapse = ", " + ), + xlab = df$xlab[1], + ylab = df$ylab[1], + subtitle_text = df$subtitle[1], + color = df$color, + symbol = df$symbol, + group_vars = group_vars(pknca_data), + add_annotations = add_annotations + ) + data_list[[plotid]] <- df + } + return(list(plots = plot_list, data = data_list)) } diff --git a/inst/shiny/modules/tab_nca/setup/slope_selector.R b/inst/shiny/modules/tab_nca/setup/slope_selector.R index 803ed2259..e4c132f51 100644 --- a/inst/shiny/modules/tab_nca/setup/slope_selector.R +++ b/inst/shiny/modules/tab_nca/setup/slope_selector.R @@ -140,7 +140,7 @@ slope_selector_server <- function( # nolint # Modify plots with new half-life adjustments (inclusions/exclusions) plot_outputs(handle_hl_adj_change(new_pknca_data, pknca_data(), plot_outputs())) } else if (changes$in_selected_intervals) { - # Add/remove plots based on interval changes (analyte, profile, specimen selection from setup.R) + # Add/remove plots based on intervals (analyte, profile, specimen selection from setup.R) plot_outputs(handle_interval_change(new_pknca_data, pknca_data(), plot_outputs())) } @@ -197,7 +197,6 @@ slope_selector_server <- function( # nolint # Display only the plots for the current page .[page_search$page_start():page_search$page_end()] }) -# session$userData$results$slope_selector <- plot_outputs() }) # Creates an initial version of the manual slope adjustments table with pknca_data diff --git a/tests/testthat/test-get_halflife_plots.R b/tests/testthat/test-get_halflife_plots.R index 8c5fd0393..b37059c5f 100644 --- a/tests/testthat/test-get_halflife_plots.R +++ b/tests/testthat/test-get_halflife_plots.R @@ -31,7 +31,7 @@ describe("get_halflife_plot", { it("marker colors and shapes - exclusion of a lambda.z point", { pknca_excl <- base_pknca - pknca_excl$intervals <- pknca_excl$intervals[2,] + pknca_excl$intervals <- pknca_excl$intervals[2, ] pknca_excl_with_excl <- pknca_excl pknca_excl_with_excl$conc$data <- pknca_excl$conc$data %>% mutate( @@ -45,27 +45,27 @@ describe("get_halflife_plot", { plots_with_excl <- get_halflife_plots(pknca_excl_with_excl)[["plots"]] plots_details <- plots[[1]]$x$data[[2]]$marker - expected_plots_details <- list( + exp_plots_details <- list( color = c("red", "red", "green", "green", "green"), size = 15, symbol = c("circle", "circle", "circle", "circle", "circle"), line = list(color = "rgba(255,127,14,1)") ) plots_with_excl_details <- plots_with_excl[[1]]$x$data[[2]]$marker - expected_plots_details_with_excl <- list( + exp_plots_details_with_excl <- list( color = c("red", "red", "red", "green", "green"), size = 15, symbol = c("circle", "circle", "x", "circle", "circle"), line = list(color = "rgba(255,127,14,1)") ) - expect_equal(plots_details, expected_plots_details, ignore_attr = TRUE) - expect_equal(plots_with_excl_details, expected_plots_details_with_excl, ignore_attr = TRUE) + expect_equal(plots_details, exp_plots_details, ignore_attr = TRUE) + expect_equal(plots_with_excl_details, exp_plots_details_with_excl, ignore_attr = TRUE) }) it("marker colors and shapes - inclusion of lambda.z points", { pknca_incl <- base_pknca - pknca_incl$intervals <- pknca_incl$intervals[3,] + pknca_incl$intervals <- pknca_incl$intervals[3, ] pknca_incl$conc$data$exclude_half.life <- FALSE pknca_incl$conc$data$include_half.life <- NA pknca_incl_with_incl <- pknca_incl @@ -81,39 +81,39 @@ describe("get_halflife_plot", { plots_with_incl <- get_halflife_plots(pknca_incl_with_incl)[["plots"]] plots_details <- plots[[1]]$x$data[[2]]$marker - expected_plots_details <- list( + exp_plots_details <- list( color = c("red", "red", "green", "green", "green"), size = 15, symbol = c("circle", "circle", "circle", "circle", "circle"), line = list(color = "rgba(255,127,14,1)") ) plots_with_incl_details <- plots_with_incl[[1]]$x$data[[2]]$marker - expected_plots_details_with_incl <- list( + exp_plots_details_with_incl <- list( color = c("green", "green", "green", "green", "green"), size = 15, symbol = c("circle", "circle", "circle", "circle", "circle"), line = list(color = "rgba(255,127,14,1)") ) - expect_equal(plots_details, expected_plots_details, ignore_attr = TRUE) - expect_equal(plots_with_incl_details, expected_plots_details_with_incl, ignore_attr = TRUE) + expect_equal(plots_details, exp_plots_details, ignore_attr = TRUE) + expect_equal(plots_with_incl_details, exp_plots_details_with_incl, ignore_attr = TRUE) }) it("marker colors and shapes - exclusion column missing still works", { pknca_no_excl_col <- base_pknca - pknca_no_excl_col$intervals <- pknca_no_excl_col$intervals[2,] + pknca_no_excl_col$intervals <- pknca_no_excl_col$intervals[2, ] pknca_no_excl_col$conc$data$exclude_half.life <- NULL pknca_no_excl_col$conc$columns$exclude_half.life <- NULL plots <- get_halflife_plots(pknca_no_excl_col)[["plots"]] plots_details <- plots[[1]]$x$data[[2]]$marker - expected_plots_details <- list( + exp_plots_details <- list( color = c("red", "red", "green", "green", "green"), size = 15, symbol = c("circle", "circle", "circle", "circle", "circle"), line = list(color = "rgba(255,127,14,1)") ) - expect_equal(plots_details, expected_plots_details, ignore_attr = TRUE) + expect_equal(plots_details, exp_plots_details, ignore_attr = TRUE) }) -}) \ No newline at end of file +}) diff --git a/tests/testthat/test-utils-slope_selector.R b/tests/testthat/test-utils-slope_selector.R index 7bb1fe0ef..3ed1357fa 100644 --- a/tests/testthat/test-utils-slope_selector.R +++ b/tests/testthat/test-utils-slope_selector.R @@ -18,9 +18,9 @@ describe("check_slope_rule_overlap", { PCSPEC = 1, RANGE = "1:3" ) - + expect_equal(nrow(check_slope_rule_overlap(EXISTING_FIXTURE, NEW)), 2) - + # different USUBJID # NEW <- data.frame( TYPE = "Exclusion", @@ -30,9 +30,9 @@ describe("check_slope_rule_overlap", { PCSPEC = 1, RANGE = "1:3" ) - + expect_equal(nrow(check_slope_rule_overlap(EXISTING_FIXTURE, NEW)), 2) - + # different NCA_PROFILE # NEW <- data.frame( TYPE = "Exclusion", @@ -42,11 +42,11 @@ describe("check_slope_rule_overlap", { PCSPEC = 1, RANGE = "1:3" ) - + expect_equal(nrow(check_slope_rule_overlap(EXISTING_FIXTURE, NEW)), 2) - + }) - + it("should remove overlapping points if no new points are detected", { NEW <- data.frame( TYPE = "Exclusion", @@ -56,9 +56,9 @@ describe("check_slope_rule_overlap", { PCSPEC = 1, RANGE = "4:5" ) - + expect_equal(check_slope_rule_overlap(EXISTING_FIXTURE, NEW)$RANGE, "3,6") - + NEW <- data.frame( TYPE = "Exclusion", USUBJID = 1, @@ -67,11 +67,11 @@ describe("check_slope_rule_overlap", { PCSPEC = 1, RANGE = "3:4" ) - + expect_equal(check_slope_rule_overlap(EXISTING_FIXTURE, NEW)$RANGE, "5:6") - + }) - + it("should add new points of partial overlap is detected", { NEW <- data.frame( TYPE = "Exclusion", @@ -81,11 +81,11 @@ describe("check_slope_rule_overlap", { PCSPEC = 1, RANGE = "4:9" ) - + expect_equal(check_slope_rule_overlap(EXISTING_FIXTURE, NEW)$RANGE, "3:9") - + }) - + it("should remove full row if full range of rule is removed", { NEW <- data.frame( TYPE = "Exclusion", @@ -95,11 +95,11 @@ describe("check_slope_rule_overlap", { PCSPEC = 1, RANGE = "3:6" ) - + expect_equal(nrow(check_slope_rule_overlap(EXISTING_FIXTURE, NEW)), 0) - + }) - + it("should warn if more than one range for single subject, profile and rule type is detected", { EXISTING <- data.frame( TYPE = "Exclusion", @@ -109,12 +109,12 @@ describe("check_slope_rule_overlap", { PCSPEC = 1, RANGE = "3:6" ) - + DUPLICATE <- EXISTING %>% mutate( RANGE = "4:7" ) - + expect_warning( check_slope_rule_overlap(rbind(EXISTING, DUPLICATE), DUPLICATE), "More than one range for single subject, profile and rule type detected." @@ -123,40 +123,40 @@ describe("check_slope_rule_overlap", { }) describe("detect_pknca_data_changes", { - + old_data <- FIXTURE_PKNCA_DATA - + it("detects change in data when there is no previous object", { new_data <- old_data - res <- detect_pknca_data_changes(NULL, new_data, "exclude_half.life", "include_half.life") + res <- detect_pknca_data_changes(NULL, new_data) expect_true(res$in_data) }) - + it("detects no change when old and new are identical", { - res <- detect_pknca_data_changes(old_data, old_data, "exclude_half.life", "include_half.life") + res <- detect_pknca_data_changes(old_data, old_data) expect_false(res$in_data) expect_false(res$in_hl_adj) expect_false(res$in_selected_intervals) }) - + it("detects change in data", { changed_data <- old_data changed_data$conc$data$VAL[1] <- 99 - res <- detect_pknca_data_changes(old_data, changed_data, "exclude_half.life", "include_half.life") + res <- detect_pknca_data_changes(old_data, changed_data) expect_true(res$in_data) }) - + it("detects change in hl_adj", { changed_hl <- old_data changed_hl$conc$data$exclude_half.life[2] <- TRUE - res <- detect_pknca_data_changes(old_data, changed_hl, "exclude_half.life", "include_half.life") + res <- detect_pknca_data_changes(old_data, changed_hl) expect_true(res$in_hl_adj) }) - + it("detects change in intervals", { - changed_intervals <- old_data - changed_intervals$intervals <- data.frame(ID = 1:4) - res <- detect_pknca_data_changes(old_data, changed_intervals, "exclude_half.life", "include_half.life") + changed_int <- old_data + changed_int$intervals <- data.frame(ID = 1:4) + res <- detect_pknca_data_changes(old_data, changed_int) expect_true(res$in_selected_intervals) }) }) @@ -174,15 +174,15 @@ describe("handle_hl_adj_change", { exclude_half.life ) ) - + # Create plots using the original and updated PKNCA data old_plots <- get_halflife_plots(old_data)$plots new_plots <- handle_hl_adj_change(new_data, old_data, old_plots) - + # Check that the plots for other groups remain unchanged ix_unchanged_plots <- setdiff(seq_len(length(new_plots)), 4) expect_equal(new_plots[ix_unchanged_plots], old_plots[ix_unchanged_plots]) - + # Define the expected differences in the original and updated plots old_plots_exp_details <- list( color = c("red", "red", "green", "green", "green"), @@ -211,17 +211,17 @@ describe("handle_interval_change", { new_data <- FIXTURE_PKNCA_DATA new_data$intervals <- new_data$intervals[1, ] new_plots <- handle_interval_change(new_data, old_data, old_plots) - + expect_equal(length(old_plots), 3) expect_equal(length(new_plots), 1) expect_equal(old_plots[names(new_plots)], new_plots) }) - + it("adds plots when intervals are added", { new_data <- FIXTURE_PKNCA_DATA new_data$intervals <- new_data$intervals[1:4, ] new_plots <- handle_interval_change(new_data, old_data, old_plots) - + expect_equal(length(old_plots), 3) expect_equal(length(new_plots), 4) expect_equal(new_plots[names(old_plots)], old_plots) @@ -244,21 +244,23 @@ describe("arrange_plots_by_groups", { describe(".update_pknca_with_rules", { old_data <- FIXTURE_PKNCA_DATA - group1 <- old_data$intervals %>% select(any_of(c(group_vars(old_data), "start", "end"))) %>% .[1,] + group1 <- old_data$intervals %>% + select(any_of(c(group_vars(old_data), "start", "end"))) %>% + .[1, ] it("applies selection and exclusion rules to data", { slopes_incl <- cbind( - data.frame(TYPE = "Selection", ID = 1, RANGE = "2:4", REASON = "because I want to test it :)"), + data.frame(TYPE = "Selection", ID = 1, RANGE = "2:4", REASON = "because I want to"), group1 ) slopes_excl <- cbind( - data.frame(TYPE = "Exclusion", ID = 1, RANGE = "2:4", REASON = "there are always good reasons"), + data.frame(TYPE = "Exclusion", ID = 1, RANGE = "2:4", REASON = "always good reasons"), group1 ) - + new_with_incl <- .update_pknca_with_rules(old_data, slopes_incl) new_with_excl <- .update_pknca_with_rules(old_data, slopes_excl) - + old_have_points_na <- all(is.na(old_data$conc$data %>% filter(USUBJID == group1$USUBJID, AFRLT >= 2, AFRLT <= 4) %>% pull(include_half.life)) @@ -266,11 +268,11 @@ describe(".update_pknca_with_rules", { new_have_points_incl <- all(new_with_incl$conc$data %>% filter(USUBJID == group1$USUBJID, AFRLT >= 2, AFRLT <= 4) %>% pull(include_half.life)) - + new_have_points_excl <- all(new_with_excl$conc$data %>% filter(USUBJID == group1$USUBJID, AFRLT >= 2, AFRLT <= 4) %>% pull(exclude_half.life)) - + expect_true(all(old_have_points_na, new_have_points_incl, new_have_points_excl)) }) it("returns an error for invalid rule types", { From 5628c01d3ae7627ed51747b0180236ac62b8ef33 Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Tue, 16 Sep 2025 08:18:36 +0200 Subject: [PATCH 065/113] docs: update PKNCA.R & rename funs in utils-slope_selector.R --- R/PKNCA.R | 7 ++++++- R/utils-slope_selector.R | 10 +++++----- tests/testthat/test-utils-slope_selector.R | 14 +++++++------- 3 files changed, 18 insertions(+), 13 deletions(-) diff --git a/R/PKNCA.R b/R/PKNCA.R index a614d7cb3..e6a681e57 100644 --- a/R/PKNCA.R +++ b/R/PKNCA.R @@ -195,6 +195,8 @@ PKNCA_create_data_object <- function(adnca_data) { # nolint: object_name_linter #' Step 4: Apply filtering based on user selections and partial aucs #' #' Step 5: Impute start values if requested +#' +#' Step 6: Indicate points excluded / selected manually for half-life #' #' Note*: The function assumes that the `adnca_data` object has been #' created using the `PKNCA_create_data_object()` function. @@ -207,6 +209,9 @@ PKNCA_create_data_object <- function(adnca_data) { # nolint: object_name_linter #' @param selected_pcspec User selected specimen #' @param params A list of parameters for NCA calculation #' @param should_impute_c0 Logical indicating if start values should be imputed +#' @param hl_adj_rules A data frame containing half-life adjustment rules. It must +#' contain group columns and rule specification columns, +#' (TYPE: {Inclusion, Exclusion}, RANGE: {start-end}). #' #' @returns A fully configured `PKNCAdata` object. #' @@ -319,7 +324,7 @@ PKNCA_update_data_object <- function( # nolint: object_name_linter # Update concentration data to indicate points excluded / selected manually for half-life if (!is.null(hl_adj_rules)) { - data <- .update_pknca_with_rules(data, hl_adj_rules) + data <- update_pknca_with_rules(data, hl_adj_rules) } data diff --git a/R/utils-slope_selector.R b/R/utils-slope_selector.R index a42d5a8c8..b418aa662 100644 --- a/R/utils-slope_selector.R +++ b/R/utils-slope_selector.R @@ -10,7 +10,7 @@ #' @param old Previous PKNCA data object #' @param new New PKNCA data object #' @return List with logicals: in_data, in_hl_adj, in_selected_intervals -detect_pknca_data_changes <- function(old, new, excl_hl_col, incl_hl_col) { +detect_pknca_data_changes <- function(old, new) { excl_hl_col <- new$conc$columns$exclude_half.life incl_hl_col <- new$conc$columns$include_half.life list( @@ -64,7 +64,7 @@ handle_hl_adj_change <- function(new_pknca_data, old_pknca_data, plot_outputs) { ) %>% select(any_of(c(group_vars(new_pknca_data), "NCA_PROFILE"))) %>% distinct() - .update_plots_with_pknca(new_pknca_data, plot_outputs, affected_groups) + update_plots_with_pknca(new_pknca_data, plot_outputs, affected_groups) } @@ -98,7 +98,7 @@ handle_interval_change <- function(new_pknca_data, old_pknca_data, plot_outputs) merge(unique(PKNCA::getGroups(new_pknca_data$conc)), all.x = TRUE) %>% select(any_of(c(group_vars(new_pknca_data), "start", "end"))) %>% distinct() - plot_outputs <- .update_plots_with_pknca( + plot_outputs <- update_plots_with_pknca( new_pknca_data, plot_outputs, affected_groups @@ -215,7 +215,7 @@ check_slope_rule_overlap <- function(existing, new, .keep = FALSE) { #' @param data PKNCA data object #' @param slopes Data frame of slope rules (TYPE, RANGE, REASON, group columns) #' @return Modified data object with updated flags -.update_pknca_with_rules <- function(data, slopes) { +update_pknca_with_rules <- function(data, slopes) { slope_groups <- intersect(group_vars(data), names(slopes)) time_col <- data$conc$columns$time exclude_hl_col <- data$conc$columns$exclude_half.life @@ -254,7 +254,7 @@ check_slope_rule_overlap <- function(existing, new, .keep = FALSE) { #' @param plot_outputs Named list of current plot outputs #' @param intervals_to_update Data frame of intervals to update (default: all in pknca_data) #' @return Updated plot_outputs (named list) -.update_plots_with_pknca <- function(pknca_data, plot_outputs, intervals_to_update = NULL) { +update_plots_with_pknca <- function(pknca_data, plot_outputs, intervals_to_update = NULL) { if (is.null(intervals_to_update)) { intervals_to_update <- pknca_data$intervals %>% select(any_of(c(group_vars(pknca_data), "start", "end"))) %>% diff --git a/tests/testthat/test-utils-slope_selector.R b/tests/testthat/test-utils-slope_selector.R index 3ed1357fa..6c6c74418 100644 --- a/tests/testthat/test-utils-slope_selector.R +++ b/tests/testthat/test-utils-slope_selector.R @@ -242,7 +242,7 @@ describe("arrange_plots_by_groups", { }) }) -describe(".update_pknca_with_rules", { +describe("update_pknca_with_rules", { old_data <- FIXTURE_PKNCA_DATA group1 <- old_data$intervals %>% select(any_of(c(group_vars(old_data), "start", "end"))) %>% @@ -258,8 +258,8 @@ describe(".update_pknca_with_rules", { group1 ) - new_with_incl <- .update_pknca_with_rules(old_data, slopes_incl) - new_with_excl <- .update_pknca_with_rules(old_data, slopes_excl) + new_with_incl <- update_pknca_with_rules(old_data, slopes_incl) + new_with_excl <- update_pknca_with_rules(old_data, slopes_excl) old_have_points_na <- all(is.na(old_data$conc$data %>% filter(USUBJID == group1$USUBJID, AFRLT >= 2, AFRLT <= 4) %>% @@ -280,11 +280,11 @@ describe(".update_pknca_with_rules", { data.frame(TYPE = "Invalid", ID = 1, RANGE = "2:4", REASON = "invalid type"), group1 ) - expect_error(.update_pknca_with_rules(old, slopes_invalid)) + expect_error(update_pknca_with_rules(old, slopes_invalid)) }) }) -describe(".update_plots_with_pknca", { +describe("update_plots_with_pknca", { old_data <- FIXTURE_PKNCA_DATA old_data$intervals <- old_data$intervals[1:2, ] old_plots <- get_halflife_plots(old_data)$plots @@ -293,12 +293,12 @@ describe(".update_plots_with_pknca", { new_data$conc$data$exclude_half.life <- TRUE it("updates all plots when no intervals are specified", { - updated_plots <- .update_plots_with_pknca(new_data, old_plots, NULL) + updated_plots <- update_plots_with_pknca(new_data, old_plots, NULL) expect_equal(updated_plots[[1]]$x$data[[2]]$marker$symbol, rep("x", 5), ignore_attr = TRUE) expect_equal(old_plots[[2]]$x$data[[2]]$marker$symbol, rep("circle", 5), ignore_attr = TRUE) }) it("does not update any plot when the specified intervals are an empty dataframe", { - updated_plots <- .update_plots_with_pknca(new_data, old_plots, data.frame()) + updated_plots <- update_plots_with_pknca(new_data, old_plots, data.frame()) expect_equal(updated_plots, old_plots) }) }) From ab9f9e54cace977b043f638df382557d0a4f5dc2 Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Tue, 16 Sep 2025 08:20:23 +0200 Subject: [PATCH 066/113] man: update manuals --- man/PKNCA_update_data_object.Rd | 6 ++++ man/detect_pknca_data_changes.Rd | 6 +--- man/dot-update_plots_with_rules.Rd | 28 ------------------- ...th_rules.Rd => update_pknca_with_rules.Rd} | 6 ++-- ...th_pknca.Rd => update_plots_with_pknca.Rd} | 6 ++-- 5 files changed, 13 insertions(+), 39 deletions(-) delete mode 100644 man/dot-update_plots_with_rules.Rd rename man/{dot-update_pknca_with_rules.Rd => update_pknca_with_rules.Rd} (80%) rename man/{dot-update_plots_with_pknca.Rd => update_plots_with_pknca.Rd} (77%) diff --git a/man/PKNCA_update_data_object.Rd b/man/PKNCA_update_data_object.Rd index f1697d24d..89ccf14ce 100644 --- a/man/PKNCA_update_data_object.Rd +++ b/man/PKNCA_update_data_object.Rd @@ -32,6 +32,10 @@ PKNCA_update_data_object( \item{params}{A list of parameters for NCA calculation} \item{should_impute_c0}{Logical indicating if start values should be imputed} + +\item{hl_adj_rules}{A data frame containing half-life adjustment rules. It must +contain group columns and rule specification columns, +(TYPE: {Inclusion, Exclusion}, RANGE: {start-end}).} } \value{ A fully configured \code{PKNCAdata} object. @@ -52,6 +56,8 @@ Step 4: Apply filtering based on user selections and partial aucs Step 5: Impute start values if requested +Step 6: Indicate points excluded / selected manually for half-life + Note*: The function assumes that the \code{adnca_data} object has been created using the \code{PKNCA_create_data_object()} function. } diff --git a/man/detect_pknca_data_changes.Rd b/man/detect_pknca_data_changes.Rd index 1a85198b1..40c51c49e 100644 --- a/man/detect_pknca_data_changes.Rd +++ b/man/detect_pknca_data_changes.Rd @@ -4,16 +4,12 @@ \alias{detect_pknca_data_changes} \title{Slope Selector Utility Functions} \usage{ -detect_pknca_data_changes(old, new, excl_hl_col, incl_hl_col) +detect_pknca_data_changes(old, new) } \arguments{ \item{old}{Previous PKNCA data object} \item{new}{New PKNCA data object} - -\item{excl_hl_col}{Name of exclusion column} - -\item{incl_hl_col}{Name of inclusion column} } \value{ List with logicals: in_data, in_hl_adj, in_selected_intervals diff --git a/man/dot-update_plots_with_rules.Rd b/man/dot-update_plots_with_rules.Rd deleted file mode 100644 index 7bc6b83de..000000000 --- a/man/dot-update_plots_with_rules.Rd +++ /dev/null @@ -1,28 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/utils-slope_selector.R -\name{.update_plots_with_rules} -\alias{.update_plots_with_rules} -\title{Update plots with manual slope rules} -\usage{ -.update_plots_with_rules( - pknca_data, - manual_slopes, - plot_outputs, - groups_to_update = NULL -) -} -\arguments{ -\item{pknca_data}{PKNCA data object} - -\item{manual_slopes}{Data frame of manual slope rules} - -\item{plot_outputs}{Named list of current plot outputs} - -\item{groups_to_update}{Optional: data frame of intervals to update (default: all)} -} -\value{ -Updated plot_outputs (named list) -} -\description{ -Updates the plot_outputs list by applying manual slopes. -} diff --git a/man/dot-update_pknca_with_rules.Rd b/man/update_pknca_with_rules.Rd similarity index 80% rename from man/dot-update_pknca_with_rules.Rd rename to man/update_pknca_with_rules.Rd index 427506fd0..d5641673b 100644 --- a/man/dot-update_pknca_with_rules.Rd +++ b/man/update_pknca_with_rules.Rd @@ -1,10 +1,10 @@ % Generated by roxygen2: do not edit by hand % Please edit documentation in R/utils-slope_selector.R -\name{.update_pknca_with_rules} -\alias{.update_pknca_with_rules} +\name{update_pknca_with_rules} +\alias{update_pknca_with_rules} \title{Apply Slope Rules to Update Data} \usage{ -.update_pknca_with_rules(data, slopes) +update_pknca_with_rules(data, slopes) } \arguments{ \item{data}{PKNCA data object} diff --git a/man/dot-update_plots_with_pknca.Rd b/man/update_plots_with_pknca.Rd similarity index 77% rename from man/dot-update_plots_with_pknca.Rd rename to man/update_plots_with_pknca.Rd index b487a2f70..8b415901c 100644 --- a/man/dot-update_plots_with_pknca.Rd +++ b/man/update_plots_with_pknca.Rd @@ -1,10 +1,10 @@ % Generated by roxygen2: do not edit by hand % Please edit documentation in R/utils-slope_selector.R -\name{.update_plots_with_pknca} -\alias{.update_plots_with_pknca} +\name{update_plots_with_pknca} +\alias{update_plots_with_pknca} \title{Update plots with PKNCA data (for affected intervals)} \usage{ -.update_plots_with_pknca(pknca_data, plot_outputs, intervals_to_update = NULL) +update_plots_with_pknca(pknca_data, plot_outputs, intervals_to_update = NULL) } \arguments{ \item{pknca_data}{PKNCA data object} From 57f577a5f4544423ef5514602499725691f3e94f Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Tue, 16 Sep 2025 08:32:57 +0200 Subject: [PATCH 067/113] docs: change documentation and manuals in get_halflife_plots.R --- NAMESPACE | 8 ++ R/get_halflife_plots.R | 197 ++++++++++++++++--------------- man/get_halflife_plots.Rd | 19 +++ man/get_halflife_plots_single.Rd | 55 --------- 4 files changed, 130 insertions(+), 149 deletions(-) create mode 100644 man/get_halflife_plots.Rd delete mode 100644 man/get_halflife_plots_single.Rd diff --git a/NAMESPACE b/NAMESPACE index fce7e58da..6e9af89a2 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -61,6 +61,7 @@ import(rlistings) import(tidyr) importFrom(PKNCA,exclude) importFrom(PKNCA,pk.calc.c0) +importFrom(PKNCA,pk.nca) importFrom(dplyr,"%>%") importFrom(dplyr,`%>%`) importFrom(dplyr,across) @@ -73,6 +74,7 @@ importFrom(dplyr,distinct) importFrom(dplyr,everything) importFrom(dplyr,filter) importFrom(dplyr,group_by) +importFrom(dplyr,group_split) importFrom(dplyr,inner_join) importFrom(dplyr,left_join) importFrom(dplyr,mutate) @@ -93,15 +95,21 @@ importFrom(ggplot2,scale_x_continuous) importFrom(grid,convertUnit) importFrom(magrittr,`%>%`) importFrom(methods,is) +importFrom(plotly,add_lines) +importFrom(plotly,add_trace) +importFrom(plotly,layout) importFrom(plotly,plot_ly) +importFrom(plotly,plotly_build) importFrom(purrr,map_chr) importFrom(purrr,pmap) importFrom(purrr,pmap_chr) importFrom(rlang,sym) importFrom(rlang,syms) importFrom(stats,as.formula) +importFrom(stats,lm) importFrom(stats,median) importFrom(stats,na.omit) +importFrom(stats,predict) importFrom(stats,sd) importFrom(stats,setNames) importFrom(stringr,str_glue) diff --git a/R/get_halflife_plots.R b/R/get_halflife_plots.R index 1c2664cda..640c9c075 100644 --- a/R/get_halflife_plots.R +++ b/R/get_halflife_plots.R @@ -2,99 +2,13 @@ #' #' Generates a plotly plot for NCA half-life visualization, with a fit line and scatter points. #' -#' @param fit_line_data Data frame for the fit line (must have columns for time and y) -#' @param plot_data Data frame for the scatter points -#' @param time_col Name of the time column (string) -#' @param conc_col Name of the concentration column (string) -#' @param title Plot title -#' @param xlab X axis label -#' @param ylab Y axis label -#' @param subtitle_text Subtitle/annotation (HTML allowed) -#' @param color Vector of colors for points (same length as plot_data) -#' @param symbol Vector of marker symbols for points (same length as plot_data) -#' @param group_vars Character vector of grouping variable names (for customdata) +#' @param pknca_data PKNCA data object #' @param add_annotations Logical, whether to add the subtitle annotation -#' @param text Optional vector of hover text for points (same length as plot_data) -#' @return A plotly object -get_halflife_plots_single <- function( - fit_line_data, - plot_data, - time_col, - conc_col, - title, - xlab, - ylab, - subtitle_text, - color, - symbol, - group_vars, - add_annotations = TRUE, - text = NULL -) { - if (is.null(text)) { - text <- paste0( - "Data Point: ", seq_len(nrow(plot_data)), "\n(", - plot_data[[time_col]], ", ", signif(plot_data[[conc_col]], 3), ")" - ) - } - plotly::plot_ly() %>% - plotly::add_lines( - data = fit_line_data, - x = ~get(time_col), - y = ~10^y, - line = list(color = "green", width = 2), - name = "Fit", - inherit = FALSE, - showlegend = TRUE - ) %>% - plotly::layout( - title = title, - xaxis = list( - title = xlab, - linecolor = "black", - gridcolor = "white", - zeroline = FALSE - ), - yaxis = list( - title = ylab, - type = "log", - tickformat = "f", - linecolor = "black", - gridcolor = "white", - zeroline = FALSE - ), - annotations = list( - text = subtitle_text, - showarrow = FALSE, - xref = "paper", - yref = "paper", - y = 1 - ) - ) %>% - plotly::add_trace( - data = plot_data, - x = ~plot_data[[time_col]], - y = ~plot_data[[conc_col]], - text = text, - hoverinfo = "text", - showlegend = FALSE, - type = "scatter", - mode = "markers", - marker = list( - color = color, - size = 15, - symbol = symbol, - size = 20 - ), - customdata = apply( - plot_data[, c(group_vars, "ROWID"), drop = FALSE], - 1, - function(row) as.list(setNames(row, c(group_vars, "ROWID"))) - ) - ) %>% - plotly::plotly_build() -} - +#' @return A list with plotly objects and data +#' @importFrom dplyr filter select mutate group_by ungroup group_split %>% any_of +#' @importFrom stats lm predict as.formula +#' @importFrom plotly plot_ly add_lines layout add_trace plotly_build +#' @importFrom PKNCA pk.nca get_halflife_plots <- function(pknca_data, add_annotations = TRUE) { # If the input has empty concentration or intervals, just return an empty list @@ -213,10 +127,10 @@ get_halflife_plots <- function(pknca_data, add_annotations = TRUE) { # Create line data if (any(!is.na(df$is_halflife_used))) { df_fit <- df[df$is_halflife_used, ] - fit <- lm(as.formula(paste0("log10(", conc_col, ") ~ ", time_col)), df_fit) + fit <- stats::lm(as.formula(paste0("log10(", conc_col, ") ~ ", time_col)), df_fit) fit_line_data <- data.frame(x = c(df$lambda.z.time.first[1], max(df[[time_col]]))) colnames(fit_line_data) <- time_col - fit_line_data$y <- predict(fit, fit_line_data) + fit_line_data$y <- stats::predict(fit, fit_line_data) } else { fit_line_data <- data.frame( x = c(df$start[1], df$start[1]), @@ -257,3 +171,98 @@ get_halflife_plots <- function(pknca_data, add_annotations = TRUE) { } return(list(plots = plot_list, data = data_list)) } + +# Internal helper for plotting a single half-life plot +#' +#' @keywords internal +#' @param fit_line_data Data frame for the fit line (must have columns for time and y) +#' @param plot_data Data frame for the scatter points +#' @param time_col Name of the time column (string) +#' @param conc_col Name of the concentration column (string) +#' @param title Plot title +#' @param xlab X axis label +#' @param ylab Y axis label +#' @param subtitle_text Subtitle/annotation (HTML allowed) +#' @param color Vector of colors for points (same length as plot_data) +#' @param symbol Vector of marker symbols for points (same length as plot_data) +#' @param group_vars Character vector of grouping variable names (for customdata) +#' @param add_annotations Logical, whether to add the subtitle annotation +#' @param text Optional vector of hover text for points (same length as plot_data) +get_halflife_plots_single <- function( + fit_line_data, + plot_data, + time_col, + conc_col, + title, + xlab, + ylab, + subtitle_text, + color, + symbol, + group_vars, + add_annotations = TRUE, + text = NULL +) { + if (is.null(text)) { + text <- paste0( + "Data Point: ", seq_len(nrow(plot_data)), "\n(", + plot_data[[time_col]], ", ", signif(plot_data[[conc_col]], 3), ")" + ) + } + plotly::plot_ly() %>% + plotly::add_lines( + data = fit_line_data, + x = ~get(time_col), + y = ~10^y, + line = list(color = "green", width = 2), + name = "Fit", + inherit = FALSE, + showlegend = TRUE + ) %>% + plotly::layout( + title = title, + xaxis = list( + title = xlab, + linecolor = "black", + gridcolor = "white", + zeroline = FALSE + ), + yaxis = list( + title = ylab, + type = "log", + tickformat = "f", + linecolor = "black", + gridcolor = "white", + zeroline = FALSE + ), + annotations = list( + text = subtitle_text, + showarrow = FALSE, + xref = "paper", + yref = "paper", + y = 1 + ) + ) %>% + plotly::add_trace( + data = plot_data, + x = ~plot_data[[time_col]], + y = ~plot_data[[conc_col]], + text = text, + hoverinfo = "text", + showlegend = FALSE, + type = "scatter", + mode = "markers", + marker = list( + color = color, + size = 15, + symbol = symbol, + size = 20 + ), + customdata = apply( + plot_data[, c(group_vars, "ROWID"), drop = FALSE], + 1, + function(row) as.list(setNames(row, c(group_vars, "ROWID"))) + ) + ) %>% + plotly::plotly_build() +} diff --git a/man/get_halflife_plots.Rd b/man/get_halflife_plots.Rd new file mode 100644 index 000000000..74a18f9c5 --- /dev/null +++ b/man/get_halflife_plots.Rd @@ -0,0 +1,19 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/get_halflife_plots.R +\name{get_halflife_plots} +\alias{get_halflife_plots} +\title{Create a Plotly Half-life Plot} +\usage{ +get_halflife_plots(pknca_data, add_annotations = TRUE) +} +\arguments{ +\item{pknca_data}{PKNCA data object} + +\item{add_annotations}{Logical, whether to add the subtitle annotation} +} +\value{ +A list with plotly objects and data +} +\description{ +Generates a plotly plot for NCA half-life visualization, with a fit line and scatter points. +} diff --git a/man/get_halflife_plots_single.Rd b/man/get_halflife_plots_single.Rd deleted file mode 100644 index 7c324a3e5..000000000 --- a/man/get_halflife_plots_single.Rd +++ /dev/null @@ -1,55 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/get_halflife_plots.R -\name{get_halflife_plots_single} -\alias{get_halflife_plots_single} -\title{Create a Plotly Half-life Plot} -\usage{ -get_halflife_plots_single( - fit_line_data, - plot_data, - time_col, - conc_col, - title, - xlab, - ylab, - subtitle_text, - color, - symbol, - group_vars, - add_annotations = TRUE, - text = NULL -) -} -\arguments{ -\item{fit_line_data}{Data frame for the fit line (must have columns for time and y)} - -\item{plot_data}{Data frame for the scatter points} - -\item{time_col}{Name of the time column (string)} - -\item{conc_col}{Name of the concentration column (string)} - -\item{title}{Plot title} - -\item{xlab}{X axis label} - -\item{ylab}{Y axis label} - -\item{subtitle_text}{Subtitle/annotation (HTML allowed)} - -\item{color}{Vector of colors for points (same length as plot_data)} - -\item{symbol}{Vector of marker symbols for points (same length as plot_data)} - -\item{group_vars}{Character vector of grouping variable names (for customdata)} - -\item{add_annotations}{Logical, whether to add the subtitle annotation} - -\item{text}{Optional vector of hover text for points (same length as plot_data)} -} -\value{ -A plotly object -} -\description{ -Generates a plotly plot for NCA half-life visualization, with a fit line and scatter points. -} From 32210f1f1040941f824a140537552e42a142f0d9 Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Tue, 16 Sep 2025 08:38:35 +0200 Subject: [PATCH 068/113] zzz.R: add global vars --- R/zzz.R | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/R/zzz.R b/R/zzz.R index ba75d56a5..4a164f2ed 100644 --- a/R/zzz.R +++ b/R/zzz.R @@ -94,11 +94,11 @@ "TIME_DOSE", "TYPE", "Type", - "type", "USUBJID", "VOLUME", "Value", "Variable", + "adj.r.squared", "aucs", "aucs_extravascular", "aucs_intravascular", @@ -118,9 +118,12 @@ "exclude_half.life", "facet_title", "format_fun", + "ggplotly", + "half.life", "id_list", "id_plot", "id_variable_col", + "include_half.life", "install.packages", "interval_name", "interval_name_col", @@ -132,6 +135,11 @@ "is_extravascular", "is_metabolite", "is_one_dose", + "is_halflife_used", + "lambda.z", + "lambda.z.time.first", + "lambda.z.time.last", + "layout", "legend_group", "log10_CI", "log10_Mean", @@ -149,12 +157,15 @@ "pknca_units_tbl", "ppanmeth_ref_groups", "ppanmeth_test_groups", + "predict", "prev_dosno", "single_dose_present", + "span.ratio", "start", "start_auc", "start_dose", "strata_cols_comb", + "type", "type_interval", "var_name", "view", From 7bd5d1ce05f467bd32ca1a58a3fb112916ce66c4 Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Tue, 16 Sep 2025 09:30:40 +0200 Subject: [PATCH 069/113] docs: add `` for column names in man --- R/utils-slope_selector.R | 4 ++-- man/detect_pknca_data_changes.Rd | 2 +- man/parse_plot_names_to_df.Rd | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/R/utils-slope_selector.R b/R/utils-slope_selector.R index b418aa662..bc9673393 100644 --- a/R/utils-slope_selector.R +++ b/R/utils-slope_selector.R @@ -9,7 +9,7 @@ #' or selected intervals have changed. Used to decide when to update plots. #' @param old Previous PKNCA data object #' @param new New PKNCA data object -#' @return List with logicals: in_data, in_hl_adj, in_selected_intervals +#' @return List with logicals: `in_data`, `in_hl_adj`, `in_selected_intervals` detect_pknca_data_changes <- function(old, new) { excl_hl_col <- new$conc$columns$exclude_half.life incl_hl_col <- new$conc$columns$include_half.life @@ -131,7 +131,7 @@ handle_interval_change <- function(new_pknca_data, old_pknca_data, plot_outputs) #' into a data frame with one row per plot and columns for each key. #' #' @param named_list A named list or vector, where names are key-value pairs separated by commas. -#' @return A data frame with columns for each key and a PLOTID column with the original names. +#' @return A data frame with columns for each key and a `PLOTID` column with the original names. parse_plot_names_to_df <- function(named_list) { plot_names <- names(named_list) parsed <- lapply(plot_names, function(x) { diff --git a/man/detect_pknca_data_changes.Rd b/man/detect_pknca_data_changes.Rd index 40c51c49e..2be80dcda 100644 --- a/man/detect_pknca_data_changes.Rd +++ b/man/detect_pknca_data_changes.Rd @@ -12,7 +12,7 @@ detect_pknca_data_changes(old, new) \item{new}{New PKNCA data object} } \value{ -List with logicals: in_data, in_hl_adj, in_selected_intervals +List with logicals: \code{in_data}, \code{in_hl_adj}, \code{in_selected_intervals} } \description{ These helpers support the slope selection workflow by detecting changes in PKNCA data, diff --git a/man/parse_plot_names_to_df.Rd b/man/parse_plot_names_to_df.Rd index 03df052cb..24b0460c5 100644 --- a/man/parse_plot_names_to_df.Rd +++ b/man/parse_plot_names_to_df.Rd @@ -10,7 +10,7 @@ parse_plot_names_to_df(named_list) \item{named_list}{A named list or vector, where names are key-value pairs separated by commas.} } \value{ -A data frame with columns for each key and a PLOTID column with the original names. +A data frame with columns for each key and a \code{PLOTID} column with the original names. } \description{ Converts a named list of plots (with names in the format 'col1: val1, col2: val2, ...') From 13940d87a6fcfe228c26b76e8938112ac4f03b84 Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Tue, 16 Sep 2025 09:32:16 +0200 Subject: [PATCH 070/113] lintr --- R/PKNCA.R | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/R/PKNCA.R b/R/PKNCA.R index e6a681e57..099ccd990 100644 --- a/R/PKNCA.R +++ b/R/PKNCA.R @@ -195,7 +195,7 @@ PKNCA_create_data_object <- function(adnca_data) { # nolint: object_name_linter #' Step 4: Apply filtering based on user selections and partial aucs #' #' Step 5: Impute start values if requested -#' +#' #' Step 6: Indicate points excluded / selected manually for half-life #' #' Note*: The function assumes that the `adnca_data` object has been @@ -210,7 +210,7 @@ PKNCA_create_data_object <- function(adnca_data) { # nolint: object_name_linter #' @param params A list of parameters for NCA calculation #' @param should_impute_c0 Logical indicating if start values should be imputed #' @param hl_adj_rules A data frame containing half-life adjustment rules. It must -#' contain group columns and rule specification columns, +#' contain group columns and rule specification columns, #' (TYPE: {Inclusion, Exclusion}, RANGE: {start-end}). #' #' @returns A fully configured `PKNCAdata` object. From bf46eaec91d9168da7b43eace38bb8e80da5ff64 Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Tue, 16 Sep 2025 09:45:25 +0200 Subject: [PATCH 071/113] add missing importFrom in general_meanplot --- NAMESPACE | 1 + R/general_meanplot.R | 1 + 2 files changed, 2 insertions(+) diff --git a/NAMESPACE b/NAMESPACE index 6e9af89a2..41aa3d3a4 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -97,6 +97,7 @@ importFrom(magrittr,`%>%`) importFrom(methods,is) importFrom(plotly,add_lines) importFrom(plotly,add_trace) +importFrom(plotly,ggplotly) importFrom(plotly,layout) importFrom(plotly,plot_ly) importFrom(plotly,plotly_build) diff --git a/R/general_meanplot.R b/R/general_meanplot.R index 2ea5f3493..45df89c4c 100644 --- a/R/general_meanplot.R +++ b/R/general_meanplot.R @@ -24,6 +24,7 @@ #' #' @import dplyr #' @import ggplot2 +#' @importFrom plotly ggplotly #' @export #' general_meanplot <- function(data, From 2b5f9eb36d32e0f63e90203d9dd3dc1c175c9f6f Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Tue, 16 Sep 2025 10:18:05 +0200 Subject: [PATCH 072/113] man: update --- R/get_halflife_plots.R | 4 ++- man/get_halflife_plots_single.Rd | 53 ++++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 1 deletion(-) create mode 100644 man/get_halflife_plots_single.Rd diff --git a/R/get_halflife_plots.R b/R/get_halflife_plots.R index 640c9c075..8eb0bc6d1 100644 --- a/R/get_halflife_plots.R +++ b/R/get_halflife_plots.R @@ -172,7 +172,9 @@ get_halflife_plots <- function(pknca_data, add_annotations = TRUE) { return(list(plots = plot_list, data = data_list)) } -# Internal helper for plotting a single half-life plot +#' Internal helper for plotting a single half-life plot +#' +#' Generates a single plotly for NCA half-life visualization, with a fit line and scatter points. #' #' @keywords internal #' @param fit_line_data Data frame for the fit line (must have columns for time and y) diff --git a/man/get_halflife_plots_single.Rd b/man/get_halflife_plots_single.Rd new file mode 100644 index 000000000..f2a41f39f --- /dev/null +++ b/man/get_halflife_plots_single.Rd @@ -0,0 +1,53 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/get_halflife_plots.R +\name{get_halflife_plots_single} +\alias{get_halflife_plots_single} +\title{Internal helper for plotting a single half-life plot} +\usage{ +get_halflife_plots_single( + fit_line_data, + plot_data, + time_col, + conc_col, + title, + xlab, + ylab, + subtitle_text, + color, + symbol, + group_vars, + add_annotations = TRUE, + text = NULL +) +} +\arguments{ +\item{fit_line_data}{Data frame for the fit line (must have columns for time and y)} + +\item{plot_data}{Data frame for the scatter points} + +\item{time_col}{Name of the time column (string)} + +\item{conc_col}{Name of the concentration column (string)} + +\item{title}{Plot title} + +\item{xlab}{X axis label} + +\item{ylab}{Y axis label} + +\item{subtitle_text}{Subtitle/annotation (HTML allowed)} + +\item{color}{Vector of colors for points (same length as plot_data)} + +\item{symbol}{Vector of marker symbols for points (same length as plot_data)} + +\item{group_vars}{Character vector of grouping variable names (for customdata)} + +\item{add_annotations}{Logical, whether to add the subtitle annotation} + +\item{text}{Optional vector of hover text for points (same length as plot_data)} +} +\description{ +Generates a single plotly for NCA half-life visualization, with a fit line and scatter points. +} +\keyword{internal} From e80529594999545fdc64fd6a04d13620c499ecee Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Tue, 16 Sep 2025 10:37:21 +0200 Subject: [PATCH 073/113] spelling: fix --- R/get_halflife_plots.R | 2 +- man/get_halflife_plots_single.Rd | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/R/get_halflife_plots.R b/R/get_halflife_plots.R index 8eb0bc6d1..c055606a9 100644 --- a/R/get_halflife_plots.R +++ b/R/get_halflife_plots.R @@ -187,7 +187,7 @@ get_halflife_plots <- function(pknca_data, add_annotations = TRUE) { #' @param subtitle_text Subtitle/annotation (HTML allowed) #' @param color Vector of colors for points (same length as plot_data) #' @param symbol Vector of marker symbols for points (same length as plot_data) -#' @param group_vars Character vector of grouping variable names (for customdata) +#' @param group_vars Character vector of grouping variable names (for `customdata`) #' @param add_annotations Logical, whether to add the subtitle annotation #' @param text Optional vector of hover text for points (same length as plot_data) get_halflife_plots_single <- function( diff --git a/man/get_halflife_plots_single.Rd b/man/get_halflife_plots_single.Rd index f2a41f39f..f911c07b9 100644 --- a/man/get_halflife_plots_single.Rd +++ b/man/get_halflife_plots_single.Rd @@ -41,7 +41,7 @@ get_halflife_plots_single( \item{symbol}{Vector of marker symbols for points (same length as plot_data)} -\item{group_vars}{Character vector of grouping variable names (for customdata)} +\item{group_vars}{Character vector of grouping variable names (for \code{customdata})} \item{add_annotations}{Logical, whether to add the subtitle annotation} From cb5e0429e56927156e6eb6794a90cf3dfff28449 Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Tue, 16 Sep 2025 15:24:50 +0200 Subject: [PATCH 074/113] fix: rm unused arg from detect_pknca_data_changes --- inst/shiny/modules/tab_nca/setup/slope_selector.R | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/inst/shiny/modules/tab_nca/setup/slope_selector.R b/inst/shiny/modules/tab_nca/setup/slope_selector.R index e4c132f51..6e1fe5f10 100644 --- a/inst/shiny/modules/tab_nca/setup/slope_selector.R +++ b/inst/shiny/modules/tab_nca/setup/slope_selector.R @@ -128,9 +128,7 @@ slope_selector_server <- function( # nolint unique() changes <- detect_pknca_data_changes( old = pknca_data(), - new = new_pknca_data, - excl_hl_col = new_pknca_data$conc$columns$exclude_half.life, - incl_hl_col = new_pknca_data$conc$columns$include_half.life + new = new_pknca_data ) if (changes$in_data) { From fee08b2a8d705334f802b505a6ddb395c83c181c Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Wed, 17 Sep 2025 10:42:24 +0200 Subject: [PATCH 075/113] fix: add-row + click crashes --- inst/shiny/modules/tab_nca/setup/handle_table_edits.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/inst/shiny/modules/tab_nca/setup/handle_table_edits.R b/inst/shiny/modules/tab_nca/setup/handle_table_edits.R index b4cf0825f..88d36e067 100644 --- a/inst/shiny/modules/tab_nca/setup/handle_table_edits.R +++ b/inst/shiny/modules/tab_nca/setup/handle_table_edits.R @@ -49,7 +49,7 @@ handle_table_edits_server <- function( slopes_pknca_groups <- reactive({ req(mydata()) mydata()$conc$data %>% - select(any_of(c(group_vars(mydata()), "NCA_PROFILE"))) + select(any_of(c(group_vars(mydata())))) }) # manual_slopes: stores the current table of user rules (inclusion/exclusion) From 0e6f9e85e7e844032e40ca148d64af87d600571f Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Wed, 17 Sep 2025 11:29:12 +0200 Subject: [PATCH 076/113] fix: slopes table choices need to update upon setup changes --- inst/shiny/modules/tab_nca/setup/handle_table_edits.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/inst/shiny/modules/tab_nca/setup/handle_table_edits.R b/inst/shiny/modules/tab_nca/setup/handle_table_edits.R index 88d36e067..93383fbe5 100644 --- a/inst/shiny/modules/tab_nca/setup/handle_table_edits.R +++ b/inst/shiny/modules/tab_nca/setup/handle_table_edits.R @@ -48,7 +48,7 @@ handle_table_edits_server <- function( # Get group columns for the current PKNCA data (for table structure) slopes_pknca_groups <- reactive({ req(mydata()) - mydata()$conc$data %>% + mydata()$intervals %>% select(any_of(c(group_vars(mydata())))) }) From c1f3812176e4b4ce58a974476248b0fac02e7d31 Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Wed, 17 Sep 2025 11:40:19 +0200 Subject: [PATCH 077/113] fix: add checks for exclusion-reasons before running NCA --- R/PKNCA.R | 46 ++++++++++++++++++++++++++++++++ inst/shiny/modules/tab_nca.R | 2 ++ man/checks_before_running_nca.Rd | 35 ++++++++++++++++++++++++ tests/testthat/test-PKNCA.R | 28 +++++++++++++++++++ 4 files changed, 111 insertions(+) create mode 100644 man/checks_before_running_nca.Rd diff --git a/R/PKNCA.R b/R/PKNCA.R index 099ccd990..15168e747 100644 --- a/R/PKNCA.R +++ b/R/PKNCA.R @@ -707,3 +707,49 @@ PKNCA_hl_rules_exclusion <- function(res, rules) { # nolint } res } + + +##' Checks Before Running NCA +##' +##' This function checks that: +##' 1) exclusions_have_reasons: all manually excluded half-life points in the concentration data +##' have a non-empty reason provided. If any exclusions are missing a reason, it stops with an error +##' and prints the affected rows (group columns and time column). +##' +##' @param processed_pknca_data A processed PKNCA data object. +##' @param exclusions_have_reasons Logical; if TRUE, check that all exclusions have a reason (default: TRUE). +##' +##' @return The processed_pknca_data object (input), if checks are successful. +##' +##' @details +##' - If any excluded half-life points are missing a reason, an error is thrown listing the affected rows. +##' - If no exclusions or all have reasons, the function returns the input object. +##' - Used to enforce good practice/documentation before NCA calculation. +##' +##' @examples +##' # Suppose processed_pknca_data is a valid PKNCA data object +##' # checks_before_running_nca(processed_pknca_data) +checks_before_running_nca <- function(processed_pknca_data, exclusions_have_reasons = TRUE) { + if (exclusions_have_reasons) { + data_conc <- processed_pknca_data$conc$data + excl_hl_col <- processed_pknca_data$conc$columns$exclude_half.life + conc_groups <- group_vars(processed_pknca_data$conc) + time_col <- processed_pknca_data$conc$columns$time + if (!is.null(excl_hl_col)) { + missing_reasons <- data_conc[[excl_hl_col]] & nchar(data_conc[["REASON"]]) == 0 + missing_reasons_rows <- data_conc[missing_reasons,] %>% + select(any_of(c(conc_groups, time_col))) + + if (nrow(missing_reasons_rows) > 0) { + stop( + "No reason provided for the following half-life exclusions:\n", + "\n", + paste(capture.output(print(missing_reasons_rows)), collapse = "\n"), + "\n", + "Please go to `Slope Selection` table and include it" + ) + } + } + } + processed_pknca_data +} diff --git a/inst/shiny/modules/tab_nca.R b/inst/shiny/modules/tab_nca.R index be44e9d74..e86e4d452 100644 --- a/inst/shiny/modules/tab_nca.R +++ b/inst/shiny/modules/tab_nca.R @@ -168,6 +168,8 @@ tab_nca_server <- function(id, adnca_data, grouping_vars) { #' Calculate results res <- withCallingHandlers({ processed_pknca_data %>% + # Check if there are exclusions that contains a filled reason + checks_before_running_nca() %>% # Perform PKNCA parameter calculations PKNCA_calculate_nca() %>% # Add bioavailability results if requested diff --git a/man/checks_before_running_nca.Rd b/man/checks_before_running_nca.Rd new file mode 100644 index 000000000..c984871ce --- /dev/null +++ b/man/checks_before_running_nca.Rd @@ -0,0 +1,35 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/PKNCA.R +\name{checks_before_running_nca} +\alias{checks_before_running_nca} +\title{Checks Before Running NCA} +\usage{ +checks_before_running_nca(processed_pknca_data, exclusions_have_reasons = TRUE) +} +\arguments{ +\item{processed_pknca_data}{A processed PKNCA data object.} + +\item{exclusions_have_reasons}{Logical; if TRUE, check that all exclusions have a reason (default: TRUE).} +} +\value{ +The processed_pknca_data object (input), if checks are successful. +} +\description{ +This function checks that: +\enumerate{ +\item exclusions_have_reasons: all manually excluded half-life points in the concentration data +have a non-empty reason provided. If any exclusions are missing a reason, it stops with an error +and prints the affected rows (group columns and time column). +} +} +\details{ +\itemize{ +\item If any excluded half-life points are missing a reason, an error is thrown listing the affected rows. +\item If no exclusions or all have reasons, the function returns the input object. +\item Used to enforce good practice/documentation before NCA calculation. +} +} +\examples{ +# Suppose processed_pknca_data is a valid PKNCA data object +# checks_before_running_nca(processed_pknca_data) +} diff --git a/tests/testthat/test-PKNCA.R b/tests/testthat/test-PKNCA.R index e1fc4d875..d8ccbc027 100644 --- a/tests/testthat/test-PKNCA.R +++ b/tests/testthat/test-PKNCA.R @@ -436,3 +436,31 @@ describe("select_level_grouping_cols", { expect_equal(result, data["d"]) }) }) + +describe("checks_before_running_nca", { + pknca_data <- FIXTURE_PKNCA_DATA + + it("returns the input object if no issues are found", { + # Without exclusions for half-life + result <- checks_before_running_nca(pknca_data) + expect_identical(result, pknca_data) + + # With exclusions for half-life (with REASON values) + excl_hl_col <- pknca_data$conc$columns$exclude_half.life + pknca_data$conc$data[1, excl_hl_col] <- TRUE + pknca_data$conc$data$REASON <- "Test reason" + result <- checks_before_running_nca(pknca_data) + expect_identical(result, pknca_data) + }) + + it("throws an error if exclusions for half-life do not include a REASON value", { + excl_hl_col <- pknca_data$conc$columns$exclude_half.life + pknca_data$conc$data[1, excl_hl_col] <- TRUE + pknca_data$conc$data$REASON <- "" + + expect_error( + checks_before_running_nca(pknca_data, exclusions_have_reasons = TRUE), + "No reason provided for the following half-life exclusions:" + ) + }) +}) \ No newline at end of file From e00c4e7eacb719c21786e2b246ef4de542fe4b55 Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Wed, 17 Sep 2025 13:56:46 +0200 Subject: [PATCH 078/113] refactor: lintr --- R/PKNCA.R | 6 +++--- man/checks_before_running_nca.Rd | 4 ++-- tests/testthat/test-PKNCA.R | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/R/PKNCA.R b/R/PKNCA.R index 15168e747..bd1143145 100644 --- a/R/PKNCA.R +++ b/R/PKNCA.R @@ -717,12 +717,12 @@ PKNCA_hl_rules_exclusion <- function(res, rules) { # nolint ##' and prints the affected rows (group columns and time column). ##' ##' @param processed_pknca_data A processed PKNCA data object. -##' @param exclusions_have_reasons Logical; if TRUE, check that all exclusions have a reason (default: TRUE). +##' @param exclusions_have_reasons Logical; Check that all exclusions have a reason (default: TRUE). ##' ##' @return The processed_pknca_data object (input), if checks are successful. ##' ##' @details -##' - If any excluded half-life points are missing a reason, an error is thrown listing the affected rows. +##' - If any excluded half-life points are missing a reason, an error is thrown. ##' - If no exclusions or all have reasons, the function returns the input object. ##' - Used to enforce good practice/documentation before NCA calculation. ##' @@ -737,7 +737,7 @@ checks_before_running_nca <- function(processed_pknca_data, exclusions_have_reas time_col <- processed_pknca_data$conc$columns$time if (!is.null(excl_hl_col)) { missing_reasons <- data_conc[[excl_hl_col]] & nchar(data_conc[["REASON"]]) == 0 - missing_reasons_rows <- data_conc[missing_reasons,] %>% + missing_reasons_rows <- data_conc[missing_reasons, ] %>% select(any_of(c(conc_groups, time_col))) if (nrow(missing_reasons_rows) > 0) { diff --git a/man/checks_before_running_nca.Rd b/man/checks_before_running_nca.Rd index c984871ce..06c298e7f 100644 --- a/man/checks_before_running_nca.Rd +++ b/man/checks_before_running_nca.Rd @@ -9,7 +9,7 @@ checks_before_running_nca(processed_pknca_data, exclusions_have_reasons = TRUE) \arguments{ \item{processed_pknca_data}{A processed PKNCA data object.} -\item{exclusions_have_reasons}{Logical; if TRUE, check that all exclusions have a reason (default: TRUE).} +\item{exclusions_have_reasons}{Logical; Check that all exclusions have a reason (default: TRUE).} } \value{ The processed_pknca_data object (input), if checks are successful. @@ -24,7 +24,7 @@ and prints the affected rows (group columns and time column). } \details{ \itemize{ -\item If any excluded half-life points are missing a reason, an error is thrown listing the affected rows. +\item If any excluded half-life points are missing a reason, an error is thrown. \item If no exclusions or all have reasons, the function returns the input object. \item Used to enforce good practice/documentation before NCA calculation. } diff --git a/tests/testthat/test-PKNCA.R b/tests/testthat/test-PKNCA.R index d8ccbc027..0e11a804c 100644 --- a/tests/testthat/test-PKNCA.R +++ b/tests/testthat/test-PKNCA.R @@ -463,4 +463,4 @@ describe("checks_before_running_nca", { "No reason provided for the following half-life exclusions:" ) }) -}) \ No newline at end of file +}) From 50ecaaf9dc50dbc883626b7a2c7202c4e4b887c6 Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Tue, 30 Sep 2025 17:00:50 +0200 Subject: [PATCH 079/113] draft: fix using tlast for plots limits --- R/get_halflife_plots.R | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/R/get_halflife_plots.R b/R/get_halflife_plots.R index c055606a9..68b59c267 100644 --- a/R/get_halflife_plots.R +++ b/R/get_halflife_plots.R @@ -48,7 +48,7 @@ get_halflife_plots <- function(pknca_data, add_annotations = TRUE) { wide_output$result <- wide_output$result %>% filter( PPTESTCD %in% c("lambda.z.time.first", "lambda.z.time.last", - "lambda.z", "adj.r.squared", "span.ratio") + "lambda.z", "adj.r.squared", "span.ratio", "tlast") ) %>% select(-any_of(c("PPORRESU", "PPSTRESU", "PPSTRES"))) wide_output <- as.data.frame(wide_output, out_format = "wide") %>% @@ -96,6 +96,7 @@ get_halflife_plots <- function(pknca_data, add_annotations = TRUE) { mutate( lambda.z.time.first = lambda.z.time.first + start, lambda.z.time.last = lambda.z.time.last + start, + tlast = tlast + start, is_halflife_used = .[[time_col]] >= lambda.z.time.first & .[[time_col]] <= lambda.z.time.last & !.[[exclude_hl_col]] @@ -108,7 +109,9 @@ get_halflife_plots <- function(pknca_data, add_annotations = TRUE) { is_halflife_used } ) %>% - ungroup() + ungroup() %>% + # Disconsider BLQ points at the end (never used for half-life) + filter(.[[time_col]] > tlast) info_per_plot_list <- info_per_plot_list %>% mutate( @@ -123,12 +126,12 @@ get_halflife_plots <- function(pknca_data, add_annotations = TRUE) { data_list <- list() for (i in seq_len(length(info_per_plot_list))) { df <- info_per_plot_list[[i]] - +if (df$USUBJID[1] == "XX01-11105") browser() # Create line data if (any(!is.na(df$is_halflife_used))) { df_fit <- df[df$is_halflife_used, ] fit <- stats::lm(as.formula(paste0("log10(", conc_col, ") ~ ", time_col)), df_fit) - fit_line_data <- data.frame(x = c(df$lambda.z.time.first[1], max(df[[time_col]]))) + fit_line_data <- data.frame(x = c(df$lambda.z.time.first[1], df$tlast[1])) colnames(fit_line_data) <- time_col fit_line_data$y <- stats::predict(fit, fit_line_data) } else { From 95b31073a5155bb9f519b23b6ff379c95545cefd Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Wed, 1 Oct 2025 10:19:23 +0200 Subject: [PATCH 080/113] fix: using tlast for plots limits --- R/get_halflife_plots.R | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/R/get_halflife_plots.R b/R/get_halflife_plots.R index 68b59c267..864c8053c 100644 --- a/R/get_halflife_plots.R +++ b/R/get_halflife_plots.R @@ -111,7 +111,7 @@ get_halflife_plots <- function(pknca_data, add_annotations = TRUE) { ) %>% ungroup() %>% # Disconsider BLQ points at the end (never used for half-life) - filter(.[[time_col]] > tlast) + filter(.[[time_col]] <= tlast) info_per_plot_list <- info_per_plot_list %>% mutate( @@ -126,7 +126,7 @@ get_halflife_plots <- function(pknca_data, add_annotations = TRUE) { data_list <- list() for (i in seq_len(length(info_per_plot_list))) { df <- info_per_plot_list[[i]] -if (df$USUBJID[1] == "XX01-11105") browser() + # Create line data if (any(!is.na(df$is_halflife_used))) { df_fit <- df[df$is_halflife_used, ] From a6c4bf9103449385f844e126a140315c32c64584 Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Wed, 1 Oct 2025 10:40:33 +0200 Subject: [PATCH 081/113] aesthetics: orderInput items in 1 line --- inst/shiny/modules/tab_nca/setup/slope_selector.R | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/inst/shiny/modules/tab_nca/setup/slope_selector.R b/inst/shiny/modules/tab_nca/setup/slope_selector.R index 6e1fe5f10..456ccd98a 100644 --- a/inst/shiny/modules/tab_nca/setup/slope_selector.R +++ b/inst/shiny/modules/tab_nca/setup/slope_selector.R @@ -95,7 +95,8 @@ slope_selector_ui <- function(id) { orderInput( ns("order_groups"), label = "Order plots by:", - items = NULL + items = NULL, + width = "100%" ) ), br(), From 4ecf25f47e4ad44a074560349268e0612d31c20d Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Mon, 6 Oct 2025 13:23:08 +0200 Subject: [PATCH 082/113] fix: warning in test (console) due to expected missing record --- tests/testthat/test-utils-slope_selector.R | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/tests/testthat/test-utils-slope_selector.R b/tests/testthat/test-utils-slope_selector.R index 6c6c74418..3ee41f077 100644 --- a/tests/testthat/test-utils-slope_selector.R +++ b/tests/testthat/test-utils-slope_selector.R @@ -176,7 +176,13 @@ describe("handle_hl_adj_change", { ) # Create plots using the original and updated PKNCA data - old_plots <- get_halflife_plots(old_data)$plots + old_plots <- withCallingHandlers( + get_halflife_plots(old_data)$plots, + # Because of the NA record there will be an expected warning + warning = function(w) { + if (grepl("Ignoring 1 observations", conditionMessage(w))) invokeRestart("muffleWarning") + } + ) new_plots <- handle_hl_adj_change(new_data, old_data, old_plots) # Check that the plots for other groups remain unchanged From e144f93d34b9e2349048fc042b16c249722a1027 Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Mon, 6 Oct 2025 13:31:47 +0200 Subject: [PATCH 083/113] fix: expect error test msg --- tests/testthat/test-utils-slope_selector.R | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/testthat/test-utils-slope_selector.R b/tests/testthat/test-utils-slope_selector.R index 3ee41f077..924cc997f 100644 --- a/tests/testthat/test-utils-slope_selector.R +++ b/tests/testthat/test-utils-slope_selector.R @@ -72,7 +72,7 @@ describe("check_slope_rule_overlap", { }) - it("should add new points of partial overlap is detected", { + it("should add new points if partial overlap is detected", { NEW <- data.frame( TYPE = "Exclusion", USUBJID = 1, @@ -281,12 +281,13 @@ describe("update_pknca_with_rules", { expect_true(all(old_have_points_na, new_have_points_incl, new_have_points_excl)) }) + it("returns an error for invalid rule types", { slopes_invalid <- cbind( data.frame(TYPE = "Invalid", ID = 1, RANGE = "2:4", REASON = "invalid type"), group1 ) - expect_error(update_pknca_with_rules(old, slopes_invalid)) + expect_error(update_pknca_with_rules(old_data, slopes_invalid), regexp = "Unknown TYPE in slopes: Invalid") }) }) From 20e100d7e2d43968def3406904027d7ab4c7c2d3 Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Mon, 6 Oct 2025 13:36:41 +0200 Subject: [PATCH 084/113] fix: zzz.R global var missing --- R/zzz.R | 1 + 1 file changed, 1 insertion(+) diff --git a/R/zzz.R b/R/zzz.R index 3a6637065..924b50979 100644 --- a/R/zzz.R +++ b/R/zzz.R @@ -165,6 +165,7 @@ "start_auc", "start_dose", "strata_cols_comb", + "tlast", "tooltip_text", "type", "type_interval", From 0d840d38039b450aa47847ebee3b8eb749048091 Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Mon, 6 Oct 2025 15:08:47 +0200 Subject: [PATCH 085/113] test: add exp error case for invalid TYPE --- tests/testthat/test-utils-slope_selector.R | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/testthat/test-utils-slope_selector.R b/tests/testthat/test-utils-slope_selector.R index 924cc997f..c1c449a04 100644 --- a/tests/testthat/test-utils-slope_selector.R +++ b/tests/testthat/test-utils-slope_selector.R @@ -287,7 +287,10 @@ describe("update_pknca_with_rules", { data.frame(TYPE = "Invalid", ID = 1, RANGE = "2:4", REASON = "invalid type"), group1 ) - expect_error(update_pknca_with_rules(old_data, slopes_invalid), regexp = "Unknown TYPE in slopes: Invalid") + expect_error( + update_pknca_with_rules(old_data, slopes_invalid), + regexp = "Unknown TYPE in slopes: Invalid" + ) }) }) From 03efeb3e342c34f38f550e81151d7c381ab72bf7 Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Mon, 6 Oct 2025 15:09:08 +0200 Subject: [PATCH 086/113] silence warnings in tests for exp warning --- tests/testthat/test-get_halflife_plots.R | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/tests/testthat/test-get_halflife_plots.R b/tests/testthat/test-get_halflife_plots.R index b37059c5f..be38410d8 100644 --- a/tests/testthat/test-get_halflife_plots.R +++ b/tests/testthat/test-get_halflife_plots.R @@ -3,7 +3,13 @@ base_pknca <- FIXTURE_PKNCA_DATA describe("get_halflife_plot", { it("returns a list of plotly objects with valid input", { - plots <- get_halflife_plots(base_pknca)[["plots"]] + plots <- withCallingHandlers( + get_halflife_plots(base_pknca)[["plots"]], + # Ignore the warning associated with the expected missing records + warning = function(w) { + if (grepl("Ignoring 1 observations", conditionMessage(w))) invokeRestart("muffleWarning") + } + ) expect_type(plots, "list") expect_true(length(plots) >= 1) expect_s3_class(plots[[1]], "plotly") @@ -22,7 +28,13 @@ describe("get_halflife_plot", { pknca_no_excl_incl <- base_pknca pknca_no_excl_incl$conc$data$exclude_half.life <- FALSE pknca_no_excl_incl$conc$data$include_half.life <- FALSE - plots <- get_halflife_plots(pknca_no_excl_incl)[["plots"]] + plots <- withCallingHandlers( + get_halflife_plots(pknca_no_excl_incl)[["plots"]], + # Ignore the warning associated with the expected missing records + warning = function(w) { + if (grepl("Ignoring 1 observations", conditionMessage(w))) invokeRestart("muffleWarning") + } + ) expect_true(length(plots) >= 1) plot_data <- plots[[1]]$x$data[[2]] expect_true(all(plot_data$marker$color == "black")) @@ -115,5 +127,4 @@ describe("get_halflife_plot", { ) expect_equal(plots_details, exp_plots_details, ignore_attr = TRUE) }) - }) From fe17c2559589943573f9f80911074aedfa16d99a Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Mon, 6 Oct 2025 15:09:36 +0200 Subject: [PATCH 087/113] docs: change docs so { } do not produce issues --- R/PKNCA.R | 4 ++-- man/PKNCA_update_data_object.Rd | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/R/PKNCA.R b/R/PKNCA.R index bd1143145..302f28ef9 100644 --- a/R/PKNCA.R +++ b/R/PKNCA.R @@ -210,8 +210,8 @@ PKNCA_create_data_object <- function(adnca_data) { # nolint: object_name_linter #' @param params A list of parameters for NCA calculation #' @param should_impute_c0 Logical indicating if start values should be imputed #' @param hl_adj_rules A data frame containing half-life adjustment rules. It must -#' contain group columns and rule specification columns, -#' (TYPE: {Inclusion, Exclusion}, RANGE: {start-end}). +#' contain group columns and rule specification columns; +#' TYPE: (Inclusion, Exclusion), RANGE: (start-end). #' #' @returns A fully configured `PKNCAdata` object. #' diff --git a/man/PKNCA_update_data_object.Rd b/man/PKNCA_update_data_object.Rd index 89ccf14ce..8dadc6af2 100644 --- a/man/PKNCA_update_data_object.Rd +++ b/man/PKNCA_update_data_object.Rd @@ -34,8 +34,8 @@ PKNCA_update_data_object( \item{should_impute_c0}{Logical indicating if start values should be imputed} \item{hl_adj_rules}{A data frame containing half-life adjustment rules. It must -contain group columns and rule specification columns, -(TYPE: {Inclusion, Exclusion}, RANGE: {start-end}).} +contain group columns and rule specification columns; +TYPE: (Inclusion, Exclusion), RANGE: (start-end).} } \value{ A fully configured \code{PKNCAdata} object. From a4f826bd07ee8fd69cbfffd48ea91ee264aab7a6 Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Wed, 22 Oct 2025 10:53:06 +0200 Subject: [PATCH 088/113] change plotid names to allow HTML correct download --- R/get_halflife_plots.R | 4 ++-- R/utils-slope_selector.R | 4 ++-- inst/shiny/modules/tab_nca/nca_results.R | 1 - inst/shiny/modules/tab_nca/setup/page_and_searcher.R | 2 +- 4 files changed, 5 insertions(+), 6 deletions(-) diff --git a/R/get_halflife_plots.R b/R/get_halflife_plots.R index 864c8053c..c0b7c8516 100644 --- a/R/get_halflife_plots.R +++ b/R/get_halflife_plots.R @@ -146,8 +146,8 @@ get_halflife_plots <- function(pknca_data, add_annotations = TRUE) { plotid_vars <- c(group_vars(pknca_data), "start", "end") plotid <- paste0( paste0( - plotid_vars, ": ", df[1, plotid_vars, drop = FALSE], - collapse = ", " + plotid_vars, "=", df[1, plotid_vars, drop = FALSE], + collapse = "_" ) ) diff --git a/R/utils-slope_selector.R b/R/utils-slope_selector.R index bc9673393..cf910ba38 100644 --- a/R/utils-slope_selector.R +++ b/R/utils-slope_selector.R @@ -135,8 +135,8 @@ handle_interval_change <- function(new_pknca_data, old_pknca_data, plot_outputs) parse_plot_names_to_df <- function(named_list) { plot_names <- names(named_list) parsed <- lapply(plot_names, function(x) { - pairs <- strsplit(x, ",\\s*")[[1]] - kv <- strsplit(pairs, ":\\s*") + pairs <- strsplit(x, "_\\s*")[[1]] + kv <- strsplit(pairs, "=\\s*") setNames( vapply(kv, function(y) y[2], character(1)), vapply(kv, function(y) y[1], character(1)) diff --git a/inst/shiny/modules/tab_nca/nca_results.R b/inst/shiny/modules/tab_nca/nca_results.R index b6f76a1c8..743d7b4da 100644 --- a/inst/shiny/modules/tab_nca/nca_results.R +++ b/inst/shiny/modules/tab_nca/nca_results.R @@ -105,7 +105,6 @@ nca_results_server <- function(id, pknca_data, res_nca, settings, ratio_table, g }, content = function(fname) { shiny::withProgress(message = "Preparing ZIP file...", value = 0, { - # Create an output folder with all plots, tables and listings output_tmpdir <- file.path(tempdir(), "output") save_output(output = session$userData$results, output_path = output_tmpdir) diff --git a/inst/shiny/modules/tab_nca/setup/page_and_searcher.R b/inst/shiny/modules/tab_nca/setup/page_and_searcher.R index d0eb469f6..53a79c0bb 100644 --- a/inst/shiny/modules/tab_nca/setup/page_and_searcher.R +++ b/inst/shiny/modules/tab_nca/setup/page_and_searcher.R @@ -51,7 +51,7 @@ page_and_searcher_server <- function(id, search_subject, plot_outputs, plots_per rep(TRUE, length(names(plot_outputs()))) } else { grepl( - paste0("USUBJID: (", paste0(search_val, collapse = ")|("), ")"), + paste0("USUBJID=(", paste0(search_val, collapse = ")|("), ")"), names(plot_outputs()) ) } From 85e31edec4d93957436d801cf1fc5ab3223272a6 Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Wed, 22 Oct 2025 10:53:30 +0200 Subject: [PATCH 089/113] include manual_slopes rules in session$userData --- inst/shiny/modules/tab_nca/setup/slope_selector.R | 3 +++ 1 file changed, 3 insertions(+) diff --git a/inst/shiny/modules/tab_nca/setup/slope_selector.R b/inst/shiny/modules/tab_nca/setup/slope_selector.R index 456ccd98a..4d6755136 100644 --- a/inst/shiny/modules/tab_nca/setup/slope_selector.R +++ b/inst/shiny/modules/tab_nca/setup/slope_selector.R @@ -235,6 +235,9 @@ slope_selector_server <- function( # nolint outputId = "manual_slopes", data = manual_slopes() ) + + # Load it to the session objects + session$userData$slope_rules <- manual_slopes() }) #' returns half life adjustments rules to update processed_pknca_data in setup.R list( From 8b397385f63778550e88de13638fba3fca0d481b Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Wed, 22 Oct 2025 14:11:25 +0200 Subject: [PATCH 090/113] fix: tlast exclude homogenized with others --- R/get_halflife_plots.R | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/R/get_halflife_plots.R b/R/get_halflife_plots.R index 864c8053c..271266733 100644 --- a/R/get_halflife_plots.R +++ b/R/get_halflife_plots.R @@ -50,7 +50,9 @@ get_halflife_plots <- function(pknca_data, add_annotations = TRUE) { PPTESTCD %in% c("lambda.z.time.first", "lambda.z.time.last", "lambda.z", "adj.r.squared", "span.ratio", "tlast") ) %>% - select(-any_of(c("PPORRESU", "PPSTRESU", "PPSTRES"))) + select(-any_of(c("PPORRESU", "PPSTRESU", "PPSTRES"))) %>% + mutate(exclude = paste0(na.omit(unique(exclude)), collapse = ". ")) + wide_output <- as.data.frame(wide_output, out_format = "wide") %>% unique() @@ -208,6 +210,7 @@ get_halflife_plots_single <- function( add_annotations = TRUE, text = NULL ) { + # if (is.na(subtitle_text == "New text")) browser() if (is.null(text)) { text <- paste0( "Data Point: ", seq_len(nrow(plot_data)), "\n(", From 1ef15803fe1d72dba288ddf9c008f23e810e69eb Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Wed, 22 Oct 2025 14:24:11 +0200 Subject: [PATCH 091/113] rm commented code & roxygenise --- DESCRIPTION | 2 +- R/get_halflife_plots.R | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index d00a8d25d..1ba68af5b 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -37,7 +37,7 @@ Imports: utils Encoding: UTF-8 Roxygen: list(markdown = TRUE) -RoxygenNote: 7.3.2 +RoxygenNote: 7.3.3 Suggests: arrow, ggh4x, diff --git a/R/get_halflife_plots.R b/R/get_halflife_plots.R index 271266733..fbdc359e2 100644 --- a/R/get_halflife_plots.R +++ b/R/get_halflife_plots.R @@ -210,7 +210,6 @@ get_halflife_plots_single <- function( add_annotations = TRUE, text = NULL ) { - # if (is.na(subtitle_text == "New text")) browser() if (is.null(text)) { text <- paste0( "Data Point: ", seq_len(nrow(plot_data)), "\n(", From d25281c49816dc6d432b7969bd2b901dbe35a2d4 Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Thu, 23 Oct 2025 09:04:44 +0200 Subject: [PATCH 092/113] test: adapt test to new plot ID --- tests/testthat/test-utils-slope_selector.R | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/testthat/test-utils-slope_selector.R b/tests/testthat/test-utils-slope_selector.R index c1c449a04..7579da4f1 100644 --- a/tests/testthat/test-utils-slope_selector.R +++ b/tests/testthat/test-utils-slope_selector.R @@ -237,14 +237,14 @@ describe("handle_interval_change", { describe("arrange_plots_by_groups", { it("orders plots by specified group columns", { named_list <- list( - "B: 2, A: 1" = "plot1", - "B: 1, A: 2" = "plot2", - "B: 1, A: 1" = "plot3" + "B=2_A=1" = "plot1", + "B=1_A=2" = "plot2", + "B=1_A=1" = "plot3" ) ordered <- arrange_plots_by_groups(named_list, c("B", "A")) - expect_equal(names(ordered), c("B: 1, A: 1", "B: 1, A: 2", "B: 2, A: 1")) + expect_equal(names(ordered), c("B=1_A=1", "B=1_A=2", "B=2_A=1")) ordered2 <- arrange_plots_by_groups(named_list, c("A", "B")) - expect_equal(names(ordered2), c("B: 1, A: 1", "B: 2, A: 1", "B: 1, A: 2")) + expect_equal(names(ordered2), c("B=1_A=1", "B=2_A=1", "B=1_A=2")) }) }) From 82f38d7a8edb7a75eaf40d75f12576c98dbb0dba Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Thu, 23 Oct 2025 09:07:32 +0200 Subject: [PATCH 093/113] fix: new plot ID needs to be considered in handle_interval_change --- R/utils-slope_selector.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/utils-slope_selector.R b/R/utils-slope_selector.R index cf910ba38..416e07a64 100644 --- a/R/utils-slope_selector.R +++ b/R/utils-slope_selector.R @@ -115,7 +115,7 @@ handle_interval_change <- function(new_pknca_data, old_pknca_data, plot_outputs) ., function(...) { vals <- list(...) - paste0(names(vals), ": ", vals, collapse = ", ") + paste0(names(vals), "=", vals, collapse = "_") } )) %>% pull(id) From be119573c045c060415b62e9f08a3b9df73ddf08 Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Wed, 12 Nov 2025 14:43:35 +0100 Subject: [PATCH 094/113] use for additional_analysis_server the raw pknca_data --- inst/shiny/modules/tab_nca.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/inst/shiny/modules/tab_nca.R b/inst/shiny/modules/tab_nca.R index 66cc0d345..0c4e66e1d 100644 --- a/inst/shiny/modules/tab_nca.R +++ b/inst/shiny/modules/tab_nca.R @@ -266,7 +266,7 @@ tab_nca_server <- function(id, adnca_data, grouping_vars) { descriptive_statistics_server("descriptive_stats", res_nca, grouping_vars) #' Additional analysis module - additional_analysis_server("non_nca", processed_pknca_data, grouping_vars) + additional_analysis_server("non_nca", pknca_data, grouping_vars) #' Parameter datasets module parameter_datasets_server("parameter_datasets", res_nca) From 62788c9119ddfc90d9abbdda5e1dec318180f6e6 Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Tue, 25 Nov 2025 12:27:23 +0100 Subject: [PATCH 095/113] fix: pkcg01 example typpo --- NAMESPACE | 2 +- R/g_pkcg.R | 6 +++--- man/pkcg01.Rd | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/NAMESPACE b/NAMESPACE index e2cda7c06..0d8c2877e 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -98,13 +98,13 @@ importFrom(ggplot2,scale_x_continuous) importFrom(grid,convertUnit) importFrom(magrittr,`%>%`) importFrom(methods,is) -importFrom(plotly,add_lines) importFrom(officer,add_slide) importFrom(officer,move_slide) importFrom(officer,ph_location_type) importFrom(officer,ph_slidelink) importFrom(officer,ph_with) importFrom(officer,read_pptx) +importFrom(plotly,add_lines) importFrom(plotly,add_trace) importFrom(plotly,ggplotly) importFrom(plotly,layout) diff --git a/R/g_pkcg.R b/R/g_pkcg.R index 1ff032130..79e5e495c 100644 --- a/R/g_pkcg.R +++ b/R/g_pkcg.R @@ -56,9 +56,9 @@ g_pkcg01_log <- function(data, ...) { #' attr(adpc[["AFRLT"]], "label") <- "Actual time from first dose" #' attr(adpc[["AVAL"]], "label") <- "Analysis val" #' -#' plots_lin <- pckg01(adpc = adpc, xmax = 1) -#' plots_log <- pckg01(adpc = adpc, color_var = "USUBJID", scale = "LOG") -#' plots_sbs <- pckg01( +#' plots_lin <- pkcg01(adpc = adpc, xmax = 1) +#' plots_log <- pkcg01(adpc = adpc, color_var = "USUBJID", scale = "LOG") +#' plots_sbs <- pkcg01( #' adpc = adpc, #' color_var = "USUBJID", #' xbreaks_var = "NFRLT", diff --git a/man/pkcg01.Rd b/man/pkcg01.Rd index bd30e121f..26948fb45 100644 --- a/man/pkcg01.Rd +++ b/man/pkcg01.Rd @@ -93,9 +93,9 @@ This function generates a list of ggplots for PK concentration-time profiles. attr(adpc[["AFRLT"]], "label") <- "Actual time from first dose" attr(adpc[["AVAL"]], "label") <- "Analysis val" - plots_lin <- pckg01(adpc = adpc, xmax = 1) - plots_log <- pckg01(adpc = adpc, color_var = "USUBJID", scale = "LOG") - plots_sbs <- pckg01( + plots_lin <- pkcg01(adpc = adpc, xmax = 1) + plots_log <- pkcg01(adpc = adpc, color_var = "USUBJID", scale = "LOG") + plots_sbs <- pkcg01( adpc = adpc, color_var = "USUBJID", xbreaks_var = "NFRLT", From ea6159ac0803088444c78728a183d1ffcbeef72c Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Fri, 2 Jan 2026 11:49:35 +0100 Subject: [PATCH 096/113] disconsider middle BLQ points in half life plots --- R/get_halflife_plots.R | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/R/get_halflife_plots.R b/R/get_halflife_plots.R index 5ce33d29a..d45463076 100644 --- a/R/get_halflife_plots.R +++ b/R/get_halflife_plots.R @@ -113,7 +113,9 @@ get_halflife_plots <- function(pknca_data, add_annotations = TRUE) { ) %>% ungroup() %>% # Disconsider BLQ points at the end (never used for half-life) - filter(.[[time_col]] <= tlast) + filter(.[[time_col]] <= tlast) %>% + # Disconsider BLQ points at the middle as well + filter(.[[conc_col]] > 0) info_per_plot_list <- info_per_plot_list %>% mutate( From de6870081e3c4b5a71ddc3c24834944e91ad23f8 Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Mon, 5 Jan 2026 16:33:03 +0100 Subject: [PATCH 097/113] general style, nitpicks & issues: apply refactor suggestions 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_halflife_plots.R | 15 +++++----- .../tab_nca/setup/handle_table_edits.R | 30 +++++++++---------- .../modules/tab_nca/setup/slope_selector.R | 14 ++------- 3 files changed, 26 insertions(+), 33 deletions(-) diff --git a/R/get_halflife_plots.R b/R/get_halflife_plots.R index d45463076..6f474b911 100644 --- a/R/get_halflife_plots.R +++ b/R/get_halflife_plots.R @@ -168,7 +168,7 @@ get_halflife_plots <- function(pknca_data, add_annotations = TRUE) { ), xlab = df$xlab[1], ylab = df$ylab[1], - subtitle_text = df$subtitle[1], + subtitle = df$subtitle[1], color = df$color, symbol = df$symbol, group_vars = group_vars(pknca_data), @@ -176,7 +176,7 @@ get_halflife_plots <- function(pknca_data, add_annotations = TRUE) { ) data_list[[plotid]] <- df } - return(list(plots = plot_list, data = data_list)) + list(plots = plot_list, data = data_list) } #' Internal helper for plotting a single half-life plot @@ -191,24 +191,25 @@ get_halflife_plots <- function(pknca_data, add_annotations = TRUE) { #' @param title Plot title #' @param xlab X axis label #' @param ylab Y axis label -#' @param subtitle_text Subtitle/annotation (HTML allowed) +#' @param subtitle Subtitle/annotation (HTML allowed) #' @param color Vector of colors for points (same length as plot_data) #' @param symbol Vector of marker symbols for points (same length as plot_data) #' @param group_vars Character vector of grouping variable names (for `customdata`) #' @param add_annotations Logical, whether to add the subtitle annotation #' @param text Optional vector of hover text for points (same length as plot_data) +#' @returns A plotly object representing the data points (plot_data) get_halflife_plots_single <- function( - fit_line_data, plot_data, + fit_line_data, time_col, conc_col, + group_vars, title, + subtitle, xlab, ylab, - subtitle_text, color, symbol, - group_vars, add_annotations = TRUE, text = NULL ) { @@ -245,7 +246,7 @@ get_halflife_plots_single <- function( zeroline = FALSE ), annotations = list( - text = subtitle_text, + text = subtitle, showarrow = FALSE, xref = "paper", yref = "paper", diff --git a/inst/shiny/modules/tab_nca/setup/handle_table_edits.R b/inst/shiny/modules/tab_nca/setup/handle_table_edits.R index eff17bb5a..5598493ce 100644 --- a/inst/shiny/modules/tab_nca/setup/handle_table_edits.R +++ b/inst/shiny/modules/tab_nca/setup/handle_table_edits.R @@ -34,30 +34,30 @@ handle_table_edits_ui <- function(id) { #' Handles adding/removing/editing rules, table's reactivity, and optional override logic. #' #' @param id Shiny module id -#' @param mydata Reactive providing the current PKNCA data object +#' @param pknca_data Reactive providing the current PKNCA data object #' @param manual_slopes_override Optional reactive providing a table to override manual slopes #' @return List with: #' - manual_slopes: reactiveVal containing the current manual slopes table #' - refresh_reactable: reactiveVal for triggering table re-render handle_table_edits_server <- function( - id, mydata, manual_slopes_override = NULL + id, pknca_data, manual_slopes_override = NULL ) { moduleServer(id, function(input, output, session) { ns <- session$ns # Get group columns for the current PKNCA data (for table structure) slopes_pknca_groups <- reactive({ - req(mydata()) - mydata()$intervals %>% - select(any_of(c(group_vars(mydata())))) + req(pknca_data()) + pknca_data()$intervals %>% + select(any_of(c(group_vars(pknca_data())))) }) # manual_slopes: stores the current table of user rules (inclusion/exclusion) manual_slopes <- reactiveVal(NULL) - # When mydata() changes, reset the manual_slopes table to empty with correct columns - observeEvent(mydata(), { - req(is.null(manual_slopes())) - req(slopes_pknca_groups()) + # When pknca_data() changes, reset the manual_slopes table to empty with correct columns + observeEvent(pknca_data(), { + req(is.null(manual_slopes()), slopes_pknca_groups()) + ms_colnames <- c(colnames(slopes_pknca_groups()), c("TYPE", "RANGE", "REASON")) initial_manual_slopes <- data.frame( matrix( @@ -70,19 +70,20 @@ handle_table_edits_server <- function( manual_slopes(initial_manual_slopes) }) + # create a reactive to update the reactable UI when the table changes + refresh_reactable <- reactiveVal(0) + # Add a new row to the table when the user clicks the add button observeEvent(input$add_rule, { log_trace("{id}: adding manual slopes row") first_group <- slopes_pknca_groups()[1, ] + time_col <- pknca_data()$conc$columns$time new_row <- cbind( first_group, data.frame( TYPE = "Exclusion", RANGE = paste0( - inner_join( - slopes_pknca_groups()[1, ], - mydata()$conc$data - )[[mydata()$conc$columns$time]][2] + inner_join(first_group, pknca_data()$conc$data)[[time_col]][2] ), REASON = "" ) @@ -108,7 +109,6 @@ handle_table_edits_server <- function( }) # Render the manual slopes table (reactable) - refresh_reactable <- reactiveVal(1) output$manual_slopes <- renderReactable({ req(manual_slopes()) log_trace("{id}: rendering slope edit data table") @@ -187,7 +187,7 @@ handle_table_edits_server <- function( log_debug_list("Manual slopes override:", manual_slopes_override()) override_valid <- apply(manual_slopes_override(), 1, function(r) { dplyr::filter( - mydata()$conc$data, + pknca_data()$conc$data, PCSPEC == r["PCSPEC"], USUBJID == r["USUBJID"], PARAM == r["PARAM"], diff --git a/inst/shiny/modules/tab_nca/setup/slope_selector.R b/inst/shiny/modules/tab_nca/setup/slope_selector.R index 4d6755136..fbc1990f5 100644 --- a/inst/shiny/modules/tab_nca/setup/slope_selector.R +++ b/inst/shiny/modules/tab_nca/setup/slope_selector.R @@ -166,24 +166,16 @@ slope_selector_server <- function( # nolint pknca_data(new_pknca_data) }) - # HACK: workaround to avoid plotly_click not being registered warning - session$userData$plotlyShinyEventIDs <- "plotly_click-A" - - # --- Pagination and search logic --- - search_subject_r <- reactive(input$search_subject) - plots_per_page_r <- reactive(input$plots_per_page) - # Call the pagination/searcher module to: # - Providing indices of plots for the selected subject(s) # - Providing indices for which plots to display based on pagination page_search <- page_and_searcher_server( id = "page_and_searcher", - search_subject = search_subject_r, + search_subject = reactive(input$search_subject), plot_outputs = plot_outputs, - plots_per_page = plots_per_page_r + plots_per_page = reactive(input$plots_per_page) ) - - # Render only the plots requested by the user + observe({ req(plot_outputs()) output$slope_plots_ui <- renderUI({ From 47a59de7122c16da0a2c7d331f9be928b6d1b994 Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Tue, 6 Jan 2026 10:52:08 +0100 Subject: [PATCH 098/113] apply suggested changes 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> --- NAMESPACE | 1 - R/PKNCA.R | 50 +++++++++--------- R/get_halflife_plots.R | 7 ++- inst/shiny/modules/tab_nca.R | 2 +- .../modules/tab_nca/setup/page_and_searcher.R | 2 +- .../modules/tab_nca/setup/slope_selector.R | 10 ++-- .../{handle_table_edits.R => slopes_table.R} | 4 +- inst/shiny/o_nca | Bin 366470 -> 0 bytes ...nning_nca.Rd => check_valid_pknca_data.Rd} | 8 +-- man/get_halflife_plots_single.Rd | 21 ++++---- tests/testthat/test-PKNCA.R | 28 +++++----- tests/testthat/test-get_halflife_plots.R | 8 +-- 12 files changed, 73 insertions(+), 68 deletions(-) rename inst/shiny/modules/tab_nca/setup/{handle_table_edits.R => slopes_table.R} (98%) delete mode 100644 inst/shiny/o_nca rename man/{checks_before_running_nca.Rd => check_valid_pknca_data.Rd} (83%) diff --git a/NAMESPACE b/NAMESPACE index 3b0d5dc49..0601cec0b 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -114,7 +114,6 @@ importFrom(officer,ph_with) importFrom(officer,read_pptx) importFrom(plotly,add_lines) importFrom(plotly,add_trace) -importFrom(plotly,ggplotly) importFrom(plotly,layout) importFrom(plotly,plot_ly) importFrom(plotly,plotly_build) diff --git a/R/PKNCA.R b/R/PKNCA.R index 70abb7750..3ace901e8 100644 --- a/R/PKNCA.R +++ b/R/PKNCA.R @@ -653,33 +653,35 @@ PKNCA_hl_rules_exclusion <- function(res, rules) { # nolint } -##' Checks Before Running NCA -##' -##' This function checks that: -##' 1) exclusions_have_reasons: all manually excluded half-life points in the concentration data -##' have a non-empty reason provided. If any exclusions are missing a reason, it stops with an error -##' and prints the affected rows (group columns and time column). -##' -##' @param processed_pknca_data A processed PKNCA data object. -##' @param exclusions_have_reasons Logical; Check that all exclusions have a reason (default: TRUE). -##' -##' @return The processed_pknca_data object (input), if checks are successful. -##' -##' @details -##' - If any excluded half-life points are missing a reason, an error is thrown. -##' - If no exclusions or all have reasons, the function returns the input object. -##' - Used to enforce good practice/documentation before NCA calculation. -##' -##' @examples -##' # Suppose processed_pknca_data is a valid PKNCA data object -##' # checks_before_running_nca(processed_pknca_data) -checks_before_running_nca <- function(processed_pknca_data, exclusions_have_reasons = TRUE) { +#' Checks Before Running NCA +#' +#' This function checks that: +#' 1) exclusions_have_reasons: all manually excluded half-life points in the concentration data +#' have a non-empty reason provided. If any exclusions are missing a reason, it stops with an error +#' and prints the affected rows (group columns and time column). +#' +#' @param processed_pknca_data A processed PKNCA data object. +#' @param exclusions_have_reasons Logical; Check that all exclusions have a reason (default: TRUE). +#' +#' @return The processed_pknca_data object (input), if checks are successful. +#' +#' @details +#' - If any excluded half-life points are missing a reason, an error is thrown. +#' - If no exclusions or all have reasons, the function returns the input object. +#' - Used to enforce good practice/documentation before NCA calculation. +#' +#' @examples +#' # Suppose processed_pknca_data is a valid PKNCA data object +#' # check_valid_pknca_data(processed_pknca_data) +check_valid_pknca_data <- function(processed_pknca_data, exclusions_have_reasons = TRUE) { if (exclusions_have_reasons) { - data_conc <- processed_pknca_data$conc$data excl_hl_col <- processed_pknca_data$conc$columns$exclude_half.life - conc_groups <- group_vars(processed_pknca_data$conc) - time_col <- processed_pknca_data$conc$columns$time + if (!is.null(excl_hl_col)) { + data_conc <- processed_pknca_data$conc$data + conc_groups <- group_vars(processed_pknca_data$conc) + time_col <- processed_pknca_data$conc$columns$time + missing_reasons <- data_conc[[excl_hl_col]] & nchar(data_conc[["REASON"]]) == 0 missing_reasons_rows <- data_conc[missing_reasons, ] %>% select(any_of(c(conc_groups, time_col))) diff --git a/R/get_halflife_plots.R b/R/get_halflife_plots.R index 6f474b911..444b17bbe 100644 --- a/R/get_halflife_plots.R +++ b/R/get_halflife_plots.R @@ -197,7 +197,7 @@ get_halflife_plots <- function(pknca_data, add_annotations = TRUE) { #' @param group_vars Character vector of grouping variable names (for `customdata`) #' @param add_annotations Logical, whether to add the subtitle annotation #' @param text Optional vector of hover text for points (same length as plot_data) -#' @returns A plotly object representing the data points (plot_data) +#' @returns A plotly object representing the scatter points (plot_data) get_halflife_plots_single <- function( plot_data, fit_line_data, @@ -264,9 +264,8 @@ get_halflife_plots_single <- function( mode = "markers", marker = list( color = color, - size = 15, - symbol = symbol, - size = 20 + size = 20, + symbol = symbol ), customdata = apply( plot_data[, c(group_vars, "ROWID"), drop = FALSE], diff --git a/inst/shiny/modules/tab_nca.R b/inst/shiny/modules/tab_nca.R index 54d3b25d6..c2ba48c8f 100644 --- a/inst/shiny/modules/tab_nca.R +++ b/inst/shiny/modules/tab_nca.R @@ -127,7 +127,7 @@ tab_nca_server <- function(id, pknca_data, extra_group_vars) { res <- withCallingHandlers({ processed_pknca_data %>% # Check if there are exclusions that contains a filled reason - checks_before_running_nca() %>% + check_valid_pknca_data() %>% # Perform PKNCA parameter calculations PKNCA_calculate_nca() %>% # Add bioavailability results if requested diff --git a/inst/shiny/modules/tab_nca/setup/page_and_searcher.R b/inst/shiny/modules/tab_nca/setup/page_and_searcher.R index 53a79c0bb..752948fe5 100644 --- a/inst/shiny/modules/tab_nca/setup/page_and_searcher.R +++ b/inst/shiny/modules/tab_nca/setup/page_and_searcher.R @@ -2,7 +2,7 @@ #' #' This UI module provides the page navigation controls (previous, next, selector, number). #' The search_subject input remains outside for now in the parent (slope_selector.R) -page_and_searcher_page_ui <- function(id) { +page_and_searcher_ui <- function(id) { ns <- NS(id) fluidRow( class = "plot-widgets-container-2", diff --git a/inst/shiny/modules/tab_nca/setup/slope_selector.R b/inst/shiny/modules/tab_nca/setup/slope_selector.R index fbc1990f5..a92cf6ac5 100644 --- a/inst/shiny/modules/tab_nca/setup/slope_selector.R +++ b/inst/shiny/modules/tab_nca/setup/slope_selector.R @@ -10,7 +10,7 @@ #' └─> slope_selector_server #' ├──> plot_outputs (reactive, updated by processed_pknca_data changes) #' └──> ├> handle_plotly_click (updates manual_slopes on plot click) -#' â””> handle_table_edits (updates manual_slopes on user edits & setting overrides) +#' â””> slopes_table (updates manual_slopes on user edits & setting overrides) #' └─> manual_slopes (output, used by parent to update processed_pknca_data) #' #' @@ -21,7 +21,7 @@ #' #' @details #' - The module's main output is the manual_slopes table, which is updated by user -#' edits in the table UI (handle_table_edits) or by plot clicking (handle_plotly_click). +#' edits in the table UI (slopes_table) or by plot clicking (handle_plotly_click). #' - The parent module (setup.R) uses manual_slopes to update processed_pknca_data, #' which is then fed back in this module to update plots. @@ -31,7 +31,7 @@ slope_selector_ui <- function(id) { div( class = "slope-selector-module", - handle_table_edits_ui(ns("manual_slopes")), + slopes_table_ui(ns("manual_slopes")), # Help widget # dropdown( div( @@ -104,7 +104,7 @@ slope_selector_ui <- function(id) { uiOutput(ns("slope_plots_ui"), class = "slope-plots-container"), br(), # Use the new pagination UI module - page_and_searcher_page_ui(ns("page_and_searcher")), + page_and_searcher_ui(ns("page_and_searcher")), br() ) } @@ -192,7 +192,7 @@ slope_selector_server <- function( # nolint # Creates an initial version of the manual slope adjustments table with pknca_data # and handles the addition and deletion of rows through the UI - slopes_table <- handle_table_edits_server("manual_slopes", pknca_data, manual_slopes_override) + slopes_table <- slopes_table_server("manual_slopes", pknca_data, manual_slopes_override) manual_slopes <- slopes_table$manual_slopes refresh_reactable <- slopes_table$refresh_reactable diff --git a/inst/shiny/modules/tab_nca/setup/handle_table_edits.R b/inst/shiny/modules/tab_nca/setup/slopes_table.R similarity index 98% rename from inst/shiny/modules/tab_nca/setup/handle_table_edits.R rename to inst/shiny/modules/tab_nca/setup/slopes_table.R index 5598493ce..a1fc1cfd6 100644 --- a/inst/shiny/modules/tab_nca/setup/handle_table_edits.R +++ b/inst/shiny/modules/tab_nca/setup/slopes_table.R @@ -6,7 +6,7 @@ #' #' @param id Shiny module id #' @return Shiny UI element (fluidRow) -handle_table_edits_ui <- function(id) { +slopes_table_ui <- function(id) { ns <- NS(id) fluidRow( @@ -39,7 +39,7 @@ handle_table_edits_ui <- function(id) { #' @return List with: #' - manual_slopes: reactiveVal containing the current manual slopes table #' - refresh_reactable: reactiveVal for triggering table re-render -handle_table_edits_server <- function( +slopes_table_server <- function( id, pknca_data, manual_slopes_override = NULL ) { moduleServer(id, function(input, output, session) { diff --git a/inst/shiny/o_nca b/inst/shiny/o_nca deleted file mode 100644 index a460e4ba16c52d164ea6a7ddbf3f606e739facb1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 366470 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 y8X$g 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

!Bm~GmMau%6JE3l0-E(ixyY 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 z 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೩TS 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 Date: Tue, 6 Jan 2026 11:27:05 +0100 Subject: [PATCH 099/113] mv from /R > /functions most utils-slope_selector --- R/utils-slope_selector.R | 236 -------------------- inst/shiny/functions/utils-slope_selector.R | 236 ++++++++++++++++++++ 2 files changed, 236 insertions(+), 236 deletions(-) create mode 100644 inst/shiny/functions/utils-slope_selector.R diff --git a/R/utils-slope_selector.R b/R/utils-slope_selector.R index 506b0b347..8efe81899 100644 --- a/R/utils-slope_selector.R +++ b/R/utils-slope_selector.R @@ -1,214 +1,3 @@ -#' Slope Selector Utility Functions -#' -#' These helpers support the slope selection workflow by detecting changes in PKNCA data, -#' updating plots, and managing slope rule logic. Used internally by the slope selector module. - -#' Detect changes between old and new PKNCA data -#' -#' Compares two PKNCA data objects to determine if the underlying data, half-life adjustments, -#' or selected intervals have changed. Used to decide when to update plots. -#' @param old Previous PKNCA data object -#' @param new New PKNCA data object -#' @return List with logicals: `in_data`, `in_hl_adj`, `in_selected_intervals` -detect_pknca_data_changes <- function(old, new) { - excl_hl_col <- new$conc$columns$exclude_half.life - incl_hl_col <- new$conc$columns$include_half.life - list( - in_data = if (is.null(old) & !is.null(new)) { - TRUE - } else { - !identical( - dplyr::select( - old$conc$data, - -any_of(c(excl_hl_col, incl_hl_col)) - ), - dplyr::select( - new$conc$data, - -any_of(c(excl_hl_col, incl_hl_col)) - ) - ) - }, - in_hl_adj = !identical( - old$conc$data[[excl_hl_col]], - new$conc$data[[excl_hl_col]] - ) | - !identical( - old$conc$data[[incl_hl_col]], - new$conc$data[[incl_hl_col]] - ), - in_selected_intervals = !identical(new$intervals, old$intervals) - ) -} - - -#' Handle half-life adjustment changes -#' -#' Updates only the plots affected by changes in half-life inclusion/exclusion columns. -#' @param new_pknca_data New PKNCA data object -#' @param old_pknca_data Previous PKNCA data object -#' @param plot_outputs Current plot outputs (named list) -#' @return Updated plot_outputs (named list) -handle_hl_adj_change <- function(new_pknca_data, old_pknca_data, plot_outputs) { - excl_hl_col <- new_pknca_data$conc$columns$exclude_half.life - incl_hl_col <- new_pknca_data$conc$columns$include_half.life - affected_groups <- anti_join( - dplyr::select( - new_pknca_data$conc$data, - any_of(c(group_vars(new_pknca_data), "ATPTREF", excl_hl_col, incl_hl_col)) - ), - dplyr::select( - old_pknca_data$conc$data, - any_of(c(group_vars(old_pknca_data), "ATPTREF", excl_hl_col, incl_hl_col)) - ), - by = c(group_vars(new_pknca_data), "ATPTREF", excl_hl_col, incl_hl_col) - ) %>% - select(any_of(c(group_vars(new_pknca_data), "ATPTREF"))) %>% - distinct() - update_plots_with_pknca(new_pknca_data, plot_outputs, affected_groups) -} - - -#' Handle interval changes -#' -#' Updates plots when the set of selected intervals changes (e.g., analyte/profile selection). -#' @param new_pknca_data New PKNCA data object -#' @param old_pknca_data Previous PKNCA data object -#' @param plot_outputs Current plot outputs (named list) -#' @return Updated plot_outputs (named list) -handle_interval_change <- function(new_pknca_data, old_pknca_data, plot_outputs) { - new_intervals <- anti_join( - new_pknca_data$intervals, - old_pknca_data$intervals, - by = intersect( - names(new_pknca_data$intervals), - names(old_pknca_data$intervals) - ) - ) - rm_intervals <- anti_join( - old_pknca_data$intervals, - new_pknca_data$intervals, - by = intersect( - names(new_pknca_data$intervals), - names(old_pknca_data$intervals) - ) - ) - if (nrow(new_intervals) > 0) { - affected_groups <- new_intervals %>% - select(any_of(c(group_vars(new_pknca_data), "start", "end"))) %>% - merge(unique(PKNCA::getGroups(new_pknca_data$conc)), all.x = TRUE) %>% - select(any_of(c(group_vars(new_pknca_data), "start", "end"))) %>% - distinct() - plot_outputs <- update_plots_with_pknca( - new_pknca_data, - plot_outputs, - affected_groups - ) - } - if (nrow(rm_intervals) > 0) { - rm_plot_names <- rm_intervals %>% - select(any_of(c(group_vars(new_pknca_data), "start", "end"))) %>% - merge(unique(PKNCA::getGroups(new_pknca_data$conc)), all.x = TRUE) %>% - select(any_of(c(group_vars(new_pknca_data), "start", "end"))) %>% - distinct() %>% - mutate(across(everything(), as.character)) %>% - mutate(id = purrr::pmap_chr( - ., - function(...) { - vals <- list(...) - paste0(names(vals), "=", vals, collapse = "_") - } - )) %>% - pull(id) - plot_outputs <- plot_outputs[!names(plot_outputs) %in% rm_plot_names] - } - plot_outputs -} - - -#' Parse Plot Names to Data Frame -#' -#' Converts a named list of plots (with names in the format 'col1: val1, col2: val2, ...') -#' into a data frame with one row per plot and columns for each key. -#' -#' @param named_list A named list or vector, where names are key-value pairs separated by commas. -#' @return A data frame with columns for each key and a `PLOTID` column with the original names. -parse_plot_names_to_df <- function(named_list) { - plot_names <- names(named_list) - parsed <- lapply(plot_names, function(x) { - pairs <- strsplit(x, "_\\s*")[[1]] - kv <- strsplit(pairs, "=\\s*") - setNames( - vapply(kv, function(y) y[2], character(1)), - vapply(kv, function(y) y[1], character(1)) - ) - }) - as.data.frame( - do.call(rbind, parsed), - stringsAsFactors = FALSE - ) %>% - mutate(PLOTID = names(named_list)) -} - - -#' Arrange Plots by Group Columns -#' -#' Orders a named list of plots according to specified grouping columns. -#' Assumes a specific naming format (i.e, 'col1: val1, col2: val2, ...'). -#' -#' @param named_list A named list of plots, with names in the format 'col1: val1, col2: val2, ...'. -#' @param group_cols Character vector of column names to sort by. -#' @return A named list of plots, ordered by the specified group columns. -arrange_plots_by_groups <- function(named_list, group_cols) { - plot_df <- parse_plot_names_to_df(named_list) - arranged_df <- plot_df %>% - arrange(across(all_of(group_cols))) - named_list[arranged_df$PLOTID] -} - -#' Check overlap between existing and new slope rulesets -#' -#' Takes in tables with existing and incoming selections and exclusions, finds any overlap and -#' differences, edits the ruleset table accordingly. -#' @param existing Data frame with existing selections and exclusions. -#' @param new Data frame with new rule to be added or removed. -#' @param .keep Whether to force keep fully overlapping rulesets. If FALSE, it will be assumed -#' that the user wants to remove rule if new range already exists in the dataset. -#' If TRUE, in that case full range will be kept. -#' @return Data frame with full ruleset, adjusted for new rules. -#' @export -check_slope_rule_overlap <- function(existing, new, .keep = FALSE) { - slope_groups <- setdiff(names(new), c("TYPE", "RANGE", "REASON")) - # check if any rule already exists for specific subject and profile - existing_index <- which( - existing$TYPE == new$TYPE & - Reduce( - `&`, - lapply(slope_groups, function(col) { - existing[[col]] == new[[col]] - }) - ) - ) - if (length(existing_index) != 1) { - if (length(existing_index) > 1) - warning( - "More than one range for single subject, profile and rule type detected." - ) - return(rbind(existing, new)) - } - existing_range <- .eval_range(existing$RANGE[existing_index]) - new_range <- .eval_range(new$RANGE) - is_inter <- length(intersect(existing_range, new_range)) != 0 - is_diff <- length(setdiff(new_range, existing_range)) != 0 - if (is_diff || .keep) { - existing$RANGE[existing_index] <- unique(c(existing_range, new_range)) %>% - .compress_range() - } else if (is_inter) { - existing$RANGE[existing_index] <- setdiff(existing_range, new_range) %>% - .compress_range() - } - dplyr::filter(existing, !is.na(RANGE)) -} - #' Apply Slope Rules to Update Data #' #' Iterates over the given rules and updates the PKNCA object setting inclusion/exclusion flags. @@ -246,28 +35,3 @@ update_pknca_with_rules <- function(data, slopes) { } data } - -#' Update plots with PKNCA data (for affected intervals) -#' -#' Regenerates plots for the specified intervals in the plot_outputs list. -#' @param pknca_data PKNCA data object -#' @param plot_outputs Named list of current plot outputs -#' @param intervals_to_update Data frame of intervals to update (default: all in pknca_data) -#' @return Updated plot_outputs (named list) -update_plots_with_pknca <- function(pknca_data, plot_outputs, intervals_to_update = NULL) { - if (is.null(intervals_to_update)) { - intervals_to_update <- pknca_data$intervals %>% - select(any_of(c(group_vars(pknca_data), "start", "end"))) %>% - distinct() - } - if (nrow(intervals_to_update) == 0) return(plot_outputs) - # Get the intervals of the plots affected by the current rules - pknca_data$intervals <- inner_join( - intervals_to_update, - pknca_data$intervals, - by = intersect(names(intervals_to_update), names(pknca_data$intervals)) - ) - updated_plots <- suppressWarnings(get_halflife_plots(pknca_data)[["plots"]]) - plot_outputs[names(updated_plots)] <- updated_plots - plot_outputs -} diff --git a/inst/shiny/functions/utils-slope_selector.R b/inst/shiny/functions/utils-slope_selector.R new file mode 100644 index 000000000..b02a9ff51 --- /dev/null +++ b/inst/shiny/functions/utils-slope_selector.R @@ -0,0 +1,236 @@ +#' Slope Selector Utility Functions +#' +#' These helpers support the slope selection workflow by detecting changes in PKNCA data, +#' updating plots, and managing slope rule logic. Used internally by the slope selector module. + +#' Detect changes between old and new PKNCA data +#' +#' Compares two PKNCA data objects to determine if the underlying data, half-life adjustments, +#' or selected intervals have changed. Used to decide when to update plots. +#' @param old Previous PKNCA data object +#' @param new New PKNCA data object +#' @return List with logicals: `in_data`, `in_hl_adj`, `in_selected_intervals` +detect_pknca_data_changes <- function(old, new) { + excl_hl_col <- new$conc$columns$exclude_half.life + incl_hl_col <- new$conc$columns$include_half.life + list( + in_data = if (is.null(old) & !is.null(new)) { + TRUE + } else { + !identical( + dplyr::select( + old$conc$data, + -any_of(c(excl_hl_col, incl_hl_col)) + ), + dplyr::select( + new$conc$data, + -any_of(c(excl_hl_col, incl_hl_col)) + ) + ) + }, + in_hl_adj = !identical( + old$conc$data[[excl_hl_col]], + new$conc$data[[excl_hl_col]] + ) | + !identical( + old$conc$data[[incl_hl_col]], + new$conc$data[[incl_hl_col]] + ), + in_selected_intervals = !identical(new$intervals, old$intervals) + ) +} + + +#' Handle half-life adjustment changes +#' +#' Updates only the plots affected by changes in half-life inclusion/exclusion columns. +#' @param new_pknca_data New PKNCA data object +#' @param old_pknca_data Previous PKNCA data object +#' @param plot_outputs Current plot outputs (named list) +#' @return Updated plot_outputs (named list) +handle_hl_adj_change <- function(new_pknca_data, old_pknca_data, plot_outputs) { + excl_hl_col <- new_pknca_data$conc$columns$exclude_half.life + incl_hl_col <- new_pknca_data$conc$columns$include_half.life + affected_groups <- anti_join( + dplyr::select( + new_pknca_data$conc$data, + any_of(c(group_vars(new_pknca_data), "ATPTREF", excl_hl_col, incl_hl_col)) + ), + dplyr::select( + old_pknca_data$conc$data, + any_of(c(group_vars(old_pknca_data), "ATPTREF", excl_hl_col, incl_hl_col)) + ), + by = c(group_vars(new_pknca_data), "ATPTREF", excl_hl_col, incl_hl_col) + ) %>% + select(any_of(c(group_vars(new_pknca_data), "ATPTREF"))) %>% + distinct() + update_plots_with_pknca(new_pknca_data, plot_outputs, affected_groups) +} + + +#' Handle interval changes +#' +#' Updates plots when the set of selected intervals changes (e.g., analyte/profile selection). +#' @param new_pknca_data New PKNCA data object +#' @param old_pknca_data Previous PKNCA data object +#' @param plot_outputs Current plot outputs (named list) +#' @return Updated plot_outputs (named list) +handle_interval_change <- function(new_pknca_data, old_pknca_data, plot_outputs) { + new_intervals <- anti_join( + new_pknca_data$intervals, + old_pknca_data$intervals, + by = intersect( + names(new_pknca_data$intervals), + names(old_pknca_data$intervals) + ) + ) + rm_intervals <- anti_join( + old_pknca_data$intervals, + new_pknca_data$intervals, + by = intersect( + names(new_pknca_data$intervals), + names(old_pknca_data$intervals) + ) + ) + if (nrow(new_intervals) > 0) { + affected_groups <- new_intervals %>% + select(any_of(c(group_vars(new_pknca_data), "start", "end"))) %>% + merge(unique(PKNCA::getGroups(new_pknca_data$conc)), all.x = TRUE) %>% + select(any_of(c(group_vars(new_pknca_data), "start", "end"))) %>% + distinct() + plot_outputs <- update_plots_with_pknca( + new_pknca_data, + plot_outputs, + affected_groups + ) + } + if (nrow(rm_intervals) > 0) { + rm_plot_names <- rm_intervals %>% + select(any_of(c(group_vars(new_pknca_data), "start", "end"))) %>% + merge(unique(PKNCA::getGroups(new_pknca_data$conc)), all.x = TRUE) %>% + select(any_of(c(group_vars(new_pknca_data), "start", "end"))) %>% + distinct() %>% + mutate(across(everything(), as.character)) %>% + mutate(id = purrr::pmap_chr( + ., + function(...) { + vals <- list(...) + paste0(names(vals), "=", vals, collapse = "_") + } + )) %>% + pull(id) + plot_outputs <- plot_outputs[!names(plot_outputs) %in% rm_plot_names] + } + plot_outputs +} + + +#' Parse Plot Names to Data Frame +#' +#' Converts a named list of plots (with names in the format 'col1: val1, col2: val2, ...') +#' into a data frame with one row per plot and columns for each key. +#' +#' @param named_list A named list or vector, where names are key-value pairs separated by commas. +#' @return A data frame with columns for each key and a `PLOTID` column with the original names. +parse_plot_names_to_df <- function(named_list) { + plot_names <- names(named_list) + parsed <- lapply(plot_names, function(x) { + pairs <- strsplit(x, "_\\s*")[[1]] + kv <- strsplit(pairs, "=\\s*") + setNames( + vapply(kv, function(y) y[2], character(1)), + vapply(kv, function(y) y[1], character(1)) + ) + }) + as.data.frame( + do.call(rbind, parsed), + stringsAsFactors = FALSE + ) %>% + mutate(PLOTID = names(named_list)) +} + + +#' Arrange Plots by Group Columns +#' +#' Orders a named list of plots according to specified grouping columns. +#' Assumes a specific naming format (i.e, 'col1: val1, col2: val2, ...'). +#' +#' @param named_list A named list of plots, with names in the format 'col1: val1, col2: val2, ...'. +#' @param group_cols Character vector of column names to sort by. +#' @importFrom dplyr arrange across all_of +#' @return A named list of plots, ordered by the specified group columns. +arrange_plots_by_groups <- function(named_list, group_cols) { + plot_df <- parse_plot_names_to_df(named_list) + arranged_df <- plot_df %>% + arrange(across(all_of(group_cols))) + named_list[arranged_df$PLOTID] +} + +#' Check overlap between existing and new slope rulesets +#' +#' Takes in tables with existing and incoming selections and exclusions, finds any overlap and +#' differences, edits the ruleset table accordingly. +#' @param existing Data frame with existing selections and exclusions. +#' @param new Data frame with new rule to be added or removed. +#' @param .keep Whether to force keep fully overlapping rulesets. If FALSE, it will be assumed +#' that the user wants to remove rule if new range already exists in the dataset. +#' If TRUE, in that case full range will be kept. +#' @return Data frame with full ruleset, adjusted for new rules. +#' @export +check_slope_rule_overlap <- function(existing, new, .keep = FALSE) { + slope_groups <- setdiff(names(new), c("TYPE", "RANGE", "REASON")) + # check if any rule already exists for specific subject and profile + existing_index <- which( + existing$TYPE == new$TYPE & + Reduce( + `&`, + lapply(slope_groups, function(col) { + existing[[col]] == new[[col]] + }) + ) + ) + if (length(existing_index) != 1) { + if (length(existing_index) > 1) + warning( + "More than one range for single subject, profile and rule type detected." + ) + return(rbind(existing, new)) + } + existing_range <- .eval_range(existing$RANGE[existing_index]) + new_range <- .eval_range(new$RANGE) + is_inter <- length(intersect(existing_range, new_range)) != 0 + is_diff <- length(setdiff(new_range, existing_range)) != 0 + if (is_diff || .keep) { + existing$RANGE[existing_index] <- unique(c(existing_range, new_range)) %>% + .compress_range() + } else if (is_inter) { + existing$RANGE[existing_index] <- setdiff(existing_range, new_range) %>% + .compress_range() + } + dplyr::filter(existing, !is.na(RANGE)) +} + +#' Update plots with PKNCA data (for affected intervals) +#' +#' Regenerates plots for the specified intervals in the plot_outputs list. +#' @param pknca_data PKNCA data object +#' @param plot_outputs Named list of current plot outputs +#' @param intervals_to_update Data frame of intervals to update (default: all in pknca_data) +#' @return Updated plot_outputs (named list) +update_plots_with_pknca <- function(pknca_data, plot_outputs, intervals_to_update = NULL) { + if (is.null(intervals_to_update)) { + intervals_to_update <- pknca_data$intervals %>% + select(any_of(c(group_vars(pknca_data), "start", "end"))) %>% + distinct() + } + if (nrow(intervals_to_update) == 0) return(plot_outputs) + # Get the intervals of the plots affected by the current rules + pknca_data$intervals <- inner_join( + intervals_to_update, + pknca_data$intervals, + by = intersect(names(intervals_to_update), names(pknca_data$intervals)) + ) + updated_plots <- suppressWarnings(get_halflife_plots(pknca_data)[["plots"]]) + plot_outputs[names(updated_plots)] <- updated_plots + plot_outputs +} From edcd5a7831e4bc890f7ca220b6e8f4dff3b22567 Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Tue, 6 Jan 2026 18:46:59 +0100 Subject: [PATCH 100/113] tmp fix: exclusion rm after run NCA crashes App --- R/utils-slope_selector.R | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/R/utils-slope_selector.R b/R/utils-slope_selector.R index 8efe81899..def5f10d4 100644 --- a/R/utils-slope_selector.R +++ b/R/utils-slope_selector.R @@ -9,6 +9,14 @@ update_pknca_with_rules <- function(data, slopes) { time_col <- data$conc$columns$time exclude_hl_col <- data$conc$columns$exclude_half.life include_hl_col <- data$conc$columns$include_half.life + + ##################################################### + # TODO: Make a better fix to understand why slopes is constructed 2 times + # when adding exclusion, running NCA and then removing slope (#641) + + # Make sure when rows are removed no NA value is left + slopes <- na.omit(slopes) + ##################################################### for (i in seq_len(nrow(slopes))) { # Determine the time range for the points adjusted range <- strsplit(as.character(slopes$RANGE[i]), ":")[[1]] %>% From 7b544da705df0c6b7d2a6d5152b7ac4ee274290f Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Wed, 7 Jan 2026 08:05:52 +0100 Subject: [PATCH 101/113] refactor: lintr --- R/get_halflife_plots.R | 2 +- inst/shiny/modules/tab_nca/setup/slope_selector.R | 2 +- inst/shiny/modules/tab_nca/setup/slopes_table.R | 4 ++-- tests/testthat/test-PKNCA.R | 2 +- tests/testthat/test-utils-slope_selector.R | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/R/get_halflife_plots.R b/R/get_halflife_plots.R index 444b17bbe..fa852ec23 100644 --- a/R/get_halflife_plots.R +++ b/R/get_halflife_plots.R @@ -128,7 +128,7 @@ get_halflife_plots <- function(pknca_data, add_annotations = TRUE) { plot_list <- list() data_list <- list() - for (i in seq_len(length(info_per_plot_list))) { + for (i in seq_along(info_per_plot_list)) { df <- info_per_plot_list[[i]] # Create line data diff --git a/inst/shiny/modules/tab_nca/setup/slope_selector.R b/inst/shiny/modules/tab_nca/setup/slope_selector.R index a92cf6ac5..20d00fc4d 100644 --- a/inst/shiny/modules/tab_nca/setup/slope_selector.R +++ b/inst/shiny/modules/tab_nca/setup/slope_selector.R @@ -175,7 +175,7 @@ slope_selector_server <- function( # nolint plot_outputs = plot_outputs, plots_per_page = reactive(input$plots_per_page) ) - + observe({ req(plot_outputs()) output$slope_plots_ui <- renderUI({ diff --git a/inst/shiny/modules/tab_nca/setup/slopes_table.R b/inst/shiny/modules/tab_nca/setup/slopes_table.R index a1fc1cfd6..caee7c852 100644 --- a/inst/shiny/modules/tab_nca/setup/slopes_table.R +++ b/inst/shiny/modules/tab_nca/setup/slopes_table.R @@ -193,9 +193,9 @@ slopes_table_server <- function( PARAM == r["PARAM"], ATPTREF == r["ATPTREF"], DOSNOA == r["DOSNOA"] - ) |> + ) %>% NROW() != 0 - }) |> + }) %>% all() if (!override_valid) { msg <- "Manual slopes not compatible with current data, leaving as default." diff --git a/tests/testthat/test-PKNCA.R b/tests/testthat/test-PKNCA.R index 7a030d819..a084bad6c 100644 --- a/tests/testthat/test-PKNCA.R +++ b/tests/testthat/test-PKNCA.R @@ -394,7 +394,7 @@ describe("check_valid_pknca_data", { excl_hl_col <- pknca_data_with_excl$conc$columns$exclude_half.life pknca_data_with_excl$conc$data[1, excl_hl_col] <- TRUE - it ("does not throw an error if exclusions for half-life include a REASON value", { + it("does not throw an error if exclusions for half-life include a REASON value", { pknca_data_with_excl$conc$data$REASON <- "Test reason" expect_no_error( check_valid_pknca_data(pknca_data_with_excl, exclusions_have_reasons = TRUE) diff --git a/tests/testthat/test-utils-slope_selector.R b/tests/testthat/test-utils-slope_selector.R index 701b81f7a..81b4bd010 100644 --- a/tests/testthat/test-utils-slope_selector.R +++ b/tests/testthat/test-utils-slope_selector.R @@ -186,7 +186,7 @@ describe("handle_hl_adj_change", { new_plots <- handle_hl_adj_change(new_data, old_data, old_plots) # Check that the plots for other groups remain unchanged - ix_unchanged_plots <- setdiff(seq_len(length(new_plots)), 4) + ix_unchanged_plots <- setdiff(seq_along(new_plots), 4) expect_equal(new_plots[ix_unchanged_plots], old_plots[ix_unchanged_plots]) # Define the expected differences in the original and updated plots From 3ff0937fc1e5fde6031ca6f78a35e38012218de1 Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Wed, 7 Jan 2026 08:13:27 +0100 Subject: [PATCH 102/113] fix: plot tests failing due to accidental change in marker size --- R/get_halflife_plots.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/get_halflife_plots.R b/R/get_halflife_plots.R index fa852ec23..75d89102c 100644 --- a/R/get_halflife_plots.R +++ b/R/get_halflife_plots.R @@ -264,7 +264,7 @@ get_halflife_plots_single <- function( mode = "markers", marker = list( color = color, - size = 20, + size = 15, symbol = symbol ), customdata = apply( From b5e4082198c5911a664d73d1bed4e7c5616f4cdb Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Wed, 7 Jan 2026 08:14:07 +0100 Subject: [PATCH 103/113] man: roxygenise --- NAMESPACE | 1 - man/arrange_plots_by_groups.Rd | 20 -------------------- man/check_slope_rule_overlap.Rd | 24 ------------------------ man/detect_pknca_data_changes.Rd | 25 ------------------------- man/handle_hl_adj_change.Rd | 21 --------------------- man/handle_interval_change.Rd | 21 --------------------- man/parse_plot_names_to_df.Rd | 18 ------------------ man/update_plots_with_pknca.Rd | 21 --------------------- 8 files changed, 151 deletions(-) delete mode 100644 man/arrange_plots_by_groups.Rd delete mode 100644 man/check_slope_rule_overlap.Rd delete mode 100644 man/detect_pknca_data_changes.Rd delete mode 100644 man/handle_hl_adj_change.Rd delete mode 100644 man/handle_interval_change.Rd delete mode 100644 man/parse_plot_names_to_df.Rd delete mode 100644 man/update_plots_with_pknca.Rd diff --git a/NAMESPACE b/NAMESPACE index 0601cec0b..08ec028bd 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -20,7 +20,6 @@ export(apply_mapping) export(calculate_f) export(calculate_ratios) export(calculate_summary_stats) -export(check_slope_rule_overlap) export(convert_volume_units) export(create_metabfl) export(create_start_impute) diff --git a/man/arrange_plots_by_groups.Rd b/man/arrange_plots_by_groups.Rd deleted file mode 100644 index 46131216c..000000000 --- a/man/arrange_plots_by_groups.Rd +++ /dev/null @@ -1,20 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/utils-slope_selector.R -\name{arrange_plots_by_groups} -\alias{arrange_plots_by_groups} -\title{Arrange Plots by Group Columns} -\usage{ -arrange_plots_by_groups(named_list, group_cols) -} -\arguments{ -\item{named_list}{A named list of plots, with names in the format 'col1: val1, col2: val2, ...'.} - -\item{group_cols}{Character vector of column names to sort by.} -} -\value{ -A named list of plots, ordered by the specified group columns. -} -\description{ -Orders a named list of plots according to specified grouping columns. -Assumes a specific naming format (i.e, 'col1: val1, col2: val2, ...'). -} diff --git a/man/check_slope_rule_overlap.Rd b/man/check_slope_rule_overlap.Rd deleted file mode 100644 index a6188071d..000000000 --- a/man/check_slope_rule_overlap.Rd +++ /dev/null @@ -1,24 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/utils-slope_selector.R -\name{check_slope_rule_overlap} -\alias{check_slope_rule_overlap} -\title{Check overlap between existing and new slope rulesets} -\usage{ -check_slope_rule_overlap(existing, new, .keep = FALSE) -} -\arguments{ -\item{existing}{Data frame with existing selections and exclusions.} - -\item{new}{Data frame with new rule to be added or removed.} - -\item{.keep}{Whether to force keep fully overlapping rulesets. If FALSE, it will be assumed -that the user wants to remove rule if new range already exists in the dataset. -If TRUE, in that case full range will be kept.} -} -\value{ -Data frame with full ruleset, adjusted for new rules. -} -\description{ -Takes in tables with existing and incoming selections and exclusions, finds any overlap and -differences, edits the ruleset table accordingly. -} diff --git a/man/detect_pknca_data_changes.Rd b/man/detect_pknca_data_changes.Rd deleted file mode 100644 index 2be80dcda..000000000 --- a/man/detect_pknca_data_changes.Rd +++ /dev/null @@ -1,25 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/utils-slope_selector.R -\name{detect_pknca_data_changes} -\alias{detect_pknca_data_changes} -\title{Slope Selector Utility Functions} -\usage{ -detect_pknca_data_changes(old, new) -} -\arguments{ -\item{old}{Previous PKNCA data object} - -\item{new}{New PKNCA data object} -} -\value{ -List with logicals: \code{in_data}, \code{in_hl_adj}, \code{in_selected_intervals} -} -\description{ -These helpers support the slope selection workflow by detecting changes in PKNCA data, -updating plots, and managing slope rule logic. Used internally by the slope selector module. -Detect changes between old and new PKNCA data -} -\details{ -Compares two PKNCA data objects to determine if the underlying data, half-life adjustments, -or selected intervals have changed. Used to decide when to update plots. -} diff --git a/man/handle_hl_adj_change.Rd b/man/handle_hl_adj_change.Rd deleted file mode 100644 index 31a447e6c..000000000 --- a/man/handle_hl_adj_change.Rd +++ /dev/null @@ -1,21 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/utils-slope_selector.R -\name{handle_hl_adj_change} -\alias{handle_hl_adj_change} -\title{Handle half-life adjustment changes} -\usage{ -handle_hl_adj_change(new_pknca_data, old_pknca_data, plot_outputs) -} -\arguments{ -\item{new_pknca_data}{New PKNCA data object} - -\item{old_pknca_data}{Previous PKNCA data object} - -\item{plot_outputs}{Current plot outputs (named list)} -} -\value{ -Updated plot_outputs (named list) -} -\description{ -Updates only the plots affected by changes in half-life inclusion/exclusion columns. -} diff --git a/man/handle_interval_change.Rd b/man/handle_interval_change.Rd deleted file mode 100644 index 948371293..000000000 --- a/man/handle_interval_change.Rd +++ /dev/null @@ -1,21 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/utils-slope_selector.R -\name{handle_interval_change} -\alias{handle_interval_change} -\title{Handle interval changes} -\usage{ -handle_interval_change(new_pknca_data, old_pknca_data, plot_outputs) -} -\arguments{ -\item{new_pknca_data}{New PKNCA data object} - -\item{old_pknca_data}{Previous PKNCA data object} - -\item{plot_outputs}{Current plot outputs (named list)} -} -\value{ -Updated plot_outputs (named list) -} -\description{ -Updates plots when the set of selected intervals changes (e.g., analyte/profile selection). -} diff --git a/man/parse_plot_names_to_df.Rd b/man/parse_plot_names_to_df.Rd deleted file mode 100644 index 24b0460c5..000000000 --- a/man/parse_plot_names_to_df.Rd +++ /dev/null @@ -1,18 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/utils-slope_selector.R -\name{parse_plot_names_to_df} -\alias{parse_plot_names_to_df} -\title{Parse Plot Names to Data Frame} -\usage{ -parse_plot_names_to_df(named_list) -} -\arguments{ -\item{named_list}{A named list or vector, where names are key-value pairs separated by commas.} -} -\value{ -A data frame with columns for each key and a \code{PLOTID} column with the original names. -} -\description{ -Converts a named list of plots (with names in the format 'col1: val1, col2: val2, ...') -into a data frame with one row per plot and columns for each key. -} diff --git a/man/update_plots_with_pknca.Rd b/man/update_plots_with_pknca.Rd deleted file mode 100644 index 8b415901c..000000000 --- a/man/update_plots_with_pknca.Rd +++ /dev/null @@ -1,21 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/utils-slope_selector.R -\name{update_plots_with_pknca} -\alias{update_plots_with_pknca} -\title{Update plots with PKNCA data (for affected intervals)} -\usage{ -update_plots_with_pknca(pknca_data, plot_outputs, intervals_to_update = NULL) -} -\arguments{ -\item{pknca_data}{PKNCA data object} - -\item{plot_outputs}{Named list of current plot outputs} - -\item{intervals_to_update}{Data frame of intervals to update (default: all in pknca_data)} -} -\value{ -Updated plot_outputs (named list) -} -\description{ -Regenerates plots for the specified intervals in the plot_outputs list. -} From 1facc212d2436506e6cd1ba6556046bc0ebb7e0b Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Wed, 7 Jan 2026 08:30:40 +0100 Subject: [PATCH 104/113] fix: test-pk_dose_qc_plot.R exporting ggplotly --- NAMESPACE | 8 ++++++++ R/g_pkcg.R | 1 + R/pk_dose_qc_plot.R | 3 +++ 3 files changed, 12 insertions(+) diff --git a/NAMESPACE b/NAMESPACE index 08ec028bd..d2e870f7e 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -94,10 +94,17 @@ importFrom(dplyr,where) importFrom(flextable,flextable) importFrom(formatters,var_labels) importFrom(ggplot2,aes) +importFrom(ggplot2,facet_wrap) +importFrom(ggplot2,geom_point) +importFrom(ggplot2,ggplot) importFrom(ggplot2,ggplot_build) importFrom(ggplot2,ggplot_gtable) importFrom(ggplot2,labs) +importFrom(ggplot2,scale_alpha_manual) +importFrom(ggplot2,scale_colour_manual) +importFrom(ggplot2,scale_shape_manual) importFrom(ggplot2,scale_x_continuous) +importFrom(ggplot2,theme_bw) importFrom(grid,convertUnit) importFrom(magrittr,`%>%`) importFrom(methods,is) @@ -113,6 +120,7 @@ importFrom(officer,ph_with) importFrom(officer,read_pptx) importFrom(plotly,add_lines) importFrom(plotly,add_trace) +importFrom(plotly,ggplotly) importFrom(plotly,layout) importFrom(plotly,plot_ly) importFrom(plotly,plotly_build) diff --git a/R/g_pkcg.R b/R/g_pkcg.R index 6f65a31d4..e00f61fbe 100644 --- a/R/g_pkcg.R +++ b/R/g_pkcg.R @@ -49,6 +49,7 @@ g_pkcg01_log <- function(data, ...) { #' @importFrom ggplot2 aes scale_x_continuous labs #' @importFrom tern g_ipp #' @importFrom stats setNames +#' @importFrom plotly ggplotly layout #' #' @examples #' adnca <- read.csv(system.file("shiny/data/example-ADNCA.csv", package = "aNCA")) diff --git a/R/pk_dose_qc_plot.R b/R/pk_dose_qc_plot.R index 84984e69c..7f81cd617 100644 --- a/R/pk_dose_qc_plot.R +++ b/R/pk_dose_qc_plot.R @@ -34,6 +34,9 @@ #' @return A `ggplot` object or, if `as_plotly = TRUE`, a `plotly` object. #' #' @export +#' @importFrom ggplot2 ggplot aes geom_point facet_wrap scale_shape_manual +#' scale_colour_manual scale_alpha_manual labs theme_bw +#' @importFrom plotly ggplotly layout #' @examples #' #' # Sample concentration data From 02fe1e56bf960fa0bf78c475c73104ada788a392 Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Wed, 7 Jan 2026 09:19:40 +0100 Subject: [PATCH 105/113] fix: test typpo --- tests/testthat/test-PKNCA.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/testthat/test-PKNCA.R b/tests/testthat/test-PKNCA.R index a084bad6c..c39872d90 100644 --- a/tests/testthat/test-PKNCA.R +++ b/tests/testthat/test-PKNCA.R @@ -404,7 +404,7 @@ describe("check_valid_pknca_data", { it("throws an error if exclusions for half-life do not include a REASON value", { pknca_data_with_excl$conc$data$REASON <- "" expect_error( - check_valid_pknca_data(pknca_data, exclusions_have_reasons = TRUE), + check_valid_pknca_data(pknca_data_with_excl, exclusions_have_reasons = TRUE), "No reason provided for the following half-life exclusions:" ) }) From 9bb2d882071f5892bc27db6ae0121f0ae0c38bcd Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Wed, 7 Jan 2026 09:20:32 +0100 Subject: [PATCH 106/113] mv tests for moved funs in /functions to /inst/shiny --- .../testthat/test-utils-slope-selector.R | 269 ++++++++++++++++++ tests/testthat/test-utils-slope_selector.R | 259 ----------------- 2 files changed, 269 insertions(+), 259 deletions(-) create mode 100644 inst/shiny/tests/testthat/test-utils-slope-selector.R diff --git a/inst/shiny/tests/testthat/test-utils-slope-selector.R b/inst/shiny/tests/testthat/test-utils-slope-selector.R new file mode 100644 index 000000000..29319f8c6 --- /dev/null +++ b/inst/shiny/tests/testthat/test-utils-slope-selector.R @@ -0,0 +1,269 @@ +EXISTING_FIXTURE <- data.frame( + TYPE = "Exclusion", + USUBJID = 1, + ATPTREF = 1, + PARAM = "A", + PCSPEC = 1, + RANGE = "3:6" +) + +describe("check_slope_rule_overlap", { + it("should add new row if no overlap is detected", { + # different type # + NEW <- data.frame( + TYPE = "Selection", + USUBJID = 1, + ATPTREF = 1, + PARAM = "A", + PCSPEC = 1, + RANGE = "1:3" + ) + + expect_equal(nrow(check_slope_rule_overlap(EXISTING_FIXTURE, NEW)), 2) + + # different USUBJID # + NEW <- data.frame( + TYPE = "Exclusion", + USUBJID = 2, + ATPTREF = 1, + PARAM = "A", + PCSPEC = 1, + RANGE = "1:3" + ) + + expect_equal(nrow(check_slope_rule_overlap(EXISTING_FIXTURE, NEW)), 2) + + # different ATPTREF # + NEW <- data.frame( + TYPE = "Exclusion", + USUBJID = 1, + ATPTREF = 2, + PARAM = "A", + PCSPEC = 1, + RANGE = "1:3" + ) + + expect_equal(nrow(check_slope_rule_overlap(EXISTING_FIXTURE, NEW)), 2) + + }) + + it("should remove overlapping points if no new points are detected", { + NEW <- data.frame( + TYPE = "Exclusion", + USUBJID = 1, + ATPTREF = 1, + PARAM = "A", + PCSPEC = 1, + RANGE = "4:5" + ) + + expect_equal(check_slope_rule_overlap(EXISTING_FIXTURE, NEW)$RANGE, "3,6") + + NEW <- data.frame( + TYPE = "Exclusion", + USUBJID = 1, + ATPTREF = 1, + PARAM = "A", + PCSPEC = 1, + RANGE = "3:4" + ) + + expect_equal(check_slope_rule_overlap(EXISTING_FIXTURE, NEW)$RANGE, "5:6") + + }) + + it("should add new points if partial overlap is detected", { + NEW <- data.frame( + TYPE = "Exclusion", + USUBJID = 1, + ATPTREF = 1, + PARAM = "A", + PCSPEC = 1, + RANGE = "4:9" + ) + + expect_equal(check_slope_rule_overlap(EXISTING_FIXTURE, NEW)$RANGE, "3:9") + + }) + + it("should remove full row if full range of rule is removed", { + NEW <- data.frame( + TYPE = "Exclusion", + USUBJID = 1, + ATPTREF = 1, + PARAM = "A", + PCSPEC = 1, + RANGE = "3:6" + ) + + expect_equal(nrow(check_slope_rule_overlap(EXISTING_FIXTURE, NEW)), 0) + + }) + + it("should warn if more than one range for single subject, profile and rule type is detected", { + EXISTING <- data.frame( + TYPE = "Exclusion", + USUBJID = 1, + ATPTREF = 1, + PARAM = "A", + PCSPEC = 1, + RANGE = "3:6" + ) + + DUPLICATE <- EXISTING %>% + mutate( + RANGE = "4:7" + ) + + expect_warning( + check_slope_rule_overlap(rbind(EXISTING, DUPLICATE), DUPLICATE), + "More than one range for single subject, profile and rule type detected." + ) + }) +}) + +describe("detect_pknca_data_changes", { + + old_data <- FIXTURE_PKNCA_DATA + + it("detects change in data when there is no previous object", { + new_data <- old_data + res <- detect_pknca_data_changes(NULL, new_data) + expect_true(res$in_data) + }) + + it("detects no change when old and new are identical", { + res <- detect_pknca_data_changes(old_data, old_data) + expect_false(res$in_data) + expect_false(res$in_hl_adj) + expect_false(res$in_selected_intervals) + }) + + it("detects change in data", { + changed_data <- old_data + changed_data$conc$data$VAL[1] <- 99 + res <- detect_pknca_data_changes(old_data, changed_data) + expect_true(res$in_data) + }) + + it("detects change in hl_adj", { + changed_hl <- old_data + changed_hl$conc$data$exclude_half.life[2] <- TRUE + res <- detect_pknca_data_changes(old_data, changed_hl) + expect_true(res$in_hl_adj) + }) + + it("detects change in intervals", { + changed_int <- old_data + changed_int$intervals <- data.frame(ID = 1:4) + res <- detect_pknca_data_changes(old_data, changed_int) + expect_true(res$in_selected_intervals) + }) +}) + +describe("handle_hl_adj_change", { + it("updates only affected groups in plot_outputs", { + # Setup dummy PKNCA data objects and plot_outputs + old_data <- FIXTURE_PKNCA_DATA + new_data <- old_data + new_data$conc$data <- new_data$conc$data %>% + mutate( + exclude_half.life = ifelse( + USUBJID == unique(USUBJID)[3] & AFRLT == 4.5, + TRUE, + exclude_half.life + ) + ) + + # Create plots using the original and updated PKNCA data + old_plots <- withCallingHandlers( + get_halflife_plots(old_data)$plots, + # Because of the NA record there will be an expected warning + warning = function(w) { + if (grepl("Ignoring 1 observations", conditionMessage(w))) invokeRestart("muffleWarning") + } + ) + new_plots <- handle_hl_adj_change(new_data, old_data, old_plots) + + # Check that the plots for other groups remain unchanged + ix_unchanged_plots <- setdiff(seq_along(new_plots), 4) + expect_equal(new_plots[ix_unchanged_plots], old_plots[ix_unchanged_plots]) + + # Define the expected differences in the original and updated plots + old_plots_exp_details <- list( + color = c("red", "red", "green", "green", "green"), + size = 15, + symbol = c("circle", "circle", "circle", "circle", "circle"), + line = list(color = "rgba(255,127,14,1)") + ) + new_plots_exp_details <- list( + color = c("red", "green", "green", "red", "green"), + size = 15, + symbol = c("circle", "circle", "circle", "x", "circle"), + line = list(color = "rgba(255,127,14,1)") + ) + # Check that the affected plot has been updated correctly + expect_equal(old_plots[[4]]$x$data[[2]]$marker, old_plots_exp_details, ignore_attr = TRUE) + expect_equal(new_plots[[4]]$x$data[[2]]$marker, new_plots_exp_details, ignore_attr = TRUE) + }) +}) + + +describe("handle_interval_change", { + old_data <- FIXTURE_PKNCA_DATA + old_data$intervals <- old_data$intervals[1:3, ] + old_plots <- get_halflife_plots(old_data)$plots + + it("removes plots when intervals are removed", { + new_data <- FIXTURE_PKNCA_DATA + new_data$intervals <- new_data$intervals[1, ] + new_plots <- handle_interval_change(new_data, old_data, old_plots) + + expect_equal(length(old_plots), 3) + expect_equal(length(new_plots), 1) + expect_equal(old_plots[names(new_plots)], new_plots) + }) + + it("adds plots when intervals are added", { + new_data <- FIXTURE_PKNCA_DATA + new_data$intervals <- new_data$intervals[1:4, ] + new_plots <- handle_interval_change(new_data, old_data, old_plots) + + expect_equal(length(old_plots), 3) + expect_equal(length(new_plots), 4) + expect_equal(new_plots[names(old_plots)], old_plots) + }) +}) + +describe("arrange_plots_by_groups", { + it("orders plots by specified group columns", { + named_list <- list( + "B=2_A=1" = "plot1", + "B=1_A=2" = "plot2", + "B=1_A=1" = "plot3" + ) + ordered <- arrange_plots_by_groups(named_list, c("B", "A")) + expect_equal(names(ordered), c("B=1_A=1", "B=1_A=2", "B=2_A=1")) + ordered2 <- arrange_plots_by_groups(named_list, c("A", "B")) + expect_equal(names(ordered2), c("B=1_A=1", "B=2_A=1", "B=1_A=2")) + }) +}) + +describe("update_plots_with_pknca", { + old_data <- FIXTURE_PKNCA_DATA + old_data$intervals <- old_data$intervals[1:2, ] + old_plots <- get_halflife_plots(old_data)$plots + + new_data <- old_data + new_data$conc$data$exclude_half.life <- TRUE + + it("updates all plots when no intervals are specified", { + updated_plots <- update_plots_with_pknca(new_data, old_plots, NULL) + expect_equal(updated_plots[[1]]$x$data[[2]]$marker$symbol, rep("x", 5), ignore_attr = TRUE) + expect_equal(old_plots[[2]]$x$data[[2]]$marker$symbol, rep("circle", 5), ignore_attr = TRUE) + }) + it("does not update any plot when the specified intervals are an empty dataframe", { + updated_plots <- update_plots_with_pknca(new_data, old_plots, data.frame()) + expect_equal(updated_plots, old_plots) + }) +}) \ No newline at end of file diff --git a/tests/testthat/test-utils-slope_selector.R b/tests/testthat/test-utils-slope_selector.R index 81b4bd010..1a54c7a78 100644 --- a/tests/testthat/test-utils-slope_selector.R +++ b/tests/testthat/test-utils-slope_selector.R @@ -7,246 +7,6 @@ EXISTING_FIXTURE <- data.frame( RANGE = "3:6" ) -describe("check_slope_rule_overlap", { - it("should add new row if no overlap is detected", { - # different type # - NEW <- data.frame( - TYPE = "Selection", - USUBJID = 1, - ATPTREF = 1, - PARAM = "A", - PCSPEC = 1, - RANGE = "1:3" - ) - - expect_equal(nrow(check_slope_rule_overlap(EXISTING_FIXTURE, NEW)), 2) - - # different USUBJID # - NEW <- data.frame( - TYPE = "Exclusion", - USUBJID = 2, - ATPTREF = 1, - PARAM = "A", - PCSPEC = 1, - RANGE = "1:3" - ) - - expect_equal(nrow(check_slope_rule_overlap(EXISTING_FIXTURE, NEW)), 2) - - # different ATPTREF # - NEW <- data.frame( - TYPE = "Exclusion", - USUBJID = 1, - ATPTREF = 2, - PARAM = "A", - PCSPEC = 1, - RANGE = "1:3" - ) - - expect_equal(nrow(check_slope_rule_overlap(EXISTING_FIXTURE, NEW)), 2) - - }) - - it("should remove overlapping points if no new points are detected", { - NEW <- data.frame( - TYPE = "Exclusion", - USUBJID = 1, - ATPTREF = 1, - PARAM = "A", - PCSPEC = 1, - RANGE = "4:5" - ) - - expect_equal(check_slope_rule_overlap(EXISTING_FIXTURE, NEW)$RANGE, "3,6") - - NEW <- data.frame( - TYPE = "Exclusion", - USUBJID = 1, - ATPTREF = 1, - PARAM = "A", - PCSPEC = 1, - RANGE = "3:4" - ) - - expect_equal(check_slope_rule_overlap(EXISTING_FIXTURE, NEW)$RANGE, "5:6") - - }) - - it("should add new points if partial overlap is detected", { - NEW <- data.frame( - TYPE = "Exclusion", - USUBJID = 1, - ATPTREF = 1, - PARAM = "A", - PCSPEC = 1, - RANGE = "4:9" - ) - - expect_equal(check_slope_rule_overlap(EXISTING_FIXTURE, NEW)$RANGE, "3:9") - - }) - - it("should remove full row if full range of rule is removed", { - NEW <- data.frame( - TYPE = "Exclusion", - USUBJID = 1, - ATPTREF = 1, - PARAM = "A", - PCSPEC = 1, - RANGE = "3:6" - ) - - expect_equal(nrow(check_slope_rule_overlap(EXISTING_FIXTURE, NEW)), 0) - - }) - - it("should warn if more than one range for single subject, profile and rule type is detected", { - EXISTING <- data.frame( - TYPE = "Exclusion", - USUBJID = 1, - ATPTREF = 1, - PARAM = "A", - PCSPEC = 1, - RANGE = "3:6" - ) - - DUPLICATE <- EXISTING %>% - mutate( - RANGE = "4:7" - ) - - expect_warning( - check_slope_rule_overlap(rbind(EXISTING, DUPLICATE), DUPLICATE), - "More than one range for single subject, profile and rule type detected." - ) - }) -}) - -describe("detect_pknca_data_changes", { - - old_data <- FIXTURE_PKNCA_DATA - - it("detects change in data when there is no previous object", { - new_data <- old_data - res <- detect_pknca_data_changes(NULL, new_data) - expect_true(res$in_data) - }) - - it("detects no change when old and new are identical", { - res <- detect_pknca_data_changes(old_data, old_data) - expect_false(res$in_data) - expect_false(res$in_hl_adj) - expect_false(res$in_selected_intervals) - }) - - it("detects change in data", { - changed_data <- old_data - changed_data$conc$data$VAL[1] <- 99 - res <- detect_pknca_data_changes(old_data, changed_data) - expect_true(res$in_data) - }) - - it("detects change in hl_adj", { - changed_hl <- old_data - changed_hl$conc$data$exclude_half.life[2] <- TRUE - res <- detect_pknca_data_changes(old_data, changed_hl) - expect_true(res$in_hl_adj) - }) - - it("detects change in intervals", { - changed_int <- old_data - changed_int$intervals <- data.frame(ID = 1:4) - res <- detect_pknca_data_changes(old_data, changed_int) - expect_true(res$in_selected_intervals) - }) -}) - -describe("handle_hl_adj_change", { - it("updates only affected groups in plot_outputs", { - # Setup dummy PKNCA data objects and plot_outputs - old_data <- FIXTURE_PKNCA_DATA - new_data <- old_data - new_data$conc$data <- new_data$conc$data %>% - mutate( - exclude_half.life = ifelse( - USUBJID == unique(USUBJID)[3] & AFRLT == 4.5, - TRUE, - exclude_half.life - ) - ) - - # Create plots using the original and updated PKNCA data - old_plots <- withCallingHandlers( - get_halflife_plots(old_data)$plots, - # Because of the NA record there will be an expected warning - warning = function(w) { - if (grepl("Ignoring 1 observations", conditionMessage(w))) invokeRestart("muffleWarning") - } - ) - new_plots <- handle_hl_adj_change(new_data, old_data, old_plots) - - # Check that the plots for other groups remain unchanged - ix_unchanged_plots <- setdiff(seq_along(new_plots), 4) - expect_equal(new_plots[ix_unchanged_plots], old_plots[ix_unchanged_plots]) - - # Define the expected differences in the original and updated plots - old_plots_exp_details <- list( - color = c("red", "red", "green", "green", "green"), - size = 15, - symbol = c("circle", "circle", "circle", "circle", "circle"), - line = list(color = "rgba(255,127,14,1)") - ) - new_plots_exp_details <- list( - color = c("red", "green", "green", "red", "green"), - size = 15, - symbol = c("circle", "circle", "circle", "x", "circle"), - line = list(color = "rgba(255,127,14,1)") - ) - # Check that the affected plot has been updated correctly - expect_equal(old_plots[[4]]$x$data[[2]]$marker, old_plots_exp_details, ignore_attr = TRUE) - expect_equal(new_plots[[4]]$x$data[[2]]$marker, new_plots_exp_details, ignore_attr = TRUE) - }) -}) - -describe("handle_interval_change", { - old_data <- FIXTURE_PKNCA_DATA - old_data$intervals <- old_data$intervals[1:3, ] - old_plots <- get_halflife_plots(old_data)$plots - - it("removes plots when intervals are removed", { - new_data <- FIXTURE_PKNCA_DATA - new_data$intervals <- new_data$intervals[1, ] - new_plots <- handle_interval_change(new_data, old_data, old_plots) - - expect_equal(length(old_plots), 3) - expect_equal(length(new_plots), 1) - expect_equal(old_plots[names(new_plots)], new_plots) - }) - - it("adds plots when intervals are added", { - new_data <- FIXTURE_PKNCA_DATA - new_data$intervals <- new_data$intervals[1:4, ] - new_plots <- handle_interval_change(new_data, old_data, old_plots) - - expect_equal(length(old_plots), 3) - expect_equal(length(new_plots), 4) - expect_equal(new_plots[names(old_plots)], old_plots) - }) -}) - -describe("arrange_plots_by_groups", { - it("orders plots by specified group columns", { - named_list <- list( - "B=2_A=1" = "plot1", - "B=1_A=2" = "plot2", - "B=1_A=1" = "plot3" - ) - ordered <- arrange_plots_by_groups(named_list, c("B", "A")) - expect_equal(names(ordered), c("B=1_A=1", "B=1_A=2", "B=2_A=1")) - ordered2 <- arrange_plots_by_groups(named_list, c("A", "B")) - expect_equal(names(ordered2), c("B=1_A=1", "B=2_A=1", "B=1_A=2")) - }) -}) describe("update_pknca_with_rules", { old_data <- FIXTURE_PKNCA_DATA @@ -293,22 +53,3 @@ describe("update_pknca_with_rules", { ) }) }) - -describe("update_plots_with_pknca", { - old_data <- FIXTURE_PKNCA_DATA - old_data$intervals <- old_data$intervals[1:2, ] - old_plots <- get_halflife_plots(old_data)$plots - - new_data <- old_data - new_data$conc$data$exclude_half.life <- TRUE - - it("updates all plots when no intervals are specified", { - updated_plots <- update_plots_with_pknca(new_data, old_plots, NULL) - expect_equal(updated_plots[[1]]$x$data[[2]]$marker$symbol, rep("x", 5), ignore_attr = TRUE) - expect_equal(old_plots[[2]]$x$data[[2]]$marker$symbol, rep("circle", 5), ignore_attr = TRUE) - }) - it("does not update any plot when the specified intervals are an empty dataframe", { - updated_plots <- update_plots_with_pknca(new_data, old_plots, data.frame()) - expect_equal(updated_plots, old_plots) - }) -}) From 4bb5b657d097f43e217fcb84cceec5bcbed9d710 Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Wed, 7 Jan 2026 09:24:21 +0100 Subject: [PATCH 107/113] refactor: lintr --- .../testthat/test-utils-slope-selector.R | 67 +++++++++---------- 1 file changed, 31 insertions(+), 36 deletions(-) diff --git a/inst/shiny/tests/testthat/test-utils-slope-selector.R b/inst/shiny/tests/testthat/test-utils-slope-selector.R index 29319f8c6..b1c2333a4 100644 --- a/inst/shiny/tests/testthat/test-utils-slope-selector.R +++ b/inst/shiny/tests/testthat/test-utils-slope-selector.R @@ -18,9 +18,9 @@ describe("check_slope_rule_overlap", { PCSPEC = 1, RANGE = "1:3" ) - + expect_equal(nrow(check_slope_rule_overlap(EXISTING_FIXTURE, NEW)), 2) - + # different USUBJID # NEW <- data.frame( TYPE = "Exclusion", @@ -30,9 +30,9 @@ describe("check_slope_rule_overlap", { PCSPEC = 1, RANGE = "1:3" ) - + expect_equal(nrow(check_slope_rule_overlap(EXISTING_FIXTURE, NEW)), 2) - + # different ATPTREF # NEW <- data.frame( TYPE = "Exclusion", @@ -42,11 +42,10 @@ describe("check_slope_rule_overlap", { PCSPEC = 1, RANGE = "1:3" ) - + expect_equal(nrow(check_slope_rule_overlap(EXISTING_FIXTURE, NEW)), 2) - }) - + it("should remove overlapping points if no new points are detected", { NEW <- data.frame( TYPE = "Exclusion", @@ -56,9 +55,9 @@ describe("check_slope_rule_overlap", { PCSPEC = 1, RANGE = "4:5" ) - + expect_equal(check_slope_rule_overlap(EXISTING_FIXTURE, NEW)$RANGE, "3,6") - + NEW <- data.frame( TYPE = "Exclusion", USUBJID = 1, @@ -67,11 +66,10 @@ describe("check_slope_rule_overlap", { PCSPEC = 1, RANGE = "3:4" ) - + expect_equal(check_slope_rule_overlap(EXISTING_FIXTURE, NEW)$RANGE, "5:6") - }) - + it("should add new points if partial overlap is detected", { NEW <- data.frame( TYPE = "Exclusion", @@ -81,11 +79,10 @@ describe("check_slope_rule_overlap", { PCSPEC = 1, RANGE = "4:9" ) - + expect_equal(check_slope_rule_overlap(EXISTING_FIXTURE, NEW)$RANGE, "3:9") - }) - + it("should remove full row if full range of rule is removed", { NEW <- data.frame( TYPE = "Exclusion", @@ -95,11 +92,10 @@ describe("check_slope_rule_overlap", { PCSPEC = 1, RANGE = "3:6" ) - + expect_equal(nrow(check_slope_rule_overlap(EXISTING_FIXTURE, NEW)), 0) - }) - + it("should warn if more than one range for single subject, profile and rule type is detected", { EXISTING <- data.frame( TYPE = "Exclusion", @@ -109,12 +105,12 @@ describe("check_slope_rule_overlap", { PCSPEC = 1, RANGE = "3:6" ) - + DUPLICATE <- EXISTING %>% mutate( RANGE = "4:7" ) - + expect_warning( check_slope_rule_overlap(rbind(EXISTING, DUPLICATE), DUPLICATE), "More than one range for single subject, profile and rule type detected." @@ -123,36 +119,35 @@ describe("check_slope_rule_overlap", { }) describe("detect_pknca_data_changes", { - old_data <- FIXTURE_PKNCA_DATA - + it("detects change in data when there is no previous object", { new_data <- old_data res <- detect_pknca_data_changes(NULL, new_data) expect_true(res$in_data) }) - + it("detects no change when old and new are identical", { res <- detect_pknca_data_changes(old_data, old_data) expect_false(res$in_data) expect_false(res$in_hl_adj) expect_false(res$in_selected_intervals) }) - + it("detects change in data", { changed_data <- old_data changed_data$conc$data$VAL[1] <- 99 res <- detect_pknca_data_changes(old_data, changed_data) expect_true(res$in_data) }) - + it("detects change in hl_adj", { changed_hl <- old_data changed_hl$conc$data$exclude_half.life[2] <- TRUE res <- detect_pknca_data_changes(old_data, changed_hl) expect_true(res$in_hl_adj) }) - + it("detects change in intervals", { changed_int <- old_data changed_int$intervals <- data.frame(ID = 1:4) @@ -174,7 +169,7 @@ describe("handle_hl_adj_change", { exclude_half.life ) ) - + # Create plots using the original and updated PKNCA data old_plots <- withCallingHandlers( get_halflife_plots(old_data)$plots, @@ -184,11 +179,11 @@ describe("handle_hl_adj_change", { } ) new_plots <- handle_hl_adj_change(new_data, old_data, old_plots) - + # Check that the plots for other groups remain unchanged ix_unchanged_plots <- setdiff(seq_along(new_plots), 4) expect_equal(new_plots[ix_unchanged_plots], old_plots[ix_unchanged_plots]) - + # Define the expected differences in the original and updated plots old_plots_exp_details <- list( color = c("red", "red", "green", "green", "green"), @@ -213,22 +208,22 @@ describe("handle_interval_change", { old_data <- FIXTURE_PKNCA_DATA old_data$intervals <- old_data$intervals[1:3, ] old_plots <- get_halflife_plots(old_data)$plots - + it("removes plots when intervals are removed", { new_data <- FIXTURE_PKNCA_DATA new_data$intervals <- new_data$intervals[1, ] new_plots <- handle_interval_change(new_data, old_data, old_plots) - + expect_equal(length(old_plots), 3) expect_equal(length(new_plots), 1) expect_equal(old_plots[names(new_plots)], new_plots) }) - + it("adds plots when intervals are added", { new_data <- FIXTURE_PKNCA_DATA new_data$intervals <- new_data$intervals[1:4, ] new_plots <- handle_interval_change(new_data, old_data, old_plots) - + expect_equal(length(old_plots), 3) expect_equal(length(new_plots), 4) expect_equal(new_plots[names(old_plots)], old_plots) @@ -253,10 +248,10 @@ describe("update_plots_with_pknca", { old_data <- FIXTURE_PKNCA_DATA old_data$intervals <- old_data$intervals[1:2, ] old_plots <- get_halflife_plots(old_data)$plots - + new_data <- old_data new_data$conc$data$exclude_half.life <- TRUE - + it("updates all plots when no intervals are specified", { updated_plots <- update_plots_with_pknca(new_data, old_plots, NULL) expect_equal(updated_plots[[1]]$x$data[[2]]$marker$symbol, rep("x", 5), ignore_attr = TRUE) @@ -266,4 +261,4 @@ describe("update_plots_with_pknca", { updated_plots <- update_plots_with_pknca(new_data, old_plots, data.frame()) expect_equal(updated_plots, old_plots) }) -}) \ No newline at end of file +}) From 082c140a8dd186ecc3dd7d8bcf4c4ddb0ffe595d Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Wed, 7 Jan 2026 14:45:22 +0100 Subject: [PATCH 108/113] fix: next_page, previous_page overjumping --- inst/shiny/modules/tab_nca/setup/page_and_searcher.R | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/inst/shiny/modules/tab_nca/setup/page_and_searcher.R b/inst/shiny/modules/tab_nca/setup/page_and_searcher.R index 752948fe5..0cb7fdb99 100644 --- a/inst/shiny/modules/tab_nca/setup/page_and_searcher.R +++ b/inst/shiny/modules/tab_nca/setup/page_and_searcher.R @@ -4,6 +4,7 @@ #' The search_subject input remains outside for now in the parent (slope_selector.R) page_and_searcher_ui <- function(id) { ns <- NS(id) + shinyjs::useShinyjs() fluidRow( class = "plot-widgets-container-2", div( @@ -66,13 +67,11 @@ page_and_searcher_server <- function(id, search_subject, plot_outputs, plots_per if (current_page() < num_pages()) { current_page(current_page() + 1) } - shinyjs::disable(selector = ".btn-page") }) observeEvent(input$previous_page, { if (current_page() > 1) { current_page(current_page() - 1) } - shinyjs::disable(selector = ".btn-page") }) observeEvent(input$select_page, { val <- as.numeric(input$select_page) @@ -99,10 +98,14 @@ page_and_searcher_server <- function(id, search_subject, plot_outputs, plots_per selected = current_page() ) }) + # Enable/disable page buttons based on current page observe({ - shinyjs::toggleState(id = "previous_page", condition = current_page() == 1) - shinyjs::toggleState(id = "next_page", condition = current_page() == num_pages()) + curr_page <- current_page() + last_page <- num_pages() + shinyjs::toggleState(id = "previous_page", condition = curr_page > 1) + shinyjs::toggleState(id = "next_page", condition = curr_page < last_page) }) + observe({ shinyjs::toggleClass( selector = ".slope-plots-container", From 299377861dc35a912d3853d53689f82454171579 Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Wed, 7 Jan 2026 15:38:04 +0100 Subject: [PATCH 109/113] rm shinyjs::useShinyjs() --- inst/shiny/modules/tab_nca/setup/page_and_searcher.R | 1 - 1 file changed, 1 deletion(-) diff --git a/inst/shiny/modules/tab_nca/setup/page_and_searcher.R b/inst/shiny/modules/tab_nca/setup/page_and_searcher.R index 0cb7fdb99..7f880ac1b 100644 --- a/inst/shiny/modules/tab_nca/setup/page_and_searcher.R +++ b/inst/shiny/modules/tab_nca/setup/page_and_searcher.R @@ -4,7 +4,6 @@ #' The search_subject input remains outside for now in the parent (slope_selector.R) page_and_searcher_ui <- function(id) { ns <- NS(id) - shinyjs::useShinyjs() fluidRow( class = "plot-widgets-container-2", div( From 6d26ed2364e97955f6655846c3f63781f88fd426 Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Fri, 9 Jan 2026 12:59:14 +0100 Subject: [PATCH 110/113] fix: line unclosed for tests --- tests/testthat/test-PKNCA.R | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/testthat/test-PKNCA.R b/tests/testthat/test-PKNCA.R index 666e50a45..7aa350cfc 100644 --- a/tests/testthat/test-PKNCA.R +++ b/tests/testthat/test-PKNCA.R @@ -408,6 +408,8 @@ describe("check_valid_pknca_data", { "No reason provided for the following half-life exclusions:" ) }) +}) + # Tests for add_exclusion_reasons describe("add_exclusion_reasons", { it("adds a single exclusion reason to specified rows", { From 6f0ea86a13c2adc97363557038bb7336c82a61f2 Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Fri, 9 Jan 2026 14:25:27 +0100 Subject: [PATCH 111/113] man: roxygenise --- man/PKNCA_update_data_object.Rd | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/man/PKNCA_update_data_object.Rd b/man/PKNCA_update_data_object.Rd index 4a43ff8e7..de3ab669a 100644 --- a/man/PKNCA_update_data_object.Rd +++ b/man/PKNCA_update_data_object.Rd @@ -26,10 +26,11 @@ PKNCA_update_data_object( \item{selected_pcspec}{User selected specimen} +\item{should_impute_c0}{Logical indicating whether to impute start concentration values} + \item{hl_adj_rules}{A data frame containing half-life adjustment rules. It must contain group columns and rule specification columns; TYPE: (Inclusion, Exclusion), RANGE: (start-end).} -\item{should_impute_c0}{Logical indicating whether to impute start concentration values} \item{exclusion_list}{List of exclusion reasons and row indices to apply to the concentration data. Each item in the list should have: From 67593fb9116b5269e6c34b541b7ba47f43eeda13 Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Fri, 23 Jan 2026 16:29:23 +0100 Subject: [PATCH 112/113] roxygenise NAMESPACE --- NAMESPACE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NAMESPACE b/NAMESPACE index 86fbb3771..6a67f4d6e 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -24,7 +24,6 @@ export(calculate_f) export(calculate_ratios) export(calculate_summary_stats) export(calculate_table_ratios) -export(check_slope_rule_overlap) export(convert_volume_units) export(create_metabfl) export(create_start_impute) @@ -82,6 +81,7 @@ importFrom(dplyr,"%>%") importFrom(dplyr,`%>%`) importFrom(dplyr,across) importFrom(dplyr,add_count) +importFrom(dplyr,all_of) importFrom(dplyr,any_of) importFrom(dplyr,arrange) importFrom(dplyr,bind_rows) From 7ca75b456aebc32370480c87ee6d0a9e314dd656 Mon Sep 17 00:00:00 2001 From: Gero1999 Date: Fri, 23 Jan 2026 17:12:15 +0100 Subject: [PATCH 113/113] don't add manual for get_halflife_plots_single --- R/get_halflife_plots.R | 1 + man/get_halflife_plots_single.Rd | 56 -------------------------------- 2 files changed, 1 insertion(+), 56 deletions(-) delete mode 100644 man/get_halflife_plots_single.Rd diff --git a/R/get_halflife_plots.R b/R/get_halflife_plots.R index 75d89102c..f7e55a090 100644 --- a/R/get_halflife_plots.R +++ b/R/get_halflife_plots.R @@ -198,6 +198,7 @@ get_halflife_plots <- function(pknca_data, add_annotations = TRUE) { #' @param add_annotations Logical, whether to add the subtitle annotation #' @param text Optional vector of hover text for points (same length as plot_data) #' @returns A plotly object representing the scatter points (plot_data) +#' @noRd get_halflife_plots_single <- function( plot_data, fit_line_data, diff --git a/man/get_halflife_plots_single.Rd b/man/get_halflife_plots_single.Rd deleted file mode 100644 index 904335ba2..000000000 --- a/man/get_halflife_plots_single.Rd +++ /dev/null @@ -1,56 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/get_halflife_plots.R -\name{get_halflife_plots_single} -\alias{get_halflife_plots_single} -\title{Internal helper for plotting a single half-life plot} -\usage{ -get_halflife_plots_single( - plot_data, - fit_line_data, - time_col, - conc_col, - group_vars, - title, - subtitle, - xlab, - ylab, - color, - symbol, - add_annotations = TRUE, - text = NULL -) -} -\arguments{ -\item{plot_data}{Data frame for the scatter points} - -\item{fit_line_data}{Data frame for the fit line (must have columns for time and y)} - -\item{time_col}{Name of the time column (string)} - -\item{conc_col}{Name of the concentration column (string)} - -\item{group_vars}{Character vector of grouping variable names (for \code{customdata})} - -\item{title}{Plot title} - -\item{subtitle}{Subtitle/annotation (HTML allowed)} - -\item{xlab}{X axis label} - -\item{ylab}{Y axis label} - -\item{color}{Vector of colors for points (same length as plot_data)} - -\item{symbol}{Vector of marker symbols for points (same length as plot_data)} - -\item{add_annotations}{Logical, whether to add the subtitle annotation} - -\item{text}{Optional vector of hover text for points (same length as plot_data)} -} -\value{ -A plotly object representing the scatter points (plot_data) -} -\description{ -Generates a single plotly for NCA half-life visualization, with a fit line and scatter points. -} -\keyword{internal}

?$4j< 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 _3U;S9)MpA96_4GjZr7@##tyR8>`;fEbg;ryQ1 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