Model / evaluate · All operations
Evaluate a clustering
A variant of Evaluate a model
Score a clustering by density, distance, entropy or agreement with known labels.
Status
- In arcform
Runs as a SQL step
arcform carries it as a
sql:step today.
The DuckDB step
Score a clustering by density, distance, entropy and agreement with known labels
-- ten points with the cluster a model gave each and the label they really carry
CREATE TEMP TABLE pts AS SELECT * FROM (VALUES
(1, 1.0, 1.0, 0, 'a'), (2, 1.2, 0.8, 0, 'a'), (3, 0.9, 1.1, 0, 'a'), (4, 1.1, 1.2, 0, 'a'), (5, 3.0, 3.0, 0, 'b'),
(6, 5.0, 5.0, 1, 'b'), (7, 5.2, 4.8, 1, 'b'), (8, 4.9, 5.1, 1, 'b'), (9, 5.1, 5.2, 1, 'b'), (10, 3.2, 3.0, 1, 'a')) t(id, x, y, cluster, label);
CREATE TEMP TABLE centroid AS SELECT cluster, avg(x) AS cx, avg(y) AS cy FROM pts GROUP BY cluster;
CREATE TEMP TABLE radial AS SELECT p.cluster, p.id, sqrt(power(p.x - c.cx, 2) + power(p.y - c.cy, 2)) AS r FROM pts p JOIN centroid c USING (cluster);
-- distance: within-cluster sum of squares, and the silhouette of every point (mean distance to the other clusters against its own)
CREATE TEMP TABLE silhouette AS
WITH d AS (SELECT a.id, a.cluster = b.cluster AS same, sqrt(power(a.x - b.x, 2) + power(a.y - b.y, 2)) AS dist FROM pts a JOIN pts b ON a.id <> b.id),
m AS (SELECT id, avg(dist) FILTER (WHERE same) AS a, avg(dist) FILTER (WHERE NOT same) AS b FROM d GROUP BY id)
SELECT avg((b - a) / greatest(a, b)) AS mean_silhouette FROM m;
-- density: points per unit of area of the circle round the centroid that reaches the farthest member
CREATE TEMP TABLE density AS SELECT cluster, count(*) / (pi() * power(max(r), 2)) AS points_per_area FROM radial GROUP BY cluster;
-- entropy of the true labels inside each cluster (bits), weighted by cluster size; 0 would be a pure cluster
CREATE TEMP TABLE entropy AS
WITH share AS (SELECT cluster, label, count(*)::DOUBLE / sum(count(*)) OVER (PARTITION BY cluster) AS p, sum(count(*)) OVER (PARTITION BY cluster) AS n FROM pts GROUP BY cluster, label)
SELECT sum(n * -p * log2(p)) / (SELECT count(*) FROM pts) AS weighted_entropy FROM share;
-- agreement with the labels: the Rand index and the adjusted Rand index from the cluster-by-label table
CREATE TEMP TABLE agreement AS
WITH cell AS (SELECT cluster, label, count(*) AS n FROM pts GROUP BY cluster, label),
tot AS (SELECT sum(n * (n - 1) / 2) AS together, (SELECT sum(n * (n - 1) / 2) FROM (SELECT cluster, sum(n) AS n FROM cell GROUP BY cluster)) AS in_cluster,
(SELECT sum(n * (n - 1) / 2) FROM (SELECT label, sum(n) AS n FROM cell GROUP BY label)) AS in_label, (SELECT count(*) * (count(*) - 1) / 2 FROM pts) AS pairs FROM cell)
SELECT (pairs + 2 * together - in_cluster - in_label) / pairs AS rand_index,
(together - in_cluster * in_label / pairs) / ((in_cluster + in_label) / 2 - in_cluster * in_label / pairs) AS adjusted_rand FROM tot;
CREATE TEMP TABLE result AS
SELECT (SELECT sum(r * r) FROM radial) AS within_ss, (SELECT mean_silhouette FROM silhouette) AS silhouette,
(SELECT list(round(points_per_area, 9) ORDER BY cluster) FROM density) AS densities,
(SELECT weighted_entropy FROM entropy) AS entropy, rand_index, adjusted_rand FROM agreement;
SELECT * FROM result;
-- reference values were computed outside DuckDB: the two cluster means are (1.44, 1.42) and (4.68, 4.62); each cluster has entropy 0.7219 bits
-- (four labels of one kind and one of the other); tolerance 1e-9 is for float rounding only
SELECT CASE WHEN ((SELECT abs(within_ss - 12.456) < 1e-9 AND abs(silhouette - 0.6295099535) < 1e-9
AND densities = [0.322829499, 0.330553591] AND abs(entropy - 0.7219280949) < 1e-9
AND abs(rand_index - 0.6444444444) < 1e-9 AND abs(adjusted_rand - 0.28) < 1e-9 FROM result)) THEN 'as expected' ELSE error('the clustering should score 12.456 within-cluster squares, a silhouette of 0.6295, entropy 0.7219 bits, a Rand index of 0.6444 and an adjusted Rand index of 0.28') END AS verdict;- The statement builds its own data.
- It needs nothing installed beyond DuckDB itself.
- The statement checks its own result and raises an error when it is wrong: “the clustering should score 12.456 within-cluster squares, a silhouette of 0.6295, entropy 0.7219 bits, a Rand index of 0.6444 and an adjusted Rand index of 0.28”.
- It passed on DuckDB
v2.0.0 (Cyanoptera) d54cbbe3df.
What other tools call it
Meridian's classification; an independent blind rating agreed on 95.7% of 324 rows (variant, version 3).
Alteryx Designer (1)
K-Centroids Diagnosticsread 2026-09-27Assess the right number of clusters for a dataset and clustering algorithm
KNIME Analytics Platform (1)
Entropy Scorerread 2026-09-27Score a clustering against a reference clustering by entropy and quality measures
RapidMiner Studio (5)
Cluster Count Performanceread 2026-09-27Report the number of clusters of a cluster model as a performance criterion
Cluster Density Performanceread 2026-09-27Evaluate a centroid clustering by average within-cluster density
Cluster Distance Performanceread 2026-09-27Evaluate a centroid clustering by within-centroid distance and Davies-Bouldin index
Item Distribution Performanceread 2026-09-27Evaluate a flat clustering by how evenly rows are distributed over clusters
Map Clustering on Labelsread 2026-09-27Map each cluster to its best-matching label so clusters can be scored as predictions
Background
A clustering can be evaluated internally by a quality score, externally against a ground truth classification, manually by an expert, or by its usefulness.[1] Internal evaluation measures usually give the best score to clusters with high similarity within a cluster and low similarity between clusters.[1] External evaluation measures how close a clustering is to benchmark classes, sets of pre-classified items often created by expert humans.[1]
When to use it
Evaluation of clustering results is as difficult as the clustering itself.[1] External evaluation needs ground truth labels, yet with such labels there would be no need to cluster, and in practical applications they are usually missing.[1] Ground truth labels reflect only one possible partitioning of the data set, which does not imply that no different, and perhaps better, clustering exists.[1]
Many internal measures, among them the Davies–Bouldin index and the silhouette coefficient, rest on the intuition that items in one cluster should be more similar than items in different clusters.[1] Internal evaluation is biased towards algorithms that use the same cluster model: k-means clustering optimises object distances, so a distance-based internal criterion will likely overrate its result.[1] Internal measures are best suited to showing where one algorithm performs better than another, which does not imply that one algorithm produces more valid results.[1] Score a clustering with a measure built on a different idea of a cluster from the one the algorithm optimised, and look at the clusters themselves before trusting any figure. (Meridian's view)
The method
The silhouette was proposed by the Belgian statistician Peter Rousseeuw in 1987.[2] The silhouette value measures how similar an object is to its own cluster compared with other clusters, and ranges from −1 to +1, a high value meaning a good match to its own cluster.[2] An average silhouette width over 0.7 is considered strong, over 0.5 reasonable and over 0.25 weak, though high values become harder to reach as the dimensionality of the data grows.[2] The silhouette score is specialised for convex-shaped clusters and may not perform well if the clusters have irregular shapes or varying sizes.[2]
The Davies–Bouldin index was introduced by David L. Davies and Donald W. Bouldin in 1979.[3] Clusters with low distances within them and high distances between them have a low Davies–Bouldin index, so the clustering with the smallest index is considered best by that criterion.[1] RapidMiner's Cluster Distance Performance evaluates centroid-based clusterings, such as those of k-means and k-medoids, by average within-cluster distance and the Davies–Bouldin index.[4]
The Rand index, named after William M. Rand, compares all pairs of elements and counts how often two clusterings agree, either both placing a pair in the same cluster or both placing it in different clusters.[5] The Rand index has a value between 0 and 1, where 1 means the two clusterings are exactly the same.[5] The adjusted Rand index (ARI) is the Rand index corrected for the similarity expected by chance, and is commonly used to compare a clustering with known class labels.[5] Unlike the Rand index, the ARI can be negative, when the index is less than the expected index.[5] In scikit-learn, the unadjusted Rand index does not ensure a value close to 0.0 for a random labelling, while the adjusted Rand index corrects for chance and gives that baseline.[6] Scikit-learn's clustering metrics ignore the absolute values of cluster labels: permuting 0 and 1 or renaming 2 to 3 in the predicted labels gives the same score.[6]
Sources
- [1]Cluster analysis - Wikipediasecondary source
- [2]Silhouette (clustering) - Wikipediasecondary source
- [3]Davies–Bouldin index - Wikipediasecondary source
- [4]Cluster Distance Performance - Rapidminer Documentationdocumentation
- [5]Rand index - Wikipediasecondary source
- [6]2.3. Clustering — scikit-learn 1.9.1 documentationdocumentation