← All work(04) / (04) · Case study

Loan default prediction.

Credit models tested on three things at once: performance, explainability and fairness. A missed default costs the bank; an unfair rejection costs the customer.

ContextMSc Data Science dissertation · York St John University, London
StackPython · scikit-learn · XGBoost · LightGBM · SHAP · SciPy
DataPublic credit risk dataset · 32,581 loan applications
StatusComplete · six hypotheses tested, five accepted, one rejected
0.93ROC-AUC · LightGBM on the held-out 30% test set
32,581Loan applications modelled
0.189Income-group demographic parity gap · bias detected
5 / 6Hypotheses accepted · the one rejected was fairness
01 · The question

What the dissertation asked

Can a credit model be accurate, explain itself, and treat people fairly, or does chasing one of those quietly cost the others?

Four models were trained and compared, from a logistic regression baseline to gradient-boosted ensembles. Every prediction was then explained with SHAP at both global and individual level.

Rather than describing the results loosely, six research hypotheses were set up front and formally tested with statistical significance tests, including one about fairness that the model failed.

02 · Method

From raw data to a fairness verdict

  1. 01

    Clean

    Median and most-frequent imputation; outliers capped with Tukey’s fences at 3× IQR instead of dropped.

    Winsorisation
  2. 02

    Engineer

    Debt-to-income, income per employment year and default history. Age and income groups set aside before modelling.

    24 features
  3. 03

    Train four models

    Logistic regression, random forest, XGBoost and LightGBM, with class weights for the imbalance.

    Stratified 70 / 30
  4. 04

    Evaluate

    ROC-AUC as the primary metric, plus precision, recall and F1, and 5-fold cross-validation on the training set only.

    5-fold CV
  5. 05

    Explain

    SHAP global importance, dependence plots and waterfall explanations for individual default and non-default cases.

    SHAP
  6. 06

    Audit fairness

    Demographic parity, equalised odds and disparate impact across age and income groups.

    Post-hoc audit
03 · Hypotheses

Six claims, tested

  • H1

    Top SHAP features are significantly linked to default

    4 of the 5 top features were significant (point-biserial and chi-square tests).

    Accepted
  • H2

    Boosted models beat the logistic regression baseline

    LightGBM 0.932 and XGBoost 0.932 ROC-AUC against 0.881 for logistic regression.

    Accepted
  • H3

    Ensembles differ from each other

    XGBoost and LightGBM were comparable, and both outperformed random forest.

    Accepted
  • H4

    SHAP agrees with each model’s native importance

    SHAP rankings correlated significantly with native importance (Spearman), while adding per-prediction explanations.

    Accepted
  • H5

    The best model shows no significant demographic bias

    Age passed every fairness metric. Income failed: demographic parity gap 0.189 and disparate impact 0.38.

    Rejected
  • H6

    Feature engineering improves performance

    Accepted narrowly: only logistic regression improved significantly (+0.007 AUC). The tree models gained nothing measurable.

    Accepted
04 · Results

Four models, held-out test set

ModelROC-AUC5-fold CVPrecisionRecallF1
LightGBM · best0.9320.926 ± 0.0050.7760.7700.773
XGBoost0.9320.925 ± 0.0050.7930.7700.782
Random forest0.9160.911 ± 0.0050.7500.7460.748
Logistic regression0.8810.876 ± 0.0050.5580.7890.653

How to read this. 22,806 applications for training and 9,775 held out, split with the default rate preserved. Cross-validation ran on the training set only, so the test set was never touched while choosing models. Low spread across folds (± 0.005) means the ranking is stable.

Debt-to-income

The strongest driver, in every model

SHAP ranked debt-to-income first across all four models, ahead of interest rate, income and loan-to-income share.

+0.007

Feature engineering barely helped

Engineered features significantly improved only logistic regression. For the tree models the change was within noise, so the hypothesis passed on the narrowest possible terms, and I report it that way.

05 · Fairness audit

Accurate isn’t the same as fair

Age · under 30 vs 30 and overFair

Age passed every test

Demographic parity
0.015
Equalised odds
0.017
Disparate impact
0.93
Income · low vs highBias detected

Income failed all three

Demographic parity
0.189
Equalised odds
0.123
Disparate impact
0.38
What it meansThe audit ran on the XGBoost model, which matched LightGBM on ROC-AUC. Age and income groups were kept out of the model as protected attributes, yet the income gap remained. The model’s strongest driver, debt-to-income, is itself calculated from income, so income still shapes its decisions. Thresholds: parity and odds gaps above 0.05, or disparate impact outside 0.8–1.25, count as bias.
06 · Decisions

What I chose, and what I rejected

D1

Report the failed hypothesisover quietly dropping the fairness result

H5 was rejected, and it is the most useful finding in the project: a model can top the accuracy table and still fail a fairness audit.

D2

Set protected groups aside before modellingover leaving them in the feature set

Age and income groups were saved separately and dropped from the model, so they could be audited against without being predicted from.

D3

Cap outliers at 3× IQRover 1.5× IQR or deleting rows

Income, loan size and interest rate are naturally skewed and can hold legitimate extremes. Winsorising kept every application while limiting their pull.

D4

Test claims with statisticsover reading them off a chart

Each hypothesis was set before the results and decided by a named test: point-biserial, chi-square, Spearman and paired t-tests.

07 · Scope

What this does and doesn’t prove

It does show

  • Boosted models clearly beat a linear baseline on this data
  • Explanations that agree with each model’s own importance
  • A fairness audit that found a real income gap
  • Every claim tied to a stated hypothesis and test

It doesn’t show

  • Performance over time: the split is random, not out-of-time
  • A fix for the income bias: it was measured, not mitigated
  • Production behaviour: this is a research notebook, not a deployed system
  • Real lending outcomes from one lender: it uses a public Kaggle dataset