The hardware and bandwidth for this mirror is donated by METANET, the Webhosting and Full Service-Cloud Provider.
If you wish to report a bug, or if you are interested in having us mirror your free-software or open-source project, please feel free to contact us at mirror[@]metanet.ch.
Feature selection in R is spread across a dozen packages, each with its own calling convention and its own idea of what a result looks like. Comparing two methods means learning two APIs and writing glue code to line up the answers.
featR wraps the common methods behind one convention and one return
type, so that swapping fs_lasso() for
fs_boruta() is a one-word change. It also tries hard not to
flatter itself: where a method can leak information from a held-out set,
featR either prevents it or says so in the documentation.
Every selection function takes the data first and the name of the outcome column second:
The methods that have no single outcome –
fs_correlation(), fs_unsupervised(),
fs_pca(), fs_svd() – drop target
and keep the rest.
Three conventions hold everywhere:
n_cores, and requests are capped at the cores you
actually have.seed, and the previous random state is restored when the
call returns.Suggests; a function that needs one checks at the point of
use and tells you what to install.Selection functions return an fs_result:
d <- data.frame(
spread = c(1, 2, 3, 4, 100, 6, 7, 8),
flat = rep(2, 8),
gappy = c(1, NA, 3, NA, 5, 6, NA, 8)
)
res <- fs_unsupervised(d, method = "variance", threshold = 1)
res
#> <fs_result> unsupervised_variance
#> Selected 2 of 3 features
#> spread, gappy
#> Details: mask, indices, filtered, threshold, direction, action, n_features (in $details)Seven components, the same for every method:
selected(res) # the features that were kept
#> [1] "spread" "gappy"
res$scores # per-feature scores, comparable within a method
#> spread flat gappy
#> 1147.696 0.000 7.300
res$method # which method produced this
#> [1] "unsupervised_variance"
res$task # "classification", "regression", or NA
#> [1] NA
names(res$details) # everything method-specific
#> [1] "mask" "indices" "filtered" "threshold" "direction"
#> [6] "action" "n_features"summary() adds the call and a ranked score table, with
an asterisk marking what was selected:
summary(res)
#> <fs_result> unsupervised_variance
#> Selected 2 of 3 features
#> spread, gappy
#> Details: mask, indices, filtered, threshold, direction, action, n_features (in $details)
#>
#> Call:
#> fs_unsupervised(data = d, method = "variance", threshold = 1)
#>
#> Scores (3 features, ranked):
#> feature score selected
#> spread 1148.0 *
#> gappy 7.3 *
#> flat 0.0
#> (* = selected)Because the shape is shared, methods become interchangeable.
selected() works on the result of any of them.
fs_supervised() scores each feature against the target –
absolute Pearson correlation for a numeric target, ANOVA F for a factor
– and keeps those past a threshold:
train <- data.frame(
strong = c(1, 2, 3, 4, 5, 6),
mirror = c(6, 5, 4, 3, 2, 1),
noise = c(1, 0, 1, 0, 1, 0),
y = c(1, 2, 3, 4, 5, 6)
)
fs_supervised(train, target = "y", threshold = 0.9)
#> <fs_result> supervised_correlation (regression)
#> Selected 2 of 3 features
#> strong, mirror
#> Details: mask, indices, filtered, threshold, direction, action, n_features (in $details)Both strong and mirror have |r| = 1: a
filter like this ranks features one at a time, so it cannot tell you
that the second is redundant given the first. That is what
fs_correlation() is for.
fs_correlation() looks only at how the features relate
to each other. With prune = TRUE (the default) it returns a
reduced set with no two members correlated above the threshold:
fs_correlation(train[, c("strong", "mirror", "noise")], threshold = 0.9)
#> <fs_result> correlation_pearson
#> Selected 2 of 3 features
#> strong, noise
#> Details: corr_matrix, pairs, dropped, redundant, n_features (in $details)Set prune = FALSE if you would rather see every feature
involved in a high-correlation pair, which is the set most correlation
filters report.
fs_infogain() measures how many bits of uncertainty
about the target each feature removes. Numeric features are binned;
dates expand into year, month and day:
ig <- data.frame(
perfect = rep(c("a", "b", "c", "c"), 5),
half = rep(c("n1", "n2"), 10),
target = factor(rep(c("a", "b", "c", "c"), 5))
)
fs_infogain(ig, target = "target")$scores
#> perfect half
#> 1.5 0.5Raw information gain rewards high-cardinality features – an ID column
can score near the target’s entropy while predicting nothing
generalizable. Pass normalize = "gain_ratio" to divide by
each feature’s own entropy, which corrects that bias.
The remaining methods depend on suggested packages, so they are shown here without being run:
# Regularization
fs_lasso(mtcars, "mpg", nfolds = 5, seed = 1) # glmnet
fs_elastic(mtcars, "mpg", seed = 1) # caret + glmnet
# Wrappers and model-based
fs_boruta(iris, "Species", seed = 1) # Boruta
fs_randomforest(iris, "Species", task = "classification", seed = 1)
fs_recursivefeature(mtcars, "mpg", sizes = c(2, 4), seed = 1) # caret
fs_stepwise(mtcars, "mpg", direction = "both") # MASS
fs_mars(mtcars, "mpg", seed = 1) # caret + earth
fs_svm(iris, "Species", task = "classification", # caret + kernlab
feature_select = TRUE, select_method = "svm_rfe", seed = 1)
fs_bayes(mtcars, "mpg", predictors = c("wt", "hp")) # brms + looEach errors with the exact install.packages() call it
needs, so nothing fails mysteriously.
| If you want | Use |
|---|---|
| A fast first cut on many features | fs_unsupervised(), fs_supervised() |
| To drop near-duplicate features | fs_correlation() |
| To rank categorical predictors of a categorical outcome | fs_chi(), fs_infogain() |
| A sparse, interpretable linear model | fs_lasso(), fs_elastic() |
| Every feature with any relevance, not a minimal set | fs_boruta() |
| Selection tied to the model you will actually deploy | fs_recursivefeature(), fs_svm() |
| Non-linear relationships found automatically | fs_mars() |
| Fewer dimensions rather than fewer variables | fs_pca(), fs_svd() |
Filters are cheap and model-agnostic but judge features one at a time. Wrappers respect interactions but cost a model fit per subset and tie the answer to that model. Neither is a substitute for validating the selected set on data that played no part in choosing it.
A few behaviors worth knowing, all documented per function:
fs_mars() and fs_svm()
happens inside resampling folds, so resampled metrics are not
inflated by duplicated rows.fs_recursivefeature() evaluates on rows the selection
never saw and trains its final model on training rows only.fs_lasso() refuses to mean-impute by default, because
imputing on the full data leaks across folds; pass
impute = "mean" to opt in and get a warning.fs_stepwise() labels its p-values as invalid for
inference, because post-selection they are.These binaries (installable software) and packages are in development.
They may not be fully stable and should be used with caution. We make no claims about them.