Skip to content

v3.2.3

Choose a tag to compare

@gcol33 gcol33 released this 17 Jul 23:26
· 40 commits to main since this release

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 > threshold is false in 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-8 tolerance, and could reject matrices the R layer had already accepted as symmetric. Added a minimum ncol >= 2 guard, and n_rows_used no longer reports a fabricated row count for matrix input (now NA).
  • corrSelect: numeric force_in indices 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 measure argument 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 new assoc_methods_used attribute.
  • 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 own assign bookkeeping.
  • modelPrune: formulas with a transformed response (e.g. log(mpg) ~ .) crashed during formula parsing.
  • corrSubset: which = "best" on a CorrCombo with no subsets raised an uninformative "subscript out of bounds" error instead of a clear message.
  • Internal Rcpp exports (runELS, runBronKerbosch) now validate force_in bounds directly rather than relying solely on the R-level dispatcher.
  • Added duplicate-column-name checks, force_in/by overlap detection, and a coverage warning when most groups are skipped during grouped aggregation in corrPrune.
  • 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 unparenthesized term | group fragments was silently reinterpreted by lme4/glmmTMB as 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 NA instead of floating-point noise or Inf), 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 when mode = "auto" routed through MatSelect(); threshold > 1 silently succeeded in mode = "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 NA silently dropped from the group_q quantile (breaking the documented "group_q = 1 holds in every group" guarantee), a suppressWarnings() call hid the informative NA-row-removal warning, n_rows_used double-counted rows from skipped groups, and NA in the by column itself silently excluded rows with no warning at all.
  • corrPrune: force_in association magnitude is now compared with abs() rather than the signed value, so negatively-correlated force_in pairs are correctly rejected in exact mode (greedy mode already matched).
  • corrPrune: errors when by names every column, instead of silently returning a zero-predictor result.
  • corrPrune: distance/maximal association 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_used attribute 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 while assocSelect() 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_in mutual-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 with method = "els" (a no-op there).
  • MatSelect / assocSelect: force_in is now validated as whole numbers, matching corrSelect()'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() let NA/NaN correlation entries silently pass symmetry and diagonal checks (IEEE-754 comparisons against NaN are 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 deduplicates force_in so runELS()/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-kerbosch with pivoting) returned scrambled combos, silently reordering corrPrune()/corrSubset() output columns.
  • C++ backend: greedyPruneBackend(), the fourth Rcpp-exported backend, was missed by an earlier shared-validation refactor and skipped validateCorMatrix()/validateForcedIndices() entirely.
  • corrSubset: the missing-value warning now lists only the subsets that actually contain NAs.
  • Two package examples called corrSelect(cor(mat)) where MatSelect(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_corr range) distinct from print()'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 when force_in restricts 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 corrPrune and modelPrune: hand-computed grouped quantile aggregation, exact-value tie-break tests (lexicographic and greedy), a greedy-vs-exact identity check, VIF verified against car::vif(), condition-number verified against a manual SVD reference, and seed-repeated recovery tests against simulated ground truth.
  • Fixed five modelPrune tests that silently passed a nonexistent threshold argument instead of limit.
  • 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 under force_in constraints -- 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_in cases.
  • Extracted the previously duplicated Pearson/Spearman/Kendall/bicor/distance/maximal/eta/Cramer's V logic (independently reimplemented across corrSelect(), assocSelect(), and both corrPrune() branches, with divergent NA/constant-column policies) into shared primitives in R/assoc-metrics.R, now the single source of truth for every caller.

Submitted to CRAN 2026-07-18; win-builder r-devel: Status OK.