A two-stage machine learning pipeline that predicts early default and realised return for LendingClub personal loans, built for a Kaggle in-class competition. The dataset covers 1M+ loans originated between 2008 and 2017, with loan terms (interest rate, amount, grade), borrower characteristics (FICO score, income, debt-to-income) and loan outcomes.
The core idea: a loan's return depends mostly on whether it defaults. So the pipeline first estimates each loan's probability of early default, then feeds that probability into a return regression model as an input feature.
Tech stack: Python · pandas · NumPy · scikit-learn · XGBoost · SciPy · Matplotlib · Jupyter
Raw loan data (933k train / 113k test)
│
├─ Feature engineering: ratios, log transforms, FICO interactions, decile bins,
│ delinquency missing-value indicators
├─ Preprocessing: median / mode imputation, scaling, one-hot encoding
│
├─ STAGE 1 · Early-default classifier
│ Logistic Regression · XGBoost · Calibrated Linear SVM (optional RBF SVM)
│ → best model by validation ROC-AUC, retrained on the full training period
│ → outputs p_default and margin (standardised log-odds)
│
└─ STAGE 2 · Return regressor
inputs: loan features + p_default + margin
XGBoost (RandomizedSearchCV), optionally blended with Ridge / ElasticNet
→ predicted return for every test loan → Kaggle submission (scored on R²)
- Time-aware validation. Loans issued before 2015 are used for training and 2015 onwards for validation, so every model is evaluated on loans issued after the ones it learned from.
- Out-of-fold default probabilities. The
p_defaultfeature for training rows is produced with 3-fold cross-validation, so each loan is scored by a classifier that never saw it. This prevents the regression from learning to trust over-confident in-sample probabilities that won't hold on new loans. - Class imbalance. Only ~5% of loans default early. This is handled with balanced class weights /
scale_pos_weight, and the decision threshold is tuned to maximise F1 instead of using 0.5. - Consistent train/test transformations. Bin edges, clipping thresholds and margin standardisation are fit on training data only and reused for the test set.
- Ensemble only when it helps. XGBoost is blended with linear models only if this improves validation R² by a meaningful margin.
| Stage | Model selected | Validation metric |
|---|---|---|
| Early-default classification | TBD | ROC-AUC: TBD · F1: TBD |
| Return regression | TBD | R²: TBD · RMSE: TBD |
Kaggle leaderboard R²: TBD (competition benchmark R² = 0)
The data comes from the Kaggle competition and is not included in this repository. Download lc_loan.csv and lc_loan_test.csv from the competition page and put them in a data/ folder, or point the DATA_DIR environment variable at their location.
git clone https://github.com/<survival233>/lendingclub-default-return-prediction.git
cd lendingclub-default-return-prediction
pip install -r requirements.txt
# put lc_loan.csv and lc_loan_test.csv in data/ (or set DATA_DIR), then:
jupyter notebook loan_default_return.ipynbThe notebook writes the Kaggle submission to outputs/submission_return.csv and the summary plot to figures/. The optional RBF-kernel SVM is switched off by default (RUN_RBF_SVM = False) because it is very slow on this data size.
├── loan_default_return.ipynb # Full pipeline
├── figures/ # Plots used in this README
├── requirements.txt
└── README.md
