介绍
在本指南中,我们将重点介绍实施 使用 Scikit-Learn 的层次聚类算法 解决营销问题。
阅读指南后,您将了解:
- 何时应用层次聚类
- 如何可视化数据集以了解它是否适合聚类
- 如何基于数据集预处理特征和设计新特征
- 如何使用 PCA 降低数据集的维数
- 如何使用和阅读树状图来分隔组
- 应用于树状图和聚类算法的不同链接方法和距离度量有哪些?
- 什么是凝聚和分裂聚类策略以及它们如何工作
- 如何使用 Scikit-Learn 实现凝聚层次聚类
- 处理聚类算法时最常见的问题是什么以及如何解决它们
请注意: 您可以下载包含本指南中所有代码的笔记本 点击此处.
动机
想象一个场景,您是与营销部门交互的数据科学团队的一员。 营销部门一直在收集客户购物数据,他们想根据收集到的数据了解是否有 客户之间的相似之处. 这些相似之处将客户划分为不同的群体,拥有客户群体有助于确定活动、促销、转化的目标,并建立更好的客户关系。
有什么方法可以帮助您确定哪些客户是相似的? 他们中有多少人属于同一组? 有多少个不同的组?
回答这些问题的一种方法是使用 集群 算法,如K-Means、DBSCAN、Hierarchical Clustering等。一般来说,聚类算法是寻找数据点之间的相似性并将它们分组。
在这种情况下,我们的营销数据相当小。 我们只有 200 位客户的信息。 考虑到营销团队,重要的是我们可以清楚地向他们解释如何根据集群数量做出决策,从而向他们解释算法的实际工作原理。
由于我们的数据很小,可解释性是一个主要因素,我们可以利用 层次聚类 来解决这个问题。 这个过程也被称为 层次聚类分析 (HCA).
HCA 的优点之一是它是可解释的,并且在小型数据集上运行良好。
在这种情况下要考虑的另一件事是 HCA 是 无监督 算法。 在对数据进行分组时,我们无法验证我们是否正确识别用户属于特定组(我们不知道这些组)。 我们没有标签可以比较我们的结果。 如果我们正确识别了这些组,稍后将由营销部门每天确认(通过投资回报率、转化率等指标来衡量)。
现在我们已经了解了我们要解决的问题以及如何解决它,我们可以开始查看我们的数据了!
简要探索性数据分析
请注意: 您可以下载本指南中使用的数据集 点击此处.
下载数据集后,注意它是一个 CSV(逗号分隔值) 文件名为 shopping-data.csv
. 为了更容易探索和操作数据,我们将其加载到 DataFrame
使用熊猫:
import pandas as pd
path_to_file = 'home/projects/datasets/shopping-data.csv'
customer_data = pd.read_csv(path_to_file)
营销部门表示,它已经收集了 200 条客户记录。 我们可以使用 200 行检查下载的数据是否完整 shape
属性。 它将分别告诉我们有多少行和列:
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
, 收集了 Genre
, Age
, Annual Income
(以千美元计),以及 Spending Score
1 位客户中的每一位从 100 增加到 200。 当被要求澄清时,他们说 Spending Score
列表示一个人在商场花钱的频率,从 1 到 100花钱最多的人。
让我们快速看一下这个分数的分布,以检查我们数据集中用户的消费习惯。 那是熊猫的地方 hist()
方法进来帮助:
customer_data['Spending Score (1-100)'].hist()
通过查看直方图,我们看到超过 35 位客户的得分介于 40
和 60
, 那么小于 25 的分数在 70
和 80
. 所以我们的大多数客户都是 平衡支出者,其次是中等至高消费人群。 我们还可以看到后面有一行 0
,分布的左侧,100 之前的另一行,分布的右侧。 这些空格可能意味着该分布不包含非支出者,其得分为 0
,并且也没有得分为 的高支出者 100
.
为了验证这是否属实,我们可以查看分布的最小值和最大值。 这些值可以很容易地作为描述性统计的一部分找到,因此我们可以使用 describe()
了解其他数值分布的方法:
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
is 1
最大值是 99
. 所以我们没有 0
or 100
得分消费者。 那我们再来看看转置的其他列 describe
桌子。 看的时候 mean
和 std
列,我们可以看到 Age
此 mean
is 38.85
和 std
约 13.97
. 同样的情况发生在 Annual Income
,一个 mean
of 60.56
和 std
26.26
,和 Spending Score
用 mean
of 50
和 std
of 25.82
. 对于所有功能, mean
与标准差相差甚远,这表明 我们的数据具有很高的可变性.
为了更好地理解我们的数据如何变化,让我们绘制 Annual Income
分配:
customer_data['Annual Income (k$)'].hist()
这会给我们:
请注意,在直方图中,我们的大部分数据(超过 35 个客户)都集中在数字附近 60
, 在我们的 mean
, 在水平轴上。 但是当我们走向分布的末端时会发生什么? 当向左移动时,从 60.560 美元的均值开始,我们将遇到的下一个值是 34.300 美元 – 均值(60.560 美元)减去标准差(26.260 美元)。 如果我们进一步远离数据分布的左侧,则应用类似的规则,我们从当前值($26.260)中减去标准差($34.300)。 因此,我们会遇到 8.040 美元的价值。 请注意我们的数据如何从 60 万美元迅速增加到 8 千美元。 它每次都在“上涨”26.260 美元——变化很大,这就是我们具有如此高可变性的原因。
数据的可变性和大小在聚类分析中很重要,因为大多数聚类算法的距离测量对数据量级很敏感。 大小的差异可以通过使一个点看起来比实际更接近或更远来改变聚类结果,从而扭曲数据的实际分组。
到目前为止,我们已经看到了数据的形状、一些分布和描述性统计数据。 使用 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
. 在这个阶段,重要的是我们要记住将哪些特征添加到聚类模型中看起来很有趣。 如果我们想将 Genre 列添加到我们的模型中,我们需要将其值从 明确的 至 数字的.
我们来看看如何 Genre
通过快速查看我们数据的前 5 个值来填充:
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
好像只有 Female
和 Male
类别。 我们可以通过查看其独特的价值来确定这一点 unique
:
customer_data['Genre'].unique()
这证实了我们的假设:
array(['Male', 'Female'], dtype=object)
到目前为止,我们知道我们只有两种类型,如果我们打算在我们的模型上使用这个功能, Male
可以转化为 0
和 Female
至 1
. 检查流派之间的比例也很重要,看看它们是否平衡。 我们可以做到这一点 value_counts()
方法及其参数 normalize=True
显示之间的百分比 Male
和 Female
:
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
.
建议: 在本指南中,我们仅提供简短的探索性数据分析。 但你可以走得更远,你也应该走得更远。 您可以查看是否存在基于类型和年龄的收入差异和评分差异。 这不仅丰富了分析,还带来了更好的模型结果。 要更深入地了解探索性数据分析,请查看 EDA 章节中的“动手房价预测 - Python 中的机器学习“ 指导项目。
在推测可以用分类 - 或分类 - 做什么之后 Genre
和 Age
列,让我们应用已经讨论过的内容。
编码变量和特征工程
让我们从划分开始 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
值到这些间隔中,我们可以使用 Pandas cut()
方法将它们分成垃圾箱,然后将垃圾箱分配给一个新的 Age Groups
柱:
intervals = [15, 20, 30, 40, 50, 60, 70]
col = customer_data['Age']
customer_data['Age Groups'] = pd.cut(x=col, bins=intervals)
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 岁的客户。这对营销部门来说也是很好的信息。
目前,我们有两个分类变量, Age
和 Genre
,我们需要将其转换为数字才能在我们的模型中使用。 有许多不同的方法可以实现这种转变——我们将使用 Pandas get_dummies()
为每个区间和流派创建一个新列,然后用 0 和 1 填充其值的方法——这种操作称为 一键编码. 让我们看看它的外观:
customer_data_oh = pd.get_dummies(customer_data)
customer_data_oh
这将为我们提供结果表的预览:
通过输出,很容易看出该列 Genre
被分成几列—— Genre_Female
和 Genre_Male
. 当顾客是女性时, Genre_Female
等于 1
,当客户是男性时,它等于 0
.
此外, Age Groups
列被分成6列,每个区间一列,例如 Age Groups_(15, 20]
, Age Groups_(20, 30]
, 等等。 在相同的方式 Genre
,当客户年满 18 岁时, Age Groups_(15, 20]
价值是 1
并且所有其他列的值为 0
.
优点 one-hot 编码的优点在于表示列值的简单性,很容易理解正在发生的事情——而 坏处 是我们现在创建了 8 个额外的列,以总结我们已经拥有的列。
警告:如果您有一个数据集,其中 one-hot 编码的列数超过了行数,最好采用另一种编码方法来避免数据维度问题。
One-hot 编码还会给我们的数据添加 0,使其更加稀疏,这对于一些对数据稀疏性敏感的算法来说可能是个问题。
对于我们的集群需求,one-hot 编码似乎可以工作。 但是我们可以绘制数据,看看是否真的有不同的组可供我们聚类。
基本绘图和降维
我们的数据集有 11 列,我们可以通过一些方法来可视化这些数据。 第一个是在 10 维中绘制它(祝你好运)。 十因为 Customer_ID
列不被考虑。 第二个是通过绘制我们的初始数字特征,第三个是将我们的 10 个特征转换为 2——因此,执行降维。
绘制每对数据
由于绘制 10 维有点不可能,我们将选择第二种方法——我们将绘制我们的初始特征。 我们可以选择其中两个进行聚类分析。 我们可以看到所有数据对组合的一种方法是使用 Seaborn pairplot()
:
import seaborn as sns
customer_data = customer_data.drop('CustomerID', axis=1)
sns.pairplot(customer_data)
其中显示:
一目了然,我们可以发现似乎有数据组的散点图。 一个看起来很有趣的是散点图,它结合了 Annual Income
和 Spending Score
. 请注意,其他变量散点图之间没有明确的分隔。 至多,我们也许可以说,在 Spending Score
vs Age
散点图。
两个散点图都包括 Annual Income
和 Spending Score
本质上是一样的。 我们可以看到它两次,因为 x 轴和 y 轴交换了。 通过查看其中任何一个,我们可以看到似乎是五个不同的组。 让我们用 Seaborn 绘制这两个特征 scatterplot()
仔细看看:
sns.scatterplot(x=customer_data['Annual Income (k$)'],
y=customer_data['Spending Score (1-100)'])
通过仔细观察,我们绝对可以区分 5 组不同的数据。 似乎我们的客户可以根据他们一年的收入和花费进行分组。 这是我们分析中的另一个相关点。 重要的是,我们只考虑两个特征来对我们的客户进行分组。 我们拥有的关于他们的任何其他信息都没有进入等式。 这赋予了分析意义——如果我们知道客户的收入和支出,我们可以很容易地找到我们需要的相似之处。
那太棒了! 到目前为止,我们已经有两个变量来构建我们的模型。 除了这代表什么之外,它还使模型更简单、简洁且更易于解释。
查看我们的 Git 学习实践指南,其中包含最佳实践、行业认可的标准以及随附的备忘单。 停止谷歌搜索 Git 命令,实际上 学习 它!
请注意: 数据科学通常倾向于尽可能简单的方法。 不仅因为它更容易为业务解释,还因为它更直接——具有 2 个功能和一个可解释的模型,模型在做什么以及它是如何工作的很清楚。
使用 PCA 后绘制数据
看起来我们的第二种方法可能是最好的,但让我们也看看我们的第三种方法。 当我们无法绘制数据因为它有太多维度,或者当没有数据集中或组中没有明确的分离时,它会很有用。 当这些情况发生时,建议尝试使用一种名为的方法来减少数据维度 主成分分析(PCA).
请注意: 大多数人在可视化之前使用 PCA 进行降维。 还有其他方法可以帮助在聚类之前进行数据可视化,例如 基于密度的噪声应用空间聚类 (DBSCAN) 和 自组织地图 (SOM) 聚类。 两者都是聚类算法,但也可以用于数据可视化。 由于聚类分析没有黄金标准,因此比较不同的可视化和不同的算法很重要。
PCA 将减少我们数据的维度,同时尽可能多地保留其信息。 让我们先了解一下 PCA 的工作原理,然后我们可以选择将数据缩减到多少个数据维度。
对于每一对特征,PCA 查看一个变量的较大值是否与另一个变量的较大值对应,并且对较小的值执行相同的操作。 因此,它本质上是计算特征值彼此之间的差异程度——我们称其为 协方差. 然后将这些结果组织成一个矩阵,得到一个 协方差矩阵.
获得协方差矩阵后,PCA 尝试找到最能解释它的特征的线性组合——它适合线性模型,直到它识别出解释 最多 方差量.
备注:PCA是一种线性变换,线性对数据的规模很敏感。 因此,当所有数据值都在同一尺度上时,PCA 效果最好。 这可以通过减去列来完成 意味着 从它的值,并将结果除以其标准偏差。 被称为 数据标准化. 在使用 PCA 之前,请确保数据已缩放! 如果您不确定如何,请阅读我们的 “使用 Scikit-Learn 在 Python 中进行机器学习的特征缩放数据”!
找到最佳线(线性组合)后,PCA 得到其轴的方向,称为 特征向量,及其线性系数, 特征值. 特征向量和特征值(或轴方向和系数)的组合是 主要成分 主成分分析。 那就是当我们可以根据每个特征的解释方差来选择我们的维度数时,通过了解我们想要保留或丢弃哪些主要成分,这取决于它们解释了多少方差。
在获得主成分后,PCA 使用特征向量形成特征向量,将数据从原始轴重新定向到由主成分表示的轴——这就是数据维度减少的方式。
请注意: 这里要考虑的一个重要细节是,由于其线性性质,PCA 将把大部分解释的方差集中在第一主成分中。 因此,在查看解释方差时,通常我们的前两个组件就足够了。 但这在某些情况下可能会产生误导——因此在聚类时尝试继续比较不同的图和算法,看看它们是否具有相似的结果。
在应用 PCA 之前,我们需要在 Age
列或 Age Groups
我们之前的 one-hot 编码数据中的列。 由于两列代表相同的信息,因此两次引入它会影响我们的数据方差。 如果 Age Groups
列被选中,只需删除 Age
使用 Pandas 的列 drop()
方法并将其重新分配给 customer_data_oh
变量:
customer_data_oh = customer_data_oh.drop(['Age'], axis=1)
customer_data_oh.shape
现在我们的数据有 10 列,这意味着我们可以按列获得一个主成分,并通过测量引入一个新维度在多大程度上解释了我们的数据方差来选择我们将使用的主成分。
让我们用 Scikit-Learn 做到这一点 PCA
. 我们将计算每个维度的解释方差,由下式给出 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%。 这意味着前两个维度已经解释了我们 2% 的数据。 因此,我们可以应用具有 99 个分量的 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)
PCA 之后的数据图与仅使用两列数据而不使用 PCA 的图非常相似。 请注意,形成组的点更接近,并且在 PCA 之后比之前更集中。
用树状图可视化层次结构
到目前为止,我们已经探索了数据、one-hot 编码的分类列,确定了哪些列适合聚类,并降低了数据维数。 这些图表明我们的数据中有 5 个集群,但还有另一种方法可以可视化我们的点之间的关系并帮助确定集群的数量——通过创建一个 树状图 (通常拼写为树状图)。 丹德罗 手段 树 用拉丁文
树状图 是数据集中点链接的结果。 它是层次聚类过程的可视化表示。 层次聚类过程是如何工作的? 嗯......这取决于 - 可能是您在数据科学中已经听到很多的答案。
了解层次聚类
当。。。的时候 层次聚类算法 (HCA) 开始链接点并找到聚类,它可以先将点分成 2 个大组,然后将这两个组中的每一个分成较小的 2 个组,总共有 4 个组,即 分裂 和 自上而下 的方法。
或者,它可以做相反的事情——它可以查看所有数据点,找到彼此更近的 2 个点,将它们链接起来,然后找到与这些链接点最接近的其他点,并继续构建 2 个组来自 自下而上。 哪一个是 凝聚的 我们将开发的方法。
执行凝聚层次聚类的步骤
为了使凝聚式方法更加清晰,有以下步骤: 凝聚层次聚类 (AHC) 算法:
- 一开始,将每个数据点视为一个集群。 因此,开始时的簇数将为 K - 而 K 是表示数据点数的整数。
- 通过连接两个最近的数据点来形成一个集群,从而产生 K-1 个集群。
- 通过加入两个最接近的集群来形成更多集群,从而产生 K-2 集群。
- 重复以上三个步骤,直到形成一个大簇。
备注:为简化起见,我们在第 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")
selected_data = customer_data_oh.iloc[:, 1:3]
clusters = shc.linkage(selected_data,
method='ward',
metric="euclidean")
shc.dendrogram(Z=clusters)
plt.show()
脚本的输出如下所示:
在上面的脚本中,我们用我们的点生成了集群和子集群,定义了我们的点如何链接(通过应用 ward
方法),以及如何测量点之间的距离(通过使用 euclidean
公制)。
借助树状图,可以将描述的 DHC 和 AHC 过程可视化。 要可视化自上而下的方法,从树状图的顶部开始并向下,并做相反的事情,从向下开始向上移动以可视化自下而上的方法。
联动方法
还有很多其他的联动方法,通过更多地了解它们的工作原理,您将能够根据您的需要选择合适的一种。 除此之外,它们中的每一个在应用时都会产生不同的结果。 聚类分析没有固定的规则,如果可能的话,研究问题的性质,看看哪个最适合它,测试不同的方法,检查结果。
一些链接方法是:
- 单联动: 也称为 最近邻 (NN). 集群之间的距离由它们最近的成员之间的距离定义。
- 完整联动: 也称为 最远邻居 (FN), 最远点算法或 Voor Hees 算法. 集群之间的距离由其最远成员之间的距离定义。 这种方法计算成本很高。
- 平均联动:也称为 UPGMA (具有算术平均值的未加权对组方法). 如果两个聚类合并,则计算每个聚类的点数相对于两个聚类的点数的百分比。
- 加权连杆:也称为 世界太平洋汽车制造商协会 (带算术平均值的加权对组方法). 两个集群的各个点有助于一个较小的集群和一个较大的集群之间的聚合距离。
- 质心连杆: 也称为 UPGMC公司 (使用质心的未加权对组方法). 为每个聚类计算由所有点(质心)的平均值定义的点,聚类之间的距离是它们各自质心之间的距离。
- 病房联动:也称为 小姐 (平方和的最小增加). 它指定两个集群之间的距离,计算平方和误差 (ESS),并根据较小的 ESS 依次选择下一个集群。 Ward 方法力求在每个步骤中最小化 ESS 的增加。 因此,尽量减少错误。
距离度量
除了链接之外,我们还可以指定一些最常用的距离度量:
- 欧几里德: 也称为 毕达哥拉斯或直线 距离。 它通过测量通过它们之间的线段的长度来计算空间中两点之间的距离。 它使用勾股定理,距离值是结果 (C) 等式的:
$$
c^2 = a^2 + b^2
$$
- 曼哈顿: 也叫 城市街区, 出租车 距离。 它是两点的所有维度上的度量之间的绝对差之和。 如果这些维度是两个,则类似于步行一个街区时先右后左。
- 闵可夫斯基:它是欧几里得距离和曼哈顿距离的概括。 这是一种基于 Minkowski 度量阶的绝对差来计算距离的方法 p. 虽然它被定义为任何 p> 0,它很少用于除 1、2 和 ∞(无限)以外的值。 闵可夫斯基距离与曼哈顿距离相同 P = 1, 和欧几里得距离一样 P = 2.
$$
Dleft(X,Yright) = left(sum_{i=1}^n |x_i-y_i|^pright)^{frac{1}{p}}
$$
- 切比雪夫:也称为 棋盘 距离。 这是闵可夫斯基距离的极端情况。 当我们使用无穷大作为参数的值时 p (p = ∞),我们最终得到一个度量,它将距离定义为坐标之间的最大绝对差。
- 余弦:它是两个点序列或向量之间的角余弦距离。 余弦相似度是向量的点积除以它们的长度的乘积。
- 杰卡德:测量有限点集之间的相似性。 它定义为每个集合(交点)中公共点的总点数(基数)除以两个集合(并集)总点的总点数(基数)。
- 詹森-香农: 基于 Kullback-Leibler 散度。 它考虑点的概率分布并测量这些分布之间的相似性。 它是概率论和统计学的一种流行方法。
我们选了 沃德 和 欧几里德 对于树状图,因为它们是最常用的方法和度量。 它们通常会给出很好的结果,因为 Ward 链接点基于最小化错误,而欧几里得在较低维度上工作得很好。
在此示例中,我们使用营销数据的两个特征(列)和 200 个观察值或行。 由于观察的数量大于特征的数量(200 > 2),我们在低维空间中工作。
当特征数 (六) 大于观察次数 (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 = '-')
找到水平线后,我们计算垂直线被它穿过的次数——在这个例子中,是 5 次。 所以 5 似乎很好地表明了它们之间距离最大的集群的数量。
备注:树状图在用于选择簇数时应仅作为参考。 它可以很容易地得到这个数字,并且完全受链接类型和距离度量的影响。 在进行深入的聚类分析时,建议查看具有不同链接和度量的树状图,并查看前三行生成的结果,其中聚类之间的距离最大。
实施凝聚层次聚类
使用原始数据
到目前为止,我们已经为我们的数据集计算了建议的聚类数量,这证实了我们的初始分析和 PCA 分析。 现在我们可以使用 Scikit-Learn 创建凝聚层次聚类模型 AgglomerativeClustering
并找出营销点的标签 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')
这是我们最终的聚类数据。 您可以看到五个集群形式的颜色编码数据点。
右下角的数据点(标签: 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')
观察到两个结果非常相似。 主要区别在于原始数据的第一个结果更容易解释。 可以清楚地看到,客户可以根据他们的年收入和支出得分分为五组。 虽然在 PCA 方法中,我们正在考虑我们的所有特征,但我们可以尽可能多地查看每个特征所解释的差异,但这是一个更难掌握的概念,尤其是在向营销部门报告时。
我们必须转换数据的次数越少越好。
如果您有一个非常大且复杂的数据集,您必须在聚类之前执行降维 - 尝试分析每个特征及其残差之间的线性关系,以支持 PCA 的使用并增强过程的可解释性。 通过为每对特征制作一个线性模型,您将能够了解这些特征是如何相互作用的。
如果数据量很大,就无法绘制特征对,选择数据样本,尽可能平衡且接近正态分布,然后先对样本进行分析,了解它,微调它 - 稍后将其应用于整个数据集。
您始终可以根据数据的性质(线性、非线性)选择不同的聚类可视化技术,并在必要时组合或测试所有这些技术。
结论
当涉及到未标记的数据时,聚类技术非常方便。 由于现实世界中的大多数数据都是未标记的,并且对数据进行注释具有较高的成本,因此可以使用聚类技术来标记未标记的数据。
在本指南中,我们提出了一个真正的数据科学问题,因为聚类技术主要用于营销分析(以及生物分析)。 我们还解释了获得良好层次聚类模型的许多调查步骤以及如何读取树状图,并质疑 PCA 是否是必要的步骤。 我们的主要目标是涵盖我们可以找到层次聚类的一些陷阱和不同场景。
快乐聚类!