从零开始掌握聚类分析:探索数据背后的隐藏模式与应用实例
- 基本概念
- 聚类分类
- 聚类算法的评价指标
-
- (1)内部指标
-
- [轮廓系数(Silhouette Coefficient)](#轮廓系数(Silhouette Coefficient))
- [DB指数(Davies-Bouldin Index)](#DB指数(Davies-Bouldin Index))
- Dunn指数
- (2)外部指标
-
- Jaccard系数
- Fowlkes-Mallows指数(FMI)
- [兰德指数(Rand Index)](#兰德指数(Rand Index))
- 常见聚类算法简介
-
- 基于划分的聚类算法------K-Means算法
- 基于密度的聚类算法------DBSCAN算法
- [基于层次的聚类算法------AGNES 算法](#基于层次的聚类算法——AGNES 算法)
- [基于网格的聚类算法------STING(Statistical Information Grid)和 CLIQUE(Clustering In QUEst)](#基于网格的聚类算法——STING(Statistical Information Grid)和 CLIQUE(Clustering In QUEst))
- [基于模型的聚类算法------高斯混合模型(Gaussian Mixture Model, GMM)](#基于模型的聚类算法——高斯混合模型(Gaussian Mixture Model, GMM))
- 聚类算法实战------K-Means算法------购物中心客户分群分析
- 聚类算法实战------DBSCAN算法------城市公共自行车站点分布分析
- 总结
基本概念
聚类(Clustering) 是一种无监督学习方法,其目的是将一组数据点分成若干个簇(clusters),使得同一簇内的数据点彼此之间具有较高的相似性,而不同簇之间的数据点相似性较低。然而一共有几个蔟不是事先给定的,是由待分类的数据的特征决定的。简单来说,聚类的目的就是通过无监督学习的方法,发现数据中的自然分组结构,将数据分为几堆这样,
那什么是簇 呢?簇是一组具有相似特征 的数据点或对象组成的集合。简单来说,簇是数据点按照某种相似性度量被分组的结果,其中每个簇内的成员彼此之间比与其他簇的成员更加相似。就像在一个菜篮子里面,通过辨识篮子里面蔬菜的特征,将蔬菜分成了几类、几堆。
聚类分类
根据不同的聚类原理和算法特点,聚类算法可以分为以下几类:
- 基于划分的聚类算法:如K-Means、K-Medoids、CLARANS等。这类算法通过迭代优化目标函数,将数据集划分为预定的K个簇。
- 基于密度的聚类算法:如DBSCAN、OPTICS、Mean Shift等。这类算法通过密度可达性来识别簇,能够识别出任意形状的簇。
- 基于层次的聚类算法:如AGNES(自底向上)、DIANA(自顶向下)等。这类算法通过逐步合并或分裂现有的簇,形成一棵聚类树。
- 基于网格的聚类算法:如STING、WaveCluster等。这类算法将数据空间划分为有限数量的单元格,形成网格结构,然后在网格上进行聚类。
- 基于模型的聚类算法:如高斯混合模型(GMM)、隐马尔可夫模型(HMM)等。这类算法假设数据由一系列的概率分布生成,通过优化模型参数来进行聚类。
聚类算法的评价指标
聚类算法的评价指标又称为性能度量指标,透过这个指标我们可以分析聚类算法的聚类结果的好坏。通常认为,通过聚类算法之后,簇内相似度越高算法分类效果越好,蔟间相似度越低算法分类效果越好,反之。
聚类算法的评价通常依赖于内部指标 和外部指标两大类。下面一一讲解着两种指标:
(1)内部指标
内部指标主要衡量聚类结果的紧凑性和分离性,不需要外部参考模型或基准数据,它是直接通过考察聚类结果得出的。常见的内部指标包括:
轮廓系数(Silhouette Coefficient)
轮廓系数是衡量聚类效果的一个综合指标,它结合了簇内凝聚度和簇间分离度。对于每个样本点,轮廓系数定义为:
s ( i ) = b ( i ) − a ( i ) max { a ( i ) , b ( i ) } s(i) = \frac{b(i) - a(i)}{\max\{a(i), b(i)\}} s(i)=max{a(i),b(i)}b(i)−a(i)
其中:
- a ( i ) a(i) a(i) 是样本点 i i i 到同一簇内其他点的平均距离,衡量了簇内的凝聚度。
- b ( i ) b(i) b(i) 是样本点 i i i 到最近的其他簇中点的平均距离,衡量了簇间的分离度。
轮廓系数的取值范围为 [ − 1 , 1 ] [-1, 1] [−1,1],其中: s ( i ) s(i) s(i) 接近 1 表示样本点 i i i 被很好地分配到了正确的簇中、接近 0 表示样本点 i i i 可能处于两个簇的边界上、接近 -1 表示样本点 i i i 被分配到了错误的簇中。
对于整个数据集,轮廓系数是所有样本点的轮廓系数的平均值。
DB指数(Davies-Bouldin Index)
DB指数是衡量聚类效果的一个指标,它基于簇内紧凑度和簇间分离度的比值。DB指数的公式为:
D B = 1 k ∑ i = 1 k max j ≠ i ( σ i + σ j d ( c i , c j ) ) DB = \frac{1}{k} \sum_{i=1}^{k} \max_{j \neq i} \left( \frac{\sigma_i + \sigma_j}{d(c_i, c_j)} \right) DB=k1i=1∑kj=imax(d(ci,cj)σi+σj)
其中:
- k k k 是簇的数量。
- σ i \sigma_i σi 和 σ j \sigma_j σj 分别是簇 i i i 和簇 j j j 的平均簇内距离。
- d ( c i , c j ) d(c_i, c_j) d(ci,cj) 是簇 i i i 和簇 j j j 的中心点之间的距离。
- max j ≠ i \max_{j \neq i} maxj=i 表示对所有的 j j j 不等于 i i i 的簇进行计算。
DB指数的值越小,表示聚类效果越好,因为这意味着簇内距离较小而簇间距离较大。
Dunn指数
Dunn指数是基于簇间最小距离和簇内最大距离 的一个比值,用于衡量聚类的紧密度和分离度。Dunn指数的公式为:
D I = min 1 ≤ i ≤ k { min j ≠ i ( d min ( C i , C j ) max 1 ≤ l ≤ k d max ( C l ) ) } DI = \min_{1 \leq i \leq k} \left\{ \min_{j \neq i} \left( \frac{d_{\min}(C_i, C_j)}{\max_{1 \leq l \leq k} d_{\max}(C_l)} \right) \right\} DI=1≤i≤kmin{j=imin(max1≤l≤kdmax(Cl)dmin(Ci,Cj))}
其中:
- d min ( C i , C j ) d_{\min}(C_i, C_j) dmin(Ci,Cj) 是簇 C i C_i Ci 和簇 $ C_j$ 之间的最小距离。
- d max ( C l ) d_{\max}(C_l) dmax(Cl) 是簇 C l C_l Cl 内的最大距离。
- min 1 ≤ i ≤ k \min_{1 \leq i \leq k} min1≤i≤k 和 min j ≠ i \min_{j \neq i} minj=i 分别表示对所有的簇进行最小值计算。
Dunn指数的值越大,表示聚类效果越好,因为簇间的最小距离相对较小,而簇内的最大距离相对较大,说明簇之间分离得较好,簇内数据点较为紧密。
(2)外部指标
外部指标需要借助外部某个参考模型或已知数据集的真实标签来评估聚类结果的准确性。常见的外部指标包括:
Jaccard系数
Jaccard系数是衡量两个集合相似度的一个指标,它通过比较两个集合的交集和并集的大小来确定它们的相似程度。在聚类分析中,Jaccard系数可以用来衡量两个簇之间的相似度。数学公式如下:
J ( A , B ) = ∣ A ∩ B ∣ ∣ A ∪ B ∣ J(A, B) = \frac{|A \cap B|}{|A \cup B|} J(A,B)=∣A∪B∣∣A∩B∣
其中:
- ∣ A ∩ B ∣ |A \cap B| ∣A∩B∣ 是簇 A 和簇 B 的交集的元素数量。
- ∣ A ∪ B ∣ |A \cup B| ∣A∪B∣ 是簇 A 和簇 B 的并集的元素数量。
Jaccard系数的取值范围是 [0, 1],其中 0 表示两个簇没有交集,1 表示两个簇完全相同。值越大,表示两个簇越相似。
Fowlkes-Mallows指数(FMI)
Fowlkes-Mallows指数是基于簇对的正确匹配和错误匹配来评估聚类效果的指标。它通过计算正确匹配的元素对数量的平方与所有匹配对(正确匹配和错误匹配)的总数之比来衡量聚类的质量。数学公式如下:
F M I = T P 2 T P + F P + F N FMI = \frac{TP^2}{TP + FP + FN} FMI=TP+FP+FNTP2
其中:
- T P TP TP 是正确匹配的元素对数量,即两个簇中都有相同标签的元素对。
- F P FP FP 是错误匹配的元素对数量,即两个簇中标签不同的元素对。
- F N FN FN 是未匹配的元素对数量,即至少有一个簇中没有标签的元素对。
FMI的取值范围是 [0, 1],值越大,表示聚类效果越好。
兰德指数(Rand Index)
兰德指数是衡量聚类结果与真实标签一致性的指标。它通过计算正确匹配的元素对数量与所有可能元素对的数量之比来评估聚类的准确性。数学公式如下:
R I = T P + T N T P + T N + F P + F N RI = \frac{TP + TN}{TP + TN + FP + FN} RI=TP+TN+FP+FNTP+TN
其中:
- T P TP TP 是正确匹配的元素对数量。
- T N TN TN 是两个簇中都没有相同标签的元素对数量。
- F P FP FP 是错误匹配的元素对数量。
- F N FN FN 是未匹配的元素对数量。
RI的取值范围也是 [0, 1],值越大,表示聚类结果与真实标签的一致性越高。
请注意,这些指标的计算通常需要真实的聚类标签,因此在实际应用中,它们主要用于评估聚类算法的性能,特别是在有监督学习环境中。在没有真实标签的情况下,这些指标无法直接应用。
常见聚类算法简介
基于划分的聚类算法------K-Means算法
K-Means算法是一种经典的基于划分的聚类算法,特别适用于大规模数据集。其基本思想是:给定一个数据集和一个整数K,算法通过迭代过程将数据集划分为K个簇,使得每个数据点与其所属簇的中心点(均值)的距离之和最小,最终形成若干个圆形或椭圆形的簇。
数学表示
设我们有 n n n个数据点 { x 1 , x 2 , . . . , x n } \{x_1,x_2,...,x_n\} {x1,x2,...,xn},其中每个 x i ∈ R d x_i\in\mathbb{R}^d xi∈Rd表示一个 d d d维向量。我们的目标是找到 k k k个簇心 { μ 1 , μ 2 , . . . , μ k } \{\mu_1,\mu_2,...,\mu_k\} {μ1,μ2,...,μk},使得所有点到它们所属簇心的距离平方和最小化:
J = ∑ i = 1 k ∑ x j ∈ C i ∣ ∣ x j − μ i ∣ ∣ 2 J=\sum_{i=1}^{k}\sum_{x_j\in C_i}||x_j-\mu_i||^2 J=i=1∑kxj∈Ci∑∣∣xj−μi∣∣2
这里 C i C_i Ci表示第 i i i个簇中的所有点, μ i \mu_i μi是第 i i i个簇的簇心,而 ∣ ∣ x j − μ i ∣ ∣ 2 ||x_j-\mu_i||^2 ∣∣xj−μi∣∣2是欧几里得距离的平方。
算法步骤
K-Means 算法由两个主要步骤组成:分配(Assignment)和更新(Update)。这两个步骤交替进行直到收敛。
1. 初始化
- 选择初始簇心 :随机选择 k k k个数据点作为初始簇心,或者使用更复杂的初始化方法如K-Means++来提高效率。
2. 分配步骤
对于每个数据点 x j x_j xj,计算它与所有簇心之间的距离,并将其分配给最近的那个簇心所在的簇。具体来说,对于每一个数据点 x j x_j xj,我们找到使下式最小化的 i i i:
i = arg min i ′ ∣ ∣ x j − μ i ′ ∣ ∣ 2 i=\arg\min_{i'}||x_j-\mu_{i'}||^2 i=argi′min∣∣xj−μi′∣∣2
这一步骤可以写作:
C i = { x j : ∣ ∣ x j − μ i ∣ ∣ 2 ≤ ∣ ∣ x j − μ i ′ ∣ ∣ 2 , ∀ i ′ ≠ i } C_i=\{x_j:||x_j-\mu_i||^2\leq||x_j-\mu_{i'}||^2,\forall i'\neq i\} Ci={xj:∣∣xj−μi∣∣2≤∣∣xj−μi′∣∣2,∀i′=i}
3. 更新步骤
一旦所有点都被分配给了某个簇,接下来需要重新计算每个簇的新簇心。新簇心是该簇中所有点的平均值:
μ i = 1 ∣ C i ∣ ∑ x j ∈ C i x j \mu_i=\frac{1}{|C_i|}\sum_{x_j\in C_i}x_j μi=∣Ci∣1xj∈Ci∑xj
这里 ∣ C i ∣ |C_i| ∣Ci∣表示簇 C i C_i Ci中的数据点数量。
4. 迭代
重复执行分配和更新步骤,直到满足以下任一条件:
- 簇心不再显著变化(即在两次连续迭代之间,簇心位置的变化非常小)。
- 达到了预设的最大迭代次数。
收敛性
K-Means 算法保证每次迭代都会减少目标函数 J J J,因此它会逐渐收敛到局部最优解。然而,由于K-Means使用梯度下降的一种变体,它可能陷入局部最优而非全局最优。为了减轻这个问题,通常会多次运行算法,每次使用不同的初始簇心,并选择产生最低 J J J值的结果。
缺点与限制
尽管K-Means算法简单且高效,但它也有一些局限性:
- 它假设簇为球形分布,这对于非球形簇效果不佳。
- 对异常值敏感,因为这些点可能会对簇心的位置产生较大影响。
- 需要事先指定 k k k的值,选择不当可能导致不理想的聚类结果。
- 只能处理数值型数据,无法直接应用于分类或文本数据。
希望这个详细的介绍能够帮助你更好地理解K-Means算法的工作机制及其背后的数学原理。如果你有任何疑问或者想要了解更多信息,请随时提问!
基于密度的聚类算法------DBSCAN算法
DBSCAN(Density-Based Spatial Clustering of Applications with Noise)是一种基于密度的聚类算法,能够识别任意形状的簇,并且对噪声点具有鲁棒性。与 K-Means 不同,DBSCAN 不需要预先指定簇的数量,而是根据数据点的密度自动确定簇的数量和形状。它特别适用于发现自然存在的簇结构,并能有效处理噪声数据。
数学表示与关键概念
DBSCAN 的核心思想是通过定义两个参数来确定哪些点属于同一个簇:
- E p s Eps Eps:邻域半径距离,用来定义邻域范围。
- M i n P t s MinPts MinPts:最小点数,定义一个点成为核心点所需的最少邻居数量(包括自身)。
关键概念
- 核心点(Core Point) :如果一个点在其 E p s Eps Eps 范围内至少包含 M i n P t s MinPts MinPts 个点,则该点为核心点。
- 边界点(Border Point) :不是核心点,但在某个核心点的 E p s Eps Eps 范围内的点。
- 噪声点(Noise Point):既不是核心点也不是边界点的数据点。
这些概念决定了点之间的可达性和连通性,从而影响簇的形成。
算法步骤
DBSCAN 算法通过以下详细步骤构建簇:
1. 初始化
- 选择一个未访问的数据点作为起点。
- 检查该点是否为核心点。如果是,则创建一个新的簇;如果不是,则标记为噪声点。
2. 扩展簇
对于每个找到的核心点,从它的邻域开始扩展新的簇。具体过程如下:
- 对于当前点 p p p,如果它是核心点,则创建一个新的簇 C C C,并将所有在 p p p 的 E p s Eps Eps 范围内的点添加到 C C C 中。
- 对于新加入簇的每个点 q q q,如果 q q q 也是核心点,则将 q q q 的所有未访问邻居也加入到 C C C 中,并继续这个过程直到不再有新的点可以加入。
3. 标记非核心点
对于不是核心点但位于某个核心点的 E p s Eps Eps 范围内的点,标记为边界点并分配给相应的簇。
4. 处理噪声
任何既不是核心点也不是边界点的数据点被标记为噪声。
数学公式
设我们有一个数据集 { x 1 , x 2 , . . . , x n } \{x_1, x_2, ..., x_n\} {x1,x2,...,xn},其中 x i ∈ R d x_i \in \mathbb{R}^d xi∈Rd 表示 d d d 维空间中的点。
-
邻域定义 :对于点 x i x_i xi,其在 E p s Eps Eps 范围内的邻域定义为:
N E p s ( x i ) = { x j : d i s t ( x i , x j ) ≤ E p s } N_{Eps}(x_i) = \{x_j : dist(x_i, x_j) \leq Eps\} NEps(xi)={xj:dist(xi,xj)≤Eps}这里 d i s t ( x i , x j ) dist(x_i, x_j) dist(xi,xj) 表示两点之间的距离度量(如欧几里得距离)。
-
核心点条件 :如果 ∣ N E p s ( x i ) ∣ ≥ M i n P t s |N_{Eps}(x_i)| \geq MinPts ∣NEps(xi)∣≥MinPts,则 x i x_i xi 是核心点。
-
直接密度可达 :如果存在一个点 p p p 和一个点 q q q,使得 q ∈ N E p s ( p ) q \in N_{Eps}(p) q∈NEps(p) 并且 p p p 是核心点,则称 q q q 直接密度可达于 p p p。
-
密度可达 :如果存在一个点序列 p 1 , p 2 , . . . , p n p_1, p_2, ..., p_n p1,p2,...,pn,使得对于每一个 i < n i < n i<n, p i + 1 p_{i+1} pi+1 直接密度可达于 p i p_i pi,则称 p n p_n pn 密度可达于 p 1 p_1 p1。
-
密度相连 :如果存在一个点 o o o,使得 p p p 和 q q q 都密度可达于 o o o,则称 p p p 和 q q q 密度相连。
收敛性与复杂度
DBSCAN 算法不需要迭代优化,而是通过单次扫描数据集来完成聚类。一旦所有点都被处理过,算法自然结束。由于它不依赖于随机初始化或收敛标准,因此不会陷入局部最优问题。
-
时间复杂度 :最坏情况下,DBSCAN 的时间复杂度为 O ( n 2 ) O(n^2) O(n2),因为每个点都需要计算与其他所有点的距离。然而,在实践中,通过使用空间索引结构(如 kd-tree 或 R-tree),可以显著降低复杂度至 O ( n log n ) O(n \log n) O(nlogn)。
-
空间复杂度:主要取决于存储数据点及其邻域信息所需的空间。
缺点与限制
尽管 DBSCAN 有许多优点,但它也有一些局限性:
- 参数敏感性 :对参数 E p s Eps Eps 和 M i n P t s MinPts MinPts 的选择非常敏感。不同参数设置可能导致不同的聚类结果,特别是当簇密度差异较大时。
- 高维数据挑战:在高维空间中表现不佳,因为距离度量变得不太有意义,导致难以正确识别簇。
- 计算资源需求:对于大规模数据集,计算所有点之间的距离可能需要大量的内存和计算时间。
- 处理大小和密度变化的簇:如果数据集中存在不同大小和密度的簇,DBSCAN 可能无法同时捕捉所有类型的簇。
实际应用中的考虑
在实际应用中,选择合适的 E p s Eps Eps 和 M i n P t s MinPts MinPts 参数至关重要。可以通过可视化方法或者基于领域知识来估计合理的参数值。此外,为了提高效率,可以使用近似最近邻搜索算法(如 LSH 或 annoy)来加速距离计算。最后,对于非常高维的数据,降维技术(如 PCA 或 t-SNE)可以帮助改善 DBSCAN 的性能。
当然,接下来我将详细介绍基于层次的聚类算法------AGNES(Agglomerative Nesting),这是一种自底向上的层次聚类方法。与 K-Means 和 DBSCAN 不同,AGNES 通过逐步合并最相似的簇来构建一个簇的层级结构。以下是关于 AGNES 算法的详细理论介绍,包括其工作原理、数学公式和一些关键概念。
基于层次的聚类算法------AGNES 算法
AGNES 是一种层次聚类算法,它从每个数据点作为一个独立的簇开始,然后在每次迭代中合并两个最相似的簇,直到满足某种停止条件。这种自底向上的过程最终会形成一个包含所有数据点的单一簇,并生成一个描述簇合并历史的树状图(dendrogram)。
数学表示与关键概念
初始设置
- 数据集 :设我们有一个数据集 { x 1 , x 2 , . . . , x n } \{x_1, x_2, ..., x_n\} {x1,x2,...,xn},其中 x i ∈ R d x_i \in \mathbb{R}^d xi∈Rd 表示 d d d 维空间中的点。
- 初始簇:每个数据点最初被视为一个单独的簇。
距离度量与相似性度量
为了衡量两个簇之间的相似性或距离,通常使用以下几种方法之一:
-
单链接法(Single Linkage) :
D m i n ( C i , C j ) = min x ∈ C i , y ∈ C j d ( x , y ) D_{min}(C_i, C_j) = \min_{x \in C_i, y \in C_j} d(x, y) Dmin(Ci,Cj)=x∈Ci,y∈Cjmind(x,y)这里 d ( x , y ) d(x, y) d(x,y) 是两点之间的距离度量(如欧几里得距离)。
-
全链接法(Complete Linkage) :
D m a x ( C i , C j ) = max x ∈ C i , y ∈ C j d ( x , y ) D_{max}(C_i, C_j) = \max_{x \in C_i, y \in C_j} d(x, y) Dmax(Ci,Cj)=x∈Ci,y∈Cjmaxd(x,y) -
平均链接法(Average Linkage) :
D a v g ( C i , C j ) = 1 ∣ C i ∣ ∣ C j ∣ ∑ x ∈ C i ∑ y ∈ C j d ( x , y ) D_{avg}(C_i, C_j) = \frac{1}{|C_i||C_j|}\sum_{x \in C_i}\sum_{y \in C_j} d(x, y) Davg(Ci,Cj)=∣Ci∣∣Cj∣1x∈Ci∑y∈Cj∑d(x,y) -
中心链接法(Centroid Linkage) :
D c e n t r o i d ( C i , C j ) = d ( μ i , μ j ) D_{centroid}(C_i, C_j) = d(\mu_i, \mu_j) Dcentroid(Ci,Cj)=d(μi,μj)这里 μ i \mu_i μi 和 μ j \mu_j μj 分别是簇 C i C_i Ci 和 C j C_j Cj 的质心。
算法步骤
AGNES 算法的基本流程如下:
1. 初始化
- 将每个数据点视为一个单独的簇,初始化簇集合为 { { x 1 } , { x 2 } , . . . , { x n } } \{\{x_1\}, \{x_2\}, ..., \{x_n\}\} {{x1},{x2},...,{xn}}。
2. 计算距离矩阵
- 构建一个距离矩阵 D D D,其中元素 D [ i ] [ j ] D[i][j] D[i][j] 表示簇 C i C_i Ci 和簇 C j C_j Cj 之间的距离(根据所选的距离度量方法计算)。
3. 合并最近的簇
- 在每次迭代中,找到距离最小的一对簇 C i C_i Ci 和 C j C_j Cj 并将其合并成一个新的簇 C k = C i ∪ C j C_k = C_i \cup C_j Ck=Ci∪Cj。
- 更新距离矩阵 D D D,移除旧的行和列(对应于 C i C_i Ci 和 C j C_j Cj),并在新位置插入新的行和列(对应于 C k C_k Ck)。
4. 重复合并
- 重复上述合并操作,直到满足某个停止条件,例如只剩下 k k k 个簇或者形成了一个单一的大簇。
5. 结果解释
- 最终结果可以是一个由多个簇组成的分层结构,也可以通过切割 dendrogram 来获得特定数量的非嵌套簇。
收敛性与复杂度
-
时间复杂度 :最坏情况下,AGNES 的时间复杂度为 O ( n 3 ) O(n^3) O(n3),因为每次合并后都需要重新计算所有簇之间的距离。然而,在实践中,可以通过优化技术(如提前终止策略)来减少实际计算量。
-
空间复杂度:主要取决于存储距离矩阵和簇信息所需的空间。对于大型数据集,可能需要考虑内存优化方案。
缺点与限制
尽管 AGNES 具有灵活性和直观性,但它也有一些局限性:
- 对异常值敏感:特别是使用单链接法时,容易受到噪声点的影响,导致"链式效应"(即形成细长的簇)。
- 难以处理大规模数据:由于其高时间复杂度,对于非常大的数据集,AGNES 可能变得不切实际。
- 参数选择:虽然不需要预先指定簇的数量,但如何选择合适的停止条件或切割高度仍然是一个挑战。
- 不可逆性:一旦进行了合并操作,就不能撤销,这可能导致局部最优解而不是全局最优解。
实际应用中的考虑
在实际应用中,选择合适的距离度量方法非常重要。此外,为了提高效率,可以在早期阶段使用近似方法来加速距离计算。对于非常高维的数据,降维技术(如 PCA 或 t-SNE)可以帮助改善 AGNES 的性能。最后,通过可视化 dendrogram,用户可以直观地选择适当的切割高度以获取所需的簇数。
基于网格的聚类算法------STING(Statistical Information Grid)和 CLIQUE(Clustering In QUEst)
基于网格的聚类算法概述
基于网格的聚类算法是一种高效的聚类方法,它通过将整个数据空间划分为若干个非重叠的单元格或网格,然后根据这些单元格内的数据密度来确定簇的位置和形状。这种方法不仅能够快速处理大规模数据集,而且对于高维数据也能表现出色。与传统的层次聚类或划分式聚类相比,基于网格的方法具有计算复杂度低、易于实现等优点。
STING 算法
概念与原理
STING(Statistical Information Grid) 是一种典型的基于网格的聚类算法,它使用统计信息来描述每个网格单元中的数据分布情况。STING 采用多分辨率的方式,首先在较低分辨率下进行聚类,然后逐步细化到更高分辨率,直到达到所需的精度水平。
- 网格化:将数据空间划分为多个层次的网格,每一层由更小的单元格组成。
- 统计信息:为每个网格单元存储统计信息,如平均值、方差、最大最小值等。
- 查询处理:用户可以通过指定条件(例如密度阈值)来进行聚类查询,STING 根据存储的统计信息快速返回结果。
数学公式与关键概念
-
网格划分 :设 D D D 表示数据空间, G G G 表示网格结构,则有:
G = { g 1 , g 2 , . . . , g m } G = \{g_1, g_2, ..., g_m\} G={g1,g2,...,gm}其中 g i g_i gi 表示第 i i i 个网格单元。
-
单元格统计信息 :对于每个单元格 g i g_i gi,记录其统计信息 S ( g i ) S(g_i) S(gi),包括但不限于:
- 平均值 μ ( g i ) \mu(g_i) μ(gi)
- 方差 σ 2 ( g i ) \sigma^2(g_i) σ2(gi)
- 最大值 m a x ( g i ) max(g_i) max(gi) 和最小值 m i n ( g i ) min(g_i) min(gi)
- 数据点数量 n ( g i ) n(g_i) n(gi)
-
密度计算 :定义单元格 g i g_i gi 的密度为:
d e n s i t y ( g i ) = n ( g i ) v o l ( g i ) density(g_i) = \frac{n(g_i)}{vol(g_i)} density(gi)=vol(gi)n(gi)这里 v o l ( g i ) vol(g_i) vol(gi) 表示单元格的体积。
-
层次遍历 :从顶层开始,递归地检查子单元格是否满足给定的密度阈值 θ \theta θ。如果某个单元格的密度大于等于 θ \theta θ,则进一步细分为更小的子单元格;否则,标记为非密集区域。
算法步骤
- 初始化:构建初始的粗粒度网格,并为每个单元格计算统计信息。
- 多分辨率分析:按照设定的密度阈值,自顶向下逐层细化网格,直到找到所有满足条件的密集区域。
- 结果输出:输出最终确定的簇列表及其对应的网格单元。
CLIQUE 算法
概念与原理
CLIQUE(Clustering In QUEst) 是另一种基于网格的聚类算法,它特别适合处理高维数据。CLIQUE 不仅可以识别任意形状的簇,还能有效处理噪声数据。它的核心思想是通过检测不同维度上的稠密单元格组合来发现潜在的簇。
- 子空间划分:将每个维度独立地划分为若干个区间(bins),形成一个多维网格。
- 稠密单元格检测:对于每一个可能的子空间组合,检查其中的单元格是否足够密集。
- 簇生成:连接相邻的稠密单元格以形成簇,并通过投影技术减少维度的影响。
数学公式与关键概念
-
子空间划分 :设 D D D 为 d d d 维数据空间, B i B_i Bi 表示第 i i i 维上的区间集合,则有:
B i = { b i 1 , b i 2 , . . . , b i k } B_i = \{b_{i1}, b_{i2}, ..., b_{ik}\} Bi={bi1,bi2,...,bik}每个 b i j b_{ij} bij 定义了一个特定范围。
-
稠密度计算 :定义单元格 ( b 1 j 1 , b 2 j 2 , . . . , b d j d ) (b_{1j_1}, b_{2j_2}, ..., b_{dj_d}) (b1j1,b2j2,...,bdjd) 的稠密度为:
d e n s i t y ( b 1 j 1 , b 2 j 2 , . . . , b d j d ) = n ( b 1 j 1 , b 2 j 2 , . . . , b d j d ) v o l ( b 1 j 1 , b 2 j 2 , . . . , b d j d ) density(b_{1j_1}, b_{2j_2}, ..., b_{dj_d}) = \frac{n(b_{1j_1}, b_{2j_2}, ..., b_{dj_d})}{vol(b_{1j_1}, b_{2j_2}, ..., b_{dj_d})} density(b1j1,b2j2,...,bdjd)=vol(b1j1,b2j2,...,bdjd)n(b1j1,b2j2,...,bdjd) -
显著性检验 :引入显著性水平 α \alpha α 来判断单元格是否为稠密单元格。如果一个单元格的稠密度超过 α \alpha α,则认为它是稠密的。
-
邻接关系:两个单元格被认为是邻接的,当且仅当它们在所有维度上都相邻或者至少有一个公共边界。
-
簇生成:通过连接所有邻接的稠密单元格,形成最终的簇。
算法步骤
- 初始化:对每个维度分别进行等宽划分,创建初始的多维网格。
- 稠密单元格检测:遍历所有可能的子空间组合,找出所有稠密单元格。
- 簇生成:连接相邻的稠密单元格,形成簇。
- 降维优化:通过投影技术减少不必要的维度,提高效率。
- 结果输出:输出最终确定的簇列表及其对应的稠密单元格。
收敛性与复杂度
-
时间复杂度 :由于基于网格的方法通常只需要一次扫描数据集,因此它们的时间复杂度相对较低,通常是线性的 O ( n ) O(n) O(n) 或者接近线性的 O ( n log n ) O(n \log n) O(nlogn)。
-
空间复杂度:主要取决于存储网格结构和相关统计信息所需的空间。对于非常大的数据集,可能需要考虑内存优化方案。
缺点与限制
尽管基于网格的聚类算法有许多优点,但也存在一些局限性:
- 参数敏感性:对网格大小的选择非常敏感,不同的网格尺寸可能导致不同的聚类结果。
- 处理稀疏数据困难:在某些情况下,特别是在高维空间中,可能会出现很多空单元格,导致算法效率下降。
- 难以捕捉复杂形状的簇:虽然 CLIQUE 能够处理任意形状的簇,但对于非常复杂的簇形态,基于网格的方法仍然可能不够灵活。
- 依赖预处理:为了获得最佳性能,通常需要对数据进行适当的预处理,如标准化或特征选择。
基于模型的聚类算法------高斯混合模型(Gaussian Mixture Model, GMM)
GMM 是一种概率模型,用于描述由多个高斯分布组成的复杂数据分布。每个高斯分布代表一个潜在的簇,而 GMM 的目标是找到最能解释观测数据的混合高斯分布参数。与 K-Means 不同,GMM 可以捕捉簇之间的重叠部分,并且能够处理不同形状和大小的簇。
数学表示与关键概念
模型定义
设我们有一个数据集 { x 1 , x 2 , . . . , x n } \{x_1, x_2, ..., x_n\} {x1,x2,...,xn},其中 x i ∈ R d x_i \in \mathbb{R}^d xi∈Rd 表示 d d d 维空间中的点。GMM 假设这些数据点是从 K K K 个高斯分布中生成的,每个分布由均值向量 μ k \mu_k μk、协方差矩阵 Σ k \Sigma_k Σk 和混合系数 π k \pi_k πk 描述,满足:
∑ k = 1 K π k = 1 \sum_{k=1}^{K} \pi_k = 1 k=1∑Kπk=1
每个数据点的概率密度函数可以表示为:
p ( x i ) = ∑ k = 1 K π k N ( x i ∣ μ k , Σ k ) p(x_i) = \sum_{k=1}^{K} \pi_k \mathcal{N}(x_i | \mu_k, \Sigma_k) p(xi)=k=1∑KπkN(xi∣μk,Σk)
这里 N ( x i ∣ μ k , Σ k ) \mathcal{N}(x_i | \mu_k, \Sigma_k) N(xi∣μk,Σk) 是高斯分布的概率密度函数,定义为:
N ( x i ∣ μ k , Σ k ) = 1 ( 2 π ) d / 2 ∣ Σ k ∣ 1 / 2 exp ( − 1 2 ( x i − μ k ) T Σ k − 1 ( x i − μ k ) ) \mathcal{N}(x_i | \mu_k, \Sigma_k) = \frac{1}{(2\pi)^{d/2} |\Sigma_k|^{1/2}} \exp\left(-\frac{1}{2}(x_i - \mu_k)^T \Sigma_k^{-1} (x_i - \mu_k)\right) N(xi∣μk,Σk)=(2π)d/2∣Σk∣1/21exp(−21(xi−μk)TΣk−1(xi−μk))
参数估计
为了估计 GMM 的参数,通常使用期望最大化(Expectation-Maximization, EM)算法。EM 算法通过迭代优化过程来最大化数据的对数似然函数:
L ( θ ) = ∑ i = 1 n log p ( x i ∣ θ ) = ∑ i = 1 n log ∑ k = 1 K π k N ( x i ∣ μ k , Σ k ) L(\theta) = \sum_{i=1}^{n} \log p(x_i | \theta) = \sum_{i=1}^{n} \log \sum_{k=1}^{K} \pi_k \mathcal{N}(x_i | \mu_k, \Sigma_k) L(θ)=i=1∑nlogp(xi∣θ)=i=1∑nlogk=1∑KπkN(xi∣μk,Σk)
这里 θ = ( μ 1 , Σ 1 , π 1 , . . . , μ K , Σ K , π K ) \theta = (\mu_1, \Sigma_1, \pi_1, ..., \mu_K, \Sigma_K, \pi_K) θ=(μ1,Σ1,π1,...,μK,ΣK,πK) 表示所有未知参数。
EM 算法步骤
EM 算法分为两个主要步骤:E 步(Expectation Step)和 M 步(Maximization Step),这两个步骤交替进行直到收敛。
1. 初始化
- 随机初始化或使用某种启发式方法设置初始参数 θ ( 0 ) = ( μ k ( 0 ) , Σ k ( 0 ) , π k ( 0 ) ) \theta^{(0)} = (\mu_k^{(0)}, \Sigma_k^{(0)}, \pi_k^{(0)}) θ(0)=(μk(0),Σk(0),πk(0))。
2. E 步(期望步)
- 对于每个数据点 x i x_i xi 和每个高斯分布 k k k,计算后验概率(也称为责任度,responsibility):
γ ( z i k ) = π k ( t ) N ( x i ∣ μ k ( t ) , Σ k ( t ) ) ∑ j = 1 K π j ( t ) N ( x i ∣ μ j ( t ) , Σ j ( t ) ) \gamma(z_{ik}) = \frac{\pi_k^{(t)} \mathcal{N}(x_i | \mu_k^{(t)}, \Sigma_k^{(t)})}{\sum_{j=1}^{K} \pi_j^{(t)} \mathcal{N}(x_i | \mu_j^{(t)}, \Sigma_j^{(t)})} γ(zik)=∑j=1Kπj(t)N(xi∣μj(t),Σj(t))πk(t)N(xi∣μk(t),Σk(t))
3. M 步(最大化步)
- 更新参数以最大化对数似然函数:
μ k ( t + 1 ) = ∑ i = 1 n γ ( z i k ) x i ∑ i = 1 n γ ( z i k ) \mu_k^{(t+1)} = \frac{\sum_{i=1}^{n} \gamma(z_{ik}) x_i}{\sum_{i=1}^{n} \gamma(z_{ik})} μk(t+1)=∑i=1nγ(zik)∑i=1nγ(zik)xi
Σ k ( t + 1 ) = ∑ i = 1 n γ ( z i k ) ( x i − μ k ( t + 1 ) ) ( x i − μ k ( t + 1 ) ) T ∑ i = 1 n γ ( z i k ) \Sigma_k^{(t+1)} = \frac{\sum_{i=1}^{n} \gamma(z_{ik}) (x_i - \mu_k^{(t+1)}) (x_i - \mu_k^{(t+1)})^T}{\sum_{i=1}^{n} \gamma(z_{ik})} Σk(t+1)=∑i=1nγ(zik)∑i=1nγ(zik)(xi−μk(t+1))(xi−μk(t+1))T
π k ( t + 1 ) = ∑ i = 1 n γ ( z i k ) n \pi_k^{(t+1)} = \frac{\sum_{i=1}^{n} \gamma(z_{ik})}{n} πk(t+1)=n∑i=1nγ(zik)
4. 迭代
- 重复执行 E 步和 M 步,直到参数变化小于某个阈值或者达到最大迭代次数。
收敛性与复杂度
-
时间复杂度 :每次迭代的时间复杂度为 O ( n K d 2 ) O(nKd^2) O(nKd2),其中 n n n 是数据点数量, K K K 是高斯分布的数量, d d d 是特征维度。
-
空间复杂度:主要取决于存储数据点、参数以及中间变量所需的空间。
缺点与限制
尽管 GMM 具有灵活性和强大表达能力,但它也有一些局限性:
- 过拟合风险 :如果选择过多的高斯成分,可能会导致过拟合问题。因此,需要合理选择 K K K 的值。
- 局部最优解:EM 算法容易陷入局部最优解,特别是当初始参数选择不当的时候。
- 计算成本较高:对于大规模数据集,计算协方差矩阵及其逆矩阵可能非常耗时。
- 敏感于异常值:由于使用了欧几里得距离,GMM 对异常值较为敏感。
总结
在实际应用中,选择合适的 K K K 值非常重要。可以通过交叉验证或信息准则(如 AIC 或 BIC)来确定最佳的簇数。此外,为了提高效率,可以在早期阶段使用近似方法来加速 EM 算法的收敛。对于非常高维的数据,降维技术(如 PCA 或 t-SNE)可以帮助改善 GMM 的性能。最后,通过可视化工具,用户可以直观地探索和理解聚类结果。
聚类算法实战------K-Means算法------购物中心客户分群分析
业务背景
某大型购物中心希望通过数据分析来更好地了解他们的客户群体,以便制定更有针对性的营销策略和服务方案。通过收集客户的年收入数据和消费积分(根据购物频率和金额计算),我们使用K-Means聚类算法来识别不同的客户群体。
仿真数据说明
在这个购物中心客户分群分析中,我们总共生成了300个客户样本数据,均匀分为三组,每组包含100个客户。数据包含两个特征:年收入和消费积分。使用正态分布(np.random.normal)生成数据,这样可以更好地模拟真实世界中的数据分布特征。
第一组是高收入高消费群体,年收入均值为80,000元(标准差10,000元),消费积分均值为90分(标准差20分),代表着购物中心的高端客户群。第二组是中等收入中等消费群体,年收入均值为45,000元(标准差8,000元),消费积分均值为50分(标准差15分),这代表了主流消费群体。第三组是低收入低消费群体,年收入均值为25,000元(标准差5,000元),消费积分均值为30分(标准差10分),代表预算型消费者。
通过合理设置均值和标准差,我们确保了三个群体之间既有明显的特征差异,又存在适度的重叠,这更符合现实情况。收入范围大致在15,000至100,000元之间,消费积分范围在0-100分之间。使用np.random.seed(42)确保了数据的可重复性,便于进行算法验证和结果复现。这样的数据设计既保证了群体特征的可区分性,又保持了数据分布的自然性和现实性,非常适合用来展示K-Means聚类算法的效果。仿真数据可视化效果如下图一所示。
其中用到的几个数据说明:
- 年收入:客户的年收入数据(单位:元)
- 消费积分:基于客户的购物行为计算的积分
- 考虑因素:购物频率、单次消费金额、会员等级等
- 积分范围:0-100分
图一 原始数据
聚类目标
将客户分为三个主要群体:
- 高价值客户群:高收入、高消费积分
特点:年收入约8万元,消费积分90分左右
营销策略:提供高端产品推荐、VIP服务、专属活动 - 中等价值客户群:中等收入、中等消费积分
特点:年收入约4.5万元,消费积分50分左右
营销策略:提供性价比产品、会员优惠、促销活动 - 潜力客户群:较低收入、较低消费积分
特点:年收入约2.5万元,消费积分30分左右
营销策略:提供特惠商品、入门级产品推荐、积分奖励计划
商业价值
(1)精准营销
根据不同群体特征制定差异化营销策略;提高营销效率,降低营销成本
(2)库存管理
根据不同群体的消费能力调整商品结构;优化库存水平,提高周转率
(3)服务优化:为不同群体提供差异化服务;提升客户满意度和忠诚度
(4)商业决策:为新店选址提供数据支持;指导商品采购和定价策略
通过这个分析,购物中心可以:
- 更好地理解客户构成
- 制定更有针对性的营销策略
- 优化商品结构和服务体系
- 提高经营效率和客户满意度
使用K-Means解决问题
为了解决购物中心客户分群的问题,我们使用K-Means聚类算法进行分析。首先,对原始数据进行标准化处理(使用StandardScaler),这样可以消除不同特征(年收入和消费积分)之间的尺度差异,使得聚类结果更加准确。K-Means算法通过迭代的方式,将数据点划分为k个簇(在本案例中k=3),每个簇由其质心(中心点)表示。算法的核心思想是最小化每个数据点到其所属簇中心的平方距离之和。
在实现过程中,我们使用了sklearn.cluster中的KMeans类,设置random_state=42以确保结果可重现。算法首先随机初始化三个聚类中心,然后反复进行两个步骤:将每个数据点分配给最近的聚类中心,然后重新计算每个簇的中心位置。这个过程会不断重复,直到聚类中心的位置基本稳定或达到最大迭代次数。为了可视化聚类效果,我们绘制了散点图和箱型图(图二所示),其中散点图展示了不同类别的空间分布和聚类中心,箱型图则显示了各个簇在收入和消费积分上的统计特征,帮助我们更好地理解每个客户群体的特点。
图二 聚类后的数据 ![聚类结果](https://i-blog.csdnimg.cn/direct/b45897c3f47746d4ad26fc5ec71707fe.png#pic_center) 图三 聚类后的详细信息
根据图二可知,左侧的聚类散点图显示,K-Means算法成功地将客户分成了三个相对独立的群体。红色的聚类中心点很好地捕捉到了各个群体的中心位置,表明算法的聚类效果理想。从空间分布来看,三个类别的边界相对清晰,但仍保持着适度的重叠,这符合实际业务场景中客户群体的特征。
右侧的箱型图详细展示了各个聚类在收入和消费两个维度上的分布特征:
- 各群体的中位数(箱型图中的横线)呈现明显的层次差异,验证了聚类的有效性
- 箱体的高度反映了数据的离散程度,表明高收入群体的数据波动较大,而低收入群体相对集中
- 少量的离群点(箱型图上的点)表明存在一些特殊的客户案例,这对制定个性化营销策略有重要参考价值
总的来说,聚类结果很好地反映了客户群体的自然分层现象,为购物中心制定差异化的营销策略和服务方案提供了可靠的数据支持。这种基于数据的客户分群方法,能够帮助商家更精准地了解客户特征,从而提供更有针对性的服务。这里也是用了DBSCAN进行聚类,但是效果不太好,大家有兴趣的可以自己去试一下,代码下面有提供。
完整代码------K-Means
python
import numpy as np
import matplotlib
matplotlib.use('Agg') # 设置后端为Agg
import matplotlib.pyplot as plt
from sklearn.cluster import KMeans
from sklearn.preprocessing import StandardScaler
import os
from matplotlib import font_manager
# 设置线程数以避免内存泄漏警告
os.environ['OMP_NUM_THREADS'] = '2'
# 设置中文字体
plt.rcParams['font.sans-serif'] = ['SimHei'] # 用来正常显示中文标签
plt.rcParams['axes.unicode_minus'] = False # 用来正常显示负号
# 生成模拟数据
np.random.seed(42)
# 生成三个不同的客户群体数据
n_samples = 300
# 高收入高消费群体
income_1 = np.random.normal(80000, 10000, n_samples//3)
spending_1 = np.random.normal(90, 20, n_samples//3)
# 中等收入中等消费群体
income_2 = np.random.normal(45000, 8000, n_samples//3)
spending_2 = np.random.normal(50, 15, n_samples//3)
# 低收入低消费群体
income_3 = np.random.normal(25000, 5000, n_samples//3)
spending_3 = np.random.normal(30, 10, n_samples//3)
# 合并数据
X = np.vstack([
np.column_stack((income_1, spending_1)),
np.column_stack((income_2, spending_2)),
np.column_stack((income_3, spending_3))
])
# 绘制原始数据分布图
plt.figure(figsize=(10, 6))
colors = ['#FF9999', '#66B2FF', '#99FF99']
labels = ['高收入高消费群体', '中等收入消费群体', '低收入低消费群体']
# 分别绘制三个群体的散点图
plt.scatter(income_1, spending_1, c=colors[0], label=labels[0], alpha=0.6)
plt.scatter(income_2, spending_2, c=colors[1], label=labels[1], alpha=0.6)
plt.scatter(income_3, spending_3, c=colors[2], label=labels[2], alpha=0.6)
plt.xlabel('年收入 (元)')
plt.ylabel('消费积分')
plt.title('购物中心客户原始数据分布')
plt.legend()
plt.grid(True, linestyle='--', alpha=0.7)
# 保存原始数据分布图
plt.savefig('original_data_distribution.png', dpi=300, bbox_inches='tight')
plt.close()
# 数据标准化
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
# 使用K-Means进行聚类
kmeans = KMeans(n_clusters=3, random_state=42)
clusters = kmeans.fit_predict(X_scaled)
# 创建一个图形,包含两个子图
plt.figure(figsize=(15, 6))
# 1. 散点图:显示聚类结果
plt.subplot(1, 2, 1)
scatter = plt.scatter(X[:, 0], X[:, 1], c=clusters, cmap='viridis')
plt.xlabel('年收入 (元)')
plt.ylabel('消费积分')
plt.title('购物中心客户分群散点图')
plt.colorbar(scatter)
# 添加聚类中心
centers = scaler.inverse_transform(kmeans.cluster_centers_)
plt.scatter(centers[:, 0], centers[:, 1], c='red', marker='x', s=200, linewidths=3, label='聚类中心')
plt.legend()
# 2. 箱型图:显示每个聚类的收入和消费分布
plt.subplot(1, 2, 2)
cluster_data = []
labels = []
for i in range(3):
cluster_data.extend([X[clusters == i, 0], X[clusters == i, 1]])
labels.extend([f'聚类{i+1}-收入', f'聚类{i+1}-消费'])
plt.boxplot(cluster_data, labels=labels)
plt.xticks(rotation=45)
plt.title('各聚类群体的收入和消费分布')
plt.ylabel('数值')
# 调整布局并保存图形
plt.tight_layout()
plt.savefig('customer_segmentation.png', dpi=300, bbox_inches='tight')
# 打印每个聚类的基本统计信息
for i in range(3):
cluster_data = X[clusters == i]
print(f"\n聚类 {i+1} 的统计信息:")
print(f"样本数量: {len(cluster_data)}")
print(f"平均年收入: {cluster_data[:, 0].mean():.2f} 元")
print(f"平均消费积分: {cluster_data[:, 1].mean():.2f}")
print(f"收入标准差: {cluster_data[:, 0].std():.2f}")
print(f"消费积分标准差: {cluster_data[:, 1].std():.2f}")
完整代码------DBSCAN
python
import numpy as np
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
from sklearn.cluster import DBSCAN
from sklearn.preprocessing import StandardScaler
import os
# 设置中文字体
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
# 生成模拟数据
np.random.seed(42)
n_samples = 300
# 生成环形数据代表高端客户群(年收入高,消费较分散)
radius1 = 80000
theta1 = np.random.uniform(0, 2*np.pi, n_samples//3)
income_1 = radius1 * np.cos(theta1) + 90000
spending_1 = radius1 * np.sin(theta1)*0.001 + 80
# 生成集中分布数据代表中端客户群
income_2 = np.random.normal(50000, 8000, n_samples//3)
spending_2 = np.random.normal(50, 10, n_samples//3)
# 生成条带形数据代表低端客户群(收入跨度大,消费相对固定)
income_3 = np.random.uniform(20000, 40000, n_samples//3)
spending_3 = np.random.normal(30, 5, n_samples//3)
# 合并数据
X = np.vstack([
np.column_stack((income_1, spending_1)),
np.column_stack((income_2, spending_2)),
np.column_stack((income_3, spending_3))
])
# 绘制原始数据分布图
plt.figure(figsize=(10, 6))
colors = ['#FF9999', '#66B2FF', '#99FF99']
labels = ['高端客户群(环形分布)', '中端客户群(集中分布)', '低端客户群(条带分布)']
# 分别绘制三个群体的散点图
plt.scatter(income_1, spending_1, c=colors[0], label=labels[0], alpha=0.6)
plt.scatter(income_2, spending_2, c=colors[1], label=labels[1], alpha=0.6)
plt.scatter(income_3, spending_3, c=colors[2], label=labels[2], alpha=0.6)
plt.xlabel('年收入 (元)')
plt.ylabel('消费积分')
plt.title('购物中心客户原始数据分布(非球形簇)')
plt.legend()
plt.grid(True, linestyle='--', alpha=0.7)
# 保存原始数据分布图
plt.savefig('dbscan_original_distribution.png', dpi=300, bbox_inches='tight')
plt.close()
# 数据标准化
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
# 使用DBSCAN进行聚类
dbscan = DBSCAN(eps=0.3, min_samples=10)
clusters = dbscan.fit_predict(X_scaled)
# 创建一个图形,包含两个子图
plt.figure(figsize=(15, 6))
# 1. 散点图:显示聚类结果
plt.subplot(1, 2, 1)
scatter = plt.scatter(X[:, 0], X[:, 1], c=clusters, cmap='viridis')
plt.xlabel('年收入 (元)')
plt.ylabel('消费积分')
plt.title('DBSCAN客户分群结果')
plt.colorbar(scatter)
# 2. 各群体的消费特征分布图
plt.subplot(1, 2, 2)
valid_clusters = clusters[clusters != -1] # 排除噪声点
valid_data = X[clusters != -1]
# 为每个聚类计算并显示核密度估计
for cluster_id in np.unique(valid_clusters):
cluster_data = valid_data[valid_clusters == cluster_id]
plt.hist(cluster_data[:, 1], bins=20, alpha=0.5,
label=f'群体 {cluster_id+1}', density=True)
plt.xlabel('消费积分')
plt.ylabel('密度')
plt.title('各客户群体消费特征分布')
plt.legend()
# 调整布局并保存图形
plt.tight_layout()
plt.savefig('dbscan_clustering_result.png', dpi=300, bbox_inches='tight')
# 打印每个聚类的基本统计信息
print("\n聚类统计信息:")
print(f"识别出的群体数量: {len(np.unique(clusters))}")
print(f"噪声点数量: {np.sum(clusters == -1)}")
for cluster_id in np.unique(clusters):
if cluster_id == -1:
continue
cluster_data = X[clusters == cluster_id]
print(f"\n群体 {cluster_id+1} 的统计信息:")
print(f"样本数量: {len(cluster_data)}")
print(f"平均年收入: {cluster_data[:, 0].mean():.2f} 元")
print(f"平均消费积分: {cluster_data[:, 1].mean():.2f}")
print(f"收入标准差: {cluster_data[:, 0].std():.2f}")
print(f"消费积分标准差: {cluster_data[:, 1].std():.2f}")
聚类算法实战------DBSCAN算法------城市公共自行车站点分布分析
业务背景
随着绿色出行理念的普及,城市公共自行车系统已成为城市交通网络中不可或缺的一部分。某城市正在评估其公共自行车系统的运营效果,希望通过数据分析来优化站点布局和运营策略。系统中的每个站点都配备了智能监测设备,可以实时记录自行车的借还情况,从而计算出站点的使用率。
数据说明
在这个分析中,我们生成了300个自行车站点的数据,包含三个关键特征:
- 地理位置:
经度坐标
纬度坐标 - 使用率:站点日均使用率(0-100%)
计算方式:日均租还次数/站点容量
反映站点的实际使用情况
数据按照区域特征可以大致分为三类:
- 商业区站点:
特点:密集分布,使用率高(均值80%,标准差10%)
位置:城市中心区域
使用模式:工作日高峰明显,双向流动 - 住宅区站点:
特点:分散分布,使用率中等(均值50%,标准差15%)
位置:城市居住区
使用模式:早晚高峰,单向流动为主 - 景区站点:
特点:带状分布,使用率波动大(均值60%,标准差20%)
位置:沿城市景观带分布
使用模式:周末和节假日使用率高
仿真数据可视化之后如下图四所示:
图四 原始数据分布
分析目标
(1)站点群组识别:发现自然形成的站点集群、识别孤立的站点、分析不同区域的使用特征
(2)运营优化:优化自行车重分配策略、调整站点容量配置、识别需要新增或调整的站点位置
(3)服务提升:提高车辆周转率、减少用户等待时间、提升系统运营效率
分析价值
(1)资源配置优化:根据使用率调整站点容量、优化车辆调度路线、合理分配维护资源
(2)运营效率提升:减少空置和爆满情况、降低运维成本、提高用户满意度
(3)规划决策支持:指导新站点选址、优化现有站点布局、制定差异化运营策略
(4)可持续发展:促进绿色出行、提高系统使用效率、减少碳排放
使用DBSCAN解决问题
为了解决城市公共自行车站点分布分析问题,我们使用DBSCAN(基于密度的空间聚类算法)进行分析。首先,对包含经度、纬度和使用率的三维数据进行标准化处理(使用StandardScaler),以消除不同特征之间的尺度差异。DBSCAN算法通过设置邻域半径(eps=0.3)和最小样本数(min_samples=5)两个关键参数,基于密度的连通性自动识别站点群组。该算法的核心思想是:找到密度相连的区域,将其划分为一个聚类,同时将密度不足的点标记为噪声点。这种方法特别适合我们的场景,因为它不需要预先指定群组数量,能够发现任意形状的站点集群,并且可以自动识别独立的、可能需要特殊关注的站点。为了直观地展示分析结果,我们通过散点图展示聚类的空间分布(点的大小表示使用率),并使用箱型图分析各群组的使用率分布特征,这样的可视化有助于我们理解不同区域的站点使用模式和特点。代码运行之后结果如下图五图六所示。
图五 DBSCAN聚类结果
图六 每个组群的信息
DBSCAN算法在城市自行车站点分析中识别出了8个主要群组和161个噪声点,其中最显著的是群组1,包含81个站点,主要分布在城市核心区域(经度4.02-5.78,纬度4.04-6.10),具有最高的平均使用率79.86%和较为稳定的使用模式(标准差7.88%);而群组8虽然仅有9个站点,但同样表现出高使用率(76.80%)和稳定性(标准差5.00%),这两个群组可能代表了重要的商业或交通枢纽区域。其他群组(2-7)的站点数量较少(5-18个不等),使用率普遍在41%-56%之间,标准差较小(1.15%-5.34%),表现出相对稳定的使用模式。
从聚类结果图(图五)中可以观察到,左图显示了站点的空间分布特征,其中高使用率的群组(群组1和8)在空间上形成了明显的集聚区域,而其他群组则相对分散;右图的箱型图展示了各群组的使用率分布,清晰地反映出群组1和8与其他群组在使用率上的显著差异。值得注意的是,超过一半的站点(161个)被识别为噪声点,这表明城市自行车站点的分布存在较大的离散性,这些孤立的站点可能需要进行位置调整或与周边站点进行优化整合。
完整代码
python
import numpy as np
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
from sklearn.cluster import DBSCAN
from sklearn.preprocessing import StandardScaler
import os
# 设置中文字体
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
# 生成模拟数据
np.random.seed(42)
n_samples = 300
# 生成商业区站点数据(密集使用,工作日高峰)
business_x = np.random.normal(5, 0.5, n_samples//3) # 经度
business_y = np.random.normal(5, 0.5, n_samples//3) # 纬度
business_usage = np.random.normal(80, 10, n_samples//3) # 使用率
# 生成住宅区站点数据(早晚高峰,分散分布)
residential_x = np.random.normal(2, 1.5, n_samples//3)
residential_y = np.random.normal(2, 1.5, n_samples//3)
residential_usage = np.random.normal(50, 15, n_samples//3)
# 生成景区站点数据(周末高峰,带状分布)
scenic_x = np.linspace(0, 8, n_samples//3) + np.random.normal(0, 0.3, n_samples//3)
scenic_y = np.sin(scenic_x) + np.random.normal(0, 0.3, n_samples//3)
scenic_usage = np.random.normal(60, 20, n_samples//3)
# 合并数据
X = np.vstack([
np.column_stack((business_x, business_y, business_usage)),
np.column_stack((residential_x, residential_y, residential_usage)),
np.column_stack((scenic_x, scenic_y, scenic_usage))
])
# 绘制原始数据分布图
plt.figure(figsize=(12, 5))
plt.subplot(1, 2, 1)
colors = ['#FF9999', '#66B2FF', '#99FF99']
labels = ['商业区站点', '住宅区站点', '景区站点']
# 绘制散点图,点的大小根据使用率变化
plt.scatter(business_x, business_y, c=colors[0], s=business_usage,
alpha=0.6, label=labels[0])
plt.scatter(residential_x, residential_y, c=colors[1], s=residential_usage,
alpha=0.6, label=labels[1])
plt.scatter(scenic_x, scenic_y, c=colors[2], s=scenic_usage,
alpha=0.6, label=labels[2])
plt.xlabel('经度')
plt.ylabel('纬度')
plt.title('自行车站点分布(点大小表示使用率)')
plt.legend()
plt.grid(True, linestyle='--', alpha=0.7)
# 使用率分布图
plt.subplot(1, 2, 2)
plt.hist([business_usage, residential_usage, scenic_usage],
label=labels, color=colors, alpha=0.6, bins=15)
plt.xlabel('站点使用率 (%)')
plt.ylabel('站点数量')
plt.title('不同区域站点使用率分布')
plt.legend()
# 保存原始数据分布图
plt.tight_layout()
plt.savefig('bike_stations_original.png', dpi=300, bbox_inches='tight')
plt.close()
# 数据标准化
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
# 使用DBSCAN进行聚类
dbscan = DBSCAN(eps=0.3, min_samples=5)
clusters = dbscan.fit_predict(X_scaled)
# 创建新图形展示聚类结果
plt.figure(figsize=(12, 5))
# 1. 散点图:显示聚类结果
plt.subplot(1, 2, 1)
scatter = plt.scatter(X[:, 0], X[:, 1], c=clusters, s=X[:, 2], cmap='viridis')
plt.xlabel('经度')
plt.ylabel('纬度')
plt.title('DBSCAN聚类结果\n(点大小表示使用率)')
plt.colorbar(scatter, label='聚类编号')
# 2. 各聚类的使用率箱型图
plt.subplot(1, 2, 2)
valid_clusters = clusters[clusters != -1]
valid_usage = X[clusters != -1, 2]
# 为每个聚类创建箱型图
cluster_data = []
labels = []
for cluster_id in np.unique(valid_clusters):
cluster_data.append(X[clusters == cluster_id, 2])
labels.append(f'群组 {cluster_id+1}')
plt.boxplot(cluster_data, labels=labels)
plt.xlabel('聚类群组')
plt.ylabel('使用率 (%)')
plt.title('各聚类群组使用率分布')
# 保存聚类结果图
plt.tight_layout()
plt.savefig('bike_stations_clusters.png', dpi=300, bbox_inches='tight')
# 打印聚类统计信息
print("\n聚类统计信息:")
print(f"识别出的群组数量: {len(np.unique(clusters[clusters != -1]))}")
print(f"噪声点数量: {np.sum(clusters == -1)}")
for cluster_id in np.unique(clusters):
if cluster_id == -1:
continue
cluster_data = X[clusters == cluster_id]
print(f"\n群组 {cluster_id+1} 的统计信息:")
print(f"站点数量: {len(cluster_data)}")
print(f"平均使用率: {cluster_data[:, 2].mean():.2f}%")
print(f"使用率标准差: {cluster_data[:, 2].std():.2f}%")
print(f"区域范围: 经度({cluster_data[:, 0].min():.2f}-{cluster_data[:, 0].max():.2f}), "
f"纬度({cluster_data[:, 1].min():.2f}-{cluster_data[:, 1].max():.2f})")
总结
在这篇文章中,我作为一位初学者,尝试对聚类分析这一无监督学习领域进行了探索和总结。聚类分析旨在将数据点根据其相似性分成不同的簇,从而揭示数据中的自然分组结构。文中详细介绍了几种主要的聚类算法,包括基于划分的K-Means、基于密度的DBSCAN、基于层次的AGNES、基于网格的STING与CLIQUE,以及基于模型的GMM,并且探讨了每种算法的工作原理、数学公式、应用场景及其优缺点。
为了更好地理解这些理论知识,我还通过两个实际案例来展示如何应用聚类算法解决现实问题:
- 购物中心客户分群分析:使用K-Means算法对客户的年收入和消费积分进行聚类,以识别不同价值的客户群体,为精准营销提供支持。
- 城市公共自行车站点分布分析:采用DBSCAN算法分析自行车站点的位置和使用率,帮助优化站点布局和服务策略。
在撰写过程中,我发现每个算法都有其独特的特性和适用范围,选择合适的算法对于获得有效的聚类结果至关重要。同时,我也意识到参数的选择(如K-Means中的k值或DBSCAN中的Eps和MinPts)对最终结果有着重要影响,这需要结合具体的数据特征和业务需求来进行调整。
由于我还是聚类领域的初学者,这篇文章仅代表我个人的理解和见解,可能存在不准确或不够深入的地方。因此,非常欢迎各位读者指出文章中的问题,希望我们能够就此展开讨论,互相学习,共同进步。感谢大家的阅读和支持!