1.2 聚类分析python练习

下面进入练习,我们有如下的数据集, 表示在一个坐标系中的所有点的坐标。我们希望通过聚类分析,将坐标系中的点分成几个不同的类别。


clustering_data.csv
  • 首先来导入需要的库:
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from scipy.spatial.distance import cdist
from scipy.cluster.hierarchy import linkage, fcluster, dendrogram
from sklearn.cluster import KMeans
from sklearn.cluster import DBSCAN
  • 读入数据并进行explanatory analysis:
data = pd.read_csv('clustering_data.csv')
print(data.head())
print(data.shape)
print(data.dtypes)

可以看到,通过shape属性,得知一共有1545个样本点,每个样本点有横纵坐标。

dtypes属性用于输出一个pandas dataframe中每个列数据类型的序列。要注意和dtype()函数做区分,dtype()只能作用于单个元素,并返回int,float等数据类型,而不能作用于类似列表和字典等容器,因为容器中存放了多种数据类型(但是dtype函数可以作用于np.array,因为数组只有一种数据类型)。

要想获得数据结构的类型,需要type()函数,会返回列表、字典等类型。

astype()函数用于改变数组中所有元素的数据类型,只有在能使用dtype()函数的前提下,才能使用astype()来改变类型

  • 画一个散点图,用于观察样本数据的分布情况
sns.scatterplot('x', 'y', data=data)
plt.title('Datapoints for Clustering')
plt.show()
样本点的分布
  • 为了后面作图更直观,我从原始数据中随机抽取了200个样本点进行分析。
sample = data.sample(200)
sns.scatterplot('x', 'y', data = sample) # data = 不能省略
plt.show()
200个样本点
  • 计算样本的single linkage,并画出dendrogram
    函数原型:
scipy.cluster.hierarchy.linkage(y, method='single', metric='euclidean', optimal_ordering=False)

因为在导入库的阶段我们使用了scipy.cluster.hierarchy,所以后面我们可以直接使用函数名进行调用。
对linkage函数的参数做出以下说明:
y: 可以是1维压缩向量(距离向量),也可以是二维观测向量(坐标矩阵,就是这里的data)


method参数:



返回值:
层次聚类会输出一个linkage矩阵Z,这个矩阵由四列组成,第一列和第二列分别是聚类簇的编号,在初始距离前每个初始值被从0到n-1进行标识,每生成一个聚类簇就在此基础上增加一对新的聚类簇;第三列表示前面两个聚类簇之间的距离;第四列表示新生成的聚类簇包含的元素的个数。

# build linkage from scipy module
Z = linkage(sample, metric='euclidean', method='single')
print(Z)
Z 矩阵

假设我们的y是个mn矩阵,表示m条记录,每条记录由n个特征,那么返回的结果Z是一个 (m-1)4 的矩阵:
我们使用 print(Z.shape), 得出的结果是(199,4)
层次分析图从上到下看,依次是枝和叶。

第一列和第二列代表类标签,包含叶子和枝子。

第三列代表叶叶(或叶枝,枝枝)之间的距离

第四列代表该层次类中含有的样本数(记录数)

X = linkage(y, method='single', metric='euclidean')
method是指计算类间距离的方法,比较常用的有3种:
(1)single:最近邻,把类与类间距离最近的作为类间距
(2)average:平均距离,类与类间所有pairs距离的平均
(3)complete:最远邻,把类与类间距离最远的作为类间距

  • 使用Z矩阵构造dendrogram并可视化
    这里用到了matplotlib中的plt.figure,参数figsize表示以英寸为单位的宽和高
plt.figure(figsize=(15,7))
dendrogram(Z, distance_sort='descending', no_labels=True)
plt.axhline(y=2, color='r', linestyle = '-')
plt.show()
dendrogram

distance_sort: str 或 bool,可选
对于每个节点 n,绘制 n 的两个后代链接的顺序(视觉上,从左到右)由该参数确定,该参数可以是以下任何值:
False:什么都没做。
'ascending' 或 True:首先绘制其直接后代之间距离最小的孩子。
'descending':首先绘制其直接后代之间距离最大的孩子。
注意 distance_sort 和 count_sort 不能同时为 True。

