Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Clustering Validation Metrics

Source

Chapter 17 of Zaki & Meira (2020): Clustering Validation

Clustering Validation Metrics

Introduction to Clustering Validation

Clustering validation helps assess the quality of clustering results. There are three main types of validation measures:

  1. External measures: Use external information like ground truth labels

  2. Internal measures: Use only the data and clustering results

  3. Relative measures: Compare different clusterings

External Measures

External measures assume that the correct or ground-truth clustering is known a priori, which is used to evaluate a given clustering.

Let D={xi}i=1n\mathbf{D} = \{\mathbf{x}_i\}_{i=1}^n be a dataset consisting of nn points in a dd-dimensional space, partitioned into kk clusters. Let yi∈{1,2,…,k}y_i \in \left\{ 1, 2,\ldots , k \right\} denote the ground-truth cluster membership or label information for each point.

The ground-truth clustering is given as:

T={T1,T2,…,Tk}\mathbf{T} = \left\{ T_1, T_2, \ldots, T_k \right\}

where the cluster TjT_{j} consists of all the points with label jj, i.e.,

Tj={xi∈D∣yi=j}T_{j} = \left\{ \mathbf{x}_i \in \mathbf{D} \mid y_i = j \right\}

We refer to T\mathbf{T} as the ground-truth partitioning, and to each TiT_i as a partition (class).

Let C={C1,…,Cr}\mathbf{C} = \{C_1, \ldots, C_r\} denote a clustering of the same dataset into rr clusters, obtained via some clustering algorithm, and let y^i∈{1,2,…,r}\hat{y}_i \in \left\{ 1, 2, \ldots, r \right\} denote the cluster label for xi\mathbf{x}_i.

External evaluation measures try to capture the extent to which points from the same partition appear in the same cluster, and the extent to which points from different partitions are grouped in different clusters.

All of the external measures rely on the r×kr \times k contingency table N\mathbf{N} that is induced by a clustering C\mathbf{C} and the ground-truth partitioning T\mathbf{T}, defined as follows:

N(i,j)=nij=∣Ci∩Tj∣\mathbf{N}(i,j) = n_{ij} = \left| C_i \cap T_{j} \right|

The count nijn_{ij} denotes the number of points that are common to cluster CiC_i and ground-truth partition TjT_{j}.

Let ni=∣Ci∣n_{i} = |C_i| denote the number of points in cluster CiC_i, and let mj=∣Tj∣m_{j} = |T_{j}| denote the number of points in partition TjT_{j}. The contingency table can be computed from T\mathbf{T} and C\mathbf{C} in O(n)O(n) time by examining the partition and cluster labels, yiy_i and y^i\hat{y}_i, for each point xi∈D\mathbf{x}_i \in \mathbf{D} and incrementing the corresponding count nyiy^in_{y_i\hat{y}_i}.

Contingency Table can be computed as follows:

[[1 0 1]
 [1 1 0]
 [0 1 1]]

Efficient Contingency Table Computation with np.add.at

When implementing clustering validation metrics, constructing a contingency table is a key step. One efficient way to do this in NumPy is using np.add.at. This method ensures correct accumulation of counts when duplicate indices are present.

Why np.add.at?

Unlike simple indexed incrementation, such as:

contingency_table[cluster_map, class_map] += 1

NumPy’s fancy indexing creates a temporary copy, meaning updates to repeated indices may not accumulate correctly. Instead, np.add.at ensures in-place modifications:

np.add.at(contingency_table, (cluster_map, class_map), 1)

Example: Difference Between Direct Indexing and np.add.at

Here’s an example illustrating how np.add.at correctly handles duplicate indices while direct indexing may fail:

Using direct indexing:
[[1 0 0]
 [1 1 0]
 [0 0 1]]

Using np.add.at:
[[1 0 0]
 [1 3 0]
 [0 0 4]]

1. Purity

Purity measures how “pure” each cluster is by the fraction of points from the majority class:

purity=1n∑i=1rmax⁡j=1k{nij}\text{purity} = \frac{1}{n}\sum_{i=1}^r \max_{j=1}^k \{n_{ij}\}

Where:

  • n n = total number of points

  • r r = number of clusters

  • k k = number of true classes

  • nij n_{ij} = number of points in cluster i i from class j j

