diff --git a/R/format_data.R b/R/format_data.R index 564e40e..d9d6338 100644 --- a/R/format_data.R +++ b/R/format_data.R @@ -104,7 +104,8 @@ format_pkncadose_data <- function(pkncaconc_data, #' #' This function creates a dataset with dose intervals and specified pharmacokinetic parameters. #' -#' @param df_dose A PKNCAdose object +#' @param pknca_conc A PKNCAdose object containing the concentration data. +#' @param pknca_dose A PKNCAdose object containing the dose data. #' @param params A character vector specifying the pharmacokinetic parameters to include. #' @param start_from_last_dose Logical defining if start is at time of last dose or C1. #' @@ -119,12 +120,13 @@ format_pkncadose_data <- function(pkncaconc_data, #' @examples #' \dontrun{ #' # Example usage: -#' dose_intervals <- format_pkncadata_intervals(df_dose, params) +#' dose_intervals <- format_pkncadata_intervals(pknca_conc, pknca_dose, params) #' } #' #' @import dplyr #' @export -format_pkncadata_intervals <- function(pknca_dose, +format_pkncadata_intervals <- function(pknca_conc, + pknca_dose, params = c("aucinf.obs", "aucint.last", "auclast", "cmax", "half.life", "tmax", "lambda.z", "lambda.z.n.points", "r.squared", @@ -132,8 +134,12 @@ format_pkncadata_intervals <- function(pknca_dose, "aucpext.obs", "aucpext.pred", "clast.obs", "cl.obs"), start_from_last_dose = TRUE) { + if (!inherits(pknca_conc, "PKNCAconc")) { + stop("Input pknca_conc must be a PKNCAconc object from the PKNCA package.") + } + if (!inherits(pknca_dose, "PKNCAdose")) { - stop("Input must be a PKNCAdose object from the PKNCA package.") + stop("Input pknca_dose must be a PKNCAdose object from the PKNCA package.") } required_columns <- c(unname(unlist(pknca_dose$columns$groups)), pknca_dose$columns$time) @@ -142,15 +148,44 @@ format_pkncadata_intervals <- function(pknca_dose, stop(paste("Missing required columns:", paste(missing_columns, collapse = ", "))) } + # Select relevant group columns + conc_groups <- unname(unlist(pknca_conc$columns$groups)) + dose_groups <- unname(unlist(pknca_dose$columns$groups)) + time_dose_seg <- if ("DOSNO" %in% names(pknca_dose$data)) "DOSNO" else "time_dose" + + # Select conc data and for time column give priority to non-predose samples + sub_pknca_conc <- pknca_conc$data %>% + select(any_of(c(conc_groups, "AFRLT", "ARRLT", "DOSNO"))) %>% + arrange(!!!syms(conc_groups), ARRLT < 0, AFRLT) %>% + ungroup() + + # Select dose data and use its time column as a time of last dose reference + sub_pknca_dose <- pknca_dose$data %>% + select(any_of(c(unname(unlist(pknca_dose$columns$groups)), + pknca_dose$columns$time, "DOSNO"))) %>% + rename_with(~ "time_dose", pknca_dose$columns$time) + # Based on dose times create a data frame with start and end times - dose_intervals <- pknca_dose$data %>% - mutate(start = if (start_from_last_dose) !!sym(pknca_dose$columns$time) - else !!sym(pknca_dose$columns$time) + !!sym("ARRLT")) %>% - group_by(!!!syms(unname(unlist(pknca_dose$columns$groups)))) %>% - arrange(!!sym(pknca_dose$columns$time)) %>% - mutate(end = lead(as.numeric(!!sym(pknca_dose$columns$time)), default = Inf)) %>% + dose_intervals <- left_join(sub_pknca_conc, + sub_pknca_dose, + relationship = "many-to-many") %>% + + # Pick 1 per concentration group and dose number + arrange(!!!syms(conc_groups), ARRLT < 0, AFRLT) %>% + group_by(!!!syms(c(conc_groups, time_dose_seg))) %>% + slice(1) %>% + ungroup() %>% + + # Make start from last dose (pknca_dose) or first concentration (pknca_conc) + mutate(start = if (start_from_last_dose) time_dose + else time_dose + !!sym("ARRLT")) %>% + group_by(!!!syms(c(conc_groups))) %>% + arrange(time_dose) %>% + + # Make end based on next dose time (if no more, Inf) + mutate(end = lead(as.numeric(time_dose), default = Inf)) %>% ungroup() %>% - select(any_of(c("start", "end", unname(unlist(pknca_dose$columns$groups)), "DOSNO"))) %>% + select(any_of(c("start", "end", unname(unlist(pknca_conc$columns$groups)), "DOSNO"))) %>% # Create logical columns with the TRUE and as names params argument mutate(!!!setNames(rep(TRUE, length(params)), params)) %>% diff --git a/inst/shiny/tabs/nca.R b/inst/shiny/tabs/nca.R index 0222572..e31be06 100644 --- a/inst/shiny/tabs/nca.R +++ b/inst/shiny/tabs/nca.R @@ -233,7 +233,7 @@ observeEvent(input$submit_analyte, priority = 2, { duration = "ADOSEDUR" ) - myintervals <- format_pkncadata_intervals(mydose) %>% + myintervals <- format_pkncadata_intervals(pknca_conc = myconc, pknca_dose = mydose) %>% # Filter only the doses requested by the user dplyr::filter( !!sym(dosno_column) %in% input$select_dosno @@ -484,6 +484,7 @@ observeEvent(input$nca, { res_nca <- reactiveVal(NULL) observeEvent(pk_nca_trigger(), { req(mydata()) + withProgress(message = "Calculating NCA...", value = 0, { myres <- PKNCA::pk.nca(data = mydata(), verbose = FALSE) @@ -492,7 +493,8 @@ observeEvent(pk_nca_trigger(), { # Make the starts and ends of results relative to last dose using the dose data myres$result <- myres$result %>% - inner_join(select(mydata()$dose$data, -exclude)) %>% + left_join(select(mydata()$dose$data, any_of(c(unname(unlist(mydata()$dose$columns$groups)), + mydata()$dose$columns$time)))) %>% mutate(start = start - !!sym(mydata()$dose$columns$time), end = end - !!sym(mydata()$dose$columns$time)) %>% select(names(myres$result)) @@ -520,6 +522,7 @@ final_res_nca <- reactiveVal(NULL) # creative final_res_nca, aiming to present the results in a more comprehensive way observeEvent(res_nca(), { req(res_nca()) + # Create a reshaped object that will be used to display the results in the UI final_res_nca <- pivot_wider_pknca_results(res_nca()) diff --git a/man/format_pkncadata_intervals.Rd b/man/format_pkncadata_intervals.Rd index cc48e49..c1e1b49 100644 --- a/man/format_pkncadata_intervals.Rd +++ b/man/format_pkncadata_intervals.Rd @@ -5,6 +5,7 @@ \title{Create Dose Intervals Dataset} \usage{ format_pkncadata_intervals( + pknca_conc, pknca_dose, params = c("aucinf.obs", "aucint.last", "auclast", "cmax", "half.life", "tmax", "lambda.z", "lambda.z.n.points", "r.squared", "adj.r.squared", "lambda.z.time.first", @@ -13,11 +14,13 @@ format_pkncadata_intervals( ) } \arguments{ +\item{pknca_conc}{A PKNCAdose object containing the concentration data.} + +\item{pknca_dose}{A PKNCAdose object containing the dose data.} + \item{params}{A character vector specifying the pharmacokinetic parameters to include.} \item{start_from_last_dose}{Logical defining if start is at time of last dose or C1.} - -\item{df_dose}{A PKNCAdose object} } \value{ A data frame containing the dose intervals and specified pharmacokinetic parameters. @@ -36,7 +39,7 @@ The function performs the following steps: \examples{ \dontrun{ # Example usage: - dose_intervals <- format_pkncadata_intervals(df_dose, params) + dose_intervals <- format_pkncadata_intervals(pknca_conc, pknca_dose, params) } } diff --git a/tests/testthat/test-format_data.R b/tests/testthat/test-format_data.R index 114057d..a7b4123 100644 --- a/tests/testthat/test-format_data.R +++ b/tests/testthat/test-format_data.R @@ -198,11 +198,39 @@ test_that("format_pkncadose_data handles empty input", { }) test_that("format_pkncadata_intervals handles incorrect input type", { - mydose <- list(data = data.frame(STUDYID = 1, USUBJID = 1, TIME = 0), - columns = list(groups = c("STUDYID", "USUBJID"), - time = "TIME")) - expect_error(format_pkncadata_intervals(mydose), - regexp = "Input must be a PKNCAdose object from the PKNCA package.") + ADNCA <- data.frame( + STUDYID = rep(1, 20), + USUBJID = rep(1:2, each = 10), + PCSPEC = rep("Plasma", 20), + DRUG = rep("DrugA", 20), + ANALYTE = rep("Analyte1", 20), + AFRLT = rep(seq(0, 9), 2), + ARRLT = rep(seq(0, 4), 4), + NFRLT = rep(seq(0, 9), 2), + ROUTE = "extravascular", + DOSEA = 10, + ADOSEDUR = 0, + AVAL = runif(20) + ) + + # Call format_pkncaconc_data + df_conc <- format_pkncaconc_data(ADNCA, + group_columns = c("STUDYID", "USUBJID", "PCSPEC", + "DRUG", "ANALYTE"), + time_column = "AFRLT") + + # Generate PKNCAconc and PKNCAdose objects + pknca_conc <- PKNCA::PKNCAconc( + df_conc, + formula = AVAL ~ TIME | STUDYID + PCSPEC + DRUG + USUBJID / ANALYTE, + exclude_half.life = "exclude_half.life", + time.nominal = "NFRLT" + ) + expect_error(format_pkncadata_intervals(pknca_conc = df_conc, data.frame()), + regexp = "Input pknca_conc must be a PKNCAconc object from the PKNCA package.") + + expect_error(format_pkncadata_intervals(pknca_conc = pknca_conc, data.frame()), + regexp = "Input pknca_dose must be a PKNCAdose object from the PKNCA package.") }) test_that("format_pkncadose_data handles negative time values", { @@ -257,7 +285,7 @@ test_that("format_pkncadata_intervals generates correct dataset", { DRUG = rep("DrugA", 20), ANALYTE = rep("Analyte1", 20), AFRLT = rep(seq(0, 9), 2), - ARRLT = c(rep(seq(0, 4), 2), rep(seq(5, 9), 2)), + ARRLT = rep(seq(0, 4), 4), NFRLT = rep(seq(0, 9), 2), ROUTE = "extravascular", DOSEA = 10, @@ -279,14 +307,14 @@ test_that("format_pkncadata_intervals generates correct dataset", { since_lastdose_time_column = "ARRLT") # Generate PKNCAconc and PKNCAdose objects - myconc <- PKNCA::PKNCAconc( + pknca_conc <- PKNCA::PKNCAconc( df_conc, formula = AVAL ~ TIME | STUDYID + PCSPEC + DRUG + USUBJID / ANALYTE, exclude_half.life = "exclude_half.life", time.nominal = "NFRLT" ) - mydose <- PKNCA::PKNCAdose( + pknca_dose <- PKNCA::PKNCAdose( data = df_dose, formula = DOSEA ~ TIME | STUDYID + PCSPEC + DRUG + USUBJID, route = "ROUTE", @@ -298,7 +326,10 @@ test_that("format_pkncadata_intervals generates correct dataset", { params <- c("cmax", "tmax", "half.life", "cl.obs") # Call format_pkncadata_intervals - myintervals <- format_pkncadata_intervals(mydose, params = params, start_from_last_dose = TRUE) + myintervals <- format_pkncadata_intervals(pknca_conc, + pknca_dose, + params = params, + start_from_last_dose = TRUE) # Test if myintervals is a data frame expect_s3_class(myintervals, "data.frame") @@ -313,15 +344,98 @@ test_that("format_pkncadata_intervals generates correct dataset", { # Test if myintervals can be used with PKNCAdata by testing its output expect_no_error( PKNCA::PKNCAdata( - data.conc = myconc, - data.dose = mydose, + data.conc = pknca_conc, + data.dose = pknca_dose, intervals = myintervals, units = PKNCA::pknca_units_table( - concu = myconc$data$PCSTRESU[1], - doseu = myconc$data$DOSEU[1], - amountu = myconc$data$PCSTRESU[1], - timeu = myconc$data$RRLTU[1] + concu = pknca_conc$data$PCSTRESU[1], + doseu = pknca_conc$data$DOSEU[1], + amountu = pknca_conc$data$PCSTRESU[1], + timeu = pknca_conc$data$RRLTU[1] + ) + ) + ) +}) + +test_that("format_pkncadata_intervals handles multiple analytes with metabolites", { + + # Create mock data with multiple analytes and metabolites + ADNCA <- data.frame( + STUDYID = rep(1, 20), + USUBJID = rep(1, 20), + PCSPEC = rep("Plasma", 20), + DRUG = rep("DrugA", 20), + DOSEA = 10, + ANALYTE = rep(c("Analyte1", "Metabolite1"), each = 10), + AFRLT = rep(seq(0, 9), 2), + ARRLT = rep(seq(0, 4), 4), + AVAL = c(rep(seq(0, 8, 2), 2), + rep(seq(0, 4), 2)) + ) + + # Call format_pkncaconc_data + df_conc <- format_pkncaconc_data(ADNCA, + group_columns = c("STUDYID", "USUBJID", "PCSPEC", + "DRUG", "ANALYTE"), + time_column = "AFRLT") + + # Call format_pkncadose_data + df_dose <- format_pkncadose_data(df_conc, + group_columns = c("STUDYID", "USUBJID", "PCSPEC", + "DRUG"), + time_column = "AFRLT", + since_lastdose_time_column = "ARRLT") + + # Generate PKNCAconc and PKNCAdose objects + pknca_conc <- PKNCA::PKNCAconc( + df_conc, + formula = AVAL ~ TIME | STUDYID + PCSPEC + DRUG + USUBJID / ANALYTE, + exclude_half.life = "exclude_half.life", + time.nominal = "NFRLT" + ) + + pknca_dose <- PKNCA::PKNCAdose( + data = df_dose, + formula = DOSEA ~ TIME | STUDYID + PCSPEC + DRUG + USUBJID + ) + + # Define the parameters for the dose intervals + params <- c("cmax", "tmax", "half.life", "cl.obs") + + # Test function + result <- format_pkncadata_intervals(pknca_conc, pknca_dose, params = params) + + expect_equal( + as.data.frame(result), + data.frame( + start = c(0, 0, 5, 5), + end = c(5, 5, Inf, Inf), + STUDYID = c(1, 1, 1, 1), + PCSPEC = c("Plasma", "Plasma", "Plasma", "Plasma"), + DRUG = c("DrugA", "DrugA", "DrugA", "DrugA"), + USUBJID = c(1, 1, 1, 1), + ANALYTE = c("Analyte1", "Metabolite1", "Analyte1", "Metabolite1"), + cmax = c(TRUE, TRUE, TRUE, TRUE), + tmax = c(TRUE, TRUE, TRUE, TRUE), + half.life = c(TRUE, TRUE, TRUE, TRUE), + cl.obs = c(TRUE, TRUE, TRUE, TRUE), + type_interval = c("main", "main", "main", "main") + ) + ) + + # Test if result can be used with PKNCAdata by testing its output + expect_no_error( + PKNCA::PKNCAdata( + data.conc = pknca_conc, + data.dose = pknca_dose, + intervals = result, + units = PKNCA::pknca_units_table( + concu = "ng/mL", + doseu = "mg", + amountu = "ng", + timeu = "h" ) ) ) }) +