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.

Getting started with featR

Why featR

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.

One convention

Every selection function takes the data first and the name of the outcome column second:

fs_<method>(data, target, ..., seed = NULL, verbose = FALSE, n_cores = 1L)

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:

One return type

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.

A supervised filter

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.

Removing redundancy

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.

Information gain

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.5

Raw 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.

Methods that need a modeling engine

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 + loo

Each errors with the exact install.packages() call it needs, so nothing fails mysteriously.

Choosing a method

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.

What featR will not do quietly

A few behaviors worth knowing, all documented per function:

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.