Let’s consider an example where we achieve high purity, ensuring our clustering is well-aligned with the ground-truth partitioning.

Example Data

Suppose we have the following clustering and ground-truth labels:

true_labels    = np.array([0, 0, 1, 1, 1, 2])
cluster_labels = np.array([0, 0, 1, 1, 2, 2])

Here, each cluster aligns better with a single true class.

Compute Contingency Table

Using the compute_contingency_table function, we obtain:

Class 0Class 1Class 2
C₀200
C₁020
C₂011

Middle Computations

Step 1: Find the max of each row:

  • max(C₀) = 2

  • max(C₁) = 2

  • max(C₂) = 1

Step 2: Sum the max values: ∑max⁡(nij)=2+2+1=5 \sum \max(n_{ij}) = 2 + 2 + 1 = 5

Step 3: Compute purity: Purity=∑max⁡(nij)∑nij=56≈0.833 \text{Purity} = \frac{\sum \max(n_{ij})}{\sum n_{ij}} = \frac{5}{6} \approx 0.833

Purity Score: 0.833

purity is invariant to cluster labels, meaning that relabeling clusters doesn’t change the score as long as the assignment itself remains consistent.

Let’s take the previous clustering but shift the cluster labels (e.g., renaming clusters C₀ →\rightarrow C₂, C₁ →\rightarrow C₀, C₂ →\rightarrow C₁):

Original Purity: 0.833
Shifted Labels Purity: 0.833

Why Does Purity Stay the Same?

  • The mapping between clusters and ground-truth partitions hasn’t changed, only their labels.

  • Since purity only depends on how well clusters match ground-truth partitions, renaming clusters doesn’t affect purity calculations.

Precision and Recall

For each cluster Ci C_i :

  • Precision: Fraction of points in cluster from majority class

  • Recall: Fraction of points from class found in cluster

Precision

Given cluster CiC_i, let jij_i denote the partition that contains the maximum number of points from CiC_i, that is, ji=max⁡j=1k{nij}j_i = \max_{j=1}^k \{ n_{ij} \}. The precision of a cluster CiC_i is the same as its purity:

preci=1nimax⁡j=1k{nij}=nijini\mathit{prec}_i = \frac{1}{n_{i}}\max_{j=1}^k \left\{ n_{ij} \right\} = \frac{n_{ij_i}}{n_i}

Recall

The recall of cluster CiC_i is defined as:

recalli=niji∣Tji∣=nijimji\mathit{recall}_i = \frac{n_{ij_i}}{|\mathbf{T}_{j_i}|} =\frac{n_{ij_i}}{m_{j_i}}

where mji=∣Tji∣m_{j_i} = |\mathbf{T}_{j_i}|.

F-Measure

The F-measure is the harmonic mean of the precision and recall values for each CiC_i:

Fi=21preci+1recalli=2⋅preci⋅recallipreci+recalli=2  nijini+mjiF_i = \frac{2}{\frac{1}{\mathit{prec}_i} + \frac{1}{\mathit{recall}_i}} = \frac{2 \cdot \mathit{prec}_i \cdot \mathit{recall}_i}{\mathit{prec}_i + \mathit{recall}_i} = \frac{2 \; n_{ij_i}}{n_{i} + m_{j_i}}

The F-measure for the clustering C\mathbf{C} is the mean of clusterwise F-measure values:

F=1r∑i=1rFiF = \frac{1}{r} \sum_{i=1}^r F_i

2. Maximum Matching

Finds the best matching between clusters and true classes to maximize overlap:

match=1nmax⁡M∑(i,j)∈Mnij\text{match} = \frac{1}{n}\max_M \sum_{(i,j)\in M} n_{ij}

Where M M is a matching between clusters and classes.

Maximum Weight Matching based on Zaki & Meira (2020)

Let GG be a bipartite graph over the vertex set V=C∪TV = \mathbf{C} \cup \mathbf{T}, and let the edge set be E={(Ci,Tj)}E = \{ (C_i, T_{j}) \} with edge weights w(Ci,Tj)=nijw(C_i,T_{j}) = n_{ij}. A matching MM in GG is a subset of EE, such that the edges in MM are pairwise nonadjacent, meaning they do not have a common vertex.

