Skip to content

Releases: evandeilton/OptimalBinningWoE

v1.11.0 — Runtime audit: crash, hang, data-loss and reproducibility fixes

Choose a tag to compare

@evandeilton evandeilton released this 12 Aug 21:15

Runtime audit of the C++ engine, following the static audit that shipped in 1.10.0. This round was driven by instrumented builds (-fsanitize=address,undefined), a degenerate-input stress harness, and a golden-output regression suite covering all 37 exported algorithms (~3,200 result comparisons).

Every fix below is pinned by a test in tests/testthat/test-regression-audit.R, and each of those tests fails on 1.10.0.

Crashes and hangs

  • ob_categorical_ivb crashed the R session — a segmentation fault, not a catchable error — for any feature with no more categories than max_bins. With the default max_bins = 5 that meant every 2- to 5-level predictor: sex, marital status, region, education. The ncat <= max_bins fast path skipped the only place stats_cache was built, while result assembly dereferenced it unconditionally.

  • ob_numerical_ir, ob_numerical_jedi and ob_numerical_jedi_mwoe looped forever whenever min_bins exceeded the number of distinct feature values — routine when one min_bins is applied across a whole feature set. The loops contained no R_CheckUserInterrupt(), so Ctrl-C could not interrupt them either; the session had to be killed.

Silent data loss

  • ob_numerical_cm discarded observations. Its equal-frequency pre-binner, on hitting a run of tied values straddling a bin boundary, advanced past those records without assigning them to any bin. In testing this lost 26% of an integer feature and 40% of a coarse one, with the reported WoE/IV computed from the survivors alone.

Interval convention — standardised on (a, b]

The package previously disagreed with itself about what a bin is. Labels advertised (a;b] while many algorithms assigned values as [a;b); ob_apply_woe_num() had its two binary searches swapped, so both settings of include_upper_bound did the opposite of what the argument documents; and several pre-binners split runs of tied values, so their counts could not be derived from their own cutpoints at all. A value landing exactly on a cutpoint — routine for integer, rounded or currency features — could be scored into a different bin than the one it was trained in.

Measured across 63 algorithm/dataset combinations: 31 violated the documented convention before this release, 0 do now.

Reproducibility

  • ob_categorical_sab now honours set.seed(). It was seeded from std::random_device, so identical input returned a different binning on every call. Four runs under one seed previously gave bins 4/3/5/5 with three distinct IV values — unusable for auditable or regulated models.

  • obcorr() now returns a stable row order. Per-thread buffers were spliced together in an omp critical block, so row order depended on thread scheduling; two runs with the same data and same thread count could differ. Correlation values are unchanged.

CRAN policy

  • obcorr() no longer seizes every core. With the default threads = 0 it called omp_set_num_threads(std::thread::hardware_concurrency()). CRAN Repository Policy requires never using more than two cores by default; this was an archival risk. The default is now at most 2, honouring OMP_NUM_THREADS, with an explicit threads argument still respected.

  • Removed eight stale Dropbox conflict copies from src/ that R CMD build would have shipped and compiled, producing duplicate symbols.

Compatibility

This release changes numeric output. Of 3,240 golden comparisons against 1.10.0, 2,353 are unchanged; 12 numerical algorithms plus ob_categorical_sab differ, concentrated in data with ties (integer, rounded or currency features). Continuous features without ties are largely unaffected. No public R API changed.

Users with scorecards in production should expect bin boundaries to shift on discrete features, and re-validate. The previous results were silently incorrect at those boundaries.

Verification

R CMD check green on Ubuntu (devel, release, oldrel-1/2/3) and Windows (release). Test suite runs with 0 failures and 0 skips — the ob_numerical_sketch test disabled since 1.0.7 for a segfault is re-enabled, re-verified clean under -fsanitize=address,undefined. The full suite is also clean under ASan/UBSan.

Known issues

  • ob_categorical_dp and ob_categorical_fetb report WoE = IV = 0 for perfectly separating bins instead of applying smoothing.
  • Return fields are not uniform across algorithms: event_rate appears in 13 of 37, total_iv in 28 of 37, and iv is missing from both _dmiv variants.
  • ob_apply_woe_num() does not support the multinomial *_jedi_mwoe variants.

v1.10.0 — C++ Engine Audit & Hardening

Choose a tag to compare

@evandeilton evandeilton released this 17 May 16:41

C++ Engine — Comprehensive Audit & Hardening

