🧬 Section 4 · Unsupervised Learning 🔴 Capstone Project MODULE 20

Project — Customer Segmentation with K-Means

⏱️ 90 min · hands-on
📖 End-to-End K-Means Segmentation
🧩 4 Quiz Questions
🏗️ 1 Challenge · 3 tasks
Your progress in Section 4100%
🎯 The Project: This is the Section 4 capstone — every unsupervised technique from Lessons 17–19 gets put to work on a real business scenario. You'll take a synthetic customers.csv dataset (age, annual income, spending score), scale its features, use the elbow method (Lesson 17) to choose k, fit a final KMeans model, visualize the resulting segments on a scatter plot colored by cluster, and — the step a comparison table alone never does — translate each numeric cluster into a plain-English business segment with a suggested action. Every number in this lesson is illustrative, generated for a toy dataset, not a real company's data.

The Project Brief

Lesson 17 introduced K-Means in the abstract, using generic X and no real-world framing. This project runs the FULL workflow end to end on a concrete scenario: a retail team wants to stop treating every customer the same way, and wants a small number of natural customer segments to design different marketing approaches around.

📋 The brief
Using an illustrative customers.csv dataset (Age, AnnualIncome in thousands, SpendingScore from 1–100), scale the features, use the elbow method to choose a number of clusters, fit a final KMeans model, visualize the resulting segments on a scatter plot, and write a short business interpretation of each segment with a suggested marketing action.
1
Load the dataset
Inspect the three numeric features that will drive segmentation.
2
Scale the features
StandardScaler — K-Means is distance-based, exactly like Lesson 17 stressed.
3
Run the elbow method
Loop k from 1–10, plot inertia, pick the elbow (Lesson 17, Section 2).
4
Fit the final KMeans model
K-Means++ initialization, assign every customer a cluster label.
5
Visualize the segments
A scatter plot of income vs. spending score, colored by cluster, with centroids marked.
6
Interpret in business terms
Translate each cluster's average feature values into a plain-English segment and a suggested action.

The Dataset

Three numeric features per customer, deliberately simple so the resulting segments stay easy to interpret and explain to a non-technical stakeholder — exactly the kind of dataset a first segmentation project realistically starts with.

load_customers.py
PYTHON
import pandas as pd

df = pd.read_csv("customers.csv")
print(df.head())
print(df.shape)
# (200, 4)
customers.csv — illustrative sample rows, not real customer data
CustomerIDAgeAnnualIncomeSpendingScore
1284278
2529522
3358885
4463118
5616745

AnnualIncome is in thousands of dollars, and SpendingScore is a pre-computed 1–100 score representing how much a customer spends relative to their income — a common way segmentation datasets summarize spending behavior into one comparable number.

Scaling the Features

Exactly as Lesson 17's Section 6 warned, K-Means measures raw Euclidean distance — and AnnualIncome (ranging into the 100s) would completely dominate SpendingScore (ranging 1–100) and Age (ranging maybe 18–70) if left unscaled. Every feature needs to contribute on a comparable footing.

scale_features.py
PYTHON
from sklearn.preprocessing import StandardScaler

features = ["Age", "AnnualIncome", "SpendingScore"]
X = df[features]

scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)

print(f"Scaled means (should be ~0): {X_scaled.mean(axis=0).round(3)}")
print(f"Scaled stds (should be ~1):  {X_scaled.std(axis=0).round(3)}")
⚠️
Keep the scaler object — it's needed again in Section 6
This project fits scaler once and reuses the SAME fitted object later to convert cluster centroids back to original units with scaler.inverse_transform(). Refitting a new scaler at that point would silently produce wrong numbers — the fitted scaler from this step needs to stay in scope for the rest of the project.

The Elbow Method — Choosing k

Same loop as Lesson 17, Section 2 — applied here to the actual project data instead of a generic example.

elbow_method.py
PYTHON
import matplotlib.pyplot as plt
from sklearn.cluster import KMeans

inertias = []
k_range = range(1, 11)
for k in k_range:
    km = KMeans(n_clusters=k, init="k-means++", n_init=10, random_state=42)
    km.fit(X_scaled)
    inertias.append(km.inertia_)

plt.plot(k_range, inertias, marker="o")
plt.xlabel("Number of clusters (k)")
plt.ylabel("Inertia (WCSS)")
plt.title("Elbow Method — Customer Segmentation")
plt.show()
Illustrative inertia by k on this project's data
k = 2
312
-
k = 3
213
-32%
k = 4
137
-36%
k = 5
70
-49% ⬅ elbow
k = 6
59
-16%
k = 7
52
-12%

