Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Speed up #119

Merged
merged 11 commits into from
Sep 13, 2024
4 changes: 2 additions & 2 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
# R specific hooks: https://github.com/lorenzwalthert/precommit
repos:
- repo: https://github.com/lorenzwalthert/precommit
rev: v0.4.2
rev: v0.4.3
hooks:
- id: style-files
args: [--style_pkg=styler, --style_fun=tidyverse_style]
Expand Down Expand Up @@ -91,6 +91,6 @@ repos:
files: '\.Rhistory|\.RData|\.Rds|\.rds$'
# `exclude: <regex>` to allow committing specific files.
- repo: https://github.com/igorshubovych/markdownlint-cli
rev: v0.40.0
rev: v0.41.0
hooks:
- id: markdownlint
1 change: 1 addition & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ importFrom(stats,dbeta)
importFrom(stats,dbinom)
importFrom(stats,integrate)
importFrom(stats,optimize)
importFrom(stats,pbeta)
importFrom(stats,quantile)
importFrom(stats,rbeta)
importFrom(stats,rbinom)
Expand Down
86 changes: 58 additions & 28 deletions R/dbetabinom.R
Original file line number Diff line number Diff line change
Expand Up @@ -124,14 +124,20 @@ dbetaMix <- function(x, par, weights, log = FALSE) {
assert_numeric(weights, lower = 0, upper = 1, finite = TRUE, any.missing = FALSE)
assert_true(all.equal(sum(weights), 1))
assert_true(identical(length(weights), nrow(par)))
ret <- sum(weights * dbeta(x, par[, 1], par[, 2]))
degree <- length(weights)

component_densities <- matrix(
dbeta(rep(x, each = degree), par[, 1], par[, 2]),
nrow = degree,
ncol = length(x)
)
ret <- as.numeric(weights %*% component_densities)
if (log) {
log(ret)
} else {
ret
}
}
dbetaMix <- Vectorize(dbetaMix, vectorize.args = "x")


