Nothing
#' The Student-t Distribution
#'
#' Density, distribution function, quantile function and random generation
#' for the Student-t distribution with location \code{mu}, scale \code{sigma},
#' and degrees of freedom \code{df}.
#'
#' @name StudentT
#'
#' @param x Vector of quantiles.
#' @param q Vector of quantiles.
#' @param p Vector of probabilities.
#' @param n Number of draws to sample from the distribution.
#' @param mu Vector of location values.
#' @param sigma Vector of scale values.
#' @param df Vector of degrees of freedom.
#' @param log Logical; If \code{TRUE}, values are returned on the log scale.
#' @param log.p Logical; If \code{TRUE}, values are returned on the log scale.
#' @param lower.tail Logical; If \code{TRUE} (default), return P(X <= x).
#' Else, return P(X > x) .
#'
#' @details See \code{vignette("brms_families")} for details
#' on the parameterization.
#'
#' @seealso \code{\link[stats:TDist]{TDist}}
#'
#' @export
dstudent_t <- function(x, df, mu = 0, sigma = 1, log = FALSE) {
if (isTRUE(any(sigma < 0))) {
stop2("sigma must be non-negative.")
}
if (log) {
dt((x - mu) / sigma, df = df, log = TRUE) - log(sigma)
} else {
dt((x - mu) / sigma, df = df) / sigma
}
}
#' @rdname StudentT
#' @export
pstudent_t <- function(q, df, mu = 0, sigma = 1,
lower.tail = TRUE, log.p = FALSE) {
if (isTRUE(any(sigma < 0))) {
stop2("sigma must be non-negative.")
}
pt((q - mu) / sigma, df = df, lower.tail = lower.tail, log.p = log.p)
}
#' @rdname StudentT
#' @export
qstudent_t <- function(p, df, mu = 0, sigma = 1,
lower.tail = TRUE, log.p = FALSE) {
if (isTRUE(any(sigma < 0))) {
stop2("sigma must be non-negative.")
}
mu + sigma * qt(p, df = df, lower.tail = lower.tail, log.p = log.p)
}
#' @rdname StudentT
#' @export
rstudent_t <- function(n, df, mu = 0, sigma = 1) {
if (isTRUE(any(sigma < 0))) {
stop2("sigma must be non-negative.")
}
mu + sigma * rt(n, df = df)
}
#' The Multivariate Normal Distribution
#'
#' Density function and random generation for the multivariate normal
#' distribution with mean vector \code{mu} and covariance matrix \code{Sigma}.
#'
#' @name MultiNormal
#'
#' @inheritParams StudentT
#' @param x Vector or matrix of quantiles. If \code{x} is a matrix,
#' each row is taken to be a quantile.
#' @param mu Mean vector with length equal to the number of dimensions.
#' @param Sigma Covariance matrix.
#' @param check Logical; Indicates whether several input checks
#' should be performed. Defaults to \code{FALSE} to improve
#' efficiency.
#'
#' @details See the Stan user's manual \url{https://mc-stan.org/documentation/}
#' for details on the parameterization
#'
#' @export
dmulti_normal <- function(x, mu, Sigma, log = FALSE, check = FALSE) {
if (is.vector(x) || length(dim(x)) == 1L) {
x <- matrix(x, ncol = length(x))
}
p <- ncol(x)
if (check) {
if (length(mu) != p) {
stop2("Dimension of mu is incorrect.")
}
if (!all(dim(Sigma) == c(p, p))) {
stop2("Dimension of Sigma is incorrect.")
}
if (!is_symmetric(Sigma)) {
stop2("Sigma must be a symmetric matrix.")
}
}
chol_Sigma <- chol(Sigma)
rooti <- backsolve(chol_Sigma, t(x) - mu, transpose = TRUE)
quads <- colSums(rooti^2)
out <- -(p / 2) * log(2 * pi) - sum(log(diag(chol_Sigma))) - .5 * quads
if (!log) {
out <- exp(out)
}
out
}
#' @rdname MultiNormal
#' @export
rmulti_normal <- function(n, mu, Sigma, check = FALSE) {
p <- length(mu)
if (check) {
if (!(is_wholenumber(n) && n > 0)) {
stop2("n must be a positive integer.")
}
if (!all(dim(Sigma) == c(p, p))) {
stop2("Dimension of Sigma is incorrect.")
}
if (!is_symmetric(Sigma)) {
stop2("Sigma must be a symmetric matrix.")
}
}
draws <- matrix(rnorm(n * p), nrow = n, ncol = p)
sweep(draws %*% chol(Sigma), 2, mu, "+")
}
#' The Multivariate Student-t Distribution
#'
#' Density function and random generation for the multivariate Student-t
#' distribution with location vector \code{mu}, covariance matrix \code{Sigma},
#' and degrees of freedom \code{df}.
#'
#' @name MultiStudentT
#'
#' @inheritParams StudentT
#' @param x Vector or matrix of quantiles. If \code{x} is a matrix,
#' each row is taken to be a quantile.
#' @param mu Location vector with length equal to the number of dimensions.
#' @param Sigma Covariance matrix.
#' @param check Logical; Indicates whether several input checks
#' should be performed. Defaults to \code{FALSE} to improve
#' efficiency.
#'
#' @details See the Stan user's manual \url{https://mc-stan.org/documentation/}
#' for details on the parameterization
#'
#' @export
dmulti_student_t <- function(x, df, mu, Sigma, log = FALSE, check = FALSE) {
if (is.vector(x) || length(dim(x)) == 1L) {
x <- matrix(x, ncol = length(x))
}
p <- ncol(x)
if (check) {
if (isTRUE(any(df <= 0))) {
stop2("df must be greater than 0.")
}
if (length(mu) != p) {
stop2("Dimension of mu is incorrect.")
}
if (!all(dim(Sigma) == c(p, p))) {
stop2("Dimension of Sigma is incorrect.")
}
if (!is_symmetric(Sigma)) {
stop2("Sigma must be a symmetric matrix.")
}
}
chol_Sigma <- chol(Sigma)
rooti <- backsolve(chol_Sigma, t(x) - mu, transpose = TRUE)
quads <- colSums(rooti^2)
out <- lgamma((p + df)/2) - (lgamma(df / 2) + sum(log(diag(chol_Sigma))) +
p / 2 * log(pi * df)) - 0.5 * (df + p) * log1p(quads / df)
if (!log) {
out <- exp(out)
}
out
}
#' @rdname MultiStudentT
#' @export
rmulti_student_t <- function(n, df, mu, Sigma, check = FALSE) {
p <- length(mu)
if (isTRUE(any(df <= 0))) {
stop2("df must be greater than 0.")
}
draws <- rmulti_normal(n, mu = rep(0, p), Sigma = Sigma, check = check)
draws <- draws / sqrt(rchisq(n, df = df) / df)
sweep(draws, 2, mu, "+")
}
#' The (Multivariate) Logistic Normal Distribution
#'
#' Density function and random generation for the (multivariate) logistic normal
#' distribution with latent mean vector \code{mu} and covariance matrix \code{Sigma}.
#'
#' @name LogisticNormal
#'
#' @inheritParams StudentT
#' @param x Vector or matrix of quantiles. If \code{x} is a matrix,
#' each row is taken to be a quantile.
#' @param mu Mean vector with length equal to the number of dimensions.
#' @param Sigma Covariance matrix.
#' @param refcat A single integer indicating the reference category.
#' Defaults to \code{1}.
#' @param check Logical; Indicates whether several input checks
#' should be performed. Defaults to \code{FALSE} to improve
#' efficiency.
#'
#' @export
dlogistic_normal <- function(x, mu, Sigma, refcat = 1, log = FALSE,
check = FALSE) {
if (is.vector(x) || length(dim(x)) == 1L) {
x <- matrix(x, ncol = length(x))
}
lx <- link_categorical(x, refcat)
out <- dmulti_normal(lx, mu, Sigma, log = TRUE) - rowSums(log(x))
if (!log) {
out <- exp(out)
}
out
}
#' @rdname LogisticNormal
#' @export
rlogistic_normal <- function(n, mu, Sigma, refcat = 1, check = FALSE) {
out <- rmulti_normal(n, mu, Sigma, check = check)
inv_link_categorical(out, refcat = refcat)
}
#' The Skew-Normal Distribution
#'
#' Density, distribution function, and random generation for the
#' skew-normal distribution with mean \code{mu},
#' standard deviation \code{sigma}, and skewness \code{alpha}.
#'
#' @name SkewNormal
#'
#' @inheritParams StudentT
#' @param x,q Vector of quantiles.
#' @param mu Vector of mean values.
#' @param sigma Vector of standard deviation values.
#' @param alpha Vector of skewness values.
#' @param xi Optional vector of location values.
#' If \code{NULL} (the default), will be computed internally.
#' @param omega Optional vector of scale values.
#' If \code{NULL} (the default), will be computed internally.
#' @param tol Tolerance of the approximation used in the
#' computation of quantiles.
#'
#' @details See \code{vignette("brms_families")} for details
#' on the parameterization.
#'
#' @export
dskew_normal <- function(x, mu = 0, sigma = 1, alpha = 0,
xi = NULL, omega = NULL, log = FALSE) {
if (isTRUE(any(sigma < 0))) {
stop2("sigma must be greater than 0.")
}
args <- cp2dp(mu, sigma, alpha, xi = xi, omega = omega, x = x)
out <- with(args, {
# do it like sn::dsn
z <- (x - xi) / omega
if (length(alpha) == 1L) {
alpha <- rep(alpha, length(z))
}
logN <- -log(sqrt(2 * pi)) - log(omega) - z^2 / 2
logS <- ifelse(
abs(alpha) < Inf,
pnorm(alpha * z, log.p = TRUE),
log(as.numeric(sign(alpha) * z > 0))
)
out <- logN + logS - pnorm(0, log.p = TRUE)
ifelse(abs(z) == Inf, -Inf, out)
})
if (!log) {
out <- exp(out)
}
out
}
#' @rdname SkewNormal
#' @export
pskew_normal <- function(q, mu = 0, sigma = 1, alpha = 0,
xi = NULL, omega = NULL,
lower.tail = TRUE, log.p = FALSE) {
require_package("mnormt")
if (isTRUE(any(sigma < 0))) {
stop2("sigma must be non-negative.")
}
args <- cp2dp(mu, sigma, alpha, xi = xi, omega = omega, q = q)
out <- with(args, {
# do it like sn::psn
z <- (q - xi) / omega
nz <- length(z)
is_alpha_inf <- abs(alpha) == Inf
delta[is_alpha_inf] <- sign(alpha[is_alpha_inf])
out <- numeric(nz)
for (k in seq_len(nz)) {
if (is.infinite(z[k])) {
if (z[k] > 0) {
out[k] <- 1
} else {
out[k] <- 0
}
} else if (is_alpha_inf[k]) {
if (alpha[k] > 0) {
out[k] <- 2 * (pnorm(pmax(z[k], 0)) - 0.5)
} else {
out[k] <- 1 - 2 * (0.5 - pnorm(pmin(z[k], 0)))
}
} else {
S <- matrix(c(1, -delta[k], -delta[k], 1), 2, 2)
out[k] <- 2 * mnormt::biv.nt.prob(
0, lower = rep(-Inf, 2), upper = c(z[k], 0),
mean = c(0, 0), S = S
)
}
}
pmin(1, pmax(0, out))
})
if (!lower.tail) {
out <- 1 - out
}
if (log.p) {
out <- log(out)
}
out
}
#' @rdname SkewNormal
#' @export
qskew_normal <- function(p, mu = 0, sigma = 1, alpha = 0,
xi = NULL, omega = NULL,
lower.tail = TRUE, log.p = FALSE,
tol = 1e-8) {
if (isTRUE(any(sigma < 0))) {
stop2("sigma must be non-negative.")
}
p <- validate_p_dist(p, lower.tail = lower.tail, log.p = log.p)
args <- cp2dp(mu, sigma, alpha, xi = xi, omega = omega, p = p)
out <- with(args, {
# do it like sn::qsn
na <- is.na(p) | (p < 0) | (p > 1)
zero <- (p == 0)
one <- (p == 1)
p <- replace(p, (na | zero | one), 0.5)
cum <- skew_normal_cumulants(0, 1, alpha, n = 4)
g1 <- cum[, 3] / cum[, 2]^(3 / 2)
g2 <- cum[, 4] / cum[, 2]^2
x <- qnorm(p)
x <- x + (x^2 - 1) * g1 / 6 +
x * (x^2 - 3) * g2 / 24 -
x * (2 * x^2 - 5) * g1^2 / 36
x <- cum[, 1] + sqrt(cum[, 2]) * x
px <- pskew_normal(x, xi = 0, omega = 1, alpha = alpha)
max_err <- 1
while (max_err > tol) {
x1 <- x - (px - p) /
dskew_normal(x, xi = 0, omega = 1, alpha = alpha)
x <- x1
px <- pskew_normal(x, xi = 0, omega = 1, alpha = alpha)
max_err <- max(abs(px - p))
if (is.na(max_err)) {
warning2("Approximation in 'qskew_normal' might have failed.")
}
}
x <- replace(x, na, NA)
x <- replace(x, zero, -Inf)
x <- replace(x, one, Inf)
as.numeric(xi + omega * x)
})
out
}
#' @rdname SkewNormal
#' @export
rskew_normal <- function(n, mu = 0, sigma = 1, alpha = 0,
xi = NULL, omega = NULL) {
if (isTRUE(any(sigma < 0))) {
stop2("sigma must be non-negative.")
}
args <- cp2dp(mu, sigma, alpha, xi = xi, omega = omega)
with(args, {
# do it like sn::rsn
z1 <- rnorm(n)
z2 <- rnorm(n)
id <- z2 > args$alpha * z1
z1[id] <- -z1[id]
xi + omega * z1
})
}
# convert skew-normal mixed-CP to DP parameterization
# @return a data.frame containing all relevant parameters
cp2dp <- function(mu = 0, sigma = 1, alpha = 0,
xi = NULL, omega = NULL, ...) {
delta <- alpha / sqrt(1 + alpha^2)
if (is.null(omega)) {
omega <- sigma / sqrt(1 - 2 / pi * delta^2)
}
if (is.null(xi)) {
xi <- mu - omega * delta * sqrt(2 / pi)
}
expand(dots = nlist(mu, sigma, alpha, xi, omega, delta, ...))
}
# helper function for qskew_normal
# code basis taken from sn::sn.cumulants
# uses xi and omega rather than mu and sigma
skew_normal_cumulants <- function(xi = 0, omega = 1, alpha = 0, n = 4) {
cumulants_half_norm <- function(n) {
n <- max(n, 2)
n <- as.integer(2 * ceiling(n/2))
half.n <- as.integer(n/2)
m <- 0:(half.n - 1)
a <- sqrt(2/pi)/(gamma(m + 1) * 2^m * (2 * m + 1))
signs <- rep(c(1, -1), half.n)[seq_len(half.n)]
a <- as.vector(rbind(signs * a, rep(0, half.n)))
coeff <- rep(a[1], n)
for (k in 2:n) {
ind <- seq_len(k - 1)
coeff[k] <- a[k] - sum(ind * coeff[ind] * a[rev(ind)]/k)
}
kappa <- coeff * gamma(seq_len(n) + 1)
kappa[2] <- 1 + kappa[2]
return(kappa)
}
args <- expand(dots = nlist(xi, omega, alpha))
with(args, {
# do it like sn::sn.cumulants
delta <- alpha / sqrt(1 + alpha^2)
kv <- cumulants_half_norm(n)
if (length(kv) > n) {
kv <- kv[-(n + 1)]
}
kv[2] <- kv[2] - 1
kappa <- outer(delta, 1:n, "^") *
matrix(rep(kv, length(xi)), ncol = n, byrow = TRUE)
kappa[, 2] <- kappa[, 2] + 1
kappa <- kappa * outer(omega, 1:n, "^")
kappa[, 1] <- kappa[, 1] + xi
kappa
})
}
# CDF of the inverse gamma function
pinvgamma <- function(q, shape, rate, lower.tail = TRUE, log.p = FALSE) {
pgamma(1/q, shape, rate = rate, lower.tail = !lower.tail, log.p = log.p)
}
#' The von Mises Distribution
#'
#' Density, distribution function, and random generation for the
#' von Mises distribution with location \code{mu}, and precision \code{kappa}.
#'
#' @name VonMises
#'
#' @inheritParams StudentT
#' @param x,q Vector of quantiles between \code{-pi} and \code{pi}.
#' @param kappa Vector of precision values.
#' @param acc Accuracy of numerical approximations.
#'
#' @details See \code{vignette("brms_families")} for details
#' on the parameterization.
#'
#' @export
dvon_mises <- function(x, mu, kappa, log = FALSE) {
if (isTRUE(any(kappa < 0))) {
stop2("kappa must be non-negative")
}
# expects x in [-pi, pi] rather than [0, 2*pi] as CircStats::dvm
be <- besselI(kappa, nu = 0, expon.scaled = TRUE)
out <- -log(2 * pi * be) + kappa * (cos(x - mu) - 1)
if (!log) {
out <- exp(out)
}
out
}
#' @rdname VonMises
#' @export
pvon_mises <- function(q, mu, kappa, lower.tail = TRUE,
log.p = FALSE, acc = 1e-20) {
if (isTRUE(any(kappa < 0))) {
stop2("kappa must be non-negative")
}
pi <- base::pi
pi2 <- 2 * pi
q <- (q + pi) %% pi2
mu <- (mu + pi) %% pi2
args <- expand(q = q, mu = mu, kappa = kappa)
q <- args$q
mu <- args$mu
kappa <- args$kappa
rm(args)
# code basis taken from CircStats::pvm but improved
# considerably with respect to speed and stability
rec_sum <- function(q, kappa, acc, sum = 0, i = 1) {
# compute the sum of of besselI functions recursively
term <- (besselI(kappa, nu = i) * sin(i * q)) / i
sum <- sum + term
rd <- abs(term) >= acc
if (sum(rd)) {
sum[rd] <- rec_sum(
q[rd], kappa[rd], acc, sum = sum[rd], i = i + 1
)
}
sum
}
.pvon_mises <- function(q, kappa, acc) {
sum <- rec_sum(q, kappa, acc)
q / pi2 + sum / (pi * besselI(kappa, nu = 0))
}
out <- rep(NA, length(mu))
zero_mu <- mu == 0
if (sum(zero_mu)) {
out[zero_mu] <- .pvon_mises(q[zero_mu], kappa[zero_mu], acc)
}
lq_mu <- q <= mu
if (sum(lq_mu)) {
upper <- (q[lq_mu] - mu[lq_mu]) %% pi2
upper[upper == 0] <- pi2
lower <- (-mu[lq_mu]) %% pi2
out[lq_mu] <-
.pvon_mises(upper, kappa[lq_mu], acc) -
.pvon_mises(lower, kappa[lq_mu], acc)
}
uq_mu <- q > mu
if (sum(uq_mu)) {
upper <- q[uq_mu] - mu[uq_mu]
lower <- mu[uq_mu] %% pi2
out[uq_mu] <-
.pvon_mises(upper, kappa[uq_mu], acc) +
.pvon_mises(lower, kappa[uq_mu], acc)
}
if (!lower.tail) {
out <- 1 - out
}
if (log.p) {
out <- log(out)
}
out
}
#' @rdname VonMises
#' @export
rvon_mises <- function(n, mu, kappa) {
if (isTRUE(any(kappa < 0))) {
stop2("kappa must be non-negative")
}
args <- expand(mu = mu, kappa = kappa, length = n)
mu <- args$mu
kappa <- args$kappa
rm(args)
pi <- base::pi
mu <- mu + pi
# code basis taken from CircStats::rvm but improved
# considerably with respect to speed and stability
rvon_mises_outer <- function(r, mu, kappa) {
n <- length(r)
U1 <- runif(n, 0, 1)
z <- cos(pi * U1)
f <- (1 + r * z) / (r + z)
c <- kappa * (r - f)
U2 <- runif(n, 0, 1)
outer <- is.na(f) | is.infinite(f) |
!(c * (2 - c) - U2 > 0 | log(c / U2) + 1 - c >= 0)
inner <- !outer
out <- rep(NA, n)
if (sum(inner)) {
out[inner] <- rvon_mises_inner(f[inner], mu[inner])
}
if (sum(outer)) {
# evaluate recursively until a valid sample is found
out[outer] <- rvon_mises_outer(r[outer], mu[outer], kappa[outer])
}
out
}
rvon_mises_inner <- function(f, mu) {
n <- length(f)
U3 <- runif(n, 0, 1)
(sign(U3 - 0.5) * acos(f) + mu) %% (2 * pi)
}
a <- 1 + (1 + 4 * (kappa^2))^0.5
b <- (a - (2 * a)^0.5) / (2 * kappa)
r <- (1 + b^2) / (2 * b)
# indicates underflow due to kappa being close to zero
is_uf <- is.na(r) | is.infinite(r)
not_uf <- !is_uf
out <- rep(NA, n)
if (sum(is_uf)) {
out[is_uf] <- runif(sum(is_uf), 0, 2 * pi)
}
if (sum(not_uf)) {
out[not_uf] <- rvon_mises_outer(r[not_uf], mu[not_uf], kappa[not_uf])
}
out - pi
}
#' The Exponentially Modified Gaussian Distribution
#'
#' Density, distribution function, and random generation
#' for the exponentially modified Gaussian distribution with
#' mean \code{mu} and standard deviation \code{sigma} of the gaussian
#' component, as well as scale \code{beta} of the exponential
#' component.
#'
#' @name ExGaussian
#'
#' @inheritParams StudentT
#' @param x,q Vector of quantiles.
#' @param mu Vector of means of the combined distribution.
#' @param sigma Vector of standard deviations of the gaussian component.
#' @param beta Vector of scales of the exponential component.
#'
#' @details See \code{vignette("brms_families")} for details
#' on the parameterization.
#'
#' @export
dexgaussian <- function(x, mu, sigma, beta, log = FALSE) {
if (isTRUE(any(sigma < 0))) {
stop2("sigma must be non-negative.")
}
if (isTRUE(any(beta < 0))) {
stop2("beta must be non-negative.")
}
args <- nlist(x, mu, sigma, beta)
args <- do_call(expand, args)
args$mu <- with(args, mu - beta)
args$z <- with(args, x - mu - sigma^2 / beta)
out <- with(args,
-log(beta) - (z + sigma^2 / (2 * beta)) / beta +
pnorm(z / sigma, log.p = TRUE)
)
if (!log) {
out <- exp(out)
}
out
}
#' @rdname ExGaussian
#' @export
pexgaussian <- function(q, mu, sigma, beta,
lower.tail = TRUE, log.p = FALSE) {
if (isTRUE(any(sigma < 0))) {
stop2("sigma must be non-negative.")
}
if (isTRUE(any(beta < 0))) {
stop2("beta must be non-negative.")
}
args <- nlist(q, mu, sigma, beta)
args <- do_call(expand, args)
args$mu <- with(args, mu - beta)
args$z <- with(args, q - mu - sigma^2 / beta)
out <- with(args,
pnorm((q - mu) / sigma) - pnorm(z / sigma) *
exp(((mu + sigma^2 / beta)^2 - mu^2 - 2 * q * sigma^2 / beta) /
(2 * sigma^2))
)
if (!lower.tail) {
out <- 1 - out
}
if (log.p) {
out <- log(out)
}
out
}
#' @rdname ExGaussian
#' @export
rexgaussian <- function(n, mu, sigma, beta) {
if (isTRUE(any(sigma < 0))) {
stop2("sigma must be non-negative.")
}
if (isTRUE(any(beta < 0))) {
stop2("beta must be non-negative.")
}
mu <- mu - beta
rnorm(n, mean = mu, sd = sigma) + rexp(n, rate = 1 / beta)
}
#' The Frechet Distribution
#'
#' Density, distribution function, quantile function and random generation
#' for the Frechet distribution with location \code{loc}, scale \code{scale},
#' and shape \code{shape}.
#'
#' @name Frechet
#'
#' @inheritParams StudentT
#' @param x,q Vector of quantiles.
#' @param loc Vector of locations.
#' @param scale Vector of scales.
#' @param shape Vector of shapes.
#'
#' @details See \code{vignette("brms_families")} for details
#' on the parameterization.
#'
#' @export
dfrechet <- function(x, loc = 0, scale = 1, shape = 1, log = FALSE) {
if (isTRUE(any(scale <= 0))) {
stop2("Argument 'scale' must be positive.")
}
if (isTRUE(any(shape <= 0))) {
stop2("Argument 'shape' must be positive.")
}
x <- (x - loc) / scale
args <- nlist(x, loc, scale, shape)
args <- do_call(expand, args)
out <- with(args,
log(shape / scale) - (1 + shape) * log(x) - x^(-shape)
)
if (!log) {
out <- exp(out)
}
out
}
#' @rdname Frechet
#' @export
pfrechet <- function(q, loc = 0, scale = 1, shape = 1,
lower.tail = TRUE, log.p = FALSE) {
if (isTRUE(any(scale <= 0))) {
stop2("Argument 'scale' must be positive.")
}
if (isTRUE(any(shape <= 0))) {
stop2("Argument 'shape' must be positive.")
}
q <- pmax((q - loc) / scale, 0)
out <- exp(-q^(-shape))
if (!lower.tail) {
out <- 1 - out
}
if (log.p) {
out <- log(out)
}
out
}
#' @rdname Frechet
#' @export
qfrechet <- function(p, loc = 0, scale = 1, shape = 1,
lower.tail = TRUE, log.p = FALSE) {
if (isTRUE(any(scale <= 0))) {
stop2("Argument 'scale' must be positive.")
}
if (isTRUE(any(shape <= 0))) {
stop2("Argument 'shape' must be positive.")
}
p <- validate_p_dist(p, lower.tail = lower.tail, log.p = log.p)
loc + scale * (-log(p))^(-1/shape)
}
#' @rdname Frechet
#' @export
rfrechet <- function(n, loc = 0, scale = 1, shape = 1) {
if (isTRUE(any(scale <= 0))) {
stop2("Argument 'scale' must be positive.")
}
if (isTRUE(any(shape <= 0))) {
stop2("Argument 'shape' must be positive.")
}
loc + scale * rexp(n)^(-1 / shape)
}
#' The Shifted Log Normal Distribution
#'
#' Density, distribution function, quantile function and random generation
#' for the shifted log normal distribution with mean \code{meanlog},
#' standard deviation \code{sdlog}, and shift parameter \code{shift}.
#'
#' @name Shifted_Lognormal
#'
#' @inheritParams StudentT
#' @param x,q Vector of quantiles.
#' @param meanlog Vector of means.
#' @param sdlog Vector of standard deviations.
#' @param shift Vector of shifts.
#'
#' @details See \code{vignette("brms_families")} for details
#' on the parameterization.
#'
#' @export
dshifted_lnorm <- function(x, meanlog = 0, sdlog = 1, shift = 0, log = FALSE) {
args <- nlist(dist = "lnorm", x, shift, meanlog, sdlog, log)
do_call(dshifted, args)
}
#' @rdname Shifted_Lognormal
#' @export
pshifted_lnorm <- function(q, meanlog = 0, sdlog = 1, shift = 0,
lower.tail = TRUE, log.p = FALSE) {
args <- nlist(dist = "lnorm", q, shift, meanlog, sdlog, lower.tail, log.p)
do_call(pshifted, args)
}
#' @rdname Shifted_Lognormal
#' @export
qshifted_lnorm <- function(p, meanlog = 0, sdlog = 1, shift = 0,
lower.tail = TRUE, log.p = FALSE) {
args <- nlist(dist = "lnorm", p, shift, meanlog, sdlog, lower.tail, log.p)
do_call(qshifted, args)
}
#' @rdname Shifted_Lognormal
#' @export
rshifted_lnorm <- function(n, meanlog = 0, sdlog = 1, shift = 0) {
args <- nlist(dist = "lnorm", n, shift, meanlog, sdlog)
do_call(rshifted, args)
}
#' The Inverse Gaussian Distribution
#'
#' Density, distribution function, and random generation
#' for the inverse Gaussian distribution with location \code{mu},
#' and shape \code{shape}.
#'
#' @name InvGaussian
#'
#' @inheritParams StudentT
#' @param x,q Vector of quantiles.
#' @param mu Vector of locations.
#' @param shape Vector of shapes.
#'
#' @details See \code{vignette("brms_families")} for details
#' on the parameterization.
#'
#' @export
dinv_gaussian <- function(x, mu = 1, shape = 1, log = FALSE) {
if (isTRUE(any(mu <= 0))) {
stop2("Argument 'mu' must be positive.")
}
if (isTRUE(any(shape <= 0))) {
stop2("Argument 'shape' must be positive.")
}
args <- nlist(x, mu, shape)
args <- do_call(expand, args)
out <- with(args,
0.5 * log(shape / (2 * pi)) -
1.5 * log(x) - 0.5 * shape * (x - mu)^2 / (x * mu^2)
)
if (!log) {
out <- exp(out)
}
out
}
#' @rdname InvGaussian
#' @export
pinv_gaussian <- function(q, mu = 1, shape = 1, lower.tail = TRUE,
log.p = FALSE) {
if (isTRUE(any(mu <= 0))) {
stop2("Argument 'mu' must be positive.")
}
if (isTRUE(any(shape <= 0))) {
stop2("Argument 'shape' must be positive.")
}
args <- nlist(q, mu, shape)
args <- do_call(expand, args)
out <- with(args,
pnorm(sqrt(shape / q) * (q / mu - 1)) +
exp(2 * shape / mu) * pnorm(-sqrt(shape / q) * (q / mu + 1))
)
if (!lower.tail) {
out <- 1 - out
}
if (log.p) {
out <- log(out)
}
out
}
#' @rdname InvGaussian
#' @export
rinv_gaussian <- function(n, mu = 1, shape = 1) {
# create random numbers for the inverse gaussian distribution
# Args:
# Args: see dinv_gaussian
if (isTRUE(any(mu <= 0))) {
stop2("Argument 'mu' must be positive.")
}
if (isTRUE(any(shape <= 0))) {
stop2("Argument 'shape' must be positive.")
}
args <- nlist(mu, shape, length = n)
args <- do_call(expand, args)
# algorithm from wikipedia
args$y <- rnorm(n)^2
args$x <- with(args,
mu + (mu^2 * y) / (2 * shape) - mu / (2 * shape) *
sqrt(4 * mu * shape * y + mu^2 * y^2)
)
args$z <- runif(n)
with(args, ifelse(z <= mu / (mu + x), x, mu^2 / x))
}
#' The Beta-binomial Distribution
#'
#' Cumulative density & mass functions, and random number generation for the
#' Beta-binomial distribution using the following re-parameterisation of the
#' \href{https://mc-stan.org/docs/2_29/functions-reference/beta-binomial-distribution.html}{Stan
#' Beta-binomial definition}:
#' \itemize{
#' \item{\code{mu = alpha * beta}} mean probability of trial success.
#' \item{\code{phi = (1 - mu) * beta}} precision or over-dispersion, component.
#' }
#'
#' @name BetaBinomial
#'
#' @inheritParams StudentT
#' @param x,q Vector of quantiles.
#' @param size Vector of number of trials (zero or more).
#' @param mu Vector of means.
#' @param phi Vector of precisions.
#'
#' @export
dbeta_binomial <- function(x, size, mu, phi, log = FALSE) {
require_package("extraDistr")
alpha <- mu * phi
beta <- (1 - mu) * phi
extraDistr::dbbinom(x, size, alpha = alpha, beta = beta, log = log)
}
#' @rdname BetaBinomial
#' @export
pbeta_binomial <- function(q, size, mu, phi, lower.tail = TRUE, log.p = FALSE) {
require_package("extraDistr")
alpha <- mu * phi
beta <- (1 - mu) * phi
extraDistr::pbbinom(q, size, alpha = alpha, beta = beta,
lower.tail = lower.tail, log.p = log.p)
}
#' @rdname BetaBinomial
#' @export
rbeta_binomial <- function(n, size, mu, phi) {
# beta location-scale probabilities
probs <- rbeta(n, mu * phi, (1 - mu) * phi)
# binomial draws
rbinom(n, size = size, prob = probs)
}
#' The Generalized Extreme Value Distribution
#'
#' Density, distribution function, and random generation
#' for the generalized extreme value distribution with
#' location \code{mu}, scale \code{sigma} and shape \code{xi}.
#'
#' @name GenExtremeValue
#'
#' @inheritParams StudentT
#' @param x,q Vector of quantiles.
#' @param mu Vector of locations.
#' @param sigma Vector of scales.
#' @param xi Vector of shapes.
#'
#' @details See \code{vignette("brms_families")} for details
#' on the parameterization.
#'
#' @export
dgen_extreme_value <- function(x, mu = 0, sigma = 1, xi = 0, log = FALSE) {
if (isTRUE(any(sigma <= 0))) {
stop2("sigma bust be positive.")
}
x <- (x - mu) / sigma
args <- nlist(x, mu, sigma, xi)
args <- do_call(expand, args)
args$t <- with(args, 1 + xi * x)
out <- with(args, ifelse(
xi == 0,
-log(sigma) - x - exp(-x),
-log(sigma) - (1 + 1 / xi) * log(t) - t^(-1 / xi)
))
if (!log) {
out <- exp(out)
}
out
}
#' @rdname GenExtremeValue
#' @export
pgen_extreme_value <- function(q, mu = 0, sigma = 1, xi = 0,
lower.tail = TRUE, log.p = FALSE) {
if (isTRUE(any(sigma <= 0))) {
stop2("sigma bust be positive.")
}
q <- (q - mu) / sigma
args <- nlist(q, mu, sigma, xi)
args <- do_call(expand, args)
out <- with(args, ifelse(
xi == 0,
exp(-exp(-q)),
exp(-(1 + xi * q)^(-1 / xi))
))
if (!lower.tail) {
out <- 1 - out
}
if (log.p) {
out <- log(out)
}
out
}
#' @rdname GenExtremeValue
#' @export
qgen_extreme_value <- function(p, mu = 0, sigma = 1, xi = 0,
lower.tail = TRUE, log.p = FALSE) {
if (isTRUE(any(sigma <= 0))) {
stop2("sigma bust be positive.")
}
p <- validate_p_dist(p, lower.tail = lower.tail, log.p = log.p)
args <- nlist(p, mu, sigma, xi)
args <- do_call(expand, args)
out <- with(args, ifelse(
xi == 0,
mu - sigma * log(-log(p)),
mu - sigma * (1 - (-log(p)) ^ (-xi)) / xi
))
out
}
#' @rdname GenExtremeValue
#' @export
rgen_extreme_value <- function(n, mu = 0, sigma = 1, xi = 0) {
if (isTRUE(any(sigma <= 0))) {
stop2("sigma bust be positive.")
}
args <- nlist(mu, sigma, xi, length = n)
args <- do_call(expand, args)
with(args, ifelse(
xi == 0,
mu - sigma * log(rexp(n)),
mu + sigma * (rexp(n)^(-xi) - 1) / xi
))
}
#' The Asymmetric Laplace Distribution
#'
#' Density, distribution function, quantile function and random generation
#' for the asymmetric Laplace distribution with location \code{mu},
#' scale \code{sigma} and asymmetry parameter \code{quantile}.
#'
#' @name AsymLaplace
#'
#' @inheritParams StudentT
#' @param x,q Vector of quantiles.
#' @param mu Vector of locations.
#' @param sigma Vector of scales.
#' @param quantile Asymmetry parameter corresponding to quantiles
#' in quantile regression (hence the name).
#'
#' @details See \code{vignette("brms_families")} for details
#' on the parameterization.
#'
#' @export
dasym_laplace <- function(x, mu = 0, sigma = 1, quantile = 0.5,
log = FALSE) {
out <- ifelse(x < mu,
yes = (quantile * (1 - quantile) / sigma) *
exp((1 - quantile) * (x - mu) / sigma),
no = (quantile * (1 - quantile) / sigma) *
exp(-quantile * (x - mu) / sigma)
)
if (log) {
out <- log(out)
}
out
}
#' @rdname AsymLaplace
#' @export
pasym_laplace <- function(q, mu = 0, sigma = 1, quantile = 0.5,
lower.tail = TRUE, log.p = FALSE) {
out <- ifelse(q < mu,
yes = quantile * exp((1 - quantile) * (q - mu) / sigma),
no = 1 - (1 - quantile) * exp(-quantile * (q - mu) / sigma)
)
if (!lower.tail) {
out <- 1 - out
}
if (log.p) {
out <- log(out)
}
out
}
#' @rdname AsymLaplace
#' @export
qasym_laplace <- function(p, mu = 0, sigma = 1, quantile = 0.5,
lower.tail = TRUE, log.p = FALSE) {
p <- validate_p_dist(p, lower.tail = lower.tail, log.p = log.p)
if (length(quantile) == 1L) {
quantile <- rep(quantile, length(mu))
}
ifelse(p < quantile,
yes = mu + ((sigma * log(p / quantile)) / (1 - quantile)),
no = mu - ((sigma * log((1 - p) / (1 - quantile))) / quantile)
)
}
#' @rdname AsymLaplace
#' @export
rasym_laplace <- function(n, mu = 0, sigma = 1, quantile = 0.5) {
u <- runif(n)
qasym_laplace(u, mu = mu, sigma = sigma, quantile = quantile)
}
# The Discrete Weibull Distribution
#
# Density, distribution function, quantile function and random generation
# for the discrete Weibull distribution with location \code{mu} and
# shape \code{shape}.
#
# @name DiscreteWeibull
#
# @inheritParams StudentT
# @param mu Location parameter in the unit interval.
# @param shape Positive shape parameter.
#
# @details See \code{vignette("brms_families")} for details
# on the parameterization.
#
# @export
ddiscrete_weibull <- function(x, mu, shape, log = FALSE) {
if (isTRUE(any(mu < 0 | mu > 1))) {
stop2("mu bust be between 0 and 1.")
}
if (isTRUE(any(shape <= 0))) {
stop2("shape bust be positive.")
}
x <- round(x)
out <- mu^x^shape - mu^(x + 1)^shape
out[x < 0] <- 0
if (log) {
out <- log(out)
}
out
}
# @rdname DiscreteWeibull
# @export
pdiscrete_weibull <- function(x, mu, shape, lower.tail = TRUE, log.p = FALSE) {
if (isTRUE(any(mu < 0 | mu > 1))) {
stop2("mu bust be between 0 and 1.")
}
if (isTRUE(any(shape <= 0))) {
stop2("shape bust be positive.")
}
x <- round(x)
if (lower.tail) {
out <- 1 - mu^(x + 1)^shape
out[x < 0] <- 0
} else {
out <- mu^(x + 1)^shape
out[x < 0] <- 1
}
if (log.p) {
out <- log(out)
}
out
}
# @rdname DiscreteWeibull
# @export
qdiscrete_weibull <- function(p, mu, shape, lower.tail = TRUE, log.p = FALSE) {
if (isTRUE(any(mu < 0 | mu > 1))) {
stop2("mu bust be between 0 and 1.")
}
if (isTRUE(any(shape <= 0))) {
stop2("shape bust be positive.")
}
p <- validate_p_dist(p, lower.tail = lower.tail, log.p = log.p)
ceiling((log(1 - p) / log(mu))^(1 / shape) - 1)
}
# @rdname DiscreteWeibull
# @export
rdiscrete_weibull <- function(n, mu, shape) {
u <- runif(n, 0, 1)
qdiscrete_weibull(u, mu, shape)
}
# mean of the discrete weibull distribution
# @param mu location parameter
# @param shape shape parameter
# @param M maximal evaluated element of the series
# @param thres threshold for new elements at which to stop evaluation
mean_discrete_weibull <- function(mu, shape, M = 1000, thres = 0.001) {
opt_M <- ceiling(max((log(thres) / log(mu))^(1 / shape)))
if (opt_M <= M) {
M <- opt_M
} else {
# avoid the loop below running too slow
warning2(
"Approximating the mean of the 'discrete_weibull' ",
"distribution failed and results be inaccurate."
)
}
out <- 0
for (y in seq_len(M)) {
out <- out + mu^y^shape
}
# approximation of the residual series (see Englehart & Li, 2011)
# returns unreasonably large values presumably due to numerical issues
out
}
# PDF of the COM-Poisson distribution
# com_poisson in brms uses the mode parameterization
dcom_poisson <- function(x, mu, shape, log = FALSE) {
x <- round(x)
log_mu <- log(mu)
log_Z <- log_Z_com_poisson(log_mu, shape)
out <- shape * (x * log_mu - lgamma(x + 1)) - log_Z
if (!log) {
out <- exp(out)
}
out
}
# random numbers from the COM-Poisson distribution
rcom_poisson <- function(n, mu, shape, M = 10000) {
n <- check_n_rdist(n, mu, shape)
M <- as.integer(as_one_numeric(M))
log_mu <- log(mu)
# approximating log_Z may yield too large random draws
log_Z <- log_Z_com_poisson(log_mu, shape, approx = FALSE)
u <- runif(n, 0, 1)
cdf <- exp(-log_Z)
lfac <- 0
y <- 0
out <- rep(0, n)
not_found <- cdf < u
while (any(not_found) && y <= M) {
y <- y + 1
out[not_found] <- y
lfac <- lfac + log(y)
cdf <- cdf + exp(shape * (y * log_mu - lfac) - log_Z)
not_found <- cdf < u
}
if (any(not_found)) {
out[not_found] <- NA
nfailed <- sum(not_found)
warning2(
"Drawing random numbers from the 'com_poisson' ",
"distribution failed in ", nfailed, " cases."
)
}
out
}
# CDF of the COM-Poisson distribution
pcom_poisson <- function(x, mu, shape, lower.tail = TRUE, log.p = FALSE) {
x <- round(x)
args <- expand(x = x, mu = mu, shape = shape)
x <- args$x
mu <- args$mu
shape <- args$shape
log_mu <- log(mu)
log_Z <- log_Z_com_poisson(log_mu, shape)
out <- rep(0, length(x))
dim(out) <- attributes(args)$max_dim
out[x > 0] <- log1p_exp(shape * log_mu)
k <- 2
lfac <- 0
while (any(x >= k)) {
lfac <- lfac + log(k)
term <- shape * (k * log_mu - lfac)
out[x >= k] <- log_sum_exp(out[x >= k], term)
k <- k + 1
}
out <- out - log_Z
out[out > 0] <- 0
if (!lower.tail) {
out <- log1m_exp(out)
}
if (!log.p) {
out <- exp(out)
}
out
}
# log normalizing constant of the COM Poisson distribution
# @param log_mu log location parameter
# @param shape shape parameter
# @param M maximal evaluated element of the series
# @param thres threshold for new elements at which to stop evaluation
# @param approx use a closed form approximation of the mean if appropriate?
log_Z_com_poisson <- function(log_mu, shape, M = 10000, thres = 1e-16,
approx = TRUE) {
if (isTRUE(any(shape <= 0))) {
stop2("'shape' must be positive.")
}
if (isTRUE(any(shape == Inf))) {
stop2("'shape' must be finite.")
}
approx <- as_one_logical(approx)
args <- expand(log_mu = log_mu, shape = shape)
log_mu <- args$log_mu
shape <- args$shape
out <- rep(NA, length(log_mu))
dim(out) <- attributes(args)$max_dim
use_poisson <- shape == 1
if (any(use_poisson)) {
# shape == 1 implies the poisson distribution
out[use_poisson] <- exp(log_mu[use_poisson])
}
if (approx) {
# use a closed form approximation if appropriate
use_approx <- log_mu * shape >= log(1.5) & log_mu >= log(1.5)
if (any(use_approx)) {
out[use_approx] <- log_Z_com_poisson_approx(
log_mu[use_approx], shape[use_approx]
)
}
}
use_exact <- is.na(out)
if (any(use_exact)) {
# direct computation of the truncated series
M <- as.integer(as_one_numeric(M))
thres <- as_one_numeric(thres)
log_thres <- log(thres)
log_mu <- log_mu[use_exact]
shape <- shape[use_exact]
# first 2 terms of the series
out_exact <- log1p_exp(shape * log_mu)
lfac <- 0
k <- 2
converged <- FALSE
while (!converged && k <= M) {
lfac <- lfac + log(k)
term <- shape * (k * log_mu - lfac)
out_exact <- log_sum_exp(out_exact, term)
converged <- all(term <= log_thres)
k <- k + 1
}
out[use_exact] <- out_exact
if (!converged) {
warning2(
"Approximating the normalizing constant of the 'com_poisson' ",
"distribution failed and results may be inaccurate."
)
}
}
out
}
# approximate the log normalizing constant of the COM Poisson distribution
# based on doi:10.1007/s10463-017-0629-6
log_Z_com_poisson_approx <- function(log_mu, shape) {
shape_mu <- shape * exp(log_mu)
shape2 <- shape^2
# first 4 terms of the residual series
log_sum_resid <- log(
1 + shape_mu^(-1) * (shape2 - 1) / 24 +
shape_mu^(-2) * (shape2 - 1) / 1152 * (shape2 + 23) +
shape_mu^(-3) * (shape2 - 1) / 414720 *
(5 * shape2^2 - 298 * shape2 + 11237)
)
shape_mu + log_sum_resid -
((log(2 * pi) + log_mu) * (shape - 1) / 2 + log(shape) / 2)
}
# compute the log mean of the COM Poisson distribution
# @param mu location parameter
# @param shape shape parameter
# @param M maximal evaluated element of the series
# @param thres threshold for new elements at which to stop evaluation
# @param approx use a closed form approximation of the mean if appropriate?
mean_com_poisson <- function(mu, shape, M = 10000, thres = 1e-16,
approx = TRUE) {
if (isTRUE(any(shape <= 0))) {
stop2("'shape' must be positive.")
}
if (isTRUE(any(shape == Inf))) {
stop2("'shape' must be finite.")
}
approx <- as_one_logical(approx)
args <- expand(mu = mu, shape = shape)
mu <- args$mu
shape <- args$shape
out <- rep(NA, length(mu))
dim(out) <- attributes(args)$max_dim
use_poisson <- shape == 1
if (any(use_poisson)) {
# shape == 1 implies the poisson distribution
out[use_poisson] <- mu[use_poisson]
}
if (approx) {
# use a closed form approximation if appropriate
use_approx <- mu^shape >= 1.5 & mu >= 1.5
if (any(use_approx)) {
out[use_approx] <- mean_com_poisson_approx(
mu[use_approx], shape[use_approx]
)
}
}
use_exact <- is.na(out)
if (any(use_exact)) {
# direct computation of the truncated series
M <- as.integer(as_one_numeric(M))
thres <- as_one_numeric(thres)
log_thres <- log(thres)
mu <- mu[use_exact]
shape <- shape[use_exact]
log_mu <- log(mu)
# first 2 terms of the series
log_num <- shape * log_mu # numerator
log_Z <- log1p_exp(shape * log_mu) # denominator
lfac <- 0
k <- 2
converged <- FALSE
while (!converged && k <= M) {
log_k <- log(k)
lfac <- lfac + log_k
term <- shape * (k * log_mu - lfac)
log_num <- log_sum_exp(log_num, log_k + term)
log_Z <- log_sum_exp(log_Z, term)
converged <- all(term <= log_thres)
k <- k + 1
}
if (!converged) {
warning2(
"Approximating the mean of the 'com_poisson' ",
"distribution failed and results may be inaccurate."
)
}
out[use_exact] <- exp(log_num - log_Z)
}
out
}
# approximate the mean of COM-Poisson distribution
# based on doi:10.1007/s10463-017-0629-6
mean_com_poisson_approx <- function(mu, shape) {
term <- 1 - (shape - 1) / (2 * shape) * mu^(-1) -
(shape^2 - 1) / (24 * shape^2) * mu^(-2) -
(shape^2 - 1) / (24 * shape^3) * mu^(-3)
mu * term
}
#' The Dirichlet Distribution
#'
#' Density function and random number generation for the dirichlet
#' distribution with shape parameter vector \code{alpha}.
#'
#' @name Dirichlet
#'
#' @inheritParams StudentT
#' @param x Matrix of quantiles. Each row corresponds to one probability vector.
#' @param alpha Matrix of positive shape parameters. Each row corresponds to one
#' probability vector.
#'
#' @details See \code{vignette("brms_families")} for details on the
#' parameterization.
#'
#' @export
ddirichlet <- function(x, alpha, log = FALSE) {
log <- as_one_logical(log)
if (!is.matrix(x)) {
x <- matrix(x, nrow = 1)
}
if (!is.matrix(alpha)) {
alpha <- matrix(alpha, nrow(x), length(alpha), byrow = TRUE)
}
if (nrow(x) == 1L && nrow(alpha) > 1L) {
x <- repl(x, nrow(alpha))
x <- do_call(rbind, x)
} else if (nrow(x) > 1L && nrow(alpha) == 1L) {
alpha <- repl(alpha, nrow(x))
alpha <- do_call(rbind, alpha)
}
if (isTRUE(any(x < 0))) {
stop2("x must be non-negative.")
}
if (!is_equal(rowSums(x), rep(1, nrow(x)))) {
stop2("x must sum to 1 per row.")
}
if (isTRUE(any(alpha <= 0))) {
stop2("alpha must be positive.")
}
out <- lgamma(rowSums(alpha)) - rowSums(lgamma(alpha)) +
rowSums((alpha - 1) * log(x))
if (!log) {
out <- exp(out)
}
return(out)
}
#' @rdname Dirichlet
#' @export
rdirichlet <- function(n, alpha) {
n <- as_one_numeric(n)
if (!is.matrix(alpha)) {
alpha <- matrix(alpha, nrow = 1)
}
if (prod(dim(alpha)) == 0) {
stop2("alpha should be non-empty.")
}
if (isTRUE(any(alpha <= 0))) {
stop2("alpha must be positive.")
}
if (n == 1) {
n <- nrow(alpha)
}
if (n > nrow(alpha)) {
alpha <- matrix(alpha, nrow = n, ncol = ncol(alpha), byrow = TRUE)
}
x <- matrix(rgamma(ncol(alpha) * n, alpha), ncol = ncol(alpha))
x / rowSums(x)
}
#' The Wiener Diffusion Model Distribution
#'
#' Density function and random generation for the Wiener
#' diffusion model distribution with boundary separation \code{alpha},
#' non-decision time \code{tau}, bias \code{beta} and
#' drift rate \code{delta}.
#'
#' @name Wiener
#'
#' @inheritParams StudentT
#' @param alpha Boundary separation parameter.
#' @param tau Non-decision time parameter.
#' @param beta Bias parameter.
#' @param delta Drift rate parameter.
#' @param resp Response: \code{"upper"} or \code{"lower"}.
#' If no character vector, it is coerced to logical
#' where \code{TRUE} indicates \code{"upper"} and
#' \code{FALSE} indicates \code{"lower"}.
#' @param types Which types of responses to return? By default,
#' return both the response times \code{"q"} and the dichotomous
#' responses \code{"resp"}. If either \code{"q"} or \code{"resp"},
#' return only one of the two types.
#' @param backend Name of the package to use as backend for the computations.
#' Either \code{"Rwiener"} (the default) or \code{"rtdists"}.
#' Can be set globally for the current \R session via the
#' \code{"wiener_backend"} option (see \code{\link{options}}).
#'
#' @details
#' These are wrappers around functions of the \pkg{RWiener} or \pkg{rtdists}
#' package (depending on the chosen \code{backend}). See
#' \code{vignette("brms_families")} for details on the parameterization.
#'
#' @seealso \code{\link[RWiener:wienerdist]{wienerdist}},
#' \code{\link[rtdists:Diffusion]{Diffusion}}
#'
#' @export
dwiener <- function(x, alpha, tau, beta, delta, resp = 1, log = FALSE,
backend = getOption("wiener_backend", "Rwiener")) {
alpha <- as.numeric(alpha)
tau <- as.numeric(tau)
beta <- as.numeric(beta)
delta <- as.numeric(delta)
if (!is.character(resp)) {
resp <- ifelse(resp, "upper", "lower")
}
log <- as_one_logical(log)
backend <- match.arg(backend, c("Rwiener", "rtdists"))
.dwiener <- paste0(".dwiener_", backend)
args <- nlist(x, alpha, tau, beta, delta, resp)
args <- as.list(do_call(expand, args))
args$log <- log
do_call(.dwiener, args)
}
# dwiener using Rwiener as backend
.dwiener_Rwiener <- function(x, alpha, tau, beta, delta, resp, log) {
require_package("RWiener")
.dwiener <- Vectorize(
RWiener::dwiener,
c("q", "alpha", "tau", "beta", "delta", "resp")
)
args <- nlist(q = x, alpha, tau, beta, delta, resp, give_log = log)
do_call(.dwiener, args)
}
# dwiener using rtdists as backend
.dwiener_rtdists <- function(x, alpha, tau, beta, delta, resp, log) {
require_package("rtdists")
args <- list(
rt = x, response = resp, a = alpha,
t0 = tau, z = beta * alpha, v = delta
)
out <- do_call(rtdists::ddiffusion, args)
if (log) {
out <- log(out)
}
out
}
#' @rdname Wiener
#' @export
rwiener <- function(n, alpha, tau, beta, delta, types = c("q", "resp"),
backend = getOption("wiener_backend", "Rwiener")) {
n <- as_one_numeric(n)
alpha <- as.numeric(alpha)
tau <- as.numeric(tau)
beta <- as.numeric(beta)
delta <- as.numeric(delta)
types <- match.arg(types, several.ok = TRUE)
backend <- match.arg(backend, c("Rwiener", "rtdists"))
.rwiener <- paste0(".rwiener_", backend)
args <- nlist(n, alpha, tau, beta, delta, types)
do_call(.rwiener, args)
}
# rwiener using Rwiener as backend
.rwiener_Rwiener <- function(n, alpha, tau, beta, delta, types) {
require_package("RWiener")
max_len <- max(lengths(list(alpha, tau, beta, delta)))
if (max_len > 1L) {
if (!n %in% c(1, max_len)) {
stop2("Can only sample exactly once for each condition.")
}
n <- 1
}
# helper function to return a numeric vector instead
# of a data.frame with two columns as for RWiener::rwiener
.rwiener_num <- function(n, alpha, tau, beta, delta, types) {
out <- RWiener::rwiener(n, alpha, tau, beta, delta)
out$resp <- ifelse(out$resp == "upper", 1, 0)
if (length(types) == 1L) {
out <- out[[types]]
}
out
}
# vectorized version of .rwiener_num
.rwiener <- function(...) {
fun <- Vectorize(
.rwiener_num,
c("alpha", "tau", "beta", "delta"),
SIMPLIFY = FALSE
)
do_call(rbind, fun(...))
}
args <- nlist(n, alpha, tau, beta, delta, types)
do_call(.rwiener, args)
}
# rwiener using rtdists as backend
.rwiener_rtdists <- function(n, alpha, tau, beta, delta, types) {
require_package("rtdists")
max_len <- max(lengths(list(alpha, tau, beta, delta)))
if (max_len > 1L) {
if (!n %in% c(1, max_len)) {
stop2("Can only sample exactly once for each condition.")
}
n <- max_len
}
out <- rtdists::rdiffusion(
n, a = alpha, t0 = tau, z = beta * alpha, v = delta
)
# TODO: use column names of rtdists in the output?
names(out)[names(out) == "rt"] <- "q"
names(out)[names(out) == "response"] <- "resp"
out$resp <- ifelse(out$resp == "upper", 1, 0)
if (length(types) == 1L) {
out <- out[[types]]
}
out
}
# density of the cox proportional hazards model
# @param x currently ignored as the information is passed
# via 'bhaz' and 'cbhaz'. Before exporting the cox distribution
# functions, this needs to be refactored so that x is actually used
# @param mu positive location parameter
# @param bhaz baseline hazard
# @param cbhaz cumulative baseline hazard
dcox <- function(x, mu, bhaz, cbhaz, log = FALSE) {
out <- hcox(x, mu, bhaz, cbhaz, log = TRUE) +
pcox(x, mu, bhaz, cbhaz, lower.tail = FALSE, log.p = TRUE)
if (!log) {
out <- exp(out)
}
out
}
# hazard function of the cox model
hcox <- function(x, mu, bhaz, cbhaz, log = FALSE) {
out <- log(bhaz) + log(mu)
if (!log) {
out <- exp(out)
}
out
}
# distribution function of the cox model
pcox <- function(q, mu, bhaz, cbhaz, lower.tail = TRUE, log.p = FALSE) {
log_surv <- -cbhaz * mu
if (lower.tail) {
if (log.p) {
out <- log1m_exp(log_surv)
} else {
out <- 1 - exp(log_surv)
}
} else {
if (log.p) {
out <- log_surv
} else {
out <- exp(log_surv)
}
}
out
}
#' Zero-Inflated Distributions
#'
#' Density and distribution functions for zero-inflated distributions.
#'
#' @name ZeroInflated
#'
#' @inheritParams StudentT
#' @param zi zero-inflation probability
#' @param mu,lambda location parameter
#' @param shape,shape1,shape2 shape parameter
#' @param phi precision parameter
#' @param size number of trials
#' @param prob probability of success on each trial
#'
#' @details
#' The density of a zero-inflated distribution can be specified as follows.
#' If \eqn{x = 0} set \eqn{f(x) = \theta + (1 - \theta) * g(0)}.
#' Else set \eqn{f(x) = (1 - \theta) * g(x)},
#' where \eqn{g(x)} is the density of the non-zero-inflated part.
NULL
#' @rdname ZeroInflated
#' @export
dzero_inflated_poisson <- function(x, lambda, zi, log = FALSE) {
pars <- nlist(lambda)
.dzero_inflated(x, "pois", zi, pars, log)
}
#' @rdname ZeroInflated
#' @export
pzero_inflated_poisson <- function(q, lambda, zi, lower.tail = TRUE,
log.p = FALSE) {
pars <- nlist(lambda)
.pzero_inflated(q, "pois", zi, pars, lower.tail, log.p)
}
#' @rdname ZeroInflated
#' @export
dzero_inflated_negbinomial <- function(x, mu, shape, zi, log = FALSE) {
pars <- nlist(mu, size = shape)
.dzero_inflated(x, "nbinom", zi, pars, log)
}
#' @rdname ZeroInflated
#' @export
pzero_inflated_negbinomial <- function(q, mu, shape, zi, lower.tail = TRUE,
log.p = FALSE) {
pars <- nlist(mu, size = shape)
.pzero_inflated(q, "nbinom", zi, pars, lower.tail, log.p)
}
#' @rdname ZeroInflated
#' @export
dzero_inflated_binomial <- function(x, size, prob, zi, log = FALSE) {
pars <- nlist(size, prob)
.dzero_inflated(x, "binom", zi, pars, log)
}
#' @rdname ZeroInflated
#' @export
pzero_inflated_binomial <- function(q, size, prob, zi, lower.tail = TRUE,
log.p = FALSE) {
pars <- nlist(size, prob)
.pzero_inflated(q, "binom", zi, pars, lower.tail, log.p)
}
#' @rdname ZeroInflated
#' @export
dzero_inflated_beta_binomial <- function(x, size, mu, phi, zi, log = FALSE) {
pars <- nlist(size, mu, phi)
.dzero_inflated(x, "beta_binomial", zi, pars, log)
}
#' @rdname ZeroInflated
#' @export
pzero_inflated_beta_binomial <- function(q, size, mu, phi, zi,
lower.tail = TRUE, log.p = FALSE) {
pars <- nlist(size, mu, phi)
.pzero_inflated(q, "beta_binomial", zi, pars, lower.tail, log.p)
}
#' @rdname ZeroInflated
#' @export
dzero_inflated_beta <- function(x, shape1, shape2, zi, log = FALSE) {
pars <- nlist(shape1, shape2)
# zi_beta is technically a hurdle model
.dhurdle(x, "beta", zi, pars, log, type = "real")
}
#' @rdname ZeroInflated
#' @export
pzero_inflated_beta <- function(q, shape1, shape2, zi, lower.tail = TRUE,
log.p = FALSE) {
pars <- nlist(shape1, shape2)
# zi_beta is technically a hurdle model
.phurdle(q, "beta", zi, pars, lower.tail, log.p, type = "real")
}
# @rdname ZeroInflated
# @export
dzero_inflated_asym_laplace <- function(x, mu, sigma, quantile, zi,
log = FALSE) {
pars <- nlist(mu, sigma, quantile)
# zi_asym_laplace is technically a hurdle model
.dhurdle(x, "asym_laplace", zi, pars, log, type = "real")
}
# @rdname ZeroInflated
# @export
pzero_inflated_asym_laplace <- function(q, mu, sigma, quantile, zi,
lower.tail = TRUE, log.p = FALSE) {
pars <- nlist(mu, sigma, quantile)
# zi_asym_laplace is technically a hurdle model
.phurdle(q, "asym_laplace", zi, pars, lower.tail, log.p,
type = "real", lb = -Inf, ub = Inf)
}
# density of a zero-inflated distribution
# @param dist name of the distribution
# @param zi bernoulli zero-inflated parameter
# @param pars list of parameters passed to pdf
.dzero_inflated <- function(x, dist, zi, pars, log) {
stopifnot(is.list(pars))
dist <- as_one_character(dist)
log <- as_one_logical(log)
args <- expand(dots = c(nlist(x, zi), pars))
x <- args$x
zi <- args$zi
pars <- args[names(pars)]
pdf <- paste0("d", dist)
out <- ifelse(x == 0,
log(zi + (1 - zi) * do_call(pdf, c(0, pars))),
log(1 - zi) + do_call(pdf, c(list(x), pars, log = TRUE))
)
if (!log) {
out <- exp(out)
}
out
}
# CDF of a zero-inflated distribution
# @param dist name of the distribution
# @param zi bernoulli zero-inflated parameter
# @param pars list of parameters passed to pdf
# @param lb lower bound of the conditional distribution
# @param ub upper bound of the conditional distribution
.pzero_inflated <- function(q, dist, zi, pars, lower.tail, log.p,
lb = 0, ub = Inf) {
stopifnot(is.list(pars))
dist <- as_one_character(dist)
lower.tail <- as_one_logical(lower.tail)
log.p <- as_one_logical(log.p)
lb <- as_one_numeric(lb)
ub <- as_one_numeric(ub)
args <- expand(dots = c(nlist(q, zi), pars))
q <- args$q
zi <- args$zi
pars <- args[names(pars)]
cdf <- paste0("p", dist)
# compute log CCDF values
out <- log(1 - zi) +
do_call(cdf, c(list(q), pars, lower.tail = FALSE, log.p = TRUE))
# take the limits of the distribution into account
out <- ifelse(q < lb, 0, out)
out <- ifelse(q > ub, -Inf, out)
if (lower.tail) {
out <- 1 - exp(out)
if (log.p) {
out <- log(out)
}
} else {
if (!log.p) {
out <- exp(out)
}
}
out
}
#' Hurdle Distributions
#'
#' Density and distribution functions for hurdle distributions.
#'
#' @name Hurdle
#'
#' @inheritParams StudentT
#' @param hu hurdle probability
#' @param mu,lambda location parameter
#' @param shape shape parameter
#' @param sigma,scale scale parameter
#'
#' @details
#' The density of a hurdle distribution can be specified as follows.
#' If \eqn{x = 0} set \eqn{f(x) = \theta}. Else set
#' \eqn{f(x) = (1 - \theta) * g(x) / (1 - G(0))}
#' where \eqn{g(x)} and \eqn{G(x)} are the density and distribution
#' function of the non-hurdle part, respectively.
NULL
#' @rdname Hurdle
#' @export
dhurdle_poisson <- function(x, lambda, hu, log = FALSE) {
pars <- nlist(lambda)
.dhurdle(x, "pois", hu, pars, log, type = "int")
}
#' @rdname Hurdle
#' @export
phurdle_poisson <- function(q, lambda, hu, lower.tail = TRUE,
log.p = FALSE) {
pars <- nlist(lambda)
.phurdle(q, "pois", hu, pars, lower.tail, log.p, type = "int")
}
#' @rdname Hurdle
#' @export
dhurdle_negbinomial <- function(x, mu, shape, hu, log = FALSE) {
pars <- nlist(mu, size = shape)
.dhurdle(x, "nbinom", hu, pars, log, type = "int")
}
#' @rdname Hurdle
#' @export
phurdle_negbinomial <- function(q, mu, shape, hu, lower.tail = TRUE,
log.p = FALSE) {
pars <- nlist(mu, size = shape)
.phurdle(q, "nbinom", hu, pars, lower.tail, log.p, type = "int")
}
#' @rdname Hurdle
#' @export
dhurdle_gamma <- function(x, shape, scale, hu, log = FALSE) {
pars <- nlist(shape, scale)
.dhurdle(x, "gamma", hu, pars, log, type = "real")
}
#' @rdname Hurdle
#' @export
phurdle_gamma <- function(q, shape, scale, hu, lower.tail = TRUE,
log.p = FALSE) {
pars <- nlist(shape, scale)
.phurdle(q, "gamma", hu, pars, lower.tail, log.p, type = "real")
}
#' @rdname Hurdle
#' @export
dhurdle_lognormal <- function(x, mu, sigma, hu, log = FALSE) {
pars <- list(meanlog = mu, sdlog = sigma)
.dhurdle(x, "lnorm", hu, pars, log, type = "real")
}
#' @rdname Hurdle
#' @export
phurdle_lognormal <- function(q, mu, sigma, hu, lower.tail = TRUE,
log.p = FALSE) {
pars <- list(meanlog = mu, sdlog = sigma)
.phurdle(q, "lnorm", hu, pars, lower.tail, log.p, type = "real")
}
# density of a hurdle distribution
# @param dist name of the distribution
# @param hu bernoulli hurdle parameter
# @param pars list of parameters passed to pdf
# @param type support of distribution (int or real)
.dhurdle <- function(x, dist, hu, pars, log, type) {
stopifnot(is.list(pars))
dist <- as_one_character(dist)
log <- as_one_logical(log)
type <- match.arg(type, c("int", "real"))
args <- expand(dots = c(nlist(x, hu), pars))
x <- args$x
hu <- args$hu
pars <- args[names(pars)]
pdf <- paste0("d", dist)
if (type == "int") {
lccdf0 <- log(1 - do_call(pdf, c(0, pars)))
} else {
lccdf0 <- 0
}
out <- ifelse(x == 0,
log(hu),
log(1 - hu) + do_call(pdf, c(list(x), pars, log = TRUE)) - lccdf0
)
if (!log) {
out <- exp(out)
}
out
}
# CDF of a hurdle distribution
# @param dist name of the distribution
# @param hu bernoulli hurdle parameter
# @param pars list of parameters passed to pdf
# @param type support of distribution (int or real)
# @param lb lower bound of the conditional distribution
# @param ub upper bound of the conditional distribution
.phurdle <- function(q, dist, hu, pars, lower.tail, log.p, type,
lb = 0, ub = Inf) {
stopifnot(is.list(pars))
dist <- as_one_character(dist)
lower.tail <- as_one_logical(lower.tail)
log.p <- as_one_logical(log.p)
type <- match.arg(type, c("int", "real"))
lb <- as_one_numeric(lb)
ub <- as_one_numeric(ub)
args <- expand(dots = c(nlist(q, hu), pars))
q <- args$q
hu <- args$hu
pars <- args[names(pars)]
cdf <- paste0("p", dist)
# compute log CCDF values
out <- log(1 - hu) +
do_call(cdf, c(list(q), pars, lower.tail = FALSE, log.p = TRUE))
if (type == "int") {
pdf <- paste0("d", dist)
out <- out - log(1 - do_call(pdf, c(0, pars)))
}
out <- ifelse(q < 0, log_sum_exp(out, log(hu)), out)
# take the limits of the distribution into account
out <- ifelse(q < lb, 0, out)
out <- ifelse(q > ub, -Inf, out)
if (lower.tail) {
out <- 1 - exp(out)
if (log.p) {
out <- log(out)
}
} else {
if (!log.p) {
out <- exp(out)
}
}
out
}
# density of the categorical distribution with the softmax transform
# @param x positive integers not greater than ncat
# @param eta the linear predictor (of length or ncol ncat)
# @param log return values on the log scale?
dcategorical <- function(x, eta, log = FALSE) {
if (is.null(dim(eta))) {
eta <- matrix(eta, nrow = 1)
}
if (length(dim(eta)) != 2L) {
stop2("eta must be a numeric vector or matrix.")
}
out <- inv_link_categorical(eta, log = log, refcat = NULL)
out[, x, drop = FALSE]
}
# generic inverse link function for the categorical family
#
# @param x Matrix (S x `ncat` or S x `ncat - 1` (depending on `refcat_obj`),
# with S denoting the number of posterior draws and `ncat` denoting the number
# of response categories) with values of `eta` for one observation (see
# dcategorical()) or an array (S x N x `ncat` or S x N x `ncat - 1` (depending
# on `refcat_obj`)) containing the same values as the matrix just described,
# but for N observations.
# @param refcat Integer indicating the reference category to be inserted in 'x'.
# If NULL, `x` is not modified at all.
# @param log Logical (length 1) indicating whether to log the return value.
#
# @return If `x` is a matrix, then a matrix (S x `ncat`, with S denoting the
# number of posterior draws and `ncat` denoting the number of response
# categories) containing the values of the inverse-link function applied to
# `x`. If `x` is an array, then an array (S x N x `ncat`) containing the same
# values as the matrix just described, but for N observations.
inv_link_categorical <- function(x, refcat = 1, log = FALSE) {
if (!is.null(refcat)) {
x <- insert_refcat(x, refcat = refcat)
}
out <- log_softmax(x)
if (!log) {
out <- exp(out)
}
out
}
# generic link function for the categorical family
#
# @param x Matrix (S x `ncat`, with S denoting the number of posterior draws and
# `ncat` denoting the number of response categories) of probabilities for the
# response categories or an array (S x N x `ncat`) containing the same values
# as the matrix just described, but for N observations.
# @param refcat Numeric (length 1) giving the index of the reference category.
# @param return_refcat Logical (length 1) indicating whether to include the
# reference category in the return value.
#
# @return If `x` is a matrix, then a matrix (S x `ncat` or S x `ncat - 1`
# (depending on `return_refcat`), with S denoting the number of posterior
# draws and `ncat` denoting the number of response categories) containing the
# values of the link function applied to `x`. If `x` is an array, then an
# array (S x N x `ncat` or S x N x `ncat - 1` (depending on `return_refcat`))
# containing the same values as the matrix just described, but for N
# observations.
link_categorical <- function(x, refcat = 1, return_refcat = FALSE) {
ndim <- length(dim(x))
marg_noncat <- seq_along(dim(x))[-ndim]
if (return_refcat) {
x_tosweep <- x
} else {
x_tosweep <- slice(x, ndim, -refcat, drop = FALSE)
}
log(sweep(
x_tosweep,
MARGIN = marg_noncat,
STATS = slice(x, ndim, refcat),
FUN = "/"
))
}
# CDF of the categorical distribution with the softmax transform
# @param q positive integers not greater than ncat
# @param eta the linear predictor (of length or ncol ncat)
# @param log.p return values on the log scale?
pcategorical <- function(q, eta, log.p = FALSE) {
p <- dcategorical(seq_len(max(q)), eta = eta)
out <- cblapply(q, function(j) rowSums(p[, 1:j, drop = FALSE]))
if (log.p) {
out <- log(out)
}
out
}
# density of the multinomial distribution with the softmax transform
# @param x positive integers not greater than ncat
# @param eta the linear predictor (of length or ncol ncat)
# @param log return values on the log scale?
dmultinomial <- function(x, eta, log = FALSE) {
if (is.null(dim(eta))) {
eta <- matrix(eta, nrow = 1)
}
if (length(dim(eta)) != 2L) {
stop2("eta must be a numeric vector or matrix.")
}
log_prob <- log_softmax(eta)
size <- sum(x)
x <- data2draws(x, dim = dim(eta))
out <- lgamma(size + 1) + rowSums(x * log_prob - lgamma(x + 1))
if (!log) {
out <- exp(out)
}
out
}
# density of the cumulative distribution
#
# @param x Integer vector containing response category indices to return the
# "densities" (probability masses) for.
# @param eta Vector (length S, with S denoting the number of posterior draws) of
# linear predictor draws.
# @param thres Matrix (S x `ncat - 1`, with S denoting the number of posterior
# draws and `ncat` denoting the number of response categories) of threshold
# draws.
# @param disc Vector (length S, with S denoting the number of posterior draws,
# or length 1 for recycling) of discrimination parameter draws.
# @param link Character vector (length 1) giving the name of the link function.
#
# @return A matrix (S x `length(x)`) containing the values of the inverse-link
# function applied to `disc * (thres - eta)`.
dcumulative <- function(x, eta, thres, disc = 1, link = "logit") {
eta <- disc * (thres - eta)
if (link == "identity") {
out <- eta
} else {
out <- inv_link_cumulative(eta, link = link)
}
out[, x, drop = FALSE]
}
# generic inverse link function for the cumulative family
#
# @param x Matrix (S x `ncat - 1`, with S denoting the number of posterior draws
# and `ncat` denoting the number of response categories) with values of
# `disc * (thres - eta)` for one observation (see dcumulative()) or an array
# (S x N x `ncat - 1`) containing the same values as the matrix just
# described, but for N observations.
# @param link Character vector (length 1) giving the name of the link function.
#
# @return If `x` is a matrix, then a matrix (S x `ncat`, with S denoting the
# number of posterior draws and `ncat` denoting the number of response
# categories) containing the values of the inverse-link function applied to
# `x`. If `x` is an array, then an array (S x N x `ncat`) containing the same
# values as the matrix just described, but for N observations.
inv_link_cumulative <- function(x, link) {
x <- inv_link(x, link)
ndim <- length(dim(x))
dim_noncat <- dim(x)[-ndim]
ones_arr <- array(1, dim = c(dim_noncat, 1))
zeros_arr <- array(0, dim = c(dim_noncat, 1))
abind::abind(x, ones_arr) - abind::abind(zeros_arr, x)
}
# generic link function for the cumulative family
#
# @param x Matrix (S x `ncat`, with S denoting the number of posterior draws and
# `ncat` denoting the number of response categories) of probabilities for the
# response categories or an array (S x N x `ncat`) containing the same values
# as the matrix just described, but for N observations.
# @param link Character string (length 1) giving the name of the link function.
#
# @return If `x` is a matrix, then a matrix (S x `ncat - 1`, with S denoting the
# number of posterior draws and `ncat` denoting the number of response
# categories) containing the values of the link function applied to `x`. If
# `x` is an array, then an array (S x N x `ncat - 1`) containing the same
# values as the matrix just described, but for N observations.
link_cumulative <- function(x, link) {
ndim <- length(dim(x))
ncat <- dim(x)[ndim]
dim_noncat <- dim(x)[-ndim]
nthres <- dim(x)[ndim] - 1
marg_noncat <- seq_along(dim(x))[-ndim]
dim_t <- c(nthres, dim_noncat)
x <- apply(slice(x, ndim, -ncat, drop = FALSE), marg_noncat, cumsum)
x <- aperm(array(x, dim = dim_t), perm = c(marg_noncat + 1, 1))
link(x, link)
}
# density of the sratio distribution
#
# @param x Integer vector containing response category indices to return the
# "densities" (probability masses) for.
# @param eta Vector (length S, with S denoting the number of posterior draws) of
# linear predictor draws.
# @param thres Matrix (S x `ncat - 1`, with S denoting the number of posterior
# draws and `ncat` denoting the number of response categories) of threshold
# draws.
# @param disc Vector (length S, with S denoting the number of posterior draws,
# or length 1 for recycling) of discrimination parameter draws.
# @param link Character vector (length 1) giving the name of the link function.
#
# @return A matrix (S x `length(x)`) containing the values of the inverse-link
# function applied to `disc * (thres - eta)`.
dsratio <- function(x, eta, thres, disc = 1, link = "logit") {
eta <- disc * (thres - eta)
if (link == "identity") {
out <- eta
} else {
out <- inv_link_sratio(eta, link = link)
}
out[, x, drop = FALSE]
}
# generic inverse link function for the sratio family
#
# @param x Matrix (S x `ncat - 1`, with S denoting the number of posterior draws
# and `ncat` denoting the number of response categories) with values of
# `disc * (thres - eta)` for one observation (see dsratio()) or an array
# (S x N x `ncat - 1`) containing the same values as the matrix just
# described, but for N observations.
# @param link Character vector (length 1) giving the name of the link function.
#
# @return If `x` is a matrix, then a matrix (S x `ncat`, with S denoting the
# number of posterior draws and `ncat` denoting the number of response
# categories) containing the values of the inverse-link function applied to
# `x`. If `x` is an array, then an array (S x N x `ncat`) containing the same
# values as the matrix just described, but for N observations.
inv_link_sratio <- function(x, link) {
x <- inv_link(x, link)
ndim <- length(dim(x))
dim_noncat <- dim(x)[-ndim]
nthres <- dim(x)[ndim]
marg_noncat <- seq_along(dim(x))[-ndim]
ones_arr <- array(1, dim = c(dim_noncat, 1))
dim_t <- c(nthres, dim_noncat)
Sx_cumprod <- aperm(
array(apply(1 - x, marg_noncat, cumprod), dim = dim_t),
perm = c(marg_noncat + 1, 1)
)
abind::abind(x, ones_arr) * abind::abind(ones_arr, Sx_cumprod)
}
# generic link function for the sratio family
#
# @param x Matrix (S x `ncat`, with S denoting the number of posterior draws and
# `ncat` denoting the number of response categories) of probabilities for the
# response categories or an array (S x N x `ncat`) containing the same values
# as the matrix just described, but for N observations.
# @param link Character string (length 1) giving the name of the link function.
#
# @return If `x` is a matrix, then a matrix (S x `ncat - 1`, with S denoting the
# number of posterior draws and `ncat` denoting the number of response
# categories) containing the values of the link function applied to `x`. If
# `x` is an array, then an array (S x N x `ncat - 1`) containing the same
# values as the matrix just described, but for N observations.
link_sratio <- function(x, link) {
ndim <- length(dim(x))
.F_k <- function(k) {
if (k == 1) {
prev_res <- list(F_k = NULL, S_km1_prod = 1)
} else {
prev_res <- .F_k(k - 1)
}
F_k <- slice(x, ndim, k, drop = FALSE) / prev_res$S_km1_prod
.out <- list(
F_k = abind::abind(prev_res$F_k, F_k),
S_km1_prod = prev_res$S_km1_prod * (1 - F_k)
)
return(.out)
}
x <- .F_k(dim(x)[ndim] - 1)$F_k
link(x, link)
}
# density of the cratio distribution
#
# @param x Integer vector containing response category indices to return the
# "densities" (probability masses) for.
# @param eta Vector (length S, with S denoting the number of posterior draws) of
# linear predictor draws.
# @param thres Matrix (S x `ncat - 1`, with S denoting the number of posterior
# draws and `ncat` denoting the number of response categories) of threshold
# draws.
# @param disc Vector (length S, with S denoting the number of posterior draws,
# or length 1 for recycling) of discrimination parameter draws.
# @param link Character vector (length 1) giving the name of the link function.
#
# @return A matrix (S x `length(x)`) containing the values of the inverse-link
# function applied to `disc * (thres - eta)`.
dcratio <- function(x, eta, thres, disc = 1, link = "logit") {
eta <- disc * (eta - thres)
if (link == "identity") {
out <- eta
} else {
out <- inv_link_cratio(eta, link = link)
}
out[, x, drop = FALSE]
}
# generic inverse link function for the cratio family
#
# @param x Matrix (S x `ncat - 1`, with S denoting the number of posterior draws
# and `ncat` denoting the number of response categories) with values of
# `disc * (thres - eta)` for one observation (see dcratio()) or an array
# (S x N x `ncat - 1`) containing the same values as the matrix just
# described, but for N observations.
# @param link Character vector (length 1) giving the name of the link function.
#
# @return If `x` is a matrix, then a matrix (S x `ncat`, with S denoting the
# number of posterior draws and `ncat` denoting the number of response
# categories) containing the values of the inverse-link function applied to
# `x`. If `x` is an array, then an array (S x N x `ncat`) containing the same
# values as the matrix just described, but for N observations.
inv_link_cratio <- function(x, link) {
x <- inv_link(x, link)
ndim <- length(dim(x))
dim_noncat <- dim(x)[-ndim]
nthres <- dim(x)[ndim]
marg_noncat <- seq_along(dim(x))[-ndim]
ones_arr <- array(1, dim = c(dim_noncat, 1))
dim_t <- c(nthres, dim_noncat)
x_cumprod <- aperm(
array(apply(x, marg_noncat, cumprod), dim = dim_t),
perm = c(marg_noncat + 1, 1)
)
abind::abind(1 - x, ones_arr) * abind::abind(ones_arr, x_cumprod)
}
# generic link function for the cratio family
#
# @param x Matrix (S x `ncat`, with S denoting the number of posterior draws and
# `ncat` denoting the number of response categories) of probabilities for the
# response categories or an array (S x N x `ncat`) containing the same values
# as the matrix just described, but for N observations.
# @param link Character string (length 1) giving the name of the link function.
#
# @return If `x` is a matrix, then a matrix (S x `ncat - 1`, with S denoting the
# number of posterior draws and `ncat` denoting the number of response
# categories) containing the values of the link function applied to `x`. If
# `x` is an array, then an array (S x N x `ncat - 1`) containing the same
# values as the matrix just described, but for N observations.
link_cratio <- function(x, link) {
ndim <- length(dim(x))
.F_k <- function(k) {
if (k == 1) {
prev_res <- list(F_k = NULL, F_km1_prod = 1)
} else {
prev_res <- .F_k(k - 1)
}
F_k <- 1 - slice(x, ndim, k, drop = FALSE) / prev_res$F_km1_prod
.out <- list(
F_k = abind::abind(prev_res$F_k, F_k),
F_km1_prod = prev_res$F_km1_prod * F_k
)
return(.out)
}
x <- .F_k(dim(x)[ndim] - 1)$F_k
link(x, link)
}
# density of the acat distribution
#
# @param x Integer vector containing response category indices to return the
# "densities" (probability masses) for.
# @param eta Vector (length S, with S denoting the number of posterior draws) of
# linear predictor draws.
# @param thres Matrix (S x `ncat - 1`, with S denoting the number of posterior
# draws and `ncat` denoting the number of response categories) of threshold
# draws.
# @param disc Vector (length S, with S denoting the number of posterior draws,
# or length 1 for recycling) of discrimination parameter draws.
# @param link Character vector (length 1) giving the name of the link function.
#
# @return A matrix (S x `length(x)`) containing the values of the inverse-link
# function applied to `disc * (thres - eta)`.
dacat <- function(x, eta, thres, disc = 1, link = "logit") {
eta <- disc * (eta - thres)
if (link == "identity") {
out <- eta
} else {
out <- inv_link_acat(eta, link = link)
}
out[, x, drop = FALSE]
}
# generic inverse link function for the acat family
#
# @param x Matrix (S x `ncat - 1`, with S denoting the number of posterior draws
# and `ncat` denoting the number of response categories) with values of
# `disc * (thres - eta)` (see dacat()).
# @param link Character vector (length 1) giving the name of the link function.
#
# @return A matrix (S x `ncat`, with S denoting the number of posterior draws
# and `ncat` denoting the number of response categories) containing the values
# of the inverse-link function applied to `x`.
inv_link_acat <- function(x, link) {
ndim <- length(dim(x))
dim_noncat <- dim(x)[-ndim]
nthres <- dim(x)[ndim]
marg_noncat <- seq_along(dim(x))[-ndim]
ones_arr <- array(1, dim = c(dim_noncat, 1))
dim_t <- c(nthres, dim_noncat)
if (link == "logit") {
# faster evaluation in this case
exp_x_cumprod <- aperm(
array(apply(exp(x), marg_noncat, cumprod), dim = dim_t),
perm = c(marg_noncat + 1, 1)
)
out <- abind::abind(ones_arr, exp_x_cumprod)
} else {
x <- inv_link(x, link)
x_cumprod <- aperm(
array(apply(x, marg_noncat, cumprod), dim = dim_t),
perm = c(marg_noncat + 1, 1)
)
Sx_cumprod_rev <- apply(
1 - slice(x, ndim, rev(seq_len(nthres)), drop = FALSE),
marg_noncat, cumprod
)
Sx_cumprod_rev <- aperm(
array(Sx_cumprod_rev, dim = dim_t),
perm = c(marg_noncat + 1, 1)
)
Sx_cumprod_rev <- slice(
Sx_cumprod_rev, ndim, rev(seq_len(nthres)), drop = FALSE
)
out <- abind::abind(ones_arr, x_cumprod) *
abind::abind(Sx_cumprod_rev, ones_arr)
}
catsum <- array(apply(out, marg_noncat, sum), dim = dim_noncat)
sweep(out, marg_noncat, catsum, "/")
}
# generic link function for the acat family
#
# @param x Matrix (S x `ncat`, with S denoting the number of posterior draws and
# `ncat` denoting the number of response categories) of probabilities for the
# response categories or an array (S x N x `ncat`) containing the same values
# as the matrix just described, but for N observations.
# @param link Character string (length 1) giving the name of the link function.
#
# @return If `x` is a matrix, then a matrix (S x `ncat - 1`, with S denoting the
# number of posterior draws and `ncat` denoting the number of response
# categories) containing the values of the link function applied to `x`. If
# `x` is an array, then an array (S x N x `ncat - 1`) containing the same
# values as the matrix just described, but for N observations.
link_acat <- function(x, link) {
ndim <- length(dim(x))
ncat <- dim(x)[ndim]
x <- slice(x, ndim, -1, drop = FALSE) / slice(x, ndim, -ncat, drop = FALSE)
if (link == "logit") {
# faster evaluation in this case
out <- log(x)
} else {
x <- inv_odds(x)
out <- link(x, link)
}
out
}
# CDF for ordinal distributions
# @param q positive integers not greater than ncat
# @param eta draws of the linear predictor
# @param thres draws of threshold parameters
# @param disc draws of the discrimination parameter
# @param family a character string naming the family
# @param link a character string naming the link
# @return a matrix of probabilities P(x <= q)
pordinal <- function(q, eta, thres, disc = 1, family = NULL, link = "logit") {
family <- as_one_character(family)
link <- as_one_character(link)
args <- nlist(x = seq_len(max(q)), eta, thres, disc, link)
p <- do_call(paste0("d", family), args)
.fun <- function(j) rowSums(as.matrix(p[, 1:j, drop = FALSE]))
cblapply(q, .fun)
}
# helper functions to shift arbitrary distributions
dshifted <- function(dist, x, shift = 0, ...) {
do_call(paste0("d", dist), list(x - shift, ...))
}
pshifted <- function(dist, q, shift = 0, ...) {
do_call(paste0("p", dist), list(q - shift, ...))
}
qshifted <- function(dist, p, shift = 0, ...) {
do_call(paste0("q", dist), list(p, ...)) + shift
}
rshifted <- function(dist, n, shift = 0, ...) {
do_call(paste0("r", dist), list(n, ...)) + shift
}
# validate argument p in q<dist> functions
validate_p_dist <- function(p, lower.tail = TRUE, log.p = FALSE) {
if (log.p) {
p <- exp(p)
}
if (!lower.tail) {
p <- 1 - p
}
if (isTRUE(any(p < 0)) || isTRUE(any(p > 1))) {
p[p < 0 | p > 1] <- NaN
warning2("NaNs produced")
}
p
}
# check if 'n' in r<dist> functions is valid
# @param n number of desired random draws
# @param .. parameter vectors
# @return validated 'n'
check_n_rdist <- function(n, ...) {
n <- as.integer(as_one_numeric(n))
max_len <- max(lengths(list(...)))
if (max_len > 1L) {
if (!n %in% c(1, max_len)) {
stop2("'n' must match the maximum length of the parameter vectors.")
}
n <- max_len
}
n
}
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.