Repository navigation
v3.2.3
Bug Fixes
- C++ backend:
runELS()was a single greedy expansion per seed vertex, not an implementation of Eppstein-Loffler-Strash, and could silently miss valid maximal subsets. Replaced with a genuine ELS implementation (degeneracy ordering + per-vertex bounded expansion), sharing a verified Bron-Kerbosch pivot core with the"bron-kerbosch"method. Verified against brute-force enumeration. - Greedy backend: an undefined (NaN) association was silently treated as compatible (
NaN > thresholdisfalsein C++); NaN now always registers as a threshold violation. - MatSelect: the symmetry check used exact floating-point equality, inconsistent with the R layer's
1e-8tolerance, and could reject matrices the R layer had already accepted as symmetric. Added a minimumncol >= 2guard, andn_rows_usedno longer reports a fabricated row count for matrix input (nowNA). - corrSelect: numeric
force_inindices were checked against the final (filtered) correlation matrix but never remapped from the original data frame's column positions, so a numeric index could silently force the wrong variable into every subset after non-numeric or constant columns were dropped. - corrPrune: no longer errors on trivially satisfiable inputs (a single predictor, or a set where every pair exceeds the threshold) -- the pairwise constraint holds vacuously for one variable, so one is now retained instead of raising "No valid subsets found".
- corrPrune / modelPrune / assocSelect: undefined (NA) associations were handled inconsistently -- silently treated as zero association, silently passed through the greedy backend, or produced blank/uninformative errors. All undefined associations are now surfaced explicitly with a clear message identifying the affected variable pair(s).
- corrPrune: the
measureargument had no effect on mixed-type data (numeric-numeric pairs always used Pearson regardless of the requested measure). It now customizes numeric-numeric pairs as documented, and the measure actually used per pair-type is reported via a newassoc_methods_usedattribute. - modelPrune: VIF and condition-number computation matched design-matrix columns to predictor names with a prefix-based regex, which could silently collide (e.g.
"x1"matching the"x10"column). Columns are now resolved via the model's ownassignbookkeeping. - modelPrune: formulas with a transformed response (e.g.
log(mpg) ~ .) crashed during formula parsing. - corrSubset:
which = "best"on aCorrCombowith no subsets raised an uninformative "subscript out of bounds" error instead of a clear message. - Internal Rcpp exports (
runELS,runBronKerbosch) now validateforce_inbounds directly rather than relying solely on the R-level dispatcher. - Added duplicate-column-name checks,
force_in/byoverlap detection, and a coverage warning when most groups are skipped during grouped aggregation incorrPrune. - assocSelect:
method = "eta"computed eta (the square root) instead of eta-squared, contradicting its own documentation. - modelPrune: VIF for a multi-level factor averaged its dummy columns into one number instead of computing a generalized VIF, which could report no collinearity for a factor that was severely collinear. Now uses the standard Fox & Monette GVIF determinant ratio, verified against
car::vif(). - modelPrune: refitting a reduced formula with a single random-effect term (e.g.
(1 | group)) via unparenthesizedterm | groupfragments was silently reinterpreted bylme4/glmmTMBas a random slope instead of a random intercept, and rejected outright for two or more random-effect terms. - modelPrune: added an explicit non-finite design-matrix check, a zero-variance guard for VIF/condition-number scoring (constant predictors now get
NAinstead of floating-point noise orInf), and fixed a crash when the surviving fixed-effect terms included interactions or transformations (poly(),log(),:). - corrPrune: the returned data frame was built from the internally type-converted copy of
data(character/logical coerced to factor, integer to numeric), so a kept column's type could silently change; it is now subset from the caller's original, untouched columns. - corrPrune: the threshold upper bound (
[0, 1]) was validated only whenmode = "auto"routed throughMatSelect();threshold > 1silently succeeded inmode = "greedy". Now validated once up front for every mode. - corrPrune: grouped (
by =) aggregation quantiled signed correlations, letting a strong negative association in one group be averaged away by a weak positive one in another; associations are now abs()-clamped before aggregation, matching the mixed-type code path. - corrPrune: grouped aggregation had four related silent data-loss bugs -- an unused factor level within one group produced an
NAsilently dropped from thegroup_qquantile (breaking the documented "group_q = 1holds in every group" guarantee), asuppressWarnings()call hid the informative NA-row-removal warning,n_rows_useddouble-counted rows from skipped groups, andNAin thebycolumn itself silently excluded rows with no warning at all. - corrPrune:
force_inassociation magnitude is now compared withabs()rather than the signed value, so negatively-correlatedforce_inpairs are correctly rejected in exact mode (greedy mode already matched). - corrPrune: errors when
bynames every column, instead of silently returning a zero-predictor result. - corrPrune:
distance/maximalassociation metrics leaked upstream package warnings for constant columns, unlike every other method in the same dispatch; now suppressed to match. - corrPrune: hand-rolled its own type-pair-to-association-method table for the
assoc_methods_usedattribute instead of reusing the table actually used to compute the matrix, risking drift between the two. - corrPrune / assocSelect: a constant column crashed
corrPrune()'s all-numeric branch whileassocSelect()handled the identical input gracefully; constant-column zeroing is now applied structurally for every caller. - assocSelect: reported a misleading "sparse combinations" error when the real cause was fewer than two complete-case rows; now uses the same explicit check
corrSelect()already had. - MatSelect: the
force_inmutual-violation warning now names the offending pair and value. - MatSelect:
use_pivot =now errors on non-coercible input instead of silently falling back to the default, and warns when supplied together withmethod = "els"(a no-op there). - MatSelect / assocSelect:
force_inis now validated as whole numbers, matchingcorrSelect()'s existing check, so non-integer indices error instead of silently truncating to a different column. - MatSelect / corrSelect / assocSelect: added a warning for combinatorial blowup on large, permissive inputs where both the variable count and compatibility-graph density make exponential enumeration plausible.
- C++ backend:
validateMatrixStructure()letNA/NaNcorrelation entries silently pass symmetry and diagonal checks (IEEE-754 comparisons againstNaNare always false), and checked the diagonal only on the upper-triangular path, letting a symmetric matrix with a wrong diagonal bypass validation entirely when a backend was called directly. - C++ backend:
validateForcedIndices()now deduplicatesforce_insorunELS()/runBronKerbosch()can't return a variable twice. - C++ backend: the shared Bron-Kerbosch base case never sorted a clique's element order, so the default search path (
bron-kerboschwith pivoting) returned scrambled combos, silently reorderingcorrPrune()/corrSubset()output columns. - C++ backend:
greedyPruneBackend(), the fourth Rcpp-exported backend, was missed by an earlier shared-validation refactor and skippedvalidateCorMatrix()/validateForcedIndices()entirely. - corrSubset: the missing-value warning now lists only the subsets that actually contain
NAs. - Two package examples called
corrSelect(cor(mat))whereMatSelect(cor(mat))was intended, silently computing correlations of the correlation matrix rather than using it directly.
New Features
- Added
summary.CorrCombo(), reporting aggregate statistics (size range, median,avg_corrrange) distinct fromprint()'s per-subset listing.
Performance
runELS()'s degeneracy ordering uses a bucket-queue instead of an O(m^2) repeated min-degree scan, and no longer materializes the full n x n compatibility matrix whenforce_inrestricts the search to a small induced subgraph.- The shared Bron-Kerbosch core backtracks its candidate set in place instead of copying it on every recursive call.
- The greedy backend caches per-variable tie-break statistics incrementally instead of rescanning every candidate from scratch each outer iteration.
Test Coverage Improvements
- Added recovery-style and reference-verified tests for
corrPruneandmodelPrune: hand-computed grouped quantile aggregation, exact-value tie-break tests (lexicographic and greedy), a greedy-vs-exact identity check, VIF verified againstcar::vif(), condition-number verified against a manual SVD reference, and seed-repeated recovery tests against simulated ground truth. - Fixed five
modelPrunetests that silently passed a nonexistentthresholdargument instead oflimit. - Added
test-brute-force-ground-truth.R: an independent brute-force maximal-subset enumerator, checked against ELS and Bron-Kerbosch (with and without pivoting) across 40 random seeds, plus 25 more underforce_inconstraints -- validating maximality and exhaustiveness simultaneously. - Added independent hand-derived reference-value tests for Pearson, Spearman, Kendall, bicor, distance correlation, and maximal information coefficient (previously only eta-squared/Cramer's V had these), and extended brute-force ground truth to near-0/near-1 threshold boundaries and larger
force_incases. - Extracted the previously duplicated Pearson/Spearman/Kendall/bicor/distance/maximal/eta/Cramer's V logic (independently reimplemented across
corrSelect(),assocSelect(), and bothcorrPrune()branches, with divergent NA/constant-column policies) into shared primitives inR/assoc-metrics.R, now the single source of truth for every caller.
Submitted to CRAN 2026-07-18; win-builder r-devel: Status OK.