聚类方法的可视化

Visualization of clustering methods

编辑的备注:Evie Fowler是ODSC West的演讲者。一定要去看她的演讲,“在客户细分中弥合可解释性差距”!

在今年秋季的Open Data Science Conference上,我将讲解如何对聚类模型进行系统化解释。为了准备这个话题,让我们先来谈谈聚类模型的数据可视化。

准备工作空间

所有这些可视化都可以使用数据操作的基本工具(pandas和numpy)以及可视化的基础知识(matplotlib和seaborn)来创建。

from matplotlib import colormaps, pyplot as plt
from sklearn.cluster import KMeans
from sklearn.datasets import load_diabetes
from sklearn.preprocessing import MinMaxScaler
import numpy as np
import pandas as pd
import seaborn as sns

在本教程中,我将使用matplotlib内置的糖尿病预测数据集。在ODSC上,我将提供更多关于如何训练和评估有效的聚类模型的见解,但现在,我只是简单地拟合几个简单的k-means模型。

# 载入糖尿病数据
diabetesData = load_diabetes(as_frame=True).data

# 对可聚类特征进行中心化和缩放
diabetesScaler = MinMaxScaler().fit(diabetesData)
diabetesDataScaled = pd.DataFrame(diabetesScaler.transform(diabetesData), 
                                  columns=diabetesData.columns, 
                                  index=diabetesData.index)

# 构建三个小的聚类模型
km3 = KMeans(n_clusters=3).fit(diabetesDataScaled)
km4 = KMeans(n_clusters=4).fit(diabetesDataScaled)
km10 = KMeans(n_clusters=10).fit(diabetesDataScaled)

选择颜色方案

matplotlib包通过其colormaps注册表提供了许多内置的颜色方案。选择一个颜色映射方案用于整个可视化过程非常方便,但选择时要慎重考虑。这可能包括评估颜色映射是否顺序(用于可以沿着从低到高的尺度解释数据的情况)或发散(用于数据在两个极端处最相关的情况),以及是否与主题相关(例如,对于地形项目使用绿色和棕色)。当数据与其呈现顺序之间没有特定关系时,nipy_spectral颜色映射是一个很好的选择。

# 从matplotlib中选择nipy_spectral颜色映射
nps = colormaps['nipy_spectral']

# 查看整个颜色映射
nps

每个matplotlib颜色映射由一系列元组组成,每个元组以RGBA格式描述一种颜色(尽管组件的尺度为[0, 1]而不是[0, 255])。可以通过整数(介于0和255之间)或浮点数(介于0和1之间)来访问映射中的单个颜色。接近0的数字对应于颜色映射的较低端的颜色,而接近255的整数和接近1.0的浮点数对应于颜色映射的较高端的颜色。直观地说,可以用整数或将该整数表示为255的商的浮点数来描述相同的颜色。

# 查看颜色映射中的某些颜色
print(nps(51))
#(0.0, 0.0, 0.8667, 1.0)

print(nps(0.2))
#(0.0, 0.0, 0.8667, 1.0)

创建可视化

散点图

聚类模型的经典可视化是一系列散点图,比较了输入聚类模型的每对特征,颜色表示聚类分配。虽然有内置的方法可以实现这一点,但自己动手可以更好地控制细节,如颜色方案。

def plotScatters(df, model):
    """基于数据框中的每对列创建散点图。
    使用颜色表示模型标签。
    """
    
    # 创建一个图形和坐标轴
    plotRows = df.shape[1]
    plotCols = df.shape[1]
    fig, axes = plt.subplots(
        # 为数据框中的每个特征创建一行和一列
        plotRows, plotCols
        # 增大图形的大小以便查看
        , figsize=((plotCols * 3), (plotRows * 3))
    )   
    # 遍历子图以创建散点图
    pltindex = 0
    for i in np.arange(0, plotRows):
        for j in np.arange(0, plotCols):
            pltindex += 1
            # 确定当前子图
            plt.subplot(plotRows, plotCols, pltindex)
            plt.scatter(
                # 比较数据框的第i列和第j列
                df.iloc[:, j], df.iloc[:, i]
                # 使用整数聚类标签和颜色映射来统一颜色选择
                , c=model.labels_, cmap=nps
                # 选择较小的标记大小以减少重叠
                , s=1)
            # 在子图的底部行上标记x轴
            if i == df.shape[1] - 1:
                plt.xlabel(df.columns[j])
            # 在子图的第一列上标记y轴
            if j == 0:
                plt.ylabel(df.columns[i])           

    plt.show()

这些图表具有双重作用,既显示了一对特征之间的关系,又显示了这些特征与聚类分配之间的关系。

plotScatters(diabetesDataScaled, km3)

随着分析的进行,很容易将注意力集中在一小部分特征上。

plotScatters(diabetesDataScaled.iloc[:, 2:7], km4)

小提琴图

为了更好地了解每个聚类中每个特征的分布,我们还可以查看小提琴图。如果您对小提琴图不熟悉,可以将它们视为经典箱线图的成年堂兄弟。箱线图仅识别分布的几个关键描述符,而小提琴图则通过等高线来说明整个概率密度函数。

