Skip to content

Commit 48b978d

Browse files
Gero1999ona-agent
andcommitted
fix: generate HL plots when any half.life-dependent param is selected
Keep intervals where half.life or any dependent parameter (PKNCA::get.parameter.deps('half.life')) is TRUE, then reduce them to compute only half.life. Strips imputation and other params to avoid PKNCA_impute_method_blq errors and tidyr::spread duplicate-key issues. Co-authored-by: Ona <no-reply@ona.com>
1 parent 0cffda2 commit 48b978d

File tree

3 files changed

+100
-87
lines changed

3 files changed

+100
-87
lines changed

R/get_halflife_plots.R

Lines changed: 33 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -16,39 +16,39 @@
1616
#' @export
1717
get_halflife_plots <- function(pknca_data, add_annotations = TRUE,
1818
title_vars = NULL) {
19-
19+
2020
# If the input has empty concentration or intervals, just return an empty list
2121
if (nrow(pknca_data$conc$data) == 0 || nrow(pknca_data$intervals) == 0) {
2222
return(list(plots = list(), data = list()))
2323
}
24-
24+
2525
# Identify column names
2626
time_col <- pknca_data$conc$columns$time
2727
conc_col <- pknca_data$conc$columns$concentration
2828
timeu_col <- pknca_data$conc$columns$timeu
2929
concu_col <- pknca_data$conc$columns$concu
3030
exclude_hl_col <- pknca_data$conc$columns$exclude_half.life
31-
31+
3232
# Define which columns use for the title to keep it short
3333
group_conc_cols <- group_vars(pknca_data)
3434
group_conc_n_levels <- sapply(
3535
pknca_data$conc$data[group_conc_cols], \(x) length(unique(x))
3636
)
3737
title_cols <- group_conc_cols[group_conc_n_levels > 1]
38-
38+
3939
# Append caller-specified title variables that exist in the data
4040
extra <- intersect(title_vars, names(pknca_data$conc$data))
4141
title_cols <- unique(c(extra, title_cols))
42-
42+
4343
# Make sure to create a default exclude half life column if it does not exist
4444
if (is.null(exclude_hl_col)) {
4545
pknca_data$conc$data[["exclude_half.life"]] <- FALSE
4646
exclude_hl_col <- "exclude_half.life"
4747
}
48-
48+
4949
# Adjust the input to compute half-life & show original row number
5050
pknca_data$conc$data$ROWID <- seq_len(nrow(pknca_data$conc$data))
51-
51+
5252
# Keep intervals where half.life or any dependent parameter is selected,
5353
# then reduce them to only compute half.life (which yields lambda.z,
5454
# r.squared, etc. as side-effects). This avoids imputation and duplicate-key
@@ -62,20 +62,20 @@ get_halflife_plots <- function(pknca_data, add_annotations = TRUE,
6262
names(pknca_data$intervals)
6363
)
6464
other_params <- setdiff(all_params, "half.life")
65-
65+
6666
pknca_data$intervals <- pknca_data$intervals %>%
6767
filter(type_interval == "main") %>%
6868
filter(half.life | if_any(all_of(hl_dep_params))) %>%
6969
mutate(half.life = TRUE, across(all_of(other_params), ~FALSE)) %>%
7070
mutate(impute = NA_character_) %>%
7171
unique()
7272
pknca_data$impute <- NA_character_
73-
73+
7474
d_conc_with_res <- .merge_conc_with_nca_results(
7575
pknca_data, time_col, conc_col, timeu_col,
7676
concu_col, exclude_hl_col, title_vars
7777
)
78-
78+
7979
# Mark points used in half-life calculation
8080
info_per_plot_list <- d_conc_with_res %>%
8181
# Indicate plot details
@@ -124,7 +124,7 @@ get_halflife_plots <- function(pknca_data, add_annotations = TRUE,
124124
filter(.[[time_col]] <= tlast) %>%
125125
# Disconsider BLQ points at the middle as well
126126
filter(.[[conc_col]] > 0)
127-
127+
128128
info_per_plot_list <- info_per_plot_list %>%
129129
mutate(
130130
color = "black",
@@ -134,12 +134,12 @@ get_halflife_plots <- function(pknca_data, add_annotations = TRUE,
134134
) %>%
135135
group_by(!!!syms(c(group_vars(pknca_data), "start", "end"))) %>%
136136
group_split()
137-
137+
138138
plot_list <- list()
139139
data_list <- list()
140140
for (i in seq_along(info_per_plot_list)) {
141141
df <- info_per_plot_list[[i]]
142-
142+
143143
# Create line data
144144
if (any(df$is_halflife_used, na.rm = TRUE)) {
145145
df_fit <- df[df$is_halflife_used, ]
@@ -154,7 +154,7 @@ get_halflife_plots <- function(pknca_data, add_annotations = TRUE,
154154
)
155155
colnames(fit_line_data)[1] <- time_col
156156
}
157-
157+
158158
# Unique plot ID based on grouping variables and interval times
159159
plotid_vars <- c(group_vars(pknca_data), "start", "end")
160160
plotid <- paste0(
@@ -163,7 +163,7 @@ get_halflife_plots <- function(pknca_data, add_annotations = TRUE,
163163
collapse = "_"
164164
)
165165
)
166-
166+
167167
# Create the plot
168168
available_title_cols <- intersect(title_cols, names(df))
169169
plot_list[[plotid]] <- get_halflife_plots_single(
@@ -203,14 +203,14 @@ get_halflife_plots <- function(pknca_data, add_annotations = TRUE,
203203
exclude_hl_col,
204204
extra_vars = NULL) {
205205
o_nca <- suppressWarnings(PKNCA::pk.nca(pknca_data))
206-
206+
207207
if (!"PPSTRES" %in% names(o_nca$result)) {
208208
o_nca$result$PPSTRES <- o_nca$result$PPORRES
209209
if ("PPORRESU" %in% names(o_nca$result)) {
210210
o_nca$result$PPSTRESU <- o_nca$result$PPORRESU
211211
}
212212
}
213-
213+
214214
wide_output <- o_nca
215215
wide_output$result <- wide_output$result %>%
216216
filter(
@@ -219,17 +219,17 @@ get_halflife_plots <- function(pknca_data, add_annotations = TRUE,
219219
) %>%
220220
select(-any_of(c("PPORRESU", "PPSTRESU", "PPSTRES"))) %>%
221221
mutate(exclude = paste0(na.omit(unique(exclude)), collapse = ". "))
222-
222+
223223
wide_output <- as.data.frame(wide_output, out_format = "wide") %>%
224224
unique()
225-
225+
226226
conc_select_cols <- c(group_vars(pknca_data), time_col, conc_col,
227227
timeu_col, concu_col, exclude_hl_col, "ROWID")
228228
merge_by <- c(group_vars(pknca_data))
229229
extra <- intersect(extra_vars, names(pknca_data$conc$data))
230230
conc_select_cols <- c(conc_select_cols, extra)
231231
merge_by <- c(merge_by, extra)
232-
232+
233233
merge(
234234
pknca_data$conc$data %>%
235235
select(!!!syms(conc_select_cols)),
@@ -262,19 +262,19 @@ get_halflife_plots <- function(pknca_data, add_annotations = TRUE,
262262
#' @returns A plotly object representing the scatter points (plot_data)
263263
#' @noRd
264264
get_halflife_plots_single <- function(
265-
plot_data,
266-
fit_line_data,
267-
time_col,
268-
conc_col,
269-
group_vars,
270-
title,
271-
subtitle,
272-
xlab,
273-
ylab,
274-
color,
275-
symbol,
276-
add_annotations = TRUE,
277-
text = NULL
265+
plot_data,
266+
fit_line_data,
267+
time_col,
268+
conc_col,
269+
group_vars,
270+
title,
271+
subtitle,
272+
xlab,
273+
ylab,
274+
color,
275+
symbol,
276+
add_annotations = TRUE,
277+
text = NULL
278278
) {
279279
if (is.null(text)) {
280280
text <- paste0(

inst/shiny/modules/tab_nca/setup/slope_selector.R

Lines changed: 47 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
slope_selector_ui <- function(id) {
2929
ns <- NS(id)
3030
assets <- system.file("shiny/www", package = "aNCA")
31-
31+
3232
div(
3333
class = "slope-selector-module",
3434
manual_slopes_table_ui(ns("manual_slopes")),
@@ -45,36 +45,36 @@ slope_selector_ui <- function(id) {
4545
Please remember to apply your changes once you are done by clicking Run NCA again!
4646
"),
4747
div(class = "gif-grid",
48-
div(
49-
class = "gif-container",
50-
tags$h1("Check"),
51-
tags$h6("Hover the mouse over points to inspect individual samples."),
52-
img(src = "images/slope_plot_check.gif", alt = "Check")
53-
),
54-
div(
55-
class = "gif-container",
56-
tags$h1("Zoom"),
57-
tags$h6("Click and drag to select and zoom in a specific area.",
58-
" Double click to zoom out."
59-
),
60-
img(src = "images/slope_plot_zoom.gif", alt = "Zoom")
48+
div(
49+
class = "gif-container",
50+
tags$h1("Check"),
51+
tags$h6("Hover the mouse over points to inspect individual samples."),
52+
img(src = "images/slope_plot_check.gif", alt = "Check")
53+
),
54+
div(
55+
class = "gif-container",
56+
tags$h1("Zoom"),
57+
tags$h6("Click and drag to select and zoom in a specific area.",
58+
" Double click to zoom out."
6159
),
62-
div(
63-
class = "gif-container",
64-
tags$h1("Select"),
65-
tags$h6("Click the first and then the last point",
66-
" you want to include in the slope."),
67-
img(src = "images/slope_plot_select.gif", alt = "Select")
60+
img(src = "images/slope_plot_zoom.gif", alt = "Zoom")
61+
),
62+
div(
63+
class = "gif-container",
64+
tags$h1("Select"),
65+
tags$h6("Click the first and then the last point",
66+
" you want to include in the slope."),
67+
img(src = "images/slope_plot_select.gif", alt = "Select")
68+
),
69+
div(
70+
class = "gif-container",
71+
tags$h1("Exclude"),
72+
tags$h6(
73+
tags$div("Double click a point to exclude it."),
74+
tags$div("Double click it again to include it back.")
6875
),
69-
div(
70-
class = "gif-container",
71-
tags$h1("Exclude"),
72-
tags$h6(
73-
tags$div("Double click a point to exclude it."),
74-
tags$div("Double click it again to include it back.")
75-
),
76-
img(src = "images/slope_plot_exclude.gif", alt = "Exclude")
77-
)
76+
img(src = "images/slope_plot_exclude.gif", alt = "Exclude")
77+
)
7878
)
7979
),
8080
style = "unite",
@@ -130,32 +130,25 @@ slope_selector_server <- function( # nolint
130130
) {
131131
moduleServer(id, function(input, output, session) {
132132
log_trace("{id}: Attaching server")
133-
133+
134134
ns <- session$ns
135-
135+
136136
pknca_data <- reactiveVal(NULL)
137137
plot_outputs <- reactiveVal(NULL)
138-
138+
139139
observeEvent(processed_pknca_data(), {
140140
req(processed_pknca_data())
141-
141+
142142
new_pknca_data <- processed_pknca_data()
143-
# Keep main intervals where half.life or any dependent param is selected.
144-
# get_halflife_plots() handles the rest (forcing half.life, clearing impute).
145-
hl_dep_params <- intersect(
146-
PKNCA::get.parameter.deps("half.life"),
147-
names(new_pknca_data$intervals)
148-
)
149143
new_pknca_data$intervals <- new_pknca_data$intervals %>%
150-
filter(type_interval == "main") %>%
151-
filter(half.life | if_any(all_of(hl_dep_params))) %>%
144+
filter(type_interval == "main", half.life) %>%
152145
unique()
153146
changes <- detect_pknca_data_changes(
154147
old = pknca_data(),
155148
new = new_pknca_data,
156149
reason_col = "REASON"
157150
)
158-
151+
159152
if (changes$in_data) {
160153
# New data or major changes: regenerate all plots
161154
plot_outputs(get_halflife_plots(
@@ -168,7 +161,7 @@ slope_selector_server <- function( # nolint
168161
# Add/remove plots based on intervals (selection from nca_setup.R)
169162
plot_outputs(handle_interval_change(new_pknca_data, pknca_data(), plot_outputs()))
170163
}
171-
164+
172165
# Update the searching widget choices based on the new data
173166
if (changes$in_data | changes$in_selected_intervals) {
174167
updateSelectInput(
@@ -186,21 +179,21 @@ slope_selector_server <- function( # nolint
186179
group_conc_cols <- group_vars(pknca_data)
187180
group_conc_n_levels <- sapply(pknca_data$conc$data[group_conc_cols], \(x) length(unique(x)))
188181
group_cols_to_order <- group_conc_cols[group_conc_n_levels > 1]
189-
182+
190183
updateOrderInput(
191184
session = session,
192185
inputId = "order_groups",
193186
items = group_cols_to_order
194187
)
195188
}
196-
189+
197190
# Save the plots for the zip download (nca_results.R)
198191
session$userData$results$slope_selector <- plot_outputs()
199-
192+
200193
# Update the object for future comparisons
201194
pknca_data(new_pknca_data)
202195
})
203-
196+
204197
# Call the pagination/searcher module to:
205198
# - Providing indices of plots for the selected subject(s)
206199
# - Providing indices for which plots to display based on pagination
@@ -210,7 +203,7 @@ slope_selector_server <- function( # nolint
210203
plot_outputs = plot_outputs,
211204
plots_per_page = reactive(input$plots_per_page)
212205
)
213-
206+
214207
observe({
215208
req(plot_outputs())
216209
output$slope_plots_ui <- renderUI({
@@ -224,13 +217,13 @@ slope_selector_server <- function( # nolint
224217
.[page_search$page_start():page_search$page_end()]
225218
})
226219
})
227-
220+
228221
# Creates an initial version of the manual slope adjustments table with pknca_data
229222
# and handles the addition and deletion of rows through the UI
230223
slopes_table <- manual_slopes_table_server("manual_slopes", pknca_data, manual_slopes_override)
231224
manual_slopes <- slopes_table$manual_slopes
232225
refresh_reactable <- slopes_table$refresh_reactable
233-
226+
234227
# Define the click events for the point exclusion and selection in the slope plots
235228
last_click_data <- reactiveVal(NULL)
236229
observeEvent(event_data("plotly_click", priority = "event"), {
@@ -245,24 +238,24 @@ slope_selector_server <- function( # nolint
245238
# Update reactive values: last click & manual slopes table
246239
last_click_data(click_result$last_click_data)
247240
manual_slopes(click_result$manual_slopes)
248-
241+
249242
# render rectable anew #
250243
shinyjs::runjs("memory = {};") # needed to properly reset reactable.extras widgets
251244
refresh_reactable(refresh_reactable() + 1)
252245
})
253-
246+
254247
#' Separate event handling updating displayed reactable upon every change (adding and removing
255248
#' rows, plots selection, edits). This needs to be separate call, since simply re-rendering
256249
#' the table would mean losing focus on text inputs when entering values.
257250
observeEvent(manual_slopes(), {
258251
req(manual_slopes())
259-
252+
260253
# Update reactable with rules
261254
reactable::updateReactable(
262255
outputId = "manual_slopes",
263256
data = manual_slopes()
264257
)
265-
258+
266259
})
267260
#' returns half life adjustments rules to update processed_pknca_data in nca_setup.R
268261
manual_slopes

0 commit comments

Comments
 (0)