Neural Mastery
← Practice
Silhouette Score for Cluster Qualityhard

Silhouette Score for Cluster Quality

hard Classical ML from Scratch 20–25 min

NumPy allowed · plain Python earns 10 bonus points

Implement the silhouette score -- the standard way to numerically evaluate a clustering (like K-Means' output) WITHOUT ground-truth labels, by checking whether every point is closer to its own cluster than to the nearest other one.

Task

Implement silhouette_score(X, labels): for every point, compute its mean in-cluster distance a(i) and its mean distance to the nearest other cluster b(i), combine them into s(i) = (b-a)/max(a,b), and return the mean s(i) across all points.

Function Signature

silhouette_score(X: list[list[float]], labels: list[int]) -> float

Examples

Example 1: Two Well-Separated Clusters
Input: X = [[1, 1], [1.5, 2], [8, 8], [8.5, 8.5]] labels = [0, 0, 1, 1]
Output: 0.9059559942468866
Explanation: Tight, far-apart clusters score close to the maximum of 1.0

Constraints

  • Use Euclidean distance between rows of X.
  • A point in a singleton cluster (no other members) gets s(i) = 0.0 by convention (this matches scikit-learn's own behavior).
  • Raise ValueError if labels contains fewer than 2 distinct clusters (silhouette is undefined for a single cluster).
  • The overall score is bounded in [-1, 1]: near +1 means well-separated clusters, near 0 means overlapping clusters, negative means points are likely in the wrong cluster.
Python3Saved ✓
import math

def silhouette_score(X: list[list[float]], labels: list[int]) -> float:
"""X: feature rows. labels: a cluster assignment per row. For each
point i: a(i) = mean Euclidean distance to other points in its own
cluster (0.0 if its cluster has no other members); b(i) = the
smallest mean Euclidean distance to any OTHER cluster's points;
s(i) = (b(i)-a(i)) / max(a(i), b(i)), or 0.0 if that max is 0, or 0.0
if i's own cluster has size 1. Return the mean of s(i) over all
points. Raise ValueError if fewer than 2 clusters are present."""
# Your implementation here
pass

Case 1: Two Well-Separated Clusters
Input: X = [[1, 1], [1.5, 2], [8, 8], [8.5, 8.5]] labels = [0, 0, 1, 1]
Expected: 0.9059559942468866