Trains a Multivariate Adaptive Regression Splines model with
caret::train(method = "earth") and repeated cross-validation, evaluates it
on a held-out test set, and reports the predictors earth retained together
with their variable importance.
Usage
fs_mars(
data,
target,
train_ratio = 0.8,
degree = 1:3,
nprune = c(5, 10, 15),
tuneLength = 10L,
search = "grid",
number = 5,
repeats = 3,
sample_size = 10000,
corr_cut = 0.95,
remove_nzv = TRUE,
seed = NULL,
verbose = FALSE,
n_cores = 1L
)Arguments
- data
data.frame or data.table with predictors and the target. A data.table is copied, never modified in place.
- target
Character. Name of the target column in
data. A factor or character target is treated as classification, a numeric one as regression.- train_ratio
Numeric in (0, 1). Training proportion (default 0.8).
- degree
Integer vector, each element >= 1. Interaction degrees to tune (default 1:3). Used when
search = "grid".- nprune
Integer vector, each element >= 2. Numbers of retained terms to tune (default
c(5, 10, 15)). Used whensearch = "grid".- tuneLength
Integer >= 1. Number of random hyperparameter combinations evaluated when
search = "random"(default 10; ignored for grid search).- search
Character. "grid" (default) or "random"; see Details.
- number
Integer >= 2. Cross-validation folds (default 5).
- repeats
Integer >= 1. Cross-validation repeats (default 3).
- sample_size
Integer >= 1. Maximum number of rows used; larger data sets are randomly down-sampled first, after missing rows are dropped and before the train/test split (default 10000).
- corr_cut
Numeric between 0 and 1. Correlation cutoff for dropping highly correlated numeric predictors (default 0.95; 0 disables).
- remove_nzv
Logical. Remove near-zero-variance predictors (default TRUE).
- seed
Optional whole number for reproducibility, applied locally and restored on exit. Default NULL (never seeds by default). When NULL, no deterministic resampling seed lists are constructed either.
- verbose
Logical. Print progress messages, caret's per-fold iteration log, and the small-class notice (default FALSE).
- n_cores
Integer >= 1. Number of parallel workers for model training (default 1 = sequential; no cluster is created and caret's
allowParallelstays FALSE). Values above the detected core count are capped; requires 'doParallel' and 'foreach' when greater than 1.
Value
An object of class fs_result with:
- selected
Character vector of the predictors earth retained (strictly positive
caret::varImp()importance), ordered by decreasing importance. Empty when no importance is available.- scores
Named numeric vector of unscaled variable importance, one entry per candidate predictor that entered training, floored at 0 (predictors earth pruned away score 0).
NULLwhen caret cannot compute variable importance for the fitted model, in which caseselectedis empty.- method
"mars".
- task
"classification" for a factor or character target (characters are coerced to factor), "regression" for a numeric one.
- model
The
caret::trainobject.- details
A list with
predictions(test-set predictions; a factor carrying the training levels for classification),metrics(RMSE/MAE/R2 for regression; Accuracy/Kappa plus ROC_AUC/PR_AUC when the optional 'pROC'/'PRROC' packages are installed for binary classification),confusion_matrix(classification only, else NULL),varimp(thecaret::varImp()object, or NULL when unsupported),removed_predictors(a list withnzvandcorrnaming the dropped predictors),train_index(integer row indices of the training rows, into the cleaned and optionally down-sampled data),test_data(the held-out rows after preprocessing) andn_features(number of candidate predictors that entered training).- call
The matched call.
Details
Use this when you want a model that discovers non-linear effects and interactions on its own and then tells you which predictors it used: earth fits piecewise-linear hinge terms, prunes them back, and the surviving terms define the selected set. Selection is model-based rather than a filter, so it reflects one particular fitted model and its tuning.
The pipeline, in order: rows with any missing value are dropped; the data
are randomly down-sampled to at most sample_size rows; for classification
each class must have at least two rows; a stratified
caret::createDataPartition() split keeps train_ratio of the rows for
training and holds the rest out; near-zero-variance and strongly correlated
predictors (see remove_nzv and corr_cut) are identified on the training
rows only and dropped from both halves; then caret::train() fits earth
under repeated cross-validation with preProcess = c("center", "scale").
Selection is read off the fitted model: caret::varImp(model, scale = FALSE)
is computed on the caret train object and every predictor with a strictly
positive importance is reported in selected. Predictors earth pruned away
score 0 and are not selected. caret expands factors into dummy columns
before fitting, so each importance row is attributed back to the source
predictor by name (the longest candidate name it starts with) and a
predictor keeps the largest importance of its encoded columns; that
name-based attribution is ambiguous if one predictor's name happens to be a
prefix of another predictor's encoded column name. The backticks caret adds
around non-syntactic names are stripped first, so such columns are
attributed like any other.
For classification, class imbalance is handled by sampling = "up" inside
caret::trainControl(), i.e. upsampling happens within each resample; the
data are never upsampled before cross-validation (which would leak
duplicated rows across folds). The FIRST factor level is treated as the
positive class for ROC/PR AUC, and the test-set ROC AUC is computed with a
fixed direction, so a worse-than-chance model scores below 0.5 rather than
being silently flipped. Factor levels are sanitized with
make.names(unique = TRUE), so distinct labels can never be merged.
For regression, the reported R2 comes from caret::R2(), which is the
squared correlation between predictions and observations – not
1 - SSE/SST – and can be high even for a biased model.
Everything in details$metrics comes from the single held-out split, so on
small data sets these numbers are noisy; the per-resample tuning results are
in model$resample.
search = "grid" tunes over expand.grid(nprune, degree);
search = "random" ignores that grid and evaluates tuneLength random
hyperparameter combinations instead.
Examples
# \donttest{
if (requireNamespace("caret", quietly = TRUE) &&
requireNamespace("earth", quietly = TRUE)) {
# x1 and x2 drive y; x3 is noise
df <- data.frame(
x1 = rnorm(150),
x2 = rnorm(150),
x3 = rnorm(150)
)
df$y <- 2 * df$x1 - df$x2 + rnorm(150, sd = 0.5)
res <- fs_mars(df, "y", degree = 1, nprune = c(5, 10),
number = 3, repeats = 1, seed = 42)
res$selected
res$scores
res$details$metrics
}
#> Loading required package: earth
#> Loading required package: Formula
#> Loading required package: plotmo
#> Loading required package: plotrix
#> $RMSE
#> [1] 0.4538177
#>
#> $MAE
#> [1] 0.3480216
#>
#> $R2
#> [1] 0.9614264
#>
# }