Skip to contents

Master interface for optimal discretization and Weight of Evidence (WoE) computation across numerical and categorical predictors. This function serves as the primary entry point for the OptimalBinningWoE package, providing automatic feature type detection, a default algorithm chosen from the target type ("auto"), and unified output structures for seamless integration into credit scoring and predictive modeling workflows.

Usage

obwoe(
  data,
  target,
  feature = NULL,
  min_bins = 2,
  max_bins = 7,
  algorithm = "auto",
  control = control.obwoe()
)

Arguments

data

A data.frame containing the predictor variables (features) and the response variable (target). All features to be binned must be present in this data frame. The data frame should not contain list-columns.

target

Character string specifying the column name of the response variable. Must be a binary outcome encoded as integers 0 (non-event) and 1 (event), or a multinomial outcome encoded as integers 0, 1, 2, ..., K. Missing values in the target are not permitted.

feature

Optional character vector specifying which columns to process. If NULL (default), all columns except target are processed. Features containing only missing values are automatically skipped with a warning.

min_bins

Integer specifying the minimum number of bins. Must satisfy \(2 \le\) min_bins \(\le\) max_bins. Algorithms may produce fewer bins if the data has insufficient unique values. Default is 2.

Read as a floor, but it frequently behaves as the binding constraint rather than a safety net. Several algorithms stop merging as soon as their own criterion is satisfied, which can be well before max_bins, so min_bins ends up setting the partition. On the German Credit amount variable with max_bins = 5, the default binning returns two bins and an IV of 0.0016; requesting min_bins = 4 returns an IV of 0.0824 on the same data and the same algorithm. Raising it is a legitimate way to ask for a finer partition, and worth trying when a continuous predictor collapses to two bins under a larger max_bins.

max_bins

Integer specifying the maximum number of bins. Controls the granularity of discretization. Higher values capture more detail but risk overfitting. Typical values range from 5 to 10 for credit scoring applications. Default is 7.

algorithm

Character string specifying the binning algorithm. Use "auto" (default) for automatic selection based on target type: "jedi" for binary targets, "jedi_mwoe" for multinomial. This is a fixed rule, not a search: no algorithm is compared or chosen by IV. See Details for the complete algorithm taxonomy.

control

A list of algorithm-specific control parameters created by control.obwoe. Provides fine-grained control over convergence thresholds, bin cutoffs, and other optimization parameters.

Value

An S3 object of class "obwoe" containing:

results

Named list where each element contains the binning result for a single feature, including:

bin

Character vector of bin labels/intervals

woe

Numeric vector of Weight of Evidence per bin

iv

Numeric vector of Information Value contribution per bin

count

Integer vector of observation counts per bin

count_pos

Integer vector of positive (event) counts per bin

count_neg

Integer vector of negative (non-event) counts per bin

cutpoints

Numeric vector of bin boundaries (numerical only)

converged

Logical indicating algorithm convergence

iterations

Integer count of optimization iterations

summary

Data frame with one row per feature containing: feature (name), type (numerical/categorical), algorithm (used), n_bins (count), total_iv (sum), error (logical flag)

target

Name of the target column

target_type

Detected type: "binary" or "multinomial"

n_features

Number of features processed

call

The matched function call for reproducibility

Details

Theoretical Foundation

Weight of Evidence (WoE) transformation is a staple of credit scoring methodology, originating from information theory and the concept of evidential support (Good, 1950; Kullback, 1959). For a bin \(i\), the WoE is defined as:

$$WoE_i = \ln\left(\frac{p_i}{n_i}\right) = \ln\left(\frac{N_{i,1}/N_1}{N_{i,0}/N_0}\right)$$

where:

  • \(N_{i,1}\) = number of events (target=1) in bin \(i\)

  • \(N_{i,0}\) = number of non-events (target=0) in bin \(i\)

  • \(N_1\), \(N_0\) = total events and non-events, respectively

  • \(p_i = N_{i,1}/N_1\) = proportion of events in bin \(i\)

  • \(n_i = N_{i,0}/N_0\) = proportion of non-events in bin \(i\)