Just like Lesson 17's example, the drop from k=4 to k=5 (-49%) is much larger than the drop from k=5 to k=6 (-16%) — so k=5 is this project's illustrative elbow. Five segments is also a very workable number for a marketing team to design five distinct campaigns around, which matters in practice alongside the pure math.

Fitting the Final KMeans Model

With k=5 chosen, fit one final model and attach the resulting cluster label back onto the original (unscaled) DataFrame — keeping the labels next to the human-readable values makes every later step easier.

fit_final_kmeans.py
PYTHON
kmeans = KMeans(n_clusters=5, init="k-means++", n_init=10, random_state=42)
df["Cluster"] = kmeans.fit_predict(X_scaled)

print(df.groupby("Cluster")[features].mean().round(1))
print(df["Cluster"].value_counts().sort_index())
Illustrative cluster averages (unscaled, original units) — a toy dataset, not real customer data
ClusterAvg AgeAvg Income (k$)Avg Spending ScoreCustomers
044.288.517.338
125.626.176.834
232.986.282.441
346.128.319.739
443.055.449.848

Visualizing the Segments

Income vs. spending score is the most informative pair to plot — it's the exact pair a real marketing team would look at first. Centroids get converted back to original units with scaler.inverse_transform() so they land in the same $/score scale as the raw points.

visualize_segments.py
PYTHON
import matplotlib.pyplot as plt

plt.figure(figsize=(8, 6))
scatter = plt.scatter(
    df["AnnualIncome"], df["SpendingScore"],
    c=df["Cluster"], cmap="viridis", alpha=0.7
)

# Convert centroids from scaled space back to original units
centers = scaler.inverse_transform(kmeans.cluster_centers_)
plt.scatter(centers[:, 1], centers[:, 2], c="red", marker="X", s=200, label="Centroids")

plt.xlabel("Annual Income (k$)")
plt.ylabel("Spending Score (1-100)")
plt.title("Customer Segments")
plt.legend()
plt.colorbar(scatter, label="Cluster")
plt.show()
📝
centers[:, 1] and centers[:, 2] — why those column indexes
features = ["Age", "AnnualIncome", "SpendingScore"] from Section 3 fixed the column ORDER that both X_scaled and kmeans.cluster_centers_ follow — column 0 is Age, column 1 is AnnualIncome, column 2 is SpendingScore. scaler.inverse_transform() preserves that same order, so centers[:, 1] and centers[:, 2] line up exactly with the income and spending score being plotted on the x and y axes.
Illustrative customer segments — income vs. spending score
5 clusters (colors) with their centroids (★) marked. Toy data for illustration only.
Low incomeHigh income →
Cluster 2 — high income, high spending
Cluster 1 — low income, high spending
Cluster 0 — high income, low spending
Cluster 3 — low income, low spending
Cluster 4 — mid income, mid spending

Interpreting the Segments in Business Terms

Five cluster IDs mean nothing to a marketing team on their own. The last real step of this project — the one a raw comparison table never does — is translating each numeric cluster average from Section 5 into a plain-English label and a concrete suggested action. Every label and action below is illustrative reasoning about a toy dataset, not a documented real-world result.

Illustrative segment interpretation
ClusterProfileSuggested labelPossible action
0High income, low spendingCareful SpendersUpsell target — investigate why spend is low despite ability to pay; try a premium offer or loyalty incentive.
1Low income, high spendingBudget EnthusiastsValue-focused promotions, discounts, and lower-price-point product lines.
2High income, high spendingPremium LoyalistsHighest-value segment — prioritize retention, loyalty programs, and premium/early-access offers.
3Low income, low spendingLow EngagementLowest priority for marketing spend; light-touch, low-cost outreach only.
4Mid income, mid spendingEveryday ShoppersThe largest, most "average" segment — general campaigns, broad seasonal promotions.
The "high income, low spending" segment is a classic example of why segmentation matters
Without segmentation, a "top spenders" report would completely miss Cluster 0 — customers who clearly CAN spend more (high income) but currently don't. A single blended "average customer" view hides that opportunity entirely; only comparing groups side by side (Section 5's table) reveals it. This is the concrete business value K-Means adds over just looking at overall averages.
⚠️
Segment labels are a starting hypothesis, not a final answer
"Careful Spenders" and "Premium Loyalists" are working names based on THREE numeric features — real segmentation projects usually validate these hypotheses with more data (purchase categories, channel, tenure) and, where possible, small experiments (does a targeted offer to Cluster 0 actually move spending?) before treating the labels as ground truth. K-Means found the numeric groups; the business story on top of them still needs testing.

