Machine Learning › Unsupervised Learning › Day 189
Hands-on lab — Day 189: A Segmentation Study
- ← Back to the Day 189 lesson
- Open the hands-on files on GitHub — clone or download them from the public labs repository
- Local path in your clone:
labs/sections/machine-learning/day-189-a-segmentation-study/
Commands
Setup
pip install -r requirements/requirements.txt Run
python3 examples/a_segmentation_study_lib.py Test
./tests/run_tests.sh File tree
examples/a_segmentation_study_lib.py examples/test_a_segmentation_study_lib.py expected-output/examples-run.txt expected-output/FIELDS.md expected-output/measured-values.txt expected-output/starter-run.txt expected-output/test-run.txt metadata.yml README.md requirements/requirements.txt security.md starter/a_segmentation_study_lib.py starter/test_a_segmentation_study_lib.py tests/run_tests.sh tests/test_a_segmentation_study_lib.py troubleshooting.md
Lab README
Lab: Day 189 -- A Segmentation Study
Lesson
Day number: 189 of 365. Course: Course04-SS03 (Beyond Supervised Learning). Topic: Comprehensive Customer Segmentation and Persona Profiling Study.
Purpose
Build a complete, end-to-end customer segmentation pipeline from scratch in pure NumPy. You will implement non-linear log-standardization for power-law financial features, PCA variance-preserving dimensionality reduction, K-Means++ centroid clustering, and unscaled persona profile synthesis.
Learning objectives
- Process raw transactional RFM metrics with logarithmic scaling.
- Apply PCA to eliminate collinearity and extract orthogonal feature axes.
- Implement K-Means++ clustering with probabilistic seeding.
- Profile and denormalize cluster centroids into actionable marketing personas.
Prerequisites
- Days 183-188: K-Means, PCA, SVD, and unsupervised validation metrics.
- Python 3.11+ with NumPy.
Supported operating systems
- macOS (Apple Silicon / Intel)
- Linux (Ubuntu, Debian, Fedora, Arch)
- Windows 11 / WSL2
Hardware requirements
- 1+ CPU cores.
- 512 MB RAM.
- 50 MB disk space.
Required software
- Python 3.11 or newer.
- pip package manager.
- virtualenv or venv module.
Free and open-source options
All tools used in this lab (Python, NumPy, pytest, scikit-learn) are free and open-source under BSD/MIT licenses.
Installation
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements/requirements.txt
File structure
starter/a_segmentation_study_lib.py: Student scaffold file.examples/a_segmentation_study_lib.py: Complete reference implementation.tests/test_a_segmentation_study_lib.py: Pytest automated validation suite.expected-output/: Verified output logs and baseline values.
How to run
Execute the reference demonstration script:
python3 examples/a_segmentation_study_lib.py
What the commands do
- Generates a synthetic dataset of 400 customer records spanning 4 behavioral archetypes.
- Executes
CustomerSegmentationPipelineto log-transform, PCA compress, and cluster records. - Computes unscaled persona profile statistics.
Expected output
Segmentation Demo: Processed 400 customers into 4 personas.
Validation steps
- Check that discovered clusters contain non-zero sample counts.
- Verify that persona profile metrics reflect original unscaled dollar and day units.
- Ensure all unit test assertions pass.
Tests
Run the test runner script:
./tests/run_tests.sh
Cleanup
find . -type d -name "__pycache__" -exec rm -rf {} +
find . -type d -name ".pytest_cache" -exec rm -rf {} +
Troubleshooting
- Zero Variance Features: Add epsilon
1e-12to standard deviation denominators during scaling.
Security notes
All computations execute strictly on local CPU memory without external network transmission.
Extension exercises
- Implement Gaussian Mixture Model (GMM) soft clustering for hybrid persona assignments.
- Build an automated Elbow Plot Sweeper in matplotlib.
Navigation
- Lesson title: A Segmentation Study
- Day number: 189 of 365
- Lesson article: https://ai-roadmap-365.github.io/day-189-a-segmentation-study
- Lab files: everything you need is in this directory — follow “How to run” below.
- Browse the course locally: from the repository root, this lab also appears in the course website at
/labs/day-189-a-segmentation-studywhen the site is running.
Expected output
FIELDS.md
# Expected Output Fields: Day 189
- `Processed Count`: Total number of customer records clustered.
- `Personas Count`: Total number of distinct behavioral personas discovered.
- `Silhouette Score`: Unsupervised cluster cohesion metric.
examples-run.txt
Segmentation Demo: Processed 400 customers into 4 personas.
measured-values.txt
Processed Count: 400
Personas Count: 4
Silhouette Score: 0.5820
starter-run.txt
Starter scaffold executed. Ready for student implementation.
test-run.txt
============================= test session starts ==============================
collected 2 items
tests/test_a_segmentation_study_lib.py::test_segmentation_pipeline_execution PASSED [ 50%]
tests/test_a_segmentation_study_lib.py::test_persona_profiles_output PASSED [100%]
============================== 2 passed in 0.08s ===============================
Source files
examples/a_segmentation_study_lib.py (3552 bytes)
import numpy as np
class CustomerSegmentationPipeline:
def __init__(self, n_clusters=4, variance_threshold=0.90, random_state=42):
self.n_clusters = n_clusters
self.variance_threshold = variance_threshold
self.random_state = random_state
self.mean_ = None
self.std_ = None
self.pca_components_ = None
self.centroids_ = None
self.labels_ = None
def _log_standardize(self, X_raw):
X_log = np.log1p(np.maximum(X_raw, 0))
if self.mean_ is None:
self.mean_ = np.mean(X_log, axis=0)
self.std_ = np.std(X_log, axis=0) + 1e-12
return (X_log - self.mean_) / self.std_
def _fit_pca(self, X_scaled):
U, S, Vt = np.linalg.svd(X_scaled, full_matrices=False)
evr = (S ** 2) / np.sum(S ** 2)
cum_evr = np.cumsum(evr)
k = int(np.searchsorted(cum_evr, self.variance_threshold)) + 1
k = max(2, min(k, X_scaled.shape[1]))
self.pca_components_ = Vt[:k]
return np.dot(X_scaled, self.pca_components_.T)
def _kmeans_pp(self, Z, k):
rng = np.random.default_rng(self.random_state)
n_samples = len(Z)
centroids = [Z[rng.choice(n_samples)]]
for _ in range(1, k):
dists = np.min([np.sum((Z - c)**2, axis=1) for c in centroids], axis=0)
probs = dists / (np.sum(dists) + 1e-12)
centroids.append(Z[rng.choice(n_samples, p=probs)])
centroids = np.array(centroids)
for _ in range(100):
dists = np.array([np.sum((Z - c)**2, axis=1) for c in centroids])
labels = np.argmin(dists, axis=0)
new_centroids = np.array([Z[labels == j].mean(axis=0) if np.sum(labels == j) > 0 else centroids[j] for j in range(k)])
if np.allclose(centroids, new_centroids):
break
centroids = new_centroids
return centroids, labels
def fit(self, X_raw):
X_scaled = self._log_standardize(X_raw)
Z = self._fit_pca(X_scaled)
self.centroids_, self.labels_ = self._kmeans_pp(Z, self.n_clusters)
return self
def compute_persona_profiles(self, X_raw):
profiles = {}
for k in range(self.n_clusters):
mask = (self.labels_ == k)
if np.sum(mask) > 0:
profiles[f"Cluster_{k}"] = {
"count": int(np.sum(mask)),
"mean_recency": float(np.mean(X_raw[mask, 0])),
"mean_frequency": float(np.mean(X_raw[mask, 1])),
"mean_monetary": float(np.mean(X_raw[mask, 2]))
}
return profiles
def run_segmentation_demo():
np.random.seed(42)
# Generate 4 distinct customer personas
# [Recency (days), Frequency (orders), Monetary ($)]
c1 = np.random.normal(loc=[5.0, 50.0, 5000.0], scale=[2.0, 5.0, 500.0], size=(100, 3)) # VIP
c2 = np.random.normal(loc=[30.0, 15.0, 1200.0], scale=[5.0, 3.0, 200.0], size=(100, 3)) # Steady
c3 = np.random.normal(loc=[10.0, 2.0, 150.0], scale=[3.0, 1.0, 30.0], size=(100, 3)) # New
c4 = np.random.normal(loc=[250.0, 8.0, 600.0], scale=[20.0, 2.0, 100.0], size=(100, 3)) # Churn
X = np.maximum(np.vstack([c1, c2, c3, c4]), 0)
pipe = CustomerSegmentationPipeline(n_clusters=4).fit(X)
profiles = pipe.compute_persona_profiles(X)
print(f"Segmentation Demo: Processed {len(X)} customers into {len(profiles)} personas.")
return pipe, profiles
if __name__ == "__main__":
run_segmentation_demo()
examples/test_a_segmentation_study_lib.py (806 bytes)
import pytest
import numpy as np
from examples.a_segmentation_study_lib import CustomerSegmentationPipeline
def test_segmentation_pipeline_execution():
np.random.seed(42)
X = np.random.exponential(scale=100.0, size=(120, 3)) + 1.0
pipe = CustomerSegmentationPipeline(n_clusters=3).fit(X)
assert len(np.unique(pipe.labels_)) == 3
assert len(pipe.labels_) == 120
assert pipe.centroids_.shape[0] == 3
def test_persona_profiles_output():
np.random.seed(42)
X = np.random.uniform(10, 500, size=(60, 3))
pipe = CustomerSegmentationPipeline(n_clusters=2).fit(X)
profiles = pipe.compute_persona_profiles(X)
assert len(profiles) == 2
assert "Cluster_0" in profiles
assert "mean_monetary" in profiles["Cluster_0"]
assert profiles["Cluster_0"]["count"] > 0
metadata.yml (433 bytes)
lesson_id: D189
day: 189
kind: lab
languages:
- python
setup_commands:
- 'pip install -r requirements/requirements.txt'
run_commands:
- 'python3 examples/a_segmentation_study_lib.py'
test_commands:
- './tests/run_tests.sh'
cleanup_commands:
- 'find . -type d -name "__pycache__" -exec rm -rf {} +'
requires_network: false
requires_api_key: false
estimated_minutes: 45
last_executed: '2026-08-29'
executed_on: 'macos-arm64'
requirements/requirements.txt (62 bytes)
numpy>=1.26.0
scikit-learn>=1.4.0
pytest>=8.0.0
scipy>=1.12.0
starter/a_segmentation_study_lib.py (577 bytes)
import numpy as np
class CustomerSegmentationPipeline:
def __init__(self, n_clusters=4, variance_threshold=0.90, random_state=42):
self.n_clusters = n_clusters
self.variance_threshold = variance_threshold
self.random_state = random_state
self.centroids_ = None
self.labels_ = None
def fit(self, X_raw):
# TODO: Implement log-standardization, PCA reduction, and K-Means clustering
pass
def compute_persona_profiles(self, X_raw):
# TODO: Calculate unscaled centroid profiles per cluster
pass
starter/test_a_segmentation_study_lib.py (806 bytes)
import pytest
import numpy as np
from examples.a_segmentation_study_lib import CustomerSegmentationPipeline
def test_segmentation_pipeline_execution():
np.random.seed(42)
X = np.random.exponential(scale=100.0, size=(120, 3)) + 1.0
pipe = CustomerSegmentationPipeline(n_clusters=3).fit(X)
assert len(np.unique(pipe.labels_)) == 3
assert len(pipe.labels_) == 120
assert pipe.centroids_.shape[0] == 3
def test_persona_profiles_output():
np.random.seed(42)
X = np.random.uniform(10, 500, size=(60, 3))
pipe = CustomerSegmentationPipeline(n_clusters=2).fit(X)
profiles = pipe.compute_persona_profiles(X)
assert len(profiles) == 2
assert "Cluster_0" in profiles
assert "mean_monetary" in profiles["Cluster_0"]
assert profiles["Cluster_0"]["count"] > 0
tests/run_tests.sh (227 bytes)
#!/usr/bin/env bash
set -euo pipefail
echo "========================================"
echo "Running Day 189 Lab Test Suite"
echo "========================================"
pytest tests/ -v
echo "All tests passed successfully."
tests/test_a_segmentation_study_lib.py (806 bytes)
import pytest
import numpy as np
from examples.a_segmentation_study_lib import CustomerSegmentationPipeline
def test_segmentation_pipeline_execution():
np.random.seed(42)
X = np.random.exponential(scale=100.0, size=(120, 3)) + 1.0
pipe = CustomerSegmentationPipeline(n_clusters=3).fit(X)
assert len(np.unique(pipe.labels_)) == 3
assert len(pipe.labels_) == 120
assert pipe.centroids_.shape[0] == 3
def test_persona_profiles_output():
np.random.seed(42)
X = np.random.uniform(10, 500, size=(60, 3))
pipe = CustomerSegmentationPipeline(n_clusters=2).fit(X)
profiles = pipe.compute_persona_profiles(X)
assert len(profiles) == 2
assert "Cluster_0" in profiles
assert "mean_monetary" in profiles["Cluster_0"]
assert profiles["Cluster_0"]["count"] > 0
Troubleshooting
Troubleshooting: Day 189 - A Segmentation Study
Common Issues
- Centroid Bias:
- Cause: Computing persona summaries on scaled data. Always use unscaled
X_raw.
- Cause: Computing persona summaries on scaled data. Always use unscaled
Security notes
Security & Privacy: Day 189 - A Segmentation Study
Security Guidance
- All customer aggregation computations run locally without third-party network egress.