Nothing
#' HespDiv Sensitivity Analysis
#'
#' This function performs sensitivity analysis of the \code{hespdiv} method.
#' Starting from a reference \code{hespdiv} object, it generates a specified
#' number of alternative \code{hespdiv} calls by randomly sampling new values
#' for selected arguments from user-provided sets.
#'
#' @param obj A \code{hespdiv} class object.
#'
#' @param n.runs Integer. The number of alternative \code{hespdiv} calls to evaluate.
#'
#' @param parallel Logical. If \code{TRUE}, runs are evaluated in parallel using
#' \pkg{future.apply}. If \code{FALSE} (default), runs are evaluated sequentially.
#'
#' @param workers Integer. Number of parallel workers (CPU cores) to use when
#' \code{parallel = TRUE}. At most this many \code{hespdiv} calls will be executed
#' simultaneously. If \code{NULL}, the number of workers is determined
#' automatically based on \code{RAM} and \code{load_prop}.
#'
#' @param RAM Integer. Approximate amount of available RAM (in GB) used to limit
#' the number of parallel workers when \code{parallel = TRUE}.
#'
#' @param load_prop Numeric in (0, 1]. Proportion of available CPU cores to use
#' when determining the number of parallel workers automatically. Defaults to 0.8.
#'
#' @param chunk_size Integer. Number of runs submitted per batch (chunk) to the
#' parallel backend when \code{parallel = TRUE}. Chunking does not change how
#' many runs execute simultaneously (controlled by \code{workers}). Smaller
#' values reduce the number of queued futures at once (often lowering RAM
#' overhead). Defaults to \code{workers*2}.
#'
#' @param future_seed Logical. Passed to \code{future.apply::future_lapply()}.
#' If \code{TRUE}, RNG is managed by \pkg{future} to provide parallel-safe,
#' reproducible random streams.
#'
#' @param data.paired Logical. Controls whether alternative values of \code{data}
#' are paired with corresponding elements of \code{xy.dat}.
#'
#' @param display Logical. Controls the value of the \code{display} argument
#' in each \code{hespdiv} call.
#'
#' @param images.path Character or \code{NULL}. Path to an existing directory
#' where PNG images of displayed results will be saved. If \code{NULL}
#' (default), images are not saved.
#'
#' @param pnts.col Value passed to the \code{pnts.col} argument of each
#' \code{hespdiv} call. Can be provided as atomic vector, vector or list of
#' vectors.
#'
#' @param data A list of data objects (matrices, data frames, vectors, lists,
#' or other supported data structures) used as alternative inputs for
#' sensitivity analysis.
#'
#' @param xy.dat,study.pol Lists of data frames with two columns: \code{x} and \code{y}.
#'
#' @param same.n.split,c.fast.optim,use.chull,c.splits Logical vectors specifying
#' alternative values for corresponding \code{hespdiv} arguments.
#'
#' @param n.split.pts,c.max.iter.no,N.crit,N.loc.crit,c.X.knots,c.Y.knots
#' Integer vectors specifying alternative values for corresponding arguments.
#'
#' @param N.rel.crit,N.loc.rel.crit,S.crit,S.rel.crit Numeric vectors with values
#' between 0 and 1.
#'
#' @param Q.crit,c.Q.crit,c.crit.improv Numeric vectors specifying alternative
#' threshold or improvement criteria.
#'
#' @param c.corr.term Numeric vector with values between 0.01 and 0.2.
#'
#' @param generalize.f,compare.f Lists of functions defining custom similarity
#' or generalization methods.
#'
#' @param maximize Logical vector of the same length as \code{compare.f},
#' indicating whether the corresponding similarity metric should be maximized.
#'
#' @param method Character vector specifying predefined similarity metrics.
#'
#' @param .run.id Integer. Runs with indices less than or equal to this value
#' will be skipped. This can be used to resume an interrupted analysis.
#'
#' @return An object of class \code{hsa}, containing:
#' \describe{
#' \item{\bold{Alternatives}}{A list of alternative \code{hespdiv} results
#' produced during the sensitivity analysis.}
#' \item{\bold{Basis}}{The reference \code{hespdiv} object whose arguments
#' were perturbed.}
#' }
#'
#' @details
#' \subsection{Difference Between \code{"hsa"} and other sensitivity analysis functions}{
#' The \code{hsa_detailed} function evaluates all combinations of provided
#' argument values, resulting in dense sampling of the parameter space at
#' substantial computational cost. In contrast, \code{hsa} samples the parameter
#' space stochastically and is generally more suitable for exploratory or
#' large-scale sensitivity analyses.
#'
#' In \code{hsa_detailed}, alternative argument values are provided as lists,
#' whereas in \code{hsa} they are supplied as vectors or lists depending on
#' the argument.
#'
#'
#' The \code{hsa_sample_constrained} function performs non-recursive \code{hespdiv}
#' runs for each split-line produced based on different data subsamples. Thus,
#' \code{hsa} is more general, as it allows to inspect sensitivity to other arguments.
#' }
#'
#' \subsection{Paired Arguments}{
#' When \code{data.paired = TRUE}, the same index is used to sample elements
#' of \code{data} and \code{xy.dat}, allowing sensitivity analysis across
#' datasets of differing size or composition. When \code{FALSE}, data and
#' coordinates are sampled independently, enabling analyses based on noise
#' addition or spatial shuffling.
#'
#' Arguments defining custom methods (\code{compare.f}, \code{generalize.f},
#' \code{maximize}) are always treated as paired and must therefore have
#' equal lengths.
#' }
#'
#' @note
#' If a particular run produces a warning or an error, the corresponding list
#' element will contain two components. In case of a warning, these are the
#' resulting \code{hespdiv} object and the warning message. In case of an error,
#' they are the arguments used for the call and the error message.
#'
#' @family functions for hespdiv sensitivity analysis
#' @family functions for hespdiv results post-processing
#'
#' @export
hsa <- function(obj,
n.runs = 100,
data.paired = TRUE,
display = FALSE,
images.path = NULL,
pnts.col = 1,
data = NULL,
xy.dat = NULL,
same.n.split = NULL,
n.split.pts = NULL,
N.crit = NULL,
N.rel.crit = NULL,
N.loc.crit = NULL,
N.loc.rel.crit = NULL,
S.crit = NULL,
S.rel.crit = NULL,
Q.crit = NULL,
c.splits = NULL,
c.Q.crit = NULL,
c.crit.improv = NULL,
c.X.knots = NULL,
c.Y.knots = NULL,
c.max.iter.no = NULL,
c.fast.optim = NULL,
c.corr.term = NULL,
study.pol = NULL,
use.chull = NULL,
generalize.f = NULL,
maximize = NULL,
method = NULL,
compare.f = NULL,
.run.id = NULL,
parallel = FALSE,
RAM = NULL,
load_prop = 0.8,
chunk_size = workers*2,
workers = NULL,
future_seed = TRUE) {
eval_one <- function(i, d) {
if (!is.null(images.path)) {
fn <- file.path(images.path, sprintf("hsa_run_%04d.png", i))
png(fn)
on.exit(dev.off(), add = TRUE)
}
call_args <- quote(
hespdiv(
data = data,
xy.dat = xy.dat,
same.n.split = same.n.split,
n.split.pts = n.split.pts,
generalize.f = generalize.f,
maximize = maximize,
compare.f = compare.f,
method = method,
N.crit = N.crit,
N.rel.crit = N.rel.crit,
N.loc.crit = N.loc.crit,
N.loc.rel.crit = N.loc.rel.crit,
S.crit = S.crit,
S.rel.crit = S.rel.crit,
Q.crit = Q.crit,
c.splits = c.splits,
c.Q.crit = c.Q.crit,
c.crit.improv = c.crit.improv,
c.X.knots = c.X.knots,
c.Y.knots = c.Y.knots,
c.max.iter.no = c.max.iter.no,
c.fast.optim = c.fast.optim,
c.corr.term = c.corr.term,
use.chull = use.chull,
study.pol = study.pol,
tracing = NULL,
pnts.col = pnts.col,
display = display,
pacific.region = pacific.region
)
)
env <- list2env(c(d, list(display = display, pacific.region = pacific.region)),
parent = parent.frame())
out <- tryCatch(
eval(call_args, envir = env),
warning = function(w) {
list(Subdivision = suppressWarnings(eval(call_args, envir = env)),
warning = w)
},
error = function(e) {
list(Arguments = as.list(d), error = e)
}
)
list(Subdivison = out, Arguments = as.list(d))
}
draw_one <- function(i) {
p.id <- sample.int(dat_len, 1)
local_use_m <- use_m
if (m.compete) local_use_m <- sample(0:1, 1, prob = c(length(maximize), length(method)))
if (!local_use_m) cm.id <- sample.int(len_cm, 1) else cm.id <- NA_integer_
v1 <- data[[p.id]]
v2 <- if (data.paired) xy.dat[[p.id]] else sample(xy.dat, 1)[[1]]
v3 <- same.n.split[sample.int(length(same.n.split), 1)]
v4 <- n.split.pts[sample.int(length(n.split.pts), 1)]
v5 <- .ifelse(!local_use_m, generalize.f[[cm.id]], NULL)
v6 <- .ifelse(!local_use_m, maximize[cm.id], NULL)
v7 <- .ifelse(!local_use_m, compare.f[[cm.id]], NULL)
v8 <- .ifelse(local_use_m, sample(method, 1), NULL)
v9 <- N.crit[sample.int(length(N.crit), 1)]
v10 <- N.rel.crit[sample.int(length(N.rel.crit), 1)]
v11 <- N.loc.crit[sample.int(length(N.loc.crit), 1)]
v12 <- N.loc.rel.crit[sample.int(length(N.loc.rel.crit), 1)]
v13 <- S.crit[sample.int(length(S.crit), 1)]
v14 <- S.rel.crit[sample.int(length(S.rel.crit), 1)]
v15 <- .ifelse(local_use_m,
ifelse(v8 == "pielou",
(Q.crit[Q.crit < 1])[
sample.int(length(Q.crit[Q.crit < 1]), 1)
],
(Q.crit[Q.crit > 0])[
sample.int(length(Q.crit[Q.crit > 0]), 1)
]),
NULL)
v16 <- c.splits[sample.int(length(c.splits), 1)]
is.maxim <- ifelse(local_use_m, v8 == "pielou", v6)
if (is.null(v15)) {
v17 <- NULL
} else {
v17 <- .ifelse(v16,
ifelse(is.maxim, (c.Q.crit[c.Q.crit <= v15])[
sample.int(length(c.Q.crit[c.Q.crit <= v15]), 1)
],
(c.Q.crit[c.Q.crit >= v15])[
sample.int(length(c.Q.crit[c.Q.crit >= v15]), 1)
]),
NULL)
}
v18 <- .ifelse(v16, c.crit.improv[sample.int(length(c.crit.improv), 1)], NULL)
v19 <- .ifelse(v16, c.X.knots[sample.int(length(c.X.knots), 1)], NULL)
v20 <- .ifelse(v16, c.Y.knots[sample.int(length(c.Y.knots), 1)], NULL)
v21 <- .ifelse(v16, c.max.iter.no[sample.int(length(c.max.iter.no), 1)], NULL)
v22 <- .ifelse(v16, c.fast.optim[sample.int(length(c.fast.optim), 1)], NULL)
v23 <- .ifelse(v16, c.corr.term[sample.int(length(c.corr.term), 1)], NULL)
v24 <- use.chull[sample.int(length(use.chull), 1)]
v25 <- .ifelse(v24, obj$call$Call_ARGS$study.pol, sample(study.pol, 1)[[1]])
v26 <- .ifelse(display, .ifelse(is.pnts, pnts.col[[p.id]], pnts.col), NULL)
list(data=v1, xy.dat=v2, same.n.split=v3, n.split.pts=v4, generalize.f=v5,
maximize=v6, compare.f=v7, method=v8, N.crit=v9, N.rel.crit=v10,
N.loc.crit=v11, N.loc.rel.crit=v12, S.crit=v13, S.rel.crit=v14,
Q.crit=v15, c.splits=v16, c.Q.crit=v17, c.crit.improv=v18,
c.X.knots=v19, c.Y.knots=v20, c.max.iter.no=v21, c.fast.optim=v22,
c.corr.term=v23, use.chull=v24, study.pol=v25, pnts.col=v26)
}
if (!inherits(obj, "hespdiv")) stop("`obj` must be a `hespdiv` object.", call. = FALSE)
.check_hsa_arg_structures(
data = data,
xy.dat = xy.dat,
study.pol = study.pol,
same.n.split = same.n.split,
c.fast.optim = c.fast.optim,
use.chull = use.chull,
c.splits = c.splits,
n.split.pts = n.split.pts,
c.max.iter.no = c.max.iter.no,
N.crit = N.crit,
N.loc.crit = N.loc.crit,
c.X.knots = c.X.knots,
c.Y.knots = c.Y.knots,
N.rel.crit = N.rel.crit,
N.loc.rel.crit = N.loc.rel.crit,
S.crit = S.crit,
S.rel.crit = S.rel.crit,
Q.crit = Q.crit,
c.Q.crit = c.Q.crit,
c.crit.improv = c.crit.improv,
c.corr.term = c.corr.term,
generalize.f = generalize.f,
compare.f = compare.f,
maximize = maximize,
method = method
)
if (is.null(.run.id)) .run.id <- -1
if (!is.numeric(n.runs) || n.runs < 1 || n.runs %% 1 || length(n.runs) != 1)
stop("`n.runs` must be a positive integer.", call. = FALSE)
if (!is.numeric(chunk_size) || chunk_size < 1 || chunk_size %% 1 || length(chunk_size) != 1)
stop("`chunk_size` must be a positive integer.", call. = FALSE)
if (!is.null(images.path) && !display) stop("Attempting to save empty images.", call. = FALSE)
if (!is.null(images.path) && !dir.exists(images.path))
stop("`images.path` must be an existing directory.", call. = FALSE)
# --- pairing checks ---
if (data.paired) {
if (length(data) != length(xy.dat)) {
stop(
"When `data` and `xy.dat` are paired, their lists must have the same length.",
call. = FALSE
)
}
} else {
if (!is.null(xy.dat)) xy.len <- length(xy.dat)
}
# basal data
if ((!list(obj$call.info$Call_ARGS$xy.dat) %in% xy.dat |
!list(obj$call.info$Call_ARGS$data) %in% data)) {
if (data.paired) {
xy.dat <- c(list(obj$call.info$Call_ARGS$xy.dat), xy.dat)
data <- c(list(obj$call.info$Call_ARGS$data), data)
} else {
xy.dat <- .set("xy.dat", obj)
data <- .set("data", obj)
}
}
same.n.split <- .set("same.n.split", obj)
n.split.pts <- .set("n.split.pts", obj)
# Custom method bookkeeping
is.custom <- (!is.null(maximize) | obj$call.info$METHOD$method.type == "custom")
if (obj$call.info$METHOD$method.type == "custom" &
(!list(obj$call.info$Call_ARGS$generalize.f) %in% generalize.f |
!list(obj$call.info$Call_ARGS$compare.f) %in% compare.f |
!list(obj$call.info$Call_ARGS$maximize) %in% maximize)) {
generalize.f <- c(list(obj$call.info$Call_ARGS$generalize.f), generalize.f)
compare.f <- c(list(obj$call.info$Call_ARGS$compare.f), compare.f)
maximize <- c(obj$call.info$Call_ARGS$maximize, maximize)
}
if (!is.null(method)) {
method <- sapply(method, .arg_check, name = "metric",
NAMES = names(.get_methods()[["biozonation"]]))
names(method) <- NULL
}
method <- .set("method", obj)
is.method <- !is.null(method)
# Pull defaults/augment vectors
N.crit <- .set("N.crit", obj)
N.rel.crit <- .set("N.rel.crit", obj)
N.loc.crit <- .set("N.loc.crit", obj)
N.loc.rel.crit <- .set("N.loc.rel.crit", obj)
S.crit <- .set("S.crit", obj)
S.rel.crit <- .set("S.rel.crit", obj)
Q.crit <- .set("Q.crit", obj)
c.Q.crit <- .set("c.Q.crit", obj)
c.splits <- .set("c.splits", obj)
c.crit.improv <- .set("c.crit.improv", obj)
c.X.knots <- .set("c.X.knots", obj)
c.Y.knots <- .set("c.Y.knots", obj)
c.max.iter.no <- .set("c.max.iter.no", obj)
c.fast.optim <- .set("c.fast.optim", obj)
c.corr.term <- .set("c.corr.term", obj)
use.chull <- .set("use.chull", obj)
if (!is.null(study.pol)) {
if (!is.null(obj$call$Call_ARGS$study.pol)) {
if (!list(obj$call$Call_ARGS$study.pol) %in% study.pol) {
study.pol <- c(list(obj$call$Call_ARGS$study.pol), study.pol)
}
}
} else {
study.pol <- list(obj$call$Call_ARGS$study.pol)
}
pnts.col <- .set("pnts.col", obj)
is.pnts <- is.list(pnts.col) & length(pnts.col) > 1L
pacific.region <- obj$call.info$Call_ARGS$pacific.region
if (any(!use.chull) & is.null(study.pol) & is.null(obj$call$Call_ARGS$study.pol)) {
stop(
"`study.pol` must be provided when it is missing in `obj` and any value of ",
"`use.chull` is FALSE.",
call. = FALSE
)
}
m.compete <- ifelse(is.custom & is.method, TRUE, FALSE)
if (!m.compete) use_m <- ifelse(is.method, TRUE, FALSE)
if (is.method) {
if ("pielou" %in% method) {
if (!any(Q.crit < 1)) Q.crit <- c(-Inf, Q.crit)
if (!any(c.Q.crit < 1)) c.Q.crit <- c(-Inf, c.Q.crit)
} else {
if (!any(Q.crit > 0)) Q.crit <- c(+Inf, Q.crit)
if (!any(c.Q.crit > 0)) c.Q.crit <- c(+Inf, c.Q.crit)
}
}
# ---- Serial or parallel execution shares the same draw logic ----
len_cm <- length(compare.f)
dat_len <- length(data)
run_ids <- seq_len(n.runs)
active_runs <- run_ids[run_ids > .run.id]
# Pre-size results (important for resume/skip)
hes.res <- vector("list", length = n.runs)
names(hes.res) <- as.character(run_ids)
# =========================
# SERIAL PATH
# =========================
if (!parallel) {
for (i in active_runs) {
message(paste0("Evaluating call: ", i))
d <- draw_one(i)
hes.res[[i]] <- eval_one(i, d)
}
check.warns(hes.res, .message = FALSE)
check.errs(hes.res, .message = FALSE)
return(structure(list(Alternatives = hes.res, Basis = obj), class = "hsa"))
}
# =========================
# PARALLEL PATH
# =========================
# Decide workers
if (is.null(workers)) {
num_cores <- future::availableCores()
if (is.null(load_prop)) load_prop <- 0.8
if (!is.numeric(load_prop) || load_prop <= 0 || load_prop > 1)
stop("`load_prop` must be a numeric value in the interval (0, 1].", call. = FALSE)
safe_cores <- max(1, floor(num_cores * load_prop))
if (is.null(RAM)) {
RAM <- Inf
} else {
if (!is.numeric(RAM) || length(RAM) != 1 || RAM < 1)
stop("`RAM` must be a positive integer if not NULL.", call. = FALSE)
}
safe_workers <- max(1, min(safe_cores, RAM))
} else {
if (!is.numeric(workers) || length(workers) != 1 || workers < 1)
stop("`workers` must be a positive integer.", call. = FALSE)
safe_workers <- workers
}
message("Using ", safe_workers, " parallel workers.")
future::plan(future::multisession, workers = safe_workers)
options(future.globals.maxSize = 1024^3 * 2)
# Precompute draws for all runs so workers only evaluate
draws <- vector("list", length = n.runs)
for (i in active_runs) draws[[i]] <- draw_one(i)
# Chunk submission (queue-size control)
run_chunks <- split(active_runs, ceiling(seq_along(active_runs) / chunk_size))
res_active <- unlist(
lapply(run_chunks, function(chunk) {
future.apply::future_lapply(
chunk,
function(i) eval_one(i, draws[[i]]),
future.seed = future_seed
)
}),
recursive = FALSE
)
# Write results back (preserve indexing)
hes.res[active_runs] <- res_active
check.warns(hes.res, .message = FALSE)
check.errs(hes.res, .message = FALSE)
structure(list(Alternatives = hes.res, Basis = obj), class = "hsa")
}
#' @noRd
.ifelse <- function(test,yes,no){
if (test){
yes
} else {
no
}
}
#' @noRd
check.warns <- function(hes.res, .message = TRUE){
ids <- which(unlist(lapply(hes.res,function(o)length(o)==2)))
if (length(ids) > 0) {
ids <- ids[which(unlist(lapply(hes.res[ids],function(o) names(o)[2] ==
"warning")))]
if (length(ids) > 0) {
message(
"Warnings were detected in calls: ",
paste(as.character(ids), collapse = ", ")
)
} else {
message("No warnings detected.")
return(NULL)
}
}else {
message("No warnings detected.")
return(NULL)
}
if (.message) sapply(ids, function(o) hes.res[[o]][[2]]$message)
}
#' @noRd
check.errs <- function(hes.res, .message = TRUE){
ids <- which(unlist(lapply(hes.res,function(o)length(o)==2)))
if (length(ids) > 0) {
ids <- ids[which(unlist(lapply(hes.res[ids],function(o) names(o)[2] ==
"error")))]
if (length(ids) > 0) {
message(
"Errors were detected in calls: ",
paste(as.character(ids), collapse = ", ")
)
} else {
message("No errors detected.")
return(NULL)
}
}else {
message("No errors detected.")
return(NULL)
}
if (.message) sapply(ids, function(o) hes.res[[o]][[2]]$message)
}
#' @noRd
.set <- function(name, obi){
x <- get(name,envir = parent.frame())
if (is.null(x)){
x <- eval(parse( text = paste0("obi$call.info$Call_ARGS$",name)))
} else {
if (is.list(x)){
y <- list(eval(parse( text = paste0("obi$call.info$Call_ARGS$",name))))
} else {
y <- eval(parse( text = paste0("obi$call.info$Call_ARGS$",name)))
}
if (!y %in% x) {
x <- c(y, x)
}
}
x
}
#' @noRd
.check_hsa_arg_structures <- function(
data = NULL,
xy.dat = NULL, study.pol = NULL,
same.n.split = NULL, c.fast.optim = NULL, use.chull = NULL, c.splits = NULL,
n.split.pts = NULL, c.max.iter.no = NULL, N.crit = NULL, N.loc.crit = NULL,
c.X.knots = NULL, c.Y.knots = NULL,
N.rel.crit = NULL, N.loc.rel.crit = NULL, S.crit = NULL, S.rel.crit = NULL,
Q.crit = NULL, c.Q.crit = NULL, c.crit.improv = NULL,
c.corr.term = NULL,
generalize.f = NULL, compare.f = NULL,
maximize = NULL,
method = NULL
) {
.bad <- function(name, msg) {
stop(paste0("`", name, "`: ", msg), call. = FALSE)
}
.is_intish <- function(x) is.numeric(x) && all(is.finite(x)) && all(x %% 1 == 0)
.chk_list <- function(x, name) {
if (is.null(x)) return(invisible(TRUE))
if (!is.list(x)) .bad(name, "must be a list or NULL.")
invisible(TRUE)
}
.chk_vec_type <- function(x, name, type = c("logical","numeric","integer","character")) {
if (is.null(x)) return(invisible(TRUE))
type <- match.arg(type)
ok <- switch(
type,
logical = is.logical(x),
numeric = is.numeric(x),
integer = .is_intish(x),
character = is.character(x)
)
if (!ok) .bad(name, paste0("must be a ", type, " vector or NULL."))
invisible(TRUE)
}
.chk_range <- function(x, name, lo, hi) {
if (is.null(x)) return(invisible(TRUE))
if (!is.numeric(x)) .bad(name, "must be a numeric vector or NULL.")
if (any(x < lo | x > hi, na.rm = TRUE)) {
.bad(name, paste0("values must be in [", lo, ", ", hi, "]."))
}
invisible(TRUE)
}
.chk_xy_list <- function(x, name) {
if (is.null(x)) return(invisible(TRUE))
if (!is.list(x)) .bad(name, "must be a list of data.frames or NULL.")
for (i in seq_along(x)) {
d <- x[[i]]
if (!is.data.frame(d)) {
.bad(name, paste0("element ", i, " must be a data frame."))
}
if (ncol(d) != 2) {
.bad(name, paste0("element ", i, " must have exactly two columns: `x` and `y`."))
}
if (!all(c("x", "y") %in% names(d))) {
.bad(name, paste0("element ", i, " must have columns named `x` and `y`."))
}
if (!is.numeric(d[["x"]]) || !is.numeric(d[["y"]])) {
.bad(name, paste0("element ", i, " columns `x` and `y` must be numeric."))
}
}
invisible(TRUE)
}
.chk_fun_list <- function(x, name) {
if (is.null(x)) return(invisible(TRUE))
if (!is.list(x)) .bad(name, "must be a list of functions or NULL.")
if (any(!vapply(x, is.function, logical(1)))) .bad(name, "must contain only functions.")
invisible(TRUE)
}
# data: list
.chk_list(data, "data")
# xy.dat, study.pol: lists of 2-col data.frames (x,y)
.chk_xy_list(xy.dat, "xy.dat")
.chk_xy_list(study.pol, "study.pol")
# logical vectors
.chk_vec_type(same.n.split, "same.n.split", "logical")
.chk_vec_type(c.fast.optim, "c.fast.optim", "logical")
.chk_vec_type(use.chull, "use.chull", "logical")
.chk_vec_type(c.splits, "c.splits", "logical")
# integer vectors (integer-like numeric)
.chk_vec_type(n.split.pts, "n.split.pts", "integer")
.chk_vec_type(c.max.iter.no, "c.max.iter.no", "integer")
.chk_vec_type(N.crit, "N.crit", "integer")
.chk_vec_type(N.loc.crit, "N.loc.crit", "integer")
.chk_vec_type(c.X.knots, "c.X.knots", "integer")
.chk_vec_type(c.Y.knots, "c.Y.knots", "integer")
# numeric vectors with bounds
.chk_range(N.rel.crit, "N.rel.crit", 0, 1)
.chk_range(N.loc.rel.crit, "N.loc.rel.crit", 0, 1)
.chk_range(S.crit, "S.crit", 0, 1)
.chk_range(S.rel.crit, "S.rel.crit", 0, 1)
# numeric vectors (no bounds stated)
.chk_vec_type(Q.crit, "Q.crit", "numeric")
.chk_vec_type(c.Q.crit, "c.Q.crit", "numeric")
.chk_vec_type(c.crit.improv, "c.crit.improv", "numeric")
# c.corr.term bounds
.chk_range(c.corr.term, "c.corr.term", 0.01, 0.2)
# lists of functions
.chk_fun_list(generalize.f, "generalize.f")
.chk_fun_list(compare.f, "compare.f")
# maximize: logical vector; if compare.f also provided, lengths must match
.chk_vec_type(maximize, "maximize", "logical")
if (!is.null(maximize) && !is.null(compare.f) && length(maximize) != length(compare.f)) {
.bad(
"maximize",
paste0(
"must have the same length as `compare.f` (",
length(compare.f),
")."
)
)
}
# method: character vector
.chk_vec_type(method, "method", "character")
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.