The Information Value (IV) quantifies the total predictive power of a binning:

$$IV = \sum_{i=1}^{k} (p_i - n_i) \times WoE_i = \sum_{i=1}^{k} (p_i - n_i) \times \ln\left(\frac{p_i}{n_i}\right)$$

where \(k\) is the number of bins. IV is equivalent to the Kullback-Leibler divergence between the event and non-event distributions.

Algorithm Taxonomy

The package provides 28 algorithms (37 implementations: 21 numerical and 16 categorical) organized by supported feature types. The last column says what the code does, which is not always what the name suggests; the algorithm reference vignette (vignette("algorithms", package = "OptimalBinningWoE")) gives the mechanism of each engine.

Universal Algorithms (both numerical and categorical):

IDFull NameMethod
jediJoint Entropy-Driven InformationHeuristic + IV optimization
jedi_mwoeJEDI Multinomial WoEExtension for K>2 classes
cmChiMergeBottom-up chi-squared merging
dpDynamic ProgrammingExact DP over event-rate-ordered levels (categorical); greedy merging, no DP table (numerical)
dmivMetric Divergence MeasuresDivergence-based merging (nine measures)
fetbFisher's Exact TestStatistical significance-based
mobMonotonic Optimal BinningGreedy IV-loss merging with monotonicity
sketchSketchingProbabilistic data structures
udtUnsupervised Decision Tree (misnomer)Supervised: entropy-ranked cuts, then bottom-up merging; no tree

Numerical-Only Algorithms:

IDDescription
bbBranch and Bound (greedy: merges the lowest-IV bin repeatedly; no search tree)
ewbEqual Width Binning (equal-width start, supervised merging)
fast_mdlpFayyad-Irani MDLP (recursive entropy partitioning)
irIsotonic Regression
kmbK-Means inspired Binning (evenly spaced centroids; no k-means iteration)
ldbLocal Density Binning
lpdbLocal Polynomial Density (Gaussian KDE; no polynomial is fitted)
mblpMonotonic Binning LP (greedy; no linear program is solved)
mdlpMinimum Description Length (greedy bottom-up merging by MDL cost)
mrblpMonotonic Risk Binning (greedy; no LP, no likelihood-ratio pre-binning)
oslpOptimal Supervised LP (greedy; no linear program is solved)
ubsdUnsupervised Std-Dev Based (unsupervised start, supervised refinement)

Categorical-Only Algorithms:

IDDescription
gmbGreedy Monotonic Binning
ivbInformation Value DP (exact over levels ordered by event rate)
mbaModified Binning Algorithm
milpMixed Integer LP (greedy merging; no integer program is solved)
sabSimulated Annealing
sblpSimilarity-Based LP (exact DP over levels ordered by event rate)
swbSliding Window Binning (greedy Jensen-Shannon merging)

Automatic Type Detection

Feature types are detected as follows:

  • Numerical: numeric or integer vectors not of class factor

  • Categorical: character, factor, or logical vectors

When algorithm = "auto", the function selects:

  • "jedi" for binary targets (recommended for most use cases)

  • "jedi_mwoe" for multinomial targets (K > 2 classes)

IV Interpretation Guidelines

The package grades IV with the usual rule-of-thumb bands. The cut-offs 0.02, 0.10 and 0.30 are usually credited to Siddiqi (2006); the top band is a common extension that Navas-Palencia (2020, Table 3) labels "over-prediction". The bands are conventions, not statistical tests.

IV RangePredictive Power
< 0.02Unpredictive
0.02 - 0.10Weak
0.10 - 0.30Medium
0.30 - 0.50Strong
>= 0.50Suspicious (look for leakage or overfitting)

Computational Considerations

Time complexity varies by algorithm:

  • JEDI, ChiMerge, MOB: \(O(n \log n + k^2 m)\) where \(n\) = observations, \(k\) = bins, \(m\) = iterations

  • Dynamic programming over ordered levels (ivb, categorical dp, sblp): \(O(K L^2)\) where \(K\) = max_bins and \(L\) = levels left after pre-binning

  • Equal Width: \(O(n)\) (fastest, but unsupervised start)

  • MILP, numerical DP: greedy merging; no integer program is solved and no DP table is built

