An end-to-end data engineering pipeline for segmenting banking customers in the Kenyan market. The project covers data ingestion and validation, geographic enrichment, feature engineering, unsupervised clustering, segment profiling, and an interactive Streamlit dashboard — built to production-grade code standards with a reproducible, modular pipeline.
Banks in Kenya manage thousands of customers with different financial profiles, relationship tenures, and product needs. Treating them as a single group leads to poorly targeted products, weak retention, and missed revenue. The goal was to build a structured segmentation pipeline that identifies distinct customer groups from raw banking data — and produces something the business can act on, not just a model that outputs cluster labels.
Two datasets were used:
Customer data (customer_data.xlsx) — 2,812 records, 32 columns:
- Demographics: age, sex, nationality, occupation
- Banking metadata: relationship type, fee structure, loyalty classification, investment advisor, banking contact
- Financial balances: estimated income, bank deposits, bank loans, saving accounts, checking accounts, credit card balance, superannuation savings, foreign currency account, business lending
- Relationship history: date joined, last contact, last meeting, days with bank, relationship time frame
Location data (location.xlsx) — 10,013 records, 8 columns:
- Location ID, street, suburb, postcode, longitude, latitude, full address, city
The two datasets were merged on Location ID using a left join with referential integrity validation (validate="m:1"). Zero customers were left unmatched. The resulting dataset had 39 columns. The only missing values were 183 records in the Street column, imputed using Suburb as a proxy.
The pipeline was built in five distinct stages, each with validation at entry and exit:
- Ingestion — schema validation, duplicate detection, and missing value checks on both datasets before any processing
- Integration — left merge on Location ID with post-merge row count validation and unmatched record logging
- EDA — statistical profiling, distribution analysis, correlation matrix, and categorical breakdowns before feature engineering
- Feature engineering — engineered features on top of the clean, merged dataset
- Clustering — standardised features, silhouette-based cluster selection, K-Means, and segment profiling
The interim merged dataset was saved to Parquet (merged_customer_data.parquet) for reproducibility between stages.
Raw balances don't capture financial behaviour. The following features were engineered to represent what customers actually do with their money:
| Feature | Formula | Business Logic |
|---|---|---|
| Total Assets | Bank Deposits + Saving Accounts + Superannuation Savings | Full picture of liquid and semi-liquid wealth |
| Total Debt | Bank Loans + Credit Card Balance | Consolidated liability position |
| Net Wealth | Total Assets − Total Debt | True financial position |
| Debt-to-Income Ratio | Total Debt / Estimated Income | Leverage relative to earnings |
| Savings Ratio | Saving Accounts / Estimated Income | Propensity to save |
| Deposit-to-Loan Ratio | Bank Deposits / Bank Loans | Asset coverage of debt |
| Credit Utilization Ratio | Credit Card Balance / Number of Credit Cards | Proxy for financial stress |
| Product Diversity Score | Count of non-zero product balances | Cross-sell depth |
| Income Category | Binned: 0–20K / 20–50K / 50–100K / 100K+ | Interpretable income segment |
| Date features | Year, month, quarter, days-since for Joined Bank / Last Contact / Last Meeting | Recency and relationship tenure |
All division operations used np.where guards to handle zero denominators cleanly.
Outliers were treated using IQR clipping (1.5× IQR) applied to all numeric columns before clustering.
A scikit-learn ColumnTransformer pipeline was built with separate branches for numeric and categorical features:
- Numeric: mean imputation → StandardScaler
- Categorical: most-frequent imputation → OneHotEncoder (handle_unknown="ignore")
The transformed output was converted from sparse to dense matrix before being passed to the clustering step.
Features used for segmentation:
- Age
- Estimated Income
- Total Assets
- Credit Utilization Ratio
- Total Debt
- Saving Accounts
Three algorithms were implemented:
| Algorithm | Rationale |
|---|---|
| K-Means | Primary model — interpretable, scalable, deterministic with fixed random seed |
| Gaussian Mixture Models | Probabilistic alternative for soft cluster boundaries |
| DBSCAN | Density-based, useful for detecting noise and non-spherical clusters |
Optimal cluster count was selected using silhouette score analysis across k = 2 to 10, plotted alongside WCSS (elbow method) for visual confirmation. The best silhouette score determined the final k used in production.
Fitted models and scalers were persisted to disk using joblib for reuse without retraining.
| Cluster 0 | Cluster 1 | |
|---|---|---|
| Size | 797 customers (28.3%) | 2,015 customers (71.7%) |
| Avg Age | 50.9 years | 51.2 years |
| Avg Income | KES 155,295 | KES 173,448 |
| Avg Total Assets | KES 2,255,881 | KES 905,124 |
| Debt-to-Income Ratio | 8.49 | 3.25 |
| Savings Ratio | 14.53 | 4.22 |
| Credit Utilization | Higher | Lower |
Cluster 0 — High-Asset, High-Leverage customers (28.3%): Despite lower average income than Cluster 1, this group holds more than double the total assets and carries proportionally more debt. Savings and deposit-to-loan ratios are significantly elevated. Retail is the dominant banking relationship (385 customers), followed by Investments Only (162) and Private Bank (114). Top cities: Nairobi (83), Kiambu (46), Nakuru (44). Business recommendation: premium wealth management, dedicated relationship manager.
Cluster 1 — Mainstream Retail customers (71.7%): The broader customer base. Moderate income, lower assets, conservative debt levels. Retail dominates (964 customers), followed by Investments Only (412) and Commercial (299). Top cities: Nairobi (234), Kiambu (106), Nyeri (98). Business recommendation: investment product promotion, financial advisory services.
Strong correlations identified (|r| ≥ 0.70):
- Bank Deposits ↔ Checking Accounts: r = 0.84
- Bank Deposits ↔ Saving Accounts: r = 0.75
- Location ID ↔ Location ID_1: r = 1.00 (exact duplicate — one column should be dropped before modelling)
Distribution characteristics:
- Age is symmetrically distributed (skewness ≈ 0.00), ranging 17–85, median 51
- Estimated Income is right-skewed (skewness = 0.88), median KES 141,620 vs mean KES 171,118
- Bank Deposits, Saving Accounts, and Business Lending are all positively skewed — a small number of high-balance customers pull the mean upward
Customer breakdown:
- 51.2% have been with the bank for over 20 years
- Retail is the largest relationship type at 48.0% of customers
- European nationality accounts for 44.0% of the base (5 nationalities total)
- Nairobi accounts for 11.3% of customers; Kiambu (5.4%), Mombasa (4.1%), Meru (4.0%), and Nakuru (4.8%) are the next largest markets
| Layer | Tools |
|---|---|
| Data ingestion | pandas, openpyxl, pathlib |
| Validation | Custom schema and referential integrity checks |
| Feature engineering | pandas, numpy, scikit-learn |
| Preprocessing | scikit-learn Pipeline, ColumnTransformer |
| Clustering | scikit-learn KMeans, GaussianMixture, DBSCAN |
| Visualisation | matplotlib, seaborn, plotly |
| Dashboard | Streamlit |
| Persistence | joblib, Parquet |
| Logging | Python logging module |
| Environment | Python 3.8+, virtual environment |
CustomerSegmentation/
├── bank_customer_segmentation.ipynb # End-to-end analysis notebook
├── app.py # Streamlit dashboard
├── requirements.txt # Dependencies
├── data/
│ ├── raw/
│ │ ├── customer_data.xlsx
│ │ └── location.xlsx
│ ├── interim/
│ │ └── merged_customer_data.parquet
│ └── processed/
├── models/
│ ├── customer_segmentation_kmeans.joblib
│ └── feature_scaler.joblib
├── reports/
│ └── segment_analysis.json
└── README.md
1. Clone and set up
git clone https://github.com/James-Muguro/CustomerSegmentation.git
cd CustomerSegmentation
python -m venv .venv
source .venv/Scripts/activate # Windows: .venv\Scripts\activate
pip install -r requirements.txt2. Run the notebook
Open bank_customer_segmentation.ipynb in Jupyter and run all cells top to bottom. The pipeline auto-detects Excel files in data/raw/ — no hardcoded paths.
3. Launch the dashboard
streamlit run app.pyThe app loads data/customer_data.xlsx automatically. If the file is missing it falls back to a synthetic dataset so the UI is always explorable. Use the sidebar to select clustering features, adjust k, and run the model interactively.
Drop redundant and identifier columns before the preprocessing pipeline. Location ID and Location ID_1 are identical (r = 1.0). Columns like Name, Customer ID, Banking Contact, and Full Address add no signal to clustering and inflate the one-hot encoded feature matrix significantly. A deliberate feature selection step before the ColumnTransformer would produce cleaner clusters.
Validate the Credit Utilization Ratio formula. The current formula divides Credit Card Balance by Number of Credit Cards, which produces a per-card balance rather than a true utilization rate. A proper credit utilization ratio requires a credit limit denominator. The notebook acknowledges this in a comment — fixing it upstream would make the ratio more meaningful as a risk proxy.
Restrict scaler fitting to training data. The current pipeline fits StandardScaler on the full dataset. For a production model with a train/test split, fitting on training data only is necessary to prevent leakage. The CustomerSegmentation class fits in __init__, which means refitting is also triggered on every call — worth separating into a dedicated fit() method.
Test higher values of k with stakeholder input. The silhouette score selected k = 2, which is interpretable but likely too coarse for product-level targeting. In a real deployment, k = 4 or 5 would be worth validating with business stakeholders to check whether the additional segments are actionable rather than just statistically distinct.
Automate reporting. Segment profiles and recommendations are currently printed to the notebook. Templated HTML or PDF reports generated on each run would make the output shareable with non-technical stakeholders without requiring them to open a Jupyter notebook.
Built with scikit-learn, pandas, plotly, matplotlib, and Streamlit. Dataset is a simulated Kenyan banking dataset used for analytical and portfolio purposes.