The maximum weight matching in GG is given as:

match=arg⁡max⁡M{w(M)n}\mathit{match} = \arg \max_M \left\{ \frac{w(M)}{n} \right\}

where w(M)w(M) is the sum of all edge weights in matching MM, given as w(M)=∑e∈Mw(e)w(M) = \sum_{e \in M} w(e).

Maximum Matching Example (Bipartite Graph Interpretation)

Scenario: Imagine we have clustered 10 fruits into 3 clusters, and we know their true categories (ground truth):

True Categories (T₁=apples, T₂=oranges, T₃=bananas):
T₁: apple1, apple2, apple3, apple4
T₂: orange1, orange2, orange3
T₃: banana1, banana2, banana3

Cluster Assignments (C₁, C₂, C₃):
C₁: [apple1, apple2, banana1]
C₂: [orange1, orange2, apple3] 
C₃: [banana2, banana3, orange3, apple4]

Contingency Table

Apples (T₁)Oranges (T₂)Bananas (T₃)Total (nᵢ)
C₁ (apple1, apple2, banana1)2013
C₂ (orange1, orange2, apple3)1203
C₃ (banana2, banana3, orange3, apple4)1124
Total (mⱼ)433n = 10

Step 1: Build the Bipartite Graph

We create edges between clusters and categories with weights equal to their overlap:

Clusters       Categories
   C₁ ------------- T₁ (weight=2: apple1,apple2)
      \------------ T₃ (weight=1: banana1)
   C₂ ------------- T₁ (weight=1: apple3)
      \------------ T₂ (weight=2: orange1,orange2)
   C₃ ------------- T₁ (weight=1: apple4)
      \------------ T₂ (weight=1: orange3)
      \------------ T₃ (weight=2: banana2,banana3)

Step 2: Find Maximum Weight Matching

We want to match each cluster to at most one category (and vice versa) to maximize total weight:

Possible matching options:

  1. Match C₁-T₁ (2), C₂-T₂ (2), C₃-T₃ (2) →\rightarrow Total weight = 6

  2. Match C₁-T₃ (1), C₂-T₂ (2), C₃-T₁ (1) →\rightarrow Total weight = 4

The first option gives us the maximum weight matching.

Step 3: Calculate Maximum Matching Score

match=Total matched weightn=2+2+210=0.6\text{match} = \frac{\text{Total matched weight}}{n} = \frac{2+2+2}{10} = 0.6
Optimal matching: [(0, 0), (1, 1), (2, 2)]
Max weight: 6

Example 17.1: Good vs Bad Clustering on Iris Data

Let’s examine two different clusterings of the Iris dataset using principal components:

Good Clustering Results:

Contingency Table

iris-setosairis-versicoloriris-virginicaTotal (n_i)
C₁ (squares)0471461
C₂ (circles)500050
C₃ (triangles)033639
Total (m_j)505050n = 100

Purity: 0.887
Match: 0.887
F: 0.885

Bad Clustering Results:

Contingency Table

iris-setosairis-versicoloriris-virginicaTotal (n_i)
C₁ (squares)300030
C₂ (circles)204024
C₃ (triangles)0465096
Total (m_j)505050n = 150

Purity: 0.667
Match: 0.560
F: 0.658

4. Pairwise Measures

First, let’s define some key quantities used in external measures:

  • TP (True Positives): Pairs in same cluster and same true partition

  • FP (False Positives): Pairs in same cluster but different partitions

  • TN (True Negatives): Pairs in different clusters and different partitions

  • FN (False Negatives): Pairs in different clusters but same partition

Given clustering C\mathbf{C} and ground-truth partitioning T\mathbf{T}, let xi,xj∈D\mathbf{x}_i, \mathbf{x}_{j} \in \mathbf{D} be any two points, with i≠ji \ne j. Let yiy_i denote the true partition label and let y^i\hat{y}_i denote the cluster label for point xi\mathbf{x}_i.

If both xi\mathbf{x}_i and xj\mathbf{x}_{j} belong to the same cluster, that is, y^i=y^j\hat{y}_i = \hat{y}_{j}, we call it a positive event, and if they do not belong to the same cluster, that is, y^i≠y^j\hat{y}_i \ne \hat{y}_{j}, we call that a negative event. Depending on whether there is agreement between the cluster labels and partition labels, there are four possibilities to consider:

