本案例对2016世界幸福报告进行分析,首先对不同国家根据幸福指数进行聚类,然后使用不同降维方法在二维空间对数据进行展示,最后分析幸福指数与国家区域的关系。
本案例主要包括以下内容:
1. 数据集描述
2. 数据探索
2.1 相关性分析
2.2 各个区域的幸福指数差异
2.3 不同区域关键因素对幸福指数的影响差异
2.4 在地图上可视化各国幸福指数
3. 对样本进行聚类
4. 层次聚类
4.1 实现层次聚类
4.2 选取最佳的连接算法和距离度量方式
4.3 层次聚类过程的解释
4.4 树状图
4.5 提取层次聚类结果
4.6 利用距离矩阵实现聚类
5. 数据可视化
5.1 降维方法的选择
5.2 聚类结果的展示
6. 聚类结果分析
世界幸福报告(The World Happiness Report)是对世界各国幸福状态的一个具有里程碑意义的调查报告(官方网址),2017年的世界幸福报告于2017年的3月20日在联合国大会上公布,当天也是国际幸福日(International Day of Happiness)。随着各国政府、组织以及民间社会越来越多地使用世界幸福报告来指导其政策趋向并反映其政策开展的效果,世界幸福报告获得了全球的认可。有趣的是:在2017年的世界幸福报告中,首次加入了在工作场所的幸福度的分析,而且还深入地分析了中国和非洲。难道联合国也受到了中国威胁论的影响?anyway,我们还是的案例还是不要涉及到任何政治为妙,中国的国际影响力由此可见一斑。各个领域的顶尖专家——经济学、心理学、调查分析、国家统计、健康、公共政策等等——研究如何通过这些指数来评估一个国家的发展。这份报告也展示了新的“幸福科学”对个人和民族的快乐程度的变化的解释。
第一份世界幸福报告发布于2012年,第二份发布于2013年,第三份发布于2015年,本案例使用2016年度的世界幸福报告(数据来源),该报告涵盖了全球的157个国家(全球共有193个国家)在2016年的各项指数的调查结果。
在本案例中,我们首先探索了该数据集中各个指数的相关关系,随后我们通过各种聚类方法进行聚类,然后我们使用了多种降维方法将样本(一个国家)映射到了二维平面上,并通过指定降维方法(Isomap)进行可视化,我们用聚类结果的标签对样本点进行渲染,比较不同聚类方法的效果,随后将聚类的结果与幸福指数排名Happiness Rank
和国家所在区域Region
的关系进行了探索。
通常我们在分析过程中才知道我们需要用到哪些包,所以一种常见的做法是在我们完成整个分析过程之后,把所有调包的操作放到最前面,使得文件易读,并且整个分析中需要用到技术手段也一目了然。 按照惯例,首先导入之后需要用到的包。
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
import scipy.cluster.hierarchy as sch # 层次聚类
from scipy.spatial.distance import pdist, squareform # pair-wise distance样本距离计算,pdist返回向量形式,squareform返回矩阵形式
from sklearn import preprocessing, neighbors, cluster, metrics, manifold, decomposition
import warnings
import plotly.graph_objs as go
from plotly.offline import download_plotlyjs, init_notebook_mode, plot, iplot
init_notebook_mode(connected=True)
warnings.filterwarnings('ignore') # 不显示warning信息
sns.set_context('notebook')
np.set_printoptions(precision=5, suppress=True) # 设置浮点数显示位数,尽量不用科学计数法表示
%matplotlib inline
%config InlineBackend.figure_format = 'retina' #调整notebook中图片输出的清晰度,使得在retina屏幕上显示的更清楚
读取2016_world_happiness.csv
文件,并展示前五个样本。
wh = pd.read_csv('./input/2016_world_happiness.csv')
print wh.shape
wh.head()
wh.columns
该数据集共包含13个特征,Country
和Region
分别表示国家名称和国家所在地区。Happiness Rank
和Happiness Score
分别表示幸福指数排名和幸福指数,该数据通过盖洛普世界民意调查(the Gallup World Poll)获得。被调查者会被问及一系列被称为Cantril ladder的问题,被调查者的回答为0-10不同分数,0表示最差的状态,10表示最好的状态。分数来自于2013-2016年的全国代表性样本,并使用盖洛普权重对每个被调查者的幸福指数做出估计。Lower Confidence Interval
和Upper Confidence Interval
表示置信区间的下界和上届。
在以上6个特征之后,列出了另外6个影响幸福指数的因素:Economy (GDP per Capita)
——经济状况(人均GDP)、Family
——家庭情况(表示社会支持度)、Health (Life Expectancy)
——健康状况(预期寿命)、Freedom
——自由情况(包括了人身、财务自由等)、Trust (Government Corruption)
——对政府的信任度(腐败程度)和Generosity
——慷慨情况。其具体取值代表了该特征对Happiness Score
的贡献,这个贡献为拟合系数及变量的取值两者之积。2016世界幸福报告中对于每个特征对Happiness Score
的OLS(最小二乘)拟合情况如下表所示。下文叙述时使用“6个关键因素”或“关键因素”代表这6个特征。
Dystopia Residual
表示反乌托邦残差。Dystopia
处在Utopia(乌托邦)的对立面,是一个虚构的国家,这个国家拥有世界上最不幸福的人,即这个国家的六个影响幸福指数的关键因素都是最低的,收入最低、预期寿命最短、最不慷慨、腐败最严重、最不自由以及社会支持最少。建立反乌托邦的目的是要有一个基准,使得所有的国家都可以比较好,利于国家之间进行比较。而residual
称作残差,是利用以上6个因素预测幸福指数时无法解释的部分因素,因国家而异,反映利用6个关键预测Happiness Score
的残差。我们可以利用上表的回归模型预测Dystopia
的幸福指数(结果为2.33),下图展示了上表中的方程式应用于每个国家时的平均残差。所以Dystopia Residual
的值实际上为Dystopia
的幸福指数(2.33)+每个国家的残差。
由此,对于该数据集,我们已经没有必要再构建回归模型对幸福指数进行预测,所以我们从聚类的角度来看一看,每个国家的关键因素的权重是否可以将各个国家分成某几类,并观察聚类结果与国家所在地区的关系。
在上一部分,我们花了大量的篇幅对其中的特征进行了解释,在这一部分,我们使用相关性分析和描述性统计等相关方法进行数据探索。
Economy (GDP per Capita)
的值相较于关键因素中的其他5个因素大,故一国的经济状况很大程度上决定了一国的幸福指数。
下面我们首先画出关键因素的相关矩阵:
wh_key = wh.drop(['Country','Region' , 'Happiness Rank','Happiness Score',
'Lower Confidence Interval','Upper Confidence Interval', 'Dystopia Residual'],axis=1)
corrmat = wh_key.corr() #得到相关性矩阵
f, ax = plt.subplots(figsize = (8,8))
sns.heatmap(corrmat, square=True)
同样可以使用scatterplot
画出两者之间的散点图和相关关系图:
sns.pairplot(wh_key, kind='reg', diag_kind='kde', diag_kws=dict(shade=True), size=4)
从上面的两个图可以看出,Family
与Economy (GDP per Capita)
呈正相关关系(另外还有其他的正相关或负相关关系)。但是我们需要仔细想一想,这是否真的意味着两者存在正相关关系?由于关键因素的取值代表了在对Happiness Score
做预测时的系数,所以只能代表两者对Happiness Score
的权重一致,并不能说明两者的相关关系,所以对于该数据集,做相关分析是没有任何意义的,除非拿到原始的关键因素的取值,而不是拟合之后的权重。
我们先来看看中国和日本的幸福指数排在世界的第几位?
wh.ix[wh['Country']=='China']
wh.ix[wh['Country']=='Japan']
中国的幸福指数排名处于中等,日本的幸福指数排名处于中等偏上的水平,两者相差30名。
我们利用seaborn中的boxplot
来观察不同区域之间的幸福指数差异:
f, ax = plt.subplots(figsize = (8,8))
ax = sns.swarmplot(y="Region", x="Happiness Score", data=wh)
ax = sns.boxplot(y="Region", x="Happiness Score", data=wh)
从平均幸福指数来看,撒哈拉以南的非洲地区的幸福程度最低,南亚地区的幸福程度排在倒数第二,西欧及北美这两个以资本主义国家为主的地区幸福程度较高。同时,Middle East and Northern Africa
中东和北非地区的幸福指数差异最大,我们猜测中东和北非这部分石油国与战乱国混合而成的区域,由石油带来的大量财富与战争带来的国家分裂导致了国家之间幸福感的巨大差异。
类似上节的方法,我们根据区域画出不同关键参数对幸福指数的影响差异。 由于各国的幸福指数是不同的,为了得出不同国家的每个关键因素对幸福指数的影响,我们应该将各个关键因素贡献值除以其幸福指数,得到关键因素贡献比例值。
首先,对于wh_key
中的每一行除以对应的Happiness Score
值:
for row in range(wh.shape[0]):
wh_key.ix[row] = wh_key.ix[row]/wh['Happiness Score'].ix[row]
然后将Region
列与wh_key
通过pd.concat
方法拼接在一起,得到wh_norm
:
reg_df = pd.DataFrame(wh['Region'],columns=['Region']) # 将Series转换为DataFrame
wh_norm = pd.concat([reg_df,wh_key],axis=1) # 将wh_key与reg_df拼接
接下来,我们使用该贡献比例值来考察不同区域关键因素对幸福指数的影响差异。
fig, axes = plt.subplots(nrows=6, ncols=1,figsize=(10,30))
for col, ax in zip(wh_key.columns,axes.flatten()):
sns.swarmplot(x=col, y='Region', data=wh_norm, ax=ax)
sns.boxplot(x=col, y='Region', data=wh_norm, ax=ax)
从上面六张图我们可以看出,在低收入区域(从该数据集无法得出收入状况,这里的“低收入”是一个经验判断)如Sub-Saharan Africa
和Southern Asia
区域,国家GDP对幸福指数的贡献相较于其他区域小,这些地区的主要幸福来源为自由、慷慨换来的幸福感。高收入地区如Western Europe
西欧和North America
北美的幸福感来自于经济状况、家庭、健康状况、自由等各个方面,似乎它们在各个方面较其他区域都相当满意。有趣的是,Australia and New Zealand
澳洲和新西兰地区对政府的各项决策非常信任,较其他地区,它们从政府的各项福利和政策中收获的幸福感最强。
data = dict(type = 'choropleth',
locations = wh['Country'],
locationmode = 'country names',
z = wh['Happiness Rank'],
text = wh['Country'],
colorbar = {'title':'Happiness'})
layout = dict(title = 'Global Happiness',
geo = dict(showframe = False,
projection = {'type': 'Mercator'}))
choromap3 = go.Figure(data = [data], layout=layout)
iplot(choromap3)
我们将使用关键因素的贡献比例值数据wh_norm
,将各个样本进行聚类。我们将使用Kmeans
、MiniBatchKMeans
、SpectralClustering
和DBSCAN
四种方法进行聚类,sklearn
中各聚类方法的比较如下图所示。
我们首先在高维空间(6维)进行聚类,在下一节再把样本映射到二维空间,并用聚类结果标签对样本的颜色进行渲染。
首先去掉wh_norm
数据框中的'Region'
列,并将DataFrame
转换为ndarray
:
wh_array = wh_norm.drop('Region', axis=1).values
为方便后续画图,将聚类的过程定义为函数clustering
:
names = ['KMeans', 'MiniBatchKMeans','SpectralClustering','DBSCAN']
# 定义聚类的函数
def clustering(n_clusters=5, eps=.04): # DBSCAN 无 n_clusters参数,最终簇的数目由 eps 决定
labels = {} # 存储聚类结果的类别标签
centers = {} # 存储各类中心点位置的数据
preds = {} # 当聚类对象不存在labels_属性,将预测结果存储在这个字典变量中
# 建立各个聚类模型类的实例对象
kmeans = cluster.KMeans(n_clusters=n_clusters)
minibatch_means = cluster.MiniBatchKMeans(n_clusters=n_clusters)
spectral = cluster.SpectralClustering(n_clusters=n_clusters,
eigen_solver='arpack', affinity="nearest_neighbors")
dbscan = cluster.DBSCAN(eps=eps)
algorithms = [kmeans, minibatch_means, spectral, dbscan]
for name, algorithm in zip(names, algorithms):
algorithm.fit(wh_array)
if hasattr(algorithm, 'labels_'): # 判断聚类对象是否存在labels_属性
labels[name] = algorithm.labels_.astype(np.int)
else:
preds[name] = algorithm.predict(wh_array)
if hasattr(algorithm, 'cluster_centers_'):
centers[name] = algorithm.cluster_centers_
return labels, centers, preds
在这一部分,我们使用scipy.cluster.hierarrchy
层次聚类方法进行聚类,这里的层次聚类为聚合式层次聚类,即初始时每个样本点看成一个簇。然后画出聚类结果的树状图。
首先通过wh_array
数据矩阵得到连接矩阵(linkage matrix),其中'ward'
表示使用ward方差最小化算法计算类之间的距离,也可以尝试改变连接算法为single
、complete
或average
等,也可以把距离度量方法metrics
从默认的欧式距离euclidean
改为cityblock
、hamming
或cosine
等,比如在文本聚类时,选取cosine
计算距离更合适。
Z = sch.linkage(wh_array, method='ward', metric='euclidean')
为了选取最佳的连接算法和距离度量方式,我们需要通过cophenet
函数来计算聚类结果的同表像相关系数(Cophenetic Correlation Coefficient),该系数度量了树状图隐含的成对距离在多大程度上保留了原始数据的实际成对距离信息。其值越接近1,层次聚类越好地保持了原始成对距离信息。所以我们通过穷举methods
和metrics
,希望找到最接近于1的(methods,metrics)
组合。
methods = ['single', 'complete', 'average', 'weighted', 'ward']
metrics = ['braycurtis', 'canberra', 'chebyshev', 'cityblock',
'correlation', 'cosine', 'dice', 'euclidean', 'hamming',
'jaccard', 'kulsinski', 'mahalanobis', 'matching',
'minkowski', 'rogerstanimoto', 'russellrao', 'seuclidean',
'sokalmichener', 'sokalsneath', 'sqeuclidean']
c_init = 0
for method in methods:
for metric in metrics:
try: # 某些(method,metric)的组合理论上是不可行的,比如在连接方法为'ward'的情况下只能使用`euclidean`
Z = sch.linkage(wh_array, method=method, metric=metric)
c, coph_dists = sch.cophenet(Z, pdist(wh_array))
except:
pass
if c > c_init:
c_init = c
best_para = (method, metric, c)
print best_para
使用最佳参数组合best_para
进行层次聚类:
Z = sch.linkage(wh_array, method=best_para[0], metric=best_para[1])
linkage
函数将使用我们指定的连接方法和距离度量方法来计算各簇之间的距离。层次聚类过程首先将每个样本点看做一个簇(singleton clusters),在随后的每一次迭代中,将距离最近的两个簇合并成一个新的簇。所以linkage
将返回一个$n-1$($n$为样本数)维的数组,提供了$n$个簇合并时$n-1$次合并过程的相关信息。
Z[i]
展示了第$i$次合并的相关信息,比如第一次合并的信息Z[0]
:
Z[0]
每一行array具有这样的格式:[idx1, idx2, dist, sample_count]
,以上返回的结果表示linkage
算法将索引号为5
和8
的两个样本点合并在了一起,他们两者之间的距离为0.00784
,这次合并产生了一个包含2
个样本的簇。
我们来看看使用pdist
得出的两者之间的距离:
n_samples = wh_key.shape[0] # 样本数
idx1 = 5
idx2 = 8
# 距离矩阵的索引号dist_idx,这里比较复杂,需要花一些时间思考
dist_idx = np.abs(sum(range(-(n_samples-1),-(n_samples-1)+idx1)))+(idx2-idx1)
dist = pdist(wh_array)[dist_idx-1]
print dist
这个距离值正是Z[0]
的第三个元素。
下面我们看一下前15次迭代过程;
Z[:15]
我们发现一个奇怪的问题,我们的样本数有157个,索引是从0开始到156结束,那么在第4次迭代时,为何会出现一个索引号为157的簇?这是因为,我们每产生一个新的簇,索引号就会加1,簇的索引号与我们的样本索引号是不同的,如果簇的索引号idx >= len(X)
,那么实际上该簇为Z[idx-len(X)]
迭代过程形成的新簇。
树状图(dendrogram),也可以叫做谱系图、树形图,是层次聚类的结果的一种极好的展示方式,可以从这个图中非常清楚的看到层次聚类过程中簇之间的距离和合并的顺序。
f, ax = plt.subplots(figsize=(20,10))
plt.xlabel('sample index', fontsize=20)
plt.ylabel('distance',fontsize=20)
sch.set_link_color_palette(['g', 'y', 'c', 'k']) # 设置色系
sch.dendrogram(Z, leaf_rotation=90, leaf_font_size=8, color_threshold =0.12)
plt.xticks(fontsize=8)
plt.yticks(fontsize=15)
plt.show()
树状图的横坐标表示样本的索引号,纵坐标表示使用average
方法得到的距离。
如果我们要查看将所有样本聚为10个簇的结果,则我们可以利用truncate_mode='lastp'
指定显示最后10个合并的簇,show_contracted=True
可以显示每个簇所包含的样本数(使用括号包括起来),包含一个样本的簇显示簇索引号。
f, ax = plt.subplots(figsize=(20,10))
plt.xlabel('sample index', fontsize=20)
plt.ylabel('distance',fontsize=20)
sch.set_link_color_palette(['g', 'y', 'c', 'k'])
sch.dendrogram(Z,
truncate_mode='lastp',
p=10,
leaf_rotation=90,
leaf_font_size=8,
color_threshold =0.125,
show_contracted=True,)
plt.xticks(fontsize=15)
plt.yticks(fontsize=15)
plt.show()
使用fcluster
方法提取聚类结果,若我们需要指定最终簇的个数为10,则可以指定criterion='maxclust'
,并使得t=10
。
hierarchy_labels = sch.fcluster(Z, t=10, criterion = 'maxclust')
hierarchy_labels
我们也可以结合树状图进行簇的划分,设定一个最大的距离,在树状图中用最大距离所在的水平线切割树状图。不难理解,这一条水平线切割了多少条竖直线就最终将所有样本分成了几个簇。比如我们设定最大距离max_distance
为0.15
,则我们可以得到5个簇:
sch.fcluster(Z, t=0.15, criterion = 'distance')
若我们事先无法确定簇的个数,则我们可以使用fcluster
的默认计算标准inconsistent
去决定簇的划分,即通过树状图上的每个簇在竖直方向跳跃至合并时的高度分布,决定何种跳跃将两个“真实”(大部分情况下我们无法得知真实的簇划分情况)的簇错误的合并在了一起,即是考虑“what makes a distance jump a jump?”,详细算法参考scipy.cluster.hierarchy.inconsistent。
在使用这个方法时,我们需要指定阈值t
和深度depth
。如果簇及其所有后代(由depth
决定)的inconsistency
值小于或等于阈值t
,则其所有后代属于同一个簇。
sch.fcluster(Z, t=3, depth=10)
可以看到inconsistency
得出的结果与maxclust
得到结果比较一致。在下一节,我们将使用maxclust
的方法得到的聚类结果hierarchy_labels
进行可视化展示。
我们可以结合sns.clustermap
利用距离矩阵做出聚类的热图。
首先选择你认为你比较好的色系进行可视化(色系的调整参考color_palettes),可以通过sns.palplot
预览你的色系图块,以调整出你认为最舒服的色系。
sns.palplot(sns.diverging_palette(130, 25, n=10))
通过pdist()
得到距离向量,然后再使用squareform()
转换为矩阵:
dist_mat = squareform(pdist(wh_array))
cmap = sns.diverging_palette(130, 25, n=10, as_cmap=True)
sns.clustermap(dist_mat, cmap=cmap, xticklabels=False, yticklabels=False,figsize=(8,8))
容易看到,这个聚类结果与4.4中的聚类结果是一致的。
我们也可以直接通过array得到 clustermap,不过图形的上方是对特征进行的聚类,读者可以思考一下这个clustermap 得到的特征的聚类结果可以作为特征选择的方法吗?
sns.clustermap(wh_norm.drop('Region',axis=1), cmap=cmap, yticklabels=False,figsize=(8,8))
在这一部分,我们将样本映射到二维空间,并用不同颜色区别聚类得到的不同类。在利用聚类结果之前,我们需要先决定我们使用何种降维方法。
对每个特征进行Z-score标准化,得到数组wh_array
:
wh_array = preprocessing.StandardScaler().fit_transform(wh_norm.drop('Region',axis=1))
dimen_reduc_names = ['PCA', 'MDS', 'ISOMAP', 'LLE', 't-SNE', 'Spectral Embedding']
pca = decomposition.PCA(n_components=2)
mds = manifold.MDS(n_components=2, n_init=1, max_iter=100)
iso = manifold.Isomap(n_components=2, n_neighbors=2)
lle = manifold.LocallyLinearEmbedding(n_components=2, n_neighbors=3)
tsne = manifold.TSNE(n_components=2, init='pca', random_state=10)
spectral = manifold.SpectralEmbedding(n_components=2, random_state=10, eigen_solver='arpack')
dimen_reduc_algorithms = [pca, mds, iso, lle, tsne, spectral]
fig, axes = plt.subplots(2, 3, figsize=(15,10))
for i, name, algorithm in zip(range(len(dimen_reduc_names)), dimen_reduc_names, dimen_reduc_algorithms):
wh_2d = algorithm.fit_transform(wh_array)
plt.subplot(2, 3, i+1)
plt.scatter(wh_2d[:,0], wh_2d[:,1], c='g')
plt.title(name, fontsize=15)
plt.xticks([])
plt.yticks([])
这六种降维方法并没有哪一种能够很明显地看出样本点聚集而成的集团(簇),故我们不带任何先验假设地选择 ISOMAP 作为下一节聚类结果的展示,二维空间中的样本点位置信息储存在数组wh_2d
中。
iso = manifold.Isomap(n_components=2, n_neighbors=2)
wh_2d = iso.fit_transform(wh_array)
为了使得不同簇之间容易区分,我们选择一个比较容易区别的色系。
sns.palplot(sns.color_palette("Set3", 10))
colors = np.array([x for x in sns.color_palette("Set3", 10)])
画出最终簇的数目分别为2、6、10,以及使用四种不同聚类算法的结果:
"""
names: 聚类算法的名称列表
n_clusters: 簇的个数列表[0,6,10]
fig_counts: 子图的数量
"""
n_clusters = range(2,11,4)
nrows=len(n_clusters)
ncols=len(names)
fig_counts = nrows*ncols
fig, axes = plt.subplots(nrows=nrows, ncols=ncols, figsize=(25,15))
for i, n_cluster in enumerate(n_clusters):
labels, centers, preds = clustering(n_cluster, eps=1.6)
for j, name in enumerate(names):
label = labels[name]
label_transform = np.array([x-label.min() for x in label])
plt.subplot(3, 4, i*len(names)+j+1)
plt.scatter(wh_2d[:,0], wh_2d[:,1], color=colors[label_transform].tolist())
if name == 'DBSCAN':
plt.title(name)
else:
plt.title(name+'\n'+'n_cluster='+str(n_cluster), fontsize=15)
plt.xticks([])
plt.yticks([])
读者也可以试试其他降维方法展示聚类结果。
由以上的图可以看出,KMeans
方法与MiniBatchKmeans
方法的聚类结果大致相同(注意簇的颜色在不同的子图中没有对应关系),这个结果也非常容易理解。SpectralClustering
方法与前两种方法存在较大的差异。DBSCAN
是基于密度的聚类方法,簇内不同点散乱的分布在二维空间中。
那么聚类的结果会不会与Happiness Rank
存在关系呢?下面我们来check一下。
首先我们需要把Happiness Rank
由高到低划分为10个等级,由于样本数共有157个,若每一个包含16个样本,那么最后一个等级将包含157-16*9=13
个样本。
label_rank = wh['Happiness Rank'].copy(deep=True)
# label_rank: 将变量'Region'进行按照等级编码
for i, element in enumerate(label_rank):
label_rank[i] = np.int(np.floor(i/16)) # np.floor()是为了保证python 2.x版本和3.x版本都能得到相同的值
print label_rank.value_counts()
labels, centers, preds = clustering(n_clusters=10, eps=1.6)
labels['Happiness Ranks']=label_rank
names.insert(0, 'Happiness Ranks') # 在names列表最前面加上 'Regions'
fig, axes = plt.subplots(1, 5, figsize=(20,4))
for i, name in enumerate(names):
plt.subplot(1, 5, i+1)
plt.scatter(wh_2d[:,0], wh_2d[:,1], color=colors[labels[name]].tolist())
plt.title(name, fontsize=15)
plt.xticks([])
plt.yticks([])
names.remove('Happiness Ranks') #最后将'Regions'删除,防止重复运行时出错
容易看到,聚类结果与Happiness Rank
似乎不存在任何的关系。
下面我们来看看这个聚类结果与国家所在区域是否存在一定的关系,这里我们用Region
做簇的渲染与用聚类方法的结果标签渲染进行比较:
label_region = wh_norm['Region'].copy(deep=True)
for i, element in enumerate(pd.unique(wh_norm['Region'])):
label_region.replace(element, i, inplace=True) # label_region: 将类别变量'Region'进行数字编码之后得到的Series
labels, centers, preds = clustering(n_clusters=10, eps=1.6)
labels['Regions']=label_region
names.insert(0, 'Regions') # 在names列表最前面加上 'Regions'
fig, axes = plt.subplots(1, 5, figsize=(20,4))
for i, name in enumerate(names):
plt.subplot(1, 5, i+1)
plt.scatter(wh_2d[:,0], wh_2d[:,1], color=colors[labels[name]].tolist())
plt.title(name, fontsize=15)
plt.xticks([])
plt.yticks([])
names.remove('Regions') #最后将 'Regions'删除,防止重复运行时出错
很容易从上图发现,属于同一区域的国家并不属于同一簇,故以上的聚类结果与区域之间并不存在很强的关系,在同一区域的国家其幸福指数的权重差异可能由不同比重的关键因素造成。
对于每一个聚类子图,我们都可以单独拿出来做一个分析。可分析簇间的差异主要由何种因素造成,也可指导簇内成员的提高幸福指数的决策:如果簇内某一种成员为提高幸福指数采取了某类措施,那么簇内的其他成员也可以仿效,因为对于它们而言,影响幸福指数的关键因素的比重是类似的。根据不同的目的,我们可以有不同的分析手段,这里不再赘述,留给读者去探索。
综合以上分析,我们基本可以得出一个结论:为了提高一个国家的幸福指数,仿效同区域国家的改进措施,有可能是不可取的。我们可以给出的建议是仿效同簇内的其他国家的改进措施。
本案例未使用2012年、2013年及2015年的世界幸福报告的数据,有兴趣的读者可以比较各国幸福指数的变化,正如2016年的报告中指出:几乎全球所有的区域以及整个世界人口,各个国家之间的幸福不平等程度显着增加,读者可以尝试使用可视化手段(heatmap等)进行比较,将各个国家的幸福指数以热图的形式展示在地图上。
除此之外,读者还可以结合sklearn.cluster
尝试更多聚类方法:聚合聚类方法(sklearn
中的类对象为AgglomerativeClustering
)、基于仿射传播的聚类方法(AffinityPropagation
)、BIRCH聚类方法(Birch
)、均值漂移聚类方法(MeanShift
)。