tests/testthat/test-superpostion.R

# The requirements for superposition are defined below to enable
# test-driven development.

#' Dosing must be single dose
#' * A single dose within the time period
#'   * Time period may be a subset of the data from a multi-dose study
#' * Concentrations must be BLQ at the beginning of the interval
#'   * User can over-ride the requirement of BLQ at the beginning of the interval
#' * Half-life
#'   * If the number of dosing intervals times the duration of the dosing interval is less than the observed data, half-life is not required.  (This will likely be rare.)
#'   * Half-life can be specified on input.
#'   * Half-life can be sufficiently determined from the data given (see the half-life cleaning functions for the definition of "sufficient")
#'   * Mixing methods of half-life determination are supported.
#' * Extrapolation can occur via either Clast,obs or Clast,pred.
#'   * If using Clast,pred, it must be given if the half-life is given.
#' * Dosing interval can be specified as either a simple number for dosing interval (tau) or multiple doses within the interval
#'   * Example: Daily dosing would be: 24
#'   * Example: Breakfast/dinner dosing would be: c(10, 24)
#' * How many times to repeat the dosing interval can either be given as a number of taus to repeat or "steady-state"
#'   * If steady-state, then a relative tolerance will be provided to confirm that no concentration changes by a greater ratio from the previous to the current dose.
#' * Doses amount may be provided
#'   * If doses are not given, they are assumed to be the same as the input dose.
#'   * If doses are given, linear dose-proportionality will be assumed.
#'   * Dose amount can be given either as a scalar which will apply to all intervals or a vector which will apply to each interval.
#'     * If dose amount is given as a vector, it must be the same length as the number of intervals input.
#' * User can specify either integration as either linear or linear-up/log-down
#' * Time points in the output are automatically selected as all observed points modulo the dosing interval.
#'   * More time points can be added by user specification, but points cannot be removed.  (Note that this will ensure the highest possible Cmax in the output data.)
#' * The functions work with either individual or grouped data.

