线性回归模型、岭回归和LASSO回归模型、Logistic回归分类模型、决策树与随机森林、KNN模型、朴素贝叶斯模型等模型,在建模过程中都有一个共同特点,即数据集中包含了已知的因变量y值。但在有些场景中,并没有给定的y值,对于这类数据的建模,一般称为无监督的数据挖掘算法,最为典型的是聚类算法。
聚类算法的目的是依据已知的数据,将相似度高的样本聚集到各自的簇中。Kmeans聚类算法利用距离远近的思想将目标数据聚为指定的K个簇,簇内样本越相似,表明聚类效果越好。具体步骤如下:
-从数据中随机挑选k个样本点作为原始的簇中心;
-计算剩余样本与簇中心的距离,并把各样本标记为离k个簇中心最近的类别;
-重新计算各簇中样本点的均值,并把均值作为新的K个簇中心;
-不断重复上两步,直到簇中心的变化趋于稳定,形成最终的k个簇。
实现该算法可以调用sklearn子模块cluster中的Kmeans类。
1 最佳k值的确定
对于Kmeans算法来说,确定簇数k值是一个至关重要的问题。如果聚类之前不知道该聚类为几类时, 如何根据数据本身确定合理的K值呢?有三种探索的方法:簇内离差平方和拐点法、轮廓系数法和间隔统计量法(这个有点点复杂,这里就不实现了=.=)。
1.1 簇内离差平方和拐点法
拐点法的思想是在不同的k值下计算簇内离差平方和,然后通过可视化的方法找到“拐点”所对应的k值。如果某个簇内的样本很相似,那么簇内离差平方和会非常小(即方差会很小)。当簇的数量增加,簇中的样本量会越来越少,通过可视化方法,重点关注斜率的变化,当斜率由大突然变小,并且之后的斜率变化缓慢,则认为突然变化的点就是寻找的目标点。
下面通过虚拟散点来实现一下这个过程:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24%matplotlib inline
import matplotlib.pyplot as plt
import seaborn as sns
import pandas as pd
import numpy as np
from sklearn.cluster import KMeans
#随机生成三组二元正态分布随机数
np.random.seed(1234)
mean1 = [0.5, 0.5]
cov1 = [[0.3, 0],[0, 0.3]]
x1, y1 = np.random.multivariate_normal(mean1, cov1, 1000).T
mean2 = [0, 8]
cov2 = [[1.5, 0], [0, 1]]
x2, y2 = np.random.multivariate_normal(mean2, cov2, 1000).T
mean3 = [8, 4]
cov3 = [[1.5, 0], [0, 1]]
x3, y3 = np.random.multivariate_normal(mean3, cov3, 1000).T
plt.scatter(x1, y1)
plt.scatter(x2, y2)
plt.scatter(x3, y3)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34#构造自定义函数,用于绘制不同k值和对应总的簇内离差平方和的折线图
def k_SSE(X, clusters):
#选择连续的k种不同的值
K = range(1, clusters + 1)
#构造空列表存储总的簇内离差平方和
TSSE = []
for k in K:
#存储各个簇内离差平方和
SSE = []
kmeans = KMeans(n_clusters=k)
kmeans.fit(X)
#返回簇标签
labels = kmeans.labels_
#返回簇中心
centers = kmeans.cluster_centers_
#计算各个簇样本的离差平方和,并保存到列表中
for label in set(labels):
SSE.append(np.sum((X.loc[labels == label,] - centers[label,:])**2))
#计算总的簇内离差平方和
TSSE.append(np.sum(SSE))
plt.style.use('ggplot')
plt.plot(K, TSSE, 'b*-')
plt.xlabel('Number of clusters')
plt.ylabel('Sum of squares of deviations in a cluster')
plt.show()
#将三组数据汇总到数据框中
X = pd.DataFrame(np.concatenate([np.array([x1, y1]),
np.array([x2, y2]),
np.array([x3, y3])],
axis = 1).T)
#调用函数
k_SSE(X, 15)
如图所示,当簇的个数为3时形成了一个明显的“拐点”,所以合理的K值为3。
1.2 轮廓系数法
有关轮廓系数的计算,可以直接调用sklearn子模块metrics中的silhouette_score函数。下面基于该函数重新定义一个函数,用于绘制不同K值下对应轮廓系数的折线图:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18from sklearn import metrics
def k_silhouette(X, clusters):
K = range(2, clusters + 1)
S = []
for k in K:
kmeans = KMeans(n_clusters=k)
kmeans.fit(X)
labels = kmeans.labels_
S.append(metrics.silhouette_score(X, labels, metric = 'euclidean'))
plt.style.use('ggplot')
plt.plot(K, S, 'b*-')
plt.xlabel('Number of clusters')
plt.ylabel('Contour coefficient')
plt.show()
k_silhouette(X, 15)
如图,利用之前的虚拟数据,当k等于3时,轮廓系数最大,说明把虚拟数据聚为3类比较合理,且与原始的3个簇是吻合的。
2 Kmeans聚类的应用
在做Kmeans聚类有三点需要注意:
一是聚类前必须指定具体的簇数K值,如果k值未知,可用上面两(三)种方法求得;
二是对原始数据集做必要的标准化处理,可以借助sklearn子模块preprocessing中的minmax_scale或scale函数实现;
三是如果数据集中含有离散型的字符变量,需要对该变量做预处理,如设置为哑变量或转换为数值化的因子。
2.1 iris数据集的聚类
读入数据:1
2iris = pd.read_csv(r'C:\Users\Q\Desktop\iris.csv')
iris.head()
数据集中的前四个变量分别是花萼的长度、宽度及花瓣的长度、宽度,它们之间没有量纲的差异,所以不必做标准化处理;最后一个变量为鸢尾花的种类。将其聚类为3类:1
2
3
4
5
6
7
8
9
10
11
12
13#提取用于建模的数据集
X = iris.drop(labels = 'Species', axis = 1)
kmeans= KMeans(n_clusters=3)
kmeans.fit(X)
#将聚类结果标签
X['clusters'] = kmeans.labels_
X.clusters.value_counts()
**out:**
0 62
1 50
2 38
Name: clusters, dtype: int64
下面将聚类后的三类和原始数据进行可视化:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22#三个簇的中心
centers = kmeans.cluster_centers_
#绘制聚类效果的散点图
sns.lmplot(x = 'Petal_Length', y = 'Petal_Width', hue = 'clusters',
markers = ['^', 's', 'o'],data = X, fit_reg = False,
scatter_kws = {'alpha': 0.8}, legend_out = False)
plt.scatter(centers[:, 2], centers[:, 3], marker='*', color='black', s=130)
plt.xlabel('Petal_Length')
plt.ylabel('Petal_Width')
plt.show()
#添加一个辅助列,将不同的花种映射到0,1,2三个值,方便图形对比
iris['Species_map'] = iris.Species.map({'versicolor': 0,
'setosa': 1,
'virginica': 2})
#绘制原始分类图
sns.lmplot(x = 'Petal_Length', y = 'Petal_Width', hue = 'Species_map',
data = iris, markers = ['^', 's', 'o'], fit_reg = False,
scatter_kws = {'alpha': 0.8}, legend_out = False)
plt.xlabel('Petal_Length')
plt.ylabel('Petal_Width')
plt.show()
左图为聚类效果图,右图为原始分类图,由图中可以看出,聚类算法存在一些错误分割,但绝大部分样本的聚类还是与原始数据保持一致。
2.1 NBA球员数据集的聚类
读取数据:1
2players = pd.read_csv(r'C:\Users\Q\Desktop\players.csv')
players.head()
数据变量的量纲不一致,需要做标准化处理。
这里挑选得分、命中率、三分命中率和罚球命中率4个维度作为球员聚类的依据。
首先绘制球员得分与命中率之间的散点图,便于后面对比聚类后的效果:1
2sns.lmplot(x = '得分', y = '命中率', data = players, fit_reg = False,
scatter_kws = {'alpha': 0.8, 'color': 'steelblue'})
对数据集做标准化处理,再找出合理的k值:1
2
3
4
5
6
7
8
9from sklearn.preprocessing import minmax_scale
X = minmax_scale(players[['得分', '罚球命中率', '命中率', '三分命中率']])
X = pd.DataFrame(X, columns = ['得分', '罚球命中率', '命中率', '三分命中率'])
#拐点法找出最佳的k值
k_SSE(X, 15)
#轮廓系数法找出最佳的k值
k_silhouette(X, 15)
综合两图,最终确定最佳的k值为3。下面基于这个k值对数据集聚类:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15kmeans = KMeans(n_clusters=3)
kmeans.fit(X)
players['cluster'] = kmeans.labels_
centers = []
for i in players.cluster.unique():
centers.append(players.ix[players.cluster == i,
['得分', '罚球命中率', '命中率', '三分命中率']].mean())
centers = np.array(centers)
sns.lmplot(x = '得分', y = '命中率', hue = 'cluster', data = players,
markers = ['^', 's', 'o'],fit_reg = False,
scatter_kws = {'alpha': 0.8}, legend = False)
plt.scatter(centers[:, 0], centers[:, 2], c = 'k', marker = '*', s = 180)
plt.xlabel('score')
plt.ylabel('hit rate')
需要注意的是,由于对原始数据做了标准化处理,因此图中的簇中心不能直接使用cluster_centers_方法获得,因为它返回的是原始数据标准化的中心,所以用for循环重新找到原始数据下的簇中心,并将其以五角星的标记添加到散点图中。
具体怎么解释这幅图我就不写了,写不动了=.=
- 本笔记基于刘顺祥的《从零开始学Python数据分析与挖掘》整理