| Title: | Percentage-Error Support Vector Regression |
|---|---|
| Description: | Implements four support vector regression (SVR) models derived from a unified mathematical framework for percentage-error loss functions: epsilon-SVR minimizing the mean absolute percentage error (MAPE), its symmetric kernel extension, least-squares SVR (LS-SVR) minimizing the root mean square percentage error (RMSPE), and its symmetric counterpart. All models require strictly positive targets. The epsilon-SVR models are solved via a built-in sequential minimal optimization (SMO) algorithm (with 'osqp' available as an optional alternative backend) and the LS-SVR models via a linear system (base R). See Benavides-Herrera et al. (2026) <doi:10.3390/math14101679> for the mathematical derivations. |
| Authors: | Pablo Benavides-Herrera [aut, cre] (ORCID: <https://orcid.org/0000-0003-4926-4763>) |
| Maintainer: | Pablo Benavides-Herrera <[email protected]> |
| License: | MIT + file LICENSE |
| Version: | 0.1.0 |
| Built: | 2026-10-24 11:31:12 UTC |
| Source: | https://github.com/cran/psvr |
Extract coefficients from a psvr_mape model
## S3 method for class 'psvr_mape' coef(object, ...)## S3 method for class 'psvr_mape' coef(object, ...)
object |
An object of class |
... |
Ignored. |
A named list with five components:
alpha, alpha_star
The length-N pre-pruning dual variables
and .
betaThe pruned dual differences
over the support-vector indices (length n_sv); this is what
predict() uses.
bBias term.
support_dataSupport vector input matrix.
The LS-SVR classes return three components rather than five, since
they have no alpha_star and no pruned beta; the absent components are
not materialised as NULL. So names(coef(fit)) depends on the model
family. That is deliberate: each class is family-specific, and inventing
empty slots to make the two agree would add structure with nothing to
inherit it from.
alpha previously held the pruned and support_data was
named X_sv, which made coef(fit)$alpha mean the length-n_sv
here but the length-N dual on a fit
from the superseded psvr(): one generic returning two different vectors under
one name, silently, depending on entry point. The
-under-alpha meaning is the one 0.0.2.9004 moved away
from on the object itself; this aligns coef() with it.
Extract coefficients from a psvr_mape_sym model
## S3 method for class 'psvr_mape_sym' coef(object, ...)## S3 method for class 'psvr_mape_sym' coef(object, ...)
object |
An object of class |
... |
Ignored. |
A named list with five components, identical in meaning to
coef.psvr_mape():
alpha, alpha_star
The length-N pre-pruning dual variables
and .
betaThe pruned dual differences
over the support-vector indices (length n_sv); this is what
predict() uses.
bBias term.
support_dataSupport vector input matrix.
See coef.psvr_mape() — the same rename, for the same reason, applied to
both MAPE classes together.
Extract coefficients from a psvr_rmspe model
## S3 method for class 'psvr_rmspe' coef(object, ...)## S3 method for class 'psvr_rmspe' coef(object, ...)
object |
An object of class |
... |
Ignored. |
A named list with components:
alphaDual variables / Lagrange multipliers (length N).
bBias term.
support_dataTraining input matrix (all N observations).
Three components, against five for the MAPE classes (coef.psvr_mape()):
LS-SVR has no alpha_star and no pruned beta, and they are not
materialised as NULL. So names(coef(fit)) depends on the model family,
which is a decision rather than an oversight – each class is
family-specific, and inventing empty slots to make the two agree would add
structure with nothing to inherit it from. $alpha_star and $beta yield
NULL on both, so every accessor still agrees.
support_data was named X_sv. LS-SVR performs no pruning — every training
point contributes to f(x) — so there are no support vectors to name: it was
an epsilon-SVR name on an LS-SVR value. The value is unchanged.
Extract coefficients from a psvr_rmspe_sym model
## S3 method for class 'psvr_rmspe_sym' coef(object, ...)## S3 method for class 'psvr_rmspe_sym' coef(object, ...)
object |
An object of class |
... |
Ignored. |
A named list with components:
alphaDual variables / Lagrange multipliers (length N).
bBias term.
support_dataTraining input matrix (all N observations).
Three components, not the five the MAPE classes return: LS-SVR has no
alpha_star and no pruned beta, and they are not materialised as
NULL. See coef.psvr_rmspe().
See coef.psvr_rmspe() — the same rename, for the same reason, applied to
both LS-SVR classes together.
A dials parameter for the regularisation parameter in psvr models.
The default range [-2, 10] on the log2 scale (corresponding to
approximately 0.25 to 1024) is wider than dials::cost() to
accommodate the larger regularisation values typically needed by
LS-SVR models.
cost_psvr(range = c(-2, 10), trans = scales::log2_trans())cost_psvr(range = c(-2, 10), trans = scales::log2_trans())
range |
Numeric vector of length 2 on the log2 scale.
Default |
trans |
A |
For LS-SVR (m3, m4) models the static range is still frequently too
narrow, because enters the LS-SVR system through the
diagonal and so scales with var(y) * N
rather than sitting at a fixed magnitude. Prefer cost_psvr_ls_data(),
whose range is computed from the training outcome.
A quant_param dials object.
cost_psvr_ls_data() for a data-driven LS-SVR variant.
cost_psvr()cost_psvr()
Returns a quant_param whose search range scales with
var(y) * N, the standard heuristic for the LS-SVR regularisation
parameter (Suykens et al. 2002, Least Squares
Support Vector Machines, §3.1.3). On the log2 scale, the lower bound
is -2 (i.e. ) and the upper bound is
log2(var(y) * N) + width_log2.
cost_psvr_ls_data(y, n = length(y), width_log2 = 4)cost_psvr_ls_data(y, n = length(y), width_log2 = 4)
y |
Numeric vector of strictly positive training targets. |
n |
Sample size. Default |
width_log2 |
Scalar giving the half-width (in log2 units)
added above |
The default width_log2 = 4 places the upper bound about 16 times above
var(y) * n, which is headroom for a Bayesian or grid search to work in
without pinning against the boundary. Because the bound tracks
var(y) * n, it moves with the outcome: the static cost_psvr() range
[-2, 10] (i.e. ) is fixed, so it
falls short whenever var(y) * n exceeds a few hundred — which is the
usual case, not the exception.
Use this function for m3 (LS-SVR) and m4 (symmetric LS-SVR)
workflows. Stick to cost_psvr() for m1/m2
(-SVR), where
cost maps to C and typical optima lie in [10, 100].
A quant_param dials object.
cost_psvr(), psvr_option_add_cost_ls()
cost_psvr_ls_data(c(10, 20, 30, 40, 50))cost_psvr_ls_data(c(10, 20, 30, 40, 50))
Returns a closure K(xi, xj) suitable for use with all four psvr model
fitting functions. The returned function accepts numeric vectors of any sign,
which is required by the symmetric models (Models 2 and 4) that evaluate
K(xk, -xl).
make_kernel( type = c("rbf", "linear", "polynomial"), sigma = 1, degree = 3L, coef0 = 1 )make_kernel( type = c("rbf", "linear", "polynomial"), sigma = 1, degree = 3L, coef0 = 1 )
type |
Kernel type: |
sigma |
Bandwidth for the RBF kernel, |
degree |
Integer degree for the polynomial kernel, |
coef0 |
Constant term for the polynomial kernel (default |
The three supported kernels are:
RBF: K(xi, xj) = exp(-‖xi - xj‖² / (2 * sigma²))
Linear: K(xi, xj) = xi · xj
Polynomial: K(xi, xj) = (xi · xj + coef0)^degree
RBF and even-degree polynomial kernels satisfy Assumption 3 of the paper
(kernel symmetry), making them compatible with the symmetric models.
The linear kernel and odd-degree polynomial kernels do not satisfy
Assumption 3 and should not be used with the symmetric models, i.e. with
psvr_mape() / psvr_rmspe() or a parsnip spec whose sym_type is
"even" or "odd".
A function K(xi, xj) where xi and xj are numeric vectors of
the same length, returning a scalar kernel evaluation.
K <- make_kernel("rbf", sigma = 0.5) K(c(1, 2), c(3, 4)) K_lin <- make_kernel("linear") K_lin(c(1, 0), c(0, 1)) K_poly <- make_kernel("polynomial", degree = 2, coef0 = 1) K_poly(c(1, 2), c(3, 4))K <- make_kernel("rbf", sigma = 0.5) K(c(1, 2), c(3, 4)) K_lin <- make_kernel("linear") K_lin(c(1, 0), c(0, 1)) K_poly <- make_kernel("polynomial", degree = 2, coef0 = 1) K_poly(c(1, 2), c(3, 4))
A dials parameter for the epsilon tube half-width in psvr MAPE models.
Unlike dials::svm_margin() which uses absolute units, this parameter
is expressed as a percentage of each target value. The default range
[1, 20] means the insensitivity tube spans 1% to 20% of each target.
margin_percentage(range = c(1, 20), trans = NULL)margin_percentage(range = c(1, 20), trans = NULL)
range |
Numeric vector of length 2. Default |
trans |
A |
A quant_param dials object.
margin_percentage() margin_percentage(range = c(0.5, 10))margin_percentage() margin_percentage(range = c(0.5, 10))
Method dispatched on the "psvr_mape" class, which both psvr_mape() and
the parsnip engine fit wrappers return.
## S3 method for class 'psvr_mape' predict(object, newdata, ...)## S3 method for class 'psvr_mape' predict(object, newdata, ...)
object |
An object of class |
newdata |
Numeric matrix of new inputs, one observation per row
( |
... |
Ignored. |
Numeric vector of length M with predicted values.
Method dispatched on the "psvr_mape_sym" class, which both psvr_mape()
and the parsnip engine fit wrappers return. Uses the symmetric representer
theorem
with .
## S3 method for class 'psvr_mape_sym' predict(object, newdata, ...)## S3 method for class 'psvr_mape_sym' predict(object, newdata, ...)
object |
An object of class |
newdata |
Numeric matrix of new inputs, one observation per row
( |
... |
Ignored. |
Numeric vector of length M with predicted values.
Method dispatched on the "psvr_rmspe" class, which both psvr_rmspe() and
the parsnip engine fit wrappers return.
## S3 method for class 'psvr_rmspe' predict(object, newdata, ...)## S3 method for class 'psvr_rmspe' predict(object, newdata, ...)
object |
An object of class |
newdata |
Numeric matrix of new inputs, one observation per row
( |
... |
Ignored. |
Numeric vector of length M with predicted values.
Method dispatched on the "psvr_rmspe_sym" class, which both psvr_rmspe()
and the parsnip engine fit wrappers return. Uses the symmetric representer
## S3 method for class 'psvr_rmspe_sym' predict(object, newdata, ...)## S3 method for class 'psvr_rmspe_sym' predict(object, newdata, ...)
object |
An object of class |
newdata |
Numeric matrix of new inputs, one observation per row
( |
... |
Ignored. |
Numeric vector of length M with predicted values.
Print method for psvr_mape objects
## S3 method for class 'psvr_mape' print(x, ...)## S3 method for class 'psvr_mape' print(x, ...)
x |
An object of class |
... |
Ignored. |
x, invisibly.
Print method for psvr_mape_sym objects
## S3 method for class 'psvr_mape_sym' print(x, ...)## S3 method for class 'psvr_mape_sym' print(x, ...)
x |
An object of class |
... |
Ignored. |
x, invisibly.
Print method for psvr_rmspe objects
## S3 method for class 'psvr_rmspe' print(x, ...)## S3 method for class 'psvr_rmspe' print(x, ...)
x |
An object of class |
... |
Ignored. |
x, invisibly.
Print method for psvr_rmspe_sym objects
## S3 method for class 'psvr_rmspe_sym' print(x, ...)## S3 method for class 'psvr_rmspe_sym' print(x, ...)
x |
An object of class |
... |
Ignored. |
x, invisibly.
Fits a psvr_mape() model on each split in splits, carrying the converged
dual variables (alpha, alpha_star) from one fold into the next as the SMO
warm-start, so each solve starts from the previous fold's optimum instead of
from zero. Because consecutive folds share most of their training rows, that
starting point is already close to feasible; before each solve the carried
vectors are projected back onto the constraint set (the equality
and the per-sample box), with the
residual violation absorbed by the rows that are new to this fold. The
warm-start procedure is Algorithm 1 of arXiv:2605.01446 v3. Returns a tibble
with one row per fold.
psvr_cv( splits, ..., X_var = NULL, y_var = NULL, warm_start = TRUE, verbose = FALSE )psvr_cv( splits, ..., X_var = NULL, y_var = NULL, warm_start = TRUE, verbose = FALSE )
splits |
Either an |
... |
Arguments forwarded to |
X_var |
Character vector of predictor column names. |
y_var |
Single character giving the target column name. |
warm_start |
Logical; if |
verbose |
Logical; if |
This helper is MAPE-only, and there is no loss argument. That is a
limitation of the implementation, not of the method: only psvr_mape() was
ever wired to it. LS-SVR cross-validates perfectly well, it simply has no
carryover state to exploit (each fold is a single linear-system solve), so
for psvr_rmspe() use tune::tune_grid() with parallel cold-start.
A tibble with one row per split and columns:
split_id1-based fold index.
fitA list-column of psvr_mape objects, or psvr_mape_sym
when sym_type is "even" or "odd".
predictionsA list-column of numeric vectors (predictions on the assessment set).
metricsA list-column of named numeric vectors (mape,
rmspe, mse, r2).
iter_countInteger; SMO iterations from fit$iterations.
elapsed_secNumeric; wall-clock seconds for the fit.
warm_startedLogical; TRUE for fold > 1 when
warm_start = TRUE.
if (requireNamespace("rsample", quietly = TRUE) && requireNamespace("tibble", quietly = TRUE)) { set.seed(2026) d <- data.frame( y = stats::rlnorm(80, sdlog = 1.0), x1 = stats::rnorm(80), x2 = stats::rnorm(80) ) folds <- rsample::vfold_cv(d, v = 5) res <- psvr_cv(folds, X_var = c("x1", "x2"), y_var = "y", kernel = make_kernel("rbf", sigma = 1), C = 10, eps = 5) median(vapply(res$metrics, function(m) m[["mape"]], numeric(1))) }if (requireNamespace("rsample", quietly = TRUE) && requireNamespace("tibble", quietly = TRUE)) { set.seed(2026) d <- data.frame( y = stats::rlnorm(80, sdlog = 1.0), x1 = stats::rnorm(80), x2 = stats::rnorm(80) ) folds <- rsample::vfold_cv(d, v = 5) res <- psvr_cv(folds, X_var = c("x1", "x2"), y_var = "y", kernel = make_kernel("rbf", sigma = 1), C = 10, eps = 5) median(vapply(res$metrics, function(m) m[["mape"]], numeric(1))) }
Fits the percentage-error epsilon-SVR of the paper: Model 1 when
sym_type = "none", and the symmetric-kernel Model 2 when sym_type is
"even" or "odd". The dual is a quadratic program with the
sample-dependent box
and , solved by the built-in SMO
loop or by osqp.
psvr_mape( X, y, sym_type = c("none", "even", "odd"), kernel, C, eps, solver = c("smo", "osqp"), tol = 0.001, max_iter = 100000L, alpha_init = NULL, alpha_star_init = NULL, warm_start_check = TRUE, block_k4_enabled = TRUE, engine = c("rcpp", "r"), ..., alpha_couple = 0.5, precomputed_Omega = NULL, precomputed_Omega_s = NULL )psvr_mape( X, y, sym_type = c("none", "even", "odd"), kernel, C, eps, solver = c("smo", "osqp"), tol = 0.001, max_iter = 100000L, alpha_init = NULL, alpha_star_init = NULL, warm_start_check = TRUE, block_k4_enabled = TRUE, engine = c("rcpp", "r"), ..., alpha_couple = 0.5, precomputed_Omega = NULL, precomputed_Omega_s = NULL )
X |
Numeric matrix of training inputs, one observation per row
( |
y |
Numeric vector of training targets, length |
sym_type |
Symmetry type, one of |
kernel |
A kernel function created by |
C |
Regularization parameter, |
eps |
Insensitivity tube half-width in percentage units,
|
solver |
Backend for the dual quadratic program, |
tol |
Numerical tolerance, default |
max_iter |
Maximum SMO iterations, default |
alpha_init, alpha_star_init
|
Optional warm-start vectors for the SMO
solver, each a finite numeric vector of length |
warm_start_check |
Logical; if |
block_k4_enabled |
Logical; if |
engine |
One of |
... |
Must be empty. Present only so that |
alpha_couple |
Numeric in |
precomputed_Omega, precomputed_Omega_s
|
INTERNAL – used by |
For the least-squares / RMSPE family (Models 3 and 4) see psvr_rmspe().
The two are deliberately separate functions: they share no solver, no dual
structure and no hyperparameter search space, so a single signature would
make most of its own arguments conditional. The name psvr() is reserved
for a future automatic-selection front end and is not a synonym for
either.
For sym_type = "none", an object of class "psvr_mape": a list
with components beta (the pruned dual differences
over the
support-vector indices, used by predict()), alpha and alpha_star
(the length- pre-pruning duals, retained for warm starts), b,
X_sv, y_sv, y_train, fitted_values, kernel, C, eps,
n_train, p_train, iterations, converged and block_k4.
For sym_type = "even" or "odd", an object of class "psvr_mape_sym":
the same components plus a (the symmetry parameter) and spectral
(adaptive spectral-shift diagnostics).
Methods are available for predict(), print(), coef(), summary(),
fitted() and residuals().
solver = "smo" (the default) uses the built-in sequential minimal
optimisation loop. On some problems it does not converge within
max_iter: it emits a warning(), converged is FALSE and iterations
reaches the cap. This was first observed with linear and polynomial
kernels, but it also occurs with the RBF kernel, so the kernel is not
what determines it, and what does has not been established. Check
converged on the returned fit, and refit with solver = "osqp" when it
is FALSE and accuracy matters.
psvr_rmspe() for the LS-SVR / RMSPE family, psvr_cv() for
cross-validation with warm-start carryover, make_kernel() for kernels.
set.seed(1) X <- matrix(rnorm(40), 20, 2) y <- rlnorm(20) K <- make_kernel("rbf", sigma = 1) fit <- psvr_mape(X, y, kernel = K, C = 10, eps = 5) predict(fit, X[1:3, , drop = FALSE]) # Even-symmetric variant (Model 2): f(x) = f(-x). fit_sym <- psvr_mape(X, y, sym_type = "even", kernel = K, C = 10, eps = 5) predict(fit_sym, X[1:3, , drop = FALSE])set.seed(1) X <- matrix(rnorm(40), 20, 2) y <- rlnorm(20) K <- make_kernel("rbf", sigma = 1) fit <- psvr_mape(X, y, kernel = K, C = 10, eps = 5) predict(fit, X[1:3, , drop = FALSE]) # Even-symmetric variant (Model 2): f(x) = f(-x). fit_sym <- psvr_mape(X, y, sym_type = "even", kernel = K, C = 10, eps = 5) predict(fit_sym, X[1:3, , drop = FALSE])
Create parsnip model specifications for psvr_mape() with a fixed kernel
type. Kernel parameters are tunable parsnip arguments; the symmetry
parameter a and solver tolerance are engine arguments passed via
set_engine().
psvr_mape_rbf( mode = "regression", engine = "psvr", cost = NULL, margin = NULL, rbf_sigma = NULL, sym_type = NULL ) psvr_mape_poly( mode = "regression", engine = "psvr", cost = NULL, margin = NULL, degree = NULL, scale_factor = NULL, sym_type = NULL ) psvr_mape_linear( mode = "regression", engine = "psvr", cost = NULL, margin = NULL, sym_type = NULL )psvr_mape_rbf( mode = "regression", engine = "psvr", cost = NULL, margin = NULL, rbf_sigma = NULL, sym_type = NULL ) psvr_mape_poly( mode = "regression", engine = "psvr", cost = NULL, margin = NULL, degree = NULL, scale_factor = NULL, sym_type = NULL ) psvr_mape_linear( mode = "regression", engine = "psvr", cost = NULL, margin = NULL, sym_type = NULL )
mode |
Only |
engine |
Only |
cost |
Regularization parameter |
margin |
Epsilon tube half-width |
rbf_sigma |
RBF bandwidth |
sym_type |
Symmetry type: |
degree |
Polynomial degree |
scale_factor |
Polynomial constant term ( |
A parsnip model_spec object of the corresponding class.
library(parsnip) spec <- psvr_mape_rbf(cost = 10, margin = 1, rbf_sigma = 1) |> set_engine("psvr") spec_poly <- psvr_mape_poly(cost = 10, margin = 1, degree = 2, scale_factor = 1) |> set_engine("psvr") spec_lin <- psvr_mape_linear(cost = 10, margin = 1) |> set_engine("psvr") # Symmetric epsilon-SVR (Model 2) via the sym_type argument: spec_sym <- psvr_mape_rbf(cost = 10, margin = 1, rbf_sigma = 1, sym_type = "even") |> set_engine("psvr")library(parsnip) spec <- psvr_mape_rbf(cost = 10, margin = 1, rbf_sigma = 1) |> set_engine("psvr") spec_poly <- psvr_mape_poly(cost = 10, margin = 1, degree = 2, scale_factor = 1) |> set_engine("psvr") spec_lin <- psvr_mape_linear(cost = 10, margin = 1) |> set_engine("psvr") # Symmetric epsilon-SVR (Model 2) via the sym_type argument: spec_sym <- psvr_mape_rbf(cost = 10, margin = 1, rbf_sigma = 1, sym_type = "even") |> set_engine("psvr")
A convenience wrapper that calls workflowsets::option_add() for every
psvr workflow in wf_set (those whose wflow_id contains "m1",
"m2", "m3", or "m4"), replacing the rbf_sigma dials parameter
with a data-driven one built from X via rbf_sigma_psvr_data().
psvr_option_add(wf_set, X, width = 10, sample_size = 500L, seed = NULL)psvr_option_add(wf_set, X, width = 10, sample_size = 500L, seed = NULL)
wf_set |
A |
X |
A numeric matrix or data frame of preprocessed predictors. |
width |
Positive scalar. Passed to |
sample_size |
Integer. Passed to |
seed |
Integer seed for subsampling. Default |
The updated workflow_set (the same object with option_add()
applied to each psvr workflow).
rbf_sigma_psvr_data(), sigma_heuristic()
## Not run: # After building wf_set and preprocessing: train_baked <- rec |> prep() |> bake(new_data = train) wf_set <- psvr_option_add(wf_set, train_baked |> select(-outcome)) ## End(Not run)## Not run: # After building wf_set and preprocessing: train_baked <- rec |> prep() |> bake(new_data = train) wf_set <- psvr_option_add(wf_set, train_baked |> select(-outcome)) ## End(Not run)
A convenience wrapper that calls workflowsets::option_add() for
every LS-SVR psvr workflow in wf_set (those whose wflow_id
matches "m3" or "m4"), replacing the cost dials parameter
with one built from y via cost_psvr_ls_data().
psvr_option_add_cost_ls(wf_set, y, width_log2 = 4)psvr_option_add_cost_ls(wf_set, y, width_log2 = 4)
wf_set |
A |
y |
Numeric vector of strictly positive training targets. |
width_log2 |
Passed to |
Workflows for m1/m2 (-SVR) are intentionally
skipped — for
those, cost maps to C and the static cost_psvr() range is
usually adequate.
Note: workflowsets::option_add() replaces the whole param_info
option for each matched workflow. If you also need a data-driven
rbf_sigma (via psvr_option_add()), build the full param_info
manually with tune::extract_parameter_set_dials() and call
workflowsets::option_add() in one shot, or call this helper
first and then psvr_option_add() (which only touches
rbf_sigma) — the latter currently overwrites the former, so
prefer the manual one-shot approach when both are needed.
The updated workflow_set.
cost_psvr_ls_data(), psvr_option_add()
## Not run: # After building wf_set with at least one m3 or m4 workflow: wf_set <- psvr_option_add_cost_ls(wf_set, y = train_df$y) ## End(Not run)## Not run: # After building wf_set with at least one m3 or m4 workflow: wf_set <- psvr_option_add_cost_ls(wf_set, y = train_df$y) ## End(Not run)
Fits the percentage-error LS-SVR of the paper: Model 3 when
sym_type = "none", and the symmetric-kernel Model 4 when sym_type is
"even" or "odd". There is no quadratic program and no sparsity: the fit
is a single solve of the augmented
linear system
with , and
replacing in the symmetric case.
Every training point contributes to the prediction.
psvr_rmspe( X, y, sym_type = c("none", "even", "odd"), kernel, gamma, precondition = "auto", ... )psvr_rmspe( X, y, sym_type = c("none", "even", "odd"), kernel, gamma, precondition = "auto", ... )
X |
Numeric matrix of training inputs, one observation per row
( |
y |
Numeric vector of training targets, length |
sym_type |
Symmetry type, one of |
kernel |
A kernel function created by |
gamma |
Regularization parameter |
precondition |
One of |
... |
Must be empty. Passing anything here is an error, which is how a mistyped argument name is caught. |
For the epsilon-SVR / MAPE family (Models 1 and 2) see psvr_mape(). The two
are deliberately separate functions: they share no solver, no dual structure
and no hyperparameter search space. The name psvr() is reserved for a
future automatic-selection front end and is not a synonym for either.
For sym_type = "none", an object of class "psvr_rmspe": a list
with components alpha (the length- multipliers), b,
X_train, y_train, fitted_values, kernel, gamma, n_train,
p_train and precondition_applied.
For sym_type = "even" or "odd", an object of class "psvr_rmspe_sym":
the same components plus a (the symmetry parameter).
Methods are available for predict(), print(), coef(), summary(),
fitted() and residuals(). Note that coef() returns three components
here (alpha, b, support_data) against five for the MAPE classes:
LS-SVR has no alpha_star and no pruned beta, and the absent components
are not materialised as NULL.
psvr_mape() for the epsilon-SVR / MAPE family, make_kernel()
for kernels.
set.seed(1) X <- matrix(rnorm(40), 20, 2) y <- rlnorm(20) K <- make_kernel("rbf", sigma = 1) fit <- psvr_rmspe(X, y, kernel = K, gamma = 100) predict(fit, X[1:3, , drop = FALSE]) # Even-symmetric variant (Model 4): f(x) = f(-x). fit_sym <- psvr_rmspe(X, y, sym_type = "even", kernel = K, gamma = 100) predict(fit_sym, X[1:3, , drop = FALSE])set.seed(1) X <- matrix(rnorm(40), 20, 2) y <- rlnorm(20) K <- make_kernel("rbf", sigma = 1) fit <- psvr_rmspe(X, y, kernel = K, gamma = 100) predict(fit, X[1:3, , drop = FALSE]) # Even-symmetric variant (Model 4): f(x) = f(-x). fit_sym <- psvr_rmspe(X, y, sym_type = "even", kernel = K, gamma = 100) predict(fit_sym, X[1:3, , drop = FALSE])
Create parsnip model specifications for psvr_rmspe() with a fixed kernel
type. cost maps to the regularization parameter .
psvr_rmspe_rbf( mode = "regression", engine = "psvr", cost = NULL, rbf_sigma = NULL, sym_type = NULL ) psvr_rmspe_poly( mode = "regression", engine = "psvr", cost = NULL, degree = NULL, scale_factor = NULL, sym_type = NULL ) psvr_rmspe_linear( mode = "regression", engine = "psvr", cost = NULL, sym_type = NULL )psvr_rmspe_rbf( mode = "regression", engine = "psvr", cost = NULL, rbf_sigma = NULL, sym_type = NULL ) psvr_rmspe_poly( mode = "regression", engine = "psvr", cost = NULL, degree = NULL, scale_factor = NULL, sym_type = NULL ) psvr_rmspe_linear( mode = "regression", engine = "psvr", cost = NULL, sym_type = NULL )
mode |
Only |
engine |
Only |
cost |
Regularization parameter |
rbf_sigma |
RBF bandwidth |
sym_type |
Symmetry type: |
degree |
Polynomial degree |
scale_factor |
Polynomial constant term ( |
A parsnip model_spec object of the corresponding class.
The precondition argument of psvr_rmspe() is exposed as a non-tunable
engine argument. Pass it via parsnip::set_engine(), e.g.
set_engine("psvr", precondition = "always"). Default is "auto". See
psvr_rmspe() for accepted values and semantics.
library(parsnip) spec <- psvr_rmspe_rbf(cost = 1000, rbf_sigma = 1) |> set_engine("psvr") spec_poly <- psvr_rmspe_poly(cost = 1000, degree = 2, scale_factor = 1) |> set_engine("psvr") spec_lin <- psvr_rmspe_linear(cost = 1000) |> set_engine("psvr") # Symmetric LS-SVR (Model 4) via the sym_type argument: spec_sym <- psvr_rmspe_rbf(cost = 1000, rbf_sigma = 1, sym_type = "even") |> set_engine("psvr")library(parsnip) spec <- psvr_rmspe_rbf(cost = 1000, rbf_sigma = 1) |> set_engine("psvr") spec_poly <- psvr_rmspe_poly(cost = 1000, degree = 2, scale_factor = 1) |> set_engine("psvr") spec_lin <- psvr_rmspe_linear(cost = 1000) |> set_engine("psvr") # Symmetric LS-SVR (Model 4) via the sym_type argument: spec_sym <- psvr_rmspe_rbf(cost = 1000, rbf_sigma = 1, sym_type = "even") |> set_engine("psvr")
Returns the length-N in-sample predictions recorded
when the model was fitted. No kernel matrix is rebuilt: the values are
recovered from state the solver already holds. For the MAPE models that is
a matvec against the retained ; for the LS-SVR models it
is the KKT stationarity identity
which costs and holds in both preconditioner branches.
The training inputs X are not retained for this purpose.
## S3 method for class 'psvr_mape' fitted(object, ...) ## S3 method for class 'psvr_mape_sym' fitted(object, ...) ## S3 method for class 'psvr_rmspe' fitted(object, ...) ## S3 method for class 'psvr_rmspe_sym' fitted(object, ...)## S3 method for class 'psvr_mape' fitted(object, ...) ## S3 method for class 'psvr_mape_sym' fitted(object, ...) ## S3 method for class 'psvr_rmspe' fitted(object, ...) ## S3 method for class 'psvr_rmspe_sym' fitted(object, ...)
object |
A fitted object of class |
... |
Ignored. |
The result equals predict(object, X_train) to machine precision. It is
not bit-identical: the two use different summation orders (a BLAS matvec
versus the column-wise reduction in predict()), and for the LS-SVR
models the identity above is exact only up to the residual of the linear
solve. Observed agreement is within 3e-12 relative across the four
models.
Numeric vector of length N (the number of training
observations), in training-row order.
parsnip registers neither residuals.model_fit nor fitted.model_fit, so
calling either generic on a model_fit dispatches to the stats default and
returns NULL silently - no error, no warning. Reach the psvr object
first with parsnip::extract_fit_engine(), then call fitted() or
residuals() on that. psvr deliberately does not register S3 methods on
parsnip's class. Note also that parsnip::augment() recomputes predictions
on whatever new_data it is given and reports response residuals only.
residuals.psvr_mape() and the other residuals methods
set.seed(1) X <- matrix(runif(40, 0.5, 3), 20, 2) y <- 2 + X[, 1]^2 fit <- psvr_rmspe(X, y, kernel = make_kernel("rbf"), gamma = 100) head(fitted(fit)) # Through parsnip both generics return NULL on the model_fit wrapper; # extract the engine object first. df <- data.frame(x1 = X[, 1], x2 = X[, 2], y = y) spec <- psvr_rmspe_rbf(cost = 10, rbf_sigma = 0.8) pfit <- parsnip::fit(spec, y ~ x1 + x2, data = df) fitted(pfit) # NULL head(fitted(parsnip::extract_fit_engine(pfit))) # the fitted valuesset.seed(1) X <- matrix(runif(40, 0.5, 3), 20, 2) y <- 2 + X[, 1]^2 fit <- psvr_rmspe(X, y, kernel = make_kernel("rbf"), gamma = 100) head(fitted(fit)) # Through parsnip both generics return NULL on the model_fit wrapper; # extract the engine object first. df <- data.frame(x1 = X[, 1], x2 = X[, 2], y = y) spec <- psvr_rmspe_rbf(cost = 10, rbf_sigma = 0.8) pfit <- parsnip::fit(spec, y ~ x1 + x2, data = df) fitted(pfit) # NULL head(fitted(parsnip::extract_fit_engine(pfit))) # the fitted values
Three residual types are available. They are different quantities with different denominators, not interchangeable scalings of one another:
## S3 method for class 'psvr_mape' residuals(object, type = c("response", "percentage", "multiplicative"), ...) ## S3 method for class 'psvr_mape_sym' residuals(object, type = c("response", "percentage", "multiplicative"), ...) ## S3 method for class 'psvr_rmspe' residuals(object, type = c("response", "percentage", "multiplicative"), ...) ## S3 method for class 'psvr_rmspe_sym' residuals(object, type = c("response", "percentage", "multiplicative"), ...)## S3 method for class 'psvr_mape' residuals(object, type = c("response", "percentage", "multiplicative"), ...) ## S3 method for class 'psvr_mape_sym' residuals(object, type = c("response", "percentage", "multiplicative"), ...) ## S3 method for class 'psvr_rmspe' residuals(object, type = c("response", "percentage", "multiplicative"), ...) ## S3 method for class 'psvr_rmspe_sym' residuals(object, type = c("response", "percentage", "multiplicative"), ...)
object |
A fitted object of class |
type |
One of |
... |
Ignored. |
"response". The default, following
the R convention for stats::residuals(). Units of the response.
"percentage". Divides by
the observed target. This is the per-observation contribution to
the MAPE loss the epsilon-SVR models are fitted under, so it is the
residual that corresponds to the estimated objective.
mean(abs(.)) * 100 is the training MAPE.
"multiplicative". Divides by the fitted value. This is the
of the multiplicative-noise model
, under which
. Use it to inspect
the assumed noise structure, e.g. checking whether
is homoscedastic and centred at zero.
The denominators differ and the choice matters: "percentage" divides by
the observed target because that is what the MAPE loss does,
"multiplicative" divides by the fitted value because that is what the
noise model does. They agree only when , and
diverge as the fit degrades. Choose by the question being asked: the loss
actually minimised ("percentage"), or the noise model assumed
("multiplicative").
Numeric vector of length N (the number of training
observations), in training-row order.
"multiplicative" divides by , which an SVR does not
constrain to be positive even though the targets are. Where
is at or below sqrt(.Machine$double.eps) * mean(abs(y)) - including zero and negative fitted values - the ratio is
inflated, infinite, or sign-flipped.
This function does not drop those observations: it always returns
exactly N values in training-row order, so the result stays aligned
with y_train, fitted(), and the training rows. The raw value
(possibly Inf or NaN) is returned and a single warning reports how
many observations are affected.
For pooled diagnostics (a mean multiplicative error, a variance, a histogram) the affected observations must be excluded, or one near-zero fitted value dominates the summary. This follows the standard treatment of percentage errors near zero (Makridakis): screen the fitted values against a threshold and drop the observations below it before pooling, rather than altering the per-observation values. Exclude at the point of aggregation, e.g.:
e <- residuals(fit, type = "multiplicative") mean(e[is.finite(e)])
parsnip registers neither residuals.model_fit nor fitted.model_fit, so
calling either generic on a model_fit dispatches to the stats default and
returns NULL silently - no error, no warning. Reach the psvr object
first with parsnip::extract_fit_engine(), then call residuals() or
fitted() on that. psvr deliberately does not register S3 methods on
parsnip's class. Note also that parsnip::augment() recomputes predictions
on whatever new_data it is given and reports response residuals only, so
it is not a substitute for the "percentage" and "multiplicative" types.
fitted.psvr_mape() and the other fitted methods
set.seed(1) X <- matrix(runif(40, 0.5, 3), 20, 2) y <- 2 + X[, 1]^2 fit <- psvr_rmspe(X, y, kernel = make_kernel("rbf"), gamma = 100) head(residuals(fit)) mean(abs(residuals(fit, type = "percentage"))) * 100 # training MAPE # Through parsnip both generics return NULL on the model_fit wrapper; # extract the engine object first. df <- data.frame(x1 = X[, 1], x2 = X[, 2], y = y) spec <- psvr_rmspe_rbf(cost = 10, rbf_sigma = 0.8) pfit <- parsnip::fit(spec, y ~ x1 + x2, data = df) residuals(pfit) # NULL head(residuals(parsnip::extract_fit_engine(pfit))) # the residualsset.seed(1) X <- matrix(runif(40, 0.5, 3), 20, 2) y <- 2 + X[, 1]^2 fit <- psvr_rmspe(X, y, kernel = make_kernel("rbf"), gamma = 100) head(residuals(fit)) mean(abs(residuals(fit, type = "percentage"))) * 100 # training MAPE # Through parsnip both generics return NULL on the model_fit wrapper; # extract the engine object first. df <- data.frame(x1 = X[, 1], x2 = X[, 2], y = y) spec <- psvr_rmspe_rbf(cost = 10, rbf_sigma = 0.8) pfit <- parsnip::fit(spec, y ~ x1 + x2, data = df) residuals(pfit) # NULL head(residuals(parsnip::extract_fit_engine(pfit))) # the residuals
A dials parameter for the RBF kernel bandwidth in psvr models.
The default range [-3, 1] on the log10 scale is a conservative
fallback. For best results, override the range using
sigma_heuristic() computed on the preprocessed training data:
rbf_sigma_psvr(range = c(-3, 1), trans = scales::log10_trans())rbf_sigma_psvr(range = c(-3, 1), trans = scales::log10_trans())
range |
Numeric vector of length 2 on the log10 scale.
Default |
trans |
A |
train_baked <- rec |> prep() |> bake(new_data = train)
sigma_med <- sigma_heuristic(train_baked |> select(-outcome))
rbf_sigma_custom <- rbf_sigma_psvr(
range = c(log10(sigma_med / 10), log10(sigma_med * 10))
)
# Then inject via option_add():
wf_set |> option_add(
param_info = extract_parameter_set_dials(wf) |>
update(rbf_sigma = rbf_sigma_custom),
id = "your_workflow_id"
)
A quant_param dials object.
rbf_sigma_psvr() # Override with data-driven range: X <- matrix(rnorm(200), ncol = 4) sigma_med <- sigma_heuristic(X) rbf_sigma_psvr(range = c(log10(sigma_med / 10), log10(sigma_med * 10)))rbf_sigma_psvr() # Override with data-driven range: X <- matrix(rnorm(200), ncol = 4) sigma_med <- sigma_heuristic(X) rbf_sigma_psvr(range = c(log10(sigma_med / 10), log10(sigma_med * 10)))
A convenience wrapper that combines sigma_heuristic() and
rbf_sigma_psvr() in a single call. It computes the median pairwise
distance from X and returns a quant_param whose search range spans
one order of magnitude either side of the heuristic value on the log10
scale (i.e., [log10(sigma_med / width), log10(sigma_med * width)]).
rbf_sigma_psvr_data( X, width = 10, sample_size = 500L, seed = NULL, trans = scales::log10_trans() )rbf_sigma_psvr_data( X, width = 10, sample_size = 500L, seed = NULL, trans = scales::log10_trans() )
X |
A numeric matrix or data frame of predictors (already preprocessed — centred, scaled, etc.). |
width |
Positive scalar. Multiplier that sets the half-width of the
search range around the heuristic sigma. Default |
sample_size |
Integer. Passed to |
seed |
Integer seed for subsampling. Passed to |
trans |
A |
A quant_param dials object with a data-driven search range.
sigma_heuristic(), rbf_sigma_psvr()
X <- matrix(rnorm(200), ncol = 4) rbf_sigma_psvr_data(X) rbf_sigma_psvr_data(X, width = 5)X <- matrix(rnorm(200), ncol = 4) rbf_sigma_psvr_data(X) rbf_sigma_psvr_data(X, width = 5)
Returns the median pairwise Euclidean distance between rows of X,
which is a standard data-driven starting point for the RBF kernel
bandwidth (Schölkopf & Smola, 2002). Use the result to define a
sensible rbf_sigma search range centred on this value via
dials::rbf_sigma(range = c(log10(sigma / 10), log10(sigma * 10))).
sigma_heuristic(X, sample_size = 500L, seed = NULL)sigma_heuristic(X, sample_size = 500L, seed = NULL)
X |
A numeric matrix or data frame of predictors (already preprocessed — centred, scaled, etc.). |
sample_size |
Integer. If |
seed |
Integer seed for the subsample. Default |
A scalar numeric: the median pairwise Euclidean distance.
X <- matrix(rnorm(200), ncol = 4) sigma_heuristic(X)X <- matrix(rnorm(200), ncol = 4) sigma_heuristic(X)
Prints the kernel, the training and support-vector counts, the hyperparameters, and the SMO iteration count with its convergence status. Every training point contributes for LS-SVR but not here: the support-vector percentage is the sparsity of the fit.
## S3 method for class 'psvr_mape' summary(object, ...)## S3 method for class 'psvr_mape' summary(object, ...)
object |
An object of class |
... |
Ignored. |
object, invisibly. Called for the printed summary.
print.psvr_mape(), coef.psvr_mape()
set.seed(1) X <- matrix(rnorm(40), 20, 2) y <- rlnorm(20) fit <- psvr_mape(X, y, kernel = make_kernel("rbf", sigma = 1), C = 10, eps = 5) summary(fit)set.seed(1) X <- matrix(rnorm(40), 20, 2) y <- rlnorm(20) fit <- psvr_mape(X, y, kernel = make_kernel("rbf", sigma = 1), C = 10, eps = 5) summary(fit)
As summary.psvr_mape(), with the symmetry parameter reported.
## S3 method for class 'psvr_mape_sym' summary(object, ...)## S3 method for class 'psvr_mape_sym' summary(object, ...)
object |
An object of class |
... |
Ignored. |
object, invisibly. Called for the printed summary.
print.psvr_mape_sym(), coef.psvr_mape_sym()
set.seed(1) X <- matrix(rnorm(40), 20, 2) y <- rlnorm(20) fit <- psvr_mape(X, y, sym_type = "even", kernel = make_kernel("rbf", sigma = 1), C = 10, eps = 5) summary(fit)set.seed(1) X <- matrix(rnorm(40), 20, 2) y <- rlnorm(20) fit <- psvr_mape(X, y, sym_type = "even", kernel = make_kernel("rbf", sigma = 1), C = 10, eps = 5) summary(fit)
Prints the kernel, the training count, the hyperparameter, and whether the
diag(1/y) preconditioner fired. No support-vector count is reported: LS-SVR
performs no pruning, so every training point contributes to the prediction.
## S3 method for class 'psvr_rmspe' summary(object, ...)## S3 method for class 'psvr_rmspe' summary(object, ...)
object |
An object of class |
... |
Ignored. |
object, invisibly. Called for the printed summary.
print.psvr_rmspe(), coef.psvr_rmspe()
set.seed(1) X <- matrix(rnorm(40), 20, 2) y <- rlnorm(20) fit <- psvr_rmspe(X, y, kernel = make_kernel("rbf", sigma = 1), gamma = 100) summary(fit)set.seed(1) X <- matrix(rnorm(40), 20, 2) y <- rlnorm(20) fit <- psvr_rmspe(X, y, kernel = make_kernel("rbf", sigma = 1), gamma = 100) summary(fit)
As summary.psvr_rmspe(), with the symmetry parameter reported.
## S3 method for class 'psvr_rmspe_sym' summary(object, ...)## S3 method for class 'psvr_rmspe_sym' summary(object, ...)
object |
An object of class |
... |
Ignored. |
object, invisibly. Called for the printed summary.
print.psvr_rmspe_sym(), coef.psvr_rmspe_sym()
set.seed(1) X <- matrix(rnorm(40), 20, 2) y <- rlnorm(20) fit <- psvr_rmspe(X, y, sym_type = "even", kernel = make_kernel("rbf", sigma = 1), gamma = 100) summary(fit)set.seed(1) X <- matrix(rnorm(40), 20, 2) y <- rlnorm(20) fit <- psvr_rmspe(X, y, sym_type = "even", kernel = make_kernel("rbf", sigma = 1), gamma = 100) summary(fit)
Returns a qualitative dials::new_qual_param() describing the sym_type
argument of the psvr model specs. "none" fits the non-symmetric model;
"even" maps to a = 1L (standard symmetric kernel); "odd" maps to
a = -1L (anti-symmetric kernel).
sym_type_param(values = c("none", "even", "odd"))sym_type_param(values = c("none", "even", "odd"))
values |
Character vector of levels to search over. Any subset of
|
A qual_param object.
sym_type_param() sym_type_param(values = c("even", "odd"))sym_type_param() sym_type_param(values = c("even", "odd"))