使用Python和Scikit-Learn进行层次聚类的权威指南

314 阅读24分钟

简介

在本指南中,我们将专注于用Scikit-Learn实现层次聚类算法来解决一个营销问题。

读完本指南后,你会明白。

  • 何时应用层次聚类算法
  • 如何将数据集可视化以了解它是否适合聚类
  • 如何预处理特征并根据数据集设计新的特征
  • 如何使用PCA降低数据集的维度
  • 如何使用和阅读树状图来分离群体
  • 应用于树状图和聚类算法的不同连接方法和距离度量是什么?
  • 什么是聚类和分裂聚类策略以及它们是如何工作的
  • 如何用Scikit-Learn实现聚类分级聚类的方法
  • 处理聚类算法时最常见的问题是什么以及如何解决这些问题

**注意:**你可以在这里下载包含本指南中所有代码的笔记本。

动机

想象一个场景,你是一个与市场部对接的数据科学团队的一员。市场部已经收集了一段时间的客户购物数据,他们想根据收集的数据了解客户之间是否存在相似之处。这些相似性将客户分成了不同的群体,而拥有客户群体有助于活动的定位、促销、转化和建立更好的客户关系。

有什么办法可以帮助确定哪些客户是相似的?其中有多少人属于同一群体?又有多少个不同的群体?

回答这些问题的方法之一是使用聚类算法,如K-Means、DBSCAN、层次聚类等。一般来说,聚类算法可以找到数据点之间的相似性,并将它们分组。

在这种情况下,我们的营销数据是相当小的。我们只有200个客户的信息。考虑到营销团队,我们必须清楚地向他们解释如何根据聚类的数量做出决定,因此向他们解释算法的实际运作情况。

由于我们的数据很小,可解释性是一个主要因素,我们可以利用层次分类法来解决这个问题。这个过程也被称为层次聚类分析(HCA)

HCA的优点之一是它可以解释,在小数据集上运行良好。

在这种情况下需要考虑的另一点是,HCA是一种无监督的算法。在对数据进行分组时,我们将没有办法验证我们是否正确地识别出一个用户属于某个特定的组(我们不知道这些组)。没有标签供我们比较我们的结果。如果我们正确地识别了组别,以后会由市场部在日常工作中确认(如投资回报率、转换率等指标来衡量)。

现在我们已经了解了我们要解决的问题以及如何解决这个问题,我们可以开始看一下我们的数据了

简要的探索性数据分析

**注意:**你可以在这里下载本指南中使用的数据集。

下载数据集后,注意它是一个*CSV(逗号分隔的数值)*文件,名为shopping-data.csv 。为了更容易探索和处理数据,我们将使用Pandas把它加载到DataFrame

import pandas as pd

# Substitute the path_to_file content by the path to your shopping-data.csv file 
path_to_file = 'home/projects/datasets/shopping-data.csv'
customer_data = pd.read_csv(path_to_file)

**建议。**如果你是Pandas和DataFrames的新手,你应该阅读我们的 "使用Pandas的Python指南。带例子的数据框架教程"!

市场部说它已经收集了200条客户记录。我们可以使用shape 属性检查下载的数据是否有完整的200行。它将告诉我们,我们分别有多少行和多少列。

customer_data.shape

这样的结果是:。

(200, 5)

很好!我们的数据是完整的,有200行*(客户记录),我们也有5列(特征)*。要想知道市场部从客户那里收集了哪些特征,我们可以通过columns 属性查看列名。要做到这一点,请执行。

customer_data.columns

上面的脚本返回。

Index(['CustomerID', 'Genre', 'Age', 'Annual Income (k$)',       'Spending Score (1-100)'],
      dtype='object')

在这里,我们看到市场营销部门已经生成了一个CustomerID ,收集了GenreAgeAnnual Income (以千美元为单位),以及一个Spending Score ,从1到100的200个客户。当被要求澄清时,他们说,Spending Score 栏中的数值标志着一个人在商场消费的频率,以1到100为单位。换句话说,如果一个顾客的得分是0,这个人就从来不花钱,而如果得分是100,我们就刚刚发现了最高的花费者。

让我们快速看一下这个分数的分布,以检查我们数据集中的用户的消费习惯。这就是Pandas的hist() 方法的作用了。

customer_data['Spending Score (1-100)'].hist()

img

