Siksha Sarovar

Siksha Sarovar (sikshasarovar.com) is a free educational web application that helps students in India learn programming and prepare for academic and competitive exams. The platform offers structured coding courses (C, C++, Python, Java, HTML, CSS, PHP, Power BI, AI, Machine Learning, Data Science), complete university curriculum notes for BCA/MCA students with previous year question papers, Class 10 and Class 12 CBSE/HBSE school notes, and dedicated preparation material for SSC, UPSC, Banking, Railway and other government exams. Browsing the site is completely free and requires no account. Users may optionally sign in with Google solely to save their learning progress, quiz scores and personal preferences across devices.

Privacy Policy | Terms of Service | Contact Siksha Sarovar | About Siksha Sarovar

v4.0.9 · PWA
Siksha Sarovar logo
Siksha Sarovar
Your Learning Universe

Siksha Sarovar is a free e-learning platform for coding courses, BCA university notes and competitive exam preparation. Optional Google sign-in saves your learning progress across devices.

Initializing knowledge base…
Compiling modules 0%

Unit 3 — K-Means Algorithm

Lesson 32 of 46 in the free Introduction to Data Analytics notes on Siksha Sarovar, written by Rohit Jangra.

K-Means Clustering Algorithm

K-Means partitions n observations into k clusters, where each observation belongs to the cluster with the nearest mean (centroid). It is the most widely used clustering algorithm — simple, fast, and scalable.

The Algorithm

INPUT:  dataset D, number of clusters k

1. INITIALISE:  choose k initial centroids (randomly, or by k-means++)

2. REPEAT until convergence:

   a) ASSIGNMENT STEP:
      assign each data point to the cluster whose centroid is NEAREST
      (usually by Euclidean distance)

   b) UPDATE STEP:
      recompute each centroid as the MEAN of all points now assigned to it

3. STOP when centroids no longer move (or move less than a tolerance,
   or a maximum iteration count is reached)

OUTPUT: k clusters with their centroids

Objective Function

K-Means minimises the Within-Cluster Sum of Squares (WCSS), also called inertia:

              k
   WCSS  =   Σ    Σ    || x − μᵢ ||²
             i=1  x∈Cᵢ

   Cᵢ = the i-th cluster,  μᵢ = its centroid

Each iteration is guaranteed to reduce (or keep constant) the WCSS, which is why the algorithm always converges — though possibly to a local minimum, not the global one.

Worked Example — By Hand

Data points: A(2,10), B(2,5), C(8,4), D(5,8), E(7,5), F(6,4), G(1,2), H(4,9) Let k = 3 with initial centroids μ₁ = A(2,10), μ₂ = D(5,8), μ₃ = G(1,2).

Iteration 1 — Assignment step (Euclidean distances):

Pointd to μ₁(2,10)d to μ₂(5,8)d to μ₃(1,2)Cluster
A(2,10)0.003.618.061
B(2,5)5.004.243.163
C(8,4)8.495.007.282
D(5,8)3.610.007.212
E(7,5)7.073.616.712
F(6,4)7.214.125.392
G(1,2)8.067.210.003
H(4,9)2.241.417.622
Sample calculation for B(2,5) to μ₂(5,8):
   d = √[(2−5)² + (5−8)²] = √[9 + 9] = √18 = 4.243

Cluster 1: {A}
Cluster 2: {C, D, E, F, H}
Cluster 3: {B, G}

Iteration 1 — Update step:

μ₁ = mean{A}          = (2, 10)

μ₂ = mean{C,D,E,F,H}  = ((8+5+7+6+4)/5, (4+8+5+4+9)/5)
                      = (30/5, 30/5) = (6, 6)

μ₃ = mean{B,G}        = ((2+1)/2, (5+2)/2) = (1.5, 3.5)

Iteration 2 — Assignment step with new centroids μ₁(2,10), μ₂(6,6), μ₃(1.5,3.5):

