Skip to Content
Course content

120: Cluster Analysis: K-Means and Hierarchical in R

Click on the "Edit" button in the top corner of the screen to edit your slide content.

I’ve seen a lot of analysts jump straight into clustering by feeding a dataframe directly into kmeans(), picking a random number for the centers, and calling it a day. It feels productive because you get a result immediately, but in most real-world scenarios, that approach is lying to you. When I first started doing this, I made the mistake of thinking the algorithm "figured it out." It doesn't; it just does the math you told it to do, even if that math is logically flawed for your specific data.

The Danger of Unscaled Distances

Let's say we're segmenting customers based on two metrics: Annual_Income (ranging from $20k to $150k) and Purchase_Frequency (ranging from 1 to 50 purchases per year). If you throw these raw numbers into a clustering algorithm, the Annual_Income column will completely hijack the result. Because K-Means relies on Euclidean distance, a difference of $1,000 in income looks "bigger" to the computer than the difference between a customer who shops once a year and one who shops fifty times.

The naive way is to just run kmeans(my_data, centers = 3). The better way—the only way I'll accept in a production pipeline—is to scale your data first. By using scale(), you shift your data so that every variable has a mean of 0 and a standard deviation of 1. Now, a "big jump" in income is weighted the same as a "big jump" in frequency.

# The wrong way: raw data
clusters_naive <- kmeans(customer_data, centers = 3)

# The right way: scaling first
scaled_data <- scale(customer_data)
clusters_scaled <- kmeans(scaled_data, centers = 3)

Guessing K vs. Finding the Elbow

Picking the number of clusters (k) is where most people just guess. "Three seems like a reasonable number of customer types," they'll say. But that's an assumption, not an analysis. I prefer to let the Total Within-Cluster Sum of Squares (WSS) tell me where the point of diminishing returns is. This is the "Elbow Method."

If you plot the WSS for a range of k values, you'll usually see a sharp drop that eventually levels off. That "elbow" is the sweet spot where adding more clusters doesn't significantly improve the fit. It takes a few more lines of code—usually a simple sapply or a loop—but it saves you from defending an arbitrary choice during a stakeholder meeting.

# Finding the elbow
wss <- sapply(1:10, function(k){
  kmeans(scaled_data, centers = k, nstart = 20)$tot.withinss
})
plot(1:10, wss, type = "b", pch = 19, xlab = "Number of Clusters", ylab = "WSS")

Note the nstart = 20 argument. K-means starts with random centroids; if you're unlucky, it can get stuck in a local optimum. Running it 20 times and picking the best start is a cheap insurance policy against weird results.

When to Stop Partitioning and Start Nesting

K-means is great for large datasets, but it forces every point into a hard partition. Sometimes, that's too blunt. This is where Hierarchical Clustering comes in. I use this when I need to understand the relationship between clusters, not just the clusters themselves. Instead of picking k upfront, it builds a tree (a dendrogram) of similarities.

The trade-off here is computational cost. K-means is linear; hierarchical clustering is quadratic. If you try to run hclust() on a million rows, your R session will likely crash or hang indefinitely. But for smaller, high-value datasets, the visual clarity of a dendrogram is worth the hit. You use dist() to create a distance matrix first, then pass that into hclust().

# Hierarchical approach
dist_matrix <- dist(scaled_data, method = "euclidean")
hc_model <- hclust(dist_matrix, method = "complete")
plot(hc_model)

# Now you can 'cut' the tree at the desired height or number of groups
groups <- cutree(hc_model, k = 3)

I generally use hierarchical clustering as a diagnostic tool to help me decide what k to use for a K-means model on the full dataset. It's the best of both worlds: the insight of the tree and the efficiency of the partition.




📋 Practical Task

Exercise: Segmenting High-Value Retail Customers

You have been provided with a dataframe called retail_data containing two columns: Spending_Score (1-100) and Annual_Income_k (in thousands). Your goal is to create a robust customer segmentation model.

Complete the following steps in your R script:

  • Prepare the Data: Create a scaled version of retail_data so that the income and spending score are weighted equally.
  • Determine Optimal K: Write a loop or use sapply to calculate the Total Within-Cluster Sum of Squares (WSS) for k = 1 through 10. Generate a plot to identify the "elbow."
  • Execute K-Means: Using the k-value identified from your elbow plot, run a kmeans model with nstart = 25.
  • Validate with Hierarchical: Create a distance matrix and a dendrogram using hclust. Use cutree to see if the hierarchical clusters align closely with your K-means assignments.
  • Final Output: Add the resulting K-means cluster assignments as a new column named Segment to the original retail_data dataframe.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.