Adjusted Rand Index (ARI) Calculator
Paste two clustering label lists, a clustering and its ground truth, or two clusterings, and get the Adjusted Rand Index, the raw Rand Index and the Fowlkes–Mallows index, with the full pair-count and contingency working shown. Matches scikit-learn, works with any label names, and handles the degenerate cases the formula leaves as 0/0. Free, no signup, and it runs entirely in your browser, your labels are never uploaded.
How it works
The Adjusted Rand Index compares two ways of grouping the same set of items, for example the clusters a k-means run produced versus the true class labels. It works at the level of pairs of items. For every one of the C(n,2) pairs, the two clusterings either place the pair in the same cluster or in different clusters, and the metric counts how often they make the same call. Nothing else about the labelings matters: not the cluster names, not the number of clusters on each side, and not the order the items were pasted in.
How to compute the Adjusted Rand Index, step by step
Concretely, you build a contingency table where n_ij is the number of items in cluster i of partition A and cluster j of partition B. Writing the row sums as a_i, the column sums as b_j, and C(x,2) = x(x−1)/2, the calculation follows scikit-learn and Hubert & Arabie (1985) in four steps:
- Count the pairs the two partitions agree on. Index = Σ C(n_ij, 2), sum C(x,2) over every cell of the contingency table.
- Count the pairs inside each partition on its own. sumA = Σ C(a_i, 2), sumB = Σ C(b_j, 2) , the same sum over the row totals, then the column totals.
- Work out the chance and ceiling terms. Expected = sumA · sumB / C(n, 2) and Max = ½ (sumA + sumB).
- Divide. ARI = (Index − Expected) / (Max − Expected) , the answer is already on the −0.5 to 1 scale, with no further normalisation.
The subtraction of Expected is what makes the index “adjusted”: it removes the agreement you would get from random labeling, so a chance clustering scores about 0 instead of the inflated value a raw Rand Index gives. That inflation is not a small effect. Split 100 items into 20 clusters at random twice over and almost every pair lands apart in both labelings, which the raw Rand Index scores as agreement. It will read around 0.90 while the two labelings share no structure at all. The chance correction is the whole reason the adjusted form is the one reported in papers.
The four pair counts
Every pair of items falls into exactly one of four buckets, and the three indices on this page are all ratios built from them:
- N11, together in both partitions (this is Index).
- N10, together in A only (sumA − Index): a cluster of A that B has split.
- N01, together in B only (sumB − Index): a cluster of B that merges two of A.
- N00, apart in both (C(n,2) − sumA − sumB + Index).
The raw Rand Index is the plain accuracy over those buckets, RI = (N11 + N00) / C(n,2). The Fowlkes–Mallows index is the geometric mean of pair precision and pair recall, FM = Index / √(sumA · sumB), which ignores N00 entirely and so behaves quite differently on sparse clusterings, the Fowlkes–Mallows index calculator breaks that one down on its own. Reading N10 against N01 is the fastest diagnosis available: a large N10 means your algorithm is over-splitting the reference classes, a large N01 means it is merging them.
Range, invariance and edge cases
ARI ranges from −0.5 to 1: 1 is a perfect match (identical partitions up to relabeling), 0 is what chance predicts, and negative values mean the two clusterings disagree more than random. Because the comparison only depends on which items share a cluster, it is permutation-invariant, renaming clusters changes nothing, which is why you can compare integer cluster ids against text class names directly. Three edge cases are worth knowing before you report a number:
- Both partitions structureless. When every item is its own cluster in both lists, or every item sits in one cluster in both, Max equals Expected and the formula is 0/0. scikit-learn defines the result as 1.0; this tool follows the same convention and labels the result so you do not mistake it for real agreement.
- One side structureless. If A has real clusters and B puts everything in one cluster, Index = sumB = 0 in the numerator terms that matter and ARI collapses to 0, correctly, since a single cluster carries no information about A.
- Unequal cluster counts. The contingency table just becomes rectangular. Over-splitting is penalised smoothly rather than rejected, which is what makes ARI usable as a k-selection curve.
All pair counts stay exact integers, every C(x,2) for the n ≤ 100,000 this tool accepts is well under 253, so nothing is lost before the final division, and the results match scikit-learn to full double precision. As a safeguard, every ARI is independently re-derived from the four pair counts with the algebraically distinct form ARI = 2(N00·N11 − N01·N10) / ((N00+N01)(N01+N11) + (N00+N10)(N10+N11)), and the two must agree to 1e−9 before any number is shown.
ARI, AMI, V-measure or silhouette, which to report
ARI is one of a family of clustering scores, and reviewers usually want the right one rather than all of them. The first question is whether you have reference labels at all.
- You have ground truth, clusters are large and balanced. Report ARI. It is the default in the clustering literature and the easiest for a reader to calibrate, because 0 always means chance.
- You have ground truth, but it is unbalanced with small clusters. Prefer Adjusted Mutual Information; scikit-learn recommends it for exactly this shape of reference. The mutual information calculator covers the information-theoretic side.
- You need to say how a clustering failed. Use V-measure, which splits the score into homogeneity (clusters contain one class) and completeness (a class is not split across clusters), the distinction ARI compresses into one number.
- You have no reference labels. No pair-counting metric applies. Use an internal measure such as the silhouette score, which scores cluster tightness and separation from the data itself.
- You are comparing two human annotators, not two clusterings. That is an agreement problem with a fixed label set, so Cohen’s kappa is the right chance-corrected statistic. ARI would throw away the fact that both annotators mean the same thing by “spam”.
- You are comparing two sets, not two partitions. Overlap between two label sets is a Jaccard similarity question, not an ARI one.
In practice, quoting ARI together with the raw Rand Index makes the chance correction visible to a reader: a pair such as RI 0.71 with ARI 0.36 tells them immediately how much of the apparent agreement was free.
Worked examples
Using ARI to pick the number of clusters
Because ARI accepts clusterings with different numbers of clusters, it doubles as a k-selection curve whenever you do have reference labels: run your algorithm for each candidate k, score every run against the same ground truth, and take the k whose ARI peaks. The table below sweeps a clean 12-item reference of three equal classes, A = [0,0,0,0,1,1,1,1,2,2,2,2], against idealised predictions at five values of k. Every row is reproducible in the calculator above.
| k | Predicted cluster sizes | ARI | Raw RI | What went wrong |
|---|---|---|---|---|
| 2 | 4, 8 | 0.5217 | 0.7576 | Two true classes merged |
| 3 | 4, 4, 4 | 1.0000 | 1.0000 | Nothing, the peak |
| 4 | 4, 4, 2, 2 | 0.8358 | 0.9394 | One class split in half |
| 6 | 2 × 6 | 0.4211 | 0.8182 | Every class split in half |
| 12 | 1 × 12 | 0.0000 | 0.7273 | All singletons, no structure left |
Two things are worth reading off that table. The first is the shape: ARI falls away on both sides of the true k, and it punishes under-clustering (k = 2, ARI 0.52) harder than a mild over-clustering (k = 4, ARI 0.84), because merging two classes creates far more wrongly-joined pairs than splitting one class creates wrongly-separated ones. The second is the last column against the one beside it. At k = 12 the prediction retains no information at all, yet the raw Rand Index still reads 0.7273, a number most people would report as decent agreement. ARI reads 0.0000, which is the truth. That gap is the whole case for the chance correction, and it widens as k grows.
One caveat: this only works when you have reference labels to score against. ARI is an external validity measure, so it cannot choose k for genuinely unlabelled data, that needs an internal measure that reads the geometry of the data itself, such as the silhouette score, the Davies–Bouldin index or the Calinski–Harabasz index. A common pattern in papers is to tune k on an internal measure, then quote ARI on whatever labelled subset exists as confirmation.
Calculating ARI in Python, with and without scikit-learn
If scikit-learn is installed, all three indices on this page are one import away, and the numbers should match this calculator to the last decimal place:
from sklearn.metrics import (
adjusted_rand_score, rand_score, fowlkes_mallows_score,
)
a = [0, 0, 0, 1, 1, 1] # reference / true labels
b = [0, 0, 1, 1, 2, 2] # predicted cluster ids
print(adjusted_rand_score(a, b)) # 0.24242424242424243
print(rand_score(a, b)) # 0.6666666666666666
print(fowlkes_mallows_score(a, b)) # 0.47140452079103173Those are worked example 1 above, to the digit. Two notes on versions: rand_score only arrived in scikit-learn 0.24, and on older installs you can recover it from sklearn.metrics.cluster.pair_confusion_matrix, which returns the four pair counts of this page’s confusion-matrix form as a 2×2 array laid out [[N00, N01], [N10, N11]], note that it counts each pair twice, so every entry is double the counts shown above the fold.
Without scikit-learn, ARI is short enough to write from the definition in pure Python, useful inside a notebook that cannot take a new dependency, and useful as a check on the library:
from collections import Counter
def comb2(x):
return x * (x - 1) // 2 # C(x, 2), exact integer
def adjusted_rand_index(a, b):
n = len(a)
cell = Counter(zip(a, b)) # contingency table n_ij
row = Counter(a) # row sums a_i
col = Counter(b) # column sums b_j
index = sum(comb2(v) for v in cell.values())
sum_a = sum(comb2(v) for v in row.values())
sum_b = sum(comb2(v) for v in col.values())
expected = sum_a * sum_b / comb2(n)
maximum = (sum_a + sum_b) / 2
if maximum == expected: # 0/0: both partitions structureless
return 1.0 # sklearn's convention
return (index - expected) / (maximum - expected)
print(adjusted_rand_index([0, 0, 0, 1, 1, 1], [0, 0, 1, 1, 2, 2]))
# 0.24242424242424246 <- last bit differs from sklearn; see note belowThat prints 0.24242424242424246 where scikit-learn prints 0.24242424242424243. Both are correct: scikit-learn evaluates the algebraically equivalent pair-count form, and the two expressions round differently in the last bit of a float64. Compare clustering scores to about twelve decimal places, never for bit equality, the calculator above applies the same rule, accepting its two independent derivations of ARI as agreeing when they fall within 1e−9.
Three details in those twenty lines are where hand-rolled versions usually go wrong. Counter(zip(a, b)) builds the contingency table without ever materialising a dense matrix, which matters when the labels are strings or there are thousands of clusters. // rather than / inside comb2 keeps every pair count an exact integer, so rounding enters only at the final division. And the maximum == expected guard is not optional, without it, an all-singleton or all-one-cluster input raises ZeroDivisionError instead of returning the conventional 1.0, which is exactly the input a sweep over k will eventually hand it.
Common mistakes when reporting ARI
Four errors account for most of the wrong ARI numbers people end up publishing.
- Aligning the labels first. Running the Hungarian algorithm to match predicted cluster ids to true class ids before scoring is unnecessary and changes nothing: ARI is already permutation-invariant. If your aligned and unaligned scores differ, something other than relabeling was changed.
- Dropping noise points. DBSCAN marks outliers as −1. Deleting those rows from one list and not the other silently misaligns every item after the first deletion. Either keep −1 as a genuine cluster, or drop the same indices from both lists.
- Reading the raw Rand Index as if it were chance-corrected. A Rand Index of 0.85 across 20 clusters is close to what random labeling produces. Quote ARI, or quote both.
- Reporting a degenerate 1.0. An all-singleton or all-one-cluster result scores 1.0 by convention, not by agreement. The tool flags it; the flag is the finding.
One more practical note: ARI is a point estimate on one sample. With a small n, a couple of items moving cluster can swing it by 0.1 or more, so when you compare two algorithms on a few dozen points, run both over several seeds and report the spread rather than a single decimal.
Frequently asked questions
Sources & references
- scikit-learn: Clustering performance evaluation: adjusted_rand_score, rand_score, fowlkes_mallows_score: the canonical definitions and the ARI ∈ [−0.5, 1] range.
- Hubert, L. & Arabie, P. (1985). Comparing partitions. Journal of Classification, 2, 193–218: the original adjusted-for-chance Rand Index.
- Fowlkes, E. B. & Mallows, C. L. (1983). A Method for Comparing Two Hierarchical Clusterings. JASA, 78(383), 553–569: the Fowlkes–Mallows index.
Every formula on this page was cross-checked against these sources on 2026-06-11, and each ARI is verified against an independent pair-count formula inside the tool. Your label lists never leave your browser.
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, an edge case, or want Normalized Mutual Information added next?
Email me at [email protected] , most fixes ship within 24 hours.