Skip to content
induwara.lk
induwara.lkAI · Clustering metrics

Silhouette Score Calculator

Paste your data points and cluster labels to get the silhouette score, the per-cluster means, and the full a(i)/b(i)/s(i) working for every point, with misassigned points flagged. Matches scikit-learn silhouette_score. No signup, nothing uploaded.

By Induwara AshinsanaUpdated Sep 30, 2026
Silhouette score calculator

One point per line. Coordinates separated by commas or spaces.

One label per point, same order. Numbers or names (e.g. 0, 0, 1).

Decimals
Examples
Silhouette score
0.9448
Strong structure

Clusters are dense and clearly separated. This labelling holds up well.

Points
4
Clusters
2
Dimensions
2
Flagged
0

Cross-check. The mean of all per-point s(i) is 0.9448; computed independently as the size-weighted average of the per-cluster means it is 0.9448. The two aggregations reconcile, as they must.

Per-cluster mean silhouette

ClusterSizeMean s(i)Reading
020.9289Strong structure
120.9606Strong structure

Per-sample silhouette

PointClustera(i)b(i)Nearests(i)
#100.50997.072510.9279
#200.50997.280110.9300
#310.28287.315700.9613
#410.28287.037000.9598

Worked formula for one point

Point #1 = [5, 2] · cluster 0 · euclidean (l2) distance
mean dist → cluster 0 = 0.5099= a(i)
mean dist → cluster 1 = 7.0725= b(i) (nearest)
s(i) = (b − a) / max(a, b) = (7.0725 − 0.5099) / 7.0725 = 0.9279

Method: a(i) = mean intra-cluster distance, b(i) = nearest other-cluster mean, s(i) = (b − a) / max(a, b); score = mean s(i). Matches scikit-learn silhouette_score. Sources: scikit-learn docs, Rousseeuw (1987), Kaufman & Rousseeuw (1990). Nothing leaves this page.

How it works

The silhouette score measures how well each point fits its assigned cluster compared with the nearest neighbouring cluster. It needs no ground-truth labels, which makes it an internal validity index, the standard way to judge a k-means, hierarchical, or DBSCAN result, and to compare different choices of k. It was introduced by Peter Rousseeuw in 1987 and is what scikit-learn returns from silhouette_score.

For a point i in cluster C, the tool computes three quantities:

  1. a(i), cohesion. The mean distance from i to every other point in its own cluster: a(i) = (1 / (|C| − 1)) · Σ d(i, j) over j in C, j ≠ i. Lower is better, the point is close to its cluster-mates.
  2. b(i), separation. For every other cluster C′, take the mean distance from i to all of its points, then keep the smallest: b(i) = min over C′ of (1 / |C′|) · Σ d(i, j). That nearest rival cluster is the one the point would most plausibly belong to instead.
  3. s(i), the silhouette. s(i) = (b(i) − a(i)) / max(a(i), b(i)), which falls between −1 and 1. Near 1 means the point is far from other clusters and snug in its own; near 0 means it sits on a boundary; negative means it is closer to a neighbour and probably misassigned. A point alone in its cluster gets s(i) = 0 by convention.

The overall score is the plain average of s(i) across all N points. Distance d(·,·) is Euclidean √(Σ (xₖ − yₖ)²) by default, matching scikit-learn, or Manhattan Σ |xₖ − yₖ| when selected. The computation is O(N²) in the number of points, which stays instant in the browser for the few-thousand-point inputs this tool accepts. As a credibility check it also recomputes the score a second way, as the size-weighted average of the per-cluster mean silhouettes, and confirms the two routes agree.

Reading the score: the four bands

The number itself is only half the answer. The bands below are the ones Kaufman and Rousseeuw published in Finding Groups in Data (1990) and the ones this tool labels your result with:

  • Above 0.70, strong structure. Clusters are dense and clearly separated. The labelling holds up.
  • 0.51 to 0.70, reasonable structure. A sensible clustering; the clusters are fairly well separated but not textbook-clean.
  • 0.26 to 0.50, weak, possibly artificial. Some structure, but the clusters are not well separated. Treat the grouping as a hypothesis.
  • 0.25 or below, no substantial structure. The clusters overlap so much that the labels carry almost no information.
  • Below 0, likely misassigned. The average point is nearer a neighbouring cluster than its own. Something is wrong with k, the metric, or the feature scaling.

