Nothing
#' @title Options for the RoBMA package
#'
#' @description Inspect or change package-level defaults used by RoBMA.
#'
#' @param name a single non-missing character string matching one public option
#' exactly; for available options, see details below.
#' @param ... named option(s) to change. Names must be exact public option names,
#' nonempty, and unique.
#'
#' @details
#' The available options are:
#' \describe{
#' \item{\code{max_cores}}{number of cores to use for parallel computing (default is one fewer than detected logical cores, with a minimum/fallback of 1)}
#' \item{\code{check_scaling}}{whether to check scaling of predictors (default \code{TRUE})}
#' \item{\code{silent}}{whether to suppress output (default \code{FALSE})}
#' \item{\code{autocompute.loo}}{whether to automatically compute LOO (default \code{FALSE})}
#' \item{\code{autocompute.waic}}{whether to automatically compute WAIC (default \code{FALSE})}
#' \item{\code{autocompute.marglik}}{whether to automatically compute marginal likelihood (default \code{FALSE})}
#' \item{\code{cluster_likelihood.n_gamma}}{number of Gauss-Hermite nodes used for cluster-unit log-likelihoods (default \code{15})}
#' \item{\code{default_UISD.effect}}{default scaling of the unit information standard deviation for the effect size parameter (default \code{0.5})}
#' \item{\code{default_UISD.heterogeneity}}{default scaling of the unit information standard deviation for the heterogeneity parameter (default \code{0.25})}
#' \item{\code{default_UISD.mods}}{default scaling of the unit information standard deviation for the moderators (default \code{0.25})}
#' \item{\code{default_UISD.scale}}{default scaling of the unit information standard deviation for the scale parameter (default \code{0.5})}
#' \item{\code{default_informed_priors.mods}}{default scaling of informed priors for moderators (default \code{0.5})}
#' \item{\code{default_informed_priors.scale}}{default scaling of informed priors for the scale parameter (default \code{0.5})}
#' \item{\code{default_bias_weightfunction.alpha}}{default alpha for the weightfunction (default \code{1})}
#' \item{\code{default_bias_PET.scale}}{default scale for the PET (default \code{1})}
#' \item{\code{default_bias_PEESE.scale}}{default scale for the PEESE (default \code{5})}
#' }
#'
#' Boolean options require scalar \code{TRUE} or \code{FALSE} values.
#' \code{max_cores} must be integer-like and at least 1;
#' \code{cluster_likelihood.n_gamma} must be integer-like and at least 3.
#' Scale and alpha defaults must be finite positive numbers.
#'
#' @return `RoBMA.options()` invisibly returns a named list with all current
#' options after applying any changes. `RoBMA.get_option()` returns the current
#' value of the requested option.
#'
#' @export RoBMA.options
#' @export RoBMA.get_option
#' @name RoBMA_options
#' @aliases RoBMA_options RoBMA.options RoBMA.get_option
NULL
#' @rdname RoBMA_options
RoBMA.options <- function(...) {
opts <- list(...)
if (length(opts) > 0 && (is.null(names(opts)) || any(names(opts) == ""))) {
stop("All options must be named.", call. = FALSE)
}
option_names <- RoBMA.private[["option_names"]]
if (length(opts) > 0 && anyDuplicated(names(opts))) {
stop("Option names must be unique.", call. = FALSE)
}
for (i in seq_along(opts)) {
if (!names(opts)[i] %in% option_names) {
stop(paste("Unmatched or ambiguous option '", names(opts)[i], "'", sep = ""), call. = FALSE)
}
value <- .RoBMA_validate_option(names(opts)[i], opts[[i]])
assign(names(opts)[i], value, envir = RoBMA.private)
}
return(invisible(.RoBMA_current_options()))
}
#' @rdname RoBMA_options
RoBMA.get_option <- function(name) {
if (!is.character(name) || length(name) != 1 || is.na(name)) {
stop("Only 1 option can be retrieved at a time", call. = FALSE)
}
option_names <- RoBMA.private[["option_names"]]
if (!name %in% option_names) {
stop(paste("Unmatched or ambiguous option '", name, "'", sep = ""), call. = FALSE)
}
return(RoBMA.private[[name]])
}
# export the function directly to suppress import warnings
.runjags__findjags <- function() runjags::findjags()
# adapted from the runjags package version 2.2.0
RoBMA.private <- new.env()
assign("defaultoptions", list(
jagspath = expression(.runjags__findjags())
), envir = RoBMA.private)
assign("options", RoBMA.private$defaultoptions, envir = RoBMA.private)
assign("RoBMA_version", "notset", envir = RoBMA.private)
assign("min_jags_major", 4, envir = RoBMA.private)
assign("max_jags_major", 4, envir = RoBMA.private)
.RoBMA_default_max_cores <- function() {
cores <- parallel::detectCores(logical = TRUE)
if (length(cores) != 1 || is.na(cores) || !is.finite(cores)) {
return(1L)
}
return(max(1L, as.integer(cores) - 1L))
}
.RoBMA_check_option_bool <- function(value, name) {
if (!is.logical(value) || length(value) != 1 || is.na(value)) {
stop(paste0("Option '", name, "' must be TRUE or FALSE."), call. = FALSE)
}
return(value)
}
.RoBMA_check_option_int <- function(value, name, lower = -Inf) {
if (!is.numeric(value) || length(value) != 1 || is.na(value) ||
!is.finite(value) || value != as.integer(value) || value < lower) {
stop(paste0("Option '", name, "' must be an integer >= ", lower, "."), call. = FALSE)
}
return(as.integer(value))
}
.RoBMA_check_option_real <- function(value, name, lower = -Inf, allow_bound = TRUE) {
if (!is.numeric(value) || length(value) != 1 || is.na(value) || !is.finite(value)) {
stop(paste0("Option '", name, "' must be a finite number."), call. = FALSE)
}
below_bound <- if (allow_bound) value < lower else value <= lower
if (below_bound) {
bound_text <- if (allow_bound) " >= " else " > "
stop(paste0("Option '", name, "' must be", bound_text, lower, "."), call. = FALSE)
}
return(as.numeric(value))
}
.RoBMA_option_schema <- list(
"max_cores" = list(
default = .RoBMA_default_max_cores,
validate = function(value, name) .RoBMA_check_option_int(value, name, lower = 1L)
),
"check_scaling" = list(
default = TRUE,
validate = .RoBMA_check_option_bool
),
"silent" = list(
default = FALSE,
validate = .RoBMA_check_option_bool
),
"autocompute.loo" = list(
default = FALSE,
validate = .RoBMA_check_option_bool
),
"autocompute.waic" = list(
default = FALSE,
validate = .RoBMA_check_option_bool
),
"autocompute.marglik" = list(
default = FALSE,
validate = .RoBMA_check_option_bool
),
"cluster_likelihood.n_gamma" = list(
default = 15L,
validate = function(value, name) .RoBMA_check_option_int(value, name, lower = 3L)
),
"default_UISD.effect" = list(
default = 1/2,
validate = function(value, name) .RoBMA_check_option_real(value, name, lower = 0, allow_bound = FALSE)
),
"default_UISD.heterogeneity" = list(
default = 1/4,
validate = function(value, name) .RoBMA_check_option_real(value, name, lower = 0, allow_bound = FALSE)
),
"default_UISD.mods" = list(
default = 1/4,
validate = function(value, name) .RoBMA_check_option_real(value, name, lower = 0, allow_bound = FALSE)
),
"default_UISD.scale" = list(
default = 1/2,
validate = function(value, name) .RoBMA_check_option_real(value, name, lower = 0, allow_bound = FALSE)
),
"default_informed_priors.mods" = list(
default = 1/2,
validate = function(value, name) .RoBMA_check_option_real(value, name, lower = 0, allow_bound = FALSE)
),
"default_informed_priors.scale" = list(
default = 1/2,
validate = function(value, name) .RoBMA_check_option_real(value, name, lower = 0, allow_bound = FALSE)
),
"default_bias_weightfunction.alpha" = list(
default = 1,
validate = function(value, name) .RoBMA_check_option_real(value, name, lower = 0, allow_bound = FALSE)
),
"default_bias_PET.scale" = list(
default = 1,
validate = function(value, name) .RoBMA_check_option_real(value, name, lower = 0, allow_bound = FALSE)
),
"default_bias_PEESE.scale" = list(
default = 5,
validate = function(value, name) .RoBMA_check_option_real(value, name, lower = 0, allow_bound = FALSE)
)
)
assign("option_names", names(.RoBMA_option_schema), envir = RoBMA.private)
for (option_name in names(.RoBMA_option_schema)) {
default <- .RoBMA_option_schema[[option_name]][["default"]]
value <- if (is.function(default)) default() else default
assign(option_name, value, envir = RoBMA.private)
}
.RoBMA_validate_option <- function(name, value) {
validator <- .RoBMA_option_schema[[name]][["validate"]]
return(validator(value, name))
}
.RoBMA_current_options <- function() {
option_names <- RoBMA.private[["option_names"]]
out <- mget(option_names, envir = RoBMA.private, inherits = FALSE)
return(out)
}
# check and fix number of threads (sometimes bugs out during installation)
.check_max_cores <- function() {
max_cores <- .RoBMA_default_max_cores()
if (RoBMA.private$max_cores > max_cores || RoBMA.private$max_cores < 1) {
RoBMA.options(max_cores = max_cores)
}
}
#' @title Control MCMC fitting process
#'
#' @description \code{set_autofit_control} and \code{set_convergence_checks} control settings for the
#' autofit process of the MCMC JAGS sampler and specify termination criteria and convergence checks.
#'
#' @param max_Rhat maximum value of the R-hat diagnostic.
#' Defaults to \code{1.05}.
#' @param min_ESS minimum estimated sample size.
#' Defaults to \code{500}.
#' @param max_error maximum value of the MCMC error.
#' Defaults to \code{NULL}. Be aware that PEESE publication bias
#' adjustment can have estimates on different scale than the rest of
#' the output, resulting in relatively large max MCMC error.
#' @param max_SD_error maximum value of the proportion of MCMC error
#' of the estimated SD of the parameter.
#' Defaults to \code{NULL}.
#' @param max_time list with the time and unit specifying the maximum
#' autofitting process per model. Passed to \link[base]{difftime} function
#' (possible units are \code{"secs"}, \code{"mins"}, \code{"hours"},
#' \code{"days"}, and \code{"weeks"}).
#' Defaults to \code{list(time = 60, unit = "mins")}.
#' @param sample_extend number of samples to extend the fitting process if
#' the criteria are not satisfied.
#' Defaults to \code{1000}.
#' @param restarts number of times new initial values should be generated in case a
#' model fails to initialize. Defaults to \code{10}.
#' @param max_extend number of times after which the automatic fitting function is stopped.
#' Defaults to \code{10}.
#'
#' @details
#' \code{set_autofit_control} controls the automatic model fitting process, determining when to
#' stop iterating based on convergence criteria. \code{set_convergence_checks} sets thresholds
#' for determining whether a model has converged adequately.
#'
#' The autofit control manages computational resources by setting maximum time limits and
#' determining how many additional samples to draw if convergence criteria are not met.
#' The convergence checks determine the quality standards that fitted models must meet.
#' Autofit thresholds \code{max_Rhat}, \code{min_ESS}, \code{max_error},
#' \code{max_SD_error}, \code{max_time}, \code{restarts}, and
#' \code{max_extend} can be set to \code{NULL}; \code{sample_extend} must be a
#' positive integer.
#' Thresholds can be disabled with \code{NULL}; otherwise \code{max_Rhat} must be
#' at least 1, \code{min_ESS} and \code{max_error} must be nonnegative, and
#' \code{max_SD_error} must be between 0 and 1.
#'
#' @examples
#' # Set custom autofit control with shorter time limit
#' autofit_ctrl <- set_autofit_control(
#' max_time = list(time = 30, unit = "mins"),
#' sample_extend = 2000
#' )
#'
#' # Set custom convergence checks with stricter criteria
#' conv_checks <- set_convergence_checks(
#' max_Rhat = 1.01,
#' min_ESS = 1000
#' )
#'
#' \dontrun{
#' if (requireNamespace("metadat", quietly = TRUE)) {
#' data(dat.lehmann2018, package = "metadat")
#'
#' fit <- RoBMA(
#' yi = yi,
#' vi = vi,
#' data = dat.lehmann2018,
#' measure = "SMD",
#' autofit_control = autofit_ctrl,
#' convergence_checks = conv_checks
#' )
#' }
#' }
#'
#' @return \code{set_autofit_control} returns a list of autofit control settings
#' including \code{max_Rhat}, \code{min_ESS}, \code{max_error},
#' \code{max_SD_error}, \code{max_time}, \code{sample_extend},
#' \code{restarts}, \code{max_extend}, and delegated
#' \code{check_indicators}.
#' \code{set_convergence_checks} returns a list with convergence thresholds
#' \code{max_Rhat}, \code{min_ESS}, \code{max_error}, \code{max_SD_error},
#' and delegated \code{check_indicators}.
#'
#' @export set_autofit_control
#' @export set_convergence_checks
#' @name RoBMA_control
#' @aliases set_autofit_control set_convergence_checks
#'
#' @seealso [RoBMA()]
NULL
#' @rdname RoBMA_control
set_autofit_control <- function(max_Rhat = 1.05, min_ESS = 500, max_error = NULL, max_SD_error = NULL, max_time = list(time = 60, unit = "mins"), sample_extend = 1000, restarts = 10, max_extend = 10){
autofit_settings <- list(
max_Rhat = max_Rhat,
min_ESS = min_ESS,
max_error = max_error,
max_SD_error = max_SD_error,
max_time = max_time,
sample_extend = sample_extend,
restarts = restarts,
max_extend = max_extend
)
autofit_settings <- BayesTools::JAGS_check_and_list_autofit_settings(autofit_settings, call = "Checking 'autofit_control':\n\t")
return(autofit_settings)
}
#' @rdname RoBMA_control
set_convergence_checks <- function(max_Rhat = 1.05, min_ESS = 500, max_error = NULL, max_SD_error = NULL){
convergence_checks <- list(
max_Rhat = max_Rhat,
min_ESS = min_ESS,
max_error = max_error,
max_SD_error = max_SD_error
)
# allows NULL arguments so it can be used in this way too
convergence_checks <- .check_and_list_convergence_checks(convergence_checks)
return(convergence_checks)
}
# Internal control parameter update functions:
# Purpose: Merge user-specified parameters with existing control settings
# Used for updating model fitting, convergence checking, and autofit parameters
# LLM Note: These functions implement parameter inheritance for model updates
# Update MCMC fitting control parameters:
# Handles chains, adaptation, burnin, sampling parameters for JAGS
.update_fit_control <- function(old_fit_control, chains, adapt, burnin, sample, thin, autofit, parallel, cores, silent, seed){
if(is.null(chains)){
chains <- old_fit_control[["chains"]]
}
if(is.null(adapt)){
adapt <- old_fit_control[["adapt"]]
}
if(is.null(burnin)){
burnin <- old_fit_control[["burnin"]]
}
if(is.null(sample)){
sample <- old_fit_control[["sample"]]
}
if(is.null(thin)){
thin <- old_fit_control[["thin"]]
}
if(is.null(autofit)){
autofit <- old_fit_control[["autofit"]]
}
if(is.null(parallel)){
parallel <- old_fit_control[["parallel"]]
}
if(is.null(cores)){
cores <- old_fit_control[["cores"]]
}
if(is.null(silent)){
silent <- old_fit_control[["silent"]]
}
if(is.null(seed)){
seed <- old_fit_control[["seed"]]
}
new_fit_control <- BayesTools::JAGS_check_and_list_fit_settings(chains = chains, adapt = adapt, burnin = burnin, sample = sample, thin = thin, autofit = autofit, parallel = parallel, cores = cores, silent = silent, seed = seed)
return(new_fit_control)
}
.update_autofit_control <- function(old_autofit_control, autofit_control){
if(!is.null(autofit_control[["max_Rhat"]])){
max_Rhat <- autofit_control[["max_Rhat"]]
}else{
max_Rhat <- old_autofit_control[["max_Rhat"]]
}
if(!is.null(autofit_control[["min_ESS"]])){
min_ESS <- autofit_control[["min_ESS"]]
}else{
min_ESS <- old_autofit_control[["min_ESS"]]
}
if(!is.null(autofit_control[["max_error"]])){
max_error <- autofit_control[["max_error"]]
}else{
max_error <- old_autofit_control[["max_error"]]
}
if(!is.null(autofit_control[["max_SD_error"]])){
max_SD_error <- autofit_control[["max_SD_error"]]
}else{
max_SD_error <- old_autofit_control[["max_SD_error"]]
}
if(!is.null(autofit_control[["max_time"]])){
max_time <- autofit_control[["max_time"]]
}else{
max_time <- old_autofit_control[["max_time"]]
}
if(!is.null(autofit_control[["sample_extend"]])){
sample_extend <- autofit_control[["sample_extend"]]
}else{
sample_extend <- old_autofit_control[["sample_extend"]]
}
if(!is.null(autofit_control[["restarts"]])){
restarts <- autofit_control[["restarts"]]
}else{
restarts <- old_autofit_control[["restarts"]]
}
if(!is.null(autofit_control[["max_extend"]])){
max_extend <- autofit_control[["max_extend"]]
}else{
max_extend <- old_autofit_control[["max_extend"]]
}
new_autofit_control <- set_autofit_control(max_Rhat = max_Rhat, min_ESS = min_ESS, max_error = max_error, max_SD_error = max_SD_error, max_time = max_time, sample_extend = sample_extend, restarts = restarts, max_extend = max_extend)
new_autofit_control <- BayesTools::JAGS_check_and_list_autofit_settings(autofit_control = new_autofit_control)
return(new_autofit_control)
}
.update_convergence_checks <- function(old_convergence_checks, convergence_checks){
if(!is.null(convergence_checks[["max_Rhat"]])){
max_Rhat <- convergence_checks[["max_Rhat"]]
}else{
max_Rhat <- old_convergence_checks[["max_Rhat"]]
}
if(!is.null(convergence_checks[["min_ESS"]])){
min_ESS <- convergence_checks[["min_ESS"]]
}else{
min_ESS <- old_convergence_checks[["min_ESS"]]
}
if(!is.null(convergence_checks[["max_error"]])){
max_error <- convergence_checks[["max_error"]]
}else{
max_error <- old_convergence_checks[["max_error"]]
}
if(!is.null(convergence_checks[["max_SD_error"]])){
max_SD_error <- convergence_checks[["max_SD_error"]]
}else{
max_SD_error <- old_convergence_checks[["max_SD_error"]]
}
new_convergence_checks <- set_convergence_checks(max_Rhat = max_Rhat, min_ESS = min_ESS, max_error = max_error, max_SD_error = max_SD_error)
new_convergence_checks <- .check_and_list_convergence_checks(new_convergence_checks)
return(new_convergence_checks)
}
.check_and_list_convergence_checks <- function(convergence_checks){
convergence_checks <- BayesTools::JAGS_check_and_list_autofit_settings(convergence_checks, skip_sample_extend = TRUE, call = "Checking 'convergence_checks':\n\t")
return(convergence_checks)
}
# Apply shared metafor-style defaults for observed-study point markers.
.plot_point_style_defaults <- function(dots) {
if (is.null(dots[["pch"]])) dots[["pch"]] <- 21
if (is.null(dots[["col"]])) dots[["col"]] <- "black"
if (is.null(dots[["bg"]])) dots[["bg"]] <- "#A6A6A6"
if (is.null(dots[["cex"]])) dots[["cex"]] <- 1
if (is.null(dots[["size"]])) dots[["size"]] <- 2
return(dots)
}
.normalize_max_samples <- function(max_samples, argument = "max_samples",
minimum = 10L) {
valid <- is.numeric(max_samples) &&
length(max_samples) == 1L &&
!is.na(max_samples) &&
max_samples > 0
if (!valid || (!is.infinite(max_samples) && max_samples != floor(max_samples))) {
stop("'", argument, "' must be a single positive integer or Inf.", call. = FALSE)
}
if (!is.infinite(max_samples) && max_samples < minimum) {
stop("'", argument, "' must be at least ", minimum, ".", call. = FALSE)
}
if (is.infinite(max_samples)) {
return(Inf)
}
return(as.integer(max_samples))
}
.thin_sample_rows <- function(n_samples, max_samples) {
if (is.infinite(max_samples) || n_samples <= max_samples) {
return(NULL)
}
return(unique(round(seq(
from = 1,
to = n_samples,
length.out = as.integer(max_samples)
))))
}
.thin_sample_rows_by_group <- function(group, max_samples) {
n_samples <- length(group)
if (is.infinite(max_samples) || n_samples <= max_samples) {
return(NULL)
}
if (anyNA(group)) {
stop("'group' must not contain missing values.", call. = FALSE)
}
max_samples <- as.integer(max_samples)
group <- factor(group)
group_rows <- split(seq_len(n_samples), group)
group_n <- as.integer(vapply(group_rows, length, integer(1)))
raw_n <- group_n / n_samples * max_samples
selected_n <- floor(raw_n)
remaining <- max_samples - sum(selected_n)
while (remaining > 0L && any(selected_n < group_n)) {
available <- which(selected_n < group_n)
remainders <- raw_n - selected_n
add_to <- available[which.max(remainders[available])]
selected_n[add_to] <- selected_n[add_to] + 1L
remaining <- remaining - 1L
}
if (max_samples >= length(group_n) && any(selected_n == 0L)) {
for (i in which(selected_n == 0L)) {
donors <- which(selected_n > 1L)
if (length(donors) == 0L) {
break
}
donor <- donors[which.max(selected_n[donors])]
selected_n[donor] <- selected_n[donor] - 1L
selected_n[i] <- 1L
}
}
rows <- integer(0)
for (i in seq_along(group_rows)) {
if (selected_n[i] == 0L) {
next
}
selected <- group_rows[[i]][round(seq(
from = 1,
to = length(group_rows[[i]]),
length.out = selected_n[i]
))]
rows <- c(rows, selected)
}
return(sort(unique(rows)))
}
.check_plot_numeric <- function(x, argument, check_length = NULL,
lower = -Inf, upper = Inf,
allow_null = TRUE) {
if (is.null(x)) {
if (allow_null) {
return(invisible(TRUE))
}
stop("'", argument, "' must not be NULL.", call. = FALSE)
}
if (!is.numeric(x) || length(x) == 0L || anyNA(x) || any(!is.finite(x))) {
stop("'", argument, "' must be a finite numeric vector.", call. = FALSE)
}
if (!is.null(check_length) && length(x) != check_length) {
stop("'", argument, "' must have length ", check_length, ".", call. = FALSE)
}
if (any(x < lower) || any(x > upper)) {
stop("'", argument, "' must be between ", lower, " and ", upper, ".", call. = FALSE)
}
return(invisible(TRUE))
}
.check_plot_limits <- function(x, argument) {
.check_plot_numeric(x, argument, check_length = 2, allow_null = TRUE)
if (is.null(x)) {
return(invisible(TRUE))
}
if (x[1] >= x[2]) {
stop("'", argument, "' must be an increasing numeric vector of length 2.", call. = FALSE)
}
return(invisible(TRUE))
}
.check_plot_label <- function(x, argument) {
if (is.null(x)) {
return(invisible(TRUE))
}
if (is.character(x) && length(x) == 1L && !is.na(x)) {
return(invisible(TRUE))
}
if (is.expression(x) && length(x) == 1L) {
return(invisible(TRUE))
}
stop("'", argument, "' must be a single character string or expression.", call. = FALSE)
}
.check_plot_function <- function(x, argument) {
if (!is.null(x) && !is.function(x)) {
stop("'", argument, "' must be a function or NULL.", call. = FALSE)
}
return(invisible(TRUE))
}
.check_plot_list <- function(x, argument) {
if (!is.null(x) && !is.list(x)) {
stop("'", argument, "' must be a list or NULL.", call. = FALSE)
}
return(invisible(TRUE))
}
.check_plot_positive_scalar <- function(x, argument, allow_zero = FALSE) {
.check_plot_numeric(x, argument, check_length = 1, lower = 0, allow_null = FALSE)
if (!allow_zero && x <= 0) {
stop("'", argument, "' must be positive.", call. = FALSE)
}
return(invisible(TRUE))
}
Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.