For large datasets (\(n > 10^6\)), consider:

  1. Using algorithm = "sketch", which for numerical features proposes cut points from a quantile sketch (the data still have to fit in memory)

  2. Reducing max_n_prebins via control.obwoe()

  3. Sampling the data before binning

References

Good, I. J. (1950). Probability and the Weighing of Evidence. Griffin, London.

Kullback, S. (1959). Information Theory and Statistics. Wiley, New York.

Siddiqi, N. (2006). Credit Risk Scorecards: Developing and Implementing Intelligent Credit Scoring. John Wiley & Sons. doi:10.1002/9781119201731

Thomas, L. C., Edelman, D. B., & Crook, J. N. (2002). Credit Scoring and Its Applications. SIAM Monographs on Mathematical Modeling and Computation. doi:10.1137/1.9780898718317

Navas-Palencia, G. (2020). Optimal Binning: Mathematical Programming Formulation. arXiv preprint arXiv:2001.08025. doi:10.48550/arXiv.2001.08025

Zeng, G. (2014). A Necessary Condition for a Good Binning Algorithm in Credit Scoring. Applied Mathematical Sciences, 8(65), 3229-3242.

See also

control.obwoe for algorithm-specific parameters, obwoe_algorithms to list all available algorithms with capabilities, print.obwoe for display methods, ob_apply_woe_num and ob_apply_woe_cat to apply WoE transformations to new data.

For individual algorithms with full parameter control: ob_numerical_jedi, ob_categorical_jedi, ob_numerical_mdlp, ob_categorical_ivb.

Examples

# \donttest{
# =============================================================================
# Example 1: Basic Usage with Mixed Feature Types
# =============================================================================
set.seed(42)
n <- 2000

# Simulate credit scoring data
df <- data.frame(
  # Numerical features
  age = pmax(18, pmin(80, rnorm(n, 45, 15))),
  income = exp(rnorm(n, 10, 0.8)),
  debt_ratio = rbeta(n, 2, 5),
  credit_history_months = rpois(n, 60),

  # Categorical features
  education = sample(c("High School", "Bachelor", "Master", "PhD"),
    n,
    replace = TRUE, prob = c(0.35, 0.40, 0.20, 0.05)
  ),
  employment = sample(c("Employed", "Self-Employed", "Unemployed", "Retired"),
    n,
    replace = TRUE, prob = c(0.60, 0.20, 0.10, 0.10)
  ),

  # Binary target (default probability varies by features)
  target = rbinom(n, 1, 0.15)
)

# Process all features with automatic algorithm selection
result <- obwoe(df, target = "target")
print(result)
#> Optimal Binning Weight of Evidence
#> ===================================
#> 
#> Target: target ( binary )
#> Features processed: 6 
#> 
#> Results:  6  successful
#> 
#> Top features by IV:
#>   employment: IV = 0.0269 (4 bins, jedi)
#>   credit_history_months: IV = 0.0193 (4 bins, jedi)
#>   income: IV = 0.0092 (4 bins, jedi)
#>   debt_ratio: IV = 0.0091 (4 bins, jedi)
#>   education: IV = 0.0048 (4 bins, jedi)
#>   ... and 1 more

# View detailed summary
print(result$summary)
#>                 feature        type algorithm n_bins    total_iv converged
#> 1                   age   numerical      jedi      3 0.002240807      TRUE
#> 2                income   numerical      jedi      4 0.009202297      TRUE
#> 3            debt_ratio   numerical      jedi      4 0.009121470      TRUE
#> 4 credit_history_months   numerical      jedi      4 0.019333587      TRUE
#> 5             education categorical      jedi      4 0.004793127      TRUE
#> 6            employment categorical      jedi      4 0.026934398      TRUE
#>   iterations error
#> 1          2 FALSE
#> 2          2 FALSE
#> 3          2 FALSE
#> 4          2 FALSE
#> 5          0 FALSE
#> 6          0 FALSE

