Skip to content

Commit 0873c55

Browse files
authored
Merge pull request #1087 from pharmaverse/1084-consolidate-update_main_intervals-and-units-update-into-pknca_update_data_object
Consolidate update_main_intervals and units update into PKNCA_update_data_object
2 parents 04c4b31 + 6a41352 commit 0873c55

File tree

11 files changed

+259
-106
lines changed

11 files changed

+259
-106
lines changed

DESCRIPTION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
Package: aNCA
22
Title: (Pre-)Clinical NCA in a Dynamic Shiny App
3-
Version: 0.1.0.9128
3+
Version: 0.1.0.9129
44
Authors@R: c(
55
person("Ercan", "Suekuer", email = "ercan.suekuer@roche.com", role = "aut",
66
comment = c(ORCID = "0009-0001-1626-1526")),

NAMESPACE

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,9 +105,11 @@ importFrom(dplyr,n)
105105
importFrom(dplyr,n_distinct)
106106
importFrom(dplyr,rename)
107107
importFrom(dplyr,row_number)
108+
importFrom(dplyr,rows_update)
108109
importFrom(dplyr,rowwise)
109110
importFrom(dplyr,select)
110111
importFrom(dplyr,slice)
112+
importFrom(dplyr,slice_max)
111113
importFrom(dplyr,slice_tail)
112114
importFrom(dplyr,summarise)
113115
importFrom(dplyr,sym)

R/PKNCA.R

Lines changed: 43 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,11 @@ PKNCA_create_data_object <- function( # nolint: object_name_linter
250250
#'
251251
#' Step 6: Indicate points excluded / selected manually for half-life
252252
#'
253+
#' Step 7 (optional): Update intervals with parameter selections per study type
254+
#' and partial AUC ranges via [update_main_intervals()].
255+
#'
256+
#' Step 8 (optional): Apply custom units table for PPSTRESU overrides.
257+
#'
253258
#' Note*: The function assumes that the `adnca_data` object has been
254259
#' created using the `PKNCA_create_data_object()` function.
255260
#'
@@ -261,17 +266,26 @@ PKNCA_create_data_object <- function( # nolint: object_name_linter
261266
#' @param hl_adj_rules A data frame containing half-life adjustment rules. It must
262267
#' contain group columns and rule specification columns;
263268
#' TYPE: (Inclusion, Exclusion), RANGE: (start-end).
264-
#' @param should_impute_c0 Logical indicating whether to impute start concentration values
269+
#' @param start_impute Logical indicating whether to impute start concentration values.
270+
#' Also forwarded to [update_main_intervals()] when `parameter_selections` is provided.
265271
#' @param exclusion_list List of exclusion reasons and row indices to apply to the
266272
#' concentration data. Each item in the list should have:
267273
#' - reason: character string with the exclusion reason (e.g., "Vomiting")
268274
#' - rows: integer vector of row indices to apply the exclusion to
269275
#' @param keep_interval_cols Optional character vector of additional columns
270276
#' to keep in the intervals data frame and when the NCA is run (pk.nca) also in the results
277+
#' @param parameter_selections Optional named list of selected PKNCA parameters
278+
#' by study type (forwarded to [update_main_intervals()]).
279+
#' @param int_parameters Optional data frame containing partial AUC ranges
280+
#' (forwarded to [update_main_intervals()]).
281+
#' @param blq_imputation_rule Optional list defining the BLQ imputation rule
282+
#' (forwarded to [update_main_intervals()]).
283+
#' @param custom_units_table Optional data frame with PPSTRESU overrides.
284+
#' When provided, applied via [dplyr::rows_update()] on the PKNCAdata units table.
271285
#'
272286
#' @returns A fully configured `PKNCAdata` object.
273287
#'
274-
#' @importFrom dplyr filter mutate select
288+
#' @importFrom dplyr filter mutate select rows_update
275289
#' @importFrom tidyr crossing
276290
#' @importFrom rlang sym
277291
#' @importFrom purrr pmap
@@ -283,10 +297,14 @@ PKNCA_update_data_object <- function( # nolint: object_name_linter
283297
selected_analytes,
284298
selected_profile,
285299
selected_pcspec,
286-
should_impute_c0 = TRUE,
300+
start_impute = TRUE,
287301
hl_adj_rules = NULL,
288302
exclusion_list = NULL,
289-
keep_interval_cols = NULL) {
303+
keep_interval_cols = NULL,
304+
parameter_selections = NULL,
305+
int_parameters = NULL,
306+
blq_imputation_rule = NULL,
307+
custom_units_table = NULL) {
290308

291309
data <- adnca_data
292310
analyte_column <- data$conc$columns$groups$group_analyte
@@ -316,7 +334,7 @@ PKNCA_update_data_object <- function( # nolint: object_name_linter
316334
data$intervals <- format_pkncadata_intervals(
317335
pknca_conc = data$conc,
318336
pknca_dose = data$dose,
319-
start_from_last_dose = should_impute_c0,
337+
start_from_last_dose = start_impute,
320338
keep_interval_cols = keep_interval_cols
321339
) %>%
322340
# Join route information
@@ -341,6 +359,26 @@ PKNCA_update_data_object <- function( # nolint: object_name_linter
341359
if (!is.null(hl_adj_rules)) {
342360
data <- update_pknca_with_rules(data, hl_adj_rules)
343361
}
362+
363+
# Update intervals with parameter selections and partial AUCs
364+
data <- update_main_intervals(
365+
data = data,
366+
parameter_selections = parameter_selections,
367+
int_parameters = int_parameters,
368+
impute = start_impute,
369+
blq_imputation_rule = blq_imputation_rule
370+
)
371+
372+
# Apply custom units table
373+
if (!is.null(custom_units_table)) {
374+
data$units <- rows_update(
375+
data$units,
376+
custom_units_table,
377+
by = c("PPTESTCD", "PPORRESU"),
378+
unmatched = "ignore"
379+
)
380+
}
381+
344382
data
345383
}
346384

R/intervals.R

Lines changed: 79 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,8 @@ format_pkncadata_intervals <- function(pknca_conc,
8282
)))
8383

8484
# Based on dose times create a data frame with start and end times
85-
dose_intervals <- left_join(sub_pknca_dose,
85+
dose_intervals <- left_join(
86+
sub_pknca_dose,
8687
sub_pknca_conc,
8788
by = intersect(names(sub_pknca_dose), c(conc_groups, "DOSNOA")),
8889
relationship = "many-to-many"
@@ -128,11 +129,74 @@ format_pkncadata_intervals <- function(pknca_conc,
128129
mutate(type_interval = "main")
129130
}
130131

132+
#' Derive study types from a PKNCAdata object
133+
#'
134+
#' Extracts concentration and dose data, merges dose duration if needed,
135+
#' and calls [detect_study_types()] to classify each group.
136+
#'
137+
#' @param data A PKNCAdata object.
138+
#'
139+
#' @returns A deduplicated data frame with grouping columns and a `type` column.
140+
#'
141+
#' @importFrom dplyr mutate left_join filter select slice_max distinct across all_of
142+
#' @noRd
143+
#' @keywords internal
144+
.derive_study_types <- function(data) {
145+
conc_data <- data$conc$data
146+
conc_groups <- group_vars(data$conc)
147+
dose_groups <- group_vars(data$dose)
148+
groups <- intersect(dose_groups, conc_groups)
149+
groups <- groups[vapply(groups, function(col) {
150+
!is.null(col) && length(unique(conc_data[[col]])) > 1
151+
}, logical(1))]
152+
153+
# Blank METABFL unconditionally so metabolite-specific types are not
154+
# assigned at the interval level.
155+
conc_data$METABFL <- ""
156+
157+
# Dose duration may live in dose data only; merge it for detect_study_types.
158+
duration_col <- data$dose$columns$duration
159+
if (!is.null(duration_col) && !duration_col %in% names(conc_data)) {
160+
dose_data <- data$dose$data
161+
if (duration_col %in% names(dose_data)) {
162+
conc_time <- data$conc$columns$time
163+
dose_time <- data$dose$columns$time
164+
join_by <- intersect(dose_groups, conc_groups)
165+
166+
dose_subset <- dose_data[, unique(c(join_by, dose_time, duration_col)), drop = FALSE]
167+
dose_subset <- unique(dose_subset)
168+
names(dose_subset)[names(dose_subset) == dose_time] <- ".dose_time"
169+
170+
conc_data <- conc_data %>%
171+
mutate(.ROWID = seq_len(n())) %>%
172+
left_join(dose_subset, by = join_by, relationship = "many-to-many") %>%
173+
filter(.dose_time <= .data[[conc_time]] | is.na(.dose_time)) %>%
174+
slice_max(.dose_time, n = 1, by = .ROWID, with_ties = FALSE) %>%
175+
select(-".ROWID", -".dose_time")
176+
} else {
177+
conc_data[[duration_col]] <- 0
178+
}
179+
}
180+
181+
study_types_df <- detect_study_types(
182+
conc_data,
183+
groups,
184+
metabfl_column = "METABFL",
185+
route_column = data$dose$columns$route,
186+
volume_column = data$conc$columns$volume
187+
)
188+
189+
# Deduplicate by grouping columns to prevent interval row duplication
190+
# when detect_study_types produces multiple types per group.
191+
grouping_cols <- setdiff(names(study_types_df), "type")
192+
study_types_df %>%
193+
distinct(across(all_of(grouping_cols)), .keep_all = TRUE)
194+
}
195+
131196
#' Update an intervals data frame with user-selected parameters by study type
132197
#'
133198
#' @param data A PKNCAdata object containing intervals and dosing data.
134199
#' @param parameter_selections A named list of selected PKNCA parameters by study type.
135-
#' @param study_types_df A data frame mapping analysis profiles to their study type.
136200
#' @param int_parameters A data frame containing partial AUC ranges.
137201
#' @param impute Logical indicating whether to impute start values for parameters.
138202
#' @param blq_imputation_rule A list defining the Below Limit of Quantification (BLQ)
@@ -143,22 +207,30 @@ format_pkncadata_intervals <- function(pknca_conc,
143207
#' which does not specify any BLQ imputation in any interval.
144208
#'
145209
#' @importFrom dplyr left_join mutate across where select all_of if_else bind_rows filter
210+
#' @importFrom dplyr group_by ungroup slice_max distinct
146211
#' @importFrom purrr pmap
147212
#' @returns An updated PKNCAdata object with parameter intervals based on user selections.
148213
#' @export
149214
update_main_intervals <- function(
150215
data,
151-
parameter_selections,
152-
study_types_df, int_parameters,
216+
parameter_selections = NULL,
217+
int_parameters = NULL,
153218
impute = TRUE,
154219
blq_imputation_rule = NULL
155220
) {
221+
if (is.null(parameter_selections)) parameter_selections <- list()
222+
if (is.null(int_parameters)) {
223+
int_parameters <- data.frame(
224+
parameter = character(), start_auc = numeric(), end_auc = numeric()
225+
)
226+
}
227+
156228
all_pknca_params <- setdiff(names(PKNCA::get.interval.cols()), c("start", "end"))
157229

158-
# Determine the grouping columns from the study_types_df
159-
grouping_cols <- setdiff(names(study_types_df), c("type"))
230+
study_types_df <- .derive_study_types(data)
231+
232+
grouping_cols <- setdiff(names(study_types_df), "type")
160233
missing_columns <- setdiff(grouping_cols, colnames(data$intervals))
161-
# check for grouping cols in intervals
162234
if (length(missing_columns) > 0) {
163235
stop(paste("Missing required columns:", paste(missing_columns, collapse = ", ")))
164236
}

R/zzz.R

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
.onLoad <- function(libname, pkgname) {
22
utils::globalVariables(c(
33
".",
4+
".dose_time",
45
".facet_label_values",
56
".facet_n",
7+
".ROWID",
68
"facet_label",
79
":=",
810
"ADOSEDUR",

inst/shiny/modules/tab_nca/nca_setup.R

Lines changed: 8 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -100,39 +100,32 @@ nca_setup_server <- function(id, data, adnca_data, extra_group_vars, settings_ov
100100
# Update pknca data object and intervals using summary output
101101
processed_pknca_data <- reactive({
102102
req(adnca_data(), settings(),
103-
parameters_output$selections(), parameters_output$types_df())
103+
parameters_output$selections())
104104

105105
log_trace("Updating PKNCA::data object.")
106106

107-
base_pknca_data <- PKNCA_update_data_object(
107+
final_data <- PKNCA_update_data_object(
108108
adnca_data = adnca_data(),
109109
method = settings()$method,
110110
selected_analytes = settings()$analyte,
111111
selected_profile = settings()$profile,
112112
selected_pcspec = settings()$pcspec,
113-
should_impute_c0 = settings()$data_imputation$impute_c0,
113+
start_impute = settings()$data_imputation$impute_c0,
114114
hl_adj_rules = slope_rules(),
115115
exclusion_list = general_exclusions(),
116-
keep_interval_cols = extra_group_vars()
116+
keep_interval_cols = extra_group_vars(),
117+
parameter_selections = parameters_output$selections(),
118+
int_parameters = settings()$int_parameters,
119+
blq_imputation_rule = settings()$data_imputation$blq_imputation_rule
117120
)
118121

119122
# Show bioavailability widget if it is possible to calculate
120-
if (base_pknca_data$dose$data$std_route %>% unique() %>% length() == 2) {
123+
if (final_data$dose$data$std_route %>% unique() %>% length() == 2) {
121124
shinyjs::show(selector = ".bioavailability-picker")
122125
} else {
123126
shinyjs::hide(selector = ".bioavailability-picker")
124127
}
125128

126-
# Call the updated function with the direct inputs
127-
final_data <- update_main_intervals(
128-
data = base_pknca_data,
129-
parameter_selections = parameters_output$selections(),
130-
study_types_df = parameters_output$types_df(),
131-
int_parameters = settings()$int_parameters,
132-
impute = settings()$data_imputation$impute_c0,
133-
blq_imputation_rule = settings()$data_imputation$blq_imputation_rule
134-
)
135-
136129
if (nrow(final_data$intervals) == 0) {
137130
showNotification(
138131
"All intervals were filtered. Please revise your settings",

inst/www/templates/script_template.R

Lines changed: 6 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ time_duplicate_rows <- settings_list$time_duplicate_rows
1919
int_parameters <- settings_list$settings$int_parameters
2020
units_table <- settings_list$units_table
2121
parameters_selected_per_study <- settings_list$settings$parameters$selections
22-
study_types_df <- settings_list$settings$parameters$types_df
2322
extra_vars_to_keep <- settings_list$extra_vars_to_keep
2423
slope_rules <- settings_list$slope_rules
2524

@@ -32,38 +31,20 @@ pknca_obj <- adnca_data %>%
3231
time_duplicate_rows = time_duplicate_rows
3332
) %>%
3433

35-
# Setup basic settings
34+
# Setup NCA settings, intervals, parameter selections, and units
3635
PKNCA_update_data_object(
3736
method = settings_list$settings$method,
3837
selected_analytes = settings_list$settings$analyte,
3938
selected_profile = settings_list$settings$profile,
4039
selected_pcspec = settings_list$settings$pcspec,
41-
should_impute_c0 = settings_list$settings$data_imputation$impute_c0,
40+
start_impute = settings_list$settings$data_imputation$impute_c0,
4241
exclusion_list = settings_list$settings$general_exclusions,
4342
hl_adj_rules = slope_rules,
44-
keep_interval_cols = setdiff(extra_vars_to_keep, c("DOSEA", "ATPTREF", "ROUTE"))
45-
) %>%
46-
47-
update_main_intervals(
48-
int_parameters = int_parameters,
43+
keep_interval_cols = setdiff(extra_vars_to_keep, c("DOSEA", "ATPTREF", "ROUTE")),
4944
parameter_selections = parameters_selected_per_study,
50-
study_types_df = study_types_df,
51-
impute = settings_list$settings$data_imputation$impute_c0
52-
) %>%
53-
54-
# Define the desired units for the parameters (PPSTRESU)
55-
{
56-
pknca_obj <- .
57-
if (!is.null(units_table)) {
58-
pknca_obj[["units"]] <- dplyr::rows_update(
59-
pknca_obj[["units"]],
60-
units_table,
61-
by = c("PPTESTCD", "PPORRESU"),
62-
unmatched = "ignore"
63-
)
64-
}
65-
pknca_obj
66-
}
45+
int_parameters = int_parameters,
46+
custom_units_table = units_table
47+
)
6748

6849
## Run NCA calculations ########################################
6950
flag_rules <- settings_list$settings$flags

0 commit comments

Comments
 (0)