A credit team needs to turn borrower characteristics into risk signals that officers can inspect. Weight of evidence (WoE) assigns each category or numeric bin a value based on its share of defaults versus its share of non-defaults. With “bad” defined as default, WoE(bin) = ln[(bads in bin / all bads) / (goods in bin / all goods)]. Positive values indicate greater relative default risk; negative values indicate lower relative risk; values near zero resemble the overall population. section 4.4.3
Information value (IV) measures how strongly the bins of a feature separate good and bad borrowers: IV = Σ[(bad share − good share) × WoE] across bins. It helps prioritize features, but a very large IV—such as above 0.5—warrants a check for leakage or overfitting. Review the WoE pattern across ordered bins as well as the aggregate IV; a strong total does not replace inspection of individual risk groups. section 4.4.3
Inspect age-group risk
Suppose borrowers are grouped into <20, 20–24, 25–29, 30–39, and 40+. Count good and bad borrowers in each group, divide by the respective totals, and calculate each group’s WoE and IV contribution. The 20–24 group has 30 bad and 120 good borrowers; the 40+ group has 5 bad and 280 good borrowers. Their WoE values make that contrast visible on a common scale. section 4.4.3
Listing 4.3 Understand WoE and IV with data
import numpy as np
import pandas as pd
data = {
'AgeGroup': ['<20', '20-24', '25-29', '30-39', '40+'],
'GoodCount': [90, 120, 160, 300, 280],
'BadCount': [10, 30, 40, 25, 5]
} #1
df_bins = pd.DataFrame(data)
total_good = df_bins['GoodCount'].sum()
total_bad = df_bins['BadCount'].sum() #2
df_bins['GoodRate'] = df_bins['GoodCount'] / total_good
df_bins['BadRate'] = df_bins['BadCount'] / total_bad #3
df_bins['WoE'] = np.log(
(df_bins['BadRate'] + 1e-9) / (df_bins['GoodRate'] + 1e-9)
) #4
df_bins['IV_bin'] = (
(df_bins['BadRate'] - df_bins['GoodRate']) * df_bins['WoE']
)
IV_age = df_bins['IV_bin'].sum() #5
print(df_bins[['AgeGroup','GoodCount','BadCount','WoE','IV_bin']])
print(f"Information Value (Age) = {IV_age:.4f}")
The pandas and NumPy example calculates group shares, WoE, per-bin IV, and total age IV. Its small constant prevents a zero share from making the logarithm undefined. section 4.4.3
For a manual credit-modeling pipeline:
- Clean the inputs first. Treat sentinel codes, missing values, and outliers deliberately; a dedicated missing bin may preserve a risk signal that imputation would hide. section 4.4.4
- Group categorical values and bin numeric features. One implementation uses ten quantile bins where possible, adds 0.5 to stabilize event and non-event proportions, and clips WoE to ±10. section 5.3.3
- Calculate IV, inspect bin-level results, and select features above a chosen threshold, such as 0.02. Retain the bin boundaries and WoE mappings so selected raw features can be converted to WoE-coded inputs. section 5.3.3 section 5.3.4
- Train and evaluate the downstream model. In the illustrated workflow, WoE-coded features feed a baseline XGBoost model; cross-validated AUC and confusion matrices are reviewed before probabilities are mapped to credit scores. section 5.0 section 5.2.3
Listing 5.13 Execute the “woe” function
def transform_to_woe(
df,
selected_features,
cat_features,
bin_edges_dict,
woe_dict,
target="target"
):
df_woe = df[selected_features + [target]].copy()
for feat in selected_features:
if feat in cat_features:
df_woe[feat] = df_woe[feat].map(woe_dict[feat])
else:
bins = bin_edges_dict[feat]
df_woe[feat] = pd.cut(
df_woe[feat],
bins=bins,
include_lowest=True
).astype(str)
df_woe[feat] = df_woe[feat].map(woe_dict[feat])
return df_woe
df_woe = transform_to_woe(
df,
selected_feats,
cat_features,
bin_edges,
woe_maps,
target=target_col
)
print("df_woe shape:", df_woe.shape)
df_woe.head(5)
The transformation function shows how stored category mappings and numeric bin edges replace selected raw columns with their WoE values. section 5.3.4
Document binning, missing-data decisions, and outlier treatment for audit and fairness review. WoE makes risk differences inspectable, but it does not resolve poor data quality or establish that a feature is safe to use. Automated binning and advanced drift checks are separate extensions to this manual approach. section 4.4.4 section 5.2.3