# Access results for a specific feature
age_bins <- result$results$age
print(data.frame(
  bin = age_bins$bin,
  woe = round(age_bins$woe, 3),
  iv = round(age_bins$iv, 4),
  count = age_bins$count
))
#>                     bin    woe     iv count
#> 1      (-Inf;40.127923] -0.022 0.0002   741
#> 2 (40.127923;59.856538] -0.020 0.0002   953
#> 3      (59.856538;+Inf]  0.109 0.0019   306

# =============================================================================
# Example 2: Using a Specific Algorithm
# =============================================================================

# Use MDLP for numerical features (entropy-based)
result_mdlp <- obwoe(df,
  target = "target",
  feature = c("age", "income"),
  algorithm = "mdlp",
  min_bins = 3,
  max_bins = 6
)

cat("\nMDLP Results:\n")
#> 
#> MDLP Results:
print(result_mdlp$summary)
#>   feature      type algorithm n_bins    total_iv converged iterations error
#> 1     age numerical      mdlp      3 0.010698216      TRUE          8 FALSE
#> 2  income numerical      mdlp      3 0.008449322      TRUE          7 FALSE

# =============================================================================
# Example 3: Custom Control Parameters
# =============================================================================

# Fine-tune algorithm behavior
ctrl <- control.obwoe(
  bin_cutoff = 0.02, # Minimum 2% per bin
  max_n_prebins = 30, # Allow more initial bins
  convergence_threshold = 1e-8
)

result_custom <- obwoe(df,
  target = "target",
  feature = "debt_ratio",
  algorithm = "jedi",
  control = ctrl
)

cat("\nCustom JEDI Result:\n")
#> 
#> Custom JEDI Result:
print(result_custom$results$debt_ratio$bin)
#> [1] "(-Inf;0.085180]"     "(0.085180;0.119174]" "(0.119174;0.345076]"
#> [4] "(0.345076;+Inf]"    

# =============================================================================
# Example 4: Comparing Multiple Algorithms
# =============================================================================

algorithms <- c("jedi", "mdlp", "ewb", "mob")
iv_comparison <- sapply(algorithms, function(algo) {
  tryCatch(
    {
      res <- obwoe(df, target = "target", feature = "income", algorithm = algo)
      res$summary$total_iv
    },
    error = function(e) NA_real_
  )
})

cat("\nAlgorithm Comparison (IV for 'income'):\n")
#> 
#> Algorithm Comparison (IV for 'income'):
print(sort(iv_comparison, decreasing = TRUE))
#>         jedi          mob          ewb         mdlp 
#> 0.0092022973 0.0079960821 0.0028174937 0.0003402702 

# =============================================================================
# Example 5: Feature Selection Based on IV
# =============================================================================

# Process all features and select those with IV > 0.02
result_all <- obwoe(df, target = "target")

strong_features <- result_all$summary[
  result_all$summary$total_iv >= 0.02 & !result_all$summary$error,
  c("feature", "total_iv", "n_bins")
]
strong_features <- strong_features[order(-strong_features$total_iv), ]

cat("\nFeatures with IV >= 0.02 (predictive):\n")
#> 
#> Features with IV >= 0.02 (predictive):
print(strong_features)
#>      feature  total_iv n_bins
#> 6 employment 0.0269344      4

# =============================================================================
# Example 6: Handling Algorithm Compatibility
# =============================================================================

# MDLP only works for numerical - will fail for categorical
result_mixed <- obwoe(df,
  target = "target",
  algorithm = "mdlp"
)

# Check for errors
cat("\nCompatibility check:\n")
#> 
#> Compatibility check:
print(result_mixed$summary[, c("feature", "type", "error")])
#>                 feature        type error
#> 1                   age   numerical FALSE
#> 2                income   numerical FALSE
#> 3            debt_ratio   numerical FALSE
#> 4 credit_history_months   numerical FALSE
#> 5             education categorical  TRUE
#> 6            employment categorical  TRUE
# }