True Positives (TP)
xi\mathbf{x}_i and xj\mathbf{x}_{j} belong to the same partition in T\mathbf{T}, and they are also in the same cluster in C\mathbf{C}. The number of true positive pairs is given as:

TP=∣{(xi,xj):  yi=yj and y^i=y^j}∣\mathit{TP} = \bigl|\{(\mathbf{x}_i, \mathbf{x}_{j}):\; y_i = y_{j} \text{ and } \hat{y}_i = \hat{y}_{j} \}\bigr|

False Negatives (FN)
xi\mathbf{x}_i and xj\mathbf{x}_{j} belong to the same partition in T\mathbf{T}, but they do not belong to the same cluster in C\mathbf{C}. The number of false negative pairs is given as:

FN=∣{(xi,xj):  yi=yj and y^i≠y^j}∣\mathit{FN} = \bigl|\{(\mathbf{x}_i, \mathbf{x}_{j}):\; y_i = y_{j} \text{ and } \hat{y}_i \ne \hat{y}_{j} \}\bigr|

False Positives (FP)
xi\mathbf{x}_i and xj\mathbf{x}_{j} do not belong to the same partition in T\mathbf{T}, but they do belong to the same cluster in C\mathbf{C}. The number of false positive pairs is given as:

FP=∣{(xi,xj):  yi≠yj and y^i=y^j}∣\mathit{FP} = \bigl|\{(\mathbf{x}_i, \mathbf{x}_{j}):\; y_i \ne y_{j} \text{ and } \hat{y}_i = \hat{y}_{j} \}\bigr|

True Negatives (TN)
xi\mathbf{x}_i and xj\mathbf{x}_{j} neither belong to the same partition in T\mathbf{T}, nor do they belong to the same cluster in C\mathbf{C}. The number of such true negative pairs is given as:

TN=∣{(xi,xj):  yi≠yj and y^i≠y^j}∣\mathit{TN} = \bigl|\{(\mathbf{x}_i, \mathbf{x}_{j}):\; y_i \ne y_{j} \text{ and } \hat{y}_i \ne \hat{y}_{j} \}\bigr|

Jaccard Coefficient

Jaccard=TPTP+FN+FP\text{Jaccard} = \frac{TP}{TP + FN + FP}

Rand Index

Rand=TP+TNTP+FN+FP+TN=TP+TN(n2)\text{Rand} = \frac{TP + TN}{TP + FN + FP + TN} = \frac{TP + TN}{\binom{n}{2}}

Internal Validation Measures

Silhouette Coefficient

Measures how similar a point is to its own cluster compared to other clusters:

For each point xi x_i :

si=bi−aimax⁡(ai,bi)s_i = \frac{b_i - a_i}{\max(a_i, b_i)}

Where:

  • ai a_i = average distance to other points in same cluster

  • bi b_i = min average distance to points in another cluster

Overall score is average of all si s_i .

Python Example on Iris Dataset

Purity: 0.887
Rand Index: 0.716
Silhouette Score: 0.551
Jaccard: 0.682
Rand Index: 0.874

Interpretation of Results

  • Purity: Higher is better (max 1.0)

  • Rand Index: Higher is better (max 1.0)

  • Silhouette: Ranges from -1 to 1, higher is better

  • Jaccard: Higher is better (max 1.0)

Silhouetter Score: 0.551
0.6810461692117462 0
<Figure size 9000x4800 with 4 Axes>

Choosing the Right Metric

  1. Use external measures when ground truth is available

  2. Use internal measures when no labels exist

  3. Consider multiple metrics together for robust evaluation

Remember that no single metric tells the whole story - different metrics may be more appropriate for different clustering goals.

Further Reading

Here are several real-world examples of bipartite matching problems that help explain the concept of maximum matching in clustering validation, along with relevant links for further reading:

1. Stable Marriage Problem

Scenario: Matching medical students to hospital residency programs based on mutual preferences
Visualization:

Students   Hospitals
  A ──────── X (1st choice)
  B ──┬───── Y 
     └───── Z (tie)
  C ──────── Y (only option)

