前言
K-means算法是一种聚类算法,用于将数据集分成具有相似特征的群组。该算法的目标是将数据点划分为k个簇,其中每个数据点属于与其最近的簇中心。K-means是一种迭代算法,它在每次迭代中更新簇中心,直到满足停止条件。
- 初始化:
随机选择k个数据点作为初始簇中心,分别表示为μ₁, μ₂, ..., μₖ。
- 分配数据点到簇:
对于每个数据点i(1 到 m),计算其到每个簇中心的距离,通常使用欧氏距离(还可以采用余弦相似度、曼哈顿距离、闵可夫斯基距离等):
将数据点分配到距离最近的簇,表示为c(i)。 9. 更新簇中心:
对于每个簇j(1 到 k),计算该簇中所有数据点的均值, 得到的簇中心:

其中,|Cj|表示第j个簇中的数据点数量。 10. 重复步骤2 和3:
重复执行步骤2和3,直到簇中心不再发生显著变化或达到预定的迭代 数 。
K-means的目标是最小化所有数据点到其所属簇中心的距离的平方和(平方误差和):
其中,δ(c(i)=j)是指示函数,当c(i)=j 时为1,否则为0。
这个目标函数J在每次迭代中应该减小,表示簇中心的不断优化。算法的停止条件可以是J不再显著减小或达到预定的迭代次数。 K-means的优点包括简单、易于实现和高效。然而,它也有一些缺点,例如对初始簇中心的选择敏感,对异常值敏感,以及对簇的形状和大小有假设(假设簇是凸的,并且具有相等的方差)。
在使用K-means时,通常需要事先确定簇的数量k。有一些方法,如肘部法则(Elbow Method)和轮廓分析(Silhouette Analysis),可以帮助选择合适的簇数。
代码实现
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
plt.rcParams['font.sans-serif'] = 'SimHei' # 设置中文显示
plt.rcParams['axes.unicode_minus'] = False
data = pd.read_excel('中草药红外光谱数据.xlsx',index_col = 0) # index_col指定索引
data.head()
根据几种药材的中红外光谱数据,鉴别药材的种类,其中 No 列为药材的编号,其余各列第一行的数据为光谱的波数( 单位 cm -1 ) 、第二行以后的数据表示该行编号的药材在对应波段光谱照射下的吸光度。
print(data.shape)
print(data.isnull().any().any()) # 空值判断
def func_1(x):
plt.plot(x.index, x.values)
def func_2(data):
fontsize = 5
plt.figure(figsize=(8, 6), dpi = 300)
plt.xticks(range(652, 4000, 500))
plt.yticks(fontsize = fontsize)
plt.xlabel('波数(cm^-1)')
plt.ylabel('吸光度(AU)')
plt.grid(True) # 网格线设置
data.agg(lambda x: func_1(x), axis = 1)
plt.show()
func_2(data)
绘制中药材波谱图,展现波谱分布,可见存在离群值。
#异常值检验3σ
def func_3(x):
lower = x.mean()-3*x.std()
toplimit = x.mean()+3*x.std()
return (x<lower)|(x>toplimit)
ycz = data.agg(lambda x:func_3(x))
ycz_index = data[(ycz.sum(axis=1)>1000)].index
print(ycz_index)
data.drop(ycz_index,axis=0,inplace = True) # 删除异常值
func_2(data)
通过3σ法则进行异常值检测,然后删除被检测到的异常值。在实际应用中,异常值检测有助于清理数据,使得后续分析更加可靠。
from sklearn.cluster import KMeans
from collections import Counter
from sklearn import metrics
from mpl_toolkits.mplot3d import Axes3D
SSE = []
for k in range(1, 11):
km = KMeans(n_clusters=k)
km.fit(data)
SSE.append(km.inertia_)
X = range(1, 11)
plt.figure(figsize=(8, 6), dpi = 300)
plt.xlabel('k')
plt.ylabel('SSE')
plt.title('肘部图')
plt.plot(X, SSE, 'o-')
plt.grid(True)
plt.show()
生成肘部图(Elbow Method),帮助选择K-means聚类合适簇数K ,即在图中肘部出现的位置。通常,在K值较小时,SSE会较大;而随着K的增加,SSE会逐渐减小。肘部图的"肘部"是指图中开始出现弯曲并趋于平缓的点,该点对应的K值即为较优的簇数。通过观察肘部图,可以尝试找到一个合适的K值,以在聚类中获得相对较好的性能。根据肘部图可知此时选择K=2或K=3。
scores = []
for k in range(3, 11):
labels = KMeans(n_clusters = k).fit(data).labels_
score = metrics.silhouette_score(data, labels)
scores.append(score)
X = range(3, 11)
plt.figure(figsize=(8, 6), dpi = 300)
plt.xlabel('k')
plt.ylabel('轮廓系数')
plt.title('轮廓系数图')
plt.plot(X, scores, 'o-')
plt.grid(True)
plt.show()
生成轮廓系数图,帮助选择K-means聚类中的合适簇数K。轮廓系数是一种评估聚类质量的指标,取值范围在[-1, 1]之间。通过观察轮廓系数图,可以尝试找到一个合适的K值,即轮廓系数较高的位置,以在聚类中获得相对较好的性能。较高的轮廓系数表示簇内相似度高、簇间相似度低,是一种有效的聚类评估指标。根据轮廓系数图可知此时选择K=3。
km = KMeans(n_clusters = 3)
km.fit(data)
print(Counter(km.labels_)) # 打印每个类多少个
print(km.cluster_centers_) # 中心点
import warnings
# 在代码之前,添加以下行来忽略SettingWithCopyWarning
pd.options.mode.chained_assignment = None # 或 pd.set_option('mode.chained_assignment', None)
data['class'] = pd.Series(km.labels_).values # dtaframe保存分类结果
data.head()
根据簇数K=3执行K-means聚类,输出了每个簇的样本数量、簇中心,并将聚类结果保存到数据框中。通过这些信息,可以初步了解数据点的分布情况和聚类效果。