Pointd to μ₁d to μ₂d to μ₃Cluster
A(2,10)0.005.666.521
B(2,5)5.004.121.583
C(8,4)8.492.836.522
D(5,8)3.612.245.702
E(7,5)7.071.415.702
F(6,4)7.212.004.532
G(1,2)8.066.401.583
H(4,9)2.243.616.041
Cluster 1: {A, H}          <- H moved from cluster 2 to cluster 1
Cluster 2: {C, D, E, F}
Cluster 3: {B, G}

New centroids:
   μ₁ = ((2+4)/2, (10+9)/2) = (3, 9.5)
   μ₂ = ((8+5+7+6)/4, (4+8+5+4)/4) = (6.5, 5.25)
   μ₃ = (1.5, 3.5)              [unchanged]

Iteration 3 produces no further reassignments → converged.

FINAL RESULT:
   Cluster 1: {A(2,10), H(4,9)}          centroid (3, 9.5)
   Cluster 2: {C(8,4), D(5,8), E(7,5), F(6,4)}   centroid (6.5, 5.25)
   Cluster 3: {B(2,5), G(1,2)}           centroid (1.5, 3.5)

Choosing k — The Elbow Method

Plot WCSS against k. WCSS always falls as k rises (at k = n, WCSS = 0), so look for the elbow — the point where the rate of decrease sharply flattens. That is the k beyond which extra clusters add little.

   WCSS
     │●
     │ ●
     │  ●
     │   ●___  <- ELBOW at k=3: adding more clusters barely helps
     │       ●___●___●___●
     └────────────────────── k
      1  2  3  4  5  6  7

Alternatives: the silhouette method (pick k maximising the average silhouette score), the gap statistic, and plain domain knowledge ("marketing wants exactly 4 segments").

Python

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.cluster import KMeans

# Reproducing the hand-worked example
points = np.array([[2,10],[2,5],[8,4],[5,8],[7,5],[6,4],[1,2],[4,9]])
labels_txt = list("ABCDEFGH")

km = KMeans(n_clusters=3, init="random", n_init=1,
            random_state=1, max_iter=100)
clusters = km.fit_predict(points)

for lbl, pt, c in zip(labels_txt, points, clusters):
    print(f"{lbl}{tuple(pt)} -> cluster {c}")
print("\nCentroids:\n", km.cluster_centers_.round(2))
print("Inertia (WCSS):", round(km.inertia_, 3))
print("Iterations to converge:", km.n_iter_)
# THE ELBOW METHOD on a realistic customer dataset
from sklearn.datasets import make_blobs
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import silhouette_score

X, _ = make_blobs(n_samples=400, centers=4, cluster_std=1.1, random_state=42)
X = StandardScaler().fit_transform(X)

wcss, silhouettes = [], []
k_range = range(2, 11)
for k in k_range:
    km = KMeans(n_clusters=k, n_init=10, random_state=42).fit(X)
    wcss.append(km.inertia_)
    silhouettes.append(silhouette_score(X, km.labels_))

fig, axes = plt.subplots(1, 2, figsize=(14, 4.5))

axes[0].plot(k_range, wcss, marker="o", color="#168B99", linewidth=2)
axes[0].set_xlabel("k"); axes[0].set_ylabel("WCSS (inertia)")
axes[0].set_title("Elbow Method"); axes[0].grid(alpha=0.3)

axes[1].plot(k_range, silhouettes, marker="s", color="#10b981", linewidth=2)
best_k = list(k_range)[int(np.argmax(silhouettes))]
axes[1].axvline(best_k, color="#ef4444", linestyle="--", label=f"best k = {best_k}")
axes[1].set_xlabel("k"); axes[1].set_ylabel("Silhouette score")
axes[1].set_title("Silhouette Method"); axes[1].legend(); axes[1].grid(alpha=0.3)

