Running a K-Means Cluster Analysis
A k-means cluster analysis was conducted to identify subgroups of countries based on their similarity of responses on 5 variables that represent characteristics that could have an impact on breast cancer incidence. Clustering variables in this case are all quantitative, measuring the consumption of alcohol, CO2 emissions, female employment rate, income per person and life expectancy.
All clustering variables were standardized to have a mean of 0 and a standard deviation of 1.
Data were not split into training and test set since there is a relatively small number of observations (214 countries).
A series of k-means cluster analyses were conducted on the data specifying k=1-9 clusters, using Euclidean distance. The variance in the clustering variables that was accounted for by the clusters (r-square) was plotted for each of the nine cluster solutions in an elbow curve to provide guidance for choosing the number of clusters to interpret.
from pandas import Series, DataFrame
import pandas as pd
import numpy as np
import matplotlib.pylab as plt
from sklearn.cross_validation import train_test_split
from sklearn import preprocessing
from sklearn.cluster import KMeans
from scipy.spatial.distance import cdist
#loading dataset
data = pd.read_csv("gapminder.csv")
def analyse(ax):
global data
#setting variables you will be working with to numeric because python read from the csv file as strings(objects)
data[ax] = pd.to_numeric(data[ax], errors='coerce')
analyse("breastcancerper100th")
analyse("alcconsumption")
analyse("co2emissions")
analyse("femaleemployrate")
analyse("incomeperperson")
analyse("lifeexpectancy")
#data management
data_clean = data.dropna()
#Subset clustering variables
cluster = data_clean[[ "alcconsumption", "co2emissions", "femaleemployrate","lifeexpectancy", "employrate", "incomeperperson"]]
cluster.describe()
#standardize clustering variables to have mean=0 and sd=1
clustervar = cluster.copy()
def standardizing(x):
clustervar[x] = preprocessing.scale(clustervar[x].astype("float64"))
standardizing("alcconsumption")
standardizing("co2emissions")
standardizing("femaleemployrate")
standardizing("incomeperperson")
standardizing("lifeexpectancy")
# k-menas cluster analysis for 1-9 clusters
clusters = range(1,10)
meandist= []
for k in clusters:
model = KMeans(n_clusters=k)
model.fit(clustervar)
clausassign = model.predict(clustervar)
meandist.append(sum(np.min(cdist(clustervar, model.cluster_centers_, "euclidean"), axis=1))/clustervar.shape[0])
#Plot average distance from observations from the cluster centroid to sue the Elbow Method to identify number of clusters to choose
plt.plot(clusters, meandist)
plt.xlabel("Number of clusters")
plt.ylabel("Average distance")
plt.title("Selecting k with the Elbow Method")
plt.show()
The figure below shows the elbow curve of r-square values for the nine cluster solution.
So what this plot shows is the decrease in the average minimum distance of the observations from the cluster centroids for each of the cluster solutions. We can see that the average distance decreases as the number of clusters increases. Since the goal of cluster analysis is to minimize the distance between observations and their assigned clusters we want to chose the fewest numbers of clusters that provides a low average distance. What we're looking for in this plot is a bend in the elbow that kind of shows where the average distance value might be leveling off such that adding more clusters doesn't decrease the average distance as much.
We can see there appears to be a couple of bends at the line at two clusters and at three clusters, but it's not very clear. To help us figure out which of the solutions is best we should further examine the cluster solutions for at least the two and three cluster solutions to see whether they do not overlap, whether the patterns of means on the clustering variables are unique and meaningful, and whether there are significant differences between the clusters on our external validation variable “breastcancerper100th”.
Here is my additional code to the tree cluster solution:
#interpret 3 cluster solution
model3 = KMeans(n_clusters=3)
model3.fit(clustervar)
clusassign = model3.predict(clustervar)
#plot clusters
pca_2 = PCA(2)
plot_columns = pca_2.fit_transform(clustervar)
plt.scatter(x=plot_columns[:,0], y=plot_columns[:,1], c=model3.labels_,)
plt.xlabel('Canonical variable 1')
plt.ylabel('Canonical variable 2')
plt.title('Scatterplot of Canonical Variables for 3 Clusters')
plt.show()
#multiple steps to merge cluster assignment with clustering variables to examine cluster variable means by cluster
# create a unique identifier variable from the index for the
# cluster training data to merge with the cluster assignment variable
clustervar.reset_index(level=0, inplace=True)
# create a list that has the new index variable
cluslist=list(clustervar['index'])
# create a list of cluster assignments
labels=list(model3.labels_)
# combine index variable list with cluster assignment list into a dictionary
newlist=dict(zip(cluslist, labels))
newlist
# convert newlist dictionary to a dataframe
newclus=DataFrame.from_dict(newlist, orient='index')
newclus
# rename the cluster assignment column
newclus.columns = ['cluster']
# now do the same for the cluster assignment variable
# create a unique identifier variable from the index for the
# cluster assignment dataframe
# to merge with cluster training data
newclus.reset_index(level=0, inplace=True)
# merge the cluster assignment dataframe with the cluster training variable dataframe
# by the index variable
merged_train=pd.merge(clustervar, newclus, on='index')
merged_train.head(n=100)
# cluster frequencies
merged_train.cluster.value_counts()
# calculate clustering variable means by cluster
clustergrp = merged_train.groupby('cluster').mean()
print ("Clustering variable means by cluster")
print(clustergrp)
# validate clusters by examining cluster differences in breastcanerper100th using ANOVA
# first have to merge breast cancer with clustering variables and cluster assignment data
bc_data=data_clean["breastcancerper100th"]
# split breastcancer data into train and test sets
bc_train, bc_test = train_test_split(bc_data, test_size=.3, random_state=123)
bc_train1=pd.DataFrame(bc_train)
bc_train1.reset_index(level=0, inplace=True)
merged_train_all=pd.merge(bc_train1, merged_train, on='index')
sub1 = merged_train_all[['breastcancerper100th', 'cluster']].dropna()
import statsmodels.formula.api as smf
import statsmodels.stats.multicomp as multi
bcmod = smf.ols(formula='breastcancerper100th ~ C(cluster)', data=sub1).fit()
print (bcmod.summary())
print ('means for breast cancer by cluster')
m1= sub1.groupby('cluster').mean()
print (m1)
print ('standard deviations for breast cancer by cluster')
m2= sub1.groupby('cluster').std()
print (m2)
mc1 = multi.MultiComparison(sub1["breastcancerper100th"], sub1['cluster'])
res1 = mc1.tukeyhsd()
print(res1.summary())
Canonical discriminant analyses was used to reduce the 5 clustering variable down a few variables that accounted for most of the variance in the clustering variables. Plot of the first two canonical variables for the clustering variables by cluster is showed below:
This scatter plot indicates that the clusters are not densely packed and they did not overlap very much each other, which means the observations within the clusters are not highly correlated, and there is a high variance between clusters.
The means on the clustering variables showed that compared to the other clusters, countries in the second cluster, cluster 1, clearly includes countries more likely to have high incidence of breast cancer due to high levels of alcohol consumption and high rates of life expectancy.
In order to externally validate the clusters, an Analysis of Variance (ANOVA) was conducting to test for significant differences between the clusters on breast cancer incidence. The results showed they do differ significantly (p-value= 0.00254).
The tukey post hoc comparisons showed significant differences between clusters on breast cancer incidence, with the exception that clusters 0 and 1 were not significantly different from each other. Countries in cluster 2 had the highest breast cancer incidence (mean=43.94, sd=22.64), and cluster 3 had the lowest (mean=23.06, sd=16.84).