no_labels: 布尔型,可选
当为 True 时,在树状图的渲染中,叶节点旁边不会出现任何标签。

*注意:在dendrogram图中,如果我们把水平线向下移动,那么基于层次的聚类会输出更多的聚类簇,但是簇与簇之间的距离变小了

使用fcluster输出每个元素的所属类别
data['cluster'] = fcluster(Z, 4, criterion='maxclust')

plt.figure(figsize=(10, 7))
sns.scatterplot(x='x', y='y', hue='cluster', data = data, legend= False, palette= ["purple", "red", "green", "orange"])
plt.show()

注意,fcluster函数输出的是一个数组,数组中的每个数字表示相应位置的元素属于哪个类别(类别用数字表示)。参数中的4表示我们希望把原始数据分成4个类别。
这样我们原有的数据就分成了如下所示的4个类别:


t = 4

使用vq也能输出这张图:

from scipy.cluster.vq import kmeans, vq

cluster_centers, distortion = kmeans(data_kmeans, 4)
data_kmeans['labels'], distortion_list = vq(data_kmeans, cluster_centers)

sns.scatterplot(x='x', y='y', hue='labels', data=data_kmeans)
plt.show()
基于划分的聚类

Kmeans算法的目的是选择出簇的质心,使得各个聚类得补的inertia值最小。inertia是类内聚合度的一种度量方式,我们可以把inertia使用append函数加入到 distortion列表中,从而进行可视化,选出最优的类的个数。

下面使用第一种方法,即kmeans的fit函数进行操作:

distortions = []  # 扭曲
K = range(1, 10)
for k in K:  # range函数中间用逗号!!!!
    km = KMeans(n_clusters=k)
    km.fit(data_kmeans)
    distortions.append(km.inertia_)  #km.inertia_是个啥???


plt.figure(figsize=(16,8))

sns.lineplot(x=K, y=distortions, marker='x')  # plt没有lineplot函数!!!!而且x参数需要一个序列

plt.xlabel('k')
plt.ylabel('Distortions')
plt.title('The elbow curve')
plt.show()    
KMeans(n_clusters=k).fit(data)

第二种方法:使用kmeans函数

from scipy.cluster.vq import kmeans, vq

num_clusters = range(1, 10)
distortions = []

for i in num_clusters:
    centroids, distortion = kmeans(data_kmeans, i)
    distortions.append(distortion)

plt.figure(figsize=(16, 8))

sns.lineplot(x=num_clusters, y=distortions, marker='x')
plt.xlabel('k')
plt.ylabel('distortions')
plt.title('The elbow curve')
plt.show()
用KMeans分成7个类别

这一节会构造KMeans类的对象,并对KMeans构造函数的参数进行讲解:

  • n_clusters: 聚类的个数k,默认为8
  • init:初始化的方式,默认使用k-means++
  • n_init: 运行kmeans的次数,最后取效果最好的一次,默认10
  • max_iter: 最大迭代次数,默认300次
kmeans = KMeans(n_clusters= 7, init="random", n_init=1, max_iter=12).fit(data_kmeans)

data_kmeans["cluster"] = kmeans.labels_  # labels_ 以np array的形式输出原始数据聚类后的标签值
print(kmeans.labels_)
print(kmeans.labels_.size)

plt.figure(figsize=(10, 7))
sns.scatterplot(x='x', y='y', hue='cluster', data = data_kmeans, legend= False)
plt.show()
7个类别

*注意:1. 这样划分并不理想, 2. labels_属性会以数组的形式输出不同的样本点所属的类别,输出的size是1545,说明每个样本点都进行了分类

预测样本数据之外的点

上面通过fit函数训练好了KMeans对象,现在用这个训练好的模型做一些预测:

to_predict = pd.DataFrame({'x': [0,1,2,3], 'y': [0,1,2,3]})
print(to_predict)
kmeans.predict(to_predict)

预测结果

