Skip to main content
v2026.11,858 entries · CC-BY 4.0

Clustering Analysis in R: kmeans() and hclust()

Clustering analysis in R with base stats functions: scale, dist, kmeans, hclust and cutree, with the defaults and traps that change the clusters.

Written and maintained by CASRAI Editorial Board

Last updated

Clustering analysis in R is done with functions that ship with the base stats package: scale() to put variables on one footing, dist() to measure dissimilarity, kmeans() for partitioning, and hclust() with cutree() for hierarchical clustering. This guide runs both approaches end to end on one data frame and spells out the argument defaults that most often change a result: the random starting centres in kmeans(), the ward.D versus ward.D2 choice in hclust(), and which of k and h wins in cutree(). Every function and argument below was checked against the R reference manual on 2026-10-29.

What cluster analysis in R is used for in research

Cluster analysis groups rows (participants, samples, sites, documents) so that rows in the same group are more alike than rows in different groups, without a pre-labelled outcome. Researchers use it to look for subgroups in survey or biomarker data, to organise samples before a heatmap, and to check whether an assumed grouping shows up in the data. It is an unsupervised method; see supervised vs unsupervised learning for the distinction.

In R the two routes you will meet most are partitioning (kmeans(), where you choose the number of clusters first) and hierarchical clustering (hclust(), where you build a full tree and cut it afterwards). The worked method for k-means itself, including the formula, is in What Is K-Means Clustering?; the same task in another language is covered in cluster analysis in Python, and the point-and-click equivalent in cluster analysis in SPSS.

Install or access (official source only)

R is free software released under the GNU General Public License (R Project, r-project.org/about.html, checked 2026-10-29), and installers are distributed through CRAN, the Comprehensive R Archive Network (cran.r-project.org/mirrors.html). Nothing in this guide needs an extra package: scale(), dist(), kmeans(), hclust() and cutree() are in R’s standard distribution. The cluster package (Rousseeuw et al., a CRAN recommended package, version 2.1.8.3 published 2026-07-30) adds further clustering functions and is loaded with library(cluster) if you want it.

Step-by-step: clustering data in R

The steps use R’s built-in iris data frame (four numeric measurement columns) so that every line can be pasted into a fresh session. Replace iris[, 1:4] with your own numeric columns.

  1. Select numeric columns and handle missing values.
    df = na.omit(iris[, 1:4])

    dist() can work around missing values (see Troubleshooting), but dropping or imputing them yourself keeps the two methods comparable.

  2. Scale the variables.
    x = scale(df)

    By default scale(x, center = TRUE, scale = TRUE) subtracts each column mean and divides by each column standard deviation, and stores the values used in the "scaled:center" and "scaled:scale" attributes. Without this step, a variable measured in large units dominates every distance. Scaling before clustering is standard practice rather than a requirement stated in the function documentation.

  3. Run k-means.
    set.seed(42)
    km = kmeans(x, centers = 3, nstart = 25)

    The usage is kmeans(x, centers, iter.max = 10, nstart = 1, algorithm = c("Hartigan-Wong", "Lloyd", "Forgy", "MacQueen"), trace = FALSE). When centers is a single number, the documentation says a random set of distinct rows of x is chosen as the initial centres, so the call depends on the random seed. nstart = 25 asks for 25 random starting sets, and set.seed() makes the run repeatable.

  4. Look for a sensible k.
    wss = sapply(1:10, function(k) kmeans(x, centers = k, nstart = 25)$tot.withinss)
    plot(1:10, wss, type = "b", xlab = "Number of clusters k", ylab = "Total within-cluster sum of squares")

    Plot tot.withinss against k and look for where adding clusters stops buying much. This is a judgement heuristic, not a test; the same logic is explained in what a scree plot shows.

  5. Run hierarchical clustering.
    d = dist(x, method = "euclidean")
    hc = hclust(d, method = "ward.D2")
    plot(hc, labels = FALSE)

    hclust() takes the dissimilarities produced by dist(), not the raw data frame. Plotting the result draws the dendrogram.

  6. Cut the tree into clusters.
    groups = cutree(hc, k = 3)
    table(groups)

    cutree(tree, k = NULL, h = NULL) returns a membership vector when you give one value of k or h, and a matrix when you give several.

  7. Compare the two solutions and attach labels to your data.
    table(kmeans = km$cluster, hclust = groups)
    df$cluster = factor(km$cluster)

Choosing the distance and the linkage

The documented options are below. They are the arguments to vary when you test how stable a clustering is.

Function Argument Options and default (R reference manual)
dist() method “euclidean” (default), “maximum”, “manhattan”, “canberra”, “binary”, “minkowski”
hclust() method “ward.D”, “ward.D2”, “single”, “complete” (default), “average” (UPGMA), “mcquitty” (WPGMA), “median” (WPGMC), “centroid” (UPGMC)
kmeans() algorithm “Hartigan-Wong” (default), “Lloyd”, “Forgy”, “MacQueen”; “Lloyd” and “Forgy” name the same algorithm
cutree() k, h k = desired number of groups; h = height at which to cut; if both are given, k takes precedence

Output and how to read it

Reading the kmeans object

The object returned by kmeans() holds cluster (each row’s assignment), centers (the cluster means), totss, withinss (per cluster), tot.withinss, betweenss, size and iter. The documentation defines betweenss as totss - tot.withinss, so km$betweenss / km$totss is the share of total variation that lies between clusters. A higher share means a tighter fit for that k, but the share generally rises as k grows, so it should not be used alone to pick k. Because the centres are on the scaled variables here, convert back with the stored "scaled:center" and "scaled:scale" attributes if you need group means in original units.