The Complete Script, Start to Finish

Every step from this project, combined into one runnable segmentation pipeline.

customer_segmentation.py — COMPLETE PROGRAM
PYTHON
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.preprocessing import StandardScaler
from sklearn.cluster import KMeans

# 1. Load
df = pd.read_csv("customers.csv")
features = ["Age", "AnnualIncome", "SpendingScore"]
X = df[features]

# 2. Scale
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)

# 3. Elbow method
inertias = []
for k in range(1, 11):
    km = KMeans(n_clusters=k, init="k-means++", n_init=10, random_state=42)
    km.fit(X_scaled)
    inertias.append(km.inertia_)
plt.plot(range(1, 11), inertias, marker="o")
plt.xlabel("k"); plt.ylabel("Inertia"); plt.show()

# 4. Fit final model (k=5 chosen from the elbow plot)
kmeans = KMeans(n_clusters=5, init="k-means++", n_init=10, random_state=42)
df["Cluster"] = kmeans.fit_predict(X_scaled)

# 5. Visualize
scatter = plt.scatter(df["AnnualIncome"], df["SpendingScore"], c=df["Cluster"], cmap="viridis", alpha=0.7)
centers = scaler.inverse_transform(kmeans.cluster_centers_)
plt.scatter(centers[:, 1], centers[:, 2], c="red", marker="X", s=200)
plt.xlabel("Annual Income (k$)"); plt.ylabel("Spending Score"); plt.show()

# 6. Business interpretation
summary = df.groupby("Cluster")[features].mean().round(1)
print(summary)
🧩 Knowledge Check — Lesson 20
4 questions on the end-to-end customer segmentation workflow.
1. Why does this project scale Age, AnnualIncome, and SpendingScore before fitting KMeans?
2. What was the elbow method used for in this project?
3. In this project's illustrative results, what business action was suggested for Cluster 0 (high income, low spending)?
4. Why does the code call scaler.inverse_transform(kmeans.cluster_centers_) before plotting the centroids?
💪
Try It Yourself — Lesson 20
Extend the customer segmentation project · Advanced Level

Use the df, X_scaled, scaler, and kmeans objects from Sections 2–5 as your starting point for each task below.

Task 1: Try a different k and compare 🔢

Refit KMeans with k=4 and again with k=6 on the same scaled data. Look at df.groupby("Cluster")[features].mean() for each. Do the segment stories still make sense with fewer or more clusters, or does k=5 genuinely tell a cleaner business story?
Task 2: Segment with DBSCAN instead 🌫️

Using Lesson 17's DBSCAN, fit it on the same X_scaled data with a couple of eps values. How many clusters does it find, and how many customers get labeled as noise (-1)? Are the customers DBSCAN flags as noise ones that also looked like edge cases in the K-Means scatter plot?
Task 3: Reduce to 2D with PCA first 📉

Using Lesson 18's PCA(n_components=2), reduce X_scaled to 2 components BEFORE running K-Means, instead of clustering on the original 3 scaled features. Compare the resulting cluster assignments to the original result with pd.crosstab. Does PCA-then-cluster produce a similar segmentation, or a meaningfully different one? Write 2–3 sentences on what you found.
💡 Show hints if you're stuck
  • Task 1: With only 3 features, k=4 or k=6 will likely just split or merge one of the k=5 segments rather than reveal something totally new — that itself is a useful observation to write down.
  • Task 2: DBSCAN has no direct concept of "5 segments" — expect a different, possibly smaller, number of dense clusters plus some noise points.
  • Task 3: With only 3 original features, PCA(n_components=2) keeps most but not all of the variance — check pca.explained_variance_ratio_.sum() to see exactly how much was kept before judging whether the clustering changed meaningfully.
Finished the capstone project?
Mark it complete to track your progress.
🎉

Section 4 Complete — Capstone Project Done!

You've now run a full unsupervised-learning workflow end to end: scaling, the elbow method, fitting K-Means, visualizing segments, and — the step that actually makes clustering useful — translating clusters into a business story with suggested actions. That's every core skill from Lessons 17–19, combined into one real project. Section 5 is next — saving trained models and shipping them as a real API.

Module 20 of 24 Section 4 — Unsupervised Learning