Key Insight: The Gale-Shapley algorithm finds stable pairings where no unmatched pair prefers each other over their current matches
Resource: Wikipedia - Stable Marriage Problem


2. Roommate Assignment

Scenario: Matching college students as roommates based on compatibility scores
Example Matrix:

       Clean | Social | Studious
Alice    5   |   3    |    2
Bob      2   |   4    |    1
Carol    4   |   1    |    5

Optimal Matching: Alice-Clean (5), Bob-Social (4), Carol-Studious (5) →\rightarrow Total=14
Resource: Roommate Assignment Algorithms


3. Thesis Advisor Matching

Scenario: Assigning graduate students to faculty advisors based on research interests
Connection: Similar to ensuring each cluster (advisor group) best represents one true category (research area)
Process Flow:

  1. Students rank advisors

  2. Advisors rank students

  3. Solve using maximum weight matching
    Visualization:

Students     Research Areas
  Sara ────┐
           ├── ML (Prof. Smith)
  John ────┘
  Emma ────── NLP (Prof. Lee)

Resource:


4. Image Feature Matching

Scenario: Matching keypoints between two images of the same object
Example:

Image A Features   Image B Features
   Corner X ─────────── Edge P (distance=1.2)
   Blob Y ──┬────────── Corner Q (distance=0.8)
           └────────── Blob R (distance=1.5)

Optimal Matching: X-P (1.2), Y-Q (0.8) →\rightarrow Total=2.0
Algorithm: Hungarian algorithm on distance matrix
Resource: OpenCV Feature Matching


5. Job Applicant Matching

Scenario: Assigning applicants to job openings based on skill fit
Matching Table:

Applicants  Jobs       Fit Score
  Amy ───── Data Sci ─── 0.9
  Sam ──┬── Engineer ── 0.7
        └── Analyst ── 0.8
  Zoe ───── Engineer ── 0.6

Maximum Matching: Amy-Data Sci (0.9), Sam-Analyst (0.8), Zoe-Engineer (0.6) →\rightarrow Total=2.3
Resource: LinkedIn Matching Algorithm


Comparative Summary Table

ApplicationCluster Validation AnalogyKey MetricCommon Algorithm
Stable MarriageCluster-to-class 1:1 mappingPreference satisfactionGale-Shapley
Roommate AssignmentIntra-cluster homogeneityCompatibility scoreHungarian Algorithm
Advisor MatchingAligning clusters with true labelsResearch overlapDeferred Acceptance
Image MatchingCross-cluster correspondenceFeature distanceRANSAC
Job ApplicantPurity maximizationSkill fit percentageLinear Sum Assignment

These examples demonstrate how maximum matching principles apply across domains while maintaining the core idea of optimal constrained assignments - exactly what we do when evaluating clustering quality against ground truth.

Maximum Cardinality Matching in Bipartite Graphs

Overview

This implementation finds the maximum cardinality matching in a bipartite graph using NetworkX. A bipartite graph is a graph whose nodes can be split into two disjoint sets such that no two nodes within the same set are adjacent.

Key Components:

  • bipartite.sets(G): Extracts the two partitions (left and right nodes).

  • max_weight_matching(G): Finds the optimal matching where each node is paired with at most one other node.

Code Implementation

Maximum Matching: {('y', 'a'), ('x', 'c')}
<Figure size 3600x2400 with 1 Axes>

Contingency Table

iris-setosairis-versicoloriris-virginicaTotal (n_i)
C₁ (squares)300030
C₂ (circles)204024
C₃ (triangles)0465096
Total (m_j)505050n = 150

Purity: 0.667
Match: 0.560

Maximum Matching with Edge Weights:
(C₁, iris-setosa) -> Weight: 30
(iris-virginica, C₃) -> Weight: 50
(C₂, iris-versicolor) -> Weight: 4
<Figure size 3600x2400 with 1 Axes>
<Figure size 4800x3300 with 1 Axes>
References
  1. Zaki, M. J., & Meira, W. (2020). Data Mining and Machine Learning: Fundamental Concepts and Algorithms (2nd ed.). Cambridge University Press. https://www.cambridge.org/9781108497367