通过观察直方图,我们看到超过35个客户的分数在4060 之间,然后不到25个客户的分数在7080 之间。因此,我们的大多数客户都是平衡型消费,其次是中度到高度消费的客户。我们还可以看到,在0 之后有一条线,在分布的左边,而在100之前还有一条线,在分布的右边。这些空白可能意味着分布中不包含不消费的人,他们的得分是0 ,而且也没有得分是100 的高消费者。

img

为了验证这一点,我们可以看一下分布的最小和最大值。这些数值作为描述性统计的一部分可以很容易找到,所以我们可以使用describe() 方法来了解其他数值的分布情况。

# transpose() transposes the table, making it easier for us to compare values
customer_data.describe().transpose()

这将给我们一个表格,我们可以从中读取我们的数据集的其他数值的分布。

 						count 	mean 	std 		min 	25% 	50% 	75% 	max
CustomerID 				200.0 	100.50 	57.879185 	1.0 	50.75 	100.5 	150.25 	200.0
Age 					200.0 	38.85 	13.969007 	18.0 	28.75 	36.0 	49.00 	70.0
Annual Income (k$) 		200.0 	60.56 	26.264721 	15.0 	41.50 	61.5 	78.00 	137.0
Spending Score (1-100) 	200.0 	50.20 	25.823522 	1.0 	34.75 	50.0 	73.00 	99.0

我们的假设得到了证实。min Spending Score 的值是1 ,最大值是99 。因此,我们没有0100 分数的花费者。然后让我们看看转置后的describe 表格的其他列。当查看meanstd 列时,我们可以看到,对于Agemean38.85std 大约是13.97 。同样的情况也发生在Annual Incomemean60.56std26.26Spending Scoremean50std25.82 。对于所有的特征,mean 远离标准差,这表明我们的数据具有高变异性

为了更好地了解我们的数据是如何变化的,让我们绘制Annual Income 的分布图。

customer_data['Annual Income (k$)'].hist()

这将给我们带来。

img

注意在直方图中,我们的大部分数据,超过35个客户,都集中在数字60 ,在我们的mean ,在横轴上。但是,当我们向分布的两端移动时会发生什么呢?当向左走时,从60.560美元的平均值开始,我们将遇到的下一个数值是34.300美元--平均值(60.560美元)减去标准差(26.260美元)。如果我们在数据分布的左边走得更远,类似的规则也适用,我们从当前值(34.300美元)中减去标准变化(26.260美元)。因此,我们会遇到一个8.040美元的数值。注意我们的数据是如何从6万美元迅速变成8万美元的。它每次都在 "跳 "26.260美元--变化很大,这就是为什么我们有如此高的变异性。

img

变异性和数据的大小在聚类分析中很重要,因为大多数聚类算法的距离测量对数据的大小很敏感。大小的不同会改变聚类的结果,使一个点看起来比另一个点更近或更远,扭曲了数据的实际分组。

到目前为止,我们已经看到了我们的数据的形状,它的一些分布,以及描述性统计。通过Pandas,我们还可以列出我们的数据类型,看看我们的200行是否都被填满,或者有一些null

customer_data.info()

这样的结果是。

<class 'pandas.core.frame.DataFrame'>
RangeIndex: 200 entries, 0 to 199
Data columns (total 5 columns):
 #   Column                  Non-Null Count  Dtype 
---  ------                  --------------  ----- 
 0   CustomerID              200 non-null    int64 
 1   Genre                   200 non-null    object
 2   Age                     200 non-null    int64 
 3   Annual Income (k$)      200 non-null    int64 
 4   Spending Score (1-100)  200 non-null    int64 
dtypes: int64(4), object(1)
memory usage: 7.9+ KB

在这里,我们可以看到,数据中没有null ,我们只有一个分类列--Genre 。在这个阶段,重要的是我们要牢记哪些特征似乎很有趣,可以添加到聚类模型中。如果我们想把 "流派 "列添加到我们的模型中,我们就需要把它的值从分类的转变为数字的

让我们通过快速浏览我们数据的前5个值来看看Genre ,如何填充。

customer_data.head() 

这样的结果是。

    CustomerID 	Genre 	Age 	Annual Income (k$) 	Spending Score (1-100)
0 	1 			Male 	19 		15 					39
1 	2 			Male 	21 		15 					81
2 	3 			Female 	20 		16 					6
3 	4 			Female 	23 		16 					77
4 	5 			Female 	31 		17 					40

似乎它只有FemaleMale 类别。我们可以通过看一下它的唯一值unique 来确定这一点。

customer_data['Genre'].unique()

这证实了我们的假设。

array(['Male', 'Female'], dtype=object)