test_that("superposition inputs", {
  # FIXME: Enforce single dose

  # Enforce BLQ checking for the first data point
  expect_error(superposition(conc=c(1, 2), time=c(0, 1), tau=24),
               regexp="The first concentration must be 0")
  expect_error(superposition(conc=c(NA, 2), time=c(0, 1), tau=24),
               regexp="The first concentration must be 0")

  # dose.input must be scalar
  expect_error(
    superposition(conc=c(0, 2), time=c(0, 1), dose.input=c(0, 1)),
    regexp="Assertion on 'dose.input' failed: Must have length 1."
  )
  # Ensure that dose.input must be a number
  expect_error(
    superposition(conc=c(0, 2), time=c(0, 1), dose.input=NA),
    regexp = "Assertion on 'dose.input' failed: Contains missing values (element 1).",
    fixed = TRUE
  )
  expect_error(
    superposition(conc=c(0, 2), time=c(0, 1), dose.input=factor("1")),
    regexp="Assertion on 'dose.input' failed: Must be of type 'numeric' (or 'NULL'), not 'factor'.",
    fixed = TRUE
  )
  # dose.input must be > 0
  expect_error(
    superposition(conc=c(0, 2), time=c(0, 1), dose.input=-1),
    regexp="Assertion on 'dose.input' failed: Element 1 is not > 0"
  )
  expect_error(
    superposition(conc=c(0, 2), time=c(0, 1), dose.input=0),
    regexp="Assertion on 'dose.input' failed: Element 1 is not > 0"
  )

  # tau must be scalar
  expect_error(
    superposition(conc=c(0, 2), time=c(0, 1), tau=c(0, 1)),
    regexp="Assertion on 'tau' failed: Must have length 1, but has length 2."
  )
  # Ensure that tau must be a number
  expect_error(
    superposition(conc=c(0, 2), time=c(0, 1), tau=NA),
    regexp="Assertion on 'tau' failed: Contains missing values (element 1).",
    fixed = TRUE
  )
  expect_error(
    superposition(conc=c(0, 2), time=c(0, 1), tau=factor("1")),
    regexp="Assertion on 'tau' failed: Must be of type 'numeric', not 'factor'."
  )
  expect_error(
    superposition(conc=c(0, 2), time=c(0, 1), tau="1"),
    regexp="Assertion on 'tau' failed: Must be of type 'numeric', not 'character'."
  )
  # tau must be > 0
  expect_error(
    superposition(conc=c(0, 2), time=c(0, 1), tau=-1),
    regexp="Assertion on 'tau' failed: Element 1 is not > 0"
  )
  expect_error(
    superposition(conc=c(0, 2), time=c(0, 1), tau=0),
    regexp="Assertion on 'tau' failed: Element 1 is not > 0"
  )

  # >= 1 dose time
  expect_error(
    superposition(conc=c(0, 2), time=c(0, 1), tau=24, dose.times=numeric()),
    regexp="Assertion on 'dose.times' failed: Must have length >= 1, but has length 0."
  )
  # Dose times must be a number
  expect_error(
    superposition(conc=c(0, 2), time=c(0, 1), tau=24, dose.times="1"),
    regexp = "Assertion on 'dose.times' failed: Must be of type 'numeric', not 'character'."
  )
  expect_error(
    superposition(conc=c(0, 2), time=c(0, 1), tau=24, dose.times=factor("1")),
    regexp="Assertion on 'dose.times' failed: Must be of type 'numeric', not 'factor'."
  )
  expect_error(
    superposition(conc=c(0, 2), time=c(0, 1), tau=24, dose.times=NA),
    regexp = "Assertion on 'dose.times' failed: Contains missing values (element 1).",
    fixed = TRUE
  )
  # Dose times nonnegative
  expect_error(
    superposition(conc=c(0, 2), time=c(0, 1), tau=24, dose.times=c(-1, 0)),
    regexp="Assertion on 'dose.times' failed: Element 1 is not >= 0."
  )
  # Dose times <= tau
  expect_error(
    superposition(conc=c(0, 2), time=c(0, 1), tau=24, dose.times=c(0, 25)),
    regexp = "Assertion on 'dose.times' failed: Element 2 is not < 24"
  )

  # dose.amount without dose.input
  expect_error(
    superposition(conc=c(0, 2), time=c(0, 1), tau=24, dose.times=0, dose.amount=1),
    regexp="must give dose.input to give dose.amount"
  )
  # dose.amount same length as dose.times
  expect_error(superposition(conc=c(0, 2), time=c(0, 1), dose.input=1,
                             tau=24, dose.times=0, dose.amount=c(1, 2)),
               regexp="dose.amount must either be a scalar or match the length of dose.times")
  expect_error(superposition(conc=c(0, 2), time=c(0, 1), dose.input=1,
                             tau=24, dose.times=c(0, 1), dose.amount=c(1, 2, 3)),
               regexp="dose.amount must either be a scalar or match the length of dose.times")
  # dose.amount numeric
  expect_error(
    superposition(conc=c(0, 2), time=c(0, 1), dose.input=1, tau=24, dose.times=0, dose.amount="1"),
    regexp = "Assertion on 'dose.amount' failed: Must be of type 'numeric', not 'character'."
  )
  expect_error(
    superposition(conc=c(0, 2), time=c(0, 1), dose.input=1, tau=24, dose.times=0, dose.amount=factor("1")),
    regexp = "Assertion on 'dose.amount' failed: Must be of type 'numeric', not 'factor'."
  )
  # dose.amount finite/NA
  expect_error(
    superposition(conc=c(0, 2), time=c(0, 1), dose.input=1, tau=24, dose.times=0, dose.amount=NA_real_),
    regexp = "Assertion on 'dose.amount' failed: Contains missing values (element 1).",
    fixed = TRUE
  )
  expect_error(
    superposition(conc=c(0, 2), time=c(0, 1), dose.input=1, tau=24, dose.times=0, dose.amount=Inf),
    regexp = "Assertion on 'dose.amount' failed: Must be finite."
  )
  # dose.amount non-negative
  expect_error(
    superposition(conc=c(0, 2), time=c(0, 1), dose.input=1, tau=24, dose.times=0, dose.amount=-1),
    regexp = "Assertion on 'dose.amount' failed: Element 1 is not > 0"
  )
  expect_error(
    superposition(conc=c(0, 2), time=c(0, 1), dose.input=1, tau=24, dose.times=0, dose.amount=0),
    regexp="Assertion on 'dose.amount' failed: Element 1 is not > 0"
  )

  expect_error(
    superposition(conc=c(0, 2), time=c(0, 1), tau=24, n.tau=c(1, 2)),
    regexp="Assertion on 'n.tau' failed: Must have length 1."
  )
  expect_error(
    superposition(conc=c(0, 2), time=c(0, 1), tau=24, n.tau="1"),
    regexp = "Assertion on 'n.tau' failed: Must be of type 'number', not 'character'."
  )
  expect_error(
    superposition(conc=c(0, 2), time=c(0, 1), tau=24, n.tau=NA),
    regexp="Assertion on 'n.tau' failed: May not be NA."
  )
  # n.tau >= 1
  expect_error(
    superposition(conc=c(0, 2), time=c(0, 1), tau=24, n.tau=0),
    regexp="Assertion on 'n.tau' failed: Element 1 is not >= 1."
  )
  expect_error(
    superposition(conc=c(0, 2), time=c(0, 1), tau=24, n.tau=-Inf),
    regexp = "Assertion on 'n.tau' failed: Element 1 is not >= 1."
  )
  # n.tau integer
  expect_error(
    superposition(conc=c(0, 2), time=c(0, 1), tau=24, n.tau=1.1),
    regexp = "Assertion on 'n.tau' failed: Must be of type 'integerish', but element 1 is not close to an integer."
  )
  expect_error(
    superposition(conc=c(0, 2), time=c(0, 1), tau=24, n.tau=1.0000001),
    regexp = "Assertion on 'n.tau' failed: Must be of type 'integerish', but element 1 is not close to an integer."
  )

  expect_error(
    superposition(conc=c(0, 2), time=c(0, 1), tau=24, n.tau=Inf, lambda.z=c(1, 2)),
    regexp="Assertion on 'lambda.z' failed: Must have length 1, but has length 2."
  )

  expect_error(
    superposition(conc=c(0, 2), time=c(0, 1), tau=24, lambda.z="1"),
    regexp="Assertion on 'lambda.z' failed: Must be of type 'numeric', not 'character'."
  )

  expect_equal(
    superposition(
      conc=c(4, 2, 1, 0.5),
      time=0:3,
      tau=24,
      n.tau=Inf,
      check.blq=FALSE
    ),
    superposition(
      conc=c(4, 2, 1, 0.5),
      time=0:3,
      tau=24,
      clast.pred=TRUE,
      n.tau=Inf,
      check.blq=FALSE
    ),
    info="clast.pred may be provided as 'TRUE'"
  )

  expect_equal(
    superposition(
      conc=c(4, 2, 1, 0.5),
      time=0:3,
      tau=24,
      clast.pred=NA,
      n.tau=Inf,
      check.blq=FALSE
    ),
    superposition(
      conc=c(4, 2, 1, 0.5),
      time=0:3,
      tau=24,
      clast.pred=FALSE,
      n.tau=Inf,
      check.blq=FALSE
    ),
    info="clast.pred NA is the same as FALSE"
  )

  expect_error(
    superposition(conc=c(0, 2), time=c(0, 1), tau=24, clast.pred=c(1, 2)),
    regexp = "Assertion on 'clast.pred' failed: One of the following must apply:.*Must have length 1"
  )
  expect_error(
    superposition(conc=c(0, 2), time=c(0, 1), tau=24, clast.pred=-1),
    regexp = "Assertion on 'clast.pred' failed: One of the following must apply:.*Element 1 is not >= 0"
  )

  expect_error(
    superposition(conc=c(0, 2), time=c(0, 1), tau=24, clast.pred="1"),
    regexp="Must be of type"
  )
  expect_error(
    superposition(conc=c(0, 2), time=c(0, 1), tau=24, clast.pred=factor("1")),
    regexp="Must be of type"
  )

  # tlast given and not a scalar
  expect_error(
    superposition(conc=c(0, 2), time=c(0, 1), tau=24, tlast=c(1, 2)),
    regexp="Assertion on 'tlast' failed: Must have length 1."
  )
  # tlast given and not a number
  expect_error(
    superposition(conc=c(0, 2), time=c(0, 1), tau=24, tlast="1"),
    regexp="Assertion on 'tlast' failed: Must be of type 'number', not 'character'."
  )
  expect_error(
    superposition(conc=c(0, 2), time=c(0, 1), tau=24, tlast=NA),
    regexp="Assertion on 'tlast' failed: May not be NA."
  )
  # TODO: test mixing clast.pred and lambda.z

  # additional.times NA
  expect_error(superposition(conc=c(0, 2), time=c(0, 1), tau=24,
                             additional.times=NA),
               regexp="No additional.times may be NA \\(to not include any additional.times, enter c\\(\\) as the function argument\\)")
  expect_error(superposition(conc=c(0, 2), time=c(0, 1), tau=24,
                             additional.times=c(2, NA)),
               regexp="No additional.times may be NA \\(to not include any additional.times, enter c\\(\\) as the function argument\\)")
  # additional.times nonnumeric
  expect_error(superposition(conc=c(0, 2), time=c(0, 1), tau=24,
                             additional.times="1"),
               regexp="additional.times must be a number")
  expect_error(superposition(conc=c(0, 2), time=c(0, 1), tau=24,
                             additional.times=factor("1")),
               regexp="additional.times must be a number")
  # additional times < 0
  expect_error(superposition(conc=c(0, 2), time=c(0, 1), tau=24,
                             additional.times=-1),
               regexp="All additional.times must be nonnegative")
  expect_error(superposition(conc=c(0, 2), time=c(0, 1), tau=24,
                             additional.times=c(-1, 0)),
               regexp="All additional.times must be nonnegative")
  # Additional times > tau
  expect_error(superposition(conc=c(0, 2), time=c(0, 1), tau=24,
                             additional.times=25),
               regexp="All additional.times must be <= tau")
  expect_error(superposition(conc=c(0, 2), time=c(0, 1), tau=24,
                             additional.times=c(0, 25)),
               regexp="All additional.times must be <= tau")

  # steady.state.tol scalar
  expect_error(superposition(conc=c(0, 2), time=c(0, 1), tau=24,
                             steady.state.tol=c(1, 2)),
               regexp="steady.state.tol must be a scalar")
  # steady.state.tol numeric
  expect_error(superposition(conc=c(0, 2), time=c(0, 1), tau=24,
                             steady.state.tol="1"),
               regexp="steady.state.tol must be a number")
  expect_error(superposition(conc=c(0, 2), time=c(0, 1), tau=24,
                             steady.state.tol="1"),
               regexp="steady.state.tol must be a number")
  expect_error(superposition(conc=c(0, 2), time=c(0, 1), tau=24,
                             steady.state.tol=factor("1")),
               regexp="steady.state.tol must be a number")
  expect_error(superposition(conc=c(0, 2), time=c(0, 1), tau=24,
                             steady.state.tol=NA),
               regexp="steady.state.tol must be a number")
  # steady.state.tol range
  expect_error(superposition(conc=c(0, 2), time=c(0, 1), tau=24,
                             steady.state.tol=0),
               regexp="steady.state.tol must be between 0 and 1, exclusive.")
  expect_error(superposition(conc=c(0, 2), time=c(0, 1), tau=24,
                             steady.state.tol=1),
               regexp="steady.state.tol must be between 0 and 1, exclusive.")
  expect_error(superposition(conc=c(0, 2), time=c(0, 1), tau=24,
                             steady.state.tol=-1),
               regexp="steady.state.tol must be between 0 and 1, exclusive.")
  expect_error(superposition(conc=c(0, 2), time=c(0, 1), tau=24,
                             steady.state.tol=2),
               regexp="steady.state.tol must be between 0 and 1, exclusive.")
  suppressWarnings(
    expect_warning(
      superposition(conc=c(0, 2), time=c(0, 1), tau=24, steady.state.tol=0.1),
      regexp="steady.state.tol is usually <= 0.01",
      fixed=TRUE
    )
  )

  # Combinations of lambda.z, clast.pred, tlast
  expect_error(superposition(conc=c(0, 2), time=c(0, 1), tau=24,
                             clast.pred=1),
               regexp="Either give all or none of the values for these arguments: lambda.z, clast.pred, and tlast")
  expect_error(superposition(conc=c(0, 2), time=c(0, 1), tau=24,
                             lambda.z=1),
               regexp="Either give all or none of the values for these arguments: lambda.z, clast.pred, and tlast")
  expect_error(superposition(conc=c(0, 2), time=c(0, 1), tau=24,
                             tlast=1),
               regexp="Either give all or none of the values for these arguments: lambda.z, clast.pred, and tlast")
  expect_error(superposition(conc=c(0, 2), time=c(0, 1), tau=24,
                             clast.pred=1, lambda.z=1),
               regexp="Either give all or none of the values for these arguments: lambda.z, clast.pred, and tlast")
  expect_error(superposition(conc=c(0, 2), time=c(0, 1), tau=24,
                             clast.pred=1, tlast=1),
               regexp="Either give all or none of the values for these arguments: lambda.z, clast.pred, and tlast")
  expect_error(superposition(conc=c(0, 2), time=c(0, 1), tau=24,
                             lambda.z=1),
               regexp="Either give all or none of the values for these arguments: lambda.z, clast.pred, and tlast")
  expect_error(superposition(conc=c(0, 2), time=c(0, 1), tau=24,
                             lambda.z=1, tlast=1),
               regexp="Either give all or none of the values for these arguments: lambda.z, clast.pred, and tlast")

})

