Nothing
# rxEt environment and chunk helpers -------------------------------------
#' Is the homogenous event table path active?
#'
#'
#' @return logical; TRUE if homogenous event table path is active,
#' FALSE otherwise, gets from option "rxode2.homogenous" with
#' default TRUE
#' @noRd
#' @author Matthew L. Fidler
.rxGetHomogenous <- function() {
isTRUE(getOption("rxode2.homogenous", TRUE))
}
#' Extract mutable .env from either new-style (data.frame subclass + .rxEtEnv attr)
#' or internal mini-rxEt (1-element named list where [[1L]] is the env).
#'
#' @param x object to extract data from
#'
#' @return environment or NULL if not found
#'
#' @noRd
.rxEtEnv <- function(x) {
.e <- attr(x, ".rxEtEnv", exact = TRUE)
if (!is.null(.e)) return(.e)
.ux <- unclass(x)
if (is.list(.ux) && ".env" %in% names(.ux)) return(.ux[[".env"]])
if (length(.ux) >= 1L && is.environment(.ux[[1L]])) return(.ux[[1L]])
NULL
}
#' Default show flags (controls which columns print/as.data.frame expose)
#'
#' This is kept here so that it can be specified once and referred to
#' by multiple locations.
#'
#' @return named logical vector of default show flags
#'
#' @noRd
.etDefaultShow <- function() {
c(id = FALSE, low = FALSE, time = TRUE, high = FALSE, cmt = FALSE,
amt = FALSE, rate = FALSE, ii = FALSE, addl = FALSE, evid = TRUE,
ss = FALSE, dur = FALSE)
}
#' Drop the units for the check
#'
#' @param df NULL, data.frame or non-data frame object.
#' from for internal use
#'
#' @return this function returns NULL or the non-materialized data, or
#' the data.frame with units columns converted to numeric for
#' internal use
#'
#' @noRd
#' @author Matthew L. Fidler
.etDropUnitsForChunk <- function(df) {
if (is.null(df) || !is.data.frame(df)) {
return(df)
}
for (.nm in names(df)) {
if (inherits(df[[.nm]], "units")) {
df[[.nm]] <- as.numeric(df[[.nm]])
}
}
df
}
#' Get the groups from the rxEt environment
#'
#' @param envRef The environmental reference for the rxEt groups
#' @return A list of groups, where each group is a list containing 'ids' and 'data'
#' @noRd
#' @author Matthew L. Fidler
.etGroups <- function(envRef) {
.groups <- envRef$groups
if (is.null(.groups)) .groups <- list()
.groups
}
#' Check if two sets of group IDs are equal
#'
#' @param x first group to check if it is equal.
#' @param y second group to check if it is equal.
#' @return logical value indicating whether the two sets of group IDs are equal
#' @noRd
#' @author Matthew L. Fidler
.etGroupIdsEqual <- function(x, y) {
identical(sort.int(as.integer(x), method = "quick"),
sort.int(as.integer(y), method = "quick"))
}
#' Process a chunk of data for grouping
#'
#' This function takes a data frame and an optional set of IDs, and
#' processes the data frame for grouping.
#'
#'
#' @param df data frame to process for grouping
#' @param ids optional vector of IDs to consider for grouping; if NULL, the function will
#' @return processed data frame
#' @noRd
#' @author Matthew L. Fidler
.etGroupChunk <- function(df, ids = NULL) {
if (!is.data.frame(df)) {
df <- .etExpandObsChunk(df)
}
df <- .etDropUnitsForChunk(df)
if (!is.null(ids) && "id" %in% names(df)) {
.id <- unique(as.integer(df$id))
if (length(.id) == 1L && length(ids) >= 1L) {
df$id <- NULL
}
}
df
}
#' Get the groups from the rxEt environment, with fallback to chunks
#' if groups are not set
#'
#' @param envRef The environment reference group
#' @return A list of groups, where each group is a list containing
#' 'ids' and 'data'. If groups are not set, it will return an empty
#' list or process the chunks to create groups.
#' @noRd
#' @author Matthew L. Fidler
.etGetGroups <- function(envRef) {
.groups <- .etGroups(envRef)
if (length(.groups) > 0L) return(.groups)
.chunks <- envRef$chunks
if (length(.chunks) == 0L) return(list())
.ret <- vector("list", 0L)
for (.i in seq_along(.chunks)) {
.chunk <- .chunks[[.i]]
if (is.null(.chunk)) next
.ids <- if (!is.null(names(.chunk)) && "id" %in% names(.chunk)) {
unique(as.integer(.chunk$id))
} else if (length(envRef$ids) == 1L) {
as.integer(envRef$ids)
} else {
as.integer(.i)
}
.df <- .etGroupChunk(.chunk, .ids)
.ret[[length(.ret) + 1L]] <- list(ids = .ids, data = .df)
}
.ret
}
.etSetGroups <- function(envRef, groups) {
envRef$groups <- groups
if (length(groups) > 0L) {
envRef$chunks <- list()
}
invisible(NULL)
}
.etPresentIds <- function(envRef) {
.groups <- .etGroups(envRef)
if (length(.groups) > 0L) {
return(sort(unique(as.integer(unlist(lapply(.groups, `[[`, "ids"), use.names = FALSE)))))
}
.chunks <- envRef$chunks
if (length(.chunks) == 0L) return(integer(0))
.ids <- integer(0)
for (.i in seq_along(.chunks)) {
.chunk <- .chunks[[.i]]
if (is.null(.chunk)) next
if (!is.null(names(.chunk)) && "id" %in% names(.chunk)) {
.ids <- c(.ids, unique(as.integer(.chunk$id)))
} else if (length(envRef$ids) == 1L) {
.ids <- c(.ids, as.integer(envRef$ids))
} else {
.ids <- c(.ids, as.integer(.i))
}
}
sort(unique(.ids))
}
#' Per-row id column of the materialized table, without materializing
#'
#' Mirrors the row counts of .etMaterialize() (obs-chunk expansion plus
#' the windowed-addl expansion of .etExpandAddl(windows=TRUE)) but never
#' draws window times, so reading ev$id does not touch the RNG (#1154).
#'
#' @param et rxEt object
#' @return integer vector matching .etMaterialize(et)$id
#' @noRd
.etIdColumn <- function(et) {
.env <- .rxEtEnv(et)
.groups <- .etGetGroups(.env)
if (length(.groups) == 0L) return(integer(0))
.ids <- integer(0)
.counts <- integer(0)
for (.g in .groups) {
.df <- .g$data
if (!is.data.frame(.df)) .df <- .etExpandObsChunk(.df)
if (!is.data.frame(.df) || nrow(.df) == 0L) next
# rows added by the windowed-addl expansion in .etMaterialize
.extra <- if (is.null(.df$evid) || is.null(.df$addl) || is.null(.df$low)) {
rep.int(0L, nrow(.df))
} else {
ifelse(!is.na(.df$evid) & .df$evid != 0L &
!is.na(.df$addl) & .df$addl > 0L & !is.na(.df$low),
as.integer(.df$addl), 0L)
}
if (!is.null(.df$id)) {
.rowIds <- as.integer(.df$id)
.tab <- tapply(1L + .extra, .rowIds, sum)
.ids <- c(.ids, as.integer(names(.tab)))
.counts <- c(.counts, as.integer(.tab))
} else {
.n <- nrow(.df) + sum(.extra)
.gIds <- as.integer(.g$ids)
.ids <- c(.ids, .gIds)
.counts <- c(.counts, rep.int(as.integer(.n), length(.gIds)))
}
}
if (length(.ids) == 0L) return(integer(0))
# total rows per unique id, in ascending id order (materialize sorts by id)
.tot <- vapply(split(.counts, .ids), sum, integer(1))
.uid <- as.integer(names(.tot))
.ord <- order(.uid)
rep(.uid[.ord], .tot[.ord])
}
.etResetCountsFromGroups <- function(envRef) {
.groups <- .etGetGroups(envRef)
if (length(.groups) == 0L) {
envRef$nobs <- 0L
envRef$ndose <- 0L
return(invisible(NULL))
}
envRef$nobs <- as.integer(sum(vapply(.groups, function(.g) {
sum(.g$data$evid == 0L, na.rm = TRUE) * length(.g$ids)
}, numeric(1))))
envRef$ndose <- as.integer(sum(vapply(.groups, function(.g) {
sum(.g$data$evid != 0L, na.rm = TRUE) * length(.g$ids)
}, numeric(1))))
invisible(NULL)
}
.etChunkIndex <- function(chunks, id) {
.idx <- as.integer(id)
if (is.na(.idx) || .idx < 1L) {
stop("invalid event table id", call. = FALSE)
}
.len <- length(chunks)
if (.idx > .len) {
length(chunks) <- .idx
}
chunks
}
.etSplitGroupToChunks <- function(chunks, group, randomType = NA_integer_) {
.df <- .etDropUnitsForChunk(group$data)
if (!is.data.frame(.df) || nrow(.df) == 0L || length(group$ids) == 0L) {
return(chunks)
}
.hasWin <- !is.na(.df$low) & !is.na(.df$high)
for (.id in as.integer(group$ids)) {
.row <- .df
if (any(.hasWin)) {
if (!is.na(randomType) && randomType == 3L) {
.row$time[.hasWin] <- stats::rnorm(sum(.hasWin), .row$low[.hasWin], .row$high[.hasWin])
} else if (!is.na(randomType) && randomType == 2L) {
.row$time[.hasWin] <- stats::runif(sum(.hasWin), .row$low[.hasWin], .row$high[.hasWin])
}
}
.row$id <- .id
chunks <- .etChunkIndex(chunks, .id)
chunks[[.id]] <- .row
}
chunks
}
.etSimulateRepresentation <- function(envRef) {
.groups <- .etGroups(envRef)
.chunks <- envRef$chunks
.randomType <- envRef$randomType
.hasWin <- FALSE
.needsChunkSplit <- length(.chunks) > 0L
if (length(.groups) > 0L) {
for (.g in .groups) {
.df <- .etDropUnitsForChunk(.g$data)
if (is.data.frame(.df) && nrow(.df) > 0L) {
.groupHasWin <- !is.na(.df$low) & !is.na(.df$high)
if (any(.groupHasWin)) {
.hasWin <- TRUE
if (length(.g$ids) > 1L) {
.needsChunkSplit <- TRUE
}
}
}
}
}
if (!.hasWin && length(.chunks) > 0L) {
for (.i in seq_along(.chunks)) {
.df <- .etDropUnitsForChunk(.chunks[[.i]])
if (is.data.frame(.df) && nrow(.df) > 0L && any(!is.na(.df$low) & !is.na(.df$high))) {
.hasWin <- TRUE
break
}
}
}
if (!.hasWin) {
return(list(hasWin = FALSE, groups = .groups, chunks = .chunks))
}
if (!is.na(.randomType) && .randomType == 1L) {
return(list(hasWin = FALSE, groups = .groups, chunks = .chunks))
}
if (.needsChunkSplit) {
.outChunks <- vector("list", max(length(.chunks), suppressWarnings(max(as.integer(envRef$ids), 0L)), na.rm = TRUE))
if (length(.groups) > 0L) {
for (.g in .groups) {
.outChunks <- .etSplitGroupToChunks(.outChunks, .g, .randomType)
}
}
if (length(.chunks) > 0L) {
for (.i in seq_along(.chunks)) {
.df <- .etDropUnitsForChunk(.chunks[[.i]])
if (!is.data.frame(.df) || nrow(.df) == 0L) next
.hasWinI <- !is.na(.df$low) & !is.na(.df$high)
if (any(.hasWinI)) {
if (!is.na(.randomType) && .randomType == 3L) {
.df$time[.hasWinI] <- stats::rnorm(sum(.hasWinI), .df$low[.hasWinI], .df$high[.hasWinI])
} else if (!is.na(.randomType) && .randomType == 2L) {
.df$time[.hasWinI] <- stats::runif(sum(.hasWinI), .df$low[.hasWinI], .df$high[.hasWinI])
}
}
.id <- if ("id" %in% names(.df)) unique(as.integer(.df$id))[1L] else as.integer(.i)
.outChunks <- .etChunkIndex(.outChunks, .id)
.outChunks[[.id]] <- .df
}
}
return(list(hasWin = TRUE, groups = list(), chunks = .outChunks))
}
.outGroups <- lapply(.groups, function(.g) {
.df <- .etDropUnitsForChunk(.g$data)
.hasWinG <- !is.na(.df$low) & !is.na(.df$high)
if (any(.hasWinG)) {
if (!is.na(.randomType) && .randomType == 3L) {
.df$time[.hasWinG] <- stats::rnorm(sum(.hasWinG), .df$low[.hasWinG], .df$high[.hasWinG])
} else if (!is.na(.randomType) && .randomType == 2L) {
.df$time[.hasWinG] <- stats::runif(sum(.hasWinG), .df$low[.hasWinG], .df$high[.hasWinG])
}
}
list(ids = .g$ids, data = .df)
})
list(hasWin = TRUE, groups = .outGroups, chunks = list())
}
.formatIds <- function(ids) {
ids <- sort(unique(as.integer(ids)))
if (length(ids) == 0L) return(NA_character_)
if (length(ids) == 1L) return(ids)
if (all(diff(ids) == 1L)) {
return(paste0(ids[1L], ":", ids[length(ids)]))
}
paste(ids, collapse = ",")
}
.etPreviewData <- function(envRef, subset = c("all", "dosing", "sampling")) {
subset <- match.arg(subset)
.groups <- .etGroups(envRef)
if (length(.groups) == 0L) {
.mat <- .etMaterialize(structure(list(env = envRef), class = "rxEt"))
if (nrow(.mat) == 0L) return(NULL)
if (subset == "dosing") {
.mat <- .mat[.mat$evid != 0L, , drop = FALSE]
} else if (subset == "sampling") {
.mat <- .mat[.mat$evid == 0L, , drop = FALSE]
}
if (nrow(.mat) == 0L) return(NULL)
rownames(.mat) <- seq_len(nrow(.mat))
return(.mat)
}
.ret <- vector("list", 0L)
.meta <- vector("list", 0L)
for (.i in seq_along(.groups)) {
.g <- .groups[[.i]]
.df <- .etDropUnitsForChunk(.g$data)
if (subset == "dosing") {
.df <- .df[.df$evid != 0L, , drop = FALSE]
} else if (subset == "sampling") {
.df <- .df[.df$evid == 0L, , drop = FALSE]
}
if (nrow(.df) == 0L) next
if (subset == "all") {
.formattedId <- if (length(.g$ids) == 1L) as.integer(.g$ids) else .formatIds(.g$ids)
.df <- cbind(id = .formattedId, .df)
}
.ret[[length(.ret) + 1L]] <- .df
.meta[[length(.meta) + 1L]] <- list(
ids = as.integer(.g$ids),
nSub = length(.g$ids),
nRow = nrow(.df)
)
}
if (length(.ret) == 0L) return(NULL)
.out <- as.data.frame(data.table::rbindlist(.ret, fill = TRUE))
rownames(.out) <- seq_len(nrow(.out))
attr(.out, "rxEtPreviewGroups") <- .meta
attr(.out, "rxEtShow") <- envRef$show
attr(.out, "rxEtExtraCols") <- .etExtraCols(envRef) # nolint
attr(.out, "rxEtMarkedCols") <- names(.out)
.tu <- envRef$units["time"]
if (!is.na(.tu) && nchar(.tu) > 0 && requireNamespace("units", quietly = TRUE)) {
for (.col in c("time", "ii", "low", "high", "dur")) {
if (.col %in% names(.out)) {
.out[[.col]] <- units::set_units(.out[[.col]], .tu, mode = "standard")
}
}
}
# class last: `[[<-` on a marked frame drops the display marking
class(.out) <- c("rxEtPreview", class(.out))
.out
}
.etMaterializeGroup <- function(group) {
.df <- .etDropUnitsForChunk(group$data)
if (!is.data.frame(.df) || nrow(.df) == 0L || length(group$ids) == 0L) {
return(.etEmptyDf())
}
.n <- nrow(.df)
.idx <- rep(seq_len(.n), times = length(group$ids))
.id <- rep(as.integer(group$ids), each = .n)
.ret <- .df[.idx, , drop = FALSE]
.ret$id <- .id
.ret
}
.etShiftChunk <- function(df, timeDelta) {
.df <- .etDropUnitsForChunk(df)
if (!is.data.frame(.df) || nrow(.df) == 0L || identical(timeDelta, 0)) {
return(.df)
}
.df$time <- .df$time + timeDelta
if (!is.null(.df$low) && any(!is.na(.df$low))) {
.df$low[!is.na(.df$low)] <- .df$low[!is.na(.df$low)] + timeDelta
}
if (!is.null(.df$high) && any(!is.na(.df$high))) {
.df$high[!is.na(.df$high)] <- .df$high[!is.na(.df$high)] + timeDelta
}
if (!is.null(.df$ii)) .df$ii[is.na(.df$ii)] <- 0.0
.df
}
.etExpandGroupData <- function(df, envRef) {
.df <- .etDropUnitsForChunk(df)
if (!is.data.frame(.df) || nrow(.df) == 0L) {
return(.df)
}
.tmp <- .df
.tmp$id <- 1L
.expanded <- .etExpandAddl(.tmp, envRef)
.expanded$id <- NULL
.expanded
}
#' Add rows of a data.frame to the ID-indexed chunks list
#'
#' Assigns id column and appends to \code{chunks[[id]]} for each ID in
#' .ids.
#'
#' @param .envRef mutable env from rxEt
#'
#' @param .df data.frame of rows to add (no 'id' column yet)
#'
#' @param .ids integer vector of ids; NULL/empty defaults to 1L
#'
#' @noRd
.etAddChunk <- function(envRef, df, ids = NULL) {
.rt <- attr(df, ".randomType")
if (!is.null(.rt) && !is.na(.rt)) {
if (is.na(envRef$randomType) || .rt > envRef$randomType)
envRef$randomType <- .rt
if (!isTRUE(envRef$show["low"])) {
envRef$show["low"] <- TRUE
envRef$show["high"] <- TRUE
}
}
if (!is.data.frame(df)) {
df <- .etExpandObsChunk(df)
}
if (nrow(df) == 0) {
return(invisible(NULL))
}
if (is.null(ids) || length(ids) == 0L) {
ids <- 1L
} else {
ids <- as.integer(ids)
}
.posIds <- ids[ids > 0L]
if (length(.posIds) == 0L) return(invisible(NULL))
if (!"id" %in% names(df)) {
.groups <- .etGetGroups(envRef)
.chunk <- .etGroupChunk(df, .posIds)
if (length(.groups) > 0L) {
.newGroups <- vector("list", 0L)
.remainingIds <- sort.int(as.integer(.posIds), method = "quick")
for (.g in .groups) {
.overlap <- intersect(.g$ids, .remainingIds)
if (length(.overlap) == 0L) {
.newGroups[[length(.newGroups) + 1L]] <- .g
next
}
.keepIds <- setdiff(.g$ids, .overlap)
if (length(.keepIds) > 0L) {
.newGroups[[length(.newGroups) + 1L]] <- list(ids = .keepIds, data = .g$data)
}
.newGroups[[length(.newGroups) + 1L]] <- list(
ids = sort.int(as.integer(.overlap), method = "quick"),
data = as.data.frame(data.table::rbindlist(list(.g$data, .chunk), fill = TRUE))
)
.remainingIds <- setdiff(.remainingIds, .overlap)
}
if (length(.remainingIds) > 0L) {
.newGroups[[length(.newGroups) + 1L]] <- list(ids = .remainingIds, data = .chunk)
}
.etSetGroups(envRef, .newGroups)
return(invisible(NULL))
}
if (length(.posIds) > 1L) {
.match <- which(vapply(.groups, function(.g) .etGroupIdsEqual(.g$ids, .posIds), logical(1)))
if (length(.match) == 1L) {
.groups[[.match]]$data <- as.data.frame(
data.table::rbindlist(list(.groups[[.match]]$data, .chunk), fill = TRUE)
)
} else {
.groups[[length(.groups) + 1L]] <- list(ids = sort.int(as.integer(.posIds), method = "quick"),
data = .chunk)
}
.etSetGroups(envRef, .groups)
return(invisible(NULL))
}
}
for (.i in .posIds) {
.row <- .etDropUnitsForChunk(df)
.row$id <- .i
if (.i <= length(envRef$chunks)) {
.cur <- envRef$chunks[[.i]] # units already dropped
envRef$chunks[[.i]] <- as.data.frame(data.table::rbindlist(list(.cur, .row), fill = TRUE))
} else {
envRef$chunks[[.i]] <- .row
}
}
invisible(NULL)
}
#' Merge a data.frame (with id column) into an ID-indexed chunks list
#'
#' Used by etSeq/etRbind when accumulating materialized data.frames.
#'
#' @param chunks list indexed by ID integer value
#'
#' @param df data.frame with 'id' column already set
#'
#' @return updated chunks
#'
#' @noRd
.addRowsToChunks <- function(chunks, df) {
if (nrow(df) == 0L) return(chunks)
.ids <- unique(as.integer(df$id))
for (.i in .ids) {
.rows <- .etDropUnitsForChunk(df[df$id == .i, , drop = FALSE])
if (.i <= length(chunks)) {
.cur <- chunks[[.i]] # units already dropped
chunks[[.i]] <- as.data.frame(data.table::rbindlist(list(.cur, .rows), fill = TRUE))
} else {
chunks[[.i]] <- .rows
}
}
chunks
}
# rxEt object construction ------------------------------------------------
#' Create a new empty rxEt object
#'
#' @param amountUnits character dose unit, e.g. "mg"
#'
#' @param timeUnits character time unit, e.g. "hours"
#'
#' @return rxEt object
#'
#' @noRd
.newRxEt <- function(amountUnits = NA_character_, timeUnits = NA_character_) {
.env <- new.env(parent = emptyenv())
.env$chunks <- list()
.env$groups <- list()
.env$units <- c(dosing = amountUnits, time = timeUnits)
.env$show <- .etDefaultShow()
.env$ids <- 1L
.env$nobs <- 0L
.env$ndose <- 0L
.env$randomType <- NA_integer_
.env$canResize <- TRUE
.env$extraCols <- character(0)
.env$methods <- .etBuildMethods(.env)
.obj <- list(
id = integer(0), low = numeric(0), time = numeric(0), high = numeric(0),
cmt = integer(0), amt = numeric(0), rate = numeric(0), ii = numeric(0),
addl = integer(0), evid = integer(0), ss = integer(0), dur = numeric(0)
)
attr(.obj, "class") <- c("rxEt", "data.frame")
attr(.obj, "row.names") <- integer(0)
attr(.obj, ".rxEtEnv") <- .env
.obj
}
#' Sync the materialized data.frame shell with the rxEt environment
#'
#' @param x rxEt object
#'
#' @return materialized synced rxEt data frame
#'
#' @noRd
.rxEtSyncData <- function(x) {
.env <- .rxEtEnv(x)
if (!is.environment(.env)) return(x)
.nTot <- .env$nobs + .env$ndose
.ret <- if (.nTot <= 1000L) .etMaterialize(x) else .etEmptyDf()
attr(.ret, ".rxEtEnv") <- .env
.rt <- .env$randomType
.cls <- c("rxEt", "data.frame")
if (!is.null(.rt) && !is.na(.rt)) {
.cls <- structure(.cls, ".rxode2.lst" = list(randomType = as.integer(.rt)))
}
class(.ret) <- .cls
.ret
}
# rxEt materialization and dose expansion --------------------------------
#' Expand addl doses into individual records
#'
#' @param df materialized data.frame from .etMaterialize()
#'
#' @param env environment for randomType and show flags
#'
#' @param windows logical; if TRUE, only expand windows (low/high) by
#' ii on each subsequent dose based on random type in env
#'
#' @return data.frame with addl expanded into individual dose records
#'
#' @noRd
.etExpandAddl <- function(df, env=NULL, windows=FALSE) {
if (windows) {
.doseRows <- df[df$evid != 0L & df$addl > 0L & !is.na(df$low), , drop = FALSE]
} else {
.doseRows <- df[df$evid != 0L & df$addl > 0L, , drop = FALSE]
}
if (nrow(.doseRows) == 0L) return(df)
if (is.environment(env) &&
exists("randomType", envir = env, inherits = FALSE)) {
.rt <- env$randomType
if (is.na(.rt)) .rt <- 1L
} else {
.rt <- 1
}
.extras <- vector("list", nrow(.doseRows))
for (.i in seq_len(nrow(.doseRows))) {
.row <- .doseRows[.i, ]
.n <- .row$addl
.extra <- .row[rep(1L, .n), , drop = FALSE]
# This works for normal and uniform cases
if (!is.na(.row$low)) {
.extra$low <- .row$low + seq_len(.n) * .row$ii
}
if (.rt == 3) {
# normal; sd remains the same on expansion
.extra$time <- vapply(seq_len(.n), function(i) {
stats::rnorm(1L, .extra$low[i], .extra$high[i])
}, numeric(1), USE.NAMES = FALSE)
} else if (!is.na(.row$high)) {
# uniform or window expand window by ii on each subsequent dose
.extra$high <- .row$high + seq_len(.n) * .row$ii
.extra$time <- vapply(seq_len(.n), function(i) {
stats::runif(1L, .extra$low[i], .extra$high[i])
}, numeric(1), USE.NAMES = FALSE)
} else {
.extra$time <- .row$time + seq_len(.n) * .row$ii
}
.extra$high <- .row$high + seq_len(.n) * .row$ii
.extra$addl <- 0L
.extra$ii <- 0.0
.extras[[.i]] <- .extra
}
df$addl[df$evid != 0L] <- 0L
df$ii[df$evid != 0L] <- 0.0
.combined <- do.call(rbind, c(list(df), .extras))
.evidSort <- ifelse(.combined$evid == 3L, -1L, as.integer(.combined$evid))
.combined[.order3(.combined$id, .combined$time, .evidSort), ] # nolint
}
# Canonical column order matching C++ etEmpty()
.etColOrder <- c("id", "low", "time", "high", "cmt", "amt", "rate", "ii", "addl", "evid", "ss", "dur")
#' Convert character cmt column to integer for C++ solver
#'
#' The C++ etTran code handles integer cmt as direct compartment indices,
#' but character cmt values are looked up as compartment NAMES. Numeric
#' strings like "2" do NOT match compartment names ("amt2"), so they get
#' assigned as extra compartments beyond state.size(). This function converts
#' all-convertible character cmt columns to integer before passing to C++.
#'
#' "(default)" and "(obs)" map to compartment 1; NA stays NA_integer_.
#' If any value is a non-numeric, non-sentinel string (a compartment name),
#' the column stays character so C++ name lookup works normally.
#'
#' @param df materialized data.frame from .etMaterialize()
#'
#' @return df with cmt column possibly converted to integer
#'
#' @noRd
.etFixCmtForSolve <- function(df) {
.cmt <- df[["cmt"]]
if (is.null(.cmt) || !is.character(.cmt)) return(df)
.isNA <- is.na(.cmt)
.isSentinel <- !.isNA & (.cmt == "(default)" | .cmt == "(obs)")
.numericOk <- !.isNA & !.isSentinel & suppressWarnings(!is.na(as.integer(.cmt)))
if (all(.isNA | .isSentinel | .numericOk)) {
.int <- integer(length(.cmt))
.int[.isNA] <- NA_integer_
.int[.isSentinel] <- 1L
.int[.numericOk] <- as.integer(.cmt[.numericOk])
df[["cmt"]] <- .int
}
df
}
.etGroupedSolveData <- function(x) {
.env <- .rxEtEnv(x)
.groups <- .etGetGroups(.env)
if (length(.groups) == 0L) {
return(NULL)
}
.dat <- vector("list", 0L)
.ids <- vector("list", 0L)
for (.i in seq_along(.groups)) {
.g <- .groups[[.i]]
.df <- .etFixCmtForSolve(.etDropUnitsForChunk(.g$data))
if (!is.data.frame(.df) || nrow(.df) == 0L || length(.g$ids) == 0L) next
.df$id <- rep.int(.i, nrow(.df))
.dat[[length(.dat) + 1L]] <- .df
.ids[[length(.ids) + 1L]] <- as.integer(.g$ids)
}
if (length(.dat) == 0L) {
return(.etEmptyDf())
}
.out <- as.data.frame(data.table::rbindlist(.dat, fill = TRUE, use.names = TRUE))
if ("evid" %in% names(.out)) {
.doseIdx <- which(.out$evid != 0L)
if (length(.doseIdx) > 0L) {
for (.col in c("rate", "ii", "dur")) {
if (.col %in% names(.out)) {
.vals <- .out[[.col]]
.naInDose <- is.na(.vals[.doseIdx])
if (any(.naInDose)) {
.vals[.doseIdx[.naInDose]] <- 0.0
.out[[.col]] <- .vals
}
}
}
for (.col in c("addl", "ss")) {
if (.col %in% names(.out)) {
.vals <- .out[[.col]]
.naInDose <- is.na(.vals[.doseIdx])
if (any(.naInDose)) {
.vals[.doseIdx[.naInDose]] <- 0L
.out[[.col]] <- .vals
}
}
}
}
}
if ("time" %in% names(.out) && "id" %in% names(.out)) {
.evidSort <- if ("evid" %in% names(.out)) {
ifelse(!is.na(.out$evid) & .out$evid == 3L, -1L, as.integer(.out$evid))
} else {
rep(0L, nrow(.out))
}
.ord <- order(.out$id, as.numeric(.out$time), .evidSort)
.out <- .out[.ord, ]
}
attr(.out, "rxHomGroups") <- .ids
attr(.out, "rxHomIdLevels") <- as.character(unlist(.ids, use.names = FALSE))
.tu <- .env$units["time"]
if (!is.na(.tu) && nchar(.tu) > 0 && requireNamespace("units", quietly = TRUE)) {
.out[["time"]] <- units::set_units(.out[["time"]], .tu, mode = "standard")
}
.out
}
.etGroupedSolveSplitKey <- function(df) {
if (!is.data.frame(df) || ncol(df) == 0L) {
return(rep.int("1", if (is.data.frame(df)) nrow(df) else 0L))
}
do.call(interaction, c(unname(df), list(drop = TRUE, lex.order = TRUE)))
}
.etGroupedSolveDataFrameICov <- function(events, iCov, keep = NULL, modelParams = character(0)) {
.groups <- attr(events, "rxHomGroups", exact = TRUE)
if (!is.data.frame(events) || is.null(.groups) || !inherits(iCov, "data.frame")) {
return(NULL)
}
.idCol <- which(tolower(names(iCov)) == "id")
if (length(.idCol) != 1L) {
return(NULL)
}
.idName <- names(iCov)[.idCol]
.keep <- as.character(keep)
.modelParams <- as.character(modelParams)
.icovIdRaw <- if (is.factor(iCov[[.idName]])) as.character(iCov[[.idName]]) else iCov[[.idName]]
.icovId <- suppressWarnings(as.integer(.icovIdRaw))
if (length(.icovId) != nrow(iCov) || anyNA(.icovId)) {
return(NULL)
}
if (anyDuplicated(.icovId)) {
return(NULL)
}
.eventId <- suppressWarnings(as.integer(if (is.factor(events$id)) as.character(events$id) else events$id))
if (length(.eventId) != nrow(events) || anyNA(.eventId)) {
return(NULL)
}
.eventRows <- vector("list", 0L)
.icovRows <- vector("list", 0L)
.outGroups <- vector("list", 0L)
.outId <- 1L
for (.i in seq_along(.groups)) {
.idsInGroup <- as.integer(.groups[[.i]])
.idx <- match(.idsInGroup, .icovId)
if (anyNA(.idx)) {
return(NULL)
}
.subIc <- iCov[.idx, , drop = FALSE]
.splitCols <- setdiff(names(.subIc), .idName)
.splitNeeded <- unique(c(tolower(.modelParams), tolower(.keep)))
.splitCols <- .splitCols[tolower(.splitCols) %in% .splitNeeded]
.splitKey <- if (length(.splitCols) == 0L) {
factor(rep.int("1", nrow(.subIc)))
} else {
.etGroupedSolveSplitKey(.subIc[.splitCols])
}
.split <- split(seq_len(nrow(.subIc)), .splitKey, drop = TRUE)
.df <- events[.eventId == .i, , drop = FALSE]
if (!is.data.frame(.df) || nrow(.df) == 0L) {
return(NULL)
}
for (.grpIdx in .split) {
.ids <- as.integer(.idsInGroup[.grpIdx])
.row <- .df
.row$id <- rep.int(.outId, nrow(.row))
.eventRows[[length(.eventRows) + 1L]] <- .row
.icovRow <- .subIc[.grpIdx[1L], , drop = FALSE]
.icovRow[[.idName]] <- .outId
.icovRows[[length(.icovRows) + 1L]] <- .icovRow
.outGroups[[length(.outGroups) + 1L]] <- .ids
.outId <- .outId + 1L
}
}
if (length(.eventRows) == 0L) {
return(NULL)
}
.events <- as.data.frame(data.table::rbindlist(.eventRows, fill = TRUE, use.names = TRUE))
.iCovOut <- as.data.frame(data.table::rbindlist(.icovRows, fill = TRUE, use.names = TRUE))
.iCovOut[[.idName]] <- suppressWarnings(as.integer(.iCovOut[[.idName]]))
if (anyNA(.iCovOut[[.idName]])) {
return(NULL)
}
attr(.events, "rxHomGroups") <- .outGroups
attr(.events, "rxHomIdLevels") <- as.character(unlist(.outGroups, use.names = FALSE))
list(
events = .events,
iCov = .iCovOut
)
}
.etGroupedSolveDataICov <- function(x, iCov, keep = NULL, modelParams = character(0)) {
.env <- .rxEtEnv(x)
.groups <- .etGetGroups(.env)
if (length(.groups) == 0L) {
return(NULL)
}
.etGroupedSolveDataFrameICov(.etGroupedSolveData(x), iCov,
keep = keep, modelParams = modelParams)
}
.etSolveObsValue <- function(x, name) {
if (is.null(x)) {
return(NULL)
}
if (length(x) != 1L) {
stop(sprintf("'%s' must be of length 1", name), call. = FALSE)
}
as.numeric(x)[1L]
}
.etSolveObsTimes <- function(events, ctl = NULL) {
.from <- .etSolveObsValue(ctl$from, "from")
if (is.null(.from)) {
.from <- 0.0
}
.to <- .etSolveObsValue(ctl$to, "to")
if (is.null(.to)) {
.maxTime <- suppressWarnings(max(as.numeric(events$time), na.rm = TRUE))
if (!is.finite(.maxTime)) {
.maxTime <- .from
}
.to <- .maxTime + 24
}
.by <- .etSolveObsValue(ctl$by, "by")
.lengthOut <- .etSolveObsValue(ctl$length.out, "length.out")
if (!is.null(.lengthOut)) {
.lengthOut <- as.integer(.lengthOut)
if (!is.null(.by)) {
stop("cannot use both 'by' and 'length.out' for rxode2 simulations", call. = FALSE)
}
.by <- (.to - .from)/(.lengthOut - 1)
} else if (is.null(.by)) {
.lengthOut <- 200L
.by <- (.to - .from)/(.lengthOut - 1)
} else {
.lengthOut <- as.integer((.to - .from)/.by + 1.0)
}
.by * seq.int(0L, .lengthOut - 1L) + .from
}
.etAddSolveObsRows <- function(events, obsTimes) {
.groups <- attr(events, "rxHomGroups", exact = TRUE)
.idLevels <- attr(events, "rxHomIdLevels", exact = TRUE)
.events <- .etFixCmtForSolve(events)
.events$time <- as.numeric(.events$time)
.ids <- sort(unique(as.integer(.events$id)))
.obs <- vector("list", length(.ids))
for (.i in seq_along(.ids)) {
.obs[[.i]] <- data.frame(
id = rep.int(.ids[.i], length(obsTimes)),
time = obsTimes,
evid = 0L
)
}
.out <- as.data.frame(data.table::rbindlist(c(list(.events), .obs), fill = TRUE, use.names = TRUE))
if (!is.null(.groups)) {
attr(.out, "rxHomGroups") <- .groups
}
if (!is.null(.idLevels)) {
attr(.out, "rxHomIdLevels") <- .idLevels
}
.out
}
.etPrepareGroupedSolveData <- function(events, ctl = NULL) {
.groups <- attr(events, "rxHomGroups", exact = TRUE)
.events <- .etFixCmtForSolve(events)
if (is.null(.groups)) {
return(.events)
}
if (nrow(.events) == 0L) {
return(.events)
}
if ("evid" %in% names(.events) && any(.events$evid == 0L, na.rm = TRUE)) {
return(.events)
}
.etAddSolveObsRows(.events, .etSolveObsTimes(.events, ctl))
}
.etPrepareSolveEvents <- function(events, ctl = NULL) {
if (!is.rxEt(events)) {
return(.etPrepareGroupedSolveData(events, ctl))
}
.env <- .rxEtEnv(events)
if (is.null(ctl$iCov) && length(.etGroups(.env)) > 0L) {
return(.etPrepareGroupedSolveData(.etGroupedSolveData(events), ctl))
}
if (isTRUE(.env$nobs == 0L)) {
return(.etFixCmtForSolve(.etMaterialize(events)))
}
.etFixCmtForSolve(.etMaterialize(events))
}
#' Empty data.frame skeleton matching materialized format
#'
#' @return empty data.frame with canonical columns and types
#' @noRd
.etEmptyDf <- function() {
data.frame(
id = integer(0),
low = numeric(0),
time = numeric(0),
high = numeric(0),
cmt = character(0),
amt = numeric(0),
rate = numeric(0),
ii = numeric(0),
addl = integer(0),
evid = integer(0),
ss = integer(0),
dur = numeric(0),
stringsAsFactors = FALSE
)
}
#' Materialize all chunks into a sorted data.frame
#'
#' Chunks is a list indexed by ID integer value; \code{chunks[[i]]} is
#' a data.frame for ID i (with 'id' column set), or NULL if no records
#' for that ID.
#'
#' @param et rxEt object
#'
#' @return data.frame with canonical columns, sorted by (id, time,
#' evid-adjusted)
#'
#' @noRd
.etMaterialize <- function(et) {
id <- NULL # for R cmd check since data.table uses id := ...
.env <- .rxEtEnv(et)
.groups <- .etGetGroups(.env)
if (length(.groups) > 0L) {
.nonNull <- Filter(function(.g) !is.null(.g$data) && nrow(.g$data) > 0L, .groups)
if (length(.nonNull) == 0L) return(.etEmptyDf())
.dtList <- lapply(.nonNull, function(.g) {
.df <- .g$data
if (!is.data.frame(.df)) {
.df <- .etExpandObsChunk(.df)
}
if ("id" %in% names(.df)) {
return(data.table::as.data.table(.df))
}
.n <- nrow(.df)
if (.n == 0L || length(.g$ids) == 0L) {
return(data.table::as.data.table(.etEmptyDf()))
}
.idx <- rep.int(seq_len(.n), times = length(.g$ids))
.id <- rep(as.integer(.g$ids), each = .n)
.ret <- data.table::as.data.table(.df[.idx, , drop = FALSE])
.ret[, id := .id]
.ret
})
.dt <- data.table::rbindlist(.dtList, fill = TRUE, use.names = TRUE)
} else {
.chunks <- .env$chunks
if (length(.chunks) == 0L) return(.etEmptyDf())
# Each element is a data.frame for one ID; filter NULL entries
.nonNull <- Filter(Negate(is.null), .chunks)
if (length(.nonNull) == 0L) return(.etEmptyDf())
# rbindlist: fast sparse bind across ID data.frames
.dt <- data.table::rbindlist(.nonNull, fill = TRUE, use.names = TRUE)
}
# ---- Fill column defaults ----
if (is.null(.dt[["id"]])) data.table::set(.dt, j = "id", value = 1L)
if (is.null(.dt[["evid"]])) data.table::set(.dt, j = "evid", value = 0L)
if (is.null(.dt[["low"]])) data.table::set(.dt, j = "low", value = NA_real_)
if (is.null(.dt[["high"]])) data.table::set(.dt, j = "high", value = NA_real_)
if (is.null(.dt[["cmt"]])) data.table::set(.dt, j = "cmt", value = "(default)")
.isDose <- .dt$evid != 0L
if (is.null(.dt[["amt"]])) data.table::set(.dt, j = "amt", value = NA_real_)
if (is.null(.dt[["rate"]])) data.table::set(.dt, j = "rate", value = ifelse(.isDose, 0.0, NA_real_))
if (is.null(.dt[["ii"]])) data.table::set(.dt, j = "ii", value = ifelse(.isDose, 0.0, NA_real_))
if (is.null(.dt[["addl"]])) data.table::set(.dt, j = "addl", value = ifelse(.isDose, 0L, NA_integer_))
if (is.null(.dt[["ss"]])) data.table::set(.dt, j = "ss", value = ifelse(.isDose, 0L, NA_integer_))
if (is.null(.dt[["dur"]])) data.table::set(.dt, j = "dur", value = ifelse(.isDose, 0.0, NA_real_))
# Patch NA cells in dose rows for numeric dose fields
.doseIdx <- which(.dt$evid != 0L)
if (length(.doseIdx) > 0L) {
for (.col in c("rate", "ii", "dur")) {
if (.col %in% names(.dt)) {
.vals <- .dt[[.col]]
.naInDose <- is.na(.vals[.doseIdx])
if (any(.naInDose)) {
.vals[.doseIdx[.naInDose]] <- 0.0
data.table::set(.dt, j = .col, value = .vals)
}
}
}
for (.col in c("addl", "ss")) {
if (.col %in% names(.dt)) {
.vals <- .dt[[.col]]
.naInDose <- is.na(.vals[.doseIdx])
if (any(.naInDose)) {
.vals[.doseIdx[.naInDose]] <- 0L
data.table::set(.dt, j = .col, value = .vals)
}
}
}
}
# Sort: id ASC, time ASC, evid=3 sorts before others at same (id, time)
.evidSort <- ifelse(.dt$evid == 3L, -1L, as.integer(.dt$evid))
.ord <- .order3(.dt$id, .dt$time, .evidSort) # nolint
.dt <- .dt[.ord, ]
# Ensure canonical column order
.missing <- setdiff(.etColOrder, names(.dt))
for (.col in .missing) data.table::set(.dt, j = .col, value = NA)
data.table::setcolorder(.dt, .etColOrder)
.df <- as.data.frame(.dt)
.df <- .etExpandAddl(.df, env = .env, windows = TRUE)
# Apply units to columns so C++ solver can propagate them to output
.tu <- .env$units["time"]
.du <- .env$units["dosing"]
.hasTimeU <- !is.na(.tu) && nchar(.tu) > 0
.hasDoseU <- !is.na(.du) && nchar(.du) > 0
if (.hasTimeU || .hasDoseU) {
if (.hasTimeU) {
for (.col in c("time", "ii", "low", "high", "dur")) {
.df[[.col]] <- units::set_units(.df[[.col]], .tu, mode = "standard")
}
}
if (.hasDoseU) {
.df[["amt"]] <- units::set_units(.df[["amt"]], .du, mode = "standard")
if (.hasTimeU) {
.df[["rate"]] <- units::set_units(.df[["rate"]], paste0(.du, "/", .tu), mode = "standard")
}
}
}
.df
}
#' Check if object is an rxEt event table
#'
#' @param x object to test
#'
#' @return logical
#'
#' @export
is.rxEt <- function(x) {
if (!inherits(x, "rxEt")) return(FALSE)
.env <- .rxEtEnv(x)
is.environment(.env)
}
# rxEt chunk builders and shell rebuilders -------------------------------
#' @noRd
.etWindowTime <- function(time, allow1 = FALSE) {
.randomType <- NA_integer_
if (inherits(time, "list")) {
.nw <- length(time)
.low <- numeric(.nw)
.mid <- numeric(.nw)
.high <- numeric(.nw)
# This is for the normal variability case that is list(c(4, 2, NA))
.hasNormal <- any(vapply(time,
function(.w) {
length(.w) == 3L && is.na(.w[3L])
}, logical(1L)))
.has3 <- any(vapply(time,
function(.w) {
length(.w) == 3L && !is.na(.w[3L])
}, logical(1L)))
for (.i in seq_len(.nw)) {
.w <- time[[.i]]
if (length(.w) == 1L) {
if (!allow1) stop("each window must be c(low, high) or c(low, mid, high)", call. = FALSE)
.low[.i] <- NA_real_
.mid[.i] <- as.numeric(.w)
.high[.i] <- NA_real_
} else if (length(.w) == 2L) {
if (.hasNormal) {
.low[.i] <- .w[1]
.mid[.i] <- stats::rnorm(1, .w[1], .w[2])
.high[.i] <- .w[2]
} else if (.has3) {
stop("Cannot mix 2 and 3 element windows", call. = FALSE)
} else {
if (.w[1] > .w[2]) {
stop("window bounds must be ordered c(low, high)", call. = FALSE)
}
.low[.i] <- .w[1]
.mid[.i] <- stats::runif(1, .w[1], .w[2])
.high[.i] <- .w[2]
}
} else if (length(.w) == 3L) {
if (is.na(.w[3L])) {
.low[.i] <- .w[1]
.mid[.i] <- stats::rnorm(1, .w[1], .w[2])
.high[.i] <- .w[2]
} else {
if (.w[1] > .w[2] || .w[2] > .w[3]) stop("window bounds must be ordered c(low, mid, high)", call. = FALSE)
.low[.i] <- .w[1]
.mid[.i] <- .w[2]
.high[.i] <- .w[3]
}
} else {
stop("each window must be c(low, high) or c(low, mid, high)", call. = FALSE)
}
}
if (.hasNormal) {
.randomType <- 3L
} else if (.has3) {
.randomType <- 1L
} else {
.randomType <- 2L
}
return(list(time = .mid, low = .low, high = .high, randomType = .randomType))
}
list(time = as.numeric(time), low = NA_real_, high = NA_real_, randomType = NA_integer_)
}
#' @noRd
.etDoseUntil <- function(until, time, ii, addl, isList) {
if (is.null(until)) return(addl)
if (ii <= 0) stop("'until' requires a positive 'ii'", call. = FALSE)
.tmp <- until - time - ii
if (any(.tmp > 0, na.rm = TRUE)) {
.ratio <- .tmp / ii
.ceil <- ceiling(.ratio)
addl <- ifelse(.ceil == .ratio, .ratio + 1, .ceil)
addl <- as.integer(addl)
} else {
addl <- 0L
if (!isList) {
warning("'time'+'ii' is greater than 'until', no additional doses added", call. = FALSE)
}
}
if (any(addl < 0L, na.rm = TRUE)) {
warning("'until' is before 'time'; setting addl=0", call. = FALSE)
addl[addl < 0L] <- 0L
}
addl
}
#' @noRd
.etDoseValidate <- function(amt, ii, addl, ss, rate) {
if (ii > 0 && ss == 0L && all(addl == 0L, na.rm = TRUE)) {
warning(sprintf(
"'ii' requires non zero additional doses ('addl') or steady state dosing ('ii': %f, 'ss': %d; 'addl': %d), reset 'ii' to zero", # nolint
ii, ss, max(addl, na.rm = TRUE)
), call. = FALSE)
ii <- 0.0
}
if (any(addl > 0L, na.rm = TRUE) && ii == 0.0)
stop("'addl' > 0 requires a positive inter-dose interval ('ii')", call. = FALSE)
if (ss > 0L) {
if (rate < -1.0 && ii == 0.0)
stop("cannot use duration flag (rate=-2) with steady-state dosing", call. = FALSE)
if (ss == 2L && ii == 0.0)
stop("ss=2 requires a positive inter-dose interval ('ii')", call. = FALSE)
if (rate > 0 && ii > 0 && amt == 0)
stop("cannot combine constant infusion (rate>0) with dose interval (ii>0) for steady-state; use ii=0 for constant infusion SS", call. = FALSE) # nolint
}
ii
}
#' Build an observation chunk list (no id column; caller uses .etAddChunk)
#'
#' Returns a sparse named list where scalar fields (evid) are stored once and
#' vector fields (time) hold per-row values. Callers use length(.df$time) for
#' row count, not nrow().
#'
#' @param time numeric vector of sample times, OR a list of c(low,high)
#' or c(low,mid,high) windows.
#'
#' @param cmt compartment name or number (optional scalar)
#'
#' @param id integer vector of ids to attach (optional)
#'
#' @return sparse named list with evid=0L scalar and time vector
#'
#' @noRd
.etObsChunk <- function(time, cmt = NULL, id = NULL) {
.time <- .etWindowTime(time)
.df <- list(evid = 0L, time = .time$time, low = .time$low, high = .time$high)
if (!is.null(cmt)) .df$cmt <- cmt
if (!is.null(id)) .df$id <- as.integer(id)
attr(.df, ".randomType") <- .time$randomType
.df
}
#' Expand a sparse obs chunk list to a full data.frame
#'
#' Replicates scalar fields to match the length of the time vector.
#' @param .sparse sparse list from .etObsChunk
#' @return data.frame with all columns same length
#' @noRd
.etExpandObsChunk <- function(.sparse) {
.n <- length(.sparse$time)
as.data.frame(
lapply(.sparse, function(.v) if (length(.v) == 1L) rep(.v, .n) else .v),
stringsAsFactors = FALSE
)
}
#' Build a dosing chunk data.frame (no id column; caller uses .etAddChunk)
#'
#' @param time numeric start time(s)
#' @param amt numeric dose amount(s)
#' @param evid integer event ID (default 1L)
#' @param cmt compartment name/number (default "(default)")
#' @param ii inter-dose interval (default 0)
#' @param addl additional doses (default 0L)
#' @param ss steady-state flag (default 0L)
#' @param rate infusion rate (default 0)
#' @param dur infusion duration (default 0.0)
#' @param until time of last dose; overrides addl
#' @param nbr.doses number of doses (legacy); sets addl = nbr.doses - 1
#' @param dosing.interval inter-dose interval (legacy alias for ii)
#' @return data.frame
#' @noRd
.etDoseChunk <- function(time = 0, amt, evid = 1L, cmt = "(default)",
ii = 0.0, addl = 0L, ss = 0L, rate = 0.0, dur = 0.0,
until = NULL, nbr.doses = NULL, dosing.interval = NULL) {
if (!is.null(dosing.interval)) ii <- as.numeric(dosing.interval)
if (!is.null(nbr.doses)) addl <- as.integer(nbr.doses) - 1L
.time <- .etWindowTime(time, allow1 = TRUE)
.timeDose <- .time$time
.lowDose <- .time$low
.highDose <- .time$high
.randomType <- .time$randomType
addl <- .etDoseUntil(until, .timeDose, ii, addl, inherits(time, "list"))
ii <- .etDoseValidate(amt, ii, addl, ss, rate)
if (dur > 0 && rate == 0.0) {
rate <- amt / dur
dur <- 0.0
}
if (length(amt) > 1L || length(time) > 1L) {
if (length(.timeDose) == 1L) .timeDose <- rep(.timeDose, length(amt))
if (length(amt) == 1L) amt <- rep(amt, length(.timeDose))
if (length(.timeDose) != length(amt)) stop("'time' and 'amt' must have the same length", call. = FALSE)
}
.res <- data.frame(
time = as.numeric(.timeDose), amt = as.numeric(amt),
evid = as.integer(evid), cmt = as.character(cmt),
ii = as.numeric(ii), addl = as.integer(addl),
ss = as.integer(ss), rate = as.numeric(rate),
dur = as.numeric(dur),
low = as.numeric(.lowDose),
high = as.numeric(.highDose),
stringsAsFactors = FALSE
)
attr(.res, ".randomType") <- .randomType
.res
}
#' Rebuild an rxEt shell after data-frame style mutation
#'
#' @param x rxEt object
#'
#' @param df materialized data.frame from .etMaterialize() after mutation
#'
#' @noRd
.rxEtRebuildShell <- function(x, df) {
.env0 <- .rxEtEnv(x)
.et <- .newRxEt(amountUnits = .env0$units["dosing"], timeUnits = .env0$units["time"])
.env <- .rxEtEnv(.et)
if (nrow(df) > 0L) {
.env$methods$import.EventTable(df)
}
.env$show[names(.env0$show)] <- .env$show[names(.env0$show)] | .env0$show
.env$randomType <- .env0$randomType
.env$canResize <- .env0$canResize
.etAddExtraCols(.env, .etExtraCols(.env0)) # nolint
.et
}
#' @export
dim.rxEt <- function(x) {
dim(as.data.frame(x, all = TRUE))
}
Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.