diff --git a/NEWS.md b/NEWS.md index 2eaadb2b..b1f3d1f8 100644 --- a/NEWS.md +++ b/NEWS.md @@ -28,6 +28,20 @@ the dosing including dose amount and route. * 9 volume of distribution at steady state parameters (`vss.*`) * 13 terminal volume of distribution parameters (`vz.*`) +## Features added + +* `add.interval.col()` gains `pptestcd_cdisc` and `pptest_cdisc` arguments for + CDISC standard parameter code and name mappings. Route-dependent parameters + (CL, VZ, MRT, VSS) accept a nested list to distinguish intravascular and + extravascular CDISC codes (#403) +* `as.data.frame.PKNCAresults()` gains `out_format = "cdisc"` to translate + PPTESTCD to CDISC standard codes and add a PPTEST column. Route-dependent + translations are resolved from the dose data (#403) +* When `out_format = "cdisc"` and any parameter has "INT" in its PPTESTCD, + PPSTINT and PPENINT columns are added with ISO 8601 durations relative to + the last dose time. The time unit is taken from `timeu_pref` or `timeu` + (#403) + ## Bug Fixes * `normalize.data.frame()` no longer triggers a dplyr deprecation warning diff --git a/R/001-add.interval.col.R b/R/001-add.interval.col.R index da794d84..47cda7a2 100644 --- a/R/001-add.interval.col.R +++ b/R/001-add.interval.col.R @@ -23,6 +23,13 @@ assign("interval.cols", list(), envir=.PKNCAEnv) #' to NCA parameter names. See the details for information on use of #' `formalsmap`. #' @param datatype The type of data used for the calculation +#' @param pptestcd_cdisc The CDISC PPTESTCD code for this parameter. Can be a +#' character string for simple mappings, or a named list for route-dependent +#' mappings (e.g., `list(route = list(extravascular = "CLF/FO", intravascular +#' = "CLO"))`). Defaults to `name` if not provided. +#' @param pptest_cdisc The CDISC PPTEST name for this parameter. Can be a +#' character string or a named list (same structure as `pptestcd_cdisc`). +#' Defaults to `desc` if not provided. #' @returns NULL (Calling this function has a side effect of changing the #' available intervals for calculations) #' @@ -90,7 +97,9 @@ add.interval.col <- function(name, formalsmap=list(), datatype=c("interval", "individual", - "population")) { + "population"), + pptestcd_cdisc=NULL, + pptest_cdisc=NULL) { # Check inputs if (!is.character(name)) { stop("name must be a character string") @@ -159,6 +168,21 @@ add.interval.col <- function(name, stop("All names for the formalsmap list must be arguments to the function.") } } + # Default CDISC mappings to name/desc when not provided + if (is.null(pptestcd_cdisc)) { + pptestcd_cdisc <- name + } + if (is.null(pptest_cdisc)) { + pptest_cdisc <- desc + } + # Validate CDISC arguments: must be a character string or a named list + # with a "route" element containing named sub-elements + if (!is.character(pptestcd_cdisc) && !is.list(pptestcd_cdisc)) { + stop("pptestcd_cdisc must be a character string or a list") + } + if (!is.character(pptest_cdisc) && !is.list(pptest_cdisc)) { + stop("pptest_cdisc must be a character string or a list") + } current <- get("interval.cols", envir=.PKNCAEnv) current[[name]] <- list( @@ -170,7 +194,9 @@ add.interval.col <- function(name, sparse=sparse, formalsmap=formalsmap, depends=depends, - datatype=datatype + datatype=datatype, + pptestcd_cdisc=pptestcd_cdisc, + pptest_cdisc=pptest_cdisc ) assign("interval.cols", current, envir=.PKNCAEnv) } diff --git a/R/auc.R b/R/auc.R index 3d7e6c6b..f79fc5a9 100644 --- a/R/auc.R +++ b/R/auc.R @@ -315,7 +315,9 @@ add.interval.col("aucinf.obs", unit_type="auc", pretty_name="AUCinf,obs", desc="The area under the concentration time curve from the beginning of the interval to infinity with extrapolation to infinity from the observed Clast", - depends=c("lambda.z", "clast.obs")) + depends=c("lambda.z", "clast.obs"), + pptestcd_cdisc="AUCIFO", + pptest_cdisc="AUC Infinity Obs") add.interval.col("aucinf.pred", FUN="pk.calc.auc.inf.pred", @@ -323,21 +325,27 @@ add.interval.col("aucinf.pred", unit_type="auc", pretty_name="AUCinf,pred", desc="The area under the concentration time curve from the beginning of the interval to infinity with extrapolation to infinity from the predicted Clast", - depends=c("lambda.z", "clast.pred")) + depends=c("lambda.z", "clast.pred"), + pptestcd_cdisc="AUCIFP", + pptest_cdisc="AUC Infinity Pred") add.interval.col("auclast", FUN="pk.calc.auc.last", values=c(FALSE, TRUE), unit_type="auc", pretty_name="AUClast", - desc="The area under the concentration time curve from the beginning of the interval to the last concentration above the limit of quantification") + desc="The area under the concentration time curve from the beginning of the interval to the last concentration above the limit of quantification", + pptestcd_cdisc="AUCLST", + pptest_cdisc="AUC to Last Nonzero Conc") add.interval.col("aucall", FUN="pk.calc.auc.all", values=c(FALSE, TRUE), unit_type="auc", pretty_name="AUCall", - desc="The area under the concentration time curve from the beginning of the interval to the last concentration above the limit of quantification plus the triangle from that last concentration to 0 at the first concentration below the limit of quantification" + desc="The area under the concentration time curve from the beginning of the interval to the last concentration above the limit of quantification plus the triangle from that last concentration to 0 at the first concentration below the limit of quantification", + pptestcd_cdisc="AUCALL", + pptest_cdisc="AUC All" ) add.interval.col("aumcinf.obs", @@ -346,7 +354,9 @@ add.interval.col("aumcinf.obs", unit_type="aumc", pretty_name="AUMC,inf,obs", desc="The area under the concentration time moment curve from the beginning of the interval to infinity with extrapolation to infinity from the observed Clast", - depends=c("lambda.z", "clast.obs")) + depends=c("lambda.z", "clast.obs"), + pptestcd_cdisc="AUMCIFO", + pptest_cdisc="AUMC Infinity Obs") add.interval.col("aumcinf.pred", FUN="pk.calc.aumc.inf.pred", @@ -354,21 +364,27 @@ add.interval.col("aumcinf.pred", unit_type="aumc", pretty_name="AUMC,inf,pred", desc="The area under the concentration time moment curve from the beginning of the interval to infinity with extrapolation to infinity from the predicted Clast", - depends=c("lambda.z", "clast.pred")) + depends=c("lambda.z", "clast.pred"), + pptestcd_cdisc="AUMCIFP", + pptest_cdisc="AUMC Infinity Pred") add.interval.col("aumclast", FUN="pk.calc.aumc.last", values=c(FALSE, TRUE), unit_type="aumc", pretty_name="AUMC,last", - desc="The area under the concentration time moment curve from the beginning of the interval to the last concentration above the limit of quantification") + desc="The area under the concentration time moment curve from the beginning of the interval to the last concentration above the limit of quantification", + pptestcd_cdisc="AUMCLST", + pptest_cdisc="AUMC to Last Nonzero Conc") add.interval.col("aumcall", FUN="pk.calc.aumc.all", values=c(FALSE, TRUE), unit_type="aumc", pretty_name="AUMC,all", - desc="The area under the concentration time moment curve from the beginning of the interval to the last concentration above the limit of quantification plus the moment of the triangle from that last concentration to 0 at the first concentration below the limit of quantification") + desc="The area under the concentration time moment curve from the beginning of the interval to the last concentration above the limit of quantification plus the moment of the triangle from that last concentration to 0 at the first concentration below the limit of quantification", + pptestcd_cdisc="AUMCALL", + pptest_cdisc="AUMC All") PKNCA.set.summary( name= diff --git a/R/aucint.R b/R/aucint.R index 1acad839..5c343c68 100644 --- a/R/aucint.R +++ b/R/aucint.R @@ -1,480 +1,496 @@ -#' Calculate AUXC (AUC or AUMC) over an interval with interpolation/extrapolation -#' -#' Calculates AUC or AUMC over a given interval, optionally interpolating or -#' extrapolating concentrations. -#' -#' @details -#' When `pk.calc.auxcint()` needs to extrapolate using `lambda.z` (in other -#' words, using the half-life), it will always extrapolate using the logarithmic -#' trapezoidal rule to align with using a half-life calculation for the -#' extrapolation. -#' -#' @inheritParams pk.calc.auxc -#' @inheritParams assert_intervaltime_single -#' @inheritParams assert_lambdaz -#' @param clast,clast.obs,clast.pred The last concentration above the limit of -#' quantification; this is used for AUCinf calculations. If provided as -#' `clast.obs` (observed clast value, default), AUCinf is AUCinf,obs. If -#' provided as `clast.pred`, AUCinf is AUCinf,pred. -#' @param time.dose,route,duration.dose The time of doses, route of -#' administration, and duration of dose used with interpolation and -#' extrapolation of concentration data (see [interp.extrap.conc.dose()]). -#' If `NULL`, [interp.extrap.conc()] will be used instead. -#' @param fun_linear,fun_log,fun_inf Integration functions for linear, -#' logarithmic, and infinite extrapolation methods. -#' @param ... Additional arguments passed to `pk.calc.auxc` and -#' `interp.extrap.conc` -#' -#' @return The AUXC for an interval of time as a number -#' -#' @family AUC calculations -#' @family AUMC calculations -#' @seealso [PKNCA.options()], [interp.extrap.conc.dose()] -#' @export -pk.calc.auxcint <- function(conc, time, - interval=NULL, start=NULL, end=NULL, - clast=pk.calc.clast.obs(conc, time), - lambda.z=NA, - time.dose=NULL, - route="extravascular", - duration.dose=0, - auc.type=c("AUClast", "AUCinf", "AUCall"), - options=list(), - method=NULL, - conc.blq=NULL, - conc.na=NULL, - check=TRUE, - fun_linear, - fun_log, - fun_inf, - ...) { - # Check inputs - auc.type <- match.arg(auc.type) - method <- PKNCA.choose.option(name="auc.method", value=method, options=options) - if (check) { - assert_conc_time(conc, time) - data <- - clean.conc.blq( - conc = conc, time = time, - conc.blq = conc.blq, conc.na = conc.na, options = options, - check = FALSE - ) - } else { - data <- data.frame(conc, time) - } - if (all(data$conc %in% 0)) { - return(structure(0, exclude = "DO NOT EXCLUDE")) - } - interval <- assert_intervaltime_single(interval = interval, start = start, end = end) - missing_times <- - if (is.infinite(interval[2])) { - setdiff(c(interval[1], time.dose), data$time) - } else { - setdiff(c(interval, time.dose), data$time) - } - # Handle the potential double-calculation (before/after tlast) with AUCinf/AUMCinf - conc_clast <- NULL - time_clast <- NULL - if (auc.type %in% "AUCinf") { - tlast <- pk.calc.tlast(conc=data$conc, time=data$time) - clast_obs <- pk.calc.clast.obs(conc=data$conc, time=data$time) - if (is.na(clast) && is.na(lambda.z)) { - # clast.pred is NA likely because the half-life was not calculable - return(structure(NA_real_, exclude = "clast.pred is NA because the half-life is NA")) - } else if (is.na(clast)) { - stop("Please report a bug. clast is NA and the half-life is not NA") # nocov - } else if (clast != clast_obs && interval[2] > tlast) { - # If using clast.pred, we need to doubly calculate at tlast. - conc_clast <- clast - time_clast <- tlast - } - } - extrap_times <- numeric() - if (length(missing_times) > 0) { - if (is.null(time.dose)) { - missing_conc <- - interp.extrap.conc( - conc = data$conc, time = data$time, - time.out = missing_times, - method = method, - auc.type = auc.type, - clast = clast, - lambda.z = lambda.z, - options = options, - ... - ) - } else { - missing_conc <- - interp.extrap.conc.dose( - conc = data$conc, time = data$time, - time.out = missing_times, - method = method, - auc.type = auc.type, - clast = clast, lambda.z = lambda.z, - options = options, - # arguments specific to interp.extrap.conc.dose - time.dose = time.dose, - route.dose = route, - duration.dose = duration.dose, - out.after = FALSE, - ... - ) - } - new_data <- data.frame(conc=c(data$conc, conc_clast, missing_conc), - time=c(data$time, time_clast, missing_times)) - tlast <- pk.calc.tlast(conc = data$conc, time = data$time, check = FALSE) - extrap_times <- missing_times[missing_times > tlast] - new_data <- new_data[new_data$time >= interval[1] & - new_data$time <= interval[2],] - new_data <- new_data[order(new_data$time),] - conc_interp <- new_data$conc - time_interp <- new_data$time - if (any(mask_na_conc <- is.na(conc_interp))) { - missing_times <- time_interp[mask_na_conc] - warning_message <- - if (any(is.na(lambda.z))) { - paste("Some interpolated/extrapolated concentration values are missing", - "(may be due to interpolating or extrapolating over a dose with lambda.z=NA).", - "Time points with missing data are: ", - paste(missing_times, collapse=", ")) - } else { - paste("Some interpolated/extrapolated concentration values are missing", - "Time points with missing data are: ", - paste(missing_times, collapse=", ")) - } - warning(warning_message) - return(NA_real_) - } - } else { - mask_time <- data$time >= interval[1] & data$time <= interval[2] - conc_interp <- data$conc[mask_time] - time_interp <- data$time[mask_time] - } - - interval_method <- - choose_interval_method( - conc = conc_interp, - time = time_interp, - tlast = max(time_interp), - method = method, - auc.type = auc.type, - options = options - ) - if (is.finite(interval[2])) { - interval_method[length(interval_method)] <- "zero" - } - if (length(extrap_times) > 0) { - interval_method[which(time_interp == extrap_times) - 1] <- "log" - } - ret <- - auc_integrate( - conc = conc_interp, time = time_interp, - clast = clast, tlast = tlast, lambda.z = lambda.z, - interval_method = interval_method, - fun_linear = fun_linear, - fun_log = fun_log, - fun_inf = fun_inf - ) - # Add method details as an attribute - attr(ret, "method") <- paste0("AUC: ", method) - - ret -} - -#' @describeIn pk.calc.auxcint Calculate AUC over an interval -#' @export -pk.calc.aucint <- function(conc, time, ..., options=list()) { - pk.calc.auxcint( - conc = conc, time = time, ..., - options = options, - fun_linear = aucintegrate_linear, - fun_log = aucintegrate_log, - fun_inf = aucintegrate_inf - ) -} - -#' @describeIn pk.calc.auxcint Interpolate or extrapolate concentrations for -#' AUClast -#' @export -pk.calc.aucint.last <- function(conc, time, start=NULL, end=NULL, time.dose, ..., options=list()) { - if (missing(time.dose)) - time.dose <- NULL - pk.calc.aucint(conc=conc, time=time, - start=start, end=end, - options=options, - time.dose=time.dose, - ..., - auc.type="AUClast") -} -#' @describeIn pk.calc.auxcint Interpolate or extrapolate concentrations for -#' AUCall -#' @export -pk.calc.aucint.all <- function(conc, time, start=NULL, end=NULL, time.dose, ..., options=list()) { - if (missing(time.dose)) - time.dose <- NULL - pk.calc.aucint(conc=conc, time=time, - start=start, end=end, - options=options, - time.dose=time.dose, - ..., - auc.type="AUCall") -} -#' @describeIn pk.calc.auxcint Interpolate or extrapolate concentrations for -#' AUCinf.obs -#' @export -pk.calc.aucint.inf.obs <- function(conc, time, start=NULL, end=NULL, time.dose, lambda.z, clast.obs, ..., options=list()) { - if (missing(time.dose)) - time.dose <- NULL - pk.calc.aucint(conc=conc, time=time, - start=start, end=end, - time.dose=time.dose, - lambda.z=lambda.z, clast=clast.obs, - options=options, ..., - auc.type="AUCinf") -} -#' @describeIn pk.calc.auxcint Interpolate or extrapolate concentrations for -#' AUCinf.pred -#' @export -pk.calc.aucint.inf.pred <- function(conc, time, start=NULL, end=NULL, time.dose, lambda.z, clast.pred, ..., options=list()) { - if (missing(time.dose)) { - time.dose <- NULL - } - pk.calc.aucint(conc=conc, time=time, - start=start, end=end, - time.dose=time.dose, - lambda.z=lambda.z, clast=clast.pred, - options=options, ..., - auc.type="AUCinf") -} - -add.interval.col("aucint.last", - FUN="pk.calc.aucint.last", - values=c(FALSE, TRUE), - unit_type="auc", - pretty_name="AUCint (based on AUClast extrapolation)", - desc="The area under the concentration time curve in the interval extrapolating from Tlast to infinity with zeros (matching AUClast)", - formalsmap=list(conc="conc.group", time="time.group", time.dose=NULL)) - -add.interval.col("aucint.last.dose", - FUN="pk.calc.aucint.last", - values=c(FALSE, TRUE), - unit_type="auc", - pretty_name="AUCint (based on AUClast extrapolation, dose-aware)", - desc="The area under the concentration time curve in the interval extrapolating from Tlast to infinity with zeros (matching AUClast) with dose-aware interpolation/extrapolation of concentrations", - formalsmap=list(conc="conc.group", time="time.group", time.dose="time.dose.group")) - -add.interval.col("aucint.all", - FUN="pk.calc.aucint.all", - values=c(FALSE, TRUE), - unit_type="auc", - pretty_name="AUCint (based on AUCall extrapolation)", - desc="The area under the concentration time curve in the interval extrapolating from Tlast to infinity with the triangle from Tlast to the next point and zero thereafter (matching AUCall)", - formalsmap=list(conc="conc.group", time="time.group", time.dose=NULL)) - -add.interval.col("aucint.all.dose", - FUN="pk.calc.aucint.all", - values=c(FALSE, TRUE), - unit_type="auc", - pretty_name="AUCint (based on AUCall extrapolation, dose-aware)", - desc="The area under the concentration time curve in the interval extrapolating from Tlast to infinity with the triangle from Tlast to the next point and zero thereafter (matching AUCall) with dose-aware interpolation/extrapolation of concentrations", - formalsmap=list(conc="conc.group", time="time.group", time.dose="time.dose.group")) - -add.interval.col("aucint.inf.obs", - FUN="pk.calc.aucint.inf.obs", - values=c(FALSE, TRUE), - unit_type="auc", - pretty_name="AUCint (based on AUCinf,obs extrapolation)", - desc="The area under the concentration time curve in the interval extrapolating from Tlast to infinity with zeros (matching AUClast)", - formalsmap=list(conc="conc.group", time="time.group", time.dose=NULL), - depends=c("lambda.z", "clast.obs")) - -add.interval.col("aucint.inf.obs.dose", - FUN="pk.calc.aucint.inf.obs", - values=c(FALSE, TRUE), - unit_type="auc", - pretty_name="AUCint (based on AUCinf,obs extrapolation, dose-aware)", - desc="The area under the concentration time curve in the interval extrapolating from Tlast to infinity with zeros (matching AUClast) with dose-aware interpolation/extrapolation of concentrations", - formalsmap=list(conc="conc.group", time="time.group", time.dose="time.dose.group"), - depends=c("lambda.z", "clast.obs")) - -add.interval.col("aucint.inf.pred", - FUN="pk.calc.aucint.inf.pred", - values=c(FALSE, TRUE), - unit_type="auc", - pretty_name="AUCint (based on AUCinf,pred extrapolation)", - desc="The area under the concentration time curve in the interval extrapolating from Tlast to infinity with the triangle from Tlast to the next point and zero thereafter (matching AUCall)", - formalsmap=list(conc="conc.group", time="time.group", time.dose=NULL), - depends=c("lambda.z", "clast.pred")) - -add.interval.col("aucint.inf.pred.dose", - FUN="pk.calc.aucint.inf.pred", - values=c(FALSE, TRUE), - unit_type="auc", - pretty_name="AUCint (based on AUCinf,pred extrapolation, dose-aware)", - desc="The area under the concentration time curve in the interval extrapolating from Tlast to infinity with the triangle from Tlast to the next point and zero thereafter (matching AUCall) with dose-aware interpolation/extrapolation of concentrations", - formalsmap=list(conc="conc.group", time="time.group", time.dose="time.dose.group"), - depends=c("lambda.z", "clast.pred")) - - -#' @describeIn pk.calc.auxcint Calculate AUMC over an interval -#' @export -pk.calc.aumcint <- function(conc, time, ..., options=list()) { - pk.calc.auxcint( - conc = conc, time = time, ..., - options = options, - fun_linear = aumcintegrate_linear, - fun_log = aumcintegrate_log, - fun_inf = aumcintegrate_inf - ) -} - -#' @describeIn pk.calc.auxcint Interpolate or extrapolate concentrations for -#' AUMClast -#' @export -pk.calc.aumcint.last <- function(conc, time, start=NULL, end=NULL, time.dose, ..., options=list()) { - if (missing(time.dose)) - time.dose <- NULL - pk.calc.aumcint(conc=conc, time=time, - start=start, end=end, - options=options, - time.dose=time.dose, - ..., - auc.type="AUClast") -} - -#' @describeIn pk.calc.auxcint Interpolate or extrapolate concentrations for -#' AUMCall -#' @export -pk.calc.aumcint.all <- function(conc, time, start=NULL, end=NULL, time.dose, ..., options=list()) { - if (missing(time.dose)) - time.dose <- NULL - pk.calc.aumcint(conc=conc, time=time, - start=start, end=end, - options=options, - time.dose=time.dose, - ..., - auc.type="AUCall") -} - -#' @describeIn pk.calc.auxcint Interpolate or extrapolate concentrations for -#' AUMCinf.obs -#' @export -pk.calc.aumcint.inf.obs <- function(conc, time, start=NULL, end=NULL, time.dose, lambda.z, clast.obs, ..., options=list()) { - if (missing(time.dose)) - time.dose <- NULL - pk.calc.aumcint(conc=conc, time=time, - start=start, end=end, - time.dose=time.dose, - lambda.z=lambda.z, clast=clast.obs, - options=options, ..., - auc.type="AUCinf") -} - -#' @describeIn pk.calc.auxcint Interpolate or extrapolate concentrations for -#' AUMCinf.pred -#' @export -pk.calc.aumcint.inf.pred <- function(conc, time, start=NULL, end=NULL, time.dose, lambda.z, clast.pred, ..., options=list()) { - if (missing(time.dose)) - time.dose <- NULL - pk.calc.aumcint(conc=conc, time=time, - start=start, end=end, - time.dose=time.dose, - lambda.z=lambda.z, clast=clast.pred, - options=options, ..., - auc.type="AUCinf") -} - - -# aumcint.last (without dose awareness) -add.interval.col("aumcint.last", - FUN="pk.calc.aumcint.last", - values=c(FALSE, TRUE), - unit_type="aumc", - pretty_name="AUMCint (based on AUMClast extrapolation)", - desc="The area under the moment curve in the interval extrapolating from Tlast to infinity with zeros (matching AUMClast)", - formalsmap=list(conc="conc.group", time="time.group", time.dose=NULL)) - -# aumcint.last.dose (WITH dose awareness) -add.interval.col("aumcint.last.dose", - FUN="pk.calc.aumcint.last", - values=c(FALSE, TRUE), - unit_type="aumc", - pretty_name="AUMCint (based on AUMClast extrapolation, dose-aware)", - desc="The area under the moment curve in the interval extrapolating from Tlast to infinity with zeros (matching AUMClast) with dose-aware interpolation/extrapolation of concentrations", - formalsmap=list(conc="conc.group", time="time.group", time.dose="time.dose.group")) - -# aumcint.all (without dose awareness) -add.interval.col("aumcint.all", - FUN="pk.calc.aumcint.all", - values=c(FALSE, TRUE), - unit_type="aumc", - pretty_name="AUMCint (based on AUMCall extrapolation)", - desc="The area under the moment curve in the interval extrapolating from Tlast to infinity with the triangle from Tlast to the next point and zero thereafter (matching AUMCall)", - formalsmap=list(conc="conc.group", time="time.group", time.dose=NULL)) - -# aumcint.all.dose (WITH dose awareness) -add.interval.col("aumcint.all.dose", - FUN="pk.calc.aumcint.all", - values=c(FALSE, TRUE), - unit_type="aumc", - pretty_name="AUMCint (based on AUMCall extrapolation, dose-aware)", - desc="The area under the moment curve in the interval extrapolating from Tlast to infinity with the triangle from Tlast to the next point and zero thereafter (matching AUMCall) with dose-aware interpolation/extrapolation of concentrations", - formalsmap=list(conc="conc.group", time="time.group", time.dose="time.dose.group")) - -# aumcint.inf.obs (without dose awareness) -add.interval.col("aumcint.inf.obs", - FUN="pk.calc.aumcint.inf.obs", - values=c(FALSE, TRUE), - unit_type="aumc", - pretty_name="AUMCint (based on AUMCinf,obs extrapolation)", - desc="The area under the moment curve in the interval extrapolating from Tlast to infinity with zeros (matching AUMClast)", - formalsmap=list(conc="conc.group", time="time.group", time.dose=NULL), - depends=c("lambda.z", "clast.obs")) - -# aumcint.inf.obs.dose (WITH dose awareness) -add.interval.col("aumcint.inf.obs.dose", - FUN="pk.calc.aumcint.inf.obs", - values=c(FALSE, TRUE), - unit_type="aumc", - pretty_name="AUMCint (based on AUMCinf,obs extrapolation, dose-aware)", - desc="The area under the moment curve in the interval extrapolating from Tlast to infinity with zeros (matching AUMClast) with dose-aware interpolation/extrapolation of concentrations", - formalsmap=list(conc="conc.group", time="time.group", time.dose="time.dose.group"), - depends=c("lambda.z", "clast.obs")) - -# aumcint.inf.pred (without dose awareness) -add.interval.col("aumcint.inf.pred", - FUN="pk.calc.aumcint.inf.pred", - values=c(FALSE, TRUE), - unit_type="aumc", - pretty_name="AUMCint (based on AUMCinf,pred extrapolation)", - desc="The area under the moment curve in the interval extrapolating from Tlast to infinity with the triangle from Tlast to the next point and zero thereafter (matching AUMCall)", - formalsmap=list(conc="conc.group", time="time.group", time.dose=NULL), - depends=c("lambda.z", "clast.pred")) - -# aumcint.inf.pred.dose (WITH dose awareness) -add.interval.col("aumcint.inf.pred.dose", - FUN="pk.calc.aumcint.inf.pred", - values=c(FALSE, TRUE), - unit_type="aumc", - pretty_name="AUMCint (based on AUMCinf,pred extrapolation, dose-aware)", - desc="The area under the moment curve in the interval extrapolating from Tlast to infinity with the triangle from Tlast to the next point and zero thereafter (matching AUMCall) with dose-aware interpolation/extrapolation of concentrations", - formalsmap=list(conc="conc.group", time="time.group", time.dose="time.dose.group"), - depends=c("lambda.z", "clast.pred")) - -# ============================================================================= -# SET SUMMARY STATISTICS - Count (16) -# ============================================================================= -PKNCA.set.summary( - name= c( - # AUC related - "aucint.last", "aucint.last.dose", "aucint.all", "aucint.all.dose", - "aucint.inf.obs", "aucint.inf.obs.dose", "aucint.inf.pred", "aucint.inf.pred.dose", - - # AUMC related - "aumcint.last", "aumcint.last.dose", "aumcint.all", "aumcint.all.dose", - "aumcint.inf.obs", "aumcint.inf.obs.dose", "aumcint.inf.pred", "aumcint.inf.pred.dose" - ), - description="geometric mean and geometric coefficient of variation", - point=business.geomean, - spread=business.geocv -) +#' Calculate AUXC (AUC or AUMC) over an interval with interpolation/extrapolation +#' +#' Calculates AUC or AUMC over a given interval, optionally interpolating or +#' extrapolating concentrations. +#' +#' @details +#' When `pk.calc.auxcint()` needs to extrapolate using `lambda.z` (in other +#' words, using the half-life), it will always extrapolate using the logarithmic +#' trapezoidal rule to align with using a half-life calculation for the +#' extrapolation. +#' +#' @inheritParams pk.calc.auxc +#' @inheritParams assert_intervaltime_single +#' @inheritParams assert_lambdaz +#' @param clast,clast.obs,clast.pred The last concentration above the limit of +#' quantification; this is used for AUCinf calculations. If provided as +#' `clast.obs` (observed clast value, default), AUCinf is AUCinf,obs. If +#' provided as `clast.pred`, AUCinf is AUCinf,pred. +#' @param time.dose,route,duration.dose The time of doses, route of +#' administration, and duration of dose used with interpolation and +#' extrapolation of concentration data (see [interp.extrap.conc.dose()]). +#' If `NULL`, [interp.extrap.conc()] will be used instead. +#' @param fun_linear,fun_log,fun_inf Integration functions for linear, +#' logarithmic, and infinite extrapolation methods. +#' @param ... Additional arguments passed to `pk.calc.auxc` and +#' `interp.extrap.conc` +#' +#' @return The AUXC for an interval of time as a number +#' +#' @family AUC calculations +#' @family AUMC calculations +#' @seealso [PKNCA.options()], [interp.extrap.conc.dose()] +#' @export +pk.calc.auxcint <- function(conc, time, + interval=NULL, start=NULL, end=NULL, + clast=pk.calc.clast.obs(conc, time), + lambda.z=NA, + time.dose=NULL, + route="extravascular", + duration.dose=0, + auc.type=c("AUClast", "AUCinf", "AUCall"), + options=list(), + method=NULL, + conc.blq=NULL, + conc.na=NULL, + check=TRUE, + fun_linear, + fun_log, + fun_inf, + ...) { + # Check inputs + auc.type <- match.arg(auc.type) + method <- PKNCA.choose.option(name="auc.method", value=method, options=options) + if (check) { + assert_conc_time(conc, time) + data <- + clean.conc.blq( + conc = conc, time = time, + conc.blq = conc.blq, conc.na = conc.na, options = options, + check = FALSE + ) + } else { + data <- data.frame(conc, time) + } + if (all(data$conc %in% 0)) { + return(structure(0, exclude = "DO NOT EXCLUDE")) + } + interval <- assert_intervaltime_single(interval = interval, start = start, end = end) + missing_times <- + if (is.infinite(interval[2])) { + setdiff(c(interval[1], time.dose), data$time) + } else { + setdiff(c(interval, time.dose), data$time) + } + # Handle the potential double-calculation (before/after tlast) with AUCinf/AUMCinf + conc_clast <- NULL + time_clast <- NULL + if (auc.type %in% "AUCinf") { + tlast <- pk.calc.tlast(conc=data$conc, time=data$time) + clast_obs <- pk.calc.clast.obs(conc=data$conc, time=data$time) + if (is.na(clast) && is.na(lambda.z)) { + # clast.pred is NA likely because the half-life was not calculable + return(structure(NA_real_, exclude = "clast.pred is NA because the half-life is NA")) + } else if (is.na(clast)) { + stop("Please report a bug. clast is NA and the half-life is not NA") # nocov + } else if (clast != clast_obs && interval[2] > tlast) { + # If using clast.pred, we need to doubly calculate at tlast. + conc_clast <- clast + time_clast <- tlast + } + } + extrap_times <- numeric() + if (length(missing_times) > 0) { + if (is.null(time.dose)) { + missing_conc <- + interp.extrap.conc( + conc = data$conc, time = data$time, + time.out = missing_times, + method = method, + auc.type = auc.type, + clast = clast, + lambda.z = lambda.z, + options = options, + ... + ) + } else { + missing_conc <- + interp.extrap.conc.dose( + conc = data$conc, time = data$time, + time.out = missing_times, + method = method, + auc.type = auc.type, + clast = clast, lambda.z = lambda.z, + options = options, + # arguments specific to interp.extrap.conc.dose + time.dose = time.dose, + route.dose = route, + duration.dose = duration.dose, + out.after = FALSE, + ... + ) + } + new_data <- data.frame(conc=c(data$conc, conc_clast, missing_conc), + time=c(data$time, time_clast, missing_times)) + tlast <- pk.calc.tlast(conc = data$conc, time = data$time, check = FALSE) + extrap_times <- missing_times[missing_times > tlast] + new_data <- new_data[new_data$time >= interval[1] & + new_data$time <= interval[2],] + new_data <- new_data[order(new_data$time),] + conc_interp <- new_data$conc + time_interp <- new_data$time + if (any(mask_na_conc <- is.na(conc_interp))) { + missing_times <- time_interp[mask_na_conc] + warning_message <- + if (any(is.na(lambda.z))) { + paste("Some interpolated/extrapolated concentration values are missing", + "(may be due to interpolating or extrapolating over a dose with lambda.z=NA).", + "Time points with missing data are: ", + paste(missing_times, collapse=", ")) + } else { + paste("Some interpolated/extrapolated concentration values are missing", + "Time points with missing data are: ", + paste(missing_times, collapse=", ")) + } + warning(warning_message) + return(NA_real_) + } + } else { + mask_time <- data$time >= interval[1] & data$time <= interval[2] + conc_interp <- data$conc[mask_time] + time_interp <- data$time[mask_time] + } + + interval_method <- + choose_interval_method( + conc = conc_interp, + time = time_interp, + tlast = max(time_interp), + method = method, + auc.type = auc.type, + options = options + ) + if (is.finite(interval[2])) { + interval_method[length(interval_method)] <- "zero" + } + if (length(extrap_times) > 0) { + interval_method[which(time_interp == extrap_times) - 1] <- "log" + } + ret <- + auc_integrate( + conc = conc_interp, time = time_interp, + clast = clast, tlast = tlast, lambda.z = lambda.z, + interval_method = interval_method, + fun_linear = fun_linear, + fun_log = fun_log, + fun_inf = fun_inf + ) + # Add method details as an attribute + attr(ret, "method") <- paste0("AUC: ", method) + + ret +} + +#' @describeIn pk.calc.auxcint Calculate AUC over an interval +#' @export +pk.calc.aucint <- function(conc, time, ..., options=list()) { + pk.calc.auxcint( + conc = conc, time = time, ..., + options = options, + fun_linear = aucintegrate_linear, + fun_log = aucintegrate_log, + fun_inf = aucintegrate_inf + ) +} + +#' @describeIn pk.calc.auxcint Interpolate or extrapolate concentrations for +#' AUClast +#' @export +pk.calc.aucint.last <- function(conc, time, start=NULL, end=NULL, time.dose, ..., options=list()) { + if (missing(time.dose)) + time.dose <- NULL + pk.calc.aucint(conc=conc, time=time, + start=start, end=end, + options=options, + time.dose=time.dose, + ..., + auc.type="AUClast") +} +#' @describeIn pk.calc.auxcint Interpolate or extrapolate concentrations for +#' AUCall +#' @export +pk.calc.aucint.all <- function(conc, time, start=NULL, end=NULL, time.dose, ..., options=list()) { + if (missing(time.dose)) + time.dose <- NULL + pk.calc.aucint(conc=conc, time=time, + start=start, end=end, + options=options, + time.dose=time.dose, + ..., + auc.type="AUCall") +} +#' @describeIn pk.calc.auxcint Interpolate or extrapolate concentrations for +#' AUCinf.obs +#' @export +pk.calc.aucint.inf.obs <- function(conc, time, start=NULL, end=NULL, time.dose, lambda.z, clast.obs, ..., options=list()) { + if (missing(time.dose)) + time.dose <- NULL + pk.calc.aucint(conc=conc, time=time, + start=start, end=end, + time.dose=time.dose, + lambda.z=lambda.z, clast=clast.obs, + options=options, ..., + auc.type="AUCinf") +} +#' @describeIn pk.calc.auxcint Interpolate or extrapolate concentrations for +#' AUCinf.pred +#' @export +pk.calc.aucint.inf.pred <- function(conc, time, start=NULL, end=NULL, time.dose, lambda.z, clast.pred, ..., options=list()) { + if (missing(time.dose)) { + time.dose <- NULL + } + pk.calc.aucint(conc=conc, time=time, + start=start, end=end, + time.dose=time.dose, + lambda.z=lambda.z, clast=clast.pred, + options=options, ..., + auc.type="AUCinf") +} + +add.interval.col("aucint.last", + FUN="pk.calc.aucint.last", + values=c(FALSE, TRUE), + unit_type="auc", + pretty_name="AUCint (based on AUClast extrapolation)", + desc="The area under the concentration time curve in the interval extrapolating from Tlast to infinity with zeros (matching AUClast)", + formalsmap=list(conc="conc.group", time="time.group", time.dose=NULL), + pptestcd_cdisc="AUCINT", + pptest_cdisc="AUC from T1 to T2") + +add.interval.col("aucint.last.dose", + FUN="pk.calc.aucint.last", + values=c(FALSE, TRUE), + unit_type="auc", + pretty_name="AUCint (based on AUClast extrapolation, dose-aware)", + desc="The area under the concentration time curve in the interval extrapolating from Tlast to infinity with zeros (matching AUClast) with dose-aware interpolation/extrapolation of concentrations", + formalsmap=list(conc="conc.group", time="time.group", time.dose="time.dose.group"), + pptestcd_cdisc="AUCINTD", + pptest_cdisc="AUC from T1 to T2 Normalized by Dose") + +add.interval.col("aucint.all", + FUN="pk.calc.aucint.all", + values=c(FALSE, TRUE), + unit_type="auc", + pretty_name="AUCint (based on AUCall extrapolation)", + desc="The area under the concentration time curve in the interval extrapolating from Tlast to infinity with the triangle from Tlast to the next point and zero thereafter (matching AUCall)", + formalsmap=list(conc="conc.group", time="time.group", time.dose=NULL), + pptestcd_cdisc="AUCINTA", + pptest_cdisc="AUCint (based on AUCall extrapolation)") + +add.interval.col("aucint.all.dose", + FUN="pk.calc.aucint.all", + values=c(FALSE, TRUE), + unit_type="auc", + pretty_name="AUCint (based on AUCall extrapolation, dose-aware)", + desc="The area under the concentration time curve in the interval extrapolating from Tlast to infinity with the triangle from Tlast to the next point and zero thereafter (matching AUCall) with dose-aware interpolation/extrapolation of concentrations", + formalsmap=list(conc="conc.group", time="time.group", time.dose="time.dose.group"), + pptestcd_cdisc="AUCINTAD", + pptest_cdisc="AUCint (based on AUCall extrapolation, dose-aware)") + +add.interval.col("aucint.inf.obs", + FUN="pk.calc.aucint.inf.obs", + values=c(FALSE, TRUE), + unit_type="auc", + pretty_name="AUCint (based on AUCinf,obs extrapolation)", + desc="The area under the concentration time curve in the interval extrapolating from Tlast to infinity with zeros (matching AUClast)", + formalsmap=list(conc="conc.group", time="time.group", time.dose=NULL), + depends=c("lambda.z", "clast.obs"), + pptestcd_cdisc="AUCINTIS", + pptest_cdisc="AUCint (based on AUCinf,obs extrapolation)") + +add.interval.col("aucint.inf.obs.dose", + FUN="pk.calc.aucint.inf.obs", + values=c(FALSE, TRUE), + unit_type="auc", + pretty_name="AUCint (based on AUCinf,obs extrapolation, dose-aware)", + desc="The area under the concentration time curve in the interval extrapolating from Tlast to infinity with zeros (matching AUClast) with dose-aware interpolation/extrapolation of concentrations", + formalsmap=list(conc="conc.group", time="time.group", time.dose="time.dose.group"), + depends=c("lambda.z", "clast.obs"), + pptestcd_cdisc="AUCINTID", + pptest_cdisc="AUCint (based on AUCinf,obs extrapolation, dose-aware)") + +add.interval.col("aucint.inf.pred", + FUN="pk.calc.aucint.inf.pred", + values=c(FALSE, TRUE), + unit_type="auc", + pretty_name="AUCint (based on AUCinf,pred extrapolation)", + desc="The area under the concentration time curve in the interval extrapolating from Tlast to infinity with the triangle from Tlast to the next point and zero thereafter (matching AUCall)", + formalsmap=list(conc="conc.group", time="time.group", time.dose=NULL), + depends=c("lambda.z", "clast.pred"), + pptestcd_cdisc="AUCINTIP", + pptest_cdisc="AUCint (based on AUCinf,pred extrapolation)") + +add.interval.col("aucint.inf.pred.dose", + FUN="pk.calc.aucint.inf.pred", + values=c(FALSE, TRUE), + unit_type="auc", + pretty_name="AUCint (based on AUCinf,pred extrapolation, dose-aware)", + desc="The area under the concentration time curve in the interval extrapolating from Tlast to infinity with the triangle from Tlast to the next point and zero thereafter (matching AUCall) with dose-aware interpolation/extrapolation of concentrations", + formalsmap=list(conc="conc.group", time="time.group", time.dose="time.dose.group"), + depends=c("lambda.z", "clast.pred"), + pptestcd_cdisc="AUCINTPD", + pptest_cdisc="AUCint (based on AUCinf,pred extrapolation, dose-aware)") + + +#' @describeIn pk.calc.auxcint Calculate AUMC over an interval +#' @export +pk.calc.aumcint <- function(conc, time, ..., options=list()) { + pk.calc.auxcint( + conc = conc, time = time, ..., + options = options, + fun_linear = aumcintegrate_linear, + fun_log = aumcintegrate_log, + fun_inf = aumcintegrate_inf + ) +} + +#' @describeIn pk.calc.auxcint Interpolate or extrapolate concentrations for +#' AUMClast +#' @export +pk.calc.aumcint.last <- function(conc, time, start=NULL, end=NULL, time.dose, ..., options=list()) { + if (missing(time.dose)) + time.dose <- NULL + pk.calc.aumcint(conc=conc, time=time, + start=start, end=end, + options=options, + time.dose=time.dose, + ..., + auc.type="AUClast") +} + +#' @describeIn pk.calc.auxcint Interpolate or extrapolate concentrations for +#' AUMCall +#' @export +pk.calc.aumcint.all <- function(conc, time, start=NULL, end=NULL, time.dose, ..., options=list()) { + if (missing(time.dose)) + time.dose <- NULL + pk.calc.aumcint(conc=conc, time=time, + start=start, end=end, + options=options, + time.dose=time.dose, + ..., + auc.type="AUCall") +} + +#' @describeIn pk.calc.auxcint Interpolate or extrapolate concentrations for +#' AUMCinf.obs +#' @export +pk.calc.aumcint.inf.obs <- function(conc, time, start=NULL, end=NULL, time.dose, lambda.z, clast.obs, ..., options=list()) { + if (missing(time.dose)) + time.dose <- NULL + pk.calc.aumcint(conc=conc, time=time, + start=start, end=end, + time.dose=time.dose, + lambda.z=lambda.z, clast=clast.obs, + options=options, ..., + auc.type="AUCinf") +} + +#' @describeIn pk.calc.auxcint Interpolate or extrapolate concentrations for +#' AUMCinf.pred +#' @export +pk.calc.aumcint.inf.pred <- function(conc, time, start=NULL, end=NULL, time.dose, lambda.z, clast.pred, ..., options=list()) { + if (missing(time.dose)) + time.dose <- NULL + pk.calc.aumcint(conc=conc, time=time, + start=start, end=end, + time.dose=time.dose, + lambda.z=lambda.z, clast=clast.pred, + options=options, ..., + auc.type="AUCinf") +} + + +# aumcint.last (without dose awareness) +add.interval.col("aumcint.last", + FUN="pk.calc.aumcint.last", + values=c(FALSE, TRUE), + unit_type="aumc", + pretty_name="AUMCint (based on AUMClast extrapolation)", + desc="The area under the moment curve in the interval extrapolating from Tlast to infinity with zeros (matching AUMClast)", + formalsmap=list(conc="conc.group", time="time.group", time.dose=NULL)) + +# aumcint.last.dose (WITH dose awareness) +add.interval.col("aumcint.last.dose", + FUN="pk.calc.aumcint.last", + values=c(FALSE, TRUE), + unit_type="aumc", + pretty_name="AUMCint (based on AUMClast extrapolation, dose-aware)", + desc="The area under the moment curve in the interval extrapolating from Tlast to infinity with zeros (matching AUMClast) with dose-aware interpolation/extrapolation of concentrations", + formalsmap=list(conc="conc.group", time="time.group", time.dose="time.dose.group")) + +# aumcint.all (without dose awareness) +add.interval.col("aumcint.all", + FUN="pk.calc.aumcint.all", + values=c(FALSE, TRUE), + unit_type="aumc", + pretty_name="AUMCint (based on AUMCall extrapolation)", + desc="The area under the moment curve in the interval extrapolating from Tlast to infinity with the triangle from Tlast to the next point and zero thereafter (matching AUMCall)", + formalsmap=list(conc="conc.group", time="time.group", time.dose=NULL)) + +# aumcint.all.dose (WITH dose awareness) +add.interval.col("aumcint.all.dose", + FUN="pk.calc.aumcint.all", + values=c(FALSE, TRUE), + unit_type="aumc", + pretty_name="AUMCint (based on AUMCall extrapolation, dose-aware)", + desc="The area under the moment curve in the interval extrapolating from Tlast to infinity with the triangle from Tlast to the next point and zero thereafter (matching AUMCall) with dose-aware interpolation/extrapolation of concentrations", + formalsmap=list(conc="conc.group", time="time.group", time.dose="time.dose.group")) + +# aumcint.inf.obs (without dose awareness) +add.interval.col("aumcint.inf.obs", + FUN="pk.calc.aumcint.inf.obs", + values=c(FALSE, TRUE), + unit_type="aumc", + pretty_name="AUMCint (based on AUMCinf,obs extrapolation)", + desc="The area under the moment curve in the interval extrapolating from Tlast to infinity with zeros (matching AUMClast)", + formalsmap=list(conc="conc.group", time="time.group", time.dose=NULL), + depends=c("lambda.z", "clast.obs")) + +# aumcint.inf.obs.dose (WITH dose awareness) +add.interval.col("aumcint.inf.obs.dose", + FUN="pk.calc.aumcint.inf.obs", + values=c(FALSE, TRUE), + unit_type="aumc", + pretty_name="AUMCint (based on AUMCinf,obs extrapolation, dose-aware)", + desc="The area under the moment curve in the interval extrapolating from Tlast to infinity with zeros (matching AUMClast) with dose-aware interpolation/extrapolation of concentrations", + formalsmap=list(conc="conc.group", time="time.group", time.dose="time.dose.group"), + depends=c("lambda.z", "clast.obs")) + +# aumcint.inf.pred (without dose awareness) +add.interval.col("aumcint.inf.pred", + FUN="pk.calc.aumcint.inf.pred", + values=c(FALSE, TRUE), + unit_type="aumc", + pretty_name="AUMCint (based on AUMCinf,pred extrapolation)", + desc="The area under the moment curve in the interval extrapolating from Tlast to infinity with the triangle from Tlast to the next point and zero thereafter (matching AUMCall)", + formalsmap=list(conc="conc.group", time="time.group", time.dose=NULL), + depends=c("lambda.z", "clast.pred")) + +# aumcint.inf.pred.dose (WITH dose awareness) +add.interval.col("aumcint.inf.pred.dose", + FUN="pk.calc.aumcint.inf.pred", + values=c(FALSE, TRUE), + unit_type="aumc", + pretty_name="AUMCint (based on AUMCinf,pred extrapolation, dose-aware)", + desc="The area under the moment curve in the interval extrapolating from Tlast to infinity with the triangle from Tlast to the next point and zero thereafter (matching AUMCall) with dose-aware interpolation/extrapolation of concentrations", + formalsmap=list(conc="conc.group", time="time.group", time.dose="time.dose.group"), + depends=c("lambda.z", "clast.pred")) + +# ============================================================================= +# SET SUMMARY STATISTICS - Count (16) +# ============================================================================= +PKNCA.set.summary( + name= c( + # AUC related + "aucint.last", "aucint.last.dose", "aucint.all", "aucint.all.dose", + "aucint.inf.obs", "aucint.inf.obs.dose", "aucint.inf.pred", "aucint.inf.pred.dose", + + # AUMC related + "aumcint.last", "aumcint.last.dose", "aumcint.all", "aumcint.all.dose", + "aumcint.inf.obs", "aumcint.inf.obs.dose", "aumcint.inf.pred", "aumcint.inf.pred.dose" + ), + description="geometric mean and geometric coefficient of variation", + point=business.geomean, + spread=business.geocv +) \ No newline at end of file diff --git a/R/auciv.R b/R/auciv.R index d9bc9889..c8f6252d 100644 --- a/R/auciv.R +++ b/R/auciv.R @@ -77,7 +77,9 @@ add.interval.col( depends = c("auclast", "c0"), desc = "The AUClast calculated with back-extrapolation for intravenous dosing using extrapolated C0", sparse = FALSE, - formalsmap = list(auc="auclast") + formalsmap = list(auc="auclast"), + pptestcd_cdisc="AUCIVLST", + pptest_cdisc="AUClast (IV dosing)" ) add.interval.col( @@ -88,7 +90,9 @@ add.interval.col( depends = c("aucall", "c0"), desc = "The AUCall calculated with back-extrapolation for intravenous dosing using extrapolated C0", sparse = FALSE, - formalsmap = list(auc="aucall") + formalsmap = list(auc="aucall"), + pptestcd_cdisc="AUCIVA", + pptest_cdisc="AUCall (IV dosing)" ) add.interval.col( @@ -99,7 +103,9 @@ add.interval.col( depends = c("aucint.last", "c0"), desc = "The AUCint,last calculated with back-extrapolation for intravenous dosing using extrapolated C0", sparse = FALSE, - formalsmap = list(auc="aucint.last") + formalsmap = list(auc="aucint.last"), + pptestcd_cdisc="AUCIVILT", + pptest_cdisc="AUCint,last (IV dosing)" ) add.interval.col( @@ -110,7 +116,9 @@ add.interval.col( depends = c("aucint.all", "c0"), desc = "The AUCint,all calculated with back-extrapolation for intravenous dosing using extrapolated C0", sparse = FALSE, - formalsmap = list(auc="aucint.all") + formalsmap = list(auc="aucint.all"), + pptestcd_cdisc="AUCIVINA", + pptest_cdisc="AUCint,all (IV dosing)" ) add.interval.col( @@ -121,7 +129,9 @@ add.interval.col( depends = c("aucinf.obs", "c0"), desc = "The AUCinf,obs calculated with back-extrapolation for intravenous dosing using extrapolated C0", sparse = FALSE, - formalsmap = list(auc="aucinf.obs") + formalsmap = list(auc="aucinf.obs"), + pptestcd_cdisc="AUCIVIS", + pptest_cdisc="AUCinf,obs (IV dosing)" ) add.interval.col( @@ -132,7 +142,9 @@ add.interval.col( depends = c("aucinf.pred", "c0"), desc = "The AUCinf,pred calculated with back-extrapolation for intravenous dosing using extrapolated C0", sparse = FALSE, - formalsmap = list(auc="aucinf.pred") + formalsmap = list(auc="aucinf.pred"), + pptestcd_cdisc="AUCIVIP", + pptest_cdisc="AUCinf,pred (IV dosing)" ) @@ -156,7 +168,9 @@ add.interval.col( depends = c("auclast", "aucivlast"), desc = "The back-extrapolation percent for intravenous dosing based on AUClast", sparse = FALSE, - formalsmap = list(auc="auclast", auciv="aucivlast") + formalsmap = list(auc="auclast", auciv="aucivlast"), + pptestcd_cdisc="AUCIVPLT", + pptest_cdisc="AUCbext (based on AUClast)" ) add.interval.col( @@ -167,7 +181,9 @@ add.interval.col( depends = c("aucall", "aucivall"), desc = "The back-extrapolation percent for intravenous dosing based on AUCall", sparse = FALSE, - formalsmap = list(auc="aucall", auciv="aucivall") + formalsmap = list(auc="aucall", auciv="aucivall"), + pptestcd_cdisc="AUCIVPEA", + pptest_cdisc="AUCbext (based on AUCall)" ) add.interval.col( @@ -178,7 +194,9 @@ add.interval.col( depends = c("aucint.last", "aucivint.last"), desc = "The back-extrapolation percent for intravenous dosing based on AUCint,last", sparse = FALSE, - formalsmap = list(auc="aucint.last", auciv="aucivint.last") + formalsmap = list(auc="aucint.last", auciv="aucivint.last"), + pptestcd_cdisc="AUCIVPIL", + pptest_cdisc="AUCbext (based on AUCint,last)" ) add.interval.col( @@ -189,7 +207,9 @@ add.interval.col( depends = c("aucint.all", "aucivint.all"), desc = "The back-extrapolation percent for intravenous dosing based on AUCint,all", sparse = FALSE, - formalsmap = list(auc="aucint.all", auciv="aucivint.all") + formalsmap = list(auc="aucint.all", auciv="aucivint.all"), + pptestcd_cdisc="AUCIVPIA", + pptest_cdisc="AUCbext (based on AUCint,all)" ) add.interval.col( @@ -200,7 +220,9 @@ add.interval.col( depends = c("aucinf.obs", "aucivinf.obs"), desc = "The back-extrapolation percent for intravenous dosing based on AUCinf,obs", sparse = FALSE, - formalsmap = list(auc="aucinf.obs", auciv="aucivinf.obs") + formalsmap = list(auc="aucinf.obs", auciv="aucivinf.obs"), + pptestcd_cdisc="AUCIVPEI", + pptest_cdisc="AUCbext (based on AUCinf,obs)" ) add.interval.col( @@ -211,7 +233,9 @@ add.interval.col( depends = c("aucinf.pred", "aucivinf.pred"), desc = "The back-extrapolation percent for intravenous dosing based on AUCinf,pred", sparse = FALSE, - formalsmap = list(auc="aucinf.pred", auciv="aucivinf.pred") + formalsmap = list(auc="aucinf.pred", auciv="aucivinf.pred"), + pptestcd_cdisc="AUCIVPEP", + pptest_cdisc="AUCbext (based on AUCinf,pred)" ) diff --git a/R/class-PKNCAresults.R b/R/class-PKNCAresults.R index cb63e4b8..41dc16b1 100644 --- a/R/class-PKNCAresults.R +++ b/R/class-PKNCAresults.R @@ -34,14 +34,18 @@ PKNCAresults <- function(result, data, exclude = NULL) { #' #' @param x The object to extract results from #' @param ... Ignored (for compatibility with generic [as.data.frame()]) -#' @param out_format Should the output be 'long' (default) or 'wide'? +#' @param out_format Should the output be 'long' (default), 'wide', or 'cdisc'? +#' When 'cdisc', the PPTESTCD column is translated to CDISC standard codes +#' and a PPTEST column with the CDISC test name is added. Route-dependent +#' parameters (e.g. CL, VZ, MRT) are resolved using the route information +#' from the dose data. #' @param filter_requested Only return rows with parameters that were #' specifically requested? #' @param filter_excluded Should excluded values be removed? #' @param out.format Deprecated in favor of `out_format` #' @returns A data.frame (or usually a tibble) of results #' @export -as.data.frame.PKNCAresults <- function(x, ..., out_format = c('long', 'wide'), filter_requested = FALSE, filter_excluded = FALSE, out.format = deprecated()) { +as.data.frame.PKNCAresults <- function(x, ..., out_format = c('long', 'wide', 'cdisc'), filter_requested = FALSE, filter_excluded = FALSE, out.format = deprecated()) { if (!filter_excluded) { ret <- x$result } else { @@ -77,7 +81,9 @@ as.data.frame.PKNCAresults <- function(x, ..., out_format = c('long', 'wide'), f ) } - if (out_format %in% 'wide') { + if (out_format %in% 'cdisc') { + ret <- pknca_cdisc_translate(ret, x) + } else if (out_format %in% 'wide') { if ("PPSTRESU" %in% names(ret)) { # Use standardized results ret$PPTESTCD <- sprintf("%s (%s)", ret$PPTESTCD, ret$PPSTRESU) @@ -95,6 +101,260 @@ as.data.frame.PKNCAresults <- function(x, ..., out_format = c('long', 'wide'), f ret } +# Translate PPTESTCD to CDISC standard codes and add PPTEST column +# +# Also adds PPSTINT and PPENINT columns (ISO 8601 durations relative to the +# last dose time) when any resolved PPTESTCD contains "INT". +# +# @param ret The long-format result data.frame +# @param x The PKNCAresults object (for accessing dose/route data) +# @returns The data.frame with PPTESTCD translated and PPTEST added +# @keywords Internal +# @noRd +pknca_cdisc_translate <- function(ret, x) { + all_intervals <- get.interval.cols() + # Determine route for each result row + route_per_row <- pknca_cdisc_get_route(ret, x) + # Build CDISC PPTESTCD and PPTEST for each row + cdisc_pptestcd <- character(nrow(ret)) + cdisc_pptest <- character(nrow(ret)) + for (i in seq_len(nrow(ret))) { + pknca_name <- ret$PPTESTCD[i] + col_def <- all_intervals[[pknca_name]] + if (is.null(col_def) || is.null(col_def$pptestcd_cdisc)) { + # No mapping available, keep original + cdisc_pptestcd[i] <- pknca_name + cdisc_pptest[i] <- if (!is.null(col_def$desc)) col_def$desc else "" + next + } + route <- route_per_row[i] + cdisc_pptestcd[i] <- resolve_cdisc_value(col_def$pptestcd_cdisc, route) + cdisc_pptest[i] <- resolve_cdisc_value(col_def$pptest_cdisc, route) + } + ret$PPTESTCD <- cdisc_pptestcd + # Insert PPTEST after PPTESTCD + pptestcd_pos <- which(names(ret) == "PPTESTCD") + if (length(pptestcd_pos) == 1) { + before <- ret[, seq_len(pptestcd_pos), drop = FALSE] + after <- ret[, seq(pptestcd_pos + 1, ncol(ret)), drop = FALSE] + ret <- cbind(before, PPTEST = cdisc_pptest, after) + } else { + ret$PPTEST <- cdisc_pptest + } + # Add PPSTINT/PPENINT if any PPTESTCD contains "INT" + has_int <- grepl("INT", cdisc_pptestcd, fixed = TRUE) + if (any(has_int)) { + ret <- pknca_cdisc_add_interval_columns(ret, x, has_int) + } + ret +} + +# Add PPSTINT and PPENINT columns for interval-based parameters +# +# These columns express the interval start and end as ISO 8601 durations +# relative to the last dose time for each subject/group. +# +# @param ret The result data.frame (already has PPTESTCD translated) +# @param x The PKNCAresults object +# @param has_int Logical vector indicating which rows have INT parameters +# @returns The data.frame with PPSTINT and PPENINT columns added +# @keywords Internal +# @noRd +pknca_cdisc_add_interval_columns <- function(ret, x, has_int) { + timeu <- pknca_cdisc_get_timeu(x) + last_dose_times <- pknca_cdisc_get_last_dose_time(ret, x) + ppstint <- rep(NA_character_, nrow(ret)) + ppenint <- rep(NA_character_, nrow(ret)) + for (i in which(has_int)) { + dose_time <- last_dose_times[i] + if (is.na(dose_time)) next + start_rel <- ret$start[i] - dose_time + end_rel <- ret$end[i] - dose_time + ppstint[i] <- format_iso8601_duration(start_rel, timeu) + ppenint[i] <- format_iso8601_duration(end_rel, timeu) + } + ret$PPSTINT <- ppstint + ret$PPENINT <- ppenint + ret +} + +# Get the time unit string for ISO 8601 formatting +# +# Uses timeu_pref if available, otherwise timeu. +# +# @param x The PKNCAresults object +# @returns A character string with the time unit (e.g. "hr", "min", "day"), +# or NA_character_ if not set +# @keywords Internal +# @noRd +pknca_cdisc_get_timeu <- function(x) { + if (is.null(x$data$conc) || !inherits(x$data$conc, "PKNCAconc")) { + return(NA_character_) + } + # Prefer timeu_pref, fall back to timeu + timeu_pref <- x$data$conc$units$timeu_pref + if (!is.null(timeu_pref) && !is.na(timeu_pref)) { + return(timeu_pref) + } + timeu <- x$data$conc$units$timeu + if (!is.null(timeu) && !is.na(timeu)) { + return(timeu) + } + # Check if timeu is stored as a column attribute + timeu_col <- x$data$conc$columns$timeu + if (!is.null(timeu_col) && length(timeu_col) > 0) { + # Column-based: take the first value + vals <- unique(x$data$conc$data[[timeu_col]]) + vals <- vals[!is.na(vals)] + if (length(vals) == 1) return(vals) + } + NA_character_ +} + +# Format a numeric duration as an ISO 8601 duration string +# +# @param value Numeric duration value +# @param timeu The time unit (e.g. "hr", "h", "min", "day", "d", "s", "sec") +# @returns An ISO 8601 duration string (e.g. "PT0H", "PT24H", "P1D") +# @keywords Internal +# @noRd +format_iso8601_duration <- function(value, timeu) { + if (is.na(value) || is.infinite(value)) return(NA_character_) + timeu_lower <- tolower(timeu) + if (is.na(timeu_lower)) { + # No unit info: assume hours + timeu_lower <- "hr" + } + # Map time unit to ISO 8601 designator + if (timeu_lower %in% c("hr", "h", "hour", "hours")) { + paste0("PT", value, "H") + } else if (timeu_lower %in% c("min", "minute", "minutes")) { + paste0("PT", value, "M") + } else if (timeu_lower %in% c("s", "sec", "second", "seconds")) { + paste0("PT", value, "S") + } else if (timeu_lower %in% c("day", "days", "d")) { + paste0("P", value, "D") + } else { + # Unknown unit: default to hours + paste0("PT", value, "H") + } +} + +# Get the last dose time for each row in the results +# +# For each result row, finds the most recent dose time that is <= the interval +# start, matching on group variables. +# +# @param ret The result data.frame +# @param x The PKNCAresults object +# @returns A numeric vector of last dose times, one per row (NA if unavailable) +# @keywords Internal +# @noRd +pknca_cdisc_get_last_dose_time <- function(ret, x) { + result <- rep(NA_real_, nrow(ret)) + if (is.null(x$data$dose) || identical(x$data$dose, NA) || + !inherits(x$data$dose, "PKNCAdose")) { + return(result) + } + dose_df <- x$data$dose$data + time_col <- x$data$dose$columns$time + if (length(time_col) == 0) return(result) + group_cols <- unlist(x$data$dose$columns$groups) + merge_cols <- intersect(group_cols, names(ret)) + for (i in seq_len(nrow(ret))) { + interval_start <- ret$start[i] + # Filter dose data to matching groups + dose_subset <- dose_df + if (length(merge_cols) > 0) { + mask <- rep(TRUE, nrow(dose_subset)) + for (gc in merge_cols) { + mask <- mask & (dose_subset[[gc]] == ret[[gc]][i]) + } + dose_subset <- dose_subset[mask, , drop = FALSE] + } + # Find the last dose time <= interval start + dose_times <- dose_subset[[time_col]] + valid_times <- dose_times[!is.na(dose_times) & dose_times <= interval_start] + if (length(valid_times) > 0) { + result[i] <- max(valid_times) + } + } + result +} + +# Resolve a CDISC value that may be a simple string or a route-dependent list +# +# @param value A character string or a list with a "route" element +# @param route The route for the current row ("extravascular" or "intravascular") +# @returns A character string with the resolved CDISC value +# @keywords Internal +# @noRd +resolve_cdisc_value <- function(value, route) { + if (is.character(value)) { + return(value) + } + if (is.list(value) && !is.null(value$route)) { + route_lower <- tolower(route) + if (route_lower %in% names(value$route)) { + return(value$route[[route_lower]]) + } + # Default to first element (extravascular) if route not found + return(value$route[[1]]) + } + # Fallback + as.character(value) +} + +# Get the route of administration for each row in the results +# +# @param ret The long-format result data.frame +# @param x The PKNCAresults object +# @returns A character vector of routes, one per row +# @keywords Internal +# @noRd +pknca_cdisc_get_route <- function(ret, x) { + default_route <- "extravascular" + # Check if dose data is available + if (is.null(x$data$dose) || identical(x$data$dose, NA) || + !inherits(x$data$dose, "PKNCAdose")) { + return(rep(default_route, nrow(ret))) + } + route_data <- getAttributeColumn( + object = x$data$dose, attr_name = "route", warn_missing = character() + ) + if (is.null(route_data)) { + return(rep(default_route, nrow(ret))) + } + # Get the dose data with route and group columns + dose_df <- x$data$dose$data + route_col <- x$data$dose$columns$route + group_cols <- unlist(x$data$dose$columns$groups) + # If route is a scalar (same for all), return it for all rows + if (length(unique(route_data[[1]])) == 1) { + return(rep(tolower(route_data[[1]][1]), nrow(ret))) + } + # Route varies by group: merge with results on group columns + # Use only group columns that exist in both datasets + merge_cols <- intersect(group_cols, names(ret)) + if (length(merge_cols) == 0) { + return(rep(default_route, nrow(ret))) + } + # Get unique route per group combination + dose_route <- unique(dose_df[, c(merge_cols, route_col), drop = FALSE]) + names(dose_route)[names(dose_route) == route_col] <- ".route_cdisc" + merged <- merge( + data.frame(.row_id = seq_len(nrow(ret)), ret[, merge_cols, drop = FALSE]), + dose_route, + by = merge_cols, + all.x = TRUE, + sort = FALSE + ) + merged <- merged[order(merged$.row_id), ] + routes <- tolower(as.character(merged$.route_cdisc)) + routes[is.na(routes)] <- default_route + routes +} + #' @rdname getDataName #' @export getDataName.PKNCAresults <- function(object) { diff --git a/R/half.life.R b/R/half.life.R index 79f50bd0..b84c77a5 100644 --- a/R/half.life.R +++ b/R/half.life.R @@ -628,7 +628,9 @@ add.interval.col("half.life", unit_type="time", pretty_name="Half-life", desc="The (terminal) half-life", - depends=c("tmax", "tlast")) + depends=c("tmax", "tlast"), + pptestcd_cdisc="LAMZHL", + pptest_cdisc="Half-Life Lambda z") PKNCA.set.summary( name="half.life", description="arithmetic mean and standard deviation", @@ -641,7 +643,9 @@ add.interval.col("r.squared", unit_type="unitless", pretty_name="$r^2$", desc="The r^2 value of the half-life calculation", - depends="half.life") + depends="half.life", + pptestcd_cdisc="R2", + pptest_cdisc="R Squared") PKNCA.set.summary( name="r.squared", description="arithmetic mean and standard deviation", @@ -654,7 +658,9 @@ add.interval.col("adj.r.squared", unit_type="unitless", pretty_name="$r^2_{adj}$", desc="The adjusted r^2 value of the half-life calculation", - depends="half.life") + depends="half.life", + pptestcd_cdisc="R2ADJ", + pptest_cdisc="R Squared Adjusted") PKNCA.set.summary( name="adj.r.squared", description="arithmetic mean and standard deviation", @@ -667,7 +673,9 @@ add.interval.col("lambda.z.corrxy", unit_type="unitless", pretty_name="Correlation (time, log-conc)", desc="Correlation between time and log-concentration for lambda.z points", - depends="half.life") + depends="half.life", + pptestcd_cdisc="CORRXY", + pptest_cdisc="Correlation Between TimeX and Log ConcY") PKNCA.set.summary( name="lambda.z.corrxy", description="arithmetic mean and standard deviation", @@ -680,7 +688,9 @@ add.interval.col("lambda.z", unit_type="inverse_time", pretty_name="$\\lambda_z$", desc="The elimination rate of the terminal half-life", - depends="half.life") + depends="half.life", + pptestcd_cdisc="LAMZ", + pptest_cdisc="Lambda z") PKNCA.set.summary( name="lambda.z", description="geometric mean and geometric coefficient of variation", @@ -693,7 +703,9 @@ add.interval.col("lambda.z.time.first", unit_type="time", pretty_name="First time for $\\lambda_z$", desc="The first time point used for the calculation of half-life", - depends="half.life") + depends="half.life", + pptestcd_cdisc="LAMZLL", + pptest_cdisc="Lambda z Lower Limit") PKNCA.set.summary( name="lambda.z.time.first", description="median and range", @@ -706,7 +718,9 @@ add.interval.col("lambda.z.time.last", unit_type="time", pretty_name="Last time for $\\lambda_z$", desc="The last time point used for the calculation of half-life", - depends="half.life") + depends="half.life", + pptestcd_cdisc="LAMZUL", + pptest_cdisc="Lambda z Upper Limit") PKNCA.set.summary( name="lambda.z.time.last", description="median and range", @@ -719,7 +733,9 @@ add.interval.col("lambda.z.n.points", unit_type="count", pretty_name="Number of points used for lambda_z", desc="The number of points used for the calculation of half-life", - depends="half.life") + depends="half.life", + pptestcd_cdisc="LAMZNPT", + pptest_cdisc="Number of Points for Lambda z") PKNCA.set.summary( name="lambda.z.n.points", description="median and range", @@ -732,7 +748,9 @@ add.interval.col("clast.pred", unit_type="conc", pretty_name="Clast,pred", desc="The concentration at Tlast as predicted by the half-life", - depends="half.life") + depends="half.life", + pptestcd_cdisc="CLSTP", + pptest_cdisc="Clast pred") PKNCA.set.summary( name="clast.pred", description="geometric mean and geometric coefficient of variation", @@ -745,7 +763,9 @@ add.interval.col("span.ratio", unit_type="fraction", pretty_name="Span ratio", desc="The ratio of the half-life to the duration used for half-life calculation", - depends="half.life") + depends="half.life", + pptestcd_cdisc="LAMZSPN", + pptest_cdisc="Lambda z Span") PKNCA.set.summary( name="span.ratio", description="geometric mean and geometric coefficient of variation", diff --git a/R/pk.calc.c0.R b/R/pk.calc.c0.R index fb23b4fc..3fa141b2 100644 --- a/R/pk.calc.c0.R +++ b/R/pk.calc.c0.R @@ -135,7 +135,9 @@ add.interval.col("c0", unit_type="conc", pretty_name="C0", desc="Initial concentration after an IV bolus", - depends=NULL) + depends=NULL, + pptestcd_cdisc="C0", + pptest_cdisc="Initial Conc") PKNCA.set.summary( name="c0", description="geometric mean and geometric coefficient of variation", diff --git a/R/pk.calc.simple.R b/R/pk.calc.simple.R index 6bf399f9..8b191ea9 100644 --- a/R/pk.calc.simple.R +++ b/R/pk.calc.simple.R @@ -44,7 +44,9 @@ add.interval.col("cmax", unit_type="conc", pretty_name="Cmax", desc="Maximum observed concentration", - depends=NULL) + depends=NULL, + pptestcd_cdisc="CMAX", + pptest_cdisc="Max Conc") #' @describeIn pk.calc.cmax Determine the minimum observed PK #' concentration @@ -70,7 +72,9 @@ add.interval.col("cmin", unit_type="conc", pretty_name="Cmin", desc="Minimum observed concentration", - depends=NULL) + depends=NULL, + pptestcd_cdisc="CMIN", + pptest_cdisc="Min Conc") #' Determine time of maximum observed PK concentration #' @@ -123,7 +127,9 @@ add.interval.col("tmax", unit_type="time", pretty_name="Tmax", desc="Time of the maximum observed concentration", - depends=NULL) + depends=NULL, + pptestcd_cdisc="TMAX", + pptest_cdisc="Time of CMAX") #' Determine time of minimum observed PK concentration #' @@ -176,7 +182,9 @@ add.interval.col("tmin", unit_type="time", pretty_name="Tmin", desc="Time of the minimum observed concentration", - depends=NULL) + depends=NULL, + pptestcd_cdisc="TMIN", + pptest_cdisc="Time of CMIN Observation") #' Determine time of last observed concentration above the limit of @@ -206,7 +214,9 @@ add.interval.col("tlast", unit_type="time", pretty_name="Tlast", desc="Time of the last concentration observed above the limit of quantification", - depends=NULL) + depends=NULL, + pptestcd_cdisc="TLST", + pptest_cdisc="Time of Last Nonzero Conc") #' @describeIn pk.calc.tlast Determine the first concentration above #' the limit of quantification. @@ -228,7 +238,9 @@ add.interval.col("tfirst", unit_type="time", pretty_name="Tfirst", desc="Time of the first concentration above the limit of quantification", - depends=NULL) + depends=NULL, + pptestcd_cdisc="TFIRST", + pptest_cdisc="Time of First Nonzero Conc") #' Determine the last observed concentration above the limit of quantification #' (LOQ). @@ -267,7 +279,9 @@ add.interval.col("clast.obs", unit_type="conc", pretty_name="Clast", desc="The last concentration observed above the limit of quantification", - depends=NULL) + depends=NULL, + pptestcd_cdisc="CLST", + pptest_cdisc="Last Nonzero Conc") #' Calculate the effective half-life #' @@ -288,7 +302,9 @@ add.interval.col("thalf.eff.obs", unit_type="time", pretty_name="Effective half-life (based on MRT,obs)", formalsmap=list(mrt="mrt.obs"), - depends="mrt.obs") + depends="mrt.obs", + pptestcd_cdisc="EFFOHL", + pptest_cdisc="Effective Half-Life Obs") add.interval.col("thalf.eff.pred", FUN="pk.calc.thalf.eff", @@ -297,7 +313,9 @@ add.interval.col("thalf.eff.pred", pretty_name="Effective half-life (based on MRT,pred)", desc="The effective half-life (as determined from the MRTpred)", formalsmap=list(mrt="mrt.pred"), - depends="mrt.pred") + depends="mrt.pred", + pptestcd_cdisc="EFFPHL", + pptest_cdisc="Effective Half-Life Pred") add.interval.col("thalf.eff.last", FUN="pk.calc.thalf.eff", @@ -306,7 +324,9 @@ add.interval.col("thalf.eff.last", pretty_name="Effective half-life (based on MRT,last)", desc="The effective half-life (as determined from the MRTlast)", formalsmap=list(mrt="mrt.last"), - depends="mrt.last") + depends="mrt.last", + pptestcd_cdisc="EFFHL", + pptest_cdisc="Effective Half-Life (based on AUClast)") add.interval.col("thalf.eff.iv.obs", FUN="pk.calc.thalf.eff", @@ -315,7 +335,9 @@ add.interval.col("thalf.eff.iv.obs", pretty_name="Effective half-life (for IV dosing, based on MRT,obs)", desc="The effective half-life (as determined from the intravenous MRTobs)", formalsmap=list(mrt="mrt.iv.obs"), - depends="mrt.iv.obs") + depends="mrt.iv.obs", + pptestcd_cdisc="EFFIVOHL", + pptest_cdisc="Effective Half-Life (for IV dosing, based on MRT Obs)") add.interval.col("thalf.eff.iv.pred", FUN="pk.calc.thalf.eff", @@ -324,7 +346,9 @@ add.interval.col("thalf.eff.iv.pred", pretty_name="Effective half-life (for IV dosing, based on MRT,pred)", desc="The effective half-life (as determined from the intravenous MRTpred)", formalsmap=list(mrt="mrt.iv.pred"), - depends="mrt.iv.pred") + depends="mrt.iv.pred", + pptestcd_cdisc="EFFIVPHL", + pptest_cdisc="Effective Half-Life (for IV dosing, based on MRT Pred)") add.interval.col("thalf.eff.iv.last", FUN="pk.calc.thalf.eff", @@ -333,7 +357,9 @@ add.interval.col("thalf.eff.iv.last", pretty_name="Effective half-life (for IV dosing, based on MRTlast)", desc="The effective half-life (as determined from the intravenous MRTlast)", formalsmap=list(mrt="mrt.iv.last"), - depends="mrt.iv.last") + depends="mrt.iv.last", + pptestcd_cdisc="EFFIVLHL", + pptest_cdisc="Effective Half-Life (for IV dosing, based on AUClast)") #' Calculate the AUC percent extrapolated @@ -392,7 +418,9 @@ add.interval.col("aucpext.obs", pretty_name="AUCpext (based on AUCinf,obs)", desc="Percent of the AUCinf that is extrapolated after Tlast calculated from the observed Clast", formalsmap=list(aucinf="aucinf.obs"), - depends=c("auclast", "aucinf.obs")) + depends=c("auclast", "aucinf.obs"), + pptestcd_cdisc="AUCPEO", + pptest_cdisc="AUC %Extrapolation Obs") add.interval.col("aucpext.pred", FUN="pk.calc.aucpext", @@ -401,7 +429,9 @@ add.interval.col("aucpext.pred", pretty_name="AUCpext (based on AUCinf,pred)", desc="Percent of the AUCinf that is extrapolated after Tlast calculated from the predicted Clast", formalsmap=list(aucinf="aucinf.pred"), - depends=c("auclast", "aucinf.pred")) + depends=c("auclast", "aucinf.pred"), + pptestcd_cdisc="AUCPEP", + pptest_cdisc="AUC %Extrapolation Pred") #' Calculate the elimination rate (Kel) @@ -423,7 +453,9 @@ add.interval.col("kel.obs", pretty_name="Kel (based on AUCinf,obs)", desc="Elimination rate (as calculated from the MRT with observed Clast)", formalsmap=list(mrt="mrt.obs"), - depends="mrt.obs") + depends="mrt.obs", + pptestcd_cdisc="KELOS", + pptest_cdisc="Kel (based on AUCinf,obs)") add.interval.col("kel.pred", FUN="pk.calc.kel", @@ -432,7 +464,9 @@ add.interval.col("kel.pred", pretty_name="Kel (based on AUCinf,pred)", desc="Elimination rate (as calculated from the MRT with predicted Clast)", formalsmap=list(mrt="mrt.pred"), - depends="mrt.pred") + depends="mrt.pred", + pptestcd_cdisc="KELP", + pptest_cdisc="Kel (based on AUCinf,pred)") add.interval.col("kel.last", FUN="pk.calc.kel", @@ -441,7 +475,9 @@ add.interval.col("kel.last", pretty_name="Kel (based on AUClast)", desc="Elimination rate (as calculated from the MRT using AUClast)", formalsmap=list(mrt="mrt.last"), - depends="mrt.last") + depends="mrt.last", + pptestcd_cdisc="KELLST", + pptest_cdisc="Kel (based on AUClast)") add.interval.col("kel.iv.obs", FUN="pk.calc.kel", @@ -450,7 +486,9 @@ add.interval.col("kel.iv.obs", pretty_name="Kel (for IV dosing, based on AUCinf,obs)", desc="Elimination rate (as calculated from the intravenous MRTobs)", formalsmap=list(mrt="mrt.iv.obs"), - depends="mrt.iv.obs") + depends="mrt.iv.obs", + pptestcd_cdisc="KELIVOS", + pptest_cdisc="Kel (for IV dosing, based on AUCinf,obs)") add.interval.col("kel.iv.pred", FUN="pk.calc.kel", @@ -459,7 +497,9 @@ add.interval.col("kel.iv.pred", pretty_name="Kel (for IV dosing, based on AUCinf,pred)", desc="Elimination rate (as calculated from the intravenous MRTpred)", formalsmap=list(mrt="mrt.iv.pred"), - depends="mrt.iv.pred") + depends="mrt.iv.pred", + pptestcd_cdisc="KELIVP", + pptest_cdisc="Kel (for IV dosing, based on AUCinf,pred)") add.interval.col("kel.iv.last", FUN="pk.calc.kel", @@ -468,7 +508,9 @@ add.interval.col("kel.iv.last", pretty_name="Kel (for IV dosing, based on AUClast)", desc="Elimination rate (as calculated from the intravenous MRTlast)", formalsmap=list(mrt="mrt.iv.last"), - depends="mrt.iv.last") + depends="mrt.iv.last", + pptestcd_cdisc="KELIVLT", + pptest_cdisc="Kel (for IV dosing, based on AUClast)") add.interval.col("kel.all", FUN = "pk.calc.kel", @@ -601,7 +643,9 @@ add.interval.col("cl.last", pretty_name="CL (based on AUClast)", desc="Clearance or observed oral clearance calculated to Clast", formalsmap=list(auc="auclast"), - depends="auclast") + depends="auclast", + pptestcd_cdisc=list(route=list(extravascular="CLF/FLST", intravascular="CLLST")), + pptest_cdisc=list(route=list(extravascular="CL by F (based on AUClast)", intravascular="CL (based on AUClast)"))) add.interval.col("cl.all", FUN="pk.calc.cl", @@ -610,7 +654,9 @@ add.interval.col("cl.all", pretty_name="CL (based on AUCall)", desc="Clearance or observed oral clearance calculated with AUCall", formalsmap=list(auc="aucall"), - depends="aucall") + depends="aucall", + pptestcd_cdisc=list(route=list(extravascular="CLF/FALL", intravascular="CLALL")), + pptest_cdisc=list(route=list(extravascular="CL by F (based on AUCall)", intravascular="CL (based on AUCall)"))) add.interval.col("cl.obs", FUN="pk.calc.cl", @@ -619,7 +665,9 @@ add.interval.col("cl.obs", pretty_name="CL (based on AUCinf,obs)", desc="Clearance or observed oral clearance calculated with observed Clast", formalsmap=list(auc="aucinf.obs"), - depends="aucinf.obs") + depends="aucinf.obs", + pptestcd_cdisc=list(route=list(extravascular="CLF/FO", intravascular="CLO")), + pptest_cdisc=list(route=list(extravascular="Total CL Obs by F", intravascular="Total CL Obs"))) add.interval.col("cl.pred", FUN="pk.calc.cl", @@ -628,7 +676,9 @@ add.interval.col("cl.pred", pretty_name="CL (based on AUCinf,pred)", desc="Clearance or observed oral clearance calculated with predicted Clast", formalsmap=list(auc="aucinf.pred"), - depends="aucinf.pred") + depends="aucinf.pred", + pptestcd_cdisc=list(route=list(extravascular="CLF/FP", intravascular="CLP")), + pptest_cdisc=list(route=list(extravascular="Total CL Pred by F", intravascular="Total CL Pred"))) add.interval.col("cl.int.all", FUN = "pk.calc.cl", @@ -768,7 +818,9 @@ add.interval.col("f", unit_type="fraction", pretty_name="Bioavailability", desc="Bioavailability or relative bioavailability", - depends=NULL) + depends=NULL, + pptestcd_cdisc="FAB", + pptest_cdisc="Absolute Bioavailability") #' Calculate the mean residence time (MRT) for single-dose data or linear @@ -795,7 +847,9 @@ add.interval.col("mrt.obs", pretty_name="MRT (based on AUCinf,obs)", desc="The mean residence time to infinity using observed Clast", formalsmap=list(auc="aucinf.obs", aumc="aumcinf.obs"), - depends=c("aucinf.obs", "aumcinf.obs")) + depends=c("aucinf.obs", "aumcinf.obs"), + pptestcd_cdisc=list(route=list(extravascular="MRTEVFO", intravascular="MRTICFO")), + pptest_cdisc=list(route=list(extravascular="MRT Extravasc Infinity Obs", intravascular="MRT IV Cont Inf Infinity Obs"))) add.interval.col("mrt.pred", FUN="pk.calc.mrt", @@ -804,7 +858,9 @@ add.interval.col("mrt.pred", pretty_name="MRT (based on AUCinf,pred)", desc="The mean residence time to infinity using predicted Clast", formalsmap=list(auc="aucinf.pred", aumc="aumcinf.pred"), - depends=c("aucinf.pred", "aumcinf.pred")) + depends=c("aucinf.pred", "aumcinf.pred"), + pptestcd_cdisc=list(route=list(extravascular="MRTEVFP", intravascular="MRTICFP")), + pptest_cdisc=list(route=list(extravascular="MRT Extravasc Infinity Pred", intravascular="MRT IV Cont Inf Infinity Pred"))) add.interval.col("mrt.last", FUN="pk.calc.mrt", @@ -813,7 +869,9 @@ add.interval.col("mrt.last", pretty_name="MRT (based on AUClast)", desc="The mean residence time to the last observed concentration above the LOQ", formalsmap=list(auc="auclast", aumc="aumclast"), - depends=c("auclast", "aumclast")) + depends=c("auclast", "aumclast"), + pptestcd_cdisc=list(route=list(extravascular="MRTEVLST", intravascular="MRTICLST")), + pptest_cdisc=list(route=list(extravascular="MRT Extravasc to Last Nonzero Conc", intravascular="MRT IV Cont Inf to Last Nonzero Conc"))) add.interval.col("mrt.all", FUN = "pk.calc.mrt", @@ -899,7 +957,9 @@ add.interval.col("mrt.iv.obs", pretty_name="MRT (for IV dosing, based on AUCinf,obs)", desc="The mean residence time to infinity using observed Clast correcting for dosing duration", formalsmap=list(auc="aucinf.obs", aumc="aumcinf.obs"), - depends=c("aucinf.obs", "aumcinf.obs")) + depends=c("aucinf.obs", "aumcinf.obs"), + pptestcd_cdisc="MRTIBIFO", + pptest_cdisc="MRT Intravasc Infinity Obs") add.interval.col("mrt.iv.pred", FUN="pk.calc.mrt.iv", @@ -908,7 +968,9 @@ add.interval.col("mrt.iv.pred", pretty_name="MRT (for IV dosing, based on AUCinf,pred)", desc="The mean residence time to infinity using predicted Clast correcting for dosing duration", formalsmap=list(auc="aucinf.pred", aumc="aumcinf.pred"), - depends=c("aucinf.pred", "aumcinf.pred")) + depends=c("aucinf.pred", "aumcinf.pred"), + pptestcd_cdisc="MRTIBIFP", + pptest_cdisc="MRT Intravasc Infinity Pred") add.interval.col("mrt.iv.last", FUN="pk.calc.mrt.iv", @@ -917,7 +979,9 @@ add.interval.col("mrt.iv.last", pretty_name="MRT (for IV dosing, based on AUClast)", desc="The mean residence time to the last observed concentration above the LOQ correcting for dosing duration", formalsmap=list(auc="auclast", aumc="aumclast"), - depends=c("auclast", "aumclast")) + depends=c("auclast", "aumclast"), + pptestcd_cdisc="MRTIBLST", + pptest_cdisc="MRT Intravasc to Last Nonzero Conc") add.interval.col("mrt.iv.all", FUN = "pk.calc.mrt.iv", @@ -976,7 +1040,9 @@ add.interval.col("mrt.md.obs", pretty_name="MRT (for multiple dosing, based on AUCinf,obs)", desc="The mean residence time with multiple dosing and nonlinear kinetics using observed Clast", formalsmap=list(auctau="auclast", aumctau="aumclast", aucinf="aucinf.obs"), - depends=c("auclast", "aumclast", "aucinf.obs")) + depends=c("auclast", "aumclast", "aucinf.obs"), + pptestcd_cdisc="MRTMDO", + pptest_cdisc="MRT (for multiple dosing, based on AUCinf,obs)") add.interval.col("mrt.md.pred", FUN="pk.calc.mrt.md", @@ -985,7 +1051,9 @@ add.interval.col("mrt.md.pred", pretty_name="MRT (for multiple dosing, based on AUCinf,pred)", desc="The mean residence time with multiple dosing and nonlinear kinetics using predicted Clast", formalsmap=list(auctau="auclast", aumctau="aumclast", aucinf="aucinf.pred"), - depends=c("auclast", "aumclast", "aucinf.pred")) + depends=c("auclast", "aumclast", "aucinf.pred"), + pptestcd_cdisc="MRTMDP", + pptest_cdisc="MRT (for multiple dosing, based on AUCinf,pred)") #' Calculate the terminal volume of distribution (Vz) @@ -1015,7 +1083,9 @@ add.interval.col("vz.obs", pretty_name="Vz (based on AUCinf,obs)", desc="The terminal volume of distribution using observed Clast", formalsmap=list(cl="cl.obs"), - depends=c("cl.obs", "lambda.z")) + depends=c("cl.obs", "lambda.z"), + pptestcd_cdisc=list(route=list(extravascular="VZF/FO", intravascular="VZO")), + pptest_cdisc=list(route=list(extravascular="Vz by F Obs", intravascular="Vz Obs"))) add.interval.col("vz.pred", FUN="pk.calc.vz", @@ -1024,7 +1094,9 @@ add.interval.col("vz.pred", pretty_name="Vz (based on AUCinf,pred)", desc="The terminal volume of distribution using predicted Clast", formalsmap=list(cl="cl.pred"), - depends=c("cl.pred", "lambda.z")) + depends=c("cl.pred", "lambda.z"), + pptestcd_cdisc=list(route=list(extravascular="VZF/FP", intravascular="VZP")), + pptest_cdisc=list(route=list(extravascular="Vz by F Pred", intravascular="Vz Pred"))) add.interval.col("vz.all", FUN = "pk.calc.vz", @@ -1174,7 +1246,9 @@ add.interval.col("vss.obs", pretty_name="Vss (based on AUCinf,obs)", desc="The steady-state volume of distribution using observed Clast", formalsmap=list(cl="cl.obs", mrt="mrt.obs"), - depends=c("cl.obs", "mrt.obs")) + depends=c("cl.obs", "mrt.obs"), + pptestcd_cdisc=list(route=list(extravascular="VSSF/FO", intravascular="VSSO")), + pptest_cdisc=list(route=list(extravascular="Vss by F Obs", intravascular="Vol Dist Steady State Obs"))) add.interval.col("vss.pred", FUN="pk.calc.vss", @@ -1183,7 +1257,9 @@ add.interval.col("vss.pred", pretty_name="Vss (based on AUCinf,pred)", desc="The steady-state volume of distribution using predicted Clast", formalsmap=list(cl="cl.pred", mrt="mrt.pred"), - depends=c("cl.pred", "mrt.pred")) + depends=c("cl.pred", "mrt.pred"), + pptestcd_cdisc=list(route=list(extravascular="VSSF/FP", intravascular="VSSP")), + pptest_cdisc=list(route=list(extravascular="Vss by F Pred", intravascular="Vol Dist Steady State Pred"))) add.interval.col("vss.last", FUN="pk.calc.vss", @@ -1192,7 +1268,9 @@ add.interval.col("vss.last", pretty_name="Vss (based on AUClast)", desc="The steady-state volume of distribution calculating through Tlast", formalsmap=list(cl="cl.last", mrt="mrt.last"), - depends=c("cl.last", "mrt.last")) + depends=c("cl.last", "mrt.last"), + pptestcd_cdisc=list(route=list(extravascular="VSSF/FLST", intravascular="VSSLST")), + pptest_cdisc=list(route=list(extravascular="Vss by F (based on AUClast)", intravascular="Vss (based on AUClast)"))) add.interval.col("vss.iv.obs", FUN="pk.calc.vss", @@ -1201,7 +1279,9 @@ add.interval.col("vss.iv.obs", pretty_name="Vss (for IV dosing, based on AUCinf,obs)", desc="The steady-state volume of distribution with intravenous infusion using observed Clast", formalsmap=list(cl="cl.obs", mrt="mrt.iv.obs"), - depends=c("cl.obs", "mrt.iv.obs")) + depends=c("cl.obs", "mrt.iv.obs"), + pptestcd_cdisc="VSSIVO", + pptest_cdisc="Vss (for IV dosing, based on AUCinf,obs)") add.interval.col("vss.iv.pred", FUN="pk.calc.vss", @@ -1210,7 +1290,9 @@ add.interval.col("vss.iv.pred", pretty_name="Vss (for IV dosing, based on AUCinf,pred)", desc="The steady-state volume of distribution with intravenous infusion using predicted Clast", formalsmap=list(cl="cl.pred", mrt="mrt.iv.pred"), - depends=c("cl.pred", "mrt.iv.pred")) + depends=c("cl.pred", "mrt.iv.pred"), + pptestcd_cdisc="VSSIVP", + pptest_cdisc="Vss (for IV dosing, based on AUCinf,pred)") add.interval.col("vss.iv.last", FUN="pk.calc.vss", @@ -1219,7 +1301,9 @@ add.interval.col("vss.iv.last", pretty_name="Vss (for IV dosing, based on AUClast)", desc="The steady-state volume of distribution with intravenous infusion calculating through Tlast", formalsmap=list(cl="cl.last", mrt="mrt.iv.last"), - depends=c("cl.last", "mrt.iv.last")) + depends=c("cl.last", "mrt.iv.last"), + pptestcd_cdisc="VSSIVLST", + pptest_cdisc="Vss (for IV dosing, based on AUClast)") add.interval.col("vss.md.obs", FUN="pk.calc.vss", @@ -1228,7 +1312,9 @@ add.interval.col("vss.md.obs", pretty_name="Vss (for multiple-dose, based on Clast,obs)", desc="The steady-state volume of distribution for nonlinear multiple-dose data using observed Clast", formalsmap=list(cl="cl.last", mrt="mrt.md.obs"), - depends=c("cl.last", "mrt.md.obs")) + depends=c("cl.last", "mrt.md.obs"), + pptestcd_cdisc="VSSMDO", + pptest_cdisc="Vss (for multiple-dose, based on AUCinf,obs)") add.interval.col("vss.md.pred", FUN="pk.calc.vss", @@ -1237,7 +1323,9 @@ add.interval.col("vss.md.pred", pretty_name="Vss (for multiple-dose, based on Clast,pred)", desc="The steady-state volume of distribution for nonlinear multiple-dose data using predicted Clast", formalsmap=list(cl="cl.last", mrt="mrt.md.pred"), - depends=c("cl.last", "mrt.md.pred")) + depends=c("cl.last", "mrt.md.pred"), + pptestcd_cdisc="VSSMDP", + pptest_cdisc="Vss (for multiple-dose, based on AUCinf,pred)") add.interval.col("vss.all", FUN = "pk.calc.vss", @@ -1357,7 +1445,9 @@ add.interval.col( desc = "The average concentration during an interval (calculated with AUClast)", depends = "auclast", formalsmap = list(auc = "auclast") -) + , + pptestcd_cdisc="CAVG", + pptest_cdisc="Average Conc") add.interval.col( "cav.int.last", FUN = "pk.calc.cav", @@ -1367,7 +1457,9 @@ add.interval.col( desc = "The average concentration during an interval (calculated with AUCint.last)", depends = "aucint.last", formalsmap = list(auc = "aucint.last"), -) + , + pptestcd_cdisc="CAVGINT", + pptest_cdisc="Average Conc from T1 to T2") add.interval.col( "cav.int.all", FUN = "pk.calc.cav", @@ -1377,7 +1469,9 @@ add.interval.col( desc = "The average concentration during an interval (calculated with AUCint.all)", depends = "aucint.all", formalsmap = list(auc = "aucint.all"), -) + , + pptestcd_cdisc="CAVGINA", + pptest_cdisc="Cavg All") add.interval.col( "cav.int.inf.obs", FUN = "pk.calc.cav", @@ -1387,7 +1481,9 @@ add.interval.col( desc = "The average concentration during an interval (calculated with AUCint.inf.obs)", depends = "aucint.inf.obs", formalsmap = list(auc = "aucint.inf.obs"), -) + , + pptestcd_cdisc="CAVGINO", + pptest_cdisc="Cavg Infinity Obs") add.interval.col( "cav.int.inf.pred", FUN = "pk.calc.cav", @@ -1397,7 +1493,9 @@ add.interval.col( desc = "The average concentration during an interval (calculated with AUCint.inf.pred)", depends = "aucint.inf.pred", formalsmap = list(auc = "aucint.inf.pred"), -) + , + pptestcd_cdisc="CAVGINP", + pptest_cdisc="Cavg Infinity Pred") #' Determine the trough (end of interval) concentration @@ -1426,7 +1524,9 @@ add.interval.col("ctrough", unit_type="conc", pretty_name="Ctrough", desc="The trough (end of interval) concentration", - depends=NULL) + depends=NULL, + pptestcd_cdisc="CTROUGH", + pptest_cdisc="Conc Trough") #' @describeIn pk.calc.ctrough Concentration at the beginning of the interval @@ -1452,7 +1552,9 @@ add.interval.col("cstart", unit_type="conc", pretty_name="Cstart", desc="The predose concentration", - depends=NULL) + depends=NULL, + pptestcd_cdisc="CSTART", + pptest_cdisc="Cstart") #' Determine the peak-to-trough ratio @@ -1475,7 +1577,9 @@ add.interval.col("ptr", unit_type="fraction", pretty_name="Peak-to-trough ratio", desc="Peak-to-Trough ratio (fraction)", - depends=c("cmax", "ctrough")) + depends=c("cmax", "ctrough"), + pptestcd_cdisc="PTROUGHR", + pptest_cdisc="Peak Trough Ratio") #' Determine the observed lag time (time before the first @@ -1501,7 +1605,9 @@ add.interval.col("tlag", unit_type="time", pretty_name="Tlag", desc="Lag time", - depends=NULL) + depends=NULL, + pptestcd_cdisc="TLAG", + pptest_cdisc="Time to First Nonzero Conc") #' Determine the degree of fluctuation @@ -1527,7 +1633,9 @@ add.interval.col("deg.fluc", unit_type="%", pretty_name="Degree of fluctuation", desc="Degree of fluctuation", - depends=c("cmax", "cmin", "cav")) + depends=c("cmax", "cmin", "cav"), + pptestcd_cdisc="DEGFLUC", + pptest_cdisc="Degree of fluctuation") #' @describeIn pk.calc.deg.fluc PK swing @@ -1549,7 +1657,9 @@ add.interval.col("swing", unit_type="%", pretty_name="Swing", desc="Swing relative to Cmin", - depends=c("cmax", "cmin")) + depends=c("cmax", "cmin"), + pptestcd_cdisc="SWING", + pptest_cdisc="Swing") #' Determine the concentration at the end of infusion @@ -1579,7 +1689,9 @@ add.interval.col("ceoi", unit_type="conc", pretty_name="Ceoi", desc="Concentration at the end of infusion", - depends=NULL) + depends=NULL, + pptestcd_cdisc="CEOI", + pptest_cdisc="Ceoi") #' Calculate the AUC above a given concentration @@ -1612,7 +1724,9 @@ add.interval.col( desc="The area under the concentration time the beginning of the interval to the last concentration above the limit of quantification plus the triangle from that last concentration to 0 at the first concentration below the limit of quantification, with a concentration subtracted from all concentrations and values below zero after subtraction set to zero", depends="cstart", formalsmap = list(conc_above = "cstart") -) + , + pptestcd_cdisc="AUCABVPA", + pptest_cdisc="AUC above predose") add.interval.col( "aucabove.trough.all", @@ -1622,7 +1736,9 @@ add.interval.col( desc="The area under the concentration time the beginning of the interval to the last concentration above the limit of quantification plus the triangle from that last concentration to 0 at the first concentration below the limit of quantification, with a concentration subtracted from all concentrations and values below zero after subtraction set to zero", depends="ctrough", formalsmap = list(conc_above = "ctrough") -) + , + pptestcd_cdisc="AUCABVTA", + pptest_cdisc="AUC above trough") #' Count the number of concentration measurements in an interval @@ -1652,7 +1768,9 @@ add.interval.col( pretty_name = "Concentration count", desc = "Number of non-missing concentrations for an interval", depends = NULL -) + , + pptestcd_cdisc="CNTCONC", + pptest_cdisc="Concentration count") #' @describeIn pk.calc.count_conc Count the number of concentration measurements #' that are not missing, above, or below the limit of quantification in an @@ -1694,7 +1812,9 @@ add.interval.col( unit_type="dose", pretty_name="Total dose", desc="Total dose administered during an interval" -) + , + pptestcd_cdisc="TDOSE", + pptest_cdisc="Total dose administered") # ============================================================================= # SET SUMMARY STATISTICS diff --git a/R/pk.calc.urine.R b/R/pk.calc.urine.R index 165b6135..1ee6479c 100644 --- a/R/pk.calc.urine.R +++ b/R/pk.calc.urine.R @@ -13,7 +13,9 @@ add.interval.col("volpk", values=c(FALSE, TRUE), unit_type="volume", pretty_name="Total Urine Volume", - desc="The sum of urine volumes for the interval") + desc="The sum of urine volumes for the interval", + pptestcd_cdisc="VOLPK", + pptest_cdisc="Volume of PK sample") PKNCA.set.summary( name="volpk", description="geometric mean and geometric coefficient of variation", @@ -52,7 +54,9 @@ add.interval.col("ae", values=c(FALSE, TRUE), unit_type="amount", pretty_name="Amount excreted", - desc="The amount excreted (typically into urine or feces)") + desc="The amount excreted (typically into urine or feces)", + pptestcd_cdisc="RCAMINT", + pptest_cdisc="Amt Rec from T1 to T2") PKNCA.set.summary( name="ae", description="geometric mean and geometric coefficient of variation", @@ -82,7 +86,9 @@ add.interval.col("clr.last", pretty_name="Renal clearance (from AUClast)", formalsmap=list(auc="auclast"), depends="ae", - desc="The renal clearance calculated using AUClast") + desc="The renal clearance calculated using AUClast", + pptestcd_cdisc="RENALCL", + pptest_cdisc="Renal CL") PKNCA.set.summary( name="clr.last", description="geometric mean and geometric coefficient of variation", @@ -96,7 +102,9 @@ add.interval.col("clr.obs", pretty_name="Renal clearance (from AUCinf,obs)", formalsmap=list(auc="aucinf.obs"), depends="ae", - desc="The renal clearance calculated using AUCinf,obs") + desc="The renal clearance calculated using AUCinf,obs", + pptestcd_cdisc="RENALCL", + pptest_cdisc="Renal CL") PKNCA.set.summary( name="clr.obs", description="geometric mean and geometric coefficient of variation", @@ -110,7 +118,9 @@ add.interval.col("clr.pred", pretty_name="Renal clearance (from AUCinf,pred)", formalsmap=list(auc="aucinf.pred"), depends="ae", - desc="The renal clearance calculated using AUCinf,pred") + desc="The renal clearance calculated using AUCinf,pred", + pptestcd_cdisc="RENALCL", + pptest_cdisc="Renal CL") PKNCA.set.summary( name="clr.pred", description="geometric mean and geometric coefficient of variation", @@ -139,7 +149,9 @@ add.interval.col("fe", pretty_name="Fraction excreted", values=c(FALSE, TRUE), depends="ae", - desc="The fraction of the dose excreted") + desc="The fraction of the dose excreted", + pptestcd_cdisc="FREXINT", + pptest_cdisc="Fract Excr from T1 to T2") PKNCA.set.summary( name="fe", description="geometric mean and geometric coefficient of variation", @@ -187,7 +199,9 @@ add.interval.col("ertlst", FUN="pk.calc.ertlst", unit_type="time", pretty_name="Tlast excretion rate", - desc="The midpoint collection time of the last measurable excretion rate (typically in urine or feces)") + desc="The midpoint collection time of the last measurable excretion rate (typically in urine or feces)", + pptestcd_cdisc="ERTLST", + pptest_cdisc="Time of Last Excretion Rate") PKNCA.set.summary( name="ertlst", @@ -235,7 +249,9 @@ add.interval.col("ermax", FUN="pk.calc.ermax", unit_type="amount_time", pretty_name="Maximum excretion rate", - desc="The maximum excretion rate (typically in urine or feces)") + desc="The maximum excretion rate (typically in urine or feces)", + pptestcd_cdisc="ERMAX", + pptest_cdisc="Max Excretion Rate") PKNCA.set.summary( name="ermax", @@ -290,7 +306,9 @@ add.interval.col("ertmax", FUN="pk.calc.ertmax", unit_type="time", pretty_name="Tmax excretion rate", - desc="The midpoint collection time of the maximum excretion rate (typically in urine or feces)") + desc="The midpoint collection time of the maximum excretion rate (typically in urine or feces)", + pptestcd_cdisc="ERTMAX", + pptest_cdisc="Midpoint of Interval of Maximum ER") PKNCA.set.summary( name="ertmax", diff --git a/R/sparse.R b/R/sparse.R index 461396d2..dcbb32f6 100644 --- a/R/sparse.R +++ b/R/sparse.R @@ -369,7 +369,9 @@ add.interval.col( values=c(FALSE, TRUE), unit_type="auc", pretty_name="Sparse AUClast", - desc="For sparse PK sampling, the area under the concentration time curve from the beginning of the interval to the last concentration above the limit of quantification" + desc="For sparse PK sampling, the area under the concentration time curve from the beginning of the interval to the last concentration above the limit of quantification", + pptestcd_cdisc="SPARSEAL", + pptest_cdisc="Sparse AUClast" ) add.interval.col( @@ -379,7 +381,9 @@ add.interval.col( unit_type="auc", pretty_name="Sparse AUClast standard error", desc="For sparse PK sampling, the standard error of the area under the concentration time curve from the beginning of the interval to the last concentration above the limit of quantification", - depends="sparse_auclast" + depends="sparse_auclast", + pptestcd_cdisc="SPARSEAS", + pptest_cdisc="Sparse AUClast standard error" ) add.interval.col( @@ -389,7 +393,9 @@ add.interval.col( unit_type="count", pretty_name="Sparse AUClast degrees of freedom", desc="For sparse PK sampling, the standard error degrees of freedom of the area under the concentration time curve from the beginning of the interval to the last concentration above the limit of quantification", - depends="sparse_auclast" + depends="sparse_auclast", + pptestcd_cdisc="SPARSEAD", + pptest_cdisc="Sparse AUClast degrees of freedom" ) #' Is a PKNCA object used for sparse PK? diff --git a/R/time.above.R b/R/time.above.R index aaad375b..a3857d36 100644 --- a/R/time.above.R +++ b/R/time.above.R @@ -89,7 +89,9 @@ add.interval.col("time_above", values=c(FALSE, TRUE), unit_type="time", pretty_name="Time above Concentration", - desc="Time above a given concentration") + desc="Time above a given concentration", + pptestcd_cdisc="TAT", + pptest_cdisc="Time Above Threshold") PKNCA.set.summary( name="time_above", description="arithmetic mean and standard deviation", diff --git a/R/zzz-pk.calc.dn.R b/R/zzz-pk.calc.dn.R index 661d16d0..eac6c64f 100644 --- a/R/zzz-pk.calc.dn.R +++ b/R/zzz-pk.calc.dn.R @@ -17,6 +17,19 @@ local({ "clr.last", "clr.obs", "clr.pred")) { current_unit_type <- get.interval.cols()[[n]]$unit_type current_pretty_name <- get.interval.cols()[[n]]$pretty_name + current_pptestcd_cdisc <- get.interval.cols()[[n]]$pptestcd_cdisc + current_pptest_cdisc <- get.interval.cols()[[n]]$pptest_cdisc + # Derive dose-normalized CDISC codes from the base parameter + dn_pptestcd <- if (is.character(current_pptestcd_cdisc)) { + paste0(current_pptestcd_cdisc, "D") + } else { + current_pptestcd_cdisc + } + dn_pptest <- if (is.character(current_pptest_cdisc)) { + paste(current_pptest_cdisc, "by Dose") + } else { + current_pptest_cdisc + } # Add the column to the interval specification add.interval.col( name=paste(n, "dn", sep="."), @@ -26,7 +39,9 @@ local({ pretty_name=paste(current_pretty_name, "(dose-normalized)"), desc=paste("Dose normalized", n), formalsmap=list(parameter=n), - depends=c(n) + depends=c(n), + pptestcd_cdisc=dn_pptestcd, + pptest_cdisc=dn_pptest ) PKNCA.set.summary( name=paste(n, "dn", sep="."), diff --git a/man/add.interval.col.Rd b/man/add.interval.col.Rd index 380cbde4..60a5dde4 100644 --- a/man/add.interval.col.Rd +++ b/man/add.interval.col.Rd @@ -14,7 +14,9 @@ add.interval.col( desc = "", sparse = FALSE, formalsmap = list(), - datatype = c("interval", "individual", "population") + datatype = c("interval", "individual", "population"), + pptestcd_cdisc = NULL, + pptest_cdisc = NULL ) } \arguments{ @@ -44,6 +46,14 @@ to NCA parameter names. See the details for information on use of \code{formalsmap}.} \item{datatype}{The type of data used for the calculation} + +\item{pptestcd_cdisc}{The CDISC PPTESTCD code for this parameter. Can be a +character string for simple mappings, or a named list for route-dependent +mappings (e.g., \code{list(route = list(extravascular = "CLF/FO", intravascular = "CLO"))}). Defaults to \code{name} if not provided.} + +\item{pptest_cdisc}{The CDISC PPTEST name for this parameter. Can be a +character string or a named list (same structure as \code{pptestcd_cdisc}). +Defaults to \code{desc} if not provided.} } \value{ NULL (Calling this function has a side effect of changing the diff --git a/man/as.data.frame.PKNCAresults.Rd b/man/as.data.frame.PKNCAresults.Rd index c5fea030..7679972d 100644 --- a/man/as.data.frame.PKNCAresults.Rd +++ b/man/as.data.frame.PKNCAresults.Rd @@ -8,7 +8,7 @@ data.frame.} \method{as.data.frame}{PKNCAresults}( x, ..., - out_format = c("long", "wide"), + out_format = c("long", "wide", "cdisc"), filter_requested = FALSE, filter_excluded = FALSE, out.format = deprecated() @@ -19,7 +19,11 @@ data.frame.} \item{...}{Ignored (for compatibility with generic \code{\link[=as.data.frame]{as.data.frame()}})} -\item{out_format}{Should the output be 'long' (default) or 'wide'?} +\item{out_format}{Should the output be 'long' (default), 'wide', or 'cdisc'? +When 'cdisc', the PPTESTCD column is translated to CDISC standard codes +and a PPTEST column with the CDISC test name is added. Route-dependent +parameters (e.g. CL, VZ, MRT) are resolved using the route information +from the dose data.} \item{filter_requested}{Only return rows with parameters that were specifically requested?} diff --git a/man/filter.PKNCAresults.Rd b/man/filter.PKNCAresults.Rd index 3898cac3..9816d574 100644 --- a/man/filter.PKNCAresults.Rd +++ b/man/filter.PKNCAresults.Rd @@ -18,14 +18,14 @@ lazy data frame (e.g. from dbplyr or dtplyr). See \emph{Methods}, below, for more details.} \item{...}{<\code{\link[rlang:args_data_masking]{data-masking}}> Expressions that -return a logical vector, defined in terms of the variables in \code{.data}. If -multiple expressions are included, they are combined with the \code{&} operator. -To combine expressions using \code{|} instead, wrap them in \code{\link[dplyr:when_any]{when_any()}}. Only -rows for which all expressions evaluate to \code{TRUE} are kept (for \code{filter()}) -or dropped (for \code{filter_out()}).} +return a logical value, and are defined in terms of the variables in +\code{.data}. If multiple expressions are included, they are combined with the +\code{&} operator. Only rows for which all conditions evaluate to \code{TRUE} are +kept.} -\item{.preserve}{Relevant when the \code{.data} input is grouped. If \code{.preserve = FALSE} (the default), the grouping structure is recalculated based on the -resulting data, otherwise the grouping is kept as is.} +\item{.preserve}{Relevant when the \code{.data} input is grouped. +If \code{.preserve = FALSE} (the default), the grouping structure +is recalculated based on the resulting data, otherwise the grouping is kept as is.} } \description{ dplyr filtering for PKNCA diff --git a/man/group_by.PKNCAresults.Rd b/man/group_by.PKNCAresults.Rd index 104f671a..adff58a5 100644 --- a/man/group_by.PKNCAresults.Rd +++ b/man/group_by.PKNCAresults.Rd @@ -26,16 +26,20 @@ lazy data frame (e.g. from dbplyr or dtplyr). See \emph{Methods}, below, for more details.} -\item{...}{<\code{\link[rlang:args_data_masking]{data-masking}}> In \code{group_by()}, -variables or computations to group by. Computations are always done on the -ungrouped data frame. To perform computations on the grouped data, you need -to use a separate \code{mutate()} step before the \code{group_by()}. +\item{...}{In \code{group_by()}, variables or computations to group by. +Computations are always done on the ungrouped data frame. +To perform computations on the grouped data, you need to use +a separate \code{mutate()} step before the \code{group_by()}. Computations are not allowed in \code{nest_by()}. In \code{ungroup()}, variables to remove from the grouping.} \item{.add}{When \code{FALSE}, the default, \code{group_by()} will override existing groups. To add to the existing groups, use -\code{.add = TRUE}.} +\code{.add = TRUE}. + +This argument was previously called \code{add}, but that prevented +creating a new grouping variable called \code{add}, and conflicts with +our naming conventions.} \item{.drop}{Drop groups formed by factor levels that don't appear in the data? The default is \code{TRUE} except when \code{.data} has been previously diff --git a/tests/testthat/test-001-add.interval.col.R b/tests/testthat/test-001-add.interval.col.R index 7a7b72af..765b26be 100644 --- a/tests/testthat/test-001-add.interval.col.R +++ b/tests/testthat/test-001-add.interval.col.R @@ -114,7 +114,9 @@ test_that("add.interval.col", { sparse=FALSE, formalsmap=list(), depends=NULL, - datatype="interval" + datatype="interval", + pptestcd_cdisc="a", + pptest_cdisc="test addition" ), info="interval column assignment works with FUN=NA" ) @@ -132,7 +134,9 @@ test_that("add.interval.col", { sparse=FALSE, formalsmap=list(), depends=NULL, - datatype="interval" + datatype="interval", + pptestcd_cdisc="a", + pptest_cdisc="test addition" ), info="interval column assignment works with FUN=a character string" ) @@ -150,7 +154,9 @@ test_that("add.interval.col", { sparse=FALSE, formalsmap=list(x="values"), depends=NULL, - datatype="interval" + datatype="interval", + pptestcd_cdisc="a", + pptest_cdisc="test addition" ), info="interval column assignment works with FUN=NA" ) @@ -176,5 +182,32 @@ test_that("fake parameters", { ) }) +test_that("add.interval.col rejects invalid pptestcd_cdisc types", { + expect_error( + add.interval.col(name="a", FUN="mean", unit_type="conc", pretty_name="a", + desc="test", pptestcd_cdisc=123), + regexp="pptestcd_cdisc must be a character string or a list" + ) +}) + +test_that("add.interval.col rejects invalid pptest_cdisc types", { + expect_error( + add.interval.col(name="a", FUN="mean", unit_type="conc", pretty_name="a", + desc="test", pptest_cdisc=123), + regexp="pptest_cdisc must be a character string or a list" + ) +}) + +test_that("add.interval.col accepts list for pptestcd_cdisc", { + add.interval.col(name="a", FUN="mean", unit_type="conc", pretty_name="a", + desc="test", + pptestcd_cdisc=list(route=list(extravascular="EV", intravascular="IV")), + pptest_cdisc="test desc") + result <- get("interval.cols", envir=PKNCA:::.PKNCAEnv)[["a"]] + expect_true(is.list(result$pptestcd_cdisc)) + expect_equal(result$pptestcd_cdisc$route$extravascular, "EV") + expect_equal(result$pptestcd_cdisc$route$intravascular, "IV") +}) + # Reset the original state assign("interval.cols", original_state, envir=PKNCA:::.PKNCAEnv) diff --git a/tests/testthat/test-class-PKNCAresults.R b/tests/testthat/test-class-PKNCAresults.R index f214ac85..87e19f66 100644 --- a/tests/testthat/test-class-PKNCAresults.R +++ b/tests/testthat/test-class-PKNCAresults.R @@ -454,3 +454,278 @@ test_that("as.data.frame.PKNCAresults can filter to remove excluded parameters", expect_equal(nrow(as.data.frame(o_result)), 24) expect_equal(nrow(as.data.frame(o_result, filter_excluded = TRUE)), 14) }) + +# CDISC output format tests ---- + +test_that("as.data.frame.PKNCAresults with out_format='cdisc' adds PPTESTCD and PPTEST", { + d_conc <- data.frame( + subject = rep(1, 4), + time = 0:3, + conc = c(0, 1, 0.5, 0.25) + ) + o_conc <- PKNCAconc(d_conc, conc ~ time | subject) + d_dose <- data.frame(subject = 1, time = 0, dose = 10) + o_dose <- PKNCAdose(d_dose, dose ~ time | subject) + o_data <- PKNCAdata(o_conc, o_dose, intervals = data.frame( + start = 0, end = 3, cmax = TRUE, auclast = TRUE, tmax = TRUE + )) + suppressMessages(o_nca <- pk.nca(o_data)) + + result_cdisc <- as.data.frame(o_nca, out_format = "cdisc") + + expect_true("PPTESTCD" %in% names(result_cdisc)) + expect_true("PPTEST" %in% names(result_cdisc)) + expect_true("CMAX" %in% result_cdisc$PPTESTCD) + expect_true("AUCLST" %in% result_cdisc$PPTESTCD) + expect_true("TMAX" %in% result_cdisc$PPTESTCD) + # PPTEST should be placed right after PPTESTCD + pptestcd_pos <- which(names(result_cdisc) == "PPTESTCD") + pptest_pos <- which(names(result_cdisc) == "PPTEST") + expect_equal(pptest_pos, pptestcd_pos + 1) + # Check PPTEST values + cmax_row <- result_cdisc[result_cdisc$PPTESTCD == "CMAX", ] + expect_equal(cmax_row$PPTEST, "Max Conc") +}) + +test_that("as.data.frame.PKNCAresults with out_format='cdisc' resolves route-dependent params", { + d_conc <- data.frame( + subject = rep(1, 5), + time = 0:4, + conc = c(0, 2, 1, 0.5, 0.25) + ) + o_conc <- PKNCAconc(d_conc, conc ~ time | subject) + d_dose <- data.frame(subject = 1, time = 0, dose = 10) + + # Extravascular + o_dose_ev <- PKNCAdose(d_dose, dose ~ time | subject, route = "extravascular") + o_data_ev <- PKNCAdata(o_conc, o_dose_ev, intervals = data.frame( + start = 0, end = Inf, cl.obs = TRUE, aucinf.obs = TRUE, half.life = TRUE + )) + suppressMessages(suppressWarnings(o_nca_ev <- pk.nca(o_data_ev))) + result_ev <- as.data.frame(o_nca_ev, out_format = "cdisc") + expect_true("CLF/FO" %in% result_ev$PPTESTCD) + expect_false("CLO" %in% result_ev$PPTESTCD) + + # Intravascular + o_dose_iv <- PKNCAdose(d_dose, dose ~ time | subject, route = "intravascular") + o_data_iv <- PKNCAdata(o_conc, o_dose_iv, intervals = data.frame( + start = 0, end = Inf, cl.obs = TRUE, aucinf.obs = TRUE, half.life = TRUE + )) + suppressMessages(suppressWarnings(o_nca_iv <- pk.nca(o_data_iv))) + result_iv <- as.data.frame(o_nca_iv, out_format = "cdisc") + expect_true("CLO" %in% result_iv$PPTESTCD) + expect_false("CLF/FO" %in% result_iv$PPTESTCD) +}) + +test_that("as.data.frame.PKNCAresults with out_format='cdisc' does not add PPSTINT/PPENINT without INT params", { + d_conc <- data.frame( + subject = rep(1, 4), + time = 0:3, + conc = c(0, 1, 0.5, 0.25) + ) + o_conc <- PKNCAconc(d_conc, conc ~ time | subject) + d_dose <- data.frame(subject = 1, time = 0, dose = 10) + o_dose <- PKNCAdose(d_dose, dose ~ time | subject) + o_data <- PKNCAdata(o_conc, o_dose, intervals = data.frame( + start = 0, end = 3, cmax = TRUE + )) + suppressMessages(o_nca <- pk.nca(o_data)) + + result_cdisc <- as.data.frame(o_nca, out_format = "cdisc") + expect_false("PPSTINT" %in% names(result_cdisc)) + expect_false("PPENINT" %in% names(result_cdisc)) +}) + +test_that("as.data.frame.PKNCAresults with out_format='cdisc' adds PPSTINT/PPENINT for INT params", { + d_conc <- data.frame( + subject = rep(1, 5), + time = 0:4, + conc = c(0, 2, 1, 0.5, 0.25) + ) + o_conc <- PKNCAconc(d_conc, conc ~ time | subject, timeu = "hr") + d_dose <- data.frame(subject = 1, time = 0, dose = 10) + o_dose <- PKNCAdose(d_dose, dose ~ time | subject) + o_data <- PKNCAdata(o_conc, o_dose, intervals = data.frame( + start = 0, end = 4, cmax = TRUE, aucint.last = TRUE + ), options = list(allow_partial_missing_units = TRUE)) + expect_warning( + suppressMessages(o_nca <- pk.nca(o_data)), + regexp = "Units are provided for some" + ) + + result_cdisc <- as.data.frame(o_nca, out_format = "cdisc") + + # PPSTINT and PPENINT columns should exist + expect_true("PPSTINT" %in% names(result_cdisc)) + expect_true("PPENINT" %in% names(result_cdisc)) + + # INT rows should have values, non-INT rows should be NA + int_rows <- grepl("INT", result_cdisc$PPTESTCD, fixed = TRUE) + expect_true(any(int_rows), info = "At least one INT parameter should be present") + expect_true(all(!is.na(result_cdisc$PPSTINT[int_rows]))) + expect_true(all(!is.na(result_cdisc$PPENINT[int_rows]))) + expect_true(all(is.na(result_cdisc$PPSTINT[!int_rows]))) + expect_true(all(is.na(result_cdisc$PPENINT[!int_rows]))) + + # Values should be ISO 8601 durations relative to dose time (0) + # start=0, dose_time=0 -> PT0H; end=4, dose_time=0 -> PT4H + int_result <- result_cdisc[int_rows, ] + expect_equal(int_result$PPSTINT[1], "PT0H") + expect_equal(int_result$PPENINT[1], "PT4H") +}) + +test_that("PPSTINT/PPENINT uses timeu_pref when available", { + d_conc <- data.frame( + subject = rep(1, 5), + time = 0:4, + conc = c(0, 2, 1, 0.5, 0.25) + ) + o_conc <- PKNCAconc(d_conc, conc ~ time | subject, timeu = "hr", timeu_pref = "min") + d_dose <- data.frame(subject = 1, time = 0, dose = 10) + o_dose <- PKNCAdose(d_dose, dose ~ time | subject) + o_data <- PKNCAdata(o_conc, o_dose, intervals = data.frame( + start = 0, end = 4, aucint.last = TRUE + ), options = list(allow_partial_missing_units = TRUE)) + expect_warning( + suppressMessages(o_nca <- pk.nca(o_data)), + regexp = "Units are provided for some" + ) + + result_cdisc <- as.data.frame(o_nca, out_format = "cdisc") + int_rows <- grepl("INT", result_cdisc$PPTESTCD, fixed = TRUE) + # timeu_pref is "min", so durations should use M designator + expect_equal(result_cdisc$PPSTINT[int_rows][1], "PT0M") + expect_equal(result_cdisc$PPENINT[int_rows][1], "PT4M") +}) + +test_that("PPSTINT/PPENINT computes relative to last dose time", { + d_conc <- data.frame( + subject = rep(1, 10), + time = 0:9, + conc = c(0, 2, 1, 0.5, 0.25, 0, 3, 1.5, 0.75, 0.3) + ) + o_conc <- PKNCAconc(d_conc, conc ~ time | subject, timeu = "hr") + # Two doses: at time 0 and time 5 + d_dose <- data.frame(subject = c(1, 1), time = c(0, 5), dose = c(10, 10)) + o_dose <- PKNCAdose(d_dose, dose ~ time | subject) + o_data <- PKNCAdata(o_conc, o_dose, intervals = data.frame( + start = c(0, 5), end = c(5, 9), aucint.last = TRUE + ), options = list(allow_partial_missing_units = TRUE)) + expect_warning( + suppressMessages(o_nca <- pk.nca(o_data)), + regexp = "Units are provided for some" + ) + + result_cdisc <- as.data.frame(o_nca, out_format = "cdisc") + int_rows <- grepl("INT", result_cdisc$PPTESTCD, fixed = TRUE) + int_result <- result_cdisc[int_rows, ] + + # First interval: start=0, end=5, last_dose=0 -> PT0H, PT5H + row1 <- int_result[int_result$start == 0, ] + expect_equal(row1$PPSTINT[1], "PT0H") + expect_equal(row1$PPENINT[1], "PT5H") + + # Second interval: start=5, end=9, last_dose=5 -> PT0H, PT4H + row2 <- int_result[int_result$start == 5, ] + expect_equal(row2$PPSTINT[1], "PT0H") + expect_equal(row2$PPENINT[1], "PT4H") +}) + +test_that("PPSTINT/PPENINT uses day designator for day units", { + d_conc <- data.frame( + subject = rep(1, 4), + time = c(0, 1, 2, 3), + conc = c(0, 2, 1, 0.5) + ) + o_conc <- PKNCAconc(d_conc, conc ~ time | subject, timeu = "day") + d_dose <- data.frame(subject = 1, time = 0, dose = 10) + o_dose <- PKNCAdose(d_dose, dose ~ time | subject) + o_data <- PKNCAdata(o_conc, o_dose, intervals = data.frame( + start = 0, end = 3, aucint.last = TRUE + ), options = list(allow_partial_missing_units = TRUE)) + expect_warning( + suppressMessages(o_nca <- pk.nca(o_data)), + regexp = "Units are provided for some" + ) + + result_cdisc <- as.data.frame(o_nca, out_format = "cdisc") + int_rows <- grepl("INT", result_cdisc$PPTESTCD, fixed = TRUE) + expect_equal(result_cdisc$PPSTINT[int_rows][1], "P0D") + expect_equal(result_cdisc$PPENINT[int_rows][1], "P3D") +}) + +test_that("format_iso8601_duration handles edge cases", { + expect_equal(PKNCA:::format_iso8601_duration(0, "hr"), "PT0H") + expect_equal(PKNCA:::format_iso8601_duration(24, "hr"), "PT24H") + expect_equal(PKNCA:::format_iso8601_duration(1.5, "hr"), "PT1.5H") + expect_equal(PKNCA:::format_iso8601_duration(30, "min"), "PT30M") + expect_equal(PKNCA:::format_iso8601_duration(3600, "s"), "PT3600S") + expect_equal(PKNCA:::format_iso8601_duration(7, "day"), "P7D") + expect_true(is.na(PKNCA:::format_iso8601_duration(NA, "hr"))) + expect_true(is.na(PKNCA:::format_iso8601_duration(Inf, "hr"))) +}) + +test_that("as.data.frame.PKNCAresults default format does not include PPSTINT/PPENINT", { + d_conc <- data.frame( + subject = rep(1, 5), + time = 0:4, + conc = c(0, 2, 1, 0.5, 0.25) + ) + o_conc <- PKNCAconc(d_conc, conc ~ time | subject, timeu = "hr") + d_dose <- data.frame(subject = 1, time = 0, dose = 10) + o_dose <- PKNCAdose(d_dose, dose ~ time | subject) + o_data <- PKNCAdata(o_conc, o_dose, intervals = data.frame( + start = 0, end = 4, aucint.last = TRUE + ), options = list(allow_partial_missing_units = TRUE)) + expect_warning( + suppressMessages(o_nca <- pk.nca(o_data)), + regexp = "Units are provided for some" + ) + + # Default (long) format should not have PPSTINT/PPENINT + result_long <- as.data.frame(o_nca) + expect_false("PPSTINT" %in% names(result_long)) + expect_false("PPENINT" %in% names(result_long)) +}) + +test_that("pknca_cdisc_get_route falls back to extravascular when no dose data", { + d_conc <- data.frame(subject = rep(1, 4), time = 0:3, conc = c(0, 1, 0.5, 0.25)) + o_conc <- PKNCAconc(d_conc, conc ~ time | subject) + o_data <- PKNCAdata(o_conc, intervals = data.frame(start = 0, end = 3, cmax = TRUE)) + suppressMessages(o_nca <- pk.nca(o_data)) + + result_cdisc <- as.data.frame(o_nca, out_format = "cdisc") + expect_true("PPTESTCD" %in% names(result_cdisc)) + expect_true("CMAX" %in% result_cdisc$PPTESTCD) +}) + +test_that("resolve_cdisc_value falls back to first route when route not matched", { + # Route-dependent list with unknown route should fall back to first element + val <- list(route = list(extravascular = "EV_CODE", intravascular = "IV_CODE")) + expect_equal(PKNCA:::resolve_cdisc_value(val, "unknown_route"), "EV_CODE") + # Non-list, non-character fallback + expect_equal(PKNCA:::resolve_cdisc_value(42, "extravascular"), "42") +}) + +test_that("format_iso8601_duration falls back to hours for unknown unit", { + expect_equal(PKNCA:::format_iso8601_duration(5, "fortnights"), "PT5H") + expect_equal(PKNCA:::format_iso8601_duration(10, NA), "PT10H") +}) + +test_that("pknca_cdisc_get_timeu returns NA when no conc data", { + # Minimal PKNCAresults with no conc object + minimal <- PKNCAresults(data.frame(a = 1), data = list()) + expect_true(is.na(PKNCA:::pknca_cdisc_get_timeu(minimal))) +}) + +test_that("pknca_cdisc_get_last_dose_time returns NA when no dose data", { + d_conc <- data.frame(subject = rep(1, 4), time = 0:3, conc = c(0, 1, 0.5, 0.25)) + o_conc <- PKNCAconc(d_conc, conc ~ time | subject) + o_data <- PKNCAdata(o_conc, intervals = data.frame(start = 0, end = 3, cmax = TRUE)) + suppressMessages(o_nca <- pk.nca(o_data)) + + ret <- as.data.frame(o_nca) + result <- PKNCA:::pknca_cdisc_get_last_dose_time(ret, o_nca) + expect_true(all(is.na(result))) +}) +