test_that("superposition math", {
  # Superposition without half-life needed and no
  # interpolation/extrapolation
  c1 <- c(0, 1, 2, 3, 2, 1, 0.5)
  t1 <- 0:6
  expect_equal(superposition(conc=c1, time=t1, tau=3, n.tau=2),
               data.frame(conc=c(3, 3, 3, 3.5),
                          time=0:3))
  # With half-life extrapolation
  v1 <- superposition(conc=c1, time=t1, tau=3, n.tau=3)
  expect_equal(v1,
               data.frame(conc=c(3.5, 3.25, 3.125, 3.5625),
                          time=0:3))
  # To steady-state
  v2 <- superposition(conc=c1, time=t1, tau=3, n.tau=Inf)
  expect_equal(v2,
               data.frame(conc=c(3.571, 3.286, 3.143, 3.571),
                          time=0:3),
               tolerance=0.001)

  # all zeros input gives all zeros output
  expect_equal(superposition(conc=rep(0, 6), time=0:5, tau=24),
               data.frame(conc=0, time=c(0:5, 24)))

  # Times for output are a collation of 0, tau, dose.times, and
  # additional.times with time included and shifted for all the
  # dose.times.
  expect_equal(superposition(conc=rep(0, 6), time=0:5, tau=24,
                             additional.times=2.5),
               data.frame(conc=0, time=c(0, 1, 2, 2.5, 3, 4, 5, 24)))
  expect_equal(superposition(conc=rep(0, 6), time=0:5, tau=25,
                             additional.times=2.5),
               data.frame(conc=0, time=c(0, 1, 2, 2.5, 3, 4, 5, 25)))
  expect_equal(superposition(conc=rep(0, 6), time=0:5, tau=24,
                             additional.times=c(2.5, 3.5)),
               data.frame(conc=0, time=c(0, 1, 2, 2.5, 3, 3.5, 4, 5, 24)))
  expect_equal(superposition(conc=rep(0, 6), time=0:5, tau=24, dose.times=c(0, 1),
                             additional.times=c(2.5, 3.5)),
               data.frame(conc=0, time=c(0, 1, 2, 2.5, 3, 3.5, 4, 5, 6, 24)))
  expect_equal(superposition(conc=rep(0, 6), time=0:5, tau=24,
                             dose.times=c(0, 0.5),
                             additional.times=c(2.5, 3.5)),
               data.frame(conc=0,
                          time=c(0, 0.5, 1, 1.5, 2, 2.5, 3, 3.5, 4, 4.5,
                            5, 5.5, 24)))

  # Dose scaling
  v1 <-
    superposition(
      conc=c1, time=t1, dose.input=1, tau=24,
      dose.times=c(0, 0.5),
      dose.amount=1,
      additional.times=c(2.5, 3.5)
    )
  expect_equal(v1,
               data.frame(conc=c(
                            4.6047e-06,
                            0.5,
                            1.5,
                            2.5,
                            3.5,
                            4.5,
                            5.5,
                            5.4495,
                            4.4495,
                            3.4142,
                            2.4142,
                            1.7071,
                            1.2071,
                            0.85355,
                            4.6047e-06),
                          time=c(0, 0.5, 1, 1.5, 2, 2.5, 3, 3.5, 4, 4.5,
                            5, 5.5, 6, 6.5, 24)),
               tolerance=0.001,
               info="Dose scaling with matching input and output doses")

  v2 <-
    superposition(
      conc=c1, time=t1, dose.input=1, tau=24,
      dose.times=c(0, 0.5),
      dose.amount=c(0.5, 5),
      additional.times=c(2.5, 3.5)
    )
  expect_equal(v2,
               data.frame(conc=c(
                            1.444e-05,
                            0.25,
                            3,
                            5.75,
                            8.5,
                            11.25,
                            14,
                            16.22,
                            13.25,
                            10.71,
                            7.571,
                            5.354,
                            3.786,
                            2.677,
                            1.444e-05),
                          time=c(0, 0.5, 1, 1.5, 2, 2.5, 3, 3.5, 4, 4.5,
                            5, 5.5, 6, 6.5, 24)),
               tolerance=0.001,
               info="Dose scaling with different input and output doses")

  expect_warning(v3 <- superposition(conc=c(0, 2, 3, 5, 6, 3, 1, 0),
                                     time=c(0, 0.5, 1, 1.5, 2, 8, 12, 24),
                                     tau=24),
                 regexp="Too few points for half-life calculation \\(min.hl.points=3 with only 2 points\\)")
  expect_equal(v3,
               data.frame(conc=NA, time=c(0, 0.5, 1, 1.5, 2, 8, 12, 24)),
               info="Uncalculable lambda.z with extrapolation to steady-state gives NA conc")

  expect_equal(
    superposition(
      conc=c(0, 2, 3, 5, 6, 3, 1, 0),
      time=c(0, 0.5, 1, 1.5, 2, 8, 12, 24),
      tau=24,
      lambda.z=1, clast.pred=1, tlast=12
    ),
    data.frame(
      conc=c(6.144e-6, 2, 3, 5, 6, 3, 1, 6.144e-6),
      time=c(0, 0.5, 1, 1.5, 2, 8, 12, 24)
    ),
    tolerance=0.001,
    info="Uncalculable lambda.z with extrapolation to steady-state with lambda.z given, gives conc values"
  )
})

test_that("PKNCAconc superposition", {
  myconc <- PKNCAconc(conc~time|ID,
                      data=data.frame(ID=rep(1:2, each=7),
                                      conc=rep(c(0, 1, 2, 3, 2, 1, 0.5), 2),
                                      time=rep(0:6, 2)))
  expect_equal(
    superposition(myconc, tau=3, n.tau=2),
    tibble::tibble(
      ID=rep(1:2, each=4),
      conc=rep(c(3, 3, 3, 3.5), 2),
      time=rep(0:3, 2)
    )
  )
})
billdenney/pknca documentation built on April 1, 2024, 10:45 p.m.