#' Create evaluation environment for specials
#'
#' Allows extension packages to make use of the formula parsing of specials.
#'
#' @param ... A named set of functions which used to parse formula inputs
#' @param .required_specials The names of specials which must be provided (and if not, are included with no inputs).
#' @param .xreg_specials The names of specials which will be only used as inputs to other specials (most commonly `xreg`).
#'
#' @export
new_specials <- function(..., .required_specials = NULL, .xreg_specials = NULL){
specials <- squash(list2(...))
if(is.null(specials$xreg)){
specials$xreg <- function(...) abort(sprintf("Exogenous regressors are not supported for %s.", self$model))
}
structure(specials,
required_specials = .required_specials,
xreg_specials = .xreg_specials,
class="fable_specials")
}
#' Helper special for producing a model matrix of exogenous regressors
#'
#' @param ... Arguments for `fable_xreg_matrix` (see Details)
#'
#' @details
#'
#' Currently the `fable_xreg_matrix` helper supports a single argument named
#' `default_intercept`. If this argument is TRUE (passed via `...` above), then
#' the intercept will be returned in the matrix if not specified (much like the
#' behaviour of `lm()`). If FALSE, then the intercept will only be included if
#' explicitly requested via `1` in the formula.
#'
#' @importFrom stats model.frame model.matrix terms
#' @export
special_xreg <- function(...) {
new_function(
args = pairlist2(...=),
body = call2(call2(":::", sym("fabletools"), sym("fable_xreg_matrix")),
sym("..."), ..., .data = parse_expr("self$data")),
env = base_env()
)
}
fable_xreg_matrix <- function(..., .data, default_intercept = TRUE) {
dots <- enexprs(...)
# Remove default intercept if needed.
if(!default_intercept) {
constants <- map_lgl(dots, inherits, "numeric")
constant_specified <- any(map_lgl(dots[constants], `%in%`, c(-1, 0, 1)))
# If the constant isn't specified, remove it.
if(!constant_specified) dots <- c(dots, list(0))
}
# Remove index and keys from .
is_dot <- vapply(dots, function(x) expr_text(x) == ".", logical(1L))
if(any(is_dot)) {
new_dot <- reduce(syms(c(".", index_var(.data), key_vars(.data))), call2, .fn = "-")
dots <- c(new_dot, dots[!is_dot])
}
# Combine `...` into a model formula, then evaluate terms() to substitute `.`
model_formula <- new_formula(
lhs = NULL,
rhs = reduce(dots, function(.x, .y) call2("+", .x, .y))
)
model_formula <- terms(model_formula, data = .data)
# Produce appropriate evaluation environment with specials
env <- map(enquos(...), get_env)
env[map_lgl(env, compose(is_empty, env_parents))] <- NULL
env <- if (!is_empty(env)) get_env(env[[1]]) else base_env()
# Produce xreg matrix
xreg <- model.frame(model_formula, data = env, na.action = stats::na.pass)
mm <- model.matrix(terms(xreg), xreg)
if (NROW(mm) == 0 && identical(colnames(mm), "(Intercept)")) {
return(matrix(data = 1, nrow = NROW(.data), dimnames = list(NULL, "(Intercept)")))
}
mm
}
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.