The silhouette score measures, for each point, how well it fits its assigned cluster compared to the next-nearest cluster — combining both cohesion (tightness) and separation into a single number between -1 and +1.
Formula
\(a\) is the average distance from a point to every other point in its own cluster (cohesion — smaller is better). \(b\) is the average distance from the point to every point in the nearest other cluster (separation — larger is better). A score near \(+1\) means the point fits its own cluster well and is far from the next-nearest one; near \(0\) means it's roughly on the boundary between two clusters; negative means it's likely in the wrong cluster entirely.
Worked Example
Using the 6-point dataset with clusters \(\{(2,2),(2,4),(2,6)\}\) and \(\{(8,2),(8,4),(8,6)\}\), compute the silhouette for point \((2,2)\):
A silhouette of 0.539 for this point is solidly positive — it's noticeably closer to its own cluster's other members than to the nearest other cluster's members, indicating a reasonably good fit.
from sklearn.metrics import silhouette_score, silhouette_samples
import numpy as np
X = np.array([[2,2],[2,4],[2,6],[8,2],[8,4],[8,6]])
labels = np.array([0,0,0,1,1,1])
per_point_scores = silhouette_samples(X, labels)
print(per_point_scores[0]) # silhouette for point (2,2) -- matches the hand calculation closely
overall_score = silhouette_score(X, labels)
print(overall_score) # average across all points -- the single summary number
Reading the Overall Silhouette Score
| Score Range | Interpretation |
|---|---|
| 0.7 to 1.0 | Strong, well-separated clustering structure |
| 0.5 to 0.7 | Reasonable structure |
| 0.25 to 0.5 | Weak structure — clusters may be somewhat artificial |
| Below 0.25 | Little to no meaningful clustering structure found |
These are general guidelines, not hard cutoffs — always interpret alongside the business context and the human sanity check from Clustering Evaluation.
Using Silhouette Score to Choose k
from sklearn.cluster import KMeans
from sklearn.metrics import silhouette_score
for k in range(2, 6):
model = KMeans(n_clusters=k, random_state=42, n_init=10)
labels = model.fit_predict(X)
print(f"k={k}: silhouette = {silhouette_score(X, labels):.3f}")
# Unlike WCSS, silhouette score does NOT always favor more clusters --
# it naturally penalizes over-splitting, since separation (b) tends to shrink
This is a genuine advantage over WCSS/the elbow method: silhouette score doesn't monotonically improve with more clusters, so the \(k\) that maximizes it is often directly usable, without needing to visually judge an "elbow."
Practical Use Cases
- Directly choosing \(k\) by maximizing the silhouette score, as an alternative or complement to the elbow method
- Identifying individual poorly-fit points (via
silhouette_samples) that might be borderline or mislabeled
Common Mistakes
- Using silhouette score on very large datasets without considering its computational cost — it requires pairwise distances between many points, which can be expensive at scale.
- Picking the \(k\) with the single highest silhouette score without also checking whether that \(k\) makes practical, business sense.
Interview Relevance
Q: "What does a silhouette score close to 0 mean for a specific point?" The point is roughly equidistant between its assigned cluster and the next-nearest cluster — sitting right on the boundary, not clearly belonging to either, which often signals either an inherently ambiguous data point or a suboptimal choice of \(k\).
Practice Question
For a point with \(a=2\) and \(b=8\), compute its silhouette score, and explain what that value suggests about how well the point fits its assigned cluster.