def plotViolins(df, model, plotCols = 5):
    """ 创建数据框中每个特征的小提琴图
    使用模型标签进行分组。
    """  

    # 计算绘图网格所需的行数
    plotRows = df.shape[1] // plotCols
    while plotRows * plotCols < df.shape[1]:
        plotRows += 1      

    # 创建图形和坐标轴
    fig, axes = plt.subplots(plotRows, plotCols
                             # 放大图形尺寸以便查看
                             , figsize = ((plotCols * 3), (plotRows * 3))
                            )  

    # 从模型中识别唯一的聚类标签
    uniqueLabels = sorted(np.unique(model.labels_))    

    # 从唯一标签创建自定义子调色板
    npsTemp = nps([x / max(uniqueLabels) for x in uniqueLabels])  

    # 将整数聚类标签添加到输入数据框中
    df2 = df.assign(cluster = model.labels_)  

    # 遍历子图以创建小提琴图
    pltindex = 0
    for col in df.columns:
        pltindex += 1
        plt.subplot(plotRows, plotCols, pltindex)
        sns.violinplot(
            data = df2
            # 使用聚类标签作为x分组器
            , x = 'cluster'
            # 使用当前特征作为y值
            , y = col
            # 使用聚类标签和自定义调色板统一颜色选择
            , hue = model.labels_
            , palette = npsTemp
        ).legend_.remove()
        # 使用特征名称标记y轴
        plt.ylabel(col)   

    plt.show()

plotViolins(diabetesDataScaled, km3, plotCols = 5)

直方图

小提琴图显示了每个聚类中每个特征的分布,但也有助于查看每个聚类在每个特征的更广泛分布中的表示情况。修改后的直方图可以很好地说明这一点。

def histogramByCluster(df, labels, plotCols = 5, nbins = 30, legend = False, vlines = False):
    """ 创建每个特征的直方图。
    使用模型标签进行颜色编码。
    """
 
    # 计算绘图网格所需的行数
    plotRows = df.shape[1] // plotCols
    while plotRows * plotCols < df.shape[1]:
        plotRows += 1

    # 识别唯一的聚类标签
    uniqueLabels = sorted(np.unique(labels))
  
    # 创建图形和坐标轴
    fig, axes = plt.subplots(plotRows, plotCols
                             # 放大图形尺寸以便查看
                             , figsize = ((plotCols * 3), (plotRows * 3))
                            )
    pltindex = 0
    # 遍历输入数据中的特征
    for col in df.columns:
        # 将特征离散化为指定数量的柱状图
        tempBins = np.trunc(nbins * df[col]) / nbins
        # 将离散化的特征与聚类标签交叉
        tempComb = pd.crosstab(tempBins, labels)
        # 创建与交叉表大小相同的索引,这将有助于对齐
        ind = np.arange(tempComb.shape[0])

        # 识别相关的子图
        pltindex += 1
        plt.subplot(plotRows, plotCols, pltindex)
        # 创建分组直方图数据
        histPrep = {}
        # 逐个聚类处理
        for lbl in uniqueLabels:
            histPrep.update(
                {
                    # 将聚类标签与条形图关联...
                    lbl:
                    # ...使用特征特定的索引设置x位置
                    plt.bar(
                        # 使用特征特定的索引设置x位置
                        x = ind
                        # 将此聚类相关的计数作为条形高度
                        , height = tempComb[lbl]
                        # 将此条形放在先前聚类条形的上方
                        , bottom = tempComb[[x for x in uniqueLabels if x < lbl]].sum(axis = 1)
                        # 消除条形之间的间隙
                        , width = 1
                        , color = nps(lbl / max(uniqueLabels))
                    )
                }
            )
       
        # 使用特征名称标记每个绘图的x轴
        plt.xlabel(col)
    
        # 在第一列的绘图上标记y轴
        if pltindex % plotCols == 1:
            plt.ylabel('Frequency')
        plt.xticks(ind[0::5], np.round(tempComb.index[0::5], 2))
     
        # 如果需要,叠加垂直线
        if vlines:
            for vline in vlines:
                plt.axvline(x = vline * ind[-1], lw = 0.5, color = 'red')
    
    if legend:
        leg1 = []; leg2 = []
        for key in histPrep:
            leg1 += [histPrep[key]]
            leg2 += [str(key)]
        plt.legend(leg1, leg2)

    plt.show()
histogramByCluster(diabetesDataScaled, km4.labels_)

当需要更多的集群类别时,这个过程很容易扩展。

histogramByCluster(diabetesDataScaled, km10.labels_)

结论

这些可视化图形将为评估聚类模型提供强有力的基础。有关如何以系统化的方式进行评估的更多信息,请务必参加我在今年秋季在旧金山举行的Open Data Science Conference上的演讲!

关于作者:

Evie Fowler是一位位于宾夕法尼亚州匹兹堡的数据科学家。她目前在医疗保健行业工作,领导一个团队的数据科学家,开发以患者护理体验为中心的预测模型。她对预测分析的道德应用和探索定性方法如何可以为数据科学工作提供信息具有特别的兴趣。她拥有布朗大学的本科学位和卡内基梅隆大学的硕士学位。