到目前为止,我们知道我们只有两个流派,如果我们打算在我们的模型上使用这个特征,Male 可以转化为0Female 转化为1 。检查流派之间的比例也很重要,看看它们是否平衡。我们可以用value_counts() 方法和它的参数normalize=True 来显示MaleFemale 之间的比例。

customer_data['Genre'].value_counts(normalize=True)

这就输出了。

Female    0.56
Male      0.44
Name: Genre, dtype: float64

我们在数据集中有56%的女性和44%的男性。他们之间的差异只有16%,我们的数据不是50/50,但足够平衡,不会造成任何麻烦。如果结果是70/30,60/40,那么可能就需要收集更多的数据,或者采用某种数据增强技术,使这个比例更加平衡。

到目前为止,除了Age ,所有的特征都被简单地探讨了。在有关Age ,通常有趣的是把它分成几档,以便能够根据客户的年龄组来细分。如果我们这样做,我们需要在将年龄类别加入我们的模型之前将其转化为一个数字。这样,我们就不会使用15-20岁的类别,而是计算有多少客户在15-20 ,这将是一个新列的数字,称为15-20

**建议。**在本指南中,我们只介绍了简单的探索性数据分析。但你可以更进一步,也应该更进一步。你可以看看是否存在收入差异和基于流派和年龄的评分差异。这不仅丰富了分析内容,而且导致了更好的模型结果。要深入了解探索性数据分析,请查看"动手做房价预测--Python中的机器学习"指导项目中的EDA章节

在猜想了对分类--或分类要--GenreAge 列可以做什么之后,让我们应用已经讨论过的内容。

编码变量和特征工程

让我们先把Age 分成10个不同的组,这样我们就有20-30,30-40,40-50,等等。由于我们最年轻的客户是15岁,我们可以从15岁开始,到70岁结束,这是数据中最年长客户的年龄。从15岁开始,到70岁结束,我们会有15-20、20-30、30-40、40-50、50-60和60-70的区间。

为了将Age 值分组或分仓到这些区间,我们可以使用Pandascut() 方法将其切成分仓,然后将分仓分配到一个新的Age Groups 列。

intervals = [15, 20, 30, 40, 50, 60, 70]
col = customer_data['Age']
customer_data['Age Groups'] = pd.cut(x=col, bins=intervals)

# To be able to look at the result stored in the variable
customer_data['Age Groups'] 

这样的结果是。

0      (15, 20]
1      (20, 30]
2      (15, 20]
3      (20, 30]
4      (30, 40]
         ...   
195    (30, 40]
196    (40, 50]
197    (30, 40]
198    (30, 40]
199    (20, 30]
Name: Age Groups, Length: 200, dtype: category
Categories (6, interval[int64, right]): [(15, 20] < (20, 30] < (30, 40] < (40, 50] < (50, 60] < (60, 70]]

请注意,在查看列值时,还有一行指定我们有6个类别,并显示所有分仓的数据区间。这样,我们就把以前的数字数据进行了分类,并创建了一个新的Age Groups 特征。

那么我们在每个类别中有多少个客户呢?我们可以通过对该列进行分组,并用groupby()count() 来计算数值,从而快速知道。

customer_data.groupby('Age Groups')['Age Groups'].count()

这样的结果是。

Age Groups
(15, 20]    17
(20, 30]    45
(30, 40]    60
(40, 50]    38
(50, 60]    23
(60, 70]    17
Name: Age Groups, dtype: int64

我们很容易发现,大多数客户的年龄在30到40岁之间,其次是20到30岁的客户,然后是40到50岁的客户。这对市场部来说也是很好的信息。

目前,我们有两个分类变量,AgeGenre ,我们需要将其转化为数字,以便能够在我们的模型中使用。有许多不同的方法来进行这种转换--我们将使用Pandasget_dummies() 方法,为每个区间和流派创建一个新的列,然后用0和1来填充它的值--这种操作被称为一键编码。让我们看看它的样子。

# The _oh means one-hot
customer_data_oh = pd.get_dummies(customer_data)
# Display the one-hot encoded dataframe
customer_data_oh 

这将给我们提供一个结果表的预视图。

img

通过输出结果,我们很容易看到,Genre 列被分成了几列 -Genre_FemaleGenre_Male 。当客户是女性时,Genre_Female 等于1 ,而当客户是男性时,等于0

建议。如果你想阅读更多关于一热编码(有时也被称为分类编码)--请阅读我们的 "用Pandas和Scikit-Learn在Python中进行一热编码"!

