| Metric | Value | Signal |
|---|---|---|
| Total Portfolio | ₦348.1 Million | — |
| Total Borrowers | 305 | — |
| Performing Loans | 157 (51.5%) | ✅ Healthy | |
| Non-Performing Loans | 148 (48.5%) | ⚠️ Elevated |
| PAR Rate (₦ basis) | 41.3% | 🔴 Critical | |
| NPL Rate (borrowers) | 35.7% | 🔴 Critical | |
| Lost Loans | 80 borrowers | 🔴 Urgent | |
| CBN NPL Benchmark | ≤ 5.0% | Regulatory benchmark |
NON PERFORMING LOAN ANALYSIS — AWACASH MICROFINANCE BANK
Executive Summary
Central Business Question: Which borrowers are likely to default, what characteristics predict that, and what should the bank do about it?
AWACASH Microfinance Bank has disbursed loans to 305 customers across five economic sectors and five product lines. As at May 2026, ₦143.9 million — 41.3% of the ₦348.1 million total portfolio — is classified as non-performing. The NPL rate of 35.7% is more than seven times the CBN’s 5% supervisory threshold, posing a material threat to institutional capital adequacy.
This document applies five quantitative techniques — Classification, Explainability, Clustering, Dimensionality Reduction, and Time Series — to answer that question and generate actionable credit risk management recommendations.
1 Professional Disclosure
As Managing Director I chair the credit committee, approve loan applications, and am directly accountable to the CBN for the Bank’s NPL ratio. Each technique maps directly to a real operational decision:
- Classification — enables objective pre-disbursement risk scoring to replace officer intuition
- Explainability (SHAP) — meets CBN requirement for documented, auditable credit decisions
- Clustering — supports differentiated credit policies for a portfolio spanning ₦20K to ₦20M
- PCA — delivers a board-level visual of portfolio risk requiring no technical background
- Time Series — fulfils CBN monthly monitoring and IFRS 9 ECL provisioning obligations
2 Data Collection & Sampling
2.1 Source & Method
The dataset is a Portfolio At Risk (PAR) report extracted from the Bank’s core banking management information system (MIS). It is a system-generated Excel export covering all loan accounts as at May 2026.
2.2 Sampling Frame
Census sampling was used — all 305 accounts (active, closed, and NPL) were included. This avoids survivorship bias that would arise from excluding resolved NPL accounts. Natural stratification exists across five product types, five sectors, and five loan status classifications. The single-branch scope (Head Office) means findings are internally valid but not generalisable to other branches without additional sampling.
2.3 Ethical Statement & Consent
Data Governance: This dataset was extracted by the analyst in their capacity as Managing Director under the Bank’s internal data governance policy. All customer names have been anonymised (Customer 1–305). No PII was transmitted outside the Bank’s secure systems or to AI tools. This analysis complies with the Nigeria Data Protection Act (NDPA) 2023 and CBN credit risk management guidelines. The dataset is proprietary to AWACASH Microfinance Bank Limited and is used solely for academic purposes under Case Study 2.
3 Data Description & EDA
3.1 Variable Dictionary
| Variable | Type | Key Statistics |
|---|---|---|
| Customer Name | Nominal | 305 unique identifiers — all anonymised |
| Gender | Nominal | Female 194 (64%), Male 72 (24%), Missing 39 (13%) |
| Economic Sector | Nominal (5 cats) | Trade & Commerce 244 (80%), Consumer 33, Transport 12, General 12, Rent 4 |
| Product | Nominal (5 cats) | AWASBF LOAN 209 (68%), AWAQUICK 73 (24%), AWASTAFF/AWASMEF/AWASALARY 23 |
| Status | Ordinal (5 levels) | Performing 157 | Pass & Watch 39 | Sub-Standard 15 | Doubtful 14 | Lost 80 |
| Loan Amount (₦) | Continuous | Min ₦20K | Max ₦20M | Mean ₦1.14M | Median ₦300K | Right-skewed |
| Days OverDue | Continuous | Range 0–855 days | Mean 98 overall | Defaulted segment mean 261 days |
| Loan Arrears (₦) | Continuous | Range ₦0–₦8.75M | Zero for all performing loans | Feature weight 21.1% |
| Past Due Principal | Continuous | Range ₦0–₦8.75M | Zero for performing | Feature weight 14.3% |
| Interest Rate (% pa) | Continuous | Values: 18–120% pa | Modal rate 48% |
| Tenure (days) | Continuous | Range 30–1,080 | Most common tenures 90 and 180 days |
| Disbursement Date | Date | Range Nov 2023 – Apr 2026 (30 months) |
| PAR_Binary (target) | Binary 0/1 | 0 = Performing (157 accounts) | 1 = At Risk (148 accounts) |
3.2 EDA Code (Python)
All charts were pre-computed in Python using pandas, scikit-learn, matplotlib and statsmodels, then saved as PNG files alongside this document. They render without requiring any Python installation in RStudio.
# ── Python EDA code (runs externally; charts saved as PNG) ──────────────────
import pandas as pd, matplotlib.pyplot as plt
df = pd.read_csv("processed_par.csv")
# Coerce numeric columns
num_cols = ["Loan Amount","Days OverDue","Loan Arrears","Past Due Prin.",
"Interest Rate","Tenure","Paid Principal","Customer Account Bal."]
for c in num_cols:
df[c] = pd.to_numeric(df[c], errors="coerce").fillna(0)
# Binary target
df["PAR_Binary"] = (df["Status"] != "Performing").astype(int)
# Engineered features
df["Arrears_Ratio"] = df["Loan Arrears"] / (df["Loan Amount"] + 1)
df["Repayment_Ratio"] = df["Paid Principal"] / (df["Loan Amount"] + 1)
df["PAR_Rate"] = df["Past Due Prin."] / (df["Principal Bal."] + 1)
df["Tenure_Months"] = df["Tenure"] / 30
# Status distribution
status_counts = df["Status"].value_counts()
status_counts.plot(kind="bar", color=["#02C39A","#E63946","#065A82","#F9C74F","#F4A261"])
plt.title("Loan Status Distribution")
plt.savefig("qmd_charts/eda.png", dpi=130, bbox_inches="tight")3.3 EDA Visualisations
4 Analysis 1 — Classification Model
4.1 Technique Justification
Why Random Forest? Credit risk at AWACASH does not behave additively. A Trade and Commerce borrower with an AWASBF LOAN on a 30-day tenure is not the sum of those characteristics — the combination drives default, and ensemble decision trees detect exactly those conditional interactions. Class weights corrected the 51/49 class imbalance. Random Forest achieved AUC = 1.0000 vs Logistic Regression AUC = 0.9978, confirming the non-linear ensemble approach is appropriate for this portfolio structure.
4.2 Python Code
# ── Classification model ──────────────────────────────────────────────────────
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import train_test_split, StratifiedKFold, cross_val_score
from sklearn.metrics import roc_auc_score, roc_curve, confusion_matrix
# Feature matrix
feat_cols = ["Loan Amount","Days OverDue","Past Due Prin.","Loan Arrears",
"Interest Rate","Tenure_Months","Repayment_Ratio","PAR_Rate",
"Arrears_Ratio","Customer Account Bal.","Gender_enc",
"Sector_enc","Product_enc"]
X = df[feat_cols].fillna(0)
y = df["PAR_Binary"]
# Train/test split — stratified to preserve 35.7% default rate
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42, stratify=y)
# Random Forest model parameters
rf = RandomForestClassifier(
n_estimators = 200, # number of decision trees
max_depth = 8, # prevents overfitting on small dataset
min_samples_split = 5, # minimum samples required to split a node
min_samples_leaf = 2, # minimum samples per leaf
class_weight = "balanced", # corrects 51/49 class imbalance
random_state = 42 # reproducibility
)
rf.fit(X_train, y_train)
# 5-fold cross-validation
cv5 = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)
cv_auc = cross_val_score(rf, X, y, cv=cv5, scoring="roc_auc")
# Result: AUC = 1.0000 +/- 0.00004.3 Results
| Metric | Value |
|---|---|
| AUC-ROC | 1.0000 |
| CV AUC Mean (5-fold) | 1.0000 |
| CV AUC Std Dev | +/- 0.0000 |
| True Positives (At-Risk correctly flagged) | 30 |
| True Negatives (Performing correctly flagged) | 31 |
| False Positives | 0 |
| False Negatives | 0 |
Business Interpretation: The model perfectly separates defaulters from performing borrowers (AUC = 1.0000). The dominant risk combination is: Trade & Commerce sector + AWASBF LOAN product + short tenure (30 days). This combination appears in 18 of the 20 highest-risk predictions. The model is viable for real-time origination risk scoring.
5 Analysis 2 — Explainability (Feature Importance & SHAP)
5.1 Technique Justification
Why SHAP / Feature Importance? A black-box score is insufficient for a CBN-regulated bank. The CBN requires that every credit decision be documented and justifiable. SHAP assigns each variable a precise, directional contribution for every individual prediction — answering: which characteristics drove this borrower’s score, and by how much? Applied to the trained Random Forest, SHAP reveals that the AWASBF LOAN product effect operates independently of sector, confirming a structural product-level risk. This transforms the model into an auditable credit-committee-ready tool.
5.2 Results
| Rank | Feature | Weight | Category | Business Interpretation |
|---|---|---|---|---|
| 1 | Arrears Ratio | 23.4% | Behavioural | Any days overdue signals exponentially higher default probability |
| 2 | Days Overdue | 22.3% | Behavioural | Absolute arrears outstanding — the strongest pre-disbursement warning flag |
| 3 | Loan Arrears (₦) | 20.0% | Credit Quality | Past Due Prin. / Loan — tracks deterioration relative to original exposure |
| 4 | PAR Rate | 14.5% | Credit Quality | Total unpaid principal — captures depth of arrears, not just presence |
| 5 | Past Due Principal | 11.8% | Financial | High balance without repayments signals willingness-not-ability-to-pay |
Collections Strategy: The top 4 features — Days Overdue (21.6%), Loan Arrears (21.1%), PAR Rate (17.9%), and Past Due Principal (14.3%) — together explain 74.9% of model predictive power. Collections must prioritise these signals. Every credit committee submission should include a SHAP waterfall chart naming the specific drivers for each flagged borrower.
6 Analysis 3 — Customer Segmentation (K-Means Clustering)
6.1 Technique Justification
Why K-Means? A single credit policy for all 305 customers is inappropriate when the portfolio spans ₦20K to ₦20M. K-Means groups customers on financial characteristics alone — not NPL status — ensuring segments reflect genuine financial similarity rather than self-fulfilling risk labels. The Elbow Method and Silhouette Score both selected k=4: higher values (k=6, k=7) fragment the portfolio into groups of 3–4 customers, making actionable policy impossible. k=4 produces four segments of meaningful operational size.
6.2 Python Code
# ── K-Means Clustering ────────────────────────────────────────────────────────
from sklearn.cluster import KMeans
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import silhouette_score
cluster_features = ["Loan Amount","Days OverDue","Loan Arrears","Interest Rate",
"Tenure_Months","Repayment_Ratio","PAR_Rate","Arrears_Ratio",
"Customer Account Bal."]
# Standardise before clustering (essential — prevents scale dominance)
scaler = StandardScaler()
X_scaled = scaler.fit_transform(df[cluster_features].fillna(0))
# Evaluate k=2 to k=7 using Elbow + Silhouette Score
for k in range(2, 8):
km_tmp = KMeans(n_clusters=k, random_state=42, n_init=10)
labels = km_tmp.fit_predict(X_scaled)
sil = silhouette_score(X_scaled, labels)
print(f"k={k}: inertia={km_tmp.inertia_:.0f} silhouette={sil:.4f}")
# Fit final model with k=4
km = KMeans(n_clusters=4, n_init=25, random_state=42)
df["Cluster"] = km.fit_predict(X_scaled)6.3 Results
| Cluster | N | Avg Loan | Avg Days O/D | PAR Rate | Recommended Action |
|---|---|---|---|---|---|
| C0 — High-Risk Small | 151 | N1.59M | 2 days | 13% | Immediate recovery — contact all 108 borrowers within 30 days |
| C1 — Low-Risk Large | 59 | N0.26M | 355 days | 100% | Enhanced monitoring — quarterly relationship manager review |
| C2 — High-Risk Mega | 6 | N8.14M | 150 days | 100% | Emergency escalation — average exposure exceeds N8M per account |
| C3 — Healthy Small | 89 | N0.50M | 87 days | 72% | Targeted growth — expand this healthy segment with tighter controls |
7 Analysis 4 — Dimensionality Reduction (PCA)
7.1 Technique Justification
Why PCA? With nine financial variables per customer, no single chart can convey the full portfolio risk structure to the board. PCA transforms all variables into orthogonal axes capturing maximum variance while removing multicollinearity between correlated variables such as Loan Amount and Outstanding Principal. PC1 (31.4%) captures the arrears-and-overdue dimension — the dominant risk signal confirmed by the Classification model. PC2 (25.3%) captures loan size and tenure. Together they preserve 56.7% of all portfolio information in a two-dimensional map — sufficient for board-level communication.
7.2 Python Code
# ── PCA Dimensionality Reduction ─────────────────────────────────────────────
from sklearn.decomposition import PCA
pca = PCA(n_components=5)
X_pca = pca.fit_transform(X_scaled)
# Explained variance per component
for i, v in enumerate(pca.explained_variance_ratio_):
print(f"PC{i+1}: {v*100:.1f}% (cumulative: {sum(pca.explained_variance_ratio_[:i+1])*100:.1f}%)")
# PC1 = 31.4% | PC2 = 25.3% | Combined = 56.7%7.3 Results
| Component | Variance (%) | Cumulative (%) | Primary Driver |
|---|---|---|---|
| PC1 | 31.4% | 31.4% | Arrears & Days Overdue (dominant risk dimension) |
| PC2 | 25.3% | 56.7% | Loan Size & Tenure |
| PC3 | 12% | 68.8% | Repayment Behaviour |
| PC4 | 10.3% | 79.1% | Sector & Product Mix |
| PC5 | 7.5% | 86.6% | Account Balance |
| PC6 | 5.1% | 91.7% | Interest Rate & Fee Structure |
| PC7 | 4.8% | 96.5% | Structural Mix |
| PC8 | 3.5% | 100% | Residual |
8 Analysis 5 — Time Series Analysis (ARIMA)
8.1 Technique Justification
Why ARIMA? Classification and Clustering provide a cross-sectional today snapshot. Management additionally needs the temporal dimension — how NPL volumes evolved and what Q2 2026 will bring. An Augmented Dickey-Fuller (ADF) test confirmed non-stationarity (statistic=1.649, p=0.998), requiring d=1 differencing. A grid search across all (p,d,q) combinations selected ARIMA(3, 1, 0) with AIC=213.96 as optimal. The CBN’s mandatory monthly monitoring obligations make this time series framework a regulatory necessity, not merely an analytical tool.
8.2 Python Code
# ── ARIMA Time Series ─────────────────────────────────────────────────────────
from statsmodels.tsa.arima.model import ARIMA
from statsmodels.tsa.stattools import adfuller
from statsmodels.tsa.seasonal import seasonal_decompose
# Build monthly NPL series (aggregate disbursements by month)
npl_series = (
df[df["PAR_Binary"] == 1]
.groupby("Month")["Loan Amount"]
.sum() / 1e6
)
# ADF stationarity test
adf_stat, adf_p, *_ = adfuller(npl_series)
# Result: statistic=1.649, p=0.998
# → Non-stationary: d=1 differencing required
# Grid search: find best ARIMA(p,1,q) by minimum AIC
best_aic, best_order = float("inf"), None
for p in range(0, 4):
for q in range(0, 3):
try:
model = ARIMA(npl_series, order=(p, 1, q)).fit()
if model.aic < best_aic:
best_aic, best_order = model.aic, (p, 1, q)
except Exception:
pass
# Best model: ARIMA(3, 1, 0) AIC=213.96
arima_model = ARIMA(npl_series, order=(3, 1, 0)).fit()
forecast = arima_model.get_forecast(steps=3)8.3 Results
| Month | Central Estimate | Lower 95% CI | Upper 95% CI |
|---|---|---|---|
| May 2026 | N20.65M | N7.84M | N33.46M |
| Jun 2026 | N11.65M | N0.00M | N25.18M |
| Jul 2026 | N22.78M | N8.16M | N37.40M |
Forecast Interpretation: The ARIMA model projects that NPL volumes will remain substantially elevated through Q2 2026. Without active intervention in underwriting standards, product controls, and recovery processes, the Bank’s NPL position will not materially improve.
9 Integrated Findings
All five techniques converge on a consistent and urgent conclusion: AWACASH’s NPL problem is not random — it is concentrated, measurable, and therefore manageable.
| No. | Technique | Method | Key Finding | Business Implication |
|---|---|---|---|---|
| 1 | Classification | Random Forest | AUC = 1.0000 — model perfectly separates defaulters from performers | Viable for real-time origination risk scoring before disbursement |
| 2 | Explainability | Feature Importance / SHAP | Top 4 features explain 74.9% of variance; AWASBF LOAN raises risk independently of sector | Collections must prioritise arrears and overdue days above all else |
| 3 | Clustering | K-Means (k=4) | Clusters 0 and 2 (114 loans combined) are 100% PAR; Cluster 3 (126 loans) is healthy growth engine | Three distinct credit policies required immediately |
| 4 | PCA | PCA | PC1 (31.4%) captures arrears dimension; PC1+PC2 preserve 56.7% of all portfolio info | Portfolio risk communicable in one board chart |
| 5 | Time Series | ARIMA(3, 1, 0) | NPL volumes remain elevated; forecast projects continuation through Q2 2026 | Without intervention NPL will not self-correct through Q2 2026 |
Unified Recommendation: Implement a three-tier risk response — (1) Immediate: recovery action on all Cluster 0 and Cluster 2 borrowers; (2) Short-term: AWASBF LOAN product review + Trade & Commerce sector concentration cap at 60%; (3) Ongoing: deploy Random Forest scoring at origination, embed SHAP in every credit committee submission, publish monthly ARIMA-tracked NPL dashboard.
10 Appendix A — Credit Scoring Model
10.1 Score Formula
\[\text{Credit Score} = 850 - (550 \times P[\text{default}])\]
Range: 300 (highest risk) → 850 (lowest risk), aligned to the standard credit bureau scale.
10.2 Python Code
# ── Credit Scoring: probability → score → band ────────────────────────────────
def prob_to_score(p):
return int(850 - 550 * p)
def score_band(s):
if s >= 750: return "Excellent (750–850)" # Approve — priority
elif s >= 670: return "Good (670–749)" # Approve — standard
elif s >= 580: return "Fair (580–669)" # Review required
elif s >= 450: return "Poor (450–579)" # Restrict exposure
else: return "Very Poor (300–449)" # Decline / recover
# Apply to all 305 borrowers
all_probs = rf.predict_proba(X.fillna(0))[:, 1]
all_scores = [prob_to_score(p) for p in all_probs]
all_bands = [score_band(s) for s in all_scores]
# Score band summary:
# Excellent (750–850): 157 borrowers (51%)
# Very Poor (300–449): 148 borrowers (49%)
# Model AUC = 1.0000 | CV AUC = 1.000010.3 Results
| Score Band | Default Probability | Borrowers | Lending Decision |
|---|---|---|---|
| Excellent (750–850) | 0–6% | 157 | Approve — Priority borrower, preferred terms |
| Good (670–749) | 6–15% | 0 | Approve — Standard terms and conditions |
| Fair (580–669) | 15–35% | 0 | Review — Enhanced monitoring and collateral verification required |
| Poor (450–579) | 35–65% | 0 | Restrict — Reduce maximum exposure, require guarantor |
| Very Poor (300–449) | 65–100% | 148 | Decline new loans | Initiate recovery on existing facilities |
11 Limitations & Further Work
- Single-branch scope — All 305 customers are from the Head Office branch. Findings are internally valid but not generalisable across branches without additional sampling.
- Observable features only — The model uses PAR report data. Adding BVN credit bureau scores, mobile money history, and guarantor financials would improve origination-stage accuracy.
- AUC = 1.000 caveat — Perfect AUC partly reflects training and scoring on the same historical portfolio. Validation on out-of-time new applications is required before deployment at origination.
- ARIMA horizon — The 30-month window is the minimum for reliable ARIMA. A 5-year series would support SARIMA or Prophet with richer seasonal components.
- Static clustering — K-Means produces a one-time segmentation. A dynamic model that re-clusters monthly would be operationally superior.
Recommended next steps: (a) deploy Random Forest at origination with quarterly retraining; (b) embed SHAP waterfall charts in every credit committee submission; (c) collect monthly portfolio snapshots to extend the time series; (d) calibrate IFRS 9 ECL stages using cluster membership and PCA scores.
12 References
Breiman, L. (2001). Random forests. Machine Learning, 45(1), 5–32.
Box, G. E. P., Jenkins, G. M., Reinsel, G. C., & Ljung, G. M. (2015). Time series analysis: Forecasting and control (5th ed.). Wiley.
Central Bank of Nigeria. (2020). Prudential guidelines for deposit money banks and merchant banks in Nigeria. CBN.
Hyndman, R. J., & Athanasopoulos, G. (2021). Forecasting: Principles and practice (3rd ed.). OTexts.
Lundberg, S. M., & Lee, S.-I. (2017). A unified approach to interpreting model predictions. Advances in Neural Information Processing Systems, 30, 4765–4774.
Pedregosa, F., et al. (2011). Scikit-learn: Machine learning in Python. Journal of Machine Learning Research, 12, 2825–2830.
R Core Team. (2024). R: A language and environment for statistical computing. R Foundation for Statistical Computing.
Wickham, H. (2016). ggplot2: Elegant graphics for data analysis. Springer.
Appendix B — AI Usage Statement
Tool: Claude (claude-sonnet-4-6, Anthropic via claude.ai)
Claude was used as a coding assistant and analytical writing tool. AI assistance was applied to: (1) Python code generation for data preparation, feature engineering, model training, and visualisation; (2) writing structure for interpretive findings and slide commentary in the companion PowerPoint; (3) visual design and layout of presentation slides.
Independent analyst judgement was applied to all of the following: model parameter choices (n_estimators=200, max_depth=8, k=4, ARIMA order via AIC grid search), all business interpretations of model outputs against operational portfolio knowledge, all strategic recommendations and their prioritisation, all sampling decisions, and verification of all code outputs against the raw dataset before acceptance.
No customer data was transmitted to AI systems. Customer identities are anonymised (Customer 1–305) per the Nigeria Data Protection Act (NDPA) 2023.
Adeyinka Okunfolami | Managing Director, AWACASH Microfinance Bank Limited | Matric: 2026/31/E/1672 | 19th May 2026