The boundaries are continuous, so a value sitting exactly on one is resolved downwards: 0.70 reads as reasonable (only above 0.70 is strong) and 0.50 reads as weak (only 0.51 and up is reasonable). That matters more than it sounds, a great many published scores land in the 0.45–0.55 window, where the honest reading is “there is structure here, but do not build a decision on this k alone.”

Using the score to pick the number of clusters

Because silhouette needs no ground-truth labels, the usual workflow is to cluster the same data at k = 2, 3, 4, … , score each labelling, and keep the k with the highest score. Paste one labelling at a time here and write the numbers down; the third worked example below does exactly that on six 1-D points, where k = 3 (0.8764) clearly beats k = 2 (0.7513).

Two caveats are worth knowing. First, silhouette prefers compact, roughly spherical clusters, which gives it a mild bias towards small k, merging two genuine groups into one big blob can still score respectably. Second, a single average hides a lopsided result, so read the per-cluster means as well: one cluster at 0.85 and another at 0.15 averages to a comfortable-looking 0.50 that describes neither. Confirming the same k with a second internal index is cheap insurance, the Davies-Bouldin index (lower is better) and the Calinski-Harabasz index (higher is better) both take the same points-and-labels input. If you do have true labels to compare against, silhouette is the wrong tool entirely, use the adjusted Rand index, which measures agreement between two labellings rather than geometry.

Choosing the distance metric

The metric decides what “close” means, so it changes the score. Euclidean distance is the default here and in scikit-learn, and it is the right pairing for k-means, which itself minimises squared Euclidean distance. Manhattan distance sums absolute coordinate differences instead, which is less dominated by a single large gap and is often preferred for high-dimensional or sparse feature vectors. Whichever you pick, scale your features first: a column measured in rupees and a column measured as a 0–1 ratio are not comparable, and the rupee column will decide every distance on its own. Standardising each column to zero mean and unit variance before clustering is the usual fix, and it frequently moves the silhouette score more than the choice of metric does.

Edge cases, and where the score misleads

Four boundary cases are defined rather than left to float, and this tool follows scikit-learn on all of them. A point that is the only member of its cluster has no a(i) to compute, so it contributes s(i) = 0 by convention. A point whose nearest rival cluster is exactly as far away as its own cluster-mates has b(i) = a(i) and therefore s(i) = 0 exactly, genuinely on the fence, not an error. Duplicate coincident points can make max(a(i), b(i)) zero, which would divide by zero; that case also returns 0. And a labelling with fewer than two distinct clusters, or one where every cluster is a singleton, leaves the score undefined, the tool refuses the input with a message naming the problem instead of returning a silent NaN.