另外,Age Groups 列被分成6列,每个区间一列,如Age Groups_(15, 20],Age Groups_(20, 30], 等等。与Genre 相同,当客户为18岁时,Age Groups_(15, 20] 的值为1 ,其他列的值为0

单一热编码的优点是在表示列值时很简单,可以直接理解发生了什么--而缺点是我们现在创建了8个额外的列,与我们已有的列相加。

警告。如果你有一个数据集,其中一热编码的列的数量超过了行的数量,最好采用另一种编码方法,以避免数据的尺寸问题。

一热编码也给我们的数据增加了0,使其更加稀疏,这对一些对数据稀疏性敏感的算法来说可能是个问题。

对于我们的聚类需求,一热编码似乎是可行的。但我们可以对数据进行绘图,看看是否真的有不同的群体供我们进行聚类。

基本绘图和降维

我们的数据集有11列,我们可以用一些方法来可视化这些数据。第一种是将其绘制成10维的图(祝你好运)。十维是因为Customer_ID 列不被考虑。第二种是通过绘制我们的初始数字特征,第三种是将我们的10个特征转化为2个--因此,进行降维。

绘制每一对数据

由于绘制10个维度是有点不可能的,我们将选择第二种方法--我们将绘制我们的初始特征。我们可以选择其中的两个进行聚类分析。我们可以看到我们所有的数据对组合的一种方式是用Seabornpairplot()

import seaborn as sns

# Dropping CustomerID column from data 
customer_data = customer_data.drop('CustomerID', axis=1)

sns.pairplot(customer_data)

其中显示。

img

一目了然,我们可以发现那些似乎有数据组的散点图。其中一个看起来很有趣的是将Annual IncomeSpending Score 的散点图。请注意,其他变量散点图之间没有明显的分隔。最多,我们也许可以看出,在Spending ScoreAge 的散点图中,有两个明显的集中点。

Annual IncomeSpending Score 组成的两幅散点图基本上是一样的。我们可以看到两次,因为x轴和y轴被交换了。通过看一下它们中的任何一个,我们可以看到似乎是五个不同的组。让我们用海博恩scatterplot() ,只绘制这两个特征,仔细看一下。

sns.scatterplot(x=customer_data['Annual Income (k$)'],
                y=customer_data['Spending Score (1-100)'])

img

通过仔细观察,我们肯定可以区分出5个不同的数据组。看来我们的客户可以根据他们一年赚多少钱和花多少钱来进行聚类。这是我们分析中的另一个相关点。重要的是,我们只考虑到两个特征来对我们的客户进行分组。我们所掌握的关于他们的任何其他信息都没有进入这个方程式。这使分析有了意义--如果我们知道一个客户的收入和支出是多少,我们可以很容易地找到我们需要的相似之处。

img

这很好!到目前为止,我们已经有两个变量来建立我们的模型。除了这所代表的,它也使模型更简单,更简明,更容易解释。

**注意:**数据科学通常倾向于尽可能的简单方法。不仅是因为它更容易为企业解释,而且还因为它更直接--有了2个特征和一个可解释的模型,就可以清楚地知道模型在做什么以及它是如何工作的。

使用PCA后绘制数据图

看起来我们的第二种方法可能是最好的,但我们也来看看我们的第三种方法。当我们因为数据有太多的维度而无法绘图时,或者当没有数据集中或在组中有明显的分离时,它可能会很有用。当这些情况发生时,建议尝试用一种叫做*主成分分析(PCA)*的方法减少数据维度。

**注意:**大多数人在可视化之前使用PCA进行降维。还有其他一些方法可以在聚类之前帮助数据可视化,比如*基于密度的带噪声的空间聚类应用(DBSCAN)自组织地图(SOM)*聚类。两者都是聚类算法,但也可用于数据可视化。由于聚类分析没有黄金标准,所以比较不同的可视化和不同的算法很重要。

PCA将减少我们数据的维度,同时尽可能多地保留其信息。让我们首先了解一下PCA的工作原理,然后我们可以选择将数据减少到多少维度。

对于每一对特征,PCA查看一个变量的较大值是否与另一个变量的较大值相对应,对于较小的值,它也是如此。因此,它本质上是计算特征值彼此之间的差异程度--我们称之为协方差。然后将这些结果整理成一个矩阵,得到一个协方差矩阵

在得到协方差矩阵后,PCA试图找到最能解释它的特征的线性组合--它拟合线性模型,直到找出能解释最大方差的模型。

