NON PERFORMING LOAN ANALYSIS — AWACASH MICROFINANCE BANK

Author

Adeyinka Okunfolami

Published

May 19, 2026


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.

Table 1 — Portfolio Health Snapshot, May 2026
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

1 Professional Disclosure

Note
Field Detail
Role Managing Director
Organisation AWACASH Microfinance Bank Limited
Sector Banking & Financial Services (Microfinance)
Regulator Central Bank of Nigeria (CBN)
Dataset Internal Portfolio At Risk (PAR) Report
Analysis Period November 2023 – May 2026 (30 months)
Records 305 Loan Accounts (census — all accounts included)

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.

3 Data Description & EDA

3.1 Variable Dictionary

Table 2 — Variable Dictionary (n = 305)
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

Figure 1: Figure 1 — Portfolio EDA Dashboard: status distribution, loan amounts, sector breakdown, PAR rates, product mix, days overdue

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.0000

4.3 Results

Figure 2: Figure 2 — Classification Results: ROC curves (Random Forest AUC=1.0000 vs Logistic Regression AUC=0.9978) and Confusion Matrix
Table 3 — Random Forest Classification Performance
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

Figure 3: Figure 3 — Feature Importance: Mean Decrease Gini for top 10 predictors, coloured by variable category (Behavioural, Credit Quality, Financial, Structural)
Table 4 — Top 5 Feature Importance Drivers
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

Figure 4: Figure 4 — K-Means: Elbow method, Silhouette scores, and cluster scatter projected onto PCA space
Figure 5: Figure 5 — Cluster Profiles: average loan amount, days overdue, and PAR rate across the four borrower segments
Table 5 — Cluster Profiles & Credit Management Actions
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

Figure 6: Figure 6 — PCA: Scree plot showing variance per component and Portfolio Risk Map (2D projection coloured by K-Means cluster)
Table 6 — PCA Variance Explained per Component
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

Figure 7: Figure 7 — Time Series: Monthly NPL amounts with 3-month ARIMA(3, 1, 0) forecast and seasonal decomposition
Table 7 — ARIMA 3-Month NPL Forecast
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.

Table 8 — Integrated Findings: Five Techniques, One Conclusion
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.0000

10.3 Results

Figure 8: Figure 8 — Credit Scoring: score distribution by risk status and borrower count by score band
Table 9 — Credit Score Decision Framework
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 &#124; Initiate recovery on existing facilities

11 Limitations & Further Work

  1. Single-branch scope — All 305 customers are from the Head Office branch. Findings are internally valid but not generalisable across branches without additional sampling.
  2. 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.
  3. 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.
  4. 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.
  5. 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