Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bug multiple analyte metabolite #193

Merged
57 changes: 46 additions & 11 deletions R/format_data.R
Original file line number Diff line number Diff line change
Expand Up @@ -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.
#'
Expand All @@ -119,21 +120,26 @@ 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",
"adj.r.squared", "lambda.z.time.first",
"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)
Expand All @@ -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)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me, thanks! Small nitpick: could you add some comments to this section to make it easier to undesrtand the workflow :)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice I will add the comments and rebase your branch!


# 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)) %>%
Expand Down
7 changes: 5 additions & 2 deletions inst/shiny/tabs/nca.R
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)

Expand All @@ -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))
Expand Down Expand Up @@ -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())

Expand Down
9 changes: 6 additions & 3 deletions man/format_pkncadata_intervals.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

144 changes: 129 additions & 15 deletions tests/testthat/test-format_data.R
Original file line number Diff line number Diff line change
Expand Up @@ -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", {
Expand Down Expand Up @@ -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,
Expand All @@ -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",
Expand All @@ -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")
Expand All @@ -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"
)
)
)
})