R语言K-Means聚类及示例

什么是聚类分析?

聚类分析是无监督学习的一部分。聚类是数据中具有相似特征的组。我们可以说,聚类分析更多的是关于发现而不是预测。机器在数据中搜索相似性。例如,您可以使用聚类分析进行以下应用:

  • 客户细分:寻找客户群之间的相似性
  • 股市聚类:根据业绩对股票进行分组
  • 通过将具有相似值的观测值分组来降低数据集的维度

聚类分析实现起来并不难,并且对企业来说既有意义又有可操作性。

监督学习和无监督学习之间最显著的区别在于结果。无监督学习创建一个新变量,即标签,而监督学习预测一个结果。机器帮助实践者根据密切相关性对数据进行标记。由分析师决定如何利用这些分组并为它们命名。

让我们举一个例子来理解聚类概念。为简单起见,我们在二维空间中进行。您拥有客户总支出和年龄的数据。为了改进广告,营销团队希望向其客户发送更有针对性的电子邮件。

在下面的图中,您绘制了客户的总支出和年龄。

library(ggplot2)
df <- data.frame(age = c(18, 21, 22, 24, 26, 26, 27, 30, 31, 35, 39, 40, 41, 42, 44, 46, 47, 48, 49, 54),
    spend = c(10, 11, 22, 15, 12, 13, 14, 33, 39, 37, 44, 27, 29, 20, 28, 21, 30, 31, 23, 24)
)
ggplot(df, aes(x = age, y = spend)) +
    geom_point()

Cluster Analysis

此时可以看到一个模式

  1. 在左下角,您可以看到年轻人的购买力较低
  2. 中上部分反映了有工作的人,他们可以负担得起更多的支出
  3. 最后,老年人的预算较低。

Cluster Analysis

在上图中,您手动聚类了观测值并定义了这三个组。这个例子有些简单且高度可视化。如果新观测值被添加到数据集中,您可以将它们标记在圆圈内。您根据自己的判断定义圆圈。相反,您可以使用 机器学习 来客观地分组数据。

在本教程中,您将学习如何使用k-means算法。

K-means 算法

K-means 无疑是最受欢迎的聚类方法。研究人员在几十年前就发布了该算法,并且对 k-means 进行了许多改进。

该算法试图通过最小化观测值之间的距离来寻找组,称为局部最优解。距离是根据观测值的坐标测量的。例如,在二维空间中,坐标是简单的 x 和 y。

K-means Algorithm

该算法如下工作:

  • 步骤 1:在特征平面中随机选择组
  • 步骤 2:最小化聚类中心与不同观测值(质心)之间的距离。这将导致形成包含观测值的组
  • 步骤 3:将初始质心移动到组内坐标的均值。
  • 步骤 4:根据新的质心最小化距离。创建了新的边界。因此,观测值将从一个组移动到另一个组。
  • 重复直到没有观测值改变组

K-means 通常采用特征 x 和特征 y 之间的欧氏距离。

K-means Algorithm

还有其他度量可用,例如曼哈顿距离或闵可夫斯基距离。请注意,每次运行算法时,K-mean 都会返回不同的组。回想一下,第一个初始猜测是随机的,并且算法计算距离直到达到组内的同质性。也就是说,k-mean 对第一个选择非常敏感,并且除非观测值数量和组数量较少,否则几乎不可能获得相同的聚类。

选择聚类数量

K-mean 的另一个难题是选择聚类数量。您可以设置较高的 k 值,即较多的组,以提高稳定性,但最终可能会导致数据过拟合。过拟合意味着模型在新的传入数据上的性能会显著下降。机器学会了数据集的细微差别,并努力概括整体模式。

聚类数量取决于数据集的性质、行业、业务等。但是,有一个经验法则可以帮助选择合适的聚类数量。

Select the Number of Clusters

其中 n 是数据集中观测值的数量。

总的来说,花时间寻找最适合业务需求的 k 值是很有价值的。

我们将使用个人电脑价格数据集来执行我们的聚类分析。该数据集包含 6259 个观测值和 10 个特征。该数据集观察了 1993 年至 1995 年间美国 486 台个人电脑的价格。变量包括价格、速度、内存、屏幕、光驱等。