注意:PCA是一种线性转换,而线性对数据的规模很敏感。因此,当所有数据值都在同一尺度上时,PCA的效果最好。这可以通过从其数值中减去列平均值并将结果除以其标准差来实现。这就是所谓的数据标准化。在使用PCA之前,请确保数据是按比例排列的如果你不确定如何做,请阅读我们的 "用Scikit-Learn在Python中进行机器学习的特征缩放数据"!

找到了最好的线(线性组合),PCA就得到了其轴的方向,称为特征向量,以及其线性系数,即特征值。特征向量和特征值的组合--或轴的方向和系数--是PCA的主成分。这时我们就可以根据每个特征的解释方差来选择我们的维数,通过了解我们要根据其解释方差的多少来保留或抛弃哪些主成分。

在获得主成分后,PCA使用特征向量形成一个特征向量,将数据从原来的坐标轴重新定位到主成分所代表的坐标轴上--这就是数据维度的减少。

**注意:**这里需要考虑的一个重要细节是,由于其线性性质,PCA将把大部分解释方差集中在第一个主成分上。因此,在看解释方差时,通常我们的前两个成分就足够了。但是,这在某些情况下可能会产生误导--所以在聚类时尽量不断比较不同的图和算法,看看它们是否持有类似的结果。

在应用PCA之前,我们需要在我们之前的一热编码数据中选择Age 列或Age Groups 列。由于这两列都代表相同的信息,所以引入两次会影响我们的数据方差。如果选择Age Groups 列,只需使用Pandasdrop() 方法删除Age 列,并将其重新分配给customer_data_oh 变量。

customer_data_oh = customer_data_oh.drop(['Age'], axis=1)
customer_data_oh.shape # (200, 10)

现在我们的数据有10列,这意味着我们可以按列获得一个主成分,并通过测量引入一个新维度对我们的数据方差的解释程度来选择使用多少个。

让我们用Scikit-LearnPCA 来做这件事。我们将计算每个维度的解释方差,由explained_variance_ratio_ ,然后用cumsum() ,看它们的累积和。

from sklearn.decomposition import PCA

pca = PCA(n_components=10)
pca.fit_transform(customer_data_oh)
pca.explained_variance_ratio_.cumsum()

我们的累积解释方差是。

array([0.509337  , 0.99909504, 0.99946364, 0.99965506, 0.99977937,
       0.99986848, 0.99993716, 1.        , 1.        , 1.        ])

我们可以看到,第一个维度解释了50%的数据,而当与第二个维度结合时,它们解释了99%的数据。这意味着前两个维度已经解释了我们99%的数据。因此,我们可以应用2个成分的PCA,获得我们的主成分,并绘制它们。

from sklearn.decomposition import PCA

pca = PCA(n_components=2)
pcs = pca.fit_transform(customer_data_oh)

pc1_values = pcs[:,0]
pc2_values = pcs[:,1]
sns.scatterplot(x=pc1_values, y=pc2_values)

img

PCA之后的数据图与只使用两列数据而不使用PCA的图非常相似。请注意,形成组的点比较接近,而且在PCA之后比之前更集中一些。

img

建议。要查看PCA的其他应用,请看 "用Scikit-Learn在Python中实现PCA"指南。

用树枝图可视化层次结构

到目前为止,我们已经探索了数据,对分类列进行了单热编码,决定哪些列适合聚类,并降低了数据维度。这些图表明我们的数据中有5个聚类,但也有另一种方法可以可视化我们的点之间的关系,并帮助确定聚类的数量--通过创建一个树状图(通常误写为dendogram)。Dendro在拉丁语中是指树

树状图是数据集中各点连接的结果。它是分层聚类过程的一种视觉表现。那么分层聚类过程是如何进行的呢?嗯......这取决于--可能你已经在数据科学领域听到了很多答案。

了解分层聚类

当 *层次聚类算法(HCA)*开始连接这些点并寻找聚类时,它可以先将这些点分成两个大组,然后再将这两个组中的每一个分成更小的两个组,总共有4个组,这就是分而治之自上而下的方法。

另外,它也可以做相反的事情--它可以查看所有的数据点,找到2个彼此比较接近的点,把它们连接起来,然后找到与这些连接点最接近的其他点,并不断从下往上建立这2个组。这就是我们要开发的聚类方法。

执行聚集式层次聚类的步骤

