Cluster Analysis - Part 2: Hands On
library(broom) library(cluster) library(dplyr) library(ggplot2) library(ggdendro)
In the first part of this blog series, we examined the theoretical foundations of cluster analysis. In the following article we put the theory into practice using R. For the analysis in R, we will use the variables
mpg (fuel consumption in miles per gallon) and
disp (cubic capacity) for 32 cars from the
mtcars data set. A detailed description of the dataset can be found here.
First we create a new data frame called
car, which contains the mentioned variables
disp for the 32 cars. The observations are shown in a scatter plot.
car <- select(mtcars, mpg, disp) ggplot(car, aes(mpg, disp)) + geom_point()
Now we perform a hierarchical clustering method and look at the dendrogram. The y-axis of the dendrogram shows the distance between the clusters (Euclidean distance).
h.cluster <- car %>% dist(., method = "euclidean") %>% hclust(., method = "ward.D") ggdendrogram(h.cluster, theme_dendro = FALSE)
Obviously, two clusters make sense in this case. Next we create a screeplot, where k-means method is performed for each . The screeplot shows the number of clusters on the x-axis and the variance within the clusters on the y-axis.
multi.clust <- data.frame(k = 1:6) %>% group_by(k) %>% do(clust = kmeans(car, .$k)) sumsq.clust <- multi.clust %>% group_by(k) %>% do(glance(.$clust[])) ggplot(sumsq.clust, aes(k, tot.withinss)) + geom_line() + geom_point()
The kink is at
p.cluster <- car %>% kmeans(., 2) p.cluster$cluster <- as.factor(p.cluster$cluster) ggplot(car, aes(mpg, disp, label = rownames(car))) + scale_fill_discrete(name = "Cluster") + coord_cartesian(xlim = c(9, 35)) + geom_label(aes(fill = p.cluster$cluster), colour = "white", fontface = "bold", size = 2)
At this point it would be interesting to compare the group structure for , , and so on.
multi.clust <- data.frame(k = 1:6) %>% group_by(k) %>% do(clust = kmeans(car, .$k)) multi.k <- multi.clust %>% group_by(k) %>% do(augment(.$clust[], car)) ggplot(multi.k, aes(mpg, disp)) + geom_point(aes(color = .cluster)) + facet_wrap(~k)
As mentioned in the first part of the series about Cluster Analysis, the quality of the group assignment can be easily assessed with a silhouette plot.
sil <- car %>% dist(.) %>% silhouette(as.numeric(p.cluster$cluster), .) sil.data <- data.frame(cluster = factor(sil[, 1]), sil_width = sil[, 3]) ggplot(sil.data, aes(x = row.names(sil.data), y = sil_width, fill = cluster, col = cluster)) + geom_bar(stat = "identity", width = 0.5) + coord_flip() + labs(x = "") + scale_x_discrete(limits = row.names(sil.data[order(sil.data$cluster, sil.data$sil_width), ]))
##  0.687796
None of the silhouette widths has a value
Now that we have validated the cluster solution, we come to the most difficult part of the analysis: interpretation. For the analysis we used the fuel consumption
mpg and the displacement
disp. Short note for those who have no idea about cars: the bigger the displacement of a vehicle, the more powerful the engine.
For assistance, we use the remaining information from the
mtcars data set for interpretation. To do this, we add the cluster mapping column to the original dataset.
clustcars <- cbind(mtcars, cluster = p.cluster$cluster)
Now we can examine the remaining variables within each cluster.
clustcars %>% group_by(cluster) %>% summarise(avg_cyl = mean(cyl), avg_horsepower = mean(hp), avg_drat = mean(drat), avg_weight = mean(wt), avg_qsec = mean(qsec), share_vmotor = mean(vs), share_manualtransmission = mean(am), avg_gears = mean(gear), avg_carb = mean(carb)) %>% mutate_if(is.numeric, round, digits = 1)
## # A tibble: 2 x 10 ## cluster avg_cyl avg_horsepower avg_drat avg_weight avg_qsec share_vmotor ## <fct> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> ## 1 1 7.9 203. 3.2 3.9 16.9 0.1 ## 2 2 4.7 97.4 3.9 2.6 18.6 0.8 ## # ... with 3 more variables: share_manualtransmission <dbl>, ## # avg_gears <dbl>, avg_carb <dbl>
We could see already in the above cluster solution that the cars assigned to cluster 1 generally have higher power and consumption than the cars from cluster 2.
In addition, the table shows that on average, cluster 1 cars have about 8 cylinders, while cluster 2 typically contains cars with 4 to 6 cylinders. Usually the power increases with the number of cylinders (the variable horsepower is on average twice as high for the cars in cluster 1 compared to cluster 2), but the price and the fuel consumption as well.
With the collected information, we interpret the two clusters as two price ranges. Cluster 1 contains the more luxurious cars, with more power, more cylinders and higher fuel consumption. The cluster 2 therefore contains less powerful cars, which are cheaper in price and have lower consumption. To confirm this interpretation, we have also computed the recommended retail price (attention: the models are from the 1970s) for the cars. The table below shows the average values for each of the two clusters, with the median in brackets.
|Avg. price cluster 1||Avg. price cluster 2|
|9974 Dollar (7474)||6283 Dollar (4295)|