Tutorial: Counterfactual explanations for scorecard with binary target
Counterfactual explanations is one of the post-hoc methods used to provide explainability to machine learning models. In this tutorial, we show how to generate optimal counterfactual explanations for scorecard models.
[1]:
import numpy as np
import pandas as pd
from sklearn.linear_model import LogisticRegression
from optbinning import BinningProcess
from optbinning import Scorecard
from optbinning.scorecard import Counterfactual
We load the adult income dataset from the UCI repository (https://archive.ics.uci.edu/ml/datasets/adult). For this example, we select 8 features. The target “income” is transformed to binary (0: low-income, <=50K) and (1: high-income, >50K).
[2]:
df = pd.read_csv("data/adult.data", sep=",", header=None)
columns = ["age", "workclass", "fnlwgt", "education", "education-num",
"marital-status", "occupation", "relationship", "race", "sex",
"capital-gain", "capital-loss", "hours-per-week","native-country",
"income"]
target = "income"
variable_names = ["age", "workclass", "education", "marital-status",
"occupation", "race", "sex", "hours-per-week"]
df.columns = columns
X = df[variable_names]
y = (df[target].values == ' >50K').astype(int)
Scorecard model
First, we require a scorecard model. As shown in previous tutorials, we need a binning process and an estimator.
[3]:
binning_process = BinningProcess(variable_names)
estimator = LogisticRegression(solver="lbfgs", class_weight="balanced")
scorecard = Scorecard(binning_process=binning_process,
estimator=estimator, scaling_method="min_max",
scaling_method_params={"min": 300, "max": 850})
scorecard.fit(X, y)
[3]:
Scorecard(binning_process=BinningProcess(variable_names=['age', 'workclass',
'education',
'marital-status',
'occupation', 'race',
'sex',
'hours-per-week']),
estimator=LogisticRegression(class_weight='balanced'),
scaling_method='min_max',
scaling_method_params={'max': 850, 'min': 300})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
| binning_process | BinningProces...rs-per-week']) | |
| estimator | LogisticRegre...ht='balanced') | |
| scaling_method | 'min_max' | |
| scaling_method_params | {'max': 850, 'min': 300} | |
| intercept_based | False | |
| reverse_scorecard | False | |
| rounding | False | |
| verbose | False |
Fitted attributes
| Name | Type | Value |
|---|---|---|
| binning_process_ | BinningProcess | BinningProces...rs-per-week']) |
| estimator_ | LogisticRegression | LogisticRegre...ht='balanced') |
| intercept_ | int | 0 |
BinningProcess(variable_names=['age', 'workclass', 'education',
'marital-status', 'occupation', 'race', 'sex',
'hours-per-week'])Parameters
| variable_names | ['age', 'workclass', ...] | |
| 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 | |
| max_pvalue | None | |
| max_pvalue_policy | 'consecutive' | |
| selection_criteria | None | |
| fixed_variables | None | |
| categorical_variables | None | |
| special_codes | None | |
| split_digits | None | |
| binning_fit_params | None | |
| binning_transform_params | None | |
| n_jobs | None | |
| verbose | False |
LogisticRegression(class_weight='balanced')
Parameters
Generating counterfactual explanations - binary outcome
Having fitted the scorecard model, we can start generating counterfactual explanations.
Single counterfactual
As an input data point or query, we choose the sample with the lowest probability of having a high income.
[4]:
idx_lowest = np.argmin(scorecard.predict_proba(df)[:, 1])
query = X.iloc[idx_lowest, :].to_dict()
[5]:
query
[5]:
{'age': 17,
'workclass': ' Private',
'education': ' 11th',
'marital-status': ' Never-married',
'occupation': ' Other-service',
'race': ' Black',
'sex': ' Female',
'hours-per-week': 25}
A counterfactual class can simply be instantiated with a scorecard model. The first step is to fit the counterfactual with the data used to develop the scorecard. This allows computing problem data required for the integer programming formulation. This method must be called once before generating counterfactual explanations.
[6]:
cf = Counterfactual(scorecard=scorecard)
[7]:
cf.fit(X)
[7]:
Counterfactual(scorecard=Scorecard(binning_process=BinningProcess(variable_names=['age',
'workclass',
'education',
'marital-status',
'occupation',
'race',
'sex',
'hours-per-week']),
estimator=LogisticRegression(class_weight='balanced'),
scaling_method='min_max',
scaling_method_params={'max': 850,
'min': 300}))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
| scorecard | Scorecard(bin..., 'min': 300}) | |
| special_missing | False | |
| n_jobs | 1 | |
| verbose | False |
Scorecard(binning_process=BinningProcess(variable_names=['age', 'workclass',
'education',
'marital-status',
'occupation', 'race',
'sex',
'hours-per-week']),
estimator=LogisticRegression(class_weight='balanced'),
scaling_method='min_max',
scaling_method_params={'max': 850, 'min': 300})BinningProcess(variable_names=['age', 'workclass', 'education',
'marital-status', 'occupation', 'race', 'sex',
'hours-per-week'])Parameters
| variable_names | ['age', 'workclass', ...] | |
| 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 | |
| max_pvalue | None | |
| max_pvalue_policy | 'consecutive' | |
| selection_criteria | None | |
| fixed_variables | None | |
| categorical_variables | None | |
| special_codes | None | |
| split_digits | None | |
| binning_fit_params | None | |
| binning_transform_params | None | |
| n_jobs | None | |
| verbose | False |
LogisticRegression(class_weight='balanced')
Parameters
The minimum parameters required to generate counterfactuals are a query, the desired target outcome y, the outcome type, and the number of counterfactuals n_cf. Other parameters such as the maximum number of changes max_changes can be specified.
[8]:
cf.generate(query=query, y=1, outcome_type="binary", n_cf=1, max_changes=3)
[8]:
Counterfactual(scorecard=Scorecard(binning_process=BinningProcess(variable_names=['age',
'workclass',
'education',
'marital-status',
'occupation',
'race',
'sex',
'hours-per-week']),
estimator=LogisticRegression(class_weight='balanced'),
scaling_method='min_max',
scaling_method_params={'max': 850,
'min': 300}))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
| scorecard | Scorecard(bin..., 'min': 300}) | |
| special_missing | False | |
| n_jobs | 1 | |
| verbose | False |
Scorecard(binning_process=BinningProcess(variable_names=['age', 'workclass',
'education',
'marital-status',
'occupation', 'race',
'sex',
'hours-per-week']),
estimator=LogisticRegression(class_weight='balanced'),
scaling_method='min_max',
scaling_method_params={'max': 850, 'min': 300})BinningProcess(variable_names=['age', 'workclass', 'education',
'marital-status', 'occupation', 'race', 'sex',
'hours-per-week'])Parameters
| variable_names | ['age', 'workclass', ...] | |
| 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 | |
| max_pvalue | None | |
| max_pvalue_policy | 'consecutive' | |
| selection_criteria | None | |
| fixed_variables | None | |
| categorical_variables | None | |
| special_codes | None | |
| split_digits | None | |
| binning_fit_params | None | |
| binning_transform_params | None | |
| n_jobs | None | |
| verbose | False |
LogisticRegression(class_weight='balanced')
Parameters
The property status retrieves the status of the optimization solver. Additional information is retrieved by using the information method, showing three sections:
Solver statistics: number of variables and constraints, and objective value.
Objectives: value of the objectives functions, by default proximity and closeness.
Timing: time statistics.
[9]:
cf.status
[9]:
'OPTIMAL'
[10]:
cf.information(print_level=2)
optbinning (Version 1.0.0)
Copyright (c) 2019-2026 Guillermo Navas-Palencia, Apache License 2.0
Begin options
scorecard yes * U
special_missing False * d
n_jobs 1 * d
verbose False * d
End options
Status : OPTIMAL
Solver statistics
Type mip
Number of variables 42
Number of constraints 95
Objective value 16.1504
Best objective bound 16.1504
Objectives
proximity 3.7572
closeness 12.3932
Timing
Total time 0.34 sec
Fit 0.24 sec ( 70.70%)
Solver 0.09 sec ( 27.17%)
Post-processing 0.01 sec ( 7.86%)
The resulting optimal counterfactual explanation can be shown using display method. By default, all feature values are displayed. We might prefer to only observe the changes from the query, then set parameter show_only_changes to True. In addition, to add the counterfactual outcome, set parameter show_outcome to True.
[11]:
cf.display()
[11]:
| age | workclass | education | marital-status | occupation | race | sex | hours-per-week | |
|---|---|---|---|---|---|---|---|---|
| 0 | [33.50, 35.50) | Private | [ Masters, Prof-school, Doctorate] | [ Married-AF-spouse, Married-civ-spouse] | Other-service | Black | Female | 25 |
[12]:
cf.display(show_only_changes=True)
[12]:
| age | workclass | education | marital-status | occupation | race | sex | hours-per-week | |
|---|---|---|---|---|---|---|---|---|
| 0 | [33.50, 35.50) | - | [ Masters, Prof-school, Doctorate] | [ Married-AF-spouse, Married-civ-spouse] | - | - | - | - |
[13]:
cf.display(show_only_changes=True, show_outcome=True)
[13]:
| age | workclass | education | marital-status | occupation | race | sex | hours-per-week | outcome | |
|---|---|---|---|---|---|---|---|---|---|
| 0 | [33.50, 35.50) | - | [ Masters, Prof-school, Doctorate] | [ Married-AF-spouse, Married-civ-spouse] | - | - | - | - | 0.505201 |
Actionable features
Note that, in general, a scorecard includes features that are not mutable or that should not be actionable. For example, we might exclude marital status by choosing only those actionable features using the parameter actionable_features.
[14]:
cf.generate(query=query, y=1, outcome_type="binary", n_cf=1, max_changes=4,
actionable_features=["age", "workclass", "education", "occupation",
"hours-per-week"]
).display(show_only_changes=True, show_outcome=True)
[14]:
| age | workclass | education | marital-status | occupation | race | sex | hours-per-week | outcome | |
|---|---|---|---|---|---|---|---|---|---|
| 0 | [43.50, 49.50) | - | [ Masters, Prof-school, Doctorate] | - | [ Tech-support, Protective-serv, Prof-specia... | - | - | [39.50, 41.50) | 0.502521 |
Weighted vs hierarchical method
Currently, there are two methods (weighted and hierarchical) supported to handle multiple objectives functions. By default, method is set to “weighted”, due to having an inferior computational cost.
[15]:
cf.generate(query=query, y=1, outcome_type="binary", n_cf=1,
method="weighted", max_changes=4).display()
[15]:
| age | workclass | education | marital-status | occupation | race | sex | hours-per-week | |
|---|---|---|---|---|---|---|---|---|
| 0 | [37.50, 40.50) | Private | [ Bachelors] | [ Married-AF-spouse, Married-civ-spouse] | [ ?, Armed-Forces, Farming-fishing] | Black | Female | 25 |
[16]:
cf.information()
optbinning (Version 1.0.0)
Copyright (c) 2019-2026 Guillermo Navas-Palencia, Apache License 2.0
Status : OPTIMAL
Solver statistics
Type mip
Number of variables 42
Number of constraints 95
Objective value 14.0048
Best objective bound 14.0048
Objectives
proximity 4.1658
closeness 9.8391
Timing
Total time 0.77 sec
Fit 0.24 sec ( 31.31%)
Solver 0.51 sec ( 66.05%)
Post-processing 0.02 sec ( 4.00%)
When generating counterfactuals the objectives are passed as a dictionary. If method="weighted", the dictionary values represent the assigned weight to each objective, whereas if method="hierarchical", the dictionary values represent the priority of each objective. In the following example, we assign a higher weight to closeness, reducing the objective value compared to the previous one, where both weights are set to 1.
[17]:
cf.generate(query=query, y=1, outcome_type="binary", n_cf=1,
method="weighted", objectives={"proximity": 0.1, "closeness": 0.9},
max_changes=4
).display()
[17]:
| age | workclass | education | marital-status | occupation | race | sex | hours-per-week | |
|---|---|---|---|---|---|---|---|---|
| 0 | [43.50, 49.50) | Private | [ Masters, Prof-school, Doctorate] | Never-married | [ Tech-support, Protective-serv, Prof-specia... | Black | Female | [39.50, 41.50) |
[18]:
cf.information()
optbinning (Version 1.0.0)
Copyright (c) 2019-2026 Guillermo Navas-Palencia, Apache License 2.0
Status : OPTIMAL
Solver statistics
Type mip
Number of variables 42
Number of constraints 95
Objective value 9.0396
Best objective bound 9.0396
Objectives
proximity 5.5887
closeness 9.4231
Timing
Total time 0.75 sec
Fit 0.24 sec ( 32.03%)
Solver 0.50 sec ( 66.49%)
Post-processing 0.01 sec ( 2.23%)
Considering the hierarchical method, the highest priority is assigned to proximity, therefore, we get the minimum objective value with a small degradation ("priority_tol"=0.1).
[19]:
cf.generate(query=query, y=1, outcome_type="binary", n_cf=1,
method="hierarchical", max_changes=4
).display()
[19]:
| age | workclass | education | marital-status | occupation | race | sex | hours-per-week | |
|---|---|---|---|---|---|---|---|---|
| 0 | [35.50, 37.50) | Private | [ Masters, Prof-school, Doctorate] | [ Married-AF-spouse, Married-civ-spouse] | Other-service | Black | Female | [25.50, 34.50) |
[20]:
cf.information()
optbinning (Version 1.0.0)
Copyright (c) 2019-2026 Guillermo Navas-Palencia, Apache License 2.0
Status : OPTIMAL
Solver statistics
Type mip
Number of variables 44
Number of constraints 97
Objective value 12.0849
Best objective bound 12.0849
Objectives
proximity 3.9661
closeness 12.0849
Timing
Total time 0.46 sec
Fit 0.24 sec ( 51.90%)
Solver 0.21 sec ( 45.52%)
Post-processing 0.01 sec ( 5.66%)
On the contrary, if we switch priorities and reduce the priority degradation, we obtain the same closeness objective value as the one generated with the weight "closeness": 0.9.
[21]:
cf.generate(query=query, y=1, outcome_type="binary", n_cf=1,
method="hierarchical", objectives={"proximity": 1, "closeness": 2},
priority_tol=0.001,
max_changes=4
).display()
[21]:
| age | workclass | education | marital-status | occupation | race | sex | hours-per-week | |
|---|---|---|---|---|---|---|---|---|
| 0 | [43.50, 49.50) | Private | [ Masters, Prof-school, Doctorate] | Never-married | [ Tech-support, Protective-serv, Prof-specia... | Black | Female | [39.50, 41.50) |
[22]:
cf.information()
optbinning (Version 1.0.0)
Copyright (c) 2019-2026 Guillermo Navas-Palencia, Apache License 2.0
Status : OPTIMAL
Solver statistics
Type mip
Number of variables 44
Number of constraints 97
Objective value 5.5887
Best objective bound 5.5887
Objectives
closeness 9.4231
proximity 5.5887
Timing
Total time 2.49 sec
Fit 0.24 sec ( 9.64%)
Solver 2.24 sec ( 90.12%)
Post-processing 0.01 sec ( 0.26%)
The hierarchical method becomes more interesting when several objectives come into play, as shown going forward.
Multiple counterfactuals
Now, let’s focus on the generation of multiple counterfactual explanations for a single input data point. The present implementation is able to generate n_cf counterfactuals simultaneously while imposing diversity constraints.
For example, we generate three counterfactuals such that a maximum of four features can be changed and enforcing (hard constraint) a diversity of features. The diversity of features to change guarantees different combinations of changed features for each counterfactual.
[23]:
cf.generate(query=query, y=1, outcome_type="binary", n_cf=3, max_changes=4,
hard_constraints=["diversity_features"], time_limit=10
).display(show_only_changes=True, show_outcome=True)
[23]:
| age | workclass | education | marital-status | occupation | race | sex | hours-per-week | outcome | |
|---|---|---|---|---|---|---|---|---|---|
| 0 | [40.50, 43.50) | - | [ Masters, Prof-school, Doctorate] | - | [ Exec-managerial] | - | - | [55.50, inf) | 0.633269 |
| 0 | [49.50, 54.50) | - | [ Masters, Prof-school, Doctorate] | [ Married-AF-spouse, Married-civ-spouse] | - | - | - | [55.50, inf) | 0.845609 |
| 0 | [49.50, 54.50) | - | - | [ Married-AF-spouse, Married-civ-spouse] | [ Exec-managerial] | - | - | [55.50, inf) | 0.591394 |
The generated counterfactuals change the following features:
age | education | occupation | hours-per-week
age | education | marital-status | hours-per-week
age | education | marital-status | occupation
[24]:
cf.information()
optbinning (Version 1.0.0)
Copyright (c) 2019-2026 Guillermo Navas-Palencia, Apache License 2.0
Status : FEASIBLE
Solver statistics
Type mip
Number of variables 1041
Number of constraints 450
Objective value 54.9108
Best objective bound 40.3651
Objectives
proximity 18.4831
closeness 36.4277
Timing
Total time 10.62 sec
Fit 0.24 sec ( 2.26%)
Solver 10.35 sec ( 97.47%)
Post-processing 0.03 sec ( 0.28%)
The reported status is only feasible, not optimal. When generating multiple counterfactual adding several constraints, the time required to prove optimality might be in the order of 10 - 120s, so one needs to increase time_limit. However, good feasible solutions are generally achieved within a few seconds. Adding extra 10 seconds to time_limit, the proximity and closeness objective values are reduced by 0.4% and 0.8%, respectively.
[25]:
cf.generate(query=query, y=1, outcome_type="binary", n_cf=3, max_changes=4,
hard_constraints=["diversity_features"], time_limit=20
).display(show_only_changes=True, show_outcome=True)
[25]:
| age | workclass | education | marital-status | occupation | race | sex | hours-per-week | outcome | |
|---|---|---|---|---|---|---|---|---|---|
| 0 | [40.50, 43.50) | - | [ Bachelors] | [ Married-AF-spouse, Married-civ-spouse] | - | - | - | [34.50, 39.50) | 0.536857 |
| 0 | [43.50, 49.50) | - | [ Masters, Prof-school, Doctorate] | - | [ Tech-support, Protective-serv, Prof-specia... | - | - | [39.50, 41.50) | 0.502521 |
| 0 | [37.50, 40.50) | - | [ Bachelors] | [ Married-AF-spouse, Married-civ-spouse] | [ ?, Armed-Forces, Farming-fishing] | - | - | - | 0.507865 |
[26]:
cf.information()
optbinning (Version 1.0.0)
Copyright (c) 2019-2026 Guillermo Navas-Palencia, Apache License 2.0
Status : FEASIBLE
Solver statistics
Type mip
Number of variables 1041
Number of constraints 450
Objective value 43.7095
Best objective bound 40.3651
Objectives
proximity 14.0732
closeness 29.6363
Timing
Total time 20.40 sec
Fit 0.24 sec ( 1.18%)
Solver 20.14 sec ( 98.71%)
Post-processing 0.02 sec ( 0.12%)
The previous conterfactuals, although showing diversity regarding features to change, some feature values appear several times. To guarantee unique combinations of feature changes and feature values, we add the hard constraint “diversity_values”.
[28]:
cf.generate(query=query, y=1, outcome_type="binary", n_cf=3, max_changes=4,
hard_constraints=["diversity_features", "diversity_values"], time_limit=30
).display(show_only_changes=True, show_outcome=True)
[28]:
| age | workclass | education | marital-status | occupation | race | sex | hours-per-week | outcome | |
|---|---|---|---|---|---|---|---|---|---|
| 0 | [40.50, 43.50) | - | [ Assoc-acdm, Assoc-voc] | [ Married-AF-spouse, Married-civ-spouse] | - | - | - | [41.50, 49.50) | 0.528592 |
| 0 | [49.50, 54.50) | - | [ Masters, Prof-school, Doctorate] | [ Married-spouse-absent, Widowed, Divorced] | [ Tech-support, Protective-serv, Prof-specia... | - | - | - | 0.509401 |
| 0 | [43.50, 49.50) | - | [ Bachelors] | - | [ Exec-managerial] | - | - | [49.50, 55.50) | 0.517386 |
[29]:
cf.information()
optbinning (Version 1.0.0)
Copyright (c) 2019-2026 Guillermo Navas-Palencia, Apache License 2.0
Status : OPTIMAL
Solver statistics
Type mip
Number of variables 1065
Number of constraints 450
Objective value 47.7114
Best objective bound 47.7114
Objectives
proximity 16.0561
closeness 31.6553
Timing
Total time 26.16 sec
Fit 0.24 sec ( 0.92%)
Solver 25.89 sec ( 99.00%)
Post-processing 0.02 sec ( 0.09%)
Adding this extra constraint, the objective values slightly increase.
Generating counterfactual explanations - probability outcome
In some situations, having an outcome = 1 with a probability outcome > 0.5 might not be sufficient. For example, an algorithm might require a probability of 0.7 for acceptance.
[30]:
df_query = pd.DataFrame([query],columns=query.keys())
[31]:
scorecard.predict_proba(df_query)
[31]:
array([[9.99831847e-01, 1.68152834e-04]])
Initially, the probability of the query is close to 0. When outcome_type="probability", at least an additional constraint must be provided. The supported constraints are:
soft constraints: “diff_outcome”.
hard constraints: “min_outcome” and “max_outcome”.
In this first try, we aim to obtain a counterfactual with an outcome probability as close as possible to 0.7. The weighted approach is used, assigning unit weights to all objectives.
[32]:
cf.generate(query=df_query, y=0.7, outcome_type="probability", n_cf=2, max_changes=4,
hard_constraints=["diversity_features"],
soft_constraints={"diff_outcome": 1},
).display(show_only_changes=True, show_outcome=True)
[32]:
| age | workclass | education | marital-status | occupation | race | sex | hours-per-week | outcome | |
|---|---|---|---|---|---|---|---|---|---|
| 0 | [23.50, 25.50) | - | [ HS-grad] | - | [ ?, Armed-Forces, Farming-fishing] | [ Other, Amer-Indian-Eskimo, Black] | - | - | 0.007156 |
| 0 | [23.50, 25.50) | - | [ HS-grad] | - | [ ?, Armed-Forces, Farming-fishing] | - | - | - | 0.007156 |
A weight=1 is not large enough given the rest of the objective values. A workaround might be to increase the corresponding weight.
[33]:
cf.generate(query=df_query, y=0.7, outcome_type="probability", n_cf=2, max_changes=4,
hard_constraints=["diversity_features"],
soft_constraints={"diff_outcome": 100}
).display(show_only_changes=True, show_outcome=True)
[33]:
| age | workclass | education | marital-status | occupation | race | sex | hours-per-week | outcome | |
|---|---|---|---|---|---|---|---|---|---|
| 0 | [43.50, 49.50) | - | [ Masters, Prof-school, Doctorate] | [ Married-AF-spouse, Married-civ-spouse] | [ ?, Armed-Forces, Farming-fishing] | - | - | - | 0.701769 |
| 0 | [40.50, 43.50) | - | [ Masters, Prof-school, Doctorate] | [ Married-AF-spouse, Married-civ-spouse] | - | - | - | [34.50, 39.50) | 0.696745 |
[34]:
cf.information()
optbinning (Version 1.0.0)
Copyright (c) 2019-2026 Guillermo Navas-Palencia, Apache License 2.0
Status : OPTIMAL
Solver statistics
Type mip
Number of variables 521
Number of constraints 311
Objective value 32.6064
Best objective bound 32.6064
Objectives
proximity 9.1411
closeness 22.6536
diff_outcome 0.0081
Timing
Total time 4.10 sec
Fit 0.24 sec ( 5.85%)
Solver 3.83 sec ( 93.31%)
Post-processing 0.03 sec ( 0.90%)
We might, however, want to guarantee a minimum probability outcome of 0.7. To do so, we add the hard constraint “min_outcome”.
[35]:
cf.generate(query=df_query, y=0.7, outcome_type="probability", n_cf=2, max_changes=4,
hard_constraints=["diversity_features", "min_outcome"]
).display(show_only_changes=True, show_outcome=True)
[35]:
| age | workclass | education | marital-status | occupation | race | sex | hours-per-week | outcome | |
|---|---|---|---|---|---|---|---|---|---|
| 0 | [49.50, 54.50) | - | [ Masters, Prof-school, Doctorate] | [ Married-AF-spouse, Married-civ-spouse] | [ ?, Armed-Forces, Farming-fishing] | - | - | - | 0.714410 |
| 0 | [49.50, 54.50) | - | [ Masters, Prof-school, Doctorate] | [ Married-AF-spouse, Married-civ-spouse] | - | - | - | [34.50, 39.50) | 0.732671 |
[36]:
cf.information()
optbinning (Version 1.0.0)
Copyright (c) 2019-2026 Guillermo Navas-Palencia, Apache License 2.0
Status : OPTIMAL
Solver statistics
Type mip
Number of variables 521
Number of constraints 311
Objective value 31.7704
Best objective bound 31.7704
Objectives
proximity 9.2375
closeness 22.5330
Timing
Total time 2.24 sec
Fit 0.24 sec ( 10.73%)
Solver 1.98 sec ( 88.68%)
Post-processing 0.01 sec ( 0.66%)
Moreover, it is possible to combine the previous constraints with diversity constraints.
[37]:
cf.generate(query=df_query, y=0.7, outcome_type="probability", n_cf=2,
max_changes=4, method="hierarchical",
objectives={"proximity": 2, "closeness": 1},
hard_constraints=["min_outcome"],
soft_constraints={"diversity_features": 2, "diversity_values": 1}
).display(show_only_changes=True, show_outcome=True)
[37]:
| age | workclass | education | marital-status | occupation | race | sex | hours-per-week | outcome | |
|---|---|---|---|---|---|---|---|---|---|
| 0 | [43.50, 49.50) | - | [ Masters, Prof-school, Doctorate] | [ Married-AF-spouse, Married-civ-spouse] | [ Adm-clerical] | - | - | - | 0.727434 |
| 0 | [49.50, 54.50) | - | [ Bachelors] | [ Married-AF-spouse, Married-civ-spouse] | - | - | - | [41.50, 49.50) | 0.706417 |
[38]:
cf.information()
optbinning (Version 1.0.0)
Copyright (c) 2019-2026 Guillermo Navas-Palencia, Apache License 2.0
Status : OPTIMAL
Solver statistics
Type mip
Number of variables 526
Number of constraints 317
Objective value -6.0000
Best objective bound -6.0000
Objectives
proximity 9.8458
diversity_features 2.0000
closeness 22.6946
diversity_values 6.0000
Timing
Total time 4.14 sec
Fit 0.24 sec ( 5.81%)
Solver 3.88 sec ( 93.79%)
Post-processing 0.02 sec ( 0.44%)
Special and missing bins
The scorecard models might have bins for special and missing values. The parameter special_missing permits including these bins as feasible solutions for the generation of counterfactual explanations.
[39]:
cf = Counterfactual(scorecard=scorecard, special_missing=True)
[40]:
cf.fit(X)
[40]:
Counterfactual(scorecard=Scorecard(binning_process=BinningProcess(variable_names=['age',
'workclass',
'education',
'marital-status',
'occupation',
'race',
'sex',
'hours-per-week']),
estimator=LogisticRegression(class_weight='balanced'),
scaling_method='min_max',
scaling_method_params={'max': 850,
'min': 300}),
special_missing=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
| scorecard | Scorecard(bin..., 'min': 300}) | |
| special_missing | True | |
| n_jobs | 1 | |
| verbose | False |
Scorecard(binning_process=BinningProcess(variable_names=['age', 'workclass',
'education',
'marital-status',
'occupation', 'race',
'sex',
'hours-per-week']),
estimator=LogisticRegression(class_weight='balanced'),
scaling_method='min_max',
scaling_method_params={'max': 850, 'min': 300})BinningProcess(variable_names=['age', 'workclass', 'education',
'marital-status', 'occupation', 'race', 'sex',
'hours-per-week'])Parameters
| variable_names | ['age', 'workclass', ...] | |
| 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 | |
| max_pvalue | None | |
| max_pvalue_policy | 'consecutive' | |
| selection_criteria | None | |
| fixed_variables | None | |
| categorical_variables | None | |
| special_codes | None | |
| split_digits | None | |
| binning_fit_params | None | |
| binning_transform_params | None | |
| n_jobs | None | |
| verbose | False |
LogisticRegression(class_weight='balanced')
Parameters
[41]:
cf.generate(query=query, y=1, outcome_type="binary", n_cf=1, max_changes=4
).display(show_only_changes=True)
[41]:
| age | workclass | education | marital-status | occupation | race | sex | hours-per-week | |
|---|---|---|---|---|---|---|---|---|
| 0 | [49.50, 54.50) | - | [ Assoc-acdm, Assoc-voc] | [ Married-AF-spouse, Married-civ-spouse] | - | - | - | Special |