为了使聚类方法更加清晰,有一些*聚类分级聚类(AHC)*算法的步骤。

  1. 在开始时,将每个数据点视为一个聚类。因此,开始时的聚类数量将是K--而K是一个代表数据点数量的整数。
  2. 通过连接两个最接近的数据点形成一个簇,从而形成K-1个簇。
  3. 通过连接两个最接近的聚类,形成更多的聚类,从而形成K-2聚类。
  4. 重复以上三个步骤,直到形成一个大聚类。

注意:为了简化,我们在第2和第3步中说的是 "两个最接近的 "数据点。但是,还有更多的连接点的方法,我们将在后面看到。

如果你把ACH算法的步骤倒过来,从4到1--这些将是*的步骤。分层聚类(DHC)*。.

请注意,HCA可以是自上而下的分裂式,也可以是自下而上的聚集式。自上而下的DHC方法在你有较少但较大的聚类时效果最好,因此它的计算成本更高。另一方面,自下而上的AHC方法适用于你有许多较小的集群时。它在计算上更简单,更常用,也更容易获得。

**注意:**无论是自上而下还是自下而上,聚类过程的树状图表示总是从一分为二开始,最后每个单独的点都被区分开来,一旦其基础结构是二叉树。

让我们绘制我们的客户数据树状图,以可视化数据的层次关系。这一次,我们将使用scipy 库来为我们的数据集创建树状图。

import scipy.cluster.hierarchy as shc
import matplotlib.pyplot as plt

plt.figure(figsize=(10, 7))
plt.title("Customers Dendrogram")

# Selecting Annual Income and Spending Scores by index
selected_data = customer_data_oh.iloc[:, 1:3]
clusters = shc.linkage(selected_data, 
            method='ward', 
            metric="euclidean")
shc.dendrogram(Z=clusters)
plt.show()

脚本的输出看起来像这样。

img

在上面的脚本中,我们已经用我们的点生成了聚类和子聚类,定义了我们的点将如何连接(通过应用ward 方法),以及如何测量点之间的距离(通过使用euclidean 指标)。

通过树状图的绘制,DHC和AHC的描述过程可以被可视化。为了使自上而下的方法可视化,从树状图的顶部开始往下走,反之,从下往上走,以使自下而上的方法可视化。

链接方法

还有许多其他的联系方法,通过了解它们的工作原理,你将能够根据你的需要选择合适的方法。除此之外,每一种方法在应用时都会产生不同的结果。在聚类分析中没有一个固定的规则,如果可能的话,研究问题的性质,看哪种方法最适合它,测试不同的方法,并检查结果。

一些联结方法有。

  • 单一联系:也被称为 最近的邻居(NN).聚类之间的距离由其最接近的成员之间的距离定义。

img

  • 完全联结:也被称为 最远邻居(FN), 最远点算法,或 Voor Hees算法.聚类之间的距离是由其最远的成员之间的距离来定义的。这种方法在计算上很昂贵。

img

  • 平均联系法:又称UPGMA(带算术平均值的未加权对群法)。计算每个群组的点数与两个群组合并后的点数的百分比。

img

  • 加权联动:又称WPGMA(带算术平均值的加权对群法)。两个聚类的各个点对较小的聚类和较大的聚类之间的聚合距离有贡献。
  • 中心点连接法:也被称为UPGMC(使用中心点的非加权对群法)。为每个聚类计算一个由所有点的平均值(中心点)定义的点,聚类之间的距离是它们各自中心点之间的距离。

img

  • 沃德联系法。也被称为MISSQ(最小化的平方之和增加)。它指定两个聚类之间的距离,计算平方误差之和(ESS),并根据较小的ESS依次选择下一个聚类。沃德方法试图使每一步的ESS增幅最小。因此,使误差最小化。

img

距离度量

除了联系,我们还可以指定一些最常用的距离度量。

  • 欧几里得:也被称为毕达哥拉斯式或直线距离。它通过测量两点之间经过的线段的长度来计算空间中两点之间的距离。它使用毕达哥拉斯定理,距离值是方程的结果*(c)*。
c2=a2+b2c^2 = a^2 + b^2
  • 曼哈顿:也叫城市街区,出租车距离。它是两点所有维度上的测量值的绝对差异之和。如果这些维度是两个,它就类似于走一个街区时向右再向左。

img

  • Minkowski:它是欧氏距离和曼哈顿距离的概括。它是一种基于绝对差异来计算距离的方法,以闵可夫斯基度量的顺序来计算距离 p.尽管它对任何p>0都有定义,但很少用于1、2和∞(无限)以外的值。当p=1时,闵可夫斯基距离与曼哈顿距离相同,而当p=2时,与欧几里得距离相同。