*注意:在预测的时候,用于预测的数据一定要和训练模型时的数据拥有相同数量的features,也就是列。预测结果显示,我们的三组数据都属于第三个类别
预测是有效的

输出7个类别质心的坐标:
质心坐标

*注意:sklearn包中,几乎所有的模型的建模函数都是fit,预测函数都是predict

执行error minimization algorithm
def getcentroids(df, k):
    x_cent = np.random.uniform(df.iloc[:,0].min(), df.iloc[:,0].max(), k)
    y_cent = np.random.uniform(df.iloc[:,1].min(), df.iloc[:,1].max(), k)
    return pd.DataFrame({"x": x_cent, "y": y_cent})
    
def kmeans_clust(x,k):
    
    fd = x.copy()
    # step a
    # Choose k random points from the dataset
    centroids = getcentroids(x, k)
     
    # step b
    #finding the distance between centroids and all the data points
    distances = cdist(x, centroids ,'euclidean')
    #print(pd.DataFrame(distances))
    
    #Centroid with the minimum Distance
    points = np.array([np.argmin(i) for i in distances])
    #print(pd.DataFrame(points))
    
    fig, ax = plt.subplots(4, 3, figsize=(18, 10))
    ax = ax.ravel()
    fig.tight_layout(pad=4)
     
    #Repeating the above steps for a defined number of iterations
    #Step c
    for iter in range(12): 
        centroids = []
        for idx in range(k):
            #Updating Centroids by taking mean of Cluster it belongs to
            temp_cent = x[points==idx].mean(axis=0) 
            centroids.append(temp_cent)
 
        centroids = np.vstack(centroids) #Updated Centroids
        distances = cdist(x, centroids ,'euclidean')
        points = np.array([np.argmin(i) for i in distances])

        fd["clusters"] = points
        cent = pd.DataFrame(centroids)
        sns.scatterplot("x", "y", hue="clusters", ax=ax[iter], data=fd, legend= False)
        sns.scatterplot(0, 1, ax=ax[iter], data = cent, legend=False)
        ax[iter].set_title(f'Iteration {iter}')

    return points

函数原型: numpy.random.uniform(low,high,size)

功能:从一个均匀分布[low,high)中随机采样,注意定义域是左闭右开,即包含low,不包含high.

参数介绍:
low: 采样下界,float类型,默认值为0;
high: 采样上界,float类型,默认值为1;
size: 输出样本数目,为int或元组(tuple)类型,例如,size=(m,n,k), 则输出mnk个样本,缺省时输出1个值。

返回值:ndarray类型,其形状和参数size中描述一致。

下面介绍基于密度的聚类分析DBSCAN
DBSCAN(eps=0.5, min_samples=5, metric='euclidean', algorithm='auto', leaf_size=30, p=None, n_jobs=1)

参数介绍:

  • eps:两个样本之间的最大距离,即扫描半径
  • min_samples:核心点的邻域(以该点为圆心,eps为半径的圆,含圆上的点)中包含的最小样本数,包含这个点本身
  • metric:度量方式,默认为欧氏距离
cluster = DBSCAN(eps=0.5, min_samples=6).fit(data_dbs)

data_dbs["cluster"] = cluster.labels_

plt.figure(figsize=(10, 7))
sns.scatterplot(x='x', y='y', hue='cluster', data = data_dbs, legend= False)
plt.show()
DBSCAN

基于密度的聚类有以下优点:

  1. 能克服基于划分(距离)的算法只能发现类圆形(凸)的聚类的缺点
  2. 可以发现任意形状的聚类,且对噪声数据不敏感
  3. 不需要指定类的数目n_clusters
  4. 算法中只有两个参数,即扫描半径eps和最小包含点数min_samples

缺点:

  1. 计算复杂度较高
  2. 受eps影响较大。在类中的数据分布不均匀时,eps较小时,密度小的cluster会被划分成多个性质 类似的cluster,当eps较大时,会使得距离较近且密度较大的cluster被合并成一个cluster

你可能感兴趣的:(1.2 聚类分析python练习)