Skip to contents

featR leaves your random number generator alone unless you pass seed, runs on one core unless you ask for more, and loads its modeling engines only when a function needs them.

Seeds

No featR function calls set.seed() unless you pass seed. When you do, the seed applies to that call only, and your previous RNG state is restored when the function returns, even if it errors. So two calls with the same seed give the same answer, and a seeded featR call does not change the random numbers your own code draws afterwards:

d <- data.frame(a = sin(1:50), b = cos(1:50), c = sin(1:50) + 0.1 * cos(3:52))

set.seed(2024)
expected <- runif(3)

set.seed(2024)
res <- fs_correlation(d, threshold = 0.9, sample_frac = 0.5, seed = 1)
observed <- runif(3)

# The seeded call left the caller's stream exactly where it was
identical(expected, observed)
#> [1] TRUE

Without seed, a randomized function draws from your global stream like any other R function, so calling set.seed() first also makes a sequential run repeatable. Parallel workers do not draw from your global stream, so a parallel run needs seed. For fs_randomforest() with more than one worker, seed is the only way to get the same forest twice.

Which functions use randomness

Source of randomness Functions
Train/test split fs_randomforest(), fs_mars(), fs_recursivefeature(), fs_svm()
Cross-validation folds fs_lasso(), fs_elastic(), fs_mars(), fs_recursivefeature(), fs_svm()
Random forests fs_randomforest(), fs_boruta()
Monte Carlo p-values, when expected cell counts are small fs_chi()
Row down-sampling fs_correlation() with sample_frac < 1, fs_mars() above sample_size rows (default 10000), fs_randomforest() with control$sample_size
Subset sampling and MCMC fs_bayes()
None fs_supervised(), fs_unsupervised(), fs_infogain(), fs_stepwise(), fs_pca(), fs_svd()

fs_stepwise(), fs_pca(), and the other deterministic functions have no seed argument because they never touch the RNG.

fs_bayes() fits each candidate model by MCMC. Its seed controls which subsets sample_combinations draws and, unless you pass brm_args = list(seed = ...), also draws one Stan seed per candidate model. A seeded call therefore reproduces the MCMC fits too, including under parallel_combinations = TRUE. An explicit brm_args$seed is used for every model instead.

Parallelism

Every function runs on one core unless you ask for more:

Function How to ask Workers when you ask
fs_lasso() parallel = TRUE n_cores, default 2
fs_chi() parallel = TRUE n_cores, default 2, or the future plan you already set
fs_correlation() parallel = TRUE, method = "pointbiserial" only n_cores, default 2
fs_recursivefeature() parallel = TRUE 2
fs_elastic(), fs_mars(), fs_randomforest(), fs_svm() n_cores greater than 1 n_cores
fs_bayes() parallel_combinations = TRUE n_cores; not available on Windows

Worker requests are capped at parallel::detectCores(), so asking for 64 workers on a 4-core laptop gives 4. Clusters featR creates are stopped when the call returns, including on error, and a foreach backend or future plan you registered yourself is restored afterwards. If you have already set a non-sequential future plan, fs_chi() uses that plan as is, ignores n_cores, and leaves your workers running.

Seeded parallel runs

For most functions, two seeded parallel runs agree with each other because everything random is decided in the main R process from the local seed: the train/test split, the resampling folds, and, for the caret-based fs_svm(), fs_recursivefeature(), and fs_mars(), one seed per resample that caret draws and hands to whichever worker fits that resample. Seeding the workers’ own RNG streams would not be enough on its own. foreach gives each resample to whichever worker is free, so which stream fits which resample changes from run to run. fs_lasso() and fs_elastic() need nothing more, because glmnet fits are deterministic once the folds are fixed.

fs_randomforest() works differently. It hands each worker a fixed block of trees and seeds the workers with L’Ecuyer-CMRG streams.

A seeded parallel run need not match a seeded sequential one. fs_elastic() guarantees that its two runs agree. fs_svm() and fs_recursivefeature() make no such promise, and fs_chi()’s parallel p-values come from furrr’s own streams, so they differ from the sequential ones for the same seed.

fs_randomforest(big_data, "y", task = "regression", n_cores = 4, seed = 1)
fs_lasso(big_data, "y", parallel = TRUE, n_cores = 4, seed = 1)

When fs_randomforest() grows its forest on more than one worker, it merges the pieces with randomForest::combine(), which drops the out-of-bag error estimates, so details$oob is NULL. Use n_cores = 1 if you need the OOB numbers. fs_bayes() with parallel_combinations = TRUE holds each model to a single MCMC chain, so cross-chain diagnostics such as R-hat are not available; it warns when that happens.

Optional dependencies

featR’s hard dependencies are data.table, methods, parallel, stats, utils, and withr. Every modeling engine is in Suggests and is loaded only on the code path that needs it. A missing package stops the call with the command to fix it:

Error: Package 'glmnet' is required for lasso feature selection but not
installed. Install with: install.packages(c("glmnet"))

The choosing a method guide lists which packages each function needs. To install all of them at once:

install.packages(c(
  "Boruta", "brms", "caret", "doParallel", "e1071", "earth", "foreach",
  "furrr", "future", "ggplot2", "glmnet", "kernlab", "loo", "MASS", "Matrix",
  "MLmetrics", "pbapply", "polycor", "pROC", "PRROC", "randomForest",
  "RSpectra", "bigstatsr"
))

fs_bayes() also needs a working C++ toolchain for Stan; the brms installation notes explain how to set one up.