diff --git a/NEWS.md b/NEWS.md index e7558031..24a9a0d8 100644 --- a/NEWS.md +++ b/NEWS.md @@ -27,6 +27,7 @@ the dosing including dose amount and route. * `PKNCA.set.summary(reset = TRUE)` warns that it may break the use of `summary()` (#477) +* `pk.nca` output now includes a `PPANMETH` column describing the analysis methods used for each parameter regarding imputations, AUC and half.life calculations (#457) * New post-processing functions to normalize PKNCA result parameters based on any column in PKNCAconc data.frame (`normalize_by_col()`) or by using a custom normalization table (`normalize()`) * New excretion rate parameters: `ermax` (Maximum excretion rate), `ertmax` (Midpoint time of maximum excretion rate) and `ertlst` (Time of last excretion rate measurement) (#433) diff --git a/R/auc.R b/R/auc.R index 5f6718b9..ed5a3ad7 100644 --- a/R/auc.R +++ b/R/auc.R @@ -184,6 +184,15 @@ pk.calc.auxc <- function(conc, time, interval=c(0, Inf), fun_linear = fun_linear, fun_log = fun_log, fun_inf = fun_inf ) } + # Add method details as an attribute + attr(ret, "method") <- character() + auc_method <- if (is.null(method)) { + PKNCA.options()$auc.method + } else { + method + } + attr(ret, "method") <- c(attr(ret, "method"), paste0("AUC: ", auc_method)) + ret } diff --git a/R/aucint.R b/R/aucint.R index b8aebf86..ca562742 100644 --- a/R/aucint.R +++ b/R/aucint.R @@ -184,6 +184,15 @@ pk.calc.aucint <- function(conc, time, fun_log = aucintegrate_log, fun_inf = aucintegrate_inf ) + # Add method details as an attribute + attr(ret, "method") <- character() + auc_method <- if (is.null(method)) { + PKNCA.options()$auc.method + } else { + method + } + attr(ret, "method") <- c(attr(ret, "method"), paste0("AUC: ", auc_method)) + ret } diff --git a/R/class-PKNCAresults.R b/R/class-PKNCAresults.R index 7fdefeb9..e42054fa 100644 --- a/R/class-PKNCAresults.R +++ b/R/class-PKNCAresults.R @@ -89,7 +89,7 @@ as.data.frame.PKNCAresults <- function(x, ..., out_format = c('long', 'wide'), f # Since we moved the results into PPTESTCD and PPORRES regardless of what # they really are in the source data, remove the extra units and unit # conversion columns to allow spread to work. - ret <- ret[, setdiff(names(ret), c("PPSTRES", "PPSTRESU", "PPORRESU"))] + ret <- ret[, setdiff(names(ret), c("PPSTRES", "PPSTRESU", "PPORRESU", "PPANMETH"))] ret <- tidyr::spread(ret, key="PPTESTCD", value="PPORRES") } ret diff --git a/R/half.life.R b/R/half.life.R index 0dab3f1e..8a360e3b 100644 --- a/R/half.life.R +++ b/R/half.life.R @@ -134,6 +134,7 @@ pk.calc.half.life <- function(conc, time, tmax, tlast, data <- data[as.numeric(data$time) > max(end.dose, na.rm = TRUE), ] } } + # Prepare the return values ret <- data.frame( # Terminal elimination slope @@ -156,6 +157,11 @@ pk.calc.half.life <- function(conc, time, tmax, tlast, half.life=NA, # T1/2 span ratio span.ratio=NA) + + + # Build method attribute + attr(ret, "method") <- character() + ret_replacements <- c("lambda.z", "r.squared", "adj.r.squared", "lambda.z.corrxy", "lambda.z.time.first", "lambda.z.time.last", "lambda.z.n.points", "clast.pred", "half.life", "span.ratio") @@ -181,6 +187,7 @@ pk.calc.half.life <- function(conc, time, tmax, tlast, dfK <- data[as.numeric(data$time) > as.numeric(ret$tmax), ] } if (manually.selected.points) { + attr(ret, "method") <- c(attr(ret, "method"), "Lambda Z: Manual selection") if (nrow(data) > 0) { fit <- fit_half_life(data=data, tlast=ret$tlast, conc_units=conc_units) ret[,ret_replacements] <- fit[,ret_replacements] @@ -271,6 +278,7 @@ pk.calc.half.life <- function(conc, time, tmax, tlast, ret$tmax <- NULL if (!missing(tlast)) ret$tlast <- NULL + ret } diff --git a/R/pk.calc.all.R b/R/pk.calc.all.R index e3cfaacc..08c660ca 100644 --- a/R/pk.calc.all.R +++ b/R/pk.calc.all.R @@ -370,6 +370,7 @@ pk.nca.interval <- function(conc, time, volume, duration.conc, if (nrow(interval) != 1) { stop("Please report a bug. Interval must be a one-row data.frame") } + if (!all(is.na(impute_method))) { impute_funs <- PKNCA_impute_fun_list(impute_method) stopifnot(length(impute_funs) == 1) @@ -385,6 +386,11 @@ pk.nca.interval <- function(conc, time, volume, duration.conc, } conc <- impute_data$conc time <- impute_data$time + tmp_imp_method <- c( + paste0("Imputation: ", paste(na.omit(impute_method), collapse = ", ")) + ) + } else { + tmp_imp_method <- character() } # Prepare the return value using SDTM names ret <- data.frame(PPTESTCD=NA, PPORRES=NA)[-1,] @@ -492,6 +498,7 @@ pk.nca.interval <- function(conc, time, volume, duration.conc, } } } + # Apply manual inclusion and exclusion if (n %in% "half.life") { uses_include_hl <- !is.null(include_half.life) && !all(is.na(include_half.life)) @@ -507,6 +514,7 @@ pk.nca.interval <- function(conc, time, volume, duration.conc, call_args$time <- call_args$time[!exclude_tf] } } + # Do the calculation tmp_result <- do.call(all_intervals[[n]]$FUN, call_args) # The handling of the exclude column is documented in the @@ -524,6 +532,10 @@ pk.nca.interval <- function(conc, time, volume, duration.conc, } else { NA_character_ } + # The handling of the method column (PPANMETH) + tmp_method <- c(tmp_imp_method, attr(tmp_result, "method")) + attr(tmp_result, "method") <- NULL + # If the function returns a data frame, save all the returned values, # otherwise, save the value returned. if (is.data.frame(tmp_result)) { @@ -557,6 +569,7 @@ pk.nca.interval <- function(conc, time, volume, duration.conc, data.frame( PPTESTCD=tmp_testcd, PPORRES=tmp_result, + PPANMETH=paste(tmp_method, collapse=". "), exclude=exclude_reason, stringsAsFactors=FALSE ) diff --git a/R/sparse.R b/R/sparse.R index 7a59f2cc..b65c8ec3 100644 --- a/R/sparse.R +++ b/R/sparse.R @@ -316,13 +316,24 @@ pk.calc.sparse_auc <- function(conc, time, subject, auc.type=auc.type, method="linear" ) + var_auc <- var_sparse_auc(sparse_pk_mean) - data.frame( + ret <- data.frame( sparse_auc=auc, # as.numeric() drops the "df" attribute sparse_auc_se=sqrt(as.numeric(var_auc)), sparse_auc_df=attr(var_auc, "df") ) + + # Add method details as an attribute + for (col in names(ret)) { + attr(ret[[col]], "method") <- character() + auc_method <- "linear" + attr(ret[[col]], "method") <- c(attr(ret[[col]], "method"), paste0("AUC: ", auc_method)) + attr(ret[[col]], "method") <- c(attr(ret[[col]], "method"), paste0("Sparse: ", "arithmetic mean, <=50% BLQ")) + } + + ret } #' @describeIn pk.calc.sparse_auc Compute the AUClast for sparse PK diff --git a/tests/testthat/test-auc.R b/tests/testthat/test-auc.R index 328f9ddc..39837e9a 100644 --- a/tests/testthat/test-auc.R +++ b/tests/testthat/test-auc.R @@ -71,7 +71,7 @@ test_that("pk.calc.auc: Linear AUC when the conc at the end of the interval is a lambda.z=NA, auc.type=t, method="linear")) - expect_equal(v1, tests[[t]], info=t) + expect_equal(v1, tests[[t]], info=t, ignore_attr = TRUE) } }) @@ -95,7 +95,7 @@ test_that("pk.calc.auc: Linear AUC when the conc at the end of the interval is B lambda.z=NA, auc.type=t, method="linear")) - expect_equal(v1, tests[[t]], info=t) + expect_equal(v1, tests[[t]], info=t, ignore_attr = TRUE) } }) @@ -118,7 +118,7 @@ test_that("pk.calc.auc: Linear AUC when the conc at the end of the interval is B lambda.z=1, auc.type=t, method="linear")) - expect_equal(v1, tests[[t]], info=t) + expect_equal(v1, tests[[t]], info=t, ignore_attr = TRUE) } }) @@ -140,7 +140,7 @@ test_that("pk.calc.auc: Linear AUC when when there are multiple BLQ values at th lambda.z=1, auc.type=t, method="linear")) - expect_equal(v1, tests[[t]], info=t) + expect_equal(v1, tests[[t]], info=t, ignore_attr = TRUE) } }) @@ -171,7 +171,7 @@ test_that("pk.calc.auc: Confirm that center BLQ points are dropped, kept, or imp auc.type=n, conc.blq="keep", method=t)) - expect_equal(v1, tests[[t]][[n]], info=paste(t, n)) + expect_equal(v1, tests[[t]][[n]], info=paste(t, n), ignore_attr = TRUE) } } }) @@ -203,7 +203,7 @@ test_that("pk.calc.auc: Confirm BLQ in the middle or end are calculated correctl auc.type=n, conc.blq="keep", method=t)) - expect_equal(v1, tests[[t]][[n]], info=paste(t, n)) + expect_equal(v1, tests[[t]][[n]], info=paste(t, n), ignore_attr = TRUE) } } @@ -238,9 +238,12 @@ test_that("pk.calc.auc: Confirm BLQ in the middle or end are calculated correctl middle="drop", last="keep"), method=t)) - expect_equal(v1, - tests[[t]][[n]], - info=paste(t, n)) + expect_equal( + v1, + tests[[t]][[n]], + info=paste(t, n), + ignore_attr = TRUE + ) } } }) @@ -274,9 +277,12 @@ test_that("pk.calc.auc: When AUCinf is requested with NA for lambda.z, the resul middle="drop", last="keep"), method=t)) - expect_equal(v1, - tests[[t]][[n]], - info=paste(t, n)) + expect_equal( + v1, + tests[[t]][[n]], + info=paste(t, n), + ignore_attr = TRUE + ) } } }) @@ -314,9 +320,12 @@ test_that("pk.calc.auc: Test NA at the end", { middle="drop", last="keep"), method=t)) - expect_equal(v1, - tests[[t]][[n]], - info=paste(t, n)) + expect_equal( + v1, + tests[[t]][[n]], + info=paste(t, n), + ignore_attr = TRUE + ) } }) @@ -349,9 +358,12 @@ test_that("pk.calc.auc: interpolation of times within the time interval", { middle="drop", last="keep"), method=t)) - expect_equal(v1, - tests[[t]][[n]], - info=paste(t, n)) + expect_equal( + v1, + tests[[t]][[n]], + info=paste(t, n), + ignore_attr = TRUE + ) } } }) @@ -397,7 +409,8 @@ test_that("pk.calc.auc: warning with beginning of interval before the beginning expect_equal( v1, tests[[t]][[n]], - info=paste(t, n) + info=paste(t, n), + ignore_attr = TRUE ) } } @@ -449,7 +462,8 @@ test_that("pk.calc.auc: warning with beginning of interval before the beginning ) expect_equal(v1, tests[[t]][[n]], - info=paste(t, n)) + info=paste(t, n), + ignore_attr = TRUE) } # Confirm error with concentration and time not of equal lengths @@ -563,14 +577,18 @@ test_that("pk.calc.aumc", { time=0:3, interval=c(0, 3), method="linear"), - 3.75) + 3.75, + ignore_attr = TRUE + ) expect_equal( pk.calc.aumc( conc=c(0, 1, 1, 0.5), time=0:3, interval=c(0, 3), method="lin up/log down"), - 2-0.5/log(0.5)+0.5/(log(0.5)^2)) + 2-0.5/log(0.5)+0.5/(log(0.5)^2), + ignore_attr = TRUE + ) expect_equal( pk.calc.aumc( conc=c(0, 1, 1, 0.5), @@ -579,7 +597,9 @@ test_that("pk.calc.aumc", { auc.type="AUCinf", lambda.z=1, method="lin up/log down"), - 2 - 0.5/log(0.5) + 0.5/(log(0.5)^2) + 1.5 + 0.5) + 2 - 0.5/log(0.5) + 0.5/(log(0.5)^2) + 1.5 + 0.5, + ignore_attr = TRUE + ) }) @@ -728,3 +748,34 @@ test_that("AUC with a single concentration measured should return NA (fix #176)" ) ) }) + +test_that("pk.calc.auc and wrappers: method attribute is set and propagated", { + + auc_params <- c( + "auc", "auc.last", "auc.inf.obs", "auc.inf.pred", "auc.all", + "aumc.last", "aumc.inf.obs", "aumc.inf.pred", "aumc.all" + ) + auc_methods <- c("linear", "lin up/log down", "lin-log") + auc_args <- list( + conc=c(0,1,1), + time=0:2, + interval=c(0,2), + lambda.z=1, + clast.pred = 1, + clast.obs = 1 + ) + + for (param in auc_params) { + auc_fun <- get(paste0("pk.calc.", param)) + args_fun <- auc_args[intersect(names(auc_args), names(formals(auc_fun)))] + for (method in auc_methods) { + args_fun$method <- method + v <- do.call(auc_fun, args_fun) + expect_equal( + attr(v, "method"), + paste0("AUC: ", method), + info=paste("pk.calc.param sets method attribute for", param, "with method", method) + ) + } + } +}) diff --git a/tests/testthat/test-aucint.R b/tests/testthat/test-aucint.R index 5b3d204f..cff7727b 100644 --- a/tests/testthat/test-aucint.R +++ b/tests/testthat/test-aucint.R @@ -84,6 +84,7 @@ test_that("AUCint gives the same value when no interpolation/extrapolation is re conc = concdata$conc, time = concdata$time, interval = c(0, 3) ), + ignore_attr = TRUE, info = "No interpolation/extrapolation is equivalent to normal AUC" ) expect_equal( @@ -95,6 +96,7 @@ test_that("AUCint gives the same value when no interpolation/extrapolation is re conc = concdata$conc, time = concdata$time, interval = c(0, 3) ), + ignore_attr = TRUE, info = "Giving interval and start+end are the same, no interp/extrap (test 1)" ) expect_equal( @@ -106,6 +108,7 @@ test_that("AUCint gives the same value when no interpolation/extrapolation is re conc = concdata$conc, time = concdata$time, interval = c(0, 2) ), + ignore_attr = TRUE, info = "Giving interval and start+end are the same, no interp/extrap (test 2)" ) }) @@ -177,6 +180,7 @@ test_that("AUCint respects auc.type and does the correct calculations for each A conc = c(concdata$conc, 0.5), time = c(concdata$time, 4), interval = c(0, 4) ), + ignore_attr = TRUE, info = "AUCinf is traced" ) expect_equal( @@ -192,6 +196,7 @@ test_that("AUCint respects auc.type and does the correct calculations for each A conc = c(2, 1), time = c(3, 4), interval = c(3, 4) ), + ignore_attr = TRUE, info = "AUCinf is traced with clast respected" ) expect_equal( @@ -203,6 +208,7 @@ test_that("AUCint respects auc.type and does the correct calculations for each A conc = c(concdata$conc, 0.25), time = c(concdata$time, 4), interval = c(0, 4) ), + ignore_attr = TRUE, info = "AUCinf is traced with lambda.z respected" ) expect_equal( @@ -218,6 +224,7 @@ test_that("AUCint respects auc.type and does the correct calculations for each A conc = c(2, 0.5), time = c(3, 4), interval = c(3, 4) ), + ignore_attr = TRUE, info = "AUCinf is traced with clast and lambda.z respected" ) @@ -230,6 +237,7 @@ test_that("AUCint respects auc.type and does the correct calculations for each A conc = concdata$conc, time = concdata$time, interval = c(0, 3), auc.type = "AUCall" ), + ignore_attr = TRUE, info = "AUCall is the same as AUClast when no BLQ follow tlast (both AUCall)" ) expect_equal( @@ -241,6 +249,7 @@ test_that("AUCint respects auc.type and does the correct calculations for each A conc = concdata$conc, time = concdata$time, interval = c(0, 3), auc.type = "AUClast" ), + ignore_attr = TRUE, info = "AUCall is the same as AUClast when no BLQ follow tlast (test AUClast)" ) expect_equal( @@ -252,6 +261,7 @@ test_that("AUCint respects auc.type and does the correct calculations for each A conc = concdata_blq$conc, time = concdata_blq$time, interval = c(0, 4), auc.type = "AUCall" ), + ignore_attr = TRUE, info = "AUCall is the same the normal calculation when no interpolation/extrapolation happens" ) expect_equal( @@ -263,6 +273,7 @@ test_that("AUCint respects auc.type and does the correct calculations for each A conc = c(concdata$conc, 0.5), time = c(concdata$time, 3.5), interval = c(0, 4), auc.type = "AUClast" ), + ignore_attr = TRUE, info = "AUCall traces correctly" ) }) @@ -321,10 +332,12 @@ test_that("aucint works with infinite intervals", { concdata <- data.frame(conc = c(8, 4, 2, 1), time = 0:3) expect_equal(pk.calc.aucint.last(conc = concdata$conc, time = concdata$time, start = 0, end = Inf), pk.calc.auc.last(conc = concdata$conc, time = concdata$time), + ignore_attr = TRUE, info = "Simple AUClast = aucint.last" ) expect_equal(pk.calc.aucint.all(conc = concdata$conc, time = concdata$time, start = 0, end = Inf), pk.calc.auc.all(conc = concdata$conc, time = concdata$time), + ignore_attr = TRUE, info = "Simple AUCall = aucint.all" ) expect_equal( @@ -337,6 +350,7 @@ test_that("aucint works with infinite intervals", { conc = concdata$conc, time = concdata$time, clast.obs = 1, lambda.z = log(2) ), + ignore_attr = TRUE, info = "Simple AUCinf.obs = aucint.inf.obs" ) expect_equal( @@ -349,6 +363,7 @@ test_that("aucint works with infinite intervals", { conc = concdata$conc, time = concdata$time, clast.pred = 2, lambda.z = log(2) ), + ignore_attr = TRUE, info = "Simple AUCinf.pred = aucint.inf.pred" ) }) @@ -429,11 +444,13 @@ test_that("aucint uses log extrapolation regardless of the interpolation method # the second is more directly mathematical. expect_equal( aucinf_obs6_lin - aucinf_obs5_lin, - aucinf_obs6_log - aucinf_obs5_log + aucinf_obs6_log - aucinf_obs5_log, + ignore_attr = TRUE ) expect_equal( aucinf_obs6_lin, - aucinf_obs5_lin + (6-5)*(clast-ctau_extrap)/log(clast/ctau_extrap) + aucinf_obs5_lin + (6-5)*(clast-ctau_extrap)/log(clast/ctau_extrap), + ignore_attr = TRUE ) }) @@ -454,3 +471,32 @@ test_that("aucint.inf.pred returns NA when half-life is not estimable (#450)", { aucint_inf_pred <- aucint_inf_pred_prep$PPORRES[aucint_inf_pred_prep$PPTESTCD %in% "aucint.inf.pred"] expect_equal(aucint_inf_pred, NA_real_) }) + +test_that("pk.calc.aucint and wrappers: method attribute is set and propagated", { + aucint_params <- c("aucint", "aucint.last", "aucint.inf.obs", "aucint.inf.pred", "aucint.all") + auc_methods <- c("linear", "lin up/log down", "lin-log") + auc_args <- list( + conc = c(0,1,1), + time = 0:2, + interval = c(0,2), + lambda.z = 1, + clast.pred = 1, + clast.obs = 1, + start = 0, + end = 2 + ) + + for (param in aucint_params) { + auc_fun <- get(paste0("pk.calc.", param)) + args_fun <- auc_args[intersect(names(auc_args), names(formals(auc_fun)))] + for (method in auc_methods) { + args_fun$method <- method + v <- do.call(auc_fun, args_fun) + expect_equal( + attr(v, "method"), + paste0("AUC: ", method), + info = paste("pk.calc.param sets method attribute for", param, "with method", method) + ) + } + } +}) diff --git a/tests/testthat/test-auciv.R b/tests/testthat/test-auciv.R index a6fbe4fb..9fd865de 100644 --- a/tests/testthat/test-auciv.R +++ b/tests/testthat/test-auciv.R @@ -6,12 +6,14 @@ test_that("pk.calc.auciv", { expect_equal( # No check is done to confirm that the auc argument matches the data pk.calc.auciv(conc = 0:5, time = 0:5, c0 = 1, auc = 2.75), - 2.75 + 1 - 0.5 + 2.75 + 1 - 0.5, + ignore_attr = TRUE ) expect_equal( # No verifications are made on the data pk.calc.auciv(conc = 0:5, time = 0:5, c0 = 1, auc = 2.75, check=FALSE), - 2.75 + 1 - 0.5 + 2.75 + 1 - 0.5, + ignore_attr = TRUE ) }) @@ -52,3 +54,30 @@ test_that("missing dose information does not cause NA time (#353)", { ) expect_s3_class(o_nca, "PKNCAresults") }) + + +test_that("pk.calc.auciv: method attribute is set and propagated", { + + auc_params <- c("auciv") + auc_methods <- c("linear", "lin up/log down", "lin-log") + auc_args <- list( + conc=3:1, + time=0:2, + c0 = 1, + auc = 2 + ) + + for (param in auc_params) { + auc_fun <- get(paste0("pk.calc.", param)) + args_fun <- auc_args[intersect(names(auc_args), names(formals(auc_fun)))] + for (method in auc_methods) { + args_fun$method <- method + v <- do.call(auc_fun, args_fun) + expect_equal( + attr(v, "method"), + paste0("AUC: ", method), + info=paste("pk.calc.param sets method attribute for", param, "with method", method) + ) + } + } +}) diff --git a/tests/testthat/test-class-PKNCAresults.R b/tests/testthat/test-class-PKNCAresults.R index 09faa6b1..d2788e35 100644 --- a/tests/testthat/test-class-PKNCAresults.R +++ b/tests/testthat/test-class-PKNCAresults.R @@ -1,7 +1,7 @@ test_that("PKNCAresults object creation", { - minimal_result <- PKNCAresults(data.frame(a=1), data=list()) + minimal_result <- PKNCAresults(data.frame(a = 1), data = list()) expect_equal(minimal_result$columns$exclude, "exclude") - result_with_exclude_col <- PKNCAresults(data.frame(exclude=1), data=list()) + result_with_exclude_col <- PKNCAresults(data.frame(exclude = 1), data = list()) expect_equal(result_with_exclude_col$columns$exclude, "exclude.exclude") }) @@ -18,7 +18,7 @@ test_that("PKNCAresults generation", { expect_equal( names(o_result), c("result", "data", "columns"), - info="Make sure that the result has the expected names (and only the expected names) in it." + info = "Make sure that the result has the expected names (and only the expected names) in it." ) expect_true( checkProvenance(o_result), @@ -33,53 +33,72 @@ test_that("PKNCAresults generation", { # calculations are done. tmp$options <- PKNCA.options() tmp - }, info="The data is just a copy of the input data plus an instantiation of the PKNCA.options" + }, + info = "The data is just a copy of the input data plus an instantiation of the PKNCA.options" ) verify.result <- tibble::tibble( - treatment="Trt 1", - ID=as.integer(rep(c(1, 2), each=16)), - start=0, - end=c(24, rep(Inf, 15), - 24, rep(Inf, 15)), - PPTESTCD=rep(c("auclast", "cmax", "tmax", "tlast", "clast.obs", - "lambda.z", "r.squared", "adj.r.squared", "lambda.z.corrxy", - "lambda.z.time.first", "lambda.z.time.last", "lambda.z.n.points", - "clast.pred", "half.life", "span.ratio", "aucinf.obs"), - times=2), - PPORRES=c(13.54, 0.9998, 4.000, 24.00, 0.3441, - 0.04297, 0.9072, 0.9021, -0.9521, 5.000, 24.00, - 20.00, 0.3356, 16.13, 1.178, - 21.55, 14.03, 0.9410, 2.000, - 24.00, 0.3148, 0.05689, 0.9000, - 0.8944, -0.9487, 5.000, 24.00, 20.00, 0.3011, - 12.18, 1.560, 19.56), - exclude=NA_character_ + treatment = "Trt 1", + ID = as.integer(rep(c(1, 2), each = 16)), + start = 0, + end = c( + 24, rep(Inf, 15), + 24, rep(Inf, 15) + ), + PPTESTCD = rep( + c( + "auclast", "cmax", "tmax", "tlast", "clast.obs", + "lambda.z", "r.squared", "adj.r.squared", "lambda.z.corrxy", + "lambda.z.time.first", "lambda.z.time.last", "lambda.z.n.points", + "clast.pred", "half.life", "span.ratio", "aucinf.obs" + ), + times = 2 + ), + PPORRES = c( + 13.54, 0.9998, 4.000, 24.00, 0.3441, + 0.04297, 0.9072, 0.9021, -0.9521, 5.000, 24.00, + 20.00, 0.3356, 16.13, 1.178, + 21.55, 14.03, 0.9410, 2.000, + 24.00, 0.3148, 0.05689, 0.9000, + 0.8944, -0.9487, 5.000, 24.00, 20.00, 0.3011, + 12.18, 1.560, 19.56 + ), + PPANMETH = c( + "AUC: lin up/log down", + rep("", 4), + rep("", 10), + "AUC: lin up/log down", + "AUC: lin up/log down", + rep("", 4), + rep("", 10), + "AUC: lin up/log down" + ), + exclude = NA_character_ ) expect_equal( o_result$result, verify.result, - tolerance=0.001, - info="The specific order of the levels isn't important-- the fact that they are factors and that the set doesn't change is important." + tolerance = 0.001, + info = "The specific order of the levels isn't important-- the fact that they are factors and that the set doesn't change is important." ) # Test conversion to a data.frame expect_equal( as.data.frame(o_result), verify.result, - tolerance=0.001, - info="Conversion of PKNCAresults to a data.frame in long format (default long format)" + tolerance = 0.001, + info = "Conversion of PKNCAresults to a data.frame in long format (default long format)" ) expect_equal( as.data.frame(o_result, out_format="long"), verify.result, - tolerance=0.001, - info="Conversion of PKNCAresults to a data.frame in long format (specifying long format)" + tolerance = 0.001, + info = "Conversion of PKNCAresults to a data.frame in long format (specifying long format)" ) expect_equal( as.data.frame(o_result, out_format="wide"), - tidyr::spread(verify.result, key="PPTESTCD", value="PPORRES"), + tidyr::spread(verify.result[names(verify.result) != "PPANMETH"], key="PPTESTCD", value="PPORRES"), tolerance=0.001, info="Conversion of PKNCAresults to a data.frame in wide format (specifying wide format)" ) @@ -103,7 +122,7 @@ test_that("PKNCAresults generation", { expect_equal( comparison_orig$PPORRES[comparison_orig$PPTESTCD %in% "aucint.inf.obs"], comparison_12$PPORRES[comparison_12$PPTESTCD %in% "aucint.inf.obs"], - info="Time shift does not affect aucint calculations." + info = "Time shift does not affect aucint calculations." ) }) @@ -144,7 +163,7 @@ test_that("PKNCAresults has exclude, when applicable", { ] ), "Too few points for half-life calculation (min.hl.points=3 with only 0 points)", - info="exclusions are propogated to results" + info = "exclusions are propogated to results" ) expect_equal( unique( @@ -154,10 +173,10 @@ test_that("PKNCAresults has exclude, when applicable", { c("lambda.z", "r.squared", "adj.r.squared", "lambda.z.corrxy", "lambda.z.time.first", "lambda.z.time.last", "lambda.z.n.points", "clast.pred", "half.life", "span.ratio") ) - ] + ] ), NA_character_, - info="exclusions are propogated to results only when applicable" + info = "exclusions are propogated to results only when applicable" ) }) @@ -173,7 +192,7 @@ test_that("ptr works as a parameter", { expect_equal( ptr_result$PPORRES[ptr_result$PPTESTCD %in% "ptr"], c(2.9055, 2.9885), - tolerance=0.0001 + tolerance = 0.0001 ) }) @@ -185,23 +204,23 @@ test_that("exclude values are maintained in derived parameters during automatic subject = 1 ) - conc_obj <- PKNCAconc(my_conc, conc~time|subject) + conc_obj <- PKNCAconc(my_conc, conc ~ time | subject) data_obj <- PKNCAdata( - data.conc=conc_obj, - intervals= + data.conc = conc_obj, + intervals = data.frame( - start=0, - end=Inf, - aucinf.obs=TRUE + start = 0, + end = Inf, + aucinf.obs = TRUE ) ) expect_message( expect_warning( results_obj <- pk.nca(data_obj), - regexp="Too few points for half-life" + regexp = "Too few points for half-life" ), - regexp="No dose information provided" + regexp = "No dose information provided" ) d_results <- as.data.frame(results_obj) expect_equal( @@ -211,16 +230,16 @@ test_that("exclude values are maintained in derived parameters during automatic }) test_that("ctrough is correctly calculated", { - my_conc <- data.frame(time=0:6, conc=2^(0:-6), subject=1) - conc_obj <- PKNCAconc(my_conc, conc~time|subject) + my_conc <- data.frame(time = 0:6, conc = 2^(0:-6), subject = 1) + conc_obj <- PKNCAconc(my_conc, conc ~ time | subject) data_obj <- PKNCAdata( - data.conc=conc_obj, - intervals= + data.conc = conc_obj, + intervals = data.frame( - start=0, - end=c(6, Inf), - ctrough=TRUE + start = 0, + end = c(6, Inf), + ctrough = TRUE ) ) expect_message( @@ -228,21 +247,21 @@ test_that("ctrough is correctly calculated", { as.data.frame(pk.nca(data_obj))$PPORRES, c(2^-6, NA_real_) ), - regexp="No dose information provided" + regexp = "No dose information provided" ) }) test_that("single subject, ungrouped data works (#74)", { - my_conc <- data.frame(time=0:6, conc=2^(0:-6)) - conc_obj <- PKNCAconc(my_conc, conc~time) + my_conc <- data.frame(time = 0:6, conc = 2^(0:-6)) + conc_obj <- PKNCAconc(my_conc, conc ~ time) data_obj <- PKNCAdata( - data.conc=conc_obj, - intervals= + data.conc = conc_obj, + intervals = data.frame( - start=0, - end=Inf, - cmax=TRUE + start = 0, + end = Inf, + cmax = TRUE ) ) expect_message( @@ -250,7 +269,7 @@ test_that("single subject, ungrouped data works (#74)", { as.data.frame(pk.nca(data_obj))$PPORRES, 1 ), - regexp="No dose information provided", + regexp = "No dose information provided", ) }) @@ -262,11 +281,11 @@ test_that("units work for calculations and summaries with one set of units acros o_data <- PKNCAdata(o_conc, o_dose) o_result <- pk.nca(o_data) - d_units_orig <- pknca_units_table(concu="ng/mL", doseu="mg", amountu="mg", timeu="hr") + d_units_orig <- pknca_units_table(concu = "ng/mL", doseu = "mg", amountu = "mg", timeu = "hr") d_units_std <- pknca_units_table( - concu="ng/mL", doseu="mg", amountu="mg", timeu="hr", - conversions=data.frame(PPORRESU="ng/mL", PPSTRESU="mg/mL") + concu = "ng/mL", doseu = "mg", amountu = "mg", timeu = "hr", + conversions = data.frame(PPORRESU = "ng/mL", PPSTRESU = "mg/mL") ) o_data_orig <- PKNCAdata(o_conc, o_dose, units=d_units_orig) o_result_units_orig <- pk.nca(o_data_orig) @@ -300,12 +319,12 @@ test_that("units work for calculations and summaries with one set of units acros as.data.frame(o_result, out_format="wide"), # The difference is the addition of units to the column names df_wide_orig %>% - dplyr::rename_with(.fn=gsub, pattern=" \\(.*$", replacement="") + dplyr::rename_with(.fn = gsub, pattern = " \\(.*$", replacement = "") ) expect_true( all( names(df_wide_orig) %in% c("treatment", "ID", "start", "end", "exclude") | - grepl(x=names(df_wide_orig), pattern=" (", fixed=TRUE) + grepl(x = names(df_wide_orig), pattern = " (", fixed = TRUE) ) ) # Everything is the same unless it is a concentration which has been converted @@ -316,7 +335,7 @@ test_that("units work for calculations and summaries with one set of units acros # Concentration conversion works correctly expect_equal( df_wide_orig$`cmax (ng/mL)`, - df_wide_std$`cmax (mg/mL)`*1e6 + df_wide_std$`cmax (mg/mL)` * 1e6 ) }) @@ -335,14 +354,14 @@ test_that("units work for calculations and summaries with one set of units acros d_units_std1 <- pknca_units_table( - concu="ng/mL", doseu="mg", amountu="mg", timeu="hr", - conversions=data.frame(PPORRESU="ng/mL", PPSTRESU="mg/mL") + concu = "ng/mL", doseu = "mg", amountu = "mg", timeu = "hr", + conversions = data.frame(PPORRESU = "ng/mL", PPSTRESU = "mg/mL") ) d_units_std1$analyte <- "drug1" d_units_std2 <- pknca_units_table( - concu="ng/mL", doseu="mg", amountu="mg", timeu="hr", - conversions=data.frame(PPORRESU="ng/mL", PPSTRESU="mmol/L", conversion_factor=2) + concu = "ng/mL", doseu = "mg", amountu = "mg", timeu = "hr", + conversions = data.frame(PPORRESU = "ng/mL", PPSTRESU = "mmol/L", conversion_factor = 2) ) d_units_std2$analyte <- "drug2" d_units_std <- rbind(d_units_std1, d_units_std2) @@ -407,14 +426,14 @@ test_that("getGroups.PKNCAresults", { }) test_that("group_vars.PKNCAresult", { - o_conc_group <- PKNCAconc(as.data.frame(datasets::Theoph), conc~Time|Subject) + o_conc_group <- PKNCAconc(as.data.frame(datasets::Theoph), conc ~ Time | Subject) o_data_group <- PKNCAdata(o_conc_group, intervals = data.frame(start = 0, end = 1, cmax = TRUE)) suppressMessages(o_nca_group <- pk.nca(o_data_group)) expect_equal(dplyr::group_vars(o_nca_group), "Subject") # Check that it works without groupings as expected [empty] - o_conc_nongroup <- PKNCAconc(as.data.frame(datasets::Theoph)[datasets::Theoph$Subject == 1,], conc~Time) + o_conc_nongroup <- PKNCAconc(as.data.frame(datasets::Theoph)[datasets::Theoph$Subject == 1, ], conc ~ Time) o_data_nogroup <- PKNCAdata(o_conc_nongroup, intervals = data.frame(start = 0, end = 1, cmax = TRUE)) suppressMessages(o_nca_nogroup <- pk.nca(o_data_nogroup)) diff --git a/tests/testthat/test-half.life.R b/tests/testthat/test-half.life.R index 202fa421..04974388 100644 --- a/tests/testthat/test-half.life.R +++ b/tests/testthat/test-half.life.R @@ -78,6 +78,7 @@ test_that("pk.calc.half.life", { clast.pred=0.12507, half.life=1.000346, span.ratio=2.998962), + ignore_attr=TRUE, tolerance=0.0001) # It only gives tlast or tmax if you don't give them as inputs. expect_equal(pk.calc.half.life(conc=c(1, 0.5, 0.25, 0.1251), @@ -98,6 +99,7 @@ test_that("pk.calc.half.life", { half.life=1.000346, span.ratio=2.998962, tmax=0), + ignore_attr=TRUE, tolerance=0.0001) expect_equal(pk.calc.half.life(conc=c(1, 0.5, 0.25, 0.1251), time=c(0, 1, 2, 3), @@ -117,6 +119,7 @@ test_that("pk.calc.half.life", { half.life=1.000346, span.ratio=2.998962, tlast=3), + ignore_attr=TRUE, tolerance=0.0001) }) @@ -194,6 +197,7 @@ test_that("half-life manual point selection", { tlast = 3L ) attr(excluded_result, "exclude") <- "Negative half-life estimated with manually-selected points" + attr(excluded_result, "method") <- "Lambda Z: Manual selection" expect_equal( pk.calc.half.life(conc = 2^(1:3), time = 1:3, manually.selected.points = TRUE), excluded_result @@ -223,7 +227,8 @@ test_that("two-point half-life succeeds (fix #114)", { span.ratio=1, tmax=0, tlast=1 - ) + ), + ignore_attr=TRUE ), class = "pknca_halflife_2points"), class = "pknca_adjr2_2points" diff --git a/tests/testthat/test-pk.calc.all.R b/tests/testthat/test-pk.calc.all.R index 084e5843..fecfb9c1 100644 --- a/tests/testthat/test-pk.calc.all.R +++ b/tests/testthat/test-pk.calc.all.R @@ -63,6 +63,16 @@ test_that("pk.nca", { 24.00, 0.3148, 0.05689, 0.9000, 0.8944, -0.952, 5.000, 24.00, 20.00, 0.3011, 12.18, 1.560, 19.56), + PPANMETH = c( + "AUC: lin up/log down", + rep("", 4), + rep("", 10), + "AUC: lin up/log down", + "AUC: lin up/log down", + rep("", 4), + rep("", 10), + "AUC: lin up/log down" + ), exclude=NA_character_ ) expect_equal( @@ -244,6 +254,12 @@ test_that("Calculations when no dose info is given", { PPTESTCD=rep(c("auclast", "cmax", "cl.last"), 2), PPORRES=c(13.5417297156528, 0.999812606062292, NA, 14.0305397438242, 0.94097296083447, NA), + PPANMETH = c( + "AUC: lin up/log down", + rep("", 2), + "AUC: lin up/log down", + rep("", 2) + ), exclude=NA_character_ ) ) @@ -757,6 +773,93 @@ test_that("do not give rbind error when interval columns have attributes (#381)" ) }) +test_that("pk.nca produces the PPANMETH column", { + # --- Setup shared concentration and dose data --- + tmpconc <- generate.conc(1, 1, 0:24) + tmpdose <- generate.dose(tmpconc) + myconc <- PKNCAconc(tmpconc, formula=conc~time|treatment+ID) + mydose <- PKNCAdose(tmpdose, formula=dose~time|treatment+ID) + + # --- PPANMETH differentiates based on the AUC method used --- + mydata_linear <- PKNCAdata(myconc, mydose, intervals=data.frame(start=0, end=24, auclast=TRUE), options=list(auc.method="linear")) + mydata_linlog <- PKNCAdata(myconc, mydose, intervals=data.frame(start=0, end=24, auclast=TRUE), options=list(auc.method="lin up/log down")) + res_linear <- pk.nca(mydata_linear) + res_linlog <- pk.nca(mydata_linlog) + expect_true("PPANMETH" %in% names(res_linear$result)) + expect_true("PPANMETH" %in% names(res_linlog$result)) + expect_true(any(grepl("AUC: linear", res_linear$result$PPANMETH, fixed=TRUE))) + expect_true(any(grepl("AUC: lin up/log down", res_linlog$result$PPANMETH, fixed=TRUE))) + + # --- PPANMETH distinguishes how the half.life was adjusted --- + tmpconc$include_hl <- tmpconc$time <= 22 + tmpconc$exclude_hl <- tmpconc$time == 22 + myconc_base <- PKNCAconc(tmpconc, formula=conc~time|treatment+ID) + myconc_incl <- PKNCAconc(tmpconc, formula=conc~time|treatment+ID, include_half.life="include_hl") + myconc_excl <- PKNCAconc(tmpconc, formula=conc~time|treatment+ID, exclude_half.life="exclude_hl") + mydata_base <- PKNCAdata(myconc_base, mydose, intervals=data.frame(start=0, end=24, lambda.z=TRUE)) + mydata_incl <- PKNCAdata(myconc_incl, mydose, intervals=data.frame(start=0, end=24, lambda.z=TRUE)) + mydata_excl <- PKNCAdata(myconc_excl, mydose, intervals=data.frame(start=0, end=24, lambda.z=TRUE)) + res_base <- pk.nca(mydata_base) + res_incl <- pk.nca(mydata_incl) + res_excl <- pk.nca(mydata_excl) + expect_true("PPANMETH" %in% names(res_base$result)) + expect_true("PPANMETH" %in% names(res_incl$result)) + expect_true("PPANMETH" %in% names(res_excl$result)) + expect_equal( + unique(res_base$result$PPANMETH[res_base$result$PPTESTCD %in% c("lambda.z", "half.life", "r.squared")]), + "" + ) + expect_equal( + unique(res_incl$result$PPANMETH[res_incl$result$PPTESTCD %in% c("lambda.z", "half.life", "r.squared")]), + "Lambda Z: Manual selection" + ) + expect_equal( + unique(res_excl$result$PPANMETH[res_excl$result$PPTESTCD %in% c("lambda.z", "half.life", "r.squared")]), + "" + ) + expect_equal( + unique(res_base$result$PPANMETH[res_base$result$PPTESTCD %in% c("tmax", "cmax")]), + "" + ) + expect_equal( + unique(res_incl$result$PPANMETH[res_incl$result$PPTESTCD %in% c("tmax", "cmax")]), + "" + ) + expect_equal( + unique(res_excl$result$PPANMETH[res_excl$result$PPTESTCD %in% c("tmax", "cmax")]), + "" + ) + + # --- PPANMETH specifies if an imputation method was used in the interval --- + o_data <- PKNCAdata(myconc, mydose, intervals=data.frame(start=0, end=24, c0=TRUE)) + o_data_impute <- PKNCAdata(myconc, mydose, intervals=data.frame(start=0, end=24, c0=TRUE), impute="start_conc0") + res <- pk.nca(o_data) + res_impute <- pk.nca(o_data_impute) + expect_equal(res$result$PPANMETH, "") + expect_true("PPANMETH" %in% names(res$result)) + expect_equal(res$result$PPANMETH, "") + expect_equal(res_impute$result$PPANMETH, "Imputation: start_conc0") + + # --- PPANMETH reports based on the parameter dependencies --- + mydata <- PKNCAdata( + myconc_incl, mydose, + intervals=data.frame(start=0, end=24, c0 = TRUE, half.life = TRUE, aucinf.pred=TRUE), + impute = "start_conc0" + ) + res <- pk.nca(mydata) + expect_equal( + res$result$PPANMETH[res$result$PPTESTCD == "c0"], + "Imputation: start_conc0" + ) + expect_equal( + res$result$PPANMETH[res$result$PPTESTCD == "half.life"], + "Imputation: start_conc0. Lambda Z: Manual selection" + ) + expect_equal( + res$result$PPANMETH[res$result$PPTESTCD == "aucinf.pred"], + "Imputation: start_conc0. AUC: lin up/log down" + ) +}) test_that("Cannot include and exclude half-life points at the same time (#406)", { o_conc <- PKNCAconc(data = data.frame(conc = 1, time = 0, inex = TRUE), conc~time, include_half.life = "inex", exclude_half.life = "inex") d_interval <- data.frame(start = 0, end = Inf, half.life = TRUE) diff --git a/tests/testthat/test-pk.calc.simple.R b/tests/testthat/test-pk.calc.simple.R index 78eadd79..c2784f84 100644 --- a/tests/testthat/test-pk.calc.simple.R +++ b/tests/testthat/test-pk.calc.simple.R @@ -453,6 +453,7 @@ test_that("pk.calc.aucabove", { pk.calc.aucabove(conc = d_conc$conc, time = d_conc$time, conc_above = 2), pk.calc.aucabove(conc = d_conc$conc, time = d_conc$time, conc_above = 3) ), + PPANMETH = c("", "", "AUC: lin up/log down", "AUC: lin up/log down"), exclude = NA_character_ ) ) diff --git a/tests/testthat/test-sparse.R b/tests/testthat/test-sparse.R index 31f49b18..fd828a80 100644 --- a/tests/testthat/test-sparse.R +++ b/tests/testthat/test-sparse.R @@ -20,14 +20,14 @@ test_that("sparse_auc", { sparse_batch <- pk.calc.sparse_auc(conc=d_sparse$conc, time=d_sparse$time, subject=d_sparse$id), regexp="Cannot yet calculate sparse degrees of freedom for multiple samples per subject" ) - expect_equal(sparse_batch$sparse_auc, auclast) - expect_equal(sparse_batch$sparse_auc_se, auclast_se_batch) - expect_equal(sparse_batch$sparse_auc_df, NA_real_) + expect_equal(sparse_batch$sparse_auc, auclast, ignore_attr = TRUE) + expect_equal(sparse_batch$sparse_auc_se, auclast_se_batch, ignore_attr = TRUE) + expect_equal(sparse_batch$sparse_auc_df, NA_real_, ignore_attr = TRUE) sparse_serial <- pk.calc.sparse_auc(conc=d_sparse$conc, time=d_sparse$time, subject=seq_len(nrow(d_sparse))) - expect_equal(sparse_serial$sparse_auc, auclast) + expect_equal(sparse_serial$sparse_auc, auclast, ignore_attr = TRUE) expect_equal(as.numeric(sparse_serial$sparse_auc_se), auclast_se_serial) - expect_equal(sparse_serial$sparse_auc_df, auclast_df_serial) + expect_equal(sparse_serial$sparse_auc_df, auclast_df_serial, ignore_attr = TRUE) }) test_that("sparse_auclast expected errors", { @@ -61,3 +61,20 @@ test_that("sparse_mean", { "arithmetic mean" ) }) + +test_that("sparse_auc and sparse_auclast method attribute", { + d_sparse <- + data.frame( + id = c(1L, 2L, 3L, 1L, 2L, 3L, 1L, 2L, 3L, 4L, 5L, 6L, 4L, 5L, 6L, 7L, 8L, 9L, 7L, 8L, 9L), + conc = c(0, 0, 0, 1.75, 2.2, 1.58, 4.63, 2.99, 1.52, 3.03, 1.98, 2.22, 3.34, 1.3, 1.22, 3.54, 2.84, 2.55, 0.3, 0.0421, 0.231), + time = c(0, 0, 0, 1, 1, 1, 6, 6, 6, 2, 2, 2, 10, 10, 10, 4, 4, 4, 24, 24, 24), + dose = c(100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100) + ) + auc <- pk.calc.sparse_auc(conc=d_sparse$conc, time=d_sparse$time, subject=seq_len(nrow(d_sparse))) + expect_equal(attr(auc$sparse_auc, "method"), + c("AUC: linear", "Sparse: arithmetic mean, <=50% BLQ")) + + auclast <- pk.calc.sparse_auclast(conc=d_sparse$conc, time=d_sparse$time, subject=seq_len(nrow(d_sparse))) + expect_equal(attr(auclast$sparse_auclast, "method"), + c("AUC: linear", "Sparse: arithmetic mean, <=50% BLQ")) +})