Clustering: k-Means, Hierarchical, DBSCAN
1874 words
9 min read
Visual companion
Python
Type and operator map
Python Week 1: the first filter for runtime behavior
View
Revision summary
What this note is really saying
Short form
# Clustering: k-Means, Hierarchical, DBSCAN ## 🎯 Learning Objectives - Explain unsupervised learning and when clustering is appropriate - Implement and interpret k-means clustering with proper k selection - Understand hierarchical clustering and dendrogram interpretation - Use DBSCAN for density-based clustering wi...

Clustering: k-Means, Hierarchical, DBSCAN
🎯 Learning Objectives
- Explain unsupervised learning and when clustering is appropriate
- Implement and interpret k-means clustering with proper k selection
- Understand hierarchical clustering and dendrogram interpretation
- Use DBSCAN for density-based clustering with arbitrary shapes
- Evaluate cluster quality using internal and external metrics
📋 Prerequisites
- Unsupervised learning concepts — learning without labels
- Distance metrics — Euclidean, Manhattan (from k-NN)
- Basic statistics — mean, variance, standard deviation
📖 Core Content
9.1 Intuition: Finding Natural Groups Without a Teacher
Imagine you're given a pile of music tracks and asked to organize them. You don't know the genres ahead of time, but you notice some are fast and loud, others are slow and melodic, and some have acoustic guitar. Clustering discovers these groupings automatically — it finds structure in unlabeled data.
(Diagram)
Applications:
- Customer segmentation (marketing)
- Image compression (color quantization)
- Anomaly detection (outliers are tiny clusters)
- Recommendation systems (cluster users, recommend within cluster)
9.2 k-Means Clustering
Algorithm:
- Choose k (number of clusters)
- Initialize k centroids (randomly or with k-means++)
- Repeat until convergence:
- Assignment step: Assign each point to nearest centroid
- Update step: Recompute centroid as mean of assigned points Objective (Inertia):
Where μc(i) is the centroid assigned to point x(i). Minimizing this makes clusters compact.
python# runnable from sklearn.cluster import KMeans from sklearn.datasets import make_blobs import matplotlib.pyplot as plt import numpy as np # Generate synthetic data X, _ = make_blobs(n_samples=300, centers=4, cluster_std=1.0, random_state=42) # Fit k-means kmeans = KMeans(n_clusters=4, random_state=42, n_init=10) kmeans.fit(X) print(f"Inertia: {kmeans.inertia_:.2f}") print(f"Centroids:\n{kmeans.cluster_centers_}") print(f"Labels: {kmeans.labels_[:10]}") # Visualize plt.scatter(X[:, 0], X[:, 1], c=kmeans.labels_, cmap='viridis', alpha=0.7) plt.scatter(kmeans.cluster_centers_[:, 0], kmeans.cluster_centers_[:, 1], marker='X', s=200, c='red', label='Centroids') plt.title('k-Means Clustering') plt.legend() plt.grid(True) plt.show()
9.2.1 Worked Example 1: k-Means by Hand
Points: A(1,2), B(2,1), C(4,4), D(5,5), E(8,1), F(9,2). k=2.
Step 1: Initialize centroids: μ₁ = A(1,2), μ₂ = E(8,1).
Step 2: Assign each point to nearest centroid.
| Point | Dist to μ₁ | Dist to μ₂ | Assign to |
|---|---|---|---|
| A(1,2) | 0 | √[(1-8)²+(2-1)²] = √50 ≈ 7.07 | 1 |
| B(2,1) | √[(2-1)²+(1-2)²] = √2 ≈ 1.41 | √[(2-8)²+(1-1)²] = 6 | 1 |
| C(4,4) | √[(4-1)²+(4-2)²] = √13 ≈ 3.61 | √[(4-8)²+(4-1)²] = 5 | 1 |
| D(5,5) | √[(5-1)²+(5-2)²] = 5 | √[(5-8)²+(5-1)²] = 5 | 1 (tie, arbitrarily) |
| E(8,1) | √50 ≈ 7.07 | 0 | 2 |
| F(9,2) | √[(9-1)²+(2-2)²] = 8 | √[(9-8)²+(2-1)²] = √2 ≈ 1.41 | 2 |
Step 3: Update centroids.
- μ₁ = mean(A, B, C, D) = mean([1,2,4,5], [2,1,4,5]) = (3, 3)
- μ₂ = mean(E, F) = mean([8,9], [1,2]) = (8.5, 1.5) Step 4: Reassign. Now check distances.
| Point | Dist to μ₁(3,3) | Dist to μ₂(8.5,1.5) | Assign |
|---|---|---|---|
| A(1,2) | √5 ≈ 2.24 | √[(1-8.5)²+(2-1.5)²] ≈ √56.5 ≈ 7.52 | 1 |
| B(2,1) | √5 ≈ 2.24 | √[(2-8.5)²+(1-1.5)²] ≈ √42.5 ≈ 6.52 | 1 |
| C(4,4) | √2 ≈ 1.41 | √[(4-8.5)²+(4-1.5)²] ≈ √26.5 ≈ 5.15 | 1 |
| D(5,5) | √8 ≈ 2.83 | √[(5-8.5)²+(5-1.5)²] ≈ √24.5 ≈ 4.95 | 1 |
| E(8,1) | √29 ≈ 5.39 | √0.5 ≈ 0.71 | 2 |
| F(9,2) | √37 ≈ 6.08 | √0.5 ≈ 0.71 | 2 |
No changes in assignment → converged!
Result: Cluster 1: {A, B, C, D}, Cluster 2: {E, F}.
9.2.2 Choosing k: The Elbow Method
python# runnable import numpy as np import matplotlib.pyplot as plt from sklearn.cluster import KMeans # Calculate inertia for k=1 to k=10 inertias = [] K = range(1, 11) for k in K: kmeans = KMeans(n_clusters=k, random_state=42, n_init=10) kmeans.fit(X) inertias.append(kmeans.inertia_) plt.plot(K, inertias, 'bo-') plt.xlabel('k') plt.ylabel('Inertia') plt.title('Elbow Method for Optimal k') plt.grid(True) plt.show()
The "elbow" is where adding more clusters gives diminishing returns (similar to PCA's scree plot).
9.3 Hierarchical Clustering
Builds a hierarchy of clusters via:
- Agglomerative (bottom-up): Start with each point as its own cluster, merge closest pairs
- Divisive (top-down): Start with all points in one cluster, split recursively
python# runnable from sklearn.cluster import AgglomerativeClustering from scipy.cluster.hierarchy import dendrogram, linkage import matplotlib.pyplot as plt # Agglomerative clustering hc = AgglomerativeClustering(n_clusters=4, linkage='ward') hc.fit(X) print(f"Hierarchical labels: {hc.labels_}") # Dendrogram Z = linkage(X, method='ward') plt.figure(figsize=(10, 5)) dendrogram(Z) plt.title('Dendrogram (Ward Linkage)') plt.ylabel('Distance') plt.show()
Linkage Criteria:
| Linkage | Distance Between Clusters | Tendency |
|---|---|---|
| Single | Minimum pairwise distance | Chaining, finds elongated clusters |
| Complete | Maximum pairwise distance | Compact clusters |
| Average | Average pairwise distance | Between single and complete |
| Ward | Variance increase when merging | Produces compact, spherical clusters |
9.4 DBSCAN: Density-Based Spatial Clustering
DBSCAN finds clusters as dense regions separated by sparse regions. It doesn't require specifying k and can find arbitrarily-shaped clusters.
Parameters:
eps: Maximum distance between two points to be considered neighborsmin_samples: Minimum points to form a dense region Point Types:- Core point: ≥ min_samples neighbors within eps
- Border point: < min_samples neighbors, but reachable from a core point
- Noise point: Not reachable from any core point
python# runnable from sklearn.cluster import DBSCAN from sklearn.datasets import make_moons # Non-spherical data X, _ = make_moons(n_samples=200, noise=0.05, random_state=42) # k-means fails on this kmeans = KMeans(n_clusters=2, random_state=42) print(f"k-means on moons: {kmeans.fit_predict(X)}") # DBSCAN succeeds db = DBSCAN(eps=0.3, min_samples=5) db_labels = db.fit_predict(X) print(f"DBSCAN labels: {db_labels}") print(f"Number of clusters found: {len(set(db_labels)) - (1 if -1 in db_labels else 0)}") print(f"Noise points: {list(db_labels).count(-1)}")
9.5 Cluster Evaluation
Internal Metrics (no ground truth):
| Metric | Formula | Range | Better |
|---|---|---|---|
| Silhouette Score | max(a,b)b−a | [-1, 1] | Higher (well-separated) |
| Davies-Bouldin | k1∑maxi=jdijsi+sj | [0, ∞) | Lower |
| Inertia | $\sum\ | x-\mu\ | ^2$ |
Where a = mean intra-cluster distance, b = mean nearest-cluster distance.
External Metrics (with ground truth labels):
| Metric | Measures |
|---|---|
| Adjusted Rand Index (ARI) | Pairwise agreement, chance-adjusted |
| Normalized Mutual Info (NMI) | Mutual information between true and predicted labels |
| Homogeneity | Each cluster contains only one class |
| Completeness | All members of a class assigned to same cluster |
9.6 When to Use / Not Use
| Algorithm | Best For | Limitations |
|---|---|---|
| k-Means | Spherical clusters, large data, equally sized clusters | Assumes spherical clusters, sensitive to initialization |
| Hierarchical | Small data, any shape, want dendrogram | O(m²), doesn't scale |
| DBSCAN | Arbitrary shapes, noisy data, outliers | Varying density clusters, high dimensions |
📐 Key Formulas / Concepts
| Concept | Formula | Notes |
|---|---|---|
| k-Means Inertia | $\sum_{i=1}^{m} \ | x^{(i)} - \mu_{c^{(i)}}\ |
| Silhouette Score | max(a,b)b−a | Measures cluster separation |
| DBSCAN Core | ≥ min_samples neighbors within eps | Dense region |
| Linkage (Ward) | Minimizes within-cluster variance | Merge clusters with smallest variance increase |
⚠️ Common Pitfalls
Pitfall 1: Wrong k in k-Means
The mistake: Choosing k arbitrarily without the elbow method or silhouette analysis.
Fix: Always use elbow + silhouette to determine k. If they disagree, consider domain knowledge.
Pitfall 2: Inconsistent Scales
The mistake: Not scaling features before clustering.
Why: All clustering algorithms use distance — features with larger ranges dominate.
Fix: Standardize features to have zero mean and unit variance.
Pitfall 3: k-Means on Non-Spherical Data
The mistake: Using k-means on moon-shaped or concentric datasets.
Why: k-means assumes spherical clusters of similar size. It can't separate interlocking moons.
Fix: Use DBSCAN for density-based clusters or spectral clustering for complex shapes.
📝 Practice Questions
Q1: Compute the 2nd step of k-means for points (0,0), (2,0), (5,5), (7,5) with k=2, centroids at (0,0) and (7,5).Assignment:
- (0,0) → 0 (dist=0 vs √74=8.6)
- (2,0) → 0 (dist=2 vs √[25+25]=√50=7.07)
- (5,5) → 1 (dist=√50=7.07 vs √[4+0]=2)
- (7,5) → 1 (dist=√74=8.6 vs 0)
Update:
- μ₀ = mean([0,0], [2,0]) = (1, 0)
- μ₁ = mean([5,5], [7,5]) = (6, 5) Q2: What does a silhouette score close to 1 mean?
Each point is much closer to its own cluster than to the nearest other cluster. The clusters are dense and well-separated. By contrast, a score near 0 means clusters overlap, and negative means many points are assigned to the wrong cluster. Q3: Why does DBSCAN label some points as -1 (noise)?Points labeled -1 cannot be reached from any core point within ε distance. They're in sparse regions that don't meet the density threshold. These are outliers that don't belong to any cluster. Q4: When would you use hierarchical over k-means?
- You want a dendrogram — visualize the hierarchy at all levels
- You don't know k — the dendrogram helps choose k post-hoc
- Small dataset (< 2000 points) — hierarchical is O(m²)
- Non-spherical clusters — with single/complete linkage Q5: What is k-means++ initialization?
Standard random initialization can lead to poor convergence. k-means++ spreads out initial centroids:
- Pick first centroid randomly
- For each point, compute distance to nearest centroid
- Pick next centroid with probability proportional to distance²
- Repeat until k centroids chosen
This leads to better, faster convergence. It's the default in sklearn (init='k-means++'). Q6: Implement k-means with silhouette score to find optimal k.pythonfrom sklearn.cluster import KMeans from sklearn.metrics import silhouette_score import numpy as np best_k, best_score = 2, -1 for k in range(2, 11): kmeans = KMeans(n_clusters=k, random_state=42, n_init=10) labels = kmeans.fit_predict(X) score = silhouette_score(X, labels) print(f"k={k}: silhouette = {score:.3f}") if score > best_score: best_k, best_score = k, score print(f"Best k = {best_k} with silhouette = {best_score:.3f}")Q7: What happens in DBSCAN if eps is too small or too large?
- eps too small: Most points become noise (-1), clusters fragment into tiny groups
- eps too large: Clusters merge together, most points become core points of one big cluster
The optimal eps creates enough density within clusters while keeping them separated. Use the k-distance plot to find the optimal eps value. Q8: Compare centroid-based vs density-based clustering.
| Aspect | k-Means (Centroid) | DBSCAN (Density) |
|---|---|---|
| Cluster shape | Spherical | Arbitrary shapes |
| Number of clusters | Must specify k | Auto-detected |
| Noise handling | Forces all points into clusters | Labels outliers as noise |
| Scalability | O(m·k·iter) | O(m²) with naive, O(m log m) with spatial index |
| Deterministic | No (initialization) | Yes (order-independent with good implementation) |
Q9: A customer segmentation has silhouette score 0.15. What does this indicate?0.15 is low — clusters are not well-separated. The customer segments likely overlap significantly. Possible issues:
- Wrong k (try different values)
- Features don't separate customers well (try different features or PCA first)
- Customers genuinely form a continuum, not discrete segments Q10: How would you cluster a dataset of 1 million customer records?
Use k-means (scalable) or Mini-Batch k-Means (even faster). Avoid hierarchical (O(m²)) and DBSCAN (O(m²) naive). Mini-Batch k-Means processes data in small batches, making it suitable for million-scale data with minimal quality loss.pythonfrom sklearn.cluster import MiniBatchKMeans mbk = MiniBatchKMeans(n_clusters=10, batch_size=1000, random_state=42)Q11: Explain single vs complete linkage in hierarchical clustering with an example.
- Single linkage: Distance between clusters = minimum distance between any point in A and any point in B. Creates long "chained" clusters (good for elongated shapes).
- Complete linkage: Distance = maximum distance between any point in A and any point in B. Creates compact clusters (good for spherical shapes).
Example: Two circular clusters connected by a thin line of points. Single linkage would merge them into one chain. Complete linkage would keep them separate (the line's nearest-endpoint distance is small, but its farthest is large). Q12: Your k-means gives different results each run. Why?k-means is sensitive to initialization. Different random starts produce different final clusters. To make it reproducible:
- Use
random_statein sklearn- Use k-means++ initialization (default)
- Run multiple restarts (n_init=10+) and keep the one with lowest inertia
sklearn's defaults handle this, but the variability is inherent to the algorithm.
🔗 Cross-References
- Next Topic: Dimensionality Reduction
- Related: k-Nearest Neighbors — distance metrics
- Related: PCA — pre-processing for clustering
- External: IITM BSCS2004 Week 9, Hands-On ML Ch. 9, ISLR Ch. 10 Join Discord PreviousSupport Vector MachinesNextDimensionality Reduction