plt.tight_layout(); plt.show()
print(f"Elbow and silhouette both point to k = {best_k}")
# CUSTOMER SEGMENTATION — the flagship business application
np.random.seed(42)
customers = pd.DataFrame({
    "age":            np.concatenate([np.random.normal(25, 4, 60),
                                      np.random.normal(45, 6, 60),
                                      np.random.normal(38, 5, 60)]),
    "annual_income":  np.concatenate([np.random.normal(30000, 6000, 60),
                                      np.random.normal(90000, 15000, 60),
                                      np.random.normal(60000, 9000, 60)]),
    "spending_score": np.concatenate([np.random.normal(75, 10, 60),
                                      np.random.normal(35, 10, 60),
                                      np.random.normal(55, 10, 60)]),
})

features = ["age", "annual_income", "spending_score"]
X_scaled = StandardScaler().fit_transform(customers[features])   # scaling is essential

km = KMeans(n_clusters=3, n_init=10, random_state=42)
customers["segment"] = km.fit_predict(X_scaled)

profile = customers.groupby("segment")[features].mean().round(1)
profile["size"] = customers["segment"].value_counts().sort_index()
print(profile)
#            age  annual_income  spending_score  size
# segment
# 0         25.1        30169.5            74.6    60
# 1         44.9        89632.2            34.6    60
# 2         38.2        60113.9            55.3    60

print(f"\nSilhouette score: {silhouette_score(X_scaled, customers['segment']):.4f}")

# Business naming of the discovered segments
names = {0: "Young Big Spenders — target with trendy, affordable products",
         1: "Affluent but Frugal — target with premium value propositions",
         2: "Balanced Mid-Market — target with loyalty programmes"}
for seg, desc in names.items():
    print(f"Segment {seg}: {desc}")
# Visualising the segments
fig, axes = plt.subplots(1, 2, figsize=(14, 5))

sc = axes[0].scatter(customers["annual_income"], customers["spending_score"],
                     c=customers["segment"], cmap="viridis", s=50, alpha=0.75)
centers_orig = StandardScaler().fit(customers[features]).inverse_transform(km.cluster_centers_)
axes[0].scatter(centers_orig[:, 1], centers_orig[:, 2], marker="X", s=350,
                c="red", edgecolor="black", label="Centroids")
axes[0].set_xlabel("Annual Income"); axes[0].set_ylabel("Spending Score")
axes[0].set_title("Customer Segments"); axes[0].legend()

axes[1].scatter(customers["age"], customers["spending_score"],
                c=customers["segment"], cmap="viridis", s=50, alpha=0.75)
axes[1].set_xlabel("Age"); axes[1].set_ylabel("Spending Score")
axes[1].set_title("Age vs Spending")

plt.tight_layout(); plt.show()

K-Means++ Initialisation

Random initial centroids can produce poor local optima. k-means++ (scikit-learn's default, init="k-means++") spreads the initial centroids out: the first is random, and each subsequent one is chosen with probability proportional to its squared distance from the nearest existing centroid. This dramatically improves both quality and convergence speed.

Also set n_init=10 so the algorithm runs 10 times from different starts and keeps the best result by inertia.

Advantages and Disadvantages

AdvantagesDisadvantages
Simple to understand and implementk must be specified in advance
Fast and scalable — O(n·k·i·d), roughly linear in nSensitive to initial centroid placement (mitigated by k-means++)
Works well when clusters are spherical and similar-sizedAssumes convex, globular clusters — fails on crescents/rings
Guaranteed to convergeSensitive to outliers (the mean is not robust)
Easy to interpret — each cluster has a meaningful centroidRequires feature scaling
Works on large datasets (MiniBatchKMeans for huge ones)Struggles with clusters of very different sizes or densities
Poor performance in high dimensions (curse of dimensionality)

K-Means vs K-Medoids

K-MeansK-Medoids (PAM)
Cluster centreMean — a computed point, may not exist in the dataMedoid — an actual data point
Outlier sensitivityHighLow (robust)
Distance metricEuclidean (essentially required)Any metric
SpeedFastSlower

Applications

Customer segmentation, document/topic grouping, image compression (colour quantisation to k colours), delivery-zone planning, sensor/IoT grouping, anomaly detection (points far from every centroid), and numerosity reduction in preprocessing.

The next lesson covers the alternative that does not require you to pick k in advance: hierarchical clustering.