您将按以下步骤进行

  • 导入数据
  • 训练模型
  • 评估模型

导入数据

K-means 不适用于因子变量,因为它基于距离,而离散值不会返回有意义的值。您可以删除我们数据集中这三个分类变量。此外,此数据集中没有缺失值。

library(dplyr)
PATH <-"https://raw.githubusercontent.com/guru99-edu/R-Programming/master/computers.csv"
df <- read.csv(PATH) %>%
select(-c(X, cd, multi, premium))
glimpse(df)

输出

## Observations: 6, 259
## Variables: 7
## $ price < int > 1499, 1795, 1595, 1849, 3295, 3695, 1720, 1995, 2225, 2...
##$ speed < int > 25, 33, 25, 25, 33, 66, 25, 50, 50, 50, 33, 66, 50, 25, ...
##$ hd < int > 80, 85, 170, 170, 340, 340, 170, 85, 210, 210, 170, 210...
##$ ram < int > 4, 2, 4, 8, 16, 16, 4, 2, 8, 4, 8, 8, 4, 8, 8, 4, 2, 4, ...
##$ screen < int > 14, 14, 15, 14, 14, 14, 14, 14, 14, 15, 15, 14, 14, 14, ...
##$ ads < int > 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, ...
## $ trend  <int> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1...

从汇总统计数据中,您可以看到数据具有大值。对于 k-means 和距离计算,一个好的做法是重新缩放数据,使均值等于 1,标准差等于 0。

summary(df)

输出

##      price          speed              hd              ram        
##  Min.   : 949   Min.   : 25.00   Min.   :  80.0   Min.   : 2.000  
##  1st Qu.:1794   1st Qu.: 33.00   1st Qu.: 214.0   1st Qu.: 4.000  `
##  Median :2144   Median : 50.00   Median : 340.0   Median : 8.000  
##  Mean   :2220   Mean   : 52.01   Mean   : 416.6   Mean   : 8.287  
##  3rd Qu.:2595   3rd Qu.: 66.00   3rd Qu.: 528.0   3rd Qu.: 8.000  
##  Max.   :5399   Max.   :100.00   Max.   :2100.0   Max.   :32.000  
##      screen           ads            trend      
##  Min.   :14.00   Min.   : 39.0   Min.   : 1.00  
##  1st Qu.:14.00   1st Qu.:162.5   1st Qu.:10.00  
##  Median :14.00   Median :246.0   Median :16.00  
##  Mean   :14.61   Mean   :221.3   Mean   :15.93  
##  3rd Qu.:15.00   3rd Qu.:275.0   3rd Qu.:21.50  
##  Max.   :17.00   Max.   :339.0   Max.   :35.00