Reading the hclust object and the dendrogram

The tree is stored in merge (the order of joins), height (the dissimilarity at each join), order (the leaf order used for plotting) and labels. On the dendrogram, a tall vertical gap before a join means the clusters being merged were far apart; cutting below that gap with cutree(hc, h = ...) keeps them separate. Whichever route you use, report the distance, the linkage or algorithm, the number of clusters, and the seed and nstart for k-means, so others can reproduce the clusters in R.

Troubleshooting

Different clusters every time you run kmeans()

Initial centres are chosen at random, so unseeded runs can land on different solutions. Call set.seed() before kmeans(), raise nstart above its default of 1 so that several starts are tried, and compare tot.withinss across runs. The documentation notes that Hartigan-Wong usually does best but recommends trying several random starts.

ward.D gives a different tree from the one you expected

Only "ward.D2" implements Ward’s criterion, because it squares the dissimilarities before updating clusters. "ward.D" does not implement Ward’s 1963 criterion; it matches the old "ward" option from R 3.0.3 and earlier, which matters if you are reproducing older code. In the cluster package, agnes(*, method = "ward") corresponds to hclust(*, "ward.D2").

Dendrogram inversions with centroid or median linkage

The documentation warns that "median" and "centroid" linkage can produce inversions in the dendrogram, and recommends "centroid" only with squared Euclidean distances (for example hclust(dist(x)^2, method = "centroid")).

NA values in dist()

Missing values are allowed in dist() and are excluded from all computations involving the row they occur in. If every pair is excluded for a distance, the result is NA, and hclust() cannot use it. Check with anyNA(d) and remove or impute the offending rows.

Clusters in R that cutree() returns look wrong

If you pass both k and h, k is used and h is ignored. Pass only the one you mean.

Free alternatives

R itself is free, so the practical choice is between functions inside it. All three below are documented by the R project or CRAN.

Option What it provides Where it comes from
stats::kmeans() Partitioning into a chosen number of clusters, with four algorithms Base R, stats package
stats::hclust() with cutree() Agglomerative hierarchical clustering with eight linkage methods and tree cutting by k or h Base R, stats package
cluster package “Finding Groups in Data”: extended cluster analysis functions based on Kaufman and Rousseeuw (1990) CRAN recommended package, Rousseeuw et al.

To see a hierarchical clustering drawn alongside the data, the heatmap workflow is in heatmaps in R. If you are choosing between R and SPSS for the whole analysis, see SPSS vs R.

FAQ

How do I do cluster analysis in R?

Scale numeric variables with scale(), then either call kmeans(x, centers = k, nstart = 25) or build dist(x), pass it to hclust(), and cut the tree with cutree(). The numbered steps above give runnable code for both.

How do I choose the number of clusters?

For k-means, run kmeans() over a range of k, plot tot.withinss and look for where the curve flattens. For hierarchical clustering, inspect the dendrogram for large gaps in height. Both are judgement aids; report the rule you used.

What is the difference between kmeans() and hclust() in R?

kmeans() needs the number of clusters (or starting centres) up front and works on the data matrix; hclust() works on a dissimilarity object from dist(), returns a full tree, and you choose the number of clusters afterwards with cutree().

How do I add the cluster number back to my data frame?

Assign the membership vector: df$cluster = factor(km$cluster) for k-means or df$cluster = factor(groups) for a cut tree. Rows keep the same order as the input.

Sources (official documentation, checked 2026-10-29)

Follow CASRAI

Research-administration guidance, standards updates and independent tool reviews.

Ask CASRAI · free to try

Ask about Clustering Analysis in R: kmeans() and hclust()

Ask your first 2 questions free below. Subscribers get 150 a day for $29 a month.

An AI assistant specialized in research administration. It cites the sources behind every answer, labels web answers and says when it can't answer.

Answers draw on CASRAI's guides and dictionary plus the federal and funder documents we index: Federal Register, Grants.gov, Regulations.gov and UKRI.

Works on this site and inside Claude, Cursor and the AI tools you already use.

Everything CASRAI publishes — this page, the dictionary, the guides and the news — stays free to read, with no account and no card.

Referenced across the research world

University of Cambridge logoColumbia University logoCrossref logoUniversity of Edinburgh logoHarvard University logoUniversity of Oxford logoPrinceton University logoStanford School of Medicine logoUniversity College London logoORCID logoUniversity of Cambridge logoColumbia University logoCrossref logoUniversity of Edinburgh logoHarvard University logoUniversity of Oxford logoPrinceton University logoStanford School of Medicine logoUniversity College London logoORCID logo
  • University of Cambridge logo
  • Columbia University logo
  • Crossref logo
  • University of Edinburgh logo
  • Harvard University logo
  • University of Oxford logo
  • Princeton University logo
  • Stanford School of Medicine logo
  • University College London logo
  • ORCID logo

View CASRAI adoption →

Ask CASRAI · Regulatory Radar

Research-admin question? Get an answer that links its sources.

An AI assistant specialized in research administration. Every answer links its sources to check before you act. 2 questions free, no account. $29/month after.

  • Answers draw on CASRAI's guides and dictionary plus the federal and funder documents we index: Federal Register, Grants.gov, Regulations.gov and UKRI.
  • Every answer numbers its sources and links each one, so you can check the source yourself.