Two failure modes are not the formula's fault but are worth watching. A density-based result from DBSCAN or HDBSCAN can be entirely correct and still score badly, because crescent and ring-shaped clusters are not compact in the way silhouette rewards; a low score on non-convex shapes is evidence about the metric, not about your clustering. And DBSCAN's noise points, labelled −1, are not a cluster: leaving them in makes the tool treat the noise as a group and drags the score down, so drop those rows before pasting. Finally, the O(N²) pairwise pass means cost grows with the square of the sample count, past a few thousand points, estimating the score from a random subsample (scikit-learn's sample_size argument) is standard practice.

Worked examples

Every line below was computed by hand and then reconciled against this calculator and scikit-learn. All distances are Euclidean. Paste any of these into the tool above to see the same numbers in the per-sample table.

Well-separated clusters (1-D), points 1, 2, 10, 11; labels 0, 0, 1, 1

  1. Point 1 (cl 0): a = |1−2| = 1; b = mean(9, 10) = 9.5; s = 8.5/9.5 = 0.8947
  2. Point 2 (cl 0): a = 1; b = mean(8, 9) = 8.5; s = 7.5/8.5 = 0.8824
  3. Point 10 (cl 1): a = 1; b = 8.5; s = 0.8824
  4. Point 11 (cl 1): a = 1; b = 9.5; s = 0.8947
  5. Score = (0.8947 + 0.8824 + 0.8824 + 0.8947)/4 = 0.8885 → strong structure

One misassigned point (2-D), A = {(0,0),(0,1)}, B = {(0,2),(20,20)}

  1. (0,0) A: a = 1; b = mean(2, √800=28.2843) = 15.1421; s = 0.9340
  2. (0,1) A: a = 1; b = mean(1, √761=27.5862) = 14.2931; s = 0.9300
  3. (0,2) B: a = √724 = 26.9072; b = mean(2, 1) = 1.5; s = −0.9443 (flagged)
  4. (20,20) B: a = 26.9072; b = mean(28.2843, 27.5862) = 27.9353; s = 0.0368
  5. Score = (0.9340 + 0.9300 − 0.9443 + 0.0368)/4 = 0.2391 → weak / questionable

Single-point cluster (s = 0 convention), points 0, 0.5, 10; labels 0, 0, 1

  1. Point 0 (cl 0): a = 0.5; b = dist to 10 = 10; s = 9.5/10 = 0.9500
  2. Point 0.5 (cl 0): a = 0.5; b = 9.5; s = 9/9.5 = 0.9474
  3. Point 10 (cl 1): only member of its cluster ⇒ s = 0 (scikit-learn rule)
  4. Score = (0.9500 + 0.9474 + 0)/3 = 0.6325 → reasonable structure

Comparing k (1-D), points 1, 2, 8, 9, 20, 21 scored at k = 2 and k = 3

  1. k=2, labels 0,0,0,0,1,1, cluster 0 = {1,2,8,9}, cluster 1 = {20,21}
  2. pt 1: a = mean(1,7,8) = 5.3333; b = mean(19,20) = 19.5; s = 0.7265
  3. pt 2: a = mean(1,6,7) = 4.6667; b = mean(18,19) = 18.5; s = 0.7477
  4. pt 8: a = mean(7,6,1) = 4.6667; b = mean(12,13) = 12.5; s = 0.6267
  5. pt 9: a = mean(8,7,1) = 5.3333; b = mean(11,12) = 11.5; s = 0.5362
  6. pt 20: a = 1; b = mean(19,18,12,11) = 15; s = 0.9333
  7. pt 21: a = 1; b = mean(20,19,13,12) = 16; s = 0.9375
  8. score(k=2) = 0.7513 → strong, but cluster 0 averages only 0.6593
  9. k=3, labels 0,0,1,1,2,2, every cluster a pair, so a = 1 throughout
  10. s = 0.8667, 0.8462, 0.8462, 0.8667, 0.9130, 0.9200
  11. score(k=3) = 0.8764 → higher, and no cluster mean below 0.8564
  12. Verdict: k = 3 wins on both the overall score and the weakest cluster

Edge case: interleaved clusters give s = 0 and a negative score, points 0, 4, 2, 6; labels 0, 0, 1, 1

  1. Point 0 (cl 0): a = |0−4| = 4; b = mean(|0−2|, |0−6|) = 4 → b = a exactly
  2. s = (4 − 4)/max(4,4) = 0 ← on the fence, not an error
  3. Point 4 (cl 0): a = 4; b = mean(|4−2|, |4−6|) = 2; s = (2 − 4)/4 = −0.5000
  4. Point 2 (cl 1): a = |2−6| = 4; b = mean(|2−0|, |2−4|) = 2; s = −0.5000
  5. Point 6 (cl 1): a = 4; b = mean(|6−0|, |6−4|) = 4; s = 0
  6. Score = (0 − 0.5 − 0.5 + 0)/4 = −0.2500 → likely misassigned
  7. Reading: the two clusters interleave along the line, so half the points are
  8. nearer the rival cluster. A negative overall score is the labels' verdict,
  9. not a bug, no value of k fixes a labelling that cuts across the geometry.

Frequently asked questions

Sources & references

Related tools

Comments & feedback

Spotted a bug or want an improvement? Tell us, our team reviews every comment, and good ideas get built. Comments are public and anonymous.

Found a bug, edge case, or want to suggest an improvement?

Email me at [email protected] , most fixes ship within 24 hours.