every uncorrelated subset of predictors
Exact enumeration of maximal predictor sets under a correlation threshold.
Hand it a data frame and a threshold. corrselect finds
every maximal set of predictors whose pairwise correlations all stay
under that threshold, by graph enumeration (Bron–Kerbosch /
Eppstein–Löffler–Strash) in C++, and returns whichever view you need: a
single pruned data frame, or every valid subset to choose from.
library(corrselect)
# prune to one low-correlation set, in a single call
corrPrune(mtcars, threshold = 0.7)
# or enumerate *every* maximal low-correlation set
corrSelect(mtcars, threshold = 0.7)corrSelect() returns every maximal low-correlation
subset, ranked by size then average correlation:
res <- corrSelect(mtcars, threshold = 0.7)
res
#> CorrCombo object
#> -----------------
#> Method: bron-kerbosch
#> Correlation: pearson
#> Threshold: 0.700
#> Subsets: 15 maximal subsets
#> Data Rows: 32 used in correlation
#> Pivot: TRUE
#>
#> Top combinations:
#> No. Variables Avg Max Size
#> ------------------------------------------------------------
#> [ 1] mpg, drat, qsec, gear, carb 0.416 0.700 5
#> [ 2] cyl, drat, qsec, gear, carb 0.434 0.700 5
#> [ 3] mpg, drat, vs, gear, carb 0.466 0.700 5
#> ... (12 more combinations)
corrSubset(res, mtcars, which = "best") # pull the top-ranked subset back out as a data frameforce_in protects variables that must survive pruning
regardless of what else gets dropped; by requires the
threshold to hold separately inside every group:
# hp and wt are kept no matter what else is removed
corrPrune(mtcars, threshold = 0.7, force_in = c("hp", "wt"))
# threshold holds within every level of `site`, checked group by group
corrPrune(longitudinal_example[, c("x1", "x2", "x3", "x4", "x5", "site")],
threshold = 0.6, by = "site")assocSelect() picks the right association measure for
each pair of columns (Pearson for numeric-numeric, the correlation ratio
eta for numeric-factor, Cramer’s V for factor-factor), and enumerates
maximal subsets under all of them at once:
df <- data.frame(
height = rnorm(30, 170, 10),
weight = rnorm(30, 70, 12),
group = factor(sample(c("A", "B"), 30, TRUE)),
rating = ordered(sample(c("low", "med", "high"), 30, TRUE))
)
assocSelect(df, threshold = 0.6)corrPrune() runs the same mixed-type dispatch under the
hood, so a single pruning call works whether the input is all-numeric or
a mix of numeric, factor, and ordered columns. Six numeric-numeric
measures are available, selectable via measure /
method_num_num: "pearson",
"spearman", "kendall", "bicor"
(WGCNA), "distance" (energy), and "maximal"
(minerva).
modelPrune() removes predictors by VIF or condition
number, refitting after each removal. Built-in engines cover
lm, glm, lme4, and
glmmTMB; anything else plugs in as a two-function
engine:
# linear model, VIF threshold
modelPrune(mpg ~ cyl + disp + hp + wt, data = mtcars, limit = 5)
# any modeling package, via a custom engine (here: INLA)
inla_engine <- list(
name = "inla",
fit = function(formula, data, ...) {
INLA::inla(formula, data = data, family = "gaussian", ...)
},
diagnostics = function(model, fixed_effects) {
scores <- model$summary.fixed[, "sd"] # posterior SD as the badness score
setNames(scores, rownames(model$summary.fixed))[fixed_effects]
}
)
modelPrune(y ~ x1 + x2, data = df, engine = inla_engine, limit = 0.5)corrPrune or
modelPrune?corrPrune() |
modelPrune() |
|
|---|---|---|
| Needs a model? | No | Yes |
| Based on | Pairwise correlation / association | Model diagnostics (VIF) |
| Works without a response? | Yes | No |
| Mixed models? | No | Yes (lme4, glmmTMB) |
| Best for | Exploratory analysis, large p |
Regression workflows, VIF reduction |
Use corrPrune() first to cut dimensionality, then
modelPrune() for final cleanup inside a modeling
framework.
corrPrune() picks a search mode for you: exact
enumeration up to max_exact_p predictors (100 by default),
and a greedy C++ backend beyond that. Exact mode guarantees the largest
subset satisfying the threshold; greedy mode trades that guarantee for
speed on wide data. Both are available directly, so you can pin one
explicitly:
corrPrune(mtcars, threshold = 0.7, mode = "exact") # guaranteed-maximal, small/medium p
corrPrune(mtcars, threshold = 0.7, mode = "greedy") # fast, approximate, any pcaret::findCorrelation() solves this with greedy
iterative removal, which is order-dependent and can vary between runs on
the same data. corrPrune(mode = "exact") solves the same
threshold constraint by maximal-clique enumeration, retaining at least
as many variables and returning the same answer every run. The Comparison
vignette walks through this and three other alternatives (Boruta,
glmnet, manual VIF removal) side by side on the same dataset.
corrPrune(): association-based
pruning, model-free. Exact mode for p <= 100, greedy
mode for larger p, protect variables with
force_in, hold the threshold across groups with
by.modelPrune(): VIF-based pruning for
lm, glm, lme4,
glmmTMB, or any custom engine (INLA, mgcv, brms, …).corrSelect() /
MatSelect(): exhaustive enumeration of all maximal
sets, on a data frame or directly on a correlation matrix.corrSubset(): pull one or more maximal
subsets back out of a CorrCombo as data frames.assocSelect(): mixed-type data
(numeric, factor, ordered), with the right association metric chosen per
pair.install.packages("corrselect") # CRAN
install.packages("pak") # development version
pak::pak("gcol33/corrselect")“Software is like sex: it’s better when it’s free.” — Linus Torvalds
I’m a PhD student who builds R packages in my free time because I believe good tools should be free and open. I started these projects for my own work and figured others might find them useful too.
If this package saved you some time, buying me a coffee is a nice way to say thanks. It helps with my coffee addiction.
MIT (see the LICENSE.md file)
@article{corrselect,
author = {Colling, Gilles},
title = {corrselect: Fast and flexible predictor pruning for data analysis and modeling},
journal = {Journal of Open Source Software},
year = {2026},
volume = {11},
number = {118},
pages = {9539},
doi = {10.21105/joss.09539},
url = {https://joss.theoj.org/papers/10.21105/joss.09539}
}