Tutorial: optimal piecewise binning with binary target
Basic
To get us started, let’s load a well-known dataset from the UCI repository and transform the data into a pandas.DataFrame.
[1]:
import numpy as np
import pandas as pd
from sklearn.datasets import load_breast_cancer
from sklearn.ensemble import GradientBoostingClassifier
[2]:
data = load_breast_cancer()
df = pd.DataFrame(data.data, columns=data.feature_names)
We choose a variable to discretize and the binary target.
[3]:
variable = "mean radius"
x = df[variable].values
y = data.target
Import and instantiate an OptimalPWBinning object class and we pass the variable name. The OptimalPWBinning can ONLY handle numerical variables. This differs from the OptimalBinning object class.
[4]:
from optbinning import OptimalPWBinning
[5]:
optb = OptimalPWBinning(name=variable)
We fit the optimal binning object with arrays x and y. We set a minimum (lower bound) and maximum (upper bound) probability using arguments lb and ub. Note that these bounds are optional, but guarantee an event rate between \([0, 1]\) as any probability distribution. As shown later, settings these bounds will increase the problem size thus leading to higher solution times. An alternative approach to bypass these bounds is implemented when transforming to event rate or WoE
using functions fit and fit_transform, where values outside the interval \([0, 1]\) are clipped.
Note that the Weight-of-Evidence is not defined for event rate equal 0 or 1. Therefore, for small datasets, we recommend modifying the interval \([0, 1]\) with a small value, thus using \([\epsilon, 1 - \epsilon]\).
[6]:
optb.fit(x, y, lb=0.001, ub=0.999)
[6]:
OptimalPWBinning(estimator=LogisticRegression(), name='mean radius')In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
Parameters
| name | 'mean radius' | |
| estimator | LogisticRegression() | |
| objective | 'l2' | |
| degree | 1 | |
| continuous | True | |
| continuous_deriv | True | |
| prebinning_method | 'cart' | |
| max_n_prebins | 20 | |
| min_prebin_size | 0.05 | |
| min_n_bins | None | |
| max_n_bins | None | |
| min_bin_size | None | |
| max_bin_size | None | |
| monotonic_trend | 'auto' | |
| n_subsamples | None | |
| max_pvalue | None | |
| max_pvalue_policy | 'consecutive' | |
| outlier_detector | None | |
| outlier_params | None | |
| user_splits | None | |
| user_splits_fixed | None | |
| special_codes | None | |
| split_digits | None | |
| solver | 'auto' | |
| h_epsilon | 1.35 | |
| quantile | 0.5 | |
| regularization | None | |
| reg_l1 | 1.0 | |
| reg_l2 | 1.0 | |
| random_state | None | |
| verbose | False |
LogisticRegression()
Parameters
You can check if an optimal solution has been found via the status attribute:
[7]:
optb.status
[7]:
'OPTIMAL'
You can also retrieve the optimal split points via the splits attribute:
[8]:
optb.splits
[8]:
array([11.42500019, 12.32999992, 13.09499979, 13.70499992, 15.04500008,
16.92500019])
The binning table
The optimal binning algorithms return a binning table; a binning table displays the binned data and several metrics for each bin. Class OptimalPWBinning returns an object PWBinningTable via the binning_table attribute.
[9]:
binning_table = optb.binning_table
[10]:
type(binning_table)
[10]:
optbinning.binning.piecewise.binning_statistics.PWBinningTable
The binning_table is instantiated, but not built. Therefore, the first step is to call the method build, which returns a pandas.DataFrame.
[11]:
binning_table.build()
[11]:
| Bin | Count | Count (%) | Non-event | Event | c0 | c1 | |
|---|---|---|---|---|---|---|---|
| 0 | (-inf, 11.43) | 118 | 0.207381 | 3 | 115 | 1.032597 | -0.004813 |
| 1 | [11.43, 12.33) | 79 | 0.138840 | 3 | 76 | 1.644465 | -0.058368 |
| 2 | [12.33, 13.09) | 68 | 0.119508 | 7 | 61 | 2.140581 | -0.098604 |
| 3 | [13.09, 13.70) | 49 | 0.086116 | 10 | 39 | 2.915209 | -0.157759 |
| 4 | [13.70, 15.05) | 83 | 0.145870 | 28 | 55 | 4.107004 | -0.244719 |
| 5 | [15.05, 16.93) | 54 | 0.094903 | 44 | 10 | 3.562027 | -0.208496 |
| 6 | [16.93, inf) | 118 | 0.207381 | 117 | 1 | 0.081993 | -0.002881 |
| 7 | Special | 0 | 0.000000 | 0 | 0 | 0.0 | 0.0 |
| 8 | Missing | 0 | 0.000000 | 0 | 0 | 0.0 | 0.0 |
| Totals | 569 | 1.000000 | 212 | 357 | - | - |
Let’s describe the columns of this binning table:
Bin: the intervals delimited by the optimal split points.
Count: the number of records for each bin.
Count (%): the percentage of records for each bin.
Non-event: the number of non-event records \((y = 0)\) for each bin.
Event: the number of event records \((y = 1)\) for each bin.
\(c_0\): the first coefficient of the event rate polynomial.
\(c_1\): the second coefficient of the event rate polynomial.
The event rate for bin \(i\) is defined as \(ER_i = c_0 + c_1 x_i\), where \(x_i \in \text{Bin}_{i}\). In general, \begin{equation} ER_i = \sum_{j=0}^d c_j x_i^j, \end{equation} where \(d\) is the degree of the event rate polynomial.
The last row shows the total number of records, non-event records, event records.
You can use the method plot to visualize the number of non-event and event records by bin, and WoE or event rate curve.
[12]:
binning_table.plot(metric="woe")
[13]:
binning_table.plot(metric="event_rate")
Event rate / WoE transformation
Now that we have checked the binned data, we can transform our original data into WoE or event rate values.
[14]:
x_transform_woe = optb.transform(x, metric="woe")
[15]:
x_transform_event_rate = optb.transform(x, metric="event_rate")
Advanced
Optimal binning Information
The OptimalPWBinning can print overview information about the options settings, problem statistics, and the solution of the computation. By default, print_level=1.
[16]:
optb = OptimalPWBinning(name=variable)
optb.fit(x, y)
[16]:
OptimalPWBinning(estimator=LogisticRegression(), name='mean radius')In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
Parameters
| name | 'mean radius' | |
| estimator | LogisticRegression() | |
| objective | 'l2' | |
| degree | 1 | |
| continuous | True | |
| continuous_deriv | True | |
| prebinning_method | 'cart' | |
| max_n_prebins | 20 | |
| min_prebin_size | 0.05 | |
| min_n_bins | None | |
| max_n_bins | None | |
| min_bin_size | None | |
| max_bin_size | None | |
| monotonic_trend | 'auto' | |
| n_subsamples | None | |
| max_pvalue | None | |
| max_pvalue_policy | 'consecutive' | |
| outlier_detector | None | |
| outlier_params | None | |
| user_splits | None | |
| user_splits_fixed | None | |
| special_codes | None | |
| split_digits | None | |
| solver | 'auto' | |
| h_epsilon | 1.35 | |
| quantile | 0.5 | |
| regularization | None | |
| reg_l1 | 1.0 | |
| reg_l2 | 1.0 | |
| random_state | None | |
| verbose | False |
LogisticRegression()
Parameters
If print_level=0, a minimal output including the header, variable name, status, and total time are printed.
[17]:
optb.information(print_level=0)
optbinning (Version 1.0.0)
Copyright (c) 2019-2026 Guillermo Navas-Palencia, Apache License 2.0
Name : mean radius
Status : OPTIMAL
Time : 0.5454 sec
If print_level>=1, statistics on the pre-binning phase and the solver are printed. More detailed timing statistics are also included.
[18]:
optb.information(print_level=1)
optbinning (Version 1.0.0)
Copyright (c) 2019-2026 Guillermo Navas-Palencia, Apache License 2.0
Name : mean radius
Status : OPTIMAL
Pre-binning statistics
Number of bins 7
Solver statistics
Type auto
Number of variables 14
Number of constraints 20
Timing
Total time 0.55 sec
Pre-processing 0.00 sec ( 0.06%)
Estimator 0.15 sec ( 26.87%)
Pre-binning 0.21 sec ( 38.29%)
Solver 0.16 sec ( 30.09%)
Post-processing 0.02 sec ( 4.49%)
If print_level=2, the list of all options of the OptimalBinning are displayed.
[19]:
optb.information(print_level=2)
optbinning (Version 1.0.0)
Copyright (c) 2019-2026 Guillermo Navas-Palencia, Apache License 2.0
Begin options
name mean radius * U
estimator yes * U
objective l2 * d
degree 1 * d
continuous True * d
prebinning_method cart * d
max_n_prebins 20 * d
min_prebin_size 0.05 * d
min_n_bins no * d
max_n_bins no * d
min_bin_size no * d
max_bin_size no * d
monotonic_trend auto * d
n_subsamples no * d
max_pvalue no * d
max_pvalue_policy consecutive * d
outlier_detector no * d
outlier_params no * d
user_splits no * d
special_codes no * d
split_digits no * d
solver auto * d
h_epsilon 1.35 * d
quantile 0.5 * d
regularization no * d
reg_l1 1.0 * d
reg_l2 1.0 * d
random_state no * d
verbose False * d
End options
Name : mean radius
Status : OPTIMAL
Pre-binning statistics
Number of bins 7
Solver statistics
Type auto
Number of variables 14
Number of constraints 20
Timing
Total time 0.55 sec
Pre-processing 0.00 sec ( 0.06%)
Estimator 0.15 sec ( 26.87%)
Pre-binning 0.21 sec ( 38.29%)
Solver 0.16 sec ( 30.09%)
Post-processing 0.02 sec ( 4.49%)
Binning table statistical analysis
The analysis method performs a statistical analysis of the binning table, computing the statistics Gini index, Information Value (IV), Jensen-Shannon divergence, and the quality score. Additionally, several statistical significance tests between consecutive bins of the contingency table are performed: a frequentist test using the Chi-square test or the Fisher’s exact test, and a Bayesian A/B test using the beta distribution as a conjugate prior of the Bernoulli distribution. The piecewise
binning also includes two performance metrics: the average precision and the Brier score.
[20]:
binning_table.analysis(pvalue_test="chi2")
---------------------------------------------
OptimalBinning: Binary Binning Table Analysis
---------------------------------------------
General metrics
Gini index 0.87503303
IV (Jeffrey) 4.48034999
JS (Jensen-Shannon) 0.37576960
Hellinger 0.44284048
Triangular 1.20890940
KS 0.72390466
Avg precision 0.95577179
Brier score 0.08956359
HHI 0.15727342
HHI (normalized) 0.01681899
Cramer's V 0.80066760
Quality score 0.00000000
Significance tests
Bin A Bin B t-statistic p-value P[A > B] P[B > A]
0 1 0.289789 5.903557e-01 0.697563 3.024368e-01
1 2 2.285355 1.306002e-01 0.941469 5.853092e-02
2 3 2.317546 1.279217e-01 0.936661 6.333919e-02
3 4 2.539025 1.110634e-01 0.950869 4.913141e-02
4 5 29.418844 5.830790e-08 1.000000 1.898159e-11
5 6 18.624518 1.591604e-05 0.999997 3.106835e-06
[21]:
binning_table.analysis(pvalue_test="fisher")
---------------------------------------------
OptimalBinning: Binary Binning Table Analysis
---------------------------------------------
General metrics
Gini index 0.87503303
IV (Jeffrey) 4.48034999
JS (Jensen-Shannon) 0.37576960
Hellinger 0.44284048
Triangular 1.20890940
KS 0.72390466
Avg precision 0.95577179
Brier score 0.08956359
HHI 0.15727342
HHI (normalized) 0.01681899
Cramer's V 0.80066760
Quality score 0.00000000
Significance tests
Bin A Bin B odd ratio p-value P[A > B] P[B > A]
0 1 0.660870 6.855009e-01 0.697563 3.024368e-01
1 2 0.343985 1.879769e-01 0.941469 5.853092e-02
2 3 0.447541 1.829850e-01 0.936661 6.333919e-02
3 4 0.503663 1.153088e-01 0.950869 4.913141e-02
4 5 0.115702 3.464724e-08 1.000000 1.898148e-11
5 6 0.037607 4.144258e-05 0.999997 3.106835e-06
Event rate / WoE monotonicity
The monotonic_trend option permits forcing a monotonic trend to the event rate curve. The default setting “auto” should be the preferred option, however, some business constraints might require to impose different trends. The default setting “auto” chooses the monotonic trend most likely to maximize the information value from the options “ascending”, “descending”, “peak” and “valley” using a machine-learning-based classifier. For this example, we choose Gradient Boosting as an estimator.
[22]:
variable = "mean texture"
x = df[variable].values
y = data.target
[23]:
optb = OptimalPWBinning(name=variable, estimator=GradientBoostingClassifier())
optb.fit(x, y, lb=0.001, ub=0.999)
[23]:
OptimalPWBinning(estimator=GradientBoostingClassifier(), name='mean texture')In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
Parameters
| name | 'mean texture' | |
| estimator | GradientBoostingClassifier() | |
| objective | 'l2' | |
| degree | 1 | |
| continuous | True | |
| continuous_deriv | True | |
| prebinning_method | 'cart' | |
| max_n_prebins | 20 | |
| min_prebin_size | 0.05 | |
| min_n_bins | None | |
| max_n_bins | None | |
| min_bin_size | None | |
| max_bin_size | None | |
| monotonic_trend | 'auto' | |
| n_subsamples | None | |
| max_pvalue | None | |
| max_pvalue_policy | 'consecutive' | |
| outlier_detector | None | |
| outlier_params | None | |
| user_splits | None | |
| user_splits_fixed | None | |
| special_codes | None | |
| split_digits | None | |
| solver | 'auto' | |
| h_epsilon | 1.35 | |
| quantile | 0.5 | |
| regularization | None | |
| reg_l1 | 1.0 | |
| reg_l2 | 1.0 | |
| random_state | None | |
| verbose | False |
GradientBoostingClassifier()
Parameters
[24]:
binning_table = optb.binning_table
binning_table.build()
[24]:
| Bin | Count | Count (%) | Non-event | Event | c0 | c1 | |
|---|---|---|---|---|---|---|---|
| 0 | (-inf, 15.05) | 92 | 0.161687 | 4 | 88 | 0.931559 | -0.0 |
| 1 | [15.05, 16.39) | 61 | 0.107206 | 8 | 53 | 2.075855 | -0.076058 |
| 2 | [16.39, 17.03) | 29 | 0.050967 | 6 | 23 | 2.149387 | -0.080543 |
| 3 | [17.03, 18.46) | 79 | 0.138840 | 17 | 62 | 1.113762 | -0.019749 |
| 4 | [18.46, 19.47) | 55 | 0.096661 | 20 | 35 | 4.139988 | -0.183684 |
| 5 | [19.47, 20.20) | 36 | 0.063269 | 18 | 18 | 4.417909 | -0.197958 |
| 6 | [20.20, 21.71) | 72 | 0.126538 | 43 | 29 | 0.933399 | -0.025415 |
| 7 | [21.71, 22.74) | 40 | 0.070299 | 27 | 13 | 2.611221 | -0.102698 |
| 8 | [22.74, 24.00) | 29 | 0.050967 | 24 | 5 | 0.275868 | -0.0 |
| 9 | [24.00, 26.98) | 43 | 0.075571 | 30 | 13 | -1.111301 | 0.057799 |
| 10 | [26.98, inf) | 33 | 0.057996 | 15 | 18 | 0.448108 | 0.0 |
| 11 | Special | 0 | 0.000000 | 0 | 0 | 0.0 | 0.0 |
| 12 | Missing | 0 | 0.000000 | 0 | 0 | 0.0 | 0.0 |
| Totals | 569 | 1.000000 | 212 | 357 | - | - |
[25]:
binning_table.plot(metric="event_rate")
For example, we can force the variable mean texture to be convex with respect to the probability of having breast cancer.
[26]:
optb = OptimalPWBinning(name=variable, estimator=GradientBoostingClassifier(),
monotonic_trend="convex")
optb.fit(x, y, lb=0.001, ub=0.999)
[26]:
OptimalPWBinning(estimator=GradientBoostingClassifier(),
monotonic_trend='convex', name='mean texture')In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook. On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
Parameters
| name | 'mean texture' | |
| estimator | GradientBoostingClassifier() | |
| monotonic_trend | 'convex' | |
| objective | 'l2' | |
| degree | 1 | |
| continuous | True | |
| continuous_deriv | True | |
| prebinning_method | 'cart' | |
| max_n_prebins | 20 | |
| min_prebin_size | 0.05 | |
| min_n_bins | None | |
| max_n_bins | None | |
| min_bin_size | None | |
| max_bin_size | None | |
| n_subsamples | None | |
| max_pvalue | None | |
| max_pvalue_policy | 'consecutive' | |
| outlier_detector | None | |
| outlier_params | None | |
| user_splits | None | |
| user_splits_fixed | None | |
| special_codes | None | |
| split_digits | None | |
| solver | 'auto' | |
| h_epsilon | 1.35 | |
| quantile | 0.5 | |
| regularization | None | |
| reg_l1 | 1.0 | |
| reg_l2 | 1.0 | |
| random_state | None | |
| verbose | False |
GradientBoostingClassifier()
Parameters
[27]:
binning_table = optb.binning_table
binning_table.build()
binning_table.plot(metric="event_rate")
User-defined split points
In some situations, we have defined split points or bins required to satisfy a priori belief, knowledge or business constraint. The OptimalPWBinning permits to pass user-defined split points. The supplied information is used as a pre-binning, disallowing any pre-binning method set by the user. Furthermore, version 0.5.0 introduces user_splits_fixed parameter, to allow the user to fix some user-defined splits, so these must appear in the solution.
Example numerical variable:
[28]:
user_splits = [ 14, 15, 16, 17, 20, 21, 22, 27]
user_splits_fixed = [False, True, True, False, False, False, False, False]
[29]:
optb = OptimalPWBinning(name=variable, user_splits=user_splits,
user_splits_fixed=user_splits_fixed)
optb.fit(x, y)
[29]:
OptimalPWBinning(estimator=LogisticRegression(), name='mean texture',
user_splits=[14, 15, 16, 17, 20, 21, 22, 27],
user_splits_fixed=[False, True, True, False, False, False,
False, False])In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook. On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
Parameters
| name | 'mean texture' | |
| estimator | LogisticRegression() | |
| user_splits | [14, 15, ...] | |
| user_splits_fixed | [False, True, ...] | |
| objective | 'l2' | |
| degree | 1 | |
| continuous | True | |
| continuous_deriv | True | |
| prebinning_method | 'cart' | |
| max_n_prebins | 20 | |
| min_prebin_size | 0.05 | |
| min_n_bins | None | |
| max_n_bins | None | |
| min_bin_size | None | |
| max_bin_size | None | |
| monotonic_trend | 'auto' | |
| n_subsamples | None | |
| max_pvalue | None | |
| max_pvalue_policy | 'consecutive' | |
| outlier_detector | None | |
| outlier_params | None | |
| special_codes | None | |
| split_digits | None | |
| solver | 'auto' | |
| h_epsilon | 1.35 | |
| quantile | 0.5 | |
| regularization | None | |
| reg_l1 | 1.0 | |
| reg_l2 | 1.0 | |
| random_state | None | |
| verbose | False |
LogisticRegression()
Parameters
[30]:
binning_table = optb.binning_table
binning_table.build()
[30]:
| Bin | Count | Count (%) | Non-event | Event | c0 | c1 | |
|---|---|---|---|---|---|---|---|
| 0 | (-inf, 14.00) | 54 | 0.094903 | 2 | 52 | 1.154009 | -0.020568 |
| 1 | [14.00, 15.00) | 37 | 0.065026 | 2 | 35 | 1.353911 | -0.034847 |
| 2 | [15.00, 16.00) | 43 | 0.075571 | 7 | 36 | 1.247073 | -0.027724 |
| 3 | [16.00, 20.00) | 210 | 0.369069 | 59 | 151 | 1.569012 | -0.047846 |
| 4 | [20.00, 21.00) | 45 | 0.079086 | 26 | 19 | 1.937899 | -0.06629 |
| 5 | [21.00, 22.00) | 49 | 0.086116 | 30 | 19 | 1.54374 | -0.047521 |
| 6 | [22.00, 27.00) | 99 | 0.173989 | 71 | 28 | 1.893766 | -0.063431 |
| 7 | [27.00, inf) | 32 | 0.056239 | 15 | 17 | 0.181134 | 0.0 |
| 8 | Special | 0 | 0.000000 | 0 | 0 | 0.0 | 0.0 |
| 9 | Missing | 0 | 0.000000 | 0 | 0 | 0.0 | 0.0 |
| Totals | 569 | 1.000000 | 212 | 357 | - | - |
[31]:
optb.information()
optbinning (Version 1.0.0)
Copyright (c) 2019-2026 Guillermo Navas-Palencia, Apache License 2.0
Name : mean texture
Status : OPTIMAL
Pre-binning statistics
Number of bins 8
Solver statistics
Type auto
Number of variables 16
Number of constraints 23
Timing
Total time 0.16 sec
Pre-processing 0.00 sec ( 0.63%)
Estimator 0.02 sec ( 11.38%)
Pre-binning 0.08 sec ( 54.53%)
Solver 0.03 sec ( 19.61%)
Post-processing 0.02 sec ( 12.95%)
Performance: choosing a solver
OptimalPWBinning uses the RoPWR library to solving the piecewise regression problem. See https://github.com/guillermo-navas-palencia/ropwr for details.
Robustness: choosing an objective function and regularization
OptimalPWBinning uses the RoPWR library to solving the piecewise regression problem. See https://github.com/guillermo-navas-palencia/ropwr for details.
Missing data and special codes
For this example, let’s load data from the FICO Explainable Machine Learning Challenge: https://community.fico.com/s/explainable-machine-learning-challenge
[32]:
df = pd.read_csv("data/FICO_challenge/heloc_dataset_v1.csv", sep=",")
The data dictionary of this challenge includes three special values/codes:
-9 No Bureau Record or No Investigation
-8 No Usable/Valid Trades or Inquiries
-7 Condition not Met (e.g. No Inquiries, No Delinquencies)
Version 0.16.0 added support to treat special codes separately.
[33]:
special_codes = {'special_9': -9, 'special_8': -8, 'special_7': -7}
[34]:
variable = "AverageMInFile"
x = df[variable].values.copy()
y = df.RiskPerformance.values
[35]:
df.RiskPerformance.unique()
[35]:
<StringArray>
['Bad', 'Good']
Length: 2, dtype: str
Target is a categorical dichotomic variable, which can be easily transform into numerical.
[36]:
mask = y == "Bad"
y = mask.astype(int)
[37]:
x[10:20] = -8
x[100:105] = -7
For the sake of completeness, we include a few missing values
[38]:
idx = np.random.randint(0, len(x), 500)
x = x.astype(float)
x[idx] = np.nan
[39]:
optb = OptimalPWBinning(name=variable, special_codes=special_codes, solver="osqp")
optb.fit(x, y, lb=0, ub=1)
[39]:
OptimalPWBinning(estimator=LogisticRegression(), name='AverageMInFile',
solver='osqp',
special_codes={'special_7': -7, 'special_8': -8,
'special_9': -9})In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook. On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
Parameters
| name | 'AverageMInFile' | |
| estimator | LogisticRegression() | |
| special_codes | {'special_7': -7, 'special_8': -8, 'special_9': -9} | |
| solver | 'osqp' | |
| objective | 'l2' | |
| degree | 1 | |
| continuous | True | |
| continuous_deriv | True | |
| prebinning_method | 'cart' | |
| max_n_prebins | 20 | |
| min_prebin_size | 0.05 | |
| min_n_bins | None | |
| max_n_bins | None | |
| min_bin_size | None | |
| max_bin_size | None | |
| monotonic_trend | 'auto' | |
| n_subsamples | None | |
| max_pvalue | None | |
| max_pvalue_policy | 'consecutive' | |
| outlier_detector | None | |
| outlier_params | None | |
| user_splits | None | |
| user_splits_fixed | None | |
| split_digits | None | |
| h_epsilon | 1.35 | |
| quantile | 0.5 | |
| regularization | None | |
| reg_l1 | 1.0 | |
| reg_l2 | 1.0 | |
| random_state | None | |
| verbose | False |
LogisticRegression()
Parameters
[40]:
optb.information(print_level=1)
optbinning (Version 1.0.0)
Copyright (c) 2019-2026 Guillermo Navas-Palencia, Apache License 2.0
Name : AverageMInFile
Status : OPTIMAL
Pre-binning statistics
Number of bins 12
Solver statistics
Type osqp
Number of variables 24
Number of constraints 39
Timing
Total time 0.34 sec
Pre-processing 0.00 sec ( 1.19%)
Estimator 0.05 sec ( 13.51%)
Pre-binning 0.21 sec ( 61.24%)
Solver 0.03 sec ( 9.04%)
Post-processing 0.05 sec ( 14.15%)
[41]:
binning_table = optb.binning_table
binning_table.build()
[41]:
| Bin | Count | Count (%) | Non-event | Event | c0 | c1 | |
|---|---|---|---|---|---|---|---|
| 0 | (-inf, 30.50) | 548 | 0.052395 | 95 | 453 | 0.791991 | -0.003076 |
| 1 | [30.50, 41.50) | 549 | 0.052491 | 148 | 401 | 0.804109 | -0.003473 |
| 2 | [41.50, 48.50) | 510 | 0.048762 | 146 | 364 | 0.808176 | -0.003571 |
| 3 | [48.50, 54.50) | 525 | 0.050196 | 180 | 345 | 0.813653 | -0.003684 |
| 4 | [54.50, 59.50) | 483 | 0.046180 | 198 | 285 | 0.817621 | -0.003757 |
| 5 | [59.50, 64.50) | 602 | 0.057558 | 261 | 341 | 0.819753 | -0.003793 |
| 6 | [64.50, 69.50) | 648 | 0.061956 | 299 | 349 | 0.826993 | -0.003905 |
| 7 | [69.50, 74.50) | 679 | 0.064920 | 324 | 355 | 0.812736 | -0.0037 |
| 8 | [74.50, 80.50) | 770 | 0.073621 | 400 | 370 | 0.870012 | -0.004469 |
| 9 | [80.50, 97.50) | 1816 | 0.173630 | 1029 | 787 | 0.777589 | -0.003321 |
| 10 | [97.50, 116.50) | 1173 | 0.112152 | 719 | 454 | 1.0448 | -0.006061 |
| 11 | [116.50, inf) | 1092 | 0.104408 | 708 | 384 | 0.486695 | -0.001271 |
| 12 | special_9 | 560 | 0.053542 | 253 | 307 | 0.548214 | 0.0 |
| 13 | special_8 | 10 | 0.000956 | 2 | 8 | 0.8 | 0.0 |
| 14 | special_7 | 5 | 0.000478 | 2 | 3 | 0.6 | 0.0 |
| 15 | Missing | 489 | 0.046754 | 236 | 253 | 0.517382 | 0.0 |
| Totals | 10459 | 1.000000 | 5000 | 5459 | - | - |
Note the dashed bins 10 and 11, corresponding to the special codes bin and the missing bin, respectively.
[42]:
binning_table.plot(metric="event_rate")
Verbosity option
For debugging purposes, we can print information on each step of the computation by triggering the verbose option.
[43]:
optb = OptimalPWBinning(name=variable, solver="osqp", verbose=True)
optb.fit(x, y, lb=0, ub=1)
2026-09-12 18:33:41,517 | INFO : Optimal piecewise binning started.
2026-09-12 18:33:41,520 | INFO : Options: check parameters.
2026-09-12 18:33:41,522 | INFO : Pre-processing started.
2026-09-12 18:33:41,523 | INFO : Pre-processing: number of samples: 10459
2026-09-12 18:33:41,526 | INFO : Pre-processing: number of clean samples: 9970
2026-09-12 18:33:41,527 | INFO : Pre-processing: number of missing samples: 489
2026-09-12 18:33:41,529 | INFO : Pre-processing: number of special samples: 0
2026-09-12 18:33:41,534 | INFO : Pre-processing terminated. Time: 0.0007s
2026-09-12 18:33:41,536 | INFO : Pre-binning: set logistic regression as an estimator.
2026-09-12 18:33:41,538 | INFO : Pre-binning: estimator fitting started.
2026-09-12 18:33:41,588 | INFO : Pre-binning: estimator terminated. Time 0.0517s.
2026-09-12 18:33:41,590 | INFO : Pre-binning: optimal binning started.
2026-09-12 18:33:41,726 | INFO : Pre-binning: number of splits: 8.
2026-09-12 18:33:41,728 | INFO : Pre-binning: no need for subsamples.
2026-09-12 18:33:41,729 | INFO : Pre-binning: optimal binning terminated. Time 0.1366s.
2026-09-12 18:33:41,731 | INFO : Optimizer started.
2026-09-12 18:33:41,735 | INFO : Optimizer: descending monotonic trend.
(CVXPY) Sep 12 06:33:41 PM: Your problem has 18 variables, 30 constraints, and 0 parameters.
(CVXPY) Sep 12 06:33:41 PM: It is compliant with the following grammars: DCP, DQCP
(CVXPY) Sep 12 06:33:41 PM: DCP verification time: 0.0003 seconds.
(CVXPY) Sep 12 06:33:41 PM: Expression tree has 4 nodes.
(CVXPY) Sep 12 06:33:41 PM: (If you need to solve this problem multiple times, but with different data, consider using parameters.)
(CVXPY) Sep 12 06:33:41 PM: CVXPY will first compile your problem; then, it will invoke a numerical solver to obtain a solution.
(CVXPY) Sep 12 06:33:41 PM: Your problem is compiled with the CPP canonicalization backend.
(CVXPY) Sep 12 06:33:41 PM: Compiling problem (target solver=OSQP).
(CVXPY) Sep 12 06:33:41 PM: Reduction chain: Dcp2Cone -> CvxAttr2Constr -> EliminateZeroSized -> ConeMatrixStuffing -> OSQP
(CVXPY) Sep 12 06:33:41 PM: Applying reduction Dcp2Cone
(CVXPY) Sep 12 06:33:41 PM: Applying reduction CvxAttr2Constr
(CVXPY) Sep 12 06:33:41 PM: Applying reduction EliminateZeroSized
(CVXPY) Sep 12 06:33:41 PM: Applying reduction ConeMatrixStuffing
(CVXPY) Sep 12 06:33:41 PM: Applying reduction OSQP
===============================================================================
CVXPY
v1.9.2
===============================================================================
-------------------------------------------------------------------------------
Compilation
-------------------------------------------------------------------------------
(CVXPY) Sep 12 06:33:41 PM: Finished problem compilation (took 5.283e-02 seconds).
(CVXPY) Sep 12 06:33:41 PM: Invoking solver OSQP to obtain a solution.
(CVXPY) Sep 12 06:33:41 PM: Problem status: optimal
(CVXPY) Sep 12 06:33:41 PM: Optimal value: -2.827e+03
(CVXPY) Sep 12 06:33:41 PM: Compilation took 5.283e-02 seconds
(CVXPY) Sep 12 06:33:41 PM: Solver (including time spent in interface) took 7.961e-03 seconds
-------------------------------------------------------------------------------
Numerical solver
-------------------------------------------------------------------------------
-----------------------------------------------------------------
OSQP v1.0.0 - Operator Splitting QP Solver
(c) The OSQP Developer Team
-----------------------------------------------------------------
problem: variables n = 18, constraints m = 30
nnz(P) + nnz(A) = 76
settings: algebra = Built-in,
OSQPInt = 4 bytes, OSQPFloat = 8 bytes,
linear system solver = QDLDL v0.1.8,
eps_abs = 1.0e-05, eps_rel = 1.0e-05,
eps_prim_inf = 1.0e-04, eps_dual_inf = 1.0e-04,
rho = 1.00e-01 (adaptive: 50 iterations),
sigma = 1.00e-06, alpha = 1.60, max_iter = 10000
check_termination: on (interval 25, duality gap: on),
time_limit: 1.00e+10 sec,
scaling: on (10 iterations), scaled_termination: off
warm starting: on, polishing: on,
iter objective prim res dual res gap rel kkt rho time
1 -1.9836e+03 6.62e-03 9.22e+04 4.27e+03 9.22e+04 1.00e-01 1.36e-04s
75 -2.8271e+03 3.48e-06 1.22e+00 -1.08e-03 1.22e+00 1.00e-01 3.09e-04s
plsh -2.8271e+03 1.13e-16 4.45e-11 -8.53e-13 4.45e-11 -------- 6.00e-04s
status: solved
solution polishing: successful
number of iterations: 75
optimal objective: -2827.0522
dual objective: -2827.0522
duality gap: -8.5324e-13
primal-dual integral: 4.2741e+03
run time: 6.00e-04s
optimal rho estimate: 2.74e-02
-------------------------------------------------------------------------------
Summary
-------------------------------------------------------------------------------
2026-09-12 18:33:41,857 | INFO : Optimizer terminated. Time: 0.1201s
2026-09-12 18:33:41,868 | INFO : Post-processing started.
2026-09-12 18:33:41,871 | INFO : Post-processing: compute binning information.
2026-09-12 18:33:41,878 | INFO : Post-processing: compute performance metrics.
2026-09-12 18:33:41,916 | INFO : Post-processing terminated. Time: 0.0437s
2026-09-12 18:33:41,918 | INFO : Optimal piecewise binning terminated. Status: OPTIMAL. Time: 0.4008s
[43]:
OptimalPWBinning(estimator=LogisticRegression(), name='AverageMInFile',
solver='osqp', verbose=True)In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook. On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
Parameters
| name | 'AverageMInFile' | |
| estimator | LogisticRegression() | |
| solver | 'osqp' | |
| verbose | True | |
| objective | 'l2' | |
| degree | 1 | |
| continuous | True | |
| continuous_deriv | True | |
| prebinning_method | 'cart' | |
| max_n_prebins | 20 | |
| min_prebin_size | 0.05 | |
| min_n_bins | None | |
| max_n_bins | None | |
| min_bin_size | None | |
| max_bin_size | None | |
| monotonic_trend | 'auto' | |
| n_subsamples | None | |
| max_pvalue | None | |
| max_pvalue_policy | 'consecutive' | |
| outlier_detector | None | |
| outlier_params | None | |
| user_splits | None | |
| user_splits_fixed | None | |
| special_codes | None | |
| split_digits | None | |
| h_epsilon | 1.35 | |
| quantile | 0.5 | |
| regularization | None | |
| reg_l1 | 1.0 | |
| reg_l2 | 1.0 | |
| random_state | None |
LogisticRegression()