您使用 dplyr 库的 scale() 函数重新缩放变量。这种转换减少了异常值的影响,并允许将单个观测值与均值进行比较。如果标准化值(或z 分数)很高,您可以确信该观测值确实高于均值(大的 z 分数意味着该点距离均值有几个标准差。z 分数为 2 表示该值距离均值有 2 个标准差。请注意,z 分数遵循高斯分布,并且围绕均值对称。

rescale_df <- df % > %
mutate(price_scal = scale(price),
    hd_scal = scale(hd),
    ram_scal = scale(ram),
    screen_scal = scale(screen),
    ads_scal = scale(ads),
    trend_scal = scale(trend)) % > %
select(-c(price, speed, hd, ram, screen, ads, trend))

R 基础有一个函数可以运行 k-means 算法。k-means 的基本函数是

kmeans(df, k)
arguments:
-df: dataset used to run the algorithm
-k: Number of clusters

训练模型

在图三中,您详细说明了算法的工作原理。您可以看到每个步骤的图形表示,这得益于 Yi Hui(也是 Knit for Rmarkdown 的创建者)构建的 great 包。动画包在 conda 库中不可用。您可以使用其他方法安装该包:install.packages(“animation”)。您可以检查该包是否已安装在我们的 Anaconda 文件夹中。

install.packages("animation")

加载库后,您只需在 kmeans 后面添加 .ani,R 就会绘制所有步骤。出于说明目的,我们仅使用重新缩放的变量 hd 和 ram 以及三个聚类来运行算法。

set.seed(2345)
library(animation)
kmeans.ani(rescale_df[2:3], 3)

代码解释

  • kmeans.ani(rescale_df[2:3], 3):选择 rescale_df 数据集的第 2 列和第 3 列,并设置 k 为 3 来运行算法。绘制动画。

Train the Model

Train the Model

您可以这样解释动画:

  • 步骤 1:R 随机选择三个点
  • 步骤 2:计算欧氏距离并绘制聚类。您在左下角有一个绿色集群,右侧有一个大的黑色集群,它们之间有一个红色集群。
  • 步骤 3:计算质心,即聚类的均值
  • 重复直到没有数据改变聚类

算法在七次迭代后收敛。您可以在我们的数据集中运行 k-means 算法,设置五个聚类,并将其命名为 pc_cluster。

pc_cluster <-kmeans(rescale_df, 5)
  • pc_cluster 列表包含七个有用的元素:
  • pc_cluster$cluster:指示每个观测值的聚类
  • pc_cluster$centers:聚类中心
  • pc_cluster$totss:总平方和
  • pc_cluster$withinss:组内平方和。返回的组件数量等于 `k`
  • pc_cluster$tot.withinss:组内平方和的总和
  • pc_cluster$betweenss:总平方和减去组内平方和
  • pc_cluster$size:每个聚类中的观测值数量

您将使用组内平方和的总和(即 tot.withinss)来计算最优聚类数量 k。寻找 k 确实是一项艰巨的任务。

最优 k

选择最佳 k 的一种技术称为肘部法则。该方法使用组内同质性或组内异质性来评估变异性。换句话说,您关心每个聚类解释的方差百分比。您可以预期随着聚类数量的增加,变异性会增加,或者换句话说,异质性会减少。我们的挑战是找到 k 值,该值超过了收益递减点。添加一个新聚类不会改进数据中的变异性,因为剩下的信息很少需要解释。

在本教程中,我们使用异质性度量来找到这一点。总组内平方和是 kmean() 返回列表中的 tot.withinss。

您可以像这样构建肘部图并找到最优 k:

  • 步骤 1:构建一个函数来计算总组内平方和
  • 步骤 2:运行算法 n 次
  • 步骤 3:创建一个包含算法结果的数据框
  • 步骤 4:绘制结果

步骤 1) 构建一个函数来计算总组内平方和

我们创建了一个运行 k-means 算法并存储总组内平方和的函数。

kmean_withinss <- function(k) {
    cluster <- kmeans(rescale_df, k)
    return (cluster$tot.withinss)
}

代码解释

  • function(k):在函数中设置参数数量
  • kmeans(rescale_df, k):运行算法 k 次
  • return(cluster$tot.withinss):存储总组内平方和

您可以用 k=2 来测试该函数。

输出

## Try with 2 cluster
kmean_withinss(2)

输出

## [1] 27087.07

步骤 2) 运行算法 n 次

我们将使用 sapply() 函数在 k 的范围内运行算法。此技术比创建循环和存储值更快。

# Set maximum cluster 
max_k <-20 
# Run algorithm over a range of k 
wss <- sapply(2:max_k, kmean_withinss)

代码解释

  • max_k <-20:将最大 k 值设置为 20
  • sapply(2:max_k, kmean_withinss):在 2:max_k 的范围内运行 kmean_withinss() 函数,即从 2 到 20。

步骤 3) 创建一个包含算法结果的数据框

在创建和测试函数后,您可以从 2 到 20 的范围内运行 k-means 算法,并存储 tot.withinss 值。

# Create a data frame to plot the graph
elbow <-data.frame(2:max_k, wss)

代码解释

  • data.frame(2:max_k, wss):创建一个包含算法输出(存储在 wss 中)的数据框。

步骤 4) 绘制结果

我们绘制图表以可视化肘部点的位置。

# Plot the graph with gglop
ggplot(elbow, aes(x = X2.max_k, y = wss)) +
    geom_point() +
    geom_line() +
    scale_x_continuous(breaks = seq(1, 20, by = 1))

Optimal k

从图中可以看出,最优 k 是七,此时曲线开始出现收益递减。

