Predicting Credit Risk with Machine Learning: A Case Study Using German Credit Dataset
How a bank can reduce loan default losses by 88.9% using XGBoost classification
Introduction
Banks face a critical challenge every day: deciding who
deserves a loan and who doesn’t. Approving a loan to a
high-risk customer can result in significant financial loss,
while rejecting a good customer means lost revenue opportunity.
In this project, I built a machine learning model to predict
credit risk using the German Credit Dataset from UCI Machine
Learning Repository. The dataset contains 1,000 loan applicants
described by 20 attributes, classified as good or bad credit risks.
The goal is simple: help banks make smarter, data-driven loan
approval decisions — and quantify the business impact of doing so.
Business Problem
When a bank receives a loan application, two types of
errors can happen:
1. False Negative: Approving a loan to a bad customer
→ the bank loses money when the customer defaults
2. False Positive: Rejecting a good customer
→ the bank loses potential revenue
According to the UCI cost matrix for this dataset,
missing a bad customer is 5x more costly than
rejecting a good one. This means our model must
prioritize catching bad customers, even if it
means occasionally rejecting good ones.
Key business metrics we care about:
- Recall for bad credit class (catch as many bad customers as possible)
- Total cost score (weighted by misclassification cost)
- ROC-AUC (overall model discrimination ability)
Dataset & Research Process
Dataset
The German Credit Dataset was obtained from the UCI Machine
Learning Repository, originally prepared by Prof. Hans Hofmann
from Universität Hamburg. It contains 1,000 loan applicants
with 20 features including:
- Financial indicators: checking account status, savings,
credit amount, loan duration
- Personal attributes: age, employment duration, housing status
- Credit history: past payment behavior, number of existing credits
The target variable is credit_risk (0 = Good, 1 = Bad).
Research Process
Before building the model, I followed this workflow:
1. Understand the business problem and define success metrics
2. Explore the data to find patterns and anomalies
3. Choose appropriate models based on the problem type
4. Evaluate models using both ML metrics and business impact
This is a binary classification problem — predicting one of
two outcomes: good or bad credit risk.
For the benchmark, I used Logistic Regression as the baseline
model, since it is the standard starting point for binary
classification problems. XGBoost was chosen as the main model
based on its strong performance on tabular data and its ability
to handle imbalanced datasets through the scale_pos_weight parameter.
Class Imbalance
The dataset is imbalanced: 70% bad credit vs 30% good credit.
To handle this, I used:
- class_weight=’balanced’ for Logistic Regression
- scale_pos_weight parameter for XGBoost
I avoided SMOTE to prevent data leakage during cross-validation.
Data Preprocessing
Before feeding data into the model, I performed the
following preprocessing steps:
1. Feature and Target Separation
I separated the dataset into features (X) and
target variable (y = credit_risk).
2. Categorical Encoding
The dataset contains 13 categorical columns such as
credit_history, purpose, savings, and housing.
I used Label Encoding to convert these into numerical
values that the model can process.
3. Train-Test Split
I split the data into:
- 80% training set (800 samples)
- 20% test set (200 samples)
I used stratify=y to ensure both sets maintain the
same 70/30 class ratio. This is important to prevent
the model from being evaluated on an unrepresentative
test set.
Final split result:
- Train: 560 bad credit, 240 good credit
- Test: 140 bad credit, 60 good credit
Get Wildan Dzulfaqor’s stories in your inbox
Join Medium for free to get updates from this writer.
Key Decision: No data leakage
The encoding and splitting were done carefully to ensure
no information from the test set leaked into the training
process. The test set was only used for final evaluation,
never during training or tuning.
Modeling
I built and compared three models in this project:
1. Logistic Regression (Baseline)
The simplest classification model, used as a benchmark
to measure how much improvement more complex models provide.
I used class_weight=’balanced’ to handle class imbalance.
2. XGBoost (Main Model)
XGBoost (Extreme Gradient Boosting) is an ensemble method
that builds multiple decision trees sequentially, where each
tree corrects the errors of the previous one. It was chosen
because:
- Strong performance on tabular data
- Built-in handling for imbalanced datasets via scale_pos_weight
- Robust to outliers and missing values
3. XGBoost Tuned
I used RandomizedSearchCV with 5-fold cross validation
to find the best hyperparameters. The following parameters
were tuned:
- n_estimators: number of trees
- max_depth: maximum tree depth
- learning_rate: step size for each iteration
- subsample: fraction of samples per tree
- colsample_bytree: fraction of features per tree
Best parameters found:
- subsample: 0.7
- n_estimators: 100
- max_depth: 3
- learning_rate: 0.1
- colsample_bytree: 0.7
Model Comparison Results:
Final Model Selection: XGBoost (default)
Although XGBoost Tuned achieved higher bad credit recall (0.95),
it came at the cost of significantly lower good credit recall (0.25),
meaning too many good customers would be rejected. The default
XGBoost provides the best balance with ROC-AUC of 0.7896 and
bad credit recall of 0.89.
Business Impact
The ultimate goal of this project is not just to build
a model with good ML metrics — but to show real business
value. Here is how the XGBoost model performs from a
business perspective.
Cost Matrix
Based on the UCI German Credit Dataset documentation,
the cost of misclassification is:
- Missing a bad customer (False Negative) = cost of 5
- Rejecting a good customer (False Positive) = cost of 1
This reflects reality — a bank loses significantly more
money by approving a bad loan than by rejecting a good one.
Results on Test Set (200 applicants):
- True Negative (Good → Good) : 24 ✓
- False Positive (Good → Bad) : 36 (rejected good customers)
- False Negative (Bad → Good) : 15 (missed bad customers)
- True Positive (Bad → Bad) : 125 ✓
Business Cost Calculation:
- Model cost score : 111
- Baseline cost score : 1,000 (if bank approves everyone)
- Cost reduction : 88.9%
Key Business Findings:
- The model caught 89.3% of bad customers (125 out of 140)
- Only 15 bad customers slipped through undetected
- Average loan amount in this dataset: 3,271 DM
- Estimated money protected from default risk:
125 × 3,271 = 408,875 DM
Conclusion of Business Impact:
By deploying this model, a bank can reduce its loan default
cost by 88.9% compared to approving all applicants blindly.
This translates to significant financial savings while still
serving the majority of good customers.
Conclusion
In this project, I built a credit risk prediction model
using the German Credit Dataset to help banks make smarter
loan approval decisions.
Key Takeaways:
1. Problem Framing Matters
Framing this as a binary classification problem with a
cost-sensitive evaluation was the right approach. Simply
optimizing for accuracy would have missed the real
business objective.
2. Model Selection is Not Always About Best Metrics
The tuned XGBoost had higher recall (0.95) but performed
worse overall because it rejected too many good customers.
The default XGBoost was the better business decision.
3. Class Imbalance Must Be Handled
Without handling the 70/30 imbalance, the model would
simply predict everyone as bad credit and appear to have
high accuracy. Using scale_pos_weight was critical.
4. Business Impact is the Real Measure of Success
An 88.9% cost reduction and 89.3% bad customer detection
rate demonstrates that machine learning can deliver
measurable, real-world value in banking decisions.
Next Steps:
- Deploy the model as a web application using Streamlit
- Collect more recent data to retrain the model
- Explore more advanced techniques such as SHAP values
for model explainability
Thank you for reading! The full source code is available
on my GitHub: Credit Risk Prediction — German Credit Dataset