D\\left(X,Y\\right) = \\left(\\sum\_{i=1}^n |x\_i-y\_i|^p\\right)^{frac{1}{p}

img

  • Chebyshev:也被称为棋盘距离。它是Minkowski距离的极端情况。当我们用无穷大作为参数p的值时*(p=∞)*,我们最终得到一个将距离定义为坐标间最大绝对差的度量。
  • 余弦:它是两个点序列,或向量之间的角度余弦距离。余弦相似度是矢量的点积除以其长度的乘积。
  • Jaccard:衡量有限的点集之间的相似性。它被定义为每个集合(交集)中的共同点的总点数(cardinality)除以两个集合(联合)的总点数(cardinality)。
  • Jensen-Shannon:以Kullback-Leibler分歧为基础。它考虑点的概率分布,并衡量这些分布之间的相似性。它是概率论和统计学的一种流行方法。

**注:**关于可用链接的完整列表,请访问Scipy链接文档
另外,关于可用度量的完整列表,以及它们的用途,请访问SciPy点距离文档

我们为树状图选择了WardEuclidean,因为它们是最常用的方法和度量。它们通常会给出很好的结果,因为Ward是基于最小化误差来链接点的,而Euclidean在低维度上效果很好。

在这个例子中,我们正在处理营销数据的两个特征(列),以及200个观察值或行。由于观察值的数量大于特征的数量(200>2),我们是在一个低维空间工作。

当特征数*(f)大于观察数(N)--大多写成f>>N*,这意味着我们有一个高维空间

如果我们要包括更多的属性,所以我们有超过200个特征,欧氏距离可能不会很好地工作,因为它很难在一个非常大的空间中测量所有的小距离,而这个空间只会变得更大。换句话说,欧氏距离的方法很难处理数据的稀疏性。这是一个被称为 "维度诅咒"的问题。距离值会变得非常小,就像它们在更大的空间中被 "稀释 "了一样,被扭曲了,直到它们变成了0。

注意:如果你曾经遇到过f>>p的数据集,你可能会使用其他的距离度量,比如马哈拉诺比斯距离。另外,你也可以通过使用**主成分分析(PCA)**来减少数据集的维度。特别是在对生物测序数据进行聚类时,这个问题经常出现。

我们已经讨论了指标、联系,以及每一个指标会如何影响我们的结果。现在让我们继续树状图分析,看看它如何给我们提供数据集中的集群数量的指示。

在树状图中找到一个有趣的集群数量,就等于找到没有任何垂直线的最大水平空间(垂直线最长的空间)。这意味着聚类之间有更多的分离。

我们可以画一条穿过该最长距离的水平线。

plt.figure(figsize=(10, 7))
plt.title("Customers Dendogram with line")
clusters = shc.linkage(selected_data, 
            method='ward', 
            metric="euclidean")
shc.dendrogram(clusters)
plt.axhline(y = 125, color = 'r', linestyle = '-')

img

找到水平线后,我们计算我们的垂直线被它穿过多少次--在这个例子中,是5次。因此,5似乎是一个很好的指示,表明了在它们之间有最多距离的集群的数量。

注意:在选择聚类的数量时,树状图应该只被看作是一个参考。它很容易使这个数字出现偏差,而且完全受联系类型和距离度量的影响。在进行深入的聚类分析时,建议查看具有不同联系和度量的树状图,并查看前三条线产生的结果,在这三条线中,聚类之间的距离是最大的。

实施聚类分级聚类法

使用原始数据

到目前为止,我们已经为我们的数据集计算出了建议的聚类数量,这与我们的初始分析和PCA分析相吻合。现在我们可以使用Scikit-LearnAgglomerativeClustering 创建我们的聚类层次聚类模型,并通过labels_ 找出营销点的标签。

from sklearn.cluster import AgglomerativeClustering

clustering_model = AgglomerativeClustering(n_clusters=5, affinity='euclidean', linkage='ward')
clustering_model.fit(selected_data)
clustering_model.labels_

这就导致了。

array([4, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4, 3,
       4, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4, 1,
       4, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
       1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
       1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
       1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 2, 1, 2, 0, 2, 0, 2,
       1, 2, 0, 2, 0, 2, 0, 2, 0, 2, 1, 2, 0, 2, 1, 2, 0, 2, 0, 2, 0, 2,
       0, 2, 0, 2, 0, 2, 1, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2,
       0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2,
       0, 2])

我们已经调查了很多,才走到这一步。那么这些标签是什么意思呢?在这里,我们的数据中的每个点都被标记为从0到4的一组。

data_labels = clustering_model.labels_
sns.scatterplot(x='Annual Income (k$)', 
                y='Spending Score (1-100)', 
                data=selected_data, 
                hue=data_labels,
                pallete="rainbow").set_title('Labeled Customer Data')

img

这就是我们最终的聚类数据。你可以看到用颜色编码的数据点以五个群组的形式出现。

右下方的数据点(标签:0 ,紫色数据点)属于高工资但低消费的客户。这些是谨慎花钱的顾客。

同样,右上方的客户(标签:2 ,绿色数据点),是拥有高工资和高消费的客户。这些是公司的目标客户类型。

中间的顾客(标签:1 ,蓝色数据点)是具有平均收入和平均支出的顾客。属于这一类型的客户数量最多。鉴于这些客户数量庞大,公司也可以将他们作为目标客户。

左下角的顾客(标签:4 ,红色)是低工资和低消费的顾客,他们可能通过提供促销活动来吸引。

最后,左上角的顾客(标签:3 ,橙色数据点)是那些高收入和低消费的顾客,他们是营销的理想目标。

使用PCA的结果

如果我们处在一个不同的场景中,我们必须降低数据的维度。我们也可以很容易地绘制聚类的PCA结果。这可以通过创建另一个聚类模型并为每个主成分获得一个数据标签来实现。

clustering_model_pca = AgglomerativeClustering(n_clusters=5, affinity='euclidean', linkage='ward')
clustering_model_pca.fit(pcs)

data_labels_pca = clustering_model_pca.labels_

sns.scatterplot(x=pc1_values, 
                y=pc2_values,
                hue=data_labels_pca,
                palette="rainbow").set_title('Labeled Customer Data Reduced with PCA')

img

观察一下,两个结果都非常相似。主要的区别是,用原始数据得出的第一个结果更容易解释。可以清楚地看到,客户可以按照他们的年收入和消费分数分为五组。虽然,在PCA方法中,我们把所有的特征都考虑在内,就像我们可以看每个特征所解释的方差一样,但这是一个更难掌握的概念,尤其是在向市场部报告时。

我们对数据的转化越少越好。

如果你有一个非常大而复杂的数据集,其中你必须在聚类之前进行降维处理--尝试分析每个特征和它们的残差之间的线性关系,以支持PCA的使用,并增强过程的可解释性。通过为每对特征做一个线性模型,你将能够了解这些特征是如何互动的。

如果数据量太大,不可能绘制出成对的特征,那就选择一个你的数据样本,尽可能的平衡和接近正态分布,先在样本上进行分析,了解它,微调它--以后再应用于整个数据集。

你总是可以根据你的数据性质(线性、非线性)选择不同的聚类可视化技术,并在必要时结合或测试所有的技术。

更进一步--手持式端到端项目

你好奇的天性使你想更进一步?我们建议查看我们的 指导性项目: "实际操作房价预测--Python中的机器学习".

在这个指导项目中,你将学习如何建立强大的传统机器学习模型以及深度学习模型,利用集合学习和追踪元学习者,从Scikit-Learn和Keras模型袋中预测房价。

使用建立在Tensorflow之上的深度学习API--Keras,我们将对架构进行实验,建立一个堆叠模型的集合,并训练一个元学习器神经网络(一级模型)来计算出房子的价格。

深度学习是惊人的--但在诉诸于它之前,建议也尝试用更简单的技术解决问题,比如用浅层学习算法。我们的基线性能将以随机森林回归算法为基础。此外,我们还将通过Scikit-Learn探索如何通过袋装投票等技术创建模型集合。

这是一个端到端的项目,和所有的机器学习项目一样,我们将从探索性数据分析开始,然后是数据预处理,最后是建立浅层深层学习模型,以适应我们之前探索和清理过的数据。

结论

当涉及到未标记的数据时,聚类技术可以非常方便地使用。由于现实世界中的大多数数据都是无标签的,而且注释数据的成本较高,所以聚类技术可以用来标记无标签的数据。

在本指南中,我们带来了一个真实的数据科学问题,因为聚类技术在很大程度上被用于营销分析(也包括生物分析)。我们还解释了许多调查步骤,以达到一个好的分层聚类模型,以及如何阅读树状图,并质疑PCA是否是一个必要步骤。我们的主要目的是,涵盖了一些陷阱和不同的场景,在这些场景中我们可以找到层次聚类的方法。

祝你聚类愉快!