Full static audit of the C++ engine covering all 36 binning algorithms. Zero public R API changes — fully backward compatible.

Bug Fixes

  • OB_LogisticRegression — Threshold-based singularity guard; Eigen::LDLT replaces hessian.inverse(); cwiseMax(0) prevents NaN SEs
  • OBN_MDLP monotonicity directionis_monotonic() / enforce_monotonicity() now auto-detect ascending vs descending trend (Welford slope); previously hardcoded ascending caused unnecessary merges on negative correlations
  • OBC_DP backtracking OOB — Guard before static_cast<size_t>(prev_j); invalid index raises descriptive error instead of UB
  • OBN_DP push-before-validate — Target validation now fires before target_vec.push_back()
  • OBN_MDLP log2(0) — Guard for single-bin MDL cost
  • NumericalBin constructorcount always derived from count_pos + count_neg; invariant enforced at construction time

Performance

  • OBC_DP DP outer loop removed — Deterministic DP was wrapped in a redundant 1000-iteration loop; up to 1000× speedup
  • OBC_DP::ensure_max_prebins() — O(m² log m) full re-sort per merge → O(m) binary-search insertion
  • OBN_MDLP::apply_mdl_merging() — O(k³) full-vector copy per candidate → O(k²) analytical MDL delta
  • OBN_BB::quantile() — O(n_prebins × n log n) per-call sorts → single sort in prebinning()
  • OBN_DP correlation — Naive Pearson (catastrophic cancellation) → Welford's online algorithm

CRAN / ODR Safety

  • safe_math.hconstexprinline for 6 math functions (SOLARIS/Studio compat)
  • chi_square_utils.h — Namespace-scope const map → static local function (one copy per process)
  • entropy_utils.h — 81 KB ENTROPY_LUT per-TU → shared static local instance
  • OBC_CM_v5 — Duplicate ChiSquareCache class removed; uses shared header
  • 35 .cpp files — Duplicate using namespace Rcpp before #include headers removed

Code Quality

  • Dead commented-out code blocks removed (OBC_DP)
  • Variable shadowing fixed (OBN_DP: total_countrare_total)
  • [[Rcpp::plugins(cpp11)]] standardised across all files
  • Auto monotonicity detection implemented for categorical DP ("auto" trend)

OptimalBinningWoE 1.0.9

Choose a tag to compare

@evandeilton evandeilton released this 14 Mar 17:10
  • CRAN Fix (2026-03-14) - Replaced Rf_error with Rcpp::stop:
    • Fixed C++ Exception Handling: Addressed an issue reported by @Enchufa2 regarding the usage of ::Rf_error inside catch(...) blocks. Updated all instances to use Rcpp::stop to ensure proper C++ stack unwinding and avoid memory leaks.
    • Affected Files: src/OBN_LPDB_v5.cpp, src/OBN_EWB_v5.cpp, src/OBN_KMB_v5.cpp, src/OBN_LDB_v5.cpp, src/OBN_MBLP_v5.cpp.

OptimalBinningWoE 1.0.8

Choose a tag to compare

@evandeilton evandeilton released this 29 Jan 15:26
  • CRAN Fix (2026-01-28) - LTO/ODR Compliance:
    • Fixed One Definition Rule (ODR) violations: Wrapped internal helper classes IVCache and CumulativeStatsCache in anonymous namespaces within OBC_GMB_v5.cpp, OBC_IVB_v5.cpp, and OBC_JEDI_v5.cpp. This resolves Link-Time Optimization (LTO) warnings/errors on CRAN checks.

OptimalBinningWoE 1.0.7

Choose a tag to compare

@evandeilton evandeilton released this 28 Jan 02:21
  • UBSAN Investigation Fix (2026-01-27) - Addressing persistent memory safety errors:

    • Temporarily disabled ob_categorical_sketch tests: The sketch-based categorical binning
      algorithm is under investigation for persistent UBSAN memory errors that appear to be
      related to cache invalidation timing in GitHub Actions CI environment.

    • Removed MergeCache class from OBC_Sketch_v5.cpp: Completely removed the caching
      mechanism and implemented on-the-fly divergence calculation to eliminate potential
      memory corruption sources.

  • Affected Files:

    • src/OBC_Sketch_v5.cpp: MergeCache class removed, divergence calculated on-the-fly
    • tests/testthat/test-categorical-all.R: Sketch tests temporarily commented out
  • No API Changes: Fully backward compatible with v1.0.6.