#' Beta-Mixture CDF
Expand All @@ -150,20 +156,34 @@ dbetaMix <- Vectorize(dbetaMix, vectorize.args = "x")
#' @typed lower.tail : flag
#' if `TRUE` (default), probabilities are `P[X <= x]`,
#' and otherwise `P[X > x]`.
#' @typed skipchecks : flag
gravesti marked this conversation as resolved.
Show resolved Hide resolved
#' Don't check arguments `q`, `weights`, `par`, `lower.tail`.
#' Useful to speed up execution within functions where values
#' are known to be correct.
#' @return The (one minus) cdf value
#'
#' @note `q` can be a vector.
#'
#' @example examples/pbetaMix.R
#' @importFrom stats pbeta
#' @export
pbetaMix <- function(q, par, weights, lower.tail = TRUE) {
assert_number(q, lower = 0, upper = 1, finite = TRUE)
assert_numeric(weights, lower = 0, upper = 1, finite = TRUE)
assert_matrix(par)
assert_flag(lower.tail)
sum(weights * pbeta(q, par[, 1], par[, 2], lower.tail = lower.tail))
pbetaMix <- function(q, par, weights, lower.tail = TRUE, skipchecks = FALSE) {
if (isFALSE(skipchecks)) {
assert_numeric(q, lower = 0, upper = 1, finite = TRUE)
assert_numeric(weights, lower = 0, upper = 1, finite = TRUE)
assert_matrix(par)
assert_flag(lower.tail)
}

degree <- length(weights)
component_p <- matrix(
pbeta(rep(q, each = degree), par[, 1], par[, 2], lower.tail = lower.tail),
nrow = degree,
ncol = length(q)
)

as.numeric(weights %*% component_p)
}
pbetaMix <- Vectorize(pbetaMix, vectorize.args = "q")


#' Beta-Mixture Quantile Function
Expand All @@ -186,23 +206,33 @@ pbetaMix <- Vectorize(pbetaMix, vectorize.args = "q")
#' @example examples/qbetaMix.R
#' @export
qbetaMix <- function(p, par, weights, lower.tail = TRUE) {
f <- function(pi) {
pbetaMix(q = pi, par = par, weights = weights, lower.tail = lower.tail) - p
}
# Note: we give the lower and upper function values here in order to avoid problems for
# p = 0 or p = 1.
unirootResult <- uniroot(
f,
lower = 0, upper = 1,
f.lower = -p, f.upper = 1 - p,
tol = sqrt(.Machine$double.eps) # Increase the precision over default `tol`.
)
if (unirootResult$iter < 0) {
NA
} else {
assert_number(unirootResult$root)
assert_true(all.equal(f(unirootResult$root), 0, tolerance = 0.001))
unirootResult$root
}
assert_numeric(p, lower = 0, upper = 1)
assert_numeric(weights, lower = 0, upper = 1, finite = TRUE)
assert_matrix(par)
assert_flag(lower.tail)

grid <- seq(0, 1, len = 31)
f_grid <- pbetaMix(grid, par, weights, lower.tail = lower.tail, skipchecks = TRUE)

gravesti marked this conversation as resolved.
Show resolved Hide resolved
sapply(p, function(p) {
# special cases
if (p == 0) {
return(0)
}
if (p == 1) {
return(1)
}

diff <- f_grid - p
pos <- diff > 0
grid_interval <- c(grid[!pos][which.max(diff[!pos])], grid[pos][which.min(diff[pos])])

uniroot(
f = function(q) pbetaMix(q, par, weights, lower.tail = lower.tail, skipchecks = TRUE) - p,
interval = grid_interval,
f.lower = -p,
f.upper = 1 - p,
tol = sqrt(.Machine$double.eps)
)$root
})
}
qbetaMix <- Vectorize(qbetaMix, vectorize.args = "p")
20 changes: 13 additions & 7 deletions R/postprob.R
Original file line number Diff line number Diff line change
Expand Up @@ -81,18 +81,25 @@ postprob <- function(x, n, p, parE = c(1, 1), weights, betamixPost, log.p = FALS
if (missing(weights)) {
weights <- rep(1, nrow(parE))
}
betamixPost <- h_getBetamixPost(
x = x,
betamixPost <- lapply(
x,
h_getBetamixPost,
n = n,
par = parE,
weights = weights
)
} else {
assert_list(betamixPost)
assert_names(names(betamixPost), identical.to = c("par", "weights"))
betamixPost <- list(betamixPost)
}
assert_list(betamixPost)
assert_names(names(betamixPost), identical.to = c("par", "weights"))
ret <- with(

ret <- vapply(
betamixPost,
pbetaMix(q = p, par = par, weights = weights, lower.tail = FALSE)
FUN = function(bmp) {
pbetaMix(q = p, par = bmp$par, weights = bmp$weights, lower.tail = FALSE, skipchecks = TRUE)
},
FUN.VALUE = numeric(length(p))
)

if (log.p) {
Expand All @@ -101,4 +108,3 @@ postprob <- function(x, n, p, parE = c(1, 1), weights, betamixPost, log.p = FALS
ret
}
}
postprob <- Vectorize(postprob, vectorize.args = "x")
50 changes: 25 additions & 25 deletions R/postprobDist.R
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ postprobDist <- function(x,
if (missing(weightsS)) {
weightsS <- rep(1, nrow(parS))
}
assert_number(n, lower = x, finite = TRUE)
assert_number(n, lower = min(x), finite = TRUE)
assert_numeric(x, lower = 0, upper = n, finite = TRUE)
assert_number(nS, lower = 0, finite = TRUE)
assert_number(xS, lower = 0, upper = nS, finite = TRUE)
Expand All @@ -174,10 +174,10 @@ postprobDist <- function(x,
assert_numeric(weightsS, lower = 0, finite = TRUE)
assert_numeric(parE, lower = 0, finite = TRUE)
assert_numeric(parS, lower = 0, finite = TRUE)
activeBetamixPost <- h_getBetamixPost(x = x, n = n, par = parE, weights = weights)

activeBetamixPost <- lapply(x, function(x) h_getBetamixPost(x = x, n = n, par = parE, weights = weights))

controlBetamixPost <- h_getBetamixPost(x = xS, n = nS, par = parS, weights = weightsS)
assert_names(names(activeBetamixPost), identical.to = c("par", "weights"))
assert_names(names(controlBetamixPost), identical.to = c("par", "weights"))
if (relativeDelta) {
epsilon <- .Machine$double.xmin
integrand <- h_integrand_relDelta
Expand All @@ -186,26 +186,26 @@ postprobDist <- function(x,
integrand <- h_integrand
}
bounds <- h_get_bounds(controlBetamixPost = controlBetamixPost)
intRes <- integrate(
f = integrand,
lower =
max(
bounds[1],
ifelse(relativeDelta, 0, 0 - delta)
),
upper =
min(
ifelse(relativeDelta, 1, 1 - delta),
bounds[2]
),
delta = delta,
activeBetamixPost = activeBetamixPost,
controlBetamixPost = controlBetamixPost

integral_results <- lapply(
seq_along(x),
function(i, this_posterior = activeBetamixPost, input_x = x) {
intRes <- integrate(
f = integrand,
lower = max(bounds[1], ifelse(relativeDelta, 0, 0 - delta)),
upper = min(ifelse(relativeDelta, 1, 1 - delta), bounds[2]),
delta = delta,
activeBetamixPost = this_posterior[[i]],
controlBetamixPost = controlBetamixPost
)
if (intRes$message == "OK") {
intRes$value
} else {
warning("Integration failed for posterior based on x =", input_x[i], "\n", intRes$message)
NA_real_
}
}
)
if (intRes$message == "OK") {
intRes$value
} else {
stop(intRes$message)
}

unlist(integral_results)
}
postprobDist <- Vectorize(postprobDist, vectorize.args = "x")
10 changes: 10 additions & 0 deletions examples/postprob.R
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,13 @@ postprob(
),
weights = c(0.6, 0.4)
)

postprob(
x = 0:23, n = 23, p = 0.60,
par =
rbind(
c(0.6, 0.4),
c(1, 1)
),
weights = c(0.6, 0.4)
)
6 changes: 5 additions & 1 deletion man/pbetaMix.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions man/postprob.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

43 changes: 43 additions & 0 deletions tests/testthat/test-dbetabinom.R
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,23 @@ test_that("pbetaMix gives the correct number result with beta-mixture", {
expect_equal(result, 0.4768404, tolerance = 1e-5)
})

test_that("pbetaMix works for edge cases", {
result_ushape <- pbetaMix(
q = c(0, 1),
par = rbind(c(0.2, 0.4), c(3, .3)),
weights = c(0.6, 0.4)
)
expect_equal(result_ushape, c(0, 1))

result_vshape <- pbetaMix(
q = c(0, 1),
par = rbind(c(9, 4), c(1, 1)),
weights = c(0.6, 0.4)
)
expect_equal(result_vshape, c(0, 1))
})


test_that("The complement of pbetaMix can be derived with a different lower.tail flag", {
result <- pbetaMix(
q = 0.3,
Expand Down Expand Up @@ -184,6 +201,32 @@ test_that("dbetaMix gives the correct result as dbeta", {
expect_equal(result, result2, tolerance = 1e-4)
})

test_that("dbetaMix handles edge cases", {
result_inf <- dbetaMix(
x = c(0, 1), par = rbind(c(0.2, 0.4), c(1, 1)),
weights = c(0.6, 0.4)
)
expect_equal(result_inf, c(Inf, Inf))

result_finite <- dbetaMix(
x = c(0, 1), par = rbind(c(2, 4), c(1, 1)),
weights = c(0.6, 0.4)
)
expect_equal(result_finite, c(0.4, 0.4))

result_right <- dbetaMix(
x = c(0, 1), par = rbind(c(0, 4), c(1, 1)),
weights = c(0.6, 0.4)
)
expect_equal(result_right, c(Inf, 0.4))

result_right <- dbetaMix(
x = c(NA, 1), par = rbind(c(0, 4), c(1, 1)),
weights = c(0.6, 0.4)
)
expect_equal(result_right, c(NA, 0.4))
})

# h_getBetamixPost ----

test_that("h_getBetamixPost gives the correct beta-mixture parameters", {
Expand Down
Loading