一旦我们有了最优 k,我们就会用 k=7 重新运行算法并评估聚类。

检查聚类

pc_cluster_2 <-kmeans(rescale_df, 7)

如前所述,您可以访问 kmean() 返回列表中的其余有用信息。

pc_cluster_2$cluster
pc_cluster_2$centers
pc_cluster_2$size

评估部分是主观的,并且依赖于算法的使用。我们这里的目标是收集具有相似特征的计算机。计算机专家可以手动完成此任务,并根据他的专业知识对计算机进行分组。但是,这个过程将花费大量时间并且容易出错。K-means 算法可以为主机做好准备,通过建议聚类。

作为初步评估,您可以检查聚类的大小。

pc_cluster_2$size

输出

## [1] 608 1596 1231  580 1003  699  542

第一个聚类由 608 个观测值组成,而最小的聚类(编号 4)只有 580 台计算机。最好在聚类之间保持同质性,否则,可能需要进行更细致的数据准备。

我们通过中心分量更深入地了解数据。行对应于聚类的编号,列对应于算法使用的变量。值是每个聚类在感兴趣列上的平均得分。标准化使解释更容易。正值表示给定聚类的 z 分数高于总体均值。例如,聚类 2 的平均价格在所有聚类中是最高的。

center <-pc_cluster_2$centers
center

输出

##   price_scal    hd_scal     ram_scal screen_scal   ads_scal trend_scal
## 1 -0.6372457 -0.7097995 -0.691520682  -0.4401632  0.6780366 -0.3379751
## 2 -0.1323863  0.6299541  0.004786730   2.6419582 -0.8894946  1.2673184
## 3  0.8745816  0.2574164  0.513105797  -0.2003237  0.6734261 -0.3300536
## 4  1.0912296 -0.2401936  0.006526723   2.6419582  0.4704301 -0.4132057
## 5 -0.8155183  0.2814882 -0.307621003  -0.3205176 -0.9052979  1.2177279
## 6  0.8830191  2.1019454  2.168706085   0.4492922 -0.9035248  1.2069855
## 7  0.2215678 -0.7132577 -0.318050275  -0.3878782 -1.3206229 -1.5490909

我们可以使用 ggplot 创建热图,以帮助我们突出类别之间的差异。

ggplot 的默认颜色需要使用 RColorBrewer 库进行更改。您可以使用 conda 和在终端中启动的代码

conda install -c r r-rcolorbrewer

创建热图需要三个步骤:

  • 构建一个包含中心值的数据框,并创建一个包含聚类数量的变量。
  • 使用 tidyr 库的 gather() 函数重塑数据。我们想将数据从宽格式转换为长格式。
  • 使用 colorRampPalette() 函数创建颜色调色板。

步骤 1) 构建一个数据框

让我们创建重塑数据集。

library(tidyr)

# create dataset with the cluster number

cluster <- c(1: 7)
center_df <- data.frame(cluster, center)

# Reshape the data

center_reshape <- gather(center_df, features, values, price_scal: trend_scal)
head(center_reshape)

输出

##   cluster   features     values
## 1       1 price_scal -0.6372457
## 2       2 price_scal -0.1323863
## 3       3 price_scal  0.8745816
## 4       4 price_scal  1.0912296
## 5       5 price_scal -0.8155183
## 6       6 price_scal  0.8830191		

步骤 2) 重塑数据

以下代码创建了我们将用于绘制热图的颜色调色板。

library(RColorBrewer)
# Create the palette
hm.palette <-colorRampPalette(rev(brewer.pal(10, 'RdYlGn')),space='Lab')

步骤 3) 可视化

我们可以绘制图表并查看聚类的样子。

# Plot the heat map
ggplot(data = center_reshape, aes(x = features, y = cluster, fill = values)) +
    scale_y_continuous(breaks = seq(1, 7, by = 1)) +
    geom_tile() +
    coord_equal() +
    scale_fill_gradientn(colours = hm.palette(90)) +
    theme_classic()

Examining the Cluster

摘要

我们可以在下表中总结 k-means 算法。

目标 函数 论证
base 训练 k-means kmeans() df, k
访问聚类 kmeans()$cluster
聚类中心 kmeans()$centers
聚类大小 kmeans()$size