#' Check the formatting of a calculation interval specification data frame.
#'
#' Calculation interval specifications are data frames defining what
#' calculations will be required and summarized from all time intervals. Note:
#' parameters which are not requested may be calculated if it is required for
#' (or computed at the same time as) a requested parameter.
#'
#' `start` and `end` time must always be given as columns, and the `start` must
#' be before the `end`. Other columns define the parameters to be calculated
#' and the groupings to apply the intervals to.
#'
#' @param x The data frame specifying what to calculate during each time
#' interval
#' @returns x The potentially updated data frame with the interval calculation
#' specification.
#'
#' @family Interval specifications
#' @seealso The vignette "Selection of Calculation Intervals"
#' @export
check.interval.specification <- function(x) {
if (!is.data.frame(x)) {
# Just a warning and let as.data.frame make it an error if it can't be
# coerced.
warning("Interval specification must be a data.frame")
x <- as.data.frame(x, stringsAsFactors=FALSE)
}
if (nrow(x) == 0) {
stop("interval specification has no rows")
}
# Confirm that the minimal columns (start and end) exist
if (length(missing.required.cols <- setdiff(c("start", "end"), names(x))) > 0) {
stop(sprintf("Column(s) %s missing from interval specification",
paste0("'", missing.required.cols, "'",
collapse=", ")))
}
interval_cols <- get.interval.cols()
# Check the edit of each column
for (n in names(interval_cols)) {
if (!(n %in% names(x))) {
if (is.vector(interval_cols[[n]]$values)) {
# Set missing columns to the default value
x[[n]] <- interval_cols[[n]]$values[1]
} else {
# It would probably take malicious code to get here (altering
# the intervals without using add.interval.col
stop("Cannot assign default value for interval column", n) # nocov
}
} else {
# Confirm the edits of the given columns
if (is.vector(interval_cols[[n]]$values)) {
if (!all(x[[n]] %in% interval_cols[[n]]$values))
stop(sprintf("Invalid value(s) in column %s:", n),
paste(unique(setdiff(x[[n]], interval_cols[[n]]$values)),
collapse=", "))
} else if (is.function(interval_cols[[n]]$values)) {
if (is.factor(x[[n]])) {
stop(sprintf("Interval column '%s' should not be a factor", n))
}
interval_cols[[n]]$values(x[[n]])
} else {
stop("Invalid 'values' for column specification ", n, " (please report this as a bug).") # nocov
}
}
}
# Now check specific columns
# start and end
if (any(x$start %in% NA)) {
stop("Interval specification may not have NA for the starting time")
}
if (any(x$end %in% NA)) {
stop("Interval specification may not have NA for the end time")
}
if (any(is.infinite(x$start))) {
stop("start may not be infinite")
}
if (any(x$start >= x$end)) {
stop("start must be < end")
}
# Confirm that something is being calculated for each interval (and warn if
# not)
mask_calculated <- rep(FALSE, nrow(x))
for (n in setdiff(names(interval_cols), c("start", "end"))) {
mask_calculated <-
(mask_calculated |
!(x[[n]] %in% c(NA, FALSE)))
}
if (any(!mask_calculated)) {
warning("Nothing to be calculated in interval specification number(s): ",
paste(seq_len(nrow(x))[!mask_calculated], collapse=", "))
}
# Put the columns in the right order and return the checked data frame
x[,
c(names(interval_cols), setdiff(names(x), names(interval_cols))),
drop=FALSE
]
}
# Helper function to get.parameter.deps to determine the function map
get.parameter.deps_helper_funmap <- function(x, all_intervals) {
if (is.na(x$FUN) &
is.null(x$depends)) {
# For columnns like "start" and "end"
retfun <- NA
} else if (is.na(x$FUN)) {
if (length(x$depends) == 1) {
# When the value is calculated by the same function as
# another parameter.
retfun <- all_intervals[[x$depends]]$FUN
} else {
# It would probably take malicious code to get here (an
# example of malicious code could be altering the
# intervals without using add.interval.col)
stop("Invalid interval definition with no function and multiple dependencies.") # nocov
}
} else {
retfun <- x$FUN
}
# Define a function call by its function name and the
# changes to the formal arguments made.
append(list(retfun), x$formalsmap)
}
# Helper function to get.parameter.deps to find all parameters that are defined
# by the same function
get.parameter.deps_helper_samefun <- function(n, funmap) {
mask.funmap <- rep(FALSE, length(funmap))
for (current in n) {
for (i in seq_len(length(funmap))) {
mask.funmap[i] <-
mask.funmap[i] |
!any(
is.na(funmap[[current]][[1]]),
is.na(funmap[[i]][[1]])
) &
identical(funmap[[current]], funmap[[i]])
}
}
names(funmap)[mask.funmap]
}
# Helper function to get.parameter.deps to search all dependencies
get.parameter.deps_helper_searchdeps <- function(current, funmap, all_intervals) {
# Find any parameters using the same function
start <- get.parameter.deps_helper_samefun(current, funmap)
# Find any parameters that depend on the current parameter
ret <-
vapply(
X = all_intervals,
FUN = function(x) {
any(x$depends %in% start)
},
FUN.VALUE = TRUE
)
# Extract their names
added <- setdiff(names(ret)[ret], start)
if (length(added) > 0) {
# Find any parameters that depend on any of those parameters
unique(
c(start, added,
get.parameter.deps_helper_searchdeps(added, funmap, all_intervals))
)
} else {
c(start, added)
}
}
#' Get all columns that depend on a parameter
#'
#' @param x The parameter name (as a character string)
#' @returns A character vector of parameter names that depend on the parameter
#' `x`. If none depend on `x`, then the result will be an empty vector.
#' @family Interval specifications
#' @export
get.parameter.deps <- function(x) {
all_intervals <- get.interval.cols()
if (!(x %in% names(all_intervals))) {
stop("`x` must be the name of an NCA parameter listed by the function `get.interval.cols()`")
}
funmap <-
lapply(
X=all_intervals,
FUN=get.parameter.deps_helper_funmap,
all_intervals=all_intervals
)
sort(get.parameter.deps_helper_searchdeps(x, funmap, all_intervals))
}
#' Take in a single row of an interval specification and return that row updated
#' with any additional calculations that must be done to fulfill all
#' dependencies.
#'
#' @param x A data frame with one or more rows of the PKNCA interval
#' @returns The interval specification with additional calculations added where
#' requested outputs require them.
#' @family Interval specifications
check.interval.deps <- function(x) {
# Ensure that the input is a valid interval specification
ret <- check.interval.specification(x)
colspec <- get.interval.cols()
for (n in names(colspec)) {
if (is.logical(ret[,n])) {
# This is a calculation to complete, otherwise it's something informative
# but not caluclated.
mask_calculated <- ret[,n]
for (deps in colspec[[n]]$depends)
ret[,deps] <- mask_calculated | ret[,deps]
}
}
ret
}
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.