## This is a list of supported distributions with the number of arguments they currently support.
.errDist <- list(
"dpois" = 1,
"pois" = 1,
"dbinom" = 1:2,
"binom"=1:2,
"dbern" = 1,
"bern" = 1,
"dbeta" = 2, # non-central isn't supported by stan so drop support
"beta" = 2,
"dt" = 1, # non-central isn't supported by stan, so drop
"t" = 1,
##
## "dnbinom"=2:3, ## dnbinom is in R; FIXME: how does ot compare to dneg_binomial
## "dneg_binomial", ## not in base R (but in glnmm2)
##
## Available as external package http://ugrad.stat.ubc.ca/R/library/rmutil/html/BetaBinom.html
## "dbetabinomial", ## not in base R (but in glnmm2)
"add" = 1,
"norm" = 0,
"dnorm" = 0,
"prop" = 1,
"propT" = 1,
"propF" = 2,
"pow" = 2,
"powT" = 2,
"powF"=3,
"tbs" = 1,
"boxCox" = 1,
"tbsYj" = 1,
"yeoJohnson" = 1,
"logn" = 1,
"lnorm" = 1,
"dlnorm" = 1,
"dlogn" = 1,
"logitNorm" = 1:3,
"probitNorm" = 1:3,
"combined1"=0,
"combined2"=0,
"comb1"=0,
"comb2"=0,
"dchisq"=1,
"chisq"=1,
"dexp"=0:1,
"df"=2:3,
"f"=2:3,
"dgeom"=1,
"geom"=1,
# "dhyper"=3,
# "hyper"=3,
"dunif"=0:2,
"unif"=0:2,
"dweibull"=1:2,
"weibull"=1:2,
"cauchy"= 0,
"dcauchy"= 0:2,
"dgamma"=1:2,
"nbinom"=2,
"dnbinom"=2,
"nbinomMu"=2,
"dnbinomMu"=2
)
.errDistsPositive <- c("add", "prop", "propT", "pow", "powT", "logn", "dlogn", "lnorm", "dlnorm", "logitNorm", "probitNorm")
.errUnsupportedDists <- "nlmixrDist"
.errAddDists <- c("add", "prop", "propT", "propF", "norm", "pow", "powT", "powF", "dnorm", "logn", "lnorm", "dlnorm", "tbs", "tbsYj", "boxCox",
"yeoJohnson", "logitNorm", "probitNorm", "combined1", "combined2", "comb1", "comb2", "t", "cauchy", "norm")
.errIdenticalDists <- list(
"lnorm"=c("logn", "dlogn", "dlnorm"),
"dnorm"="norm",
"boxCox"="tbs",
"yeoJohnson"="tbsYj",
"pois"="dpois",
"binom"="dbinom",
"bern"="dbern",
"beta"="dbeta",
"t"="dt",
"combined1"="comb1",
"combined2"="comb2",
"chisq"="dchisq", #6
"f"="df", #8
"geom"="dgeom", #9
"unif"="dunif", #10
"weibull"="dweibull", #11
"cauchy"="dcauchy", #12
"nbinom"="dnbinom",
"nbinomMu"="dnbinomMu")
.errDistArgRanges <- list(
"add"=c(0, Inf),
"prop"=c(0, Inf),
"propT"=c(0, Inf),
"propF"=c(0, Inf),
"pow"=c(0, Inf),
"powT"=c(0, Inf),
"powF"=c(0, Inf),
"pow2"=c(-Inf, Inf),
"powT2"=c(-Inf, Inf),
"powF2"=c(-Inf, Inf),
"lnorm"=c(0, Inf),
"boxCox"=c(-Inf, Inf),
"yeoJohnson"=c(-Inf, Inf),
"pois"=c(0, Inf),
"binom"=c(0, Inf),
"binom2"=c(0, 1),
"nbinom"=c(0, Inf),
"nbinom2"=c(0, 1),
"nbinomMu"=c(0, Inf),
"nbinomMu2"=c(0, Inf),
"bern"=c(0, 1),
"logitNorm"=c(0, Inf),
"probitNorm"=c(0, Inf),
"chisq"=c(0, Inf),
"chisq2"=c(0, Inf),
"dexp"=c(0, Inf),
"f"=c(0, Inf),
"f2"=c(0, Inf),
"f3"=c(0, Inf),
"geom"=c(0, 1),
## "hyper"=c(0, Inf),
## "hyper2"=c(0, Inf),
## "hyper3"=c(0, Inf),
"dunif"=c(-Inf, Inf),
"dunif2"=c(-Inf, Inf),
"weibull"=c(0, Inf),
"weibull2"=c(0, Inf),
"cauchy"=c(-Inf, Inf),
"cauchy2"=c(0, Inf),
"dgamma"=c(0, Inf),
"dgamma2"=c(0, Inf),
"geom"=c(0, 1),
"beta" = c(0, Inf),
"beta2"=c(0, Inf),
"t"=c(0, Inf),
"t2"=c(0, Inf)
)
## the desired outcome for each expression is to capture the condition
## when the multiple endpoint occurs, the lower and upper for the
## transformation (if any) and the error type of the model. The
## errors should be labeled "add" for an additive error and pow, pow2
## for the first and second argument of the power distribution. The
## ini block should have already been processed by lotri, so we have
## some information about the model. The predictions will be replaced
## by rxPred__# for easy replacement with the correct information for
## simulating or estimating in different nlmixr algorithms and
## different rxode2 simulation routines
## Currently you can support the following types of expressions:
##
## f ~ add(add.sd)
##
## This assumes a named DVID of f
##
## or
##
## g ~ add(add.sd) | namedDvid
##
## In this case the named DVID is namedDvid
##
## In the final rxode2 model expression these expressions will look like:
##
## rxPred__1 = f
## rxPred__2 = g
##
## With this change, there is sufficient information to send to the
## mu-referencing routine
#' Change distribution name to the preferred distribution name term
#'
#' This is determined by the internal preferred condition name list
#' `.errIdenticalDists`
#'
#' @param dist This is the input distribution
#' @return Preferred distribution term
#' @author Matthew Fidler
#'
#' @examples
#'
#' rxPreferredDistributionName("dt")
#'
#' rxPreferredDistributionName("add")
#'
#' # can be vectorized
#'
#' rxPreferredDistributionName(c("add","dt"))
#'
#' @export
rxPreferredDistributionName <- function(dist) {
if (length(dist) == 1) {
.names <- names(.errIdenticalDists)
for(.n in .names) {
if (dist == .n) return(.n)
else if (dist %in% .errIdenticalDists[[.n]]) return(.n)
}
dist
} else {
vapply(dist, rxPreferredDistributionName, character(1))
}
}
.incompatibleTransformations <-
list(boxCox=c("yeoJohnson", "lnorm"),
yeoJohnson=c("boxCox", "lnorm"),
lnorm=c("yeoJohnson", "boxCox", "logit", "logitNorm", "probit", "probitNorm", "logit + yeoJohnson"),
logit=c("probit", "probitNorm", "lnorm"),
probit=c("logit", "lnorm"),
"logit + yeoJohnson"=c("boxCox", "probit", "probitNorm", "lnorm"),
"probit + yeoJohnson"=c("boxCox", "logit", "logitNorm", "lnorm"),
"logit + boxCox"=c("yeoJohnson", "probit", "probitNorm", "lnorm"),
"probit + boxCox"=c("yeoJohnson", "logit", "logitNorm", "lnorm"))
.rxTransformCombineLevels <- c(
"boxCox", # 1
"yeoJohnson", # 2
"untransformed", # 3
"lnorm", # 4
"logit", # 5
"logit + yeoJohnson", # 6
"probit", # 7
"probit + yeoJohnson", #8
"logit + boxCox", # 9
"probit + boxCox" # 10
)
.rxTransformHasALambdaParameter <- function(distribution) {
(as.integer(distribution) %in% c(1L, 2L, 6L, 8L, 9L, 10L))
}
.rxTransformHasBounds <- function(distribution) {
(as.integer(distribution) %in% 5:14)
}
.rxAddPropLevels <- c(
"combined1", # 1
"combined2", # 2
"default" # 3
)
.incompatibleAddProp <- list(
combined1="combined2",
combined2="combined1"
)
.rxErrType <- c(
"add", # 1
"prop", # 2
"pow", # 3
"add + prop", # 4
"add + pow", # 5
"none" # 6
)
.rxDistributionType <- c(
"norm", # 1
"pois", # 2
"binom", # 3
"beta", # 4
"t", # 5
"chisq", # 6
"dexp", # 7
"f", # 8
"geom", # 9
"hyper", # 10; hyper will not be supported since all the inputs are integers
"unif", # 11
"weibull", # 12
"cauchy", # 13
"dgamma", # 14
"ordinal", # 15
"LL", # 16
"dnorm", # 17
"nbinom", # 18 size, prob
"nbinomMu" # 19 size, mu
)
#' Demote the error type
#'
#' @param errType Error type factor
#' @return Demoted Error Type
#' @author Matthew Fidler
#' @export
#' @examples
#' rxErrTypeCombine("add") %>%
#' rxErrTypeCombine("prop")
#'
#' # This removes the internal additive error
#' rxErrTypeCombine("add") %>%
#' rxErrTypeCombine("prop") %>%
#' rxDemoteAddErr()
#'
#' # This is used for logitNorm(NA), the additive portion is stripped
#'
#' @keywords internal
rxDemoteAddErr <- function(errType) {
if (inherits(errType, "factor")) {
return(structure(switch(as.character(errType),
add=6L,
"add + prop"=2L,
"add + pow"=3L,
as.integer(errType)),
.Label =.rxErrType,
class="factor"))
} else if (inherits(errType, "rxCombinedErrorList")) {
return(.rxTransformCombineListOrChar(list(transform=errType$transform,
errType=rxDemoteAddErr(errType$errType),
errTypeF=errType$errTypeF,
addProp=errType$addProp)))
}
}
.incompatibleErrType <- list(prop=c("pow", "powT", "powF"),
propT=c("pow", "powT", "powF"),
propF=c("pow", "powT", "powF"),
pow=c("prop", "propT", "propF"),
powT=c("prop", "propT", "propF"),
powF=c("prop", "propT", "propF"))
.rxErrTypeF <- c(
"untransformed", # 1
"transformed", # 2
"f", # 3
"none")
.incompatibleErrTypeF <- list(prop=c("propT", "propF", "powT", "powF"),
propT=c("prop", "propF", "pow", "powF"),
propF=c("prop", "propT", "pow", "powT"),
pow=c("propF", "propT", "powF", "powT"),
powT=c("prop", "propF", "pow", "powF"),
powF=c("prop", "propT", "pow", "powT"))
.incompatibleErr <- function(err1, err2) {
.errs <- sort(c(err1, err2))
paste0("`", .errs[1], "` and `", .errs[2], "` are incompatible")
}
#' Combine error types to get the model F type
#'
#' @param newAddProp New error type
#' @param oldAddProp old error type
#' @return factor of the error type function OR a string indicating a syntax error
#' @author Matthew Fidler
#' @noRd
.rxCombineAddProp <- function(newAddProp, oldAddProp="default") {
.tmp <- as.character(oldAddProp)
.w <- which(names(.incompatibleAddProp) == .tmp)
if (length(.w) == 1L) {
if (newAddProp %in% .incompatibleAddProp[[.w]]) {
return(.incompatibleErr(.tmp, newAddProp))
}
}
structure(switch(newAddProp,
combined1 =1L,
combined2=2L,
default=3L,
ifelse(inherits(oldAddProp, "character"),
switch(oldAddProp,
combined1 =1L,
combined2=2L,
default=3L,
3L),
as.integer(oldAddProp))),
.Label=.rxAddPropLevels,
class="factor")
}
#' Combine error types to get the model F type
#'
#' @param newErrTypeF New error type
#' @param oldErrTypeF old error type
#' @return factor of the error type function OR a string indicating a syntax error
#' @author Matthew Fidler
#' @noRd
.rxCombineErrTypeF <- function(newErrTypeF, oldErrTypeF="none") {
.tmp <- as.character(oldErrTypeF)
.w <- which(names(.incompatibleErrTypeF) == .tmp)
if (length(.w) == 1L) {
if (newErrTypeF %in% .incompatibleErrTypeF[[.w]]) {
return(.incompatibleErr(.tmp, newErrTypeF))
}
}
structure(switch(newErrTypeF,
prop =1L,
propT=2L,
propF=3L,
pow=1L,
powT=2L,
powF=3L,
ifelse(inherits(oldErrTypeF, "character"),
switch(oldErrTypeF,
prop =1L,
propT=2L,
propF=3L,
pow=1L,
powT=2L,
powF=3L,
4L),
as.integer(oldErrTypeF))),
.Label=.rxErrTypeF,
class="factor")
}
#' Combine error model
#'
#' @param newErrType New error portion combined with current proportion
#' @param oldErrType Old error information
#' @return A factor for error type OR a string with error information
#' @author Matthew Fidler
#' @noRd
.rxCombineErrType <- function(newErrType, oldErrType="none") {
.tmp <- as.character(oldErrType)
.w <- which(names(.incompatibleErrType) == .tmp)
if (length(.w) == 1L) {
if (newErrType %in% .incompatibleErrType[[.w]]) {
return(.incompatibleErr(.tmp, newErrType))
}
}
structure(switch(.tmp,
add=switch(newErrType,
prop=4L,
propT=4L,
propF=4L,
pow=5L,
powT=5L,
powF=5L,
1L),
prop=switch(newErrType,
add=4L,
probit=4L,
probitNorm=4L,
logit=4L,
logitNorm=4L,
lnorm=4L,
2L),
pow=switch(newErrType,
add=5L,
probit=5L,
probitNorm=5L,
logit=5L,
logitNorm=5L,
lnorm=5L,
3L),
none=structure(switch(newErrType,
add=1L,
lnorm=1L,
logit=1L,
logitNorm=1L,
probit=1L,
probitNorm=1L,
prop=2L,
propT=2L,
propF=2L,
pow=3L,
powT=3L,
powF=3L,
6L)),
as.integer(oldErrType)),
.Label=.rxErrType,
class="factor")
}
#' Combine transformations
#'
#' @param newTransform New error structure added together
#' @param oldTransform Old transformation added together
#' @return A factor describing the transformation type
#' @author Matthew Fidler
#' @noRd
.rxCombineTransform <- function(newTransform, oldTransform="untransformed") {
.tmp <- as.character(oldTransform)
.w <- which(names(.incompatibleTransformations) == .tmp)
if (length(.w) == 1L) {
if (newTransform %in% .incompatibleTransformations[[.w]]) {
return(.incompatibleErr(.tmp, newTransform))
}
}
structure(switch(.tmp,
boxCox=switch(newTransform,
logitNorm=9L,
probitNorm=10L,
1L),
yeoJohnson=switch(newTransform,
logitNorm=6L,
probitNorm=8L,
2L),
logit=switch(newTransform,
boxCox=9L,
yeoJohnson=6L,
5L),
probit=switch(newTransform,
boxCox=10L,
yeoJohnson=8L,
7L),
untransformed=structure(switch(newTransform,
boxCox=1L,
yeoJohnson=2L,
lnorm=4L,
logitNorm=5L,
probitNorm=7L,
3L),
.Label=.rxTransformCombineLevels,
class="factor"),
as.integer(oldTransform)),
.Label=.rxTransformCombineLevels,
class="factor")
}
#' This is a wrapper to make sure that the transformation combination returns the correct value
#'
#' @param inputList This is either an input list or character vector of length one
#' @return Either a complete list, or a character vector which represents the parsed error that was encountered
#' @author Matthew Fidler
#' @noRd
.rxTransformCombineListOrChar <- function(inputList) {
if (inherits(inputList, "character")) return(inputList)
.err <- NULL
for (i in names(inputList)) {
if (inherits(inputList[[i]], "character")) {
.err <- c(.err, inputList[[i]])
}
}
if (is.null(.err)) {
.ret <- inputList
class(.ret) <- "rxCombinedErrorList"
return(.ret)
} else {
return(paste(.err, collapse="\n"))
}
}
#' Combine transformations and error structures
#'
#' Combine error information to figure out what transformation is
#' being applied for the current endpoint
#'
#' @param oldErrType This is the old transformation, by default is
#' zero representing no prior transformation. This parameter is
#' first to allow piping. When the parameter `addTransform` is
#' missing and `oldErrType` is a character value, this functions
#' swaps `oldErrType` and `addTransform` and assigns
#' `oldErrType` to zero assuming that there is no prior
#' distribution.
#'
#' @param newErrType This is the new distribution that is being
#' "added" to the current transformation. These assumes the inputs
#' are in the preferred distribution name, as determined by
#' `rxPreferredDistributionName()`
#'
#' @return The new transformation as a factor
#'
#' @author Matthew Fidler
#'
#' @examples
#'
#' rxErrTypeCombine("probitNorm")
#'
#' rxErrTypeCombine("probitNorm") %>%
#' rxErrTypeCombine("boxCox")
#'
#'
#' @export
#' @keywords internal
rxErrTypeCombine <- function(oldErrType, newErrType) {
if (missing(newErrType) && inherits(oldErrType, "character")) {
return(.rxTransformCombineListOrChar(list(transform=.rxCombineTransform(oldErrType),
errType=.rxCombineErrType(oldErrType),
errTypeF=.rxCombineErrTypeF(oldErrType),
addProp=.rxCombineAddProp(oldErrType))))
} else if (inherits(oldErrType, "rxCombinedErrorList")) {
return(.rxTransformCombineListOrChar(list(transform=.rxCombineTransform(newErrType, oldErrType$transform),
errType=.rxCombineErrType(newErrType, oldErrType$errType),
errTypeF=.rxCombineErrTypeF(newErrType, oldErrType$errTypeF),
addProp=.rxCombineAddProp(newErrType, oldErrType$addProp))))
} else {
stop("old transform not in the proper format", call.=FALSE)
}
}
#' Checks to see if an expression is numeric
#'
#' @param expression quoted expression
#' @param env Environment to store result in `env$.numeric`
#' @return TRUE if this is an expression containing a positive or negative expression or FALSE if it is an expression that doesn't contain an expression.
#' @author Matthew Fidler
#' @noRd
.is.numeric <- function(expression, env) {
if (is.numeric(expression)) {
env$.numeric <- expression
return(TRUE)
} else if (length(expression) == 2L) {
if (identical(expression[[1]], quote(`-`)) &&
is.numeric(expression[[2]])) {
env$.numeric <- -(expression[[2]])
return(TRUE)
} else if (identical(expression[[1]], quote(`+`)) &&
is.numeric(expression[[2]])) {
env$.numeric <- expression[[2]]
return(TRUE)
}
}
return(FALSE)
}
.allowDemoteAddDistributions <- c("lnorm", "probitNorm", "logitNorm")
.namedArgumentsToPredDf <- list(
add="a",
lnorm="a",
boxCox="lambda",
yeoJohnson="lambda",
pow=c("b", "c"),
powT=c("b", "c"),
powF=c("b", "c", "f"),
prop="b",
propT="b",
propF=c("b", "f"),
t=c("d", "e"),
pois=c("a"),
binom=c("a", "b"),
beta=c("a", "b", "c"),
chisq=c("a", "b"), #6
dexp=c("a"), #7
f=c("a", "b", "c"), #8
geom=c("a"), #9
# hyper=c("a", "b", "c"), #10
unif=c("a", "b"), #11
weibull=c("a", "b"), #12
cauchy=c("a", "b"),
dgamma=c("a", "b"),
nbinom=c("a", "b"),
nbinomMu=c("a", "b")
)
.allowEstimatedParameters <- "ordinal"
#' This handles the error distribution for a single argument.
#'
#' @param argumentNumber The argument number of the distribution being processed
#' @param funName Function name string of the distribution name
#' @param expression Function expression (including the function name)
#' @param env Environment where the names and calculations are made for the user interface.
#' @return None, called for the side effects.
#' @author Matthew Fidler
#' @noRd
.errHandleSingleDistributionArgument <- function(argumentNumber, funName, expression, env) {
.cur <- expression[[argumentNumber + 1]]
.isLogitOrProbit <- (funName %in% c("logitNorm", "probitNorm"))
if (is.name(.cur)) {
.curName <- as.character(.cur)
.w <- which(env$df$name == .curName)
if (.isLogitOrProbit && argumentNumber > 2) {
env$err <- c(env$err,
paste0("'", funName, "()' requires numeric bounds"))
} else if (length(.w) == 1L) {
.df <- env$df
.df$err[.w] <- ifelse(argumentNumber == 1, funName, paste0(funName, argumentNumber))
if (!is.na(.df$condition[.w])) {
assign("dupErr", c(env$dupErr, .df$name[.w]), envir=env)
}
.df$condition[.w] <- env$curCondition
assign("df", .df, envir=env)
assign("lastDistAssign", .curName, envir=env)
} else {
.w <- which(names(.namedArgumentsToPredDf) == funName)
if (length(.w) == 1) {
.curLst <- .namedArgumentsToPredDf[[.w]]
if (argumentNumber <= length(.curLst)) {
assign(.curLst[argumentNumber], .curName, envir=env)
return(NULL)
}
}
if (env$estNotAllowed) {
if (!(funName %in% .allowEstimatedParameters)) {
env$err <- c(env$err,
paste0("in the error expression, the variable '", .curName, "' must be estimated, not calculated"))
}
}
}
} else if (.is.numeric(.cur, env)) {
if (.isLogitOrProbit) {
if (argumentNumber == 2 || argumentNumber == 3) {
env$trLimit[argumentNumber - 1] <- env$.numeric
}
}
} else if (is.na(.cur)) {
if (argumentNumber == 1 && funName %in% .allowDemoteAddDistributions) {
env$needToDemoteAdditiveExpression <- TRUE
} else {
env$err <- c(env$err,
paste0("NA in '", funName, "()' cannot be used here"))
}
}
}
#' Checks and modifies the error term as needed in the user interface function.
#'
#' @param funName Name of the distributional error
#' @param expression The R expression (including the function name)
#' @param env The environment that holds information about the error structure
#' @return Nothing; Called for the side effects
#' @author Matthew Fidler
#' @noRd
.errHandleSingleDistributionTerm <- function(funName, expression, env) {
.nargs <- length(expression) - 1
.doIt <- FALSE
if (funName == "ordinal") {
if (.nargs == 0) {
assign("err", c(env$err,
paste0("ordinal errors require at least 1 argument (i.e. err ~ c(err1))", envir=env)))
return(invisible())
}
.doIt <- TRUE
} else {
.errDistArgs <- .errDist[[funName]]
.doIt <- .nargs %in% .errDistArgs
}
if (.doIt) {
if (.nargs > 0) {
lapply(seq(1, .nargs), .errHandleSingleDistributionArgument, funName=funName, expression=expression, env=env)
}
env$errTypeInfo <- rxErrTypeCombine(env$errTypeInfo, funName)
env$needsToBeAnErrorExpression <- TRUE
} else {
.min <- range(.errDistArgs)
.max <- .min[2]
.min <- .min[1]
if (.min == .max) {
assign("err", c(env$err,
paste0("`", funName, "` requires ",
.max, " argument(s), you specified ", .nargs)), envir=env)
} else {
assign("err", c(env$err,
paste0("`", funName, "` requires ",
.min, " to ", .max, " argument(s), you specified ", .nargs)),
envir=env)
}
if (.nargs > 0) {
lapply(seq(1, .nargs), .errHandleSingleDistributionArgument, funName=funName, expression=expression, env=env)
}
}
}
#' This handles a function that is not an error term.
#'
#' @param funName Function that is being called, as a character
#' @param expression Expression (including function)
#' @param env Environment that stores the information about errors
#' @return Nothing, called for the side effects
#' @author Matthew Fidler
#' @noRd
.errHandleSingleTerm <- function(funName, expression, env) {
env$hasNonErrorTerm <- TRUE
}
#' Handle the error structure term
#'
#' @param expression The error structure term
#'
#' @param env The environment that holds information about the error
#' structure
#'
#' @return Nothing, called for the side efects
#' @author Matthew Fidler
#' @noRd
.errHandleErrorStructure <- function(expression, env) {
.isAdd <- FALSE
if (is.name(expression) || is.atomic(expression)) {
.isAdd <- FALSE
} else if (identical(expression[[1]], quote(`+`))) {
.isAdd <- TRUE
}
if (.isAdd) {
env$isAnAdditiveExpression <- TRUE
.errHandleErrorStructure(expression[[2]], env)
.errHandleErrorStructure(expression[[3]], env)
} else if (env$isAnAdditiveExpression) {
.currErr <- rxPreferredDistributionName(deparse1(expression[[1]]))
if (.currErr %in% .errAddDists) {
if (.currErr == "t") {
if (env$distribution == "cauchy") {
stop("you cannot combine 't' and 'cauchy' distributions")
}
if (env$distribution == "dnorm") {
stop("you cannot combine 't' and 'dnorm' distributions")
}
env$distribution <- "t"
} else if (.currErr == "cauchy") {
if (env$distribution == "t") {
stop("you cannot combine 't' and 'cauchy' distributions")
}
if (env$distribution == "dnorm") {
stop("you cannot combine 'dnorm' and 'cauchy' distributions")
}
env$distribution <- "cauchy"
} else if (.currErr == "dnorm") {
if (env$distribution == "t") {
stop("you cannot combine 't' and 'dnorm' distributions")
}
if (env$distribution == "cauchy") {
stop("you cannot combine 'cauchy' and 'dnorm' distributions")
}
env$distribution <- "dnorm"
}
.errHandleSingleDistributionTerm(.currErr, expression, env)
} else if (.currErr %in% names(.errDist)) {
assign("err", c(env$err,
paste0("`", .currErr, "` is incorrectly added to an error expression")), envir=env)
} else {
.errHandleSingleTerm(.currErr, expression, env)
}
} else {
if (is.name(expression) || is.atomic(expression)) {
.currErr <- deparse1(expression)
.isC <- FALSE
.isErrDist <- FALSE
} else {
.currErr <- deparse1(expression[[1]])
.isC <- .currErr == "c"
.isErrDist <- .currErr %in% names(.errDist)
}
if (.isC) {
env$distribution <- "ordinal"
.errHandleSingleDistributionTerm("ordinal", expression, env)
} else if (.isErrDist) {
.currErr <- rxPreferredDistributionName(.currErr)
env$distribution <- .currErr
.errHandleSingleDistributionTerm(.currErr, expression, env)
} else {
.errHandleSingleTerm(.currErr, expression, env)
}
}
}
#' Handle the right hand side conditional expression (if it exists)
#'
#' @param expression A right hand side of the tilde equation
#' @param env Environment for storing information about the expression
#' @return An expression without a conditional statement.
#'
#' @details
#'
#' In addition to stripping the conditional statement out of the
#' expression, the environment is modified when a conditional
#' expression is present. First, the environment variable
#' `needsToBeAnErrorExpression` is changed to `TRUE`. Second, the
#' expression `curCondition` is modified to match the information
#' within the conditional statement.
#'
#' @author Matthew Fidler
#' @noRd
.errHandleCondition <- function(expression, env) {
if (is.name(expression) || is.atomic(expression)) return(expression)
if (identical(expression[[1]], quote(`|`))) {
env$needsToBeAnErrorExpression <- TRUE
env$curCondition <- deparse1(expression[[3]])
return(expression[[2]])
}
expression
}
#' Handle LL equivalent for ll or `linCmt()` statements for lhs
#'
#' @param expression Left handed side of the equation
#'
#' @param env Environment for storing information about the error
#' structure
#'
#' @return Nothing called for side effects
#'
#' @details
#'
#' The takes the expression
#'
#' ll(var) ~ log(...)
#'
#' And strips the `ll` and sets the flag `env$ll` to `TRUE`
#'
#' Otherwise it leaves the `expression` alone and returns the value
#'
#' This takes the expression:
#'
#' linCmt() ~ add() ...
#'
#' And returns a `rxLinCmt` as the lhs value and sets the linCmt flag to TRUE
#'
#'
#' @author Matthew Fidler
#' @noRd
.errHandleLlOrLinCmt <- function(expression, env) {
if (is.call(expression)) {
if (identical(expression[[1]], quote(`ll`)) &&
length(expression) == 2L) {
env$ll <- TRUE
return(expression[[2]])
} else if (identical(expression[[1]], quote(`linCmt`))) {
env$linCmt <- TRUE
return(quote(`rxLinCmt`))
} else {
stop("the left handed side of the error expression (function: '", as.character(expression[[1]]), "') can only be functions with 'linCmt' or 'll'",
call.=FALSE)
}
}
expression
}
#' Handle the error expressions
#'
#' @param expression Single tilde error expression
#' @param env Environment with initial estimate data.frame
#' @return nothing, called for side effects
#' @author Matthew Fidler
#' @noRd
.errHandleTilde <- function(expression, env) {
env$ll <- FALSE
env$estNotAllowed <- TRUE
env$linCmt <- FALSE
.left <- .errHandleLlOrLinCmt(expression[[2]], env)
env$trLimit <- c(-Inf, Inf)
env$a <- env$b <- env$c <- env$d <- env$e <- env$f <- env$lambda <- NA_character_
env$curCondition <- env$curVar <- deparse1(.left)
env$hasNonErrorTerm <- FALSE
env$needsToBeAnErrorExpression <- FALSE
env$needToDemoteAdditiveExpression <- FALSE
.right <- .errHandleCondition(expression[[3]], env)
env$isAnAdditiveExpression <- FALSE
env$errTypeInfo <- rxErrTypeCombine("")
env$distribution <- "norm"
.errHandleErrorStructure(.right, env)
if (inherits(env$errTypeInfo, "character")) {
env$err <- c(env$err, env$errTypeInfo)
env$errTypeInfo <- rxErrTypeCombine("")
} else if (env$needToDemoteAdditiveExpression) {
env$errTypeInfo <- rxDemoteAddErr(env$errTypeInfo)
}
if (env$hasNonErrorTerm && env$needsToBeAnErrorExpression) {
assign("errGlobal", c(env$errGlobal, "cannot mix error expression with algebraic expressions"),
envir=env)
} else if (env$hasNonErrorTerm) {
if (env$ll) {
env$distribution <- "LL"
env$predDf <- rbind(env$predDf,
data.frame(cond=env$curCondition, var=env$curVar, dvid=env$curDvid,
trLow=env$trLimit[1], trHi=env$trLimit[2],
transform=env$errTypeInfo$transform,
errType=env$errTypeInfo$errType,
errTypeF=env$errTypeInfo$errTypeF,
addProp=env$errTypeInfo$addProp,
distribution=factor(env$distribution, levels=.rxDistributionType),
line=env$line,
a=env$a,
b=env$b,
c=env$c,
d=env$d,
e=env$e,
f=env$f,
lambda=env$lambda,
linCmt=env$linCmt))
env$curDvid <- env$curDvid + 1L
}
} else if (!env$hasNonErrorTerm) {
if (env$ll) {
assign("errGlobal", c(env$errGlobal, "a -2 log-likelihood expression cannot use abbreviated error codes like add() + prop() "),
envir=env)
} else {
if (!(env$distribution %in% .rxDistributionType)) {
env$distribution <- "norm"
}
.tmp <- data.frame(cond=env$curCondition, var=env$curVar, dvid=env$curDvid,
trLow=env$trLimit[1], trHi=env$trLimit[2],
transform=env$errTypeInfo$transform,
errType=env$errTypeInfo$errType,
errTypeF=env$errTypeInfo$errTypeF,
addProp=env$errTypeInfo$addProp,
distribution=factor(env$distribution, levels=.rxDistributionType),
line=env$line,
a=env$a,
b=env$b,
c=env$c,
d=env$d,
e=env$e,
f=env$f,
lambda=env$lambda,
linCmt=env$linCmt)
env$predDf <- rbind(env$predDf, .tmp)
env$curDvid <- env$curDvid + 1L
}
}
}
#' Add the Error Names
#'
#' @param errs Input endpoints
#' @return The endpoints that the user enters
#' @author Matthew L. Fidler
#' @noRd
.userEndpointNames <- function(endpoints) {
vapply(endpoints, function(e) {
if (e == "rxLinCmt") {
return("linCmt()")
}
e
}, character(1), USE.NAMES=FALSE)
}
#' Check for error exceptions
#'
#' @param env Environment to check for error exceptions
#' @return Nothing called for side effects
#' @author Matthew L. Fidler
#' @noRd
.checkForMissingOrDupliacteInitials <- function(env) {
.predDf <- env$predDf
.iniDf <- env$iniDf
.err <- env$dupErr
for (.i in seq_along(.predDf$cond)) {
if (!any(!is.na(.predDf[.i, c("a", "b", "c", "d", "e", "f", "lambda")]))) {
if (!(.predDf[.i, "distribution"] %in% c("LL", "ordinal"))) {
.cnd <- .predDf$cond[.i]
.w <- which(.iniDf$condition == .cnd)
if (length(.w) == 0L) {
if (!any(.errDist[[paste(.predDf$distribution[.i])]] == 0)) {
.err <- c(.err, .cnd)
}
}
}
}
}
if (length(.err) > 0) {
stop("endpoint parameter(s) missing, duplicated, or defined with '~': ", paste(.userEndpointNames(.err), collapse=", "),
call.=FALSE)
}
}
#' Get a blank, theta1, or eta1 initialization block for iniDf
#'
#' @param type type of initialization block to return
#' @return A data.frame with the appropriate number/type of columns.
#'
#' For type="empty", the data.frame will have 0 rows but all the correct types.
#'
#' For type="theta", the data.frame will have 1 row with the correct
#' types and default values. The "name" and "est" will likely need to
#' be updated.
#'
#' For type="eta", the data.frame will have 1 row with the correct
#' types and default values for the a single eta being added. The
#' "name" and "est" will likely need to be updated.
#'
#'
#' @export
#' @author Matthew L. Fidler
#' @keywords internal
#' @examples
#'
#' .rxBlankIni("empty")
#'
#' .rxBlankIni("theta")
#'
#' .rxBlankIni("eta")
#'
.rxBlankIni <- function(type=c("empty", "theta", "eta")) {
type <- match.arg(type)
if (type == "empty") {
data.frame(ntheta=integer(0),
neta1=integer(0),
neta2=integer(0),
name=character(0),
lower=numeric(0),
est=numeric(0),
upper=numeric(0),
fix=logical(0),
err=character(0),
label=character(0),
stringsAsFactors=FALSE)
} else if (type == "theta") {
data.frame(ntheta=1L,
neta1=NA_integer_,
neta2=NA_integer_,
name=NA_character_,
lower=-Inf,
est=0,
upper=Inf,
fix=FALSE,
err=NA_character_,
label=NA_character_,
stringsAsFactors=FALSE)
} else {
data.frame(ntheta=NA_integer_,
neta1=1L,
neta2=1L,
name=NA_character_,
lower=0,
est=0.1,
upper=Inf,
fix=FALSE,
err=NA_character_,
label=NA_character_,
stringsAsFactors=FALSE)
}
}
#' Process the errors in the quoted expression
#'
#' @param x Quoted expression for parsing
#' @param ini Initialization block
#' @param linCmtSens Type of linCmt sensitivity
#' @param verbose verbose
#' @param checkMissing Check for missing arguments in the ini({}) block
#' @param df lotri data.frame of estimates
#' @return Environment with error information setup.
#' @author Matthew Fidler
#' @examples
#' lmat <- lotri({
#' ## You may label each parameter with a comment
#' tka <- 0.45 # Log Ka
#' tcl <- log(c(0, 2.7, 100)) # Log Cl
#' ## This works with interactive models
#' ## You may also label the preceding line with label("label text")
#' tv <- 3.45; label("log V")
#' tvp <- 3.45; label("log V")
#' cl.wt <- 0.1
#' v.wt <- 0.1
#' cl.sex <- 0.1
#' v.sex <- 0.1
#' cl.age <- 0.1
#' v.age <- 0.1
#' vp.wt <- 1
#' vp.sex <- 1
#' vp.age <- 1
#' ## the label("Label name") works with all models
#' eta.ka ~ 0.6
#' eta.cl ~ 0.3
#' eta.v ~ 0.1
#' add.sd <- 0.7
#'})
#'
#'
#' iniDf <- as.data.frame(lmat)
#'
#' .errProcessExpression()
#' @noRd
.errProcessExpression <- function(x, ini,
linCmtSens = c("linCmtA", "linCmtB", "linCmtC"),
verbose=FALSE, checkMissing=TRUE, mv=rxUdfUiMv()) {
on.exit({
.udfUiEnv$num <- 1L
.udfUiEnv$iniDf <- NULL
.udfUiEnv$lhs <- NULL
.udfUiEnv$parsing <- FALSE
})
.udfUiEnv$parsing <- TRUE
# ntheta neta1 neta2 name lower est upper fix err label
# backTransform condition trLow trHi
.env <- new.env(parent=emptyenv())
.env$uiUseData <- FALSE
.env$uiUseMv <- FALSE
rxUdfUiEst(NULL)
rxUdfUiMv(mv)
.env$rxUdfUiCount <- new.env(parent=emptyenv())
.env$before <- list()
.env$after <- list()
.env$eta <- dimnames(ini)[[1]]
.env$top <- TRUE
if (!inherits(ini, "lotriFix") && inherits(ini, "matrix")) {
class(ini) <- c("lotriFix", class(ini))
}
.env$df <- as.data.frame(ini)
.env$dupErr <- NULL
.env$err <- NULL
.env$errGlobal <- NULL
# Add error structure like nlmixr ui had before transitioning to rxode2
if (length(.env$df$ntheta) > 0) {
.env$df$err <- NA_character_
} else {
.env$df$err <- character(0)
}
#.env$df$trLow <- .env$df$trHi <- NA_real_
.env$curDvid <- 1L
# Pred df needs to be finalized with compartment information from parsing the raw rxode2 model
.env$predDf <- NULL
.env$lastDistAssign <- ""
if (is.call(x)) {
if (.env$top && identical(x[[1]], quote(`{`))) {
.env$top <- FALSE
.y <- x[-1]
.env$lstChr <- character(length(.y))
.env$lstErr <- vector(length(.y), mode="list")
.env$lstExpr <- vector(length(.y), mode="list")
.env$hasErrors <- FALSE
.i <- 1L
.y <- lapply(seq_along(.y), function(i) {
.y[[i]]
})
while(.i <= length(.y)) {
.env$line <- .i
if (identical(.y[[.i]][[1]], quote(`~`))) {
.errHandleTilde(.y[[.i]], .env)
} else {
.env$redo <- FALSE
.cur <- .y[[.i]]
if (length(.cur) >= 3 && identical(.cur[[1]], quote(`<-`))) {
.env$lhs <- .cur[[2]]
} else if (length(.cur)>= 3 && identical(.cur[[1]], quote(`=`))) {
.env$lhs <- .cur[[2]]
} else {
.env$lhs <- NULL
}
.cur <- .handleUdfUi(.cur, .env)
.len <- length(.y)
.y <- c(lapply(seq_len(.i - 1),
function(i) {
.y[[i]]
}), .env$before, .cur, .env$after,
lapply(seq_len(.len - .i),
function(i) {
.y[[i + .i]]
}))
if (length(.y) != .len) {
# Update the lengths of lstChr, lstErr, lstExpr
.len <- length(.env$before) + length(.env$after)
.env$lstChr <- c(.env$lstChr, character(.len))
.env$lstErr <- c(.env$lstErr, vector(.len, mode="list"))
.env$lstExpr <- c(.env$lstExpr, vector(.len, mode="list"))
.env$before <- list()
.env$after <- list()
# redo the parsing since the length of the expression has changed
next
} else if (.env$redo) {
next
}
}
.env$lstChr[[.i]] <- deparse1(.y[[.i]])
.env$lstExpr[[.i]] <- .y[[.i]]
if (!is.null(.env$err)) {
.env$lstErr[[.i]] <- paste(paste(" ", .env$err), collapse="\n")
.env$err <- NULL
.env$hasErrors <- TRUE
}
.i <- .i + 1L
}
.env$iniDf <- .env$df
if (is.null(.env$predDf)) {
## .env$errGlobal <- c(.env$errGlobal,
## "there must be at least one prediction in the model({}) block. Use `~` for predictions")
} else if (length(.env$predDf[, 1]) == 0L) {
.env$predDf <- NULL
## .env$errGlobal <- c(.env$errGlobal,
## "there must be at least one prediction in the model({}) block. Use `~` for predictions")
}
if (!is.null(.env$errGlobal)) {
stop(paste(.env$errGlobal, collapse="\n"), call.=FALSE)
}
if (!is.null(.env$predDf)) {
.lstChr <- .env$lstChr
.lines <- .env$predDf$line
.w <- which(.env$predDf$distribution == "LL")
if (length(.w) > 0) {
.lstExpr <- .env$lstExpr
.w2 <- .env$predDf$line[.w]
.lstChr <- vapply(seq_along(.lstExpr),
function(i) {
.cur <- .lstExpr[[i]]
if (i %in% .w2) {
paste0("rxLL ~ ", deparse1(.cur[[3]]))
} else {
deparse1(.cur)
}
}, character(1), USE.NAMES=FALSE)
.lines <- .lines[-.w]
if (length(.lines) > 0) {
.lstChr <- .lstChr[-.lines]
}
} else {
.lstChr <- .lstChr[-.lines]
}
if (length(.lstChr) == 0) {
stop("a rxode2 ui model must have more than error definition(s) in the `model({})` block",
call.=FALSE)
}
if (any(.env$predDf$linCmt)) {
.env$mv0 <- rxModelVars(paste(c(.lstChr, "rxLinCmt ~ linCmt()"), collapse="\n"))
} else {
.env$mv0 <- rxModelVars(paste(.lstChr, collapse="\n"))
}
} else {
.env$mv0 <- rxModelVars(paste(.env$lstChr, collapse="\n"))
}
if (isTRUE(.env$uiUseMv) && is.null(mv)) {
# ui function requests model variables, so re-process
on.exit({
rxUdfUiMv(NULL)
})
return(.errProcessExpression(x=x, ini=ini,
linCmtSens = linCmtSens,
verbose=verbose, checkMissing=checkMissing,
mv=.env$mv0))
}
.env$errParams0 <- rxUiGet.errParams(list(.env, TRUE))
if (.Call(`_rxode2_isLinCmt`) == 1L) {
.env$.linCmtM <- rxNorm(.env$mv0)
.ini <- .env$mv0$ini
.ini <- which(!is.na(.ini))
.vars <- c(.env$mv0$params, .env$mv0$lhs, .env$mv0$slhs, names(.ini))
.env$mvL <- rxGetModel(.Call(
`_rxode2_linCmtGen`,
length(.env$mv0$state),
.vars,
setNames(
c("linCmtA" = 1L, "linCmtB" = 2L,
"linCmtC" = 3L
)[match.arg(linCmtSens)],
NULL
), verbose
))
} else {
.env$mvL <- NULL
}
.env$curCmt <- length(.env$mv0$state)
.env$extraCmt <- NULL
if (!is.null(.env$predDf)) {
.env$predDf$cmt <- vapply(seq_along(.env$predDf$line),
function(i) {
.cmtName <- .env$predDf$cond[i]
.w <- which(.cmtName == .env$mv0$state)
if (length(.w) == 1L) return(.w)
assign("curCmt", .env$curCmt + 1L, envir=.env)
assign("extraCmt", c(.env$extraCmt,
paste0("cmt(", .cmtName, ")")),
envir=.env)
.env$curCmt
}, integer(1))
}
.env$extraDvid <- paste0("dvid(", paste(.env$predDf$cmt, collapse = ","), ")")
# Cleanup the environment
.rm <- intersect(c("curCondition", "curDvid", "curVar", "df",
"errTypeInfo", "err", "hasNonErrorTerm", "isAnAdditiveExpression",
"lastDistAssign", "line", "needsToBeAnErrorExpression",
"needToDemoteAdditiveExpression",
"top", "trLimit", ".numeric", "a", "b", "c", "d", "e", "f", "lambda",
"curCmt", "errGlobal", "linCmt", "ll", "distribution", "rxUdfUiCount", "before", "after",
"lhs"),
ls(envir=.env, all.names=TRUE))
if (length(.rm) > 0) rm(list=.rm, envir=.env)
if (checkMissing) .checkForMissingOrDupliacteInitials(.env)
return(.env)
}
}
return(invisible(NULL))
}
#' Determine if expression is a rxode2 error expression
#'
#' Currently only handles simple error expressions, not ll(var) ~ ...
#'
#' @param expr Expression to test
#' @param uiEnv Return the uiEnv for the single expression
#' @return Boolean that says if the expression is an error expression or not
#' @author Matthew L. Fidler
#' @examples
#'
#' .isErrorExpression(quote(ipre~add(add.sd)))
#' .isErrorExpression(quote(ipre~add(add.sd)+3))
#'
#' @noRd
.isErrorExpression <- function(expr, uiEnv=FALSE) {
if (!identical(expr[[1]], quote(`~`))) return(FALSE)
if (identical(expr[[2]], quote(linCmt()))) {
.pre <- "linCmt()"
.var <- "cl=abc\nv=d\nrxDummyVarNotUsedInModel"
.lin <- TRUE
} else {
.expr2 <- expr[[2]]
if (length(.expr2) == 2L) {
if (identical(.expr2[[1]], quote(`-`)) &&
is.name(.expr2[[2]])) {
return(TRUE)
}
}
.pre <- allNames(.expr2)
if (length(.pre) != 1L) return(FALSE)
.var <- .pre
.lin <- FALSE
}
.mod <- eval(parse(text=paste0("quote({", .var, "=1+2\n", deparse1(expr), "})")))
.ini <- as.data.frame(eval(parse(text=paste0("lotri({\n",
paste(paste(allNames(expr[[3]]), "<- 1"), collapse="\n"),
"\n})"))))
.env <- try(.errProcessExpression(.mod, .ini, checkMissing=FALSE), silent=TRUE)
if (inherits(.env, "try-error")) return(FALSE)
if (uiEnv) return(.env)
ifelse(.lin, "rxLinCmt" == .env$predDf$var, .pre == .env$predDf$var)
}
#' Is Normal, Cauchy or t distribution model specification?
#'
#' @param expr Expression
#'
#' @return TRUE if this is a normal/t/cauchy model, FALSE otherwise
#' @author Matthew L. Fidler
#' @noRd
.isNormOrTErrorExpression <- function(expr) {
.env <- .isErrorExpression(expr, TRUE)
if (inherits(.env, "logical")) return(FALSE)
any(.env$predDf$distribution == c("norm", "t", "cauchy"))
}
#' Throw an error if the error expression is invalid
#'
#' @param expr Error expression from a model({}) or related pipe
#' @return Nothing, called for side effect of an error for a bad pipe
#' @author Matthew L. Fidler
#' @noRd
.throwIfInvalidTilde <- function(expr) {
.env <- .isErrorExpression(expr, TRUE)
.env$isAnAdditiveExpression <- FALSE
.env$ll <- FALSE
.env$linCmt <- FALSE
.left <- .errHandleLlOrLinCmt(expr[[2]], .env)
.env$trLimit <- c(-Inf, Inf)
.env$a <- .env$b <- .env$c <- .env$d <- .env$e <- .env$f <- .env$lambda <- NA_character_
.env$curCondition <- .env$curVar <- deparse1(.left)
.env$hasNonErrorTerm <- FALSE
.env$needsToBeAnErrorExpression <- FALSE
.env$needToDemoteAdditiveExpression <- FALSE
.right <- .errHandleCondition(expr[[3]], .env)
.env$isAnAdditiveExpression <- FALSE
.env$errTypeInfo <- rxErrTypeCombine("")
.env$distribution <- "norm"
.env$estNotAllowed <- FALSE
.errHandleErrorStructure(.right, .env)
if (!is.null(.env$err)) {
stop(paste(.env$err, collapse="\n"), call.=FALSE)
}
}
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.