ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Python聚类可视化实战:散点图、树状图与热力图详解

Python聚类可视化实战:散点图、树状图与热力图详解 搞数据分析的人迟早会遇到一个问题你辛辛苦苦把数据聚类出来了模型把用户分成了几群然后呢然后你总不能甩给老板一张全是数字的表。这时候就需要数据可视化把聚类结果用一张图讲清楚。Python生态里聚类图不是某个库的专利而是sklearn、matplotlib、seaborn协同作业的结果。这篇文章我会从聚类图的基本思路讲起用三个最常用的形态——散点聚类图、层次聚类树状图、聚类热力图——把从数据预处理到出图的全流程走一遍适合刚接触Python数据分析和可视化的人也适合那些“会调聚类算法但不知道图怎么画才好看”的读者。我的底气和经验来自过去两年处理过的几个真实项目用户分群、商品品类划分、门店动线分析。每一次最后能让业务方点头的都不是模型指标本身而是那张一眼能看懂的聚类图。所以这篇文章不准备讲复杂的算法推导算法只是工具我要讲的是工具怎么落地以及落地时那些文档里不会写的东西。1. 聚类图到底画的是什么先想清楚再动手1.1 聚类的本质是“把相似的东西放一起”可视化是把它讲清楚聚类Clustering是一种无监督学习方法目标是在没有标签的情况下把样本分成若干个簇让簇内的样本尽可能相似、簇间的样本尽可能不同。KMeans、DBSCAN、层次聚类这些算法大家或多或少听过它们负责的是“怎么分”的问题。而聚类图解决的是“分得怎么样”以及“分出来长什么样”的问题。我第一次用KMeans跑完用户分群后直接打印了每个簇的平均消费金额、平均活跃天数做了一张大表。领导说“你能不能给我看一张图让我一眼看到这些人分布在哪些位置”当时我才明白聚类输出的labels只是一串数字真正让别人理解结果必须用图把样本的空间分布呈现出来。聚类图最常见的形态有三种散点聚类图把样本投影到二维或三维空间用颜色区分簇配以簇心标记最直观。树状图Dendrogram层次聚类独有的可视化方式展示样本合并成簇的层级过程适合展示“谁跟谁更近”。聚类热力图用clustermap把样本-特征矩阵按聚类结果重排后以热力图呈现高维数据特别适合。可以打个比方。你请100个朋友吃饭没有任何人告诉你谁跟谁熟你自己观察谁聊得来然后给他们分成几桌。聚类算法就是那个“观察并分桌”的过程。而聚类图就是你把最终的分桌结果画成一张座位图谁跟谁在一桌一目了然如果座次还有层级比如大厅、包间、圆桌那就成了树状图。1.2 为什么Python生态画聚类图最顺手不是只有Python能做这件事R语言做聚类可视化也很强Excel的散点图也能勉强看但Python生态的优势在于“分析可视化”一条链路全部打通。你在同一个环境里完成数据清洗、聚类计算、降维、画图不需要在不同软件之间来回导数据。库在聚类图中干什么使用频率scikit-learn提供KMeans/DBSCAN/层次聚类接口输出labels和簇心必用matplotlib底层绘图散点聚类图的主要绘制者必用seaborn封装好看的主题clustermap热力图是杀手锏高频scipylinkage和dendrogram画树状图的源头需要时pandas/numpy数据处理读表、预处理必用这套组合的好处是生态成熟网上案例和文档都很全遇到问题基本一搜就有答案。而且这些库的接口设计是兼容的sklearn出来的标签数组可以直接交给matplotlib的c参数scipy算出来的链接矩阵可以直接喂给seaborn的clustermap不用写一堆胶水代码。2. 画图前的工具准备与数据认知2.1 环境安装一套最少依赖的组合先别急着写代码环境没配好等于白忙。我建议用虚拟环境别在系统Python里乱装一堆包踩过坑的人都懂。python -m venv venv source venv/bin/activate # Windows下用 venv\Scripts\activate然后安装依赖pip install numpy pandas matplotlib seaborn scikit-learn scipy如果你在国内pip直接装可能很慢甚至超时。我一般会加镜像源pip install -i https://pypi.tuna.tsinghua.edu.cn/simple numpy pandas matplotlib seaborn scikit-learn scipy注意matplotlib和seaborn之间有版本兼容问题一般建议都装最新稳定版不要混用老版本。Python建议3.9及以上。装好之后可以先跑一个冒烟测试确认绘图后端正常import matplotlib.pyplot as plt plt.plot([1, 2, 3], [4, 5, 6]) plt.show()如果弹出窗口能看到曲线说明环境没问题。2.2 数据标准化不缩放聚类图就是骗人的这是我觉得新手最容易踩的坑。很多人从CSV里把数据读进来直接塞进KMeans就开始画图结果画出来的图就是一条大直线簇完全分不开。问题出在量纲差异。假设你有两个特征消费金额50到10000和活跃天数1到30。KMeans算的是欧氏距离金额数值大距离计算基本被它一个特征主导活跃天数的贡献几乎可以忽略。绘图也一样横纵坐标比例失衡散点全部被拉伸视觉上根本看不出簇的形状。标准的做法是先做标准化。我用得最多的是StandardScaler它让每个特征变成均值0、方差1不受量纲影响from sklearn.preprocessing import StandardScaler scaler StandardScaler() X_scaled scaler.fit_transform(X)如果你更在意数据有明确的上下界也可以试试MinMaxScaler把数据缩放到[0,1]区间。区别在于StandardScaler对异常值更稳健一点MinMaxScaler则会把异常值硬压到边界上。我默认用StandardScaler。数据清洗这一步也别跳过。缺失值最简单的处理是dropna()但会丢样本样本量本来就小的时候要斟酌异常值也值得先看一眼因为KMeans对离群点很敏感个别极值点会直接把簇心拉偏。我会先用describe看分布再用箱线图定位异常值必要时做截尾处理。3. 核心实操三种常用的聚类图怎么画3.1 KMeans散点聚类图最直观的“簇簇心”首选从最经典的散点图开始。为了演示我用sklearn自带的make_blobs生成300个样本、4个高斯簇的数据这样大家能一眼看出正确聚类应该是什么样。from sklearn.datasets import make_blobs X, _ make_blobs(n_samples300, centers4, cluster_std0.60, random_state42)然后训练KMeans并画图import matplotlib.pyplot as plt from sklearn.cluster import KMeans kmeans KMeans(n_clusters4, initk-means, n_init10, random_state42) labels kmeans.fit_predict(X) plt.figure(figsize(10, 6)) plt.scatter(X[:, 0], X[:, 1], clabels, s40, cmapviridis, alpha0.8) plt.scatter( kmeans.cluster_centers_[:, 0], kmeans.cluster_centers_[:, 1], marker*, s300, corange, edgecolorsblack, linewidths1.5, labelCentroids, ) plt.title(KMeans Clustering Result) plt.xlabel(Feature 1) plt.ylabel(Feature 2) plt.legend() plt.grid(alpha0.3) plt.tight_layout() plt.show()几个参数我说一下为什么这样设。initk-means会优化初始质心的选择比简单的随机初始化收敛更快、结果更稳n_init10表示跑10次取最优减少陷入局部最优的概率random_state42固定随机种子保证图每次跑出来完全一样你发报告、写博客时必须固定它否则下次复现别人会骂你。cmapviridis是色盲友好的连续色图别一上来就上彩虹色看着花哨但辨识度很差。画出来之后你会看到每个点被赋予一种颜色五角星是簇心。这是最基础的聚类图如果你的数据本身就是二维或三维直接这样画就够了。3.2 层次聚类树状图看的是“合并过程”散点图展示的是最终分簇结果树状图展示的是“怎么一步步合并成这些簇”的。当你需要解释为什么这两个群体离得更近时树状图比散点图更有说服力。用经典的鸢尾花数据集演示import matplotlib.pyplot as plt from scipy.cluster.hierarchy import linkage, dendrogram from sklearn.datasets import load_iris iris load_iris() Z linkage(iris.data, methodward) plt.figure(figsize(12, 6)) dendrogram(Z, truncate_modelevel, p3, show_leaf_countsTrue) plt.title(Hierarchical Clustering Dendrogram (truncated)) plt.xlabel(Sample index or (cluster size)) plt.ylabel(Distance) plt.axhline(y8, colorred, linestyle--, linewidth1) plt.show()linkage是核心函数参数method我常用两种ward让合并时类内方差增量最小得到的簇比较紧凑业务场景里最常见average用平均距离对噪声更稳健。single容易产生长条状链实际项目中我基本不用。树状图怎么读从下往上看是“合并过程”两个样本或簇连接得越高说明它们距离越远。从上往下切一刀被水平线切到的分支数量就是聚类数量。我在图里画了一条红色虚线如果你在y8的位置切下去大约会切出3个簇。日常选K时我会同时参考树状图的“大gap”和业务可解释性而不是只盯着肘部曲线。当样本很多时树状图叶子密密麻麻根本看不清这时用truncate_modelevel配合p3只展示最上面3个层级括号里的数字表示该分支下聚合的样本数量。这个方法我强烈建议记住样本量上千的时候全靠它救人。3.3 聚类热力图高维数据的最佳拍档第三个常用形态是聚类热力图用seaborn的clustermap实现。它的思路是把样本-特征矩阵按聚类结果重新排列再用色块矩阵展示数值高低行列边上同时画上聚类树状图。高维特征下散点图没法直接画但热力图可以把“哪些样本在哪些特征上相近”完整呈现。import seaborn as sns import matplotlib.pyplot as plt from sklearn.preprocessing import StandardScaler iris_scaled StandardScaler().fit_transform(iris.data) sns.clustermap( iris_scaled, methodward, cmapcoolwarm, figsize(10, 8), dendrogram_ratio0.15, col_clusterFalse, ) plt.show()dendrogram_ratio0.15控制图边缘树状图的宽度比例特征和样本多时可以调小给主热力图让出空间。col_clusterFalse表示只对样本行聚类不对特征列聚类因为业务上我们通常关心“样本怎么分群”特征之间的聚类反而会干扰看图。如果你希望同时看看哪些特征容易抱团把它改成True即可。热力图配色我比较喜欢coolwarm或viridis不要用那种非常艳丽的红绿配色色盲用户完全看不清而且红绿在印刷出来以后对比度很差。4. 实战案例电商用户RFM分群聚类图4.1 造一份能用的数据理论讲完来一个完整案例。RFM是用户运营里非常经典的特征组合最近一次消费距今天数、消费频次、消费金额。这三个特征可以很好地支撑用户分层。我先模拟200个用户的数据import pandas as pd import numpy as np np.random.seed(42) n 200 df pd.DataFrame({ recency: np.random.randint(1, 90, sizen), frequency: np.random.randint(1, 30, sizen), monetary: np.random.normal(loc200, scale80, sizen).clip(30, 500), }) df.head()然后标准化顺便把特征单独拿出来from sklearn.preprocessing import StandardScaler feature_cols [recency, frequency, monetary] scaler StandardScaler() X_scaled scaler.fit_transform(df[feature_cols])4.2 用肘部法则确定K值KMeans要先确定K最常见的方法是肘部法则。核心指标是inertia也就是每个样本到归属簇心的距离平方和它越小说明簇越紧。from sklearn.cluster import KMeans import matplotlib.pyplot as plt sse [] K range(1, 11) for k in K: km KMeans(n_clustersk, initk-means, n_init10, random_state42) km.fit(X_scaled) sse.append(km.inertia_) plt.figure(figsize(8, 5)) plt.plot(K, sse, o-) plt.xlabel(Number of clusters (k)) plt.ylabel(Inertia (Sum of Squared Errors)) plt.title(Elbow Method for Optimal k) plt.grid(alpha0.3) plt.show()K从1增大时inertia必然下降我们要找的是“下降幅度明显变缓”的拐点。这个案例里拐点大概在K3附近而且三分类业务上也好解释高价值活跃用户、普通用户、流失边缘用户。我个人的建议是肘部法则给一个参考区间业务可解释性拍板最终K不要机械地选那个“数学最优点”。4.3 完整出图并解读结果确定K3后训练最终模型并给数据打上标签final_k 3 km KMeans(n_clustersfinal_k, initk-means, n_init10, random_state42) labels km.fit_predict(X_scaled) df[cluster] labels这里数据是三维的散点图画不了原始空间需要降维到二维。最常用也最稳的降维方法是PCAfrom sklearn.decomposition import PCA pca PCA(n_components2, random_state42) X_pca pca.fit_transform(X_scaled) plt.figure(figsize(10, 6)) scatter plt.scatter( X_pca[:, 0], X_pca[:, 1], clabels, cmapviridis, s50, alpha0.8, ) plt.title(Customer Segmentation (PCA Projection)) plt.xlabel(PC1) plt.ylabel(PC2) plt.colorbar(scatter, labelCluster) plt.show()画完图之后别急着发出去先看看PCA到底保留了多少信息print(pca.explained_variance_ratio_) print(sum(pca.explained_variance_ratio_))如果前两个主成分的累计方差贡献率在70%以上这张二维聚类图基本可信。如果只有50%左右那就要小心了图上靠得近的样本在原始空间不一定真的近建议补充热力图或者箱线图辅助解读。最后也是最容易被忽略的一步给每个簇做业务画像。聚类图本身只是“分开了”但是哪个簇是高价值用户必须回到原始特征上看均值df.groupby(cluster)[feature_cols].mean().round(1)输出大概长这样clusterrecencyfrequencymonetary068.27.3188.6114.622.4310.2242.514.1215.9这类结果一看就能给簇起名字簇1是高频高价值的活跃客户簇0是好久没来的沉睡用户簇2是中间层。下次汇报时就说“我们分出了3类用户”而不是“Cluster 0、1、2”差距立竿见影。5. 常见问题与排查技巧5.1 中文和负号显示乱码matplotlib默认字体不支持中文你只要在标题或坐标轴上用了中文画出来的就是一个个小方框。我以前第一次画聚类图看到标题变方框还以为电脑坏了。解决方案是在画图前设置字体plt.rcParams[font.sans-serif] [SimHei] # Windows一般用黑体 plt.rcParams[axes.unicode_minus] False # 解决负号显示为方块注意在Linux服务器或Docker容器里SimHei字体不一定存在。最稳妥的方案是装一个中文字体比如apt install fonts-noto-cjk然后把sans-serif设为Noto Sans CJK SC。我后来养成的习惯是但凡画图涉及到中文字体先在环境里确认字体再跑绘图脚本。5.2 高维数据画出来一团糟特征维度一多直接画二维散点图就是在自欺欺人。我见过有人拿10个特征直接取前两列画聚类图图里分得清才怪。正确的做法是先降维。PCA是首选线性、可解释、稳定代码几行就搞定。如果PCA降到二维仍一坨可以试t-SNE或UMAP它们善于保持局部结构但缺点是结果带有随机性且不保证簇之间的距离有实际意义用于探索可以用于最终业务结论要谨慎。5.3 KMeans结果每次跑都不一样如果你没设random_stateKMeans每次运行都可能给出不同的簇划分尤其是数据本身簇结构不明显的时候。这不是算法bug是因为初始质心是随机的。解决方法是固定随机种子KMeans(n_clusters3, initk-means, n_init10, random_state42)另一个思路是加大n_init从默认的10提高到50多跑几次取最优结果更稳定代价是训练时间变长。数据量几千条时几乎无感我通常直接设50。5.4 聚类图保存后变糊用plt.show()截图当然模糊。要输出高清图在show()之前调用plt.savefig(cluster.png, dpi300, bbox_inchestight)dpi300保证印刷尺寸清晰bbox_inchestight会裁掉周围多余留白。如果图里有文字说明我推荐保存成PDF或SVG矢量格式savefig(cluster.pdf)之后放多大都不糊。5.5 簇标签顺序和业务含义对不上KMeans输出的标签是0、1、2这样的数字但它不保证标签编号和你的业务顺序一致。比如你想让“高价值用户”总是红色但算法这次把高价值用户标成了1下次标成了2。解决办法是画图前先看一眼每个簇的特征均值再手动重映射标签。我用得很顺手的方式是建一个字典mapping {1: high_value, 0: sleeping, 2: medium} df[segment] df[cluster].map(mapping)再画图时直接用segment列做颜色映射配色和图例都好看很多。6. 一些实操心得6.1 让聚类图变成沟通工具画图这件事技术只占一半另一半是“别人看不懂等于白画”。我在实战中发现业务方不关心你的P值、不关心你的轮廓系数他们关心的是“这张图能让我接下来的运营动作有方向”。所以我的图一定会有三样东西簇心标记、每个簇的样本量、每个簇的业务标签。图上信息适度即可不要堆一行行注释能让看图的人在三秒内说出“分了三群人这群人别放弃”这张图就成功了。6.2 先固定随机种子再谈其他现在开始落到实处每次跑聚类的第一个动作是固定random_state第二个动作是保存版本号。如果你做分析报告代码里不固定随机种子第二天打开Jupyter再run一遍图变了、结论可能也跟着变那整个分析的复现性就没了。这个习惯帮我在多次返工里省下很多时间。最后再分享一个小技巧画完聚类图顺手把簇心在标准化之前的值打印出来。因为标准化后的簇心数值是抽象的单位业务根本看不懂还原成原始量纲比如“高价值用户平均每月消费410元、近7天活跃”这样的话从图到结论全程都是人话。聚类可视化这件事很多时候不是在炫技术而是降低别人理解你结果的门槛。希望这篇内容能帮你在下次提交分析报告时少改一版图。
返回列表