第四章:聚类分析——K-Means聚类、DBSCAN聚类、用户分群实践

说到用户画像,最核心的一步就是分群。说白了,就是把一堆看起来差不多的用户,按照他们的消费行为拆成几个有鲜明特征的群体。我做了这么多年数据分析,发现很多同学一上来就搞复杂的模型,结果连最基础的聚类都没搞明白。今天咱们就踏踏实实把K-Means和DBSCAN这两个最常用的聚类方法讲透。

4.1 聚类分析到底在干什么?

先问个问题:给你1000个用户的消费记录,你怎么把他们分成几类?

传统做法是拍脑袋定规则——「消费超过5000的是高价值用户」。但这样太粗糙了。聚类分析不一样,它让数据自己说话。算法会根据用户在各个维度上的相似度,自动把相近的用户归到一起。

我个人的习惯是,在做聚类之前先画个散点图看看数据分布。有一次我接手一个电商项目,直接跑K-Means,结果分出来的群完全没法解释。后来一查,数据里有明显的离群点,把聚类中心都带偏了。嗯,这个教训让我养成了先可视化再建模的习惯。

核心思想: 物以类聚,人以群分。聚类就是让算法自动找到数据中的「自然分组」。

4.2 K-Means聚类:最经典的分群方法

K-Means的原理其实很简单。你想想看,假如要在平面上把一堆点分成K堆,最直观的做法是什么?

先随便选K个点作为「中心」,然后计算每个点到这些中心的距离,离谁近就归到谁那一堆。接着,重新计算每一堆的中心位置,再重新分配。重复这个过程,直到中心点不再变化。

这就是K-Means的全部逻辑。我刚开始学的时候觉得这算法太简单了,后来才发现,简单的东西往往最实用。

4.2.1 怎么选K值?

这是K-Means最头疼的问题。K值选不好,分出来的群要么太碎,要么太笼统。

我常用的方法是「肘部法则」。画一条曲线,横轴是K值,纵轴是每个样本到其所属簇中心的距离平方和(SSE)。随着K增大,SSE会下降。当曲线出现一个明显的「拐点」时,那个K值就是最佳选择。

举个例子:

from sklearn.cluster import KMeans
import matplotlib.pyplot as plt

# 假设X是你的用户特征数据
sse = []
for k in range(1, 11):
    kmeans = KMeans(n_clusters=k, random_state=42)
    kmeans.fit(X)
    sse.append(kmeans.inertia_)

plt.plot(range(1, 11), sse, marker='o')
plt.xlabel('K值')
plt.ylabel('SSE')
plt.title('肘部法则确定最佳K值')
plt.show()

我曾经在一个零售项目中用这个方法,发现K=4时曲线拐点最明显。但实际业务中,我们最终选了K=5。为什么?因为业务方说「4个群太少了,我们想多分一个高净值用户群」。你看,算法给的是参考,最终决策还是要结合业务。

我的经验: 肘部法则给出的是技术上的最优解,但业务需求往往更重要。如果K=4和K=5的SSE差别不大,优先选业务更需要的那个。

4.2.2 K-Means的优缺点

优点 缺点
算法简单,计算速度快 需要预先指定K值
适合大数据集 对离群点敏感
结果容易解释 只能发现球形簇
收敛速度快 初始中心选择影响结果

这里要特别说一下「对离群点敏感」这个问题。我曾经处理过一个用户消费数据,有个用户一个月消费了100万,明显是异常值。如果不处理,K-Means会把整个聚类中心往那个方向拉,导致其他用户的分群完全失真。所以,跑K-Means之前,一定要先做数据清洗。

4.3 DBSCAN聚类:处理不规则形状的利器

K-Means有个硬伤——它假设簇是球形的。但现实中的数据哪有那么规整?用户的行为模式往往是奇形怪状的。

这时候DBSCAN就派上用场了。它不关心簇的形状,只关心点的密度。核心思想是:如果一个点周围足够密集,那它就是一个簇的核心;如果点周围很稀疏,那它就是噪声。

DBSCAN有两个关键参数:

  • eps(邻域半径): 多大的范围算「附近」
  • min_samples(最小样本数): 附近至少要有多少个点才算「密集」

我刚开始用DBSCAN的时候,调这两个参数调得想摔键盘。eps设大了,所有点都变成一个簇;设小了,全是噪声点。后来我总结了一个经验:先用KNN距离图来估计eps的合理范围。

from sklearn.neighbors import NearestNeighbors
import numpy as np

# 计算每个点的k近邻距离
neighbors = NearestNeighbors(n_neighbors=5)
neighbors_fit = neighbors.fit(X)
distances, indices = neighbors_fit.kneighbors(X)

# 排序后画图,找到拐点
distances = np.sort(distances[:, -1])
plt.plot(distances)
plt.xlabel('样本点')
plt.ylabel('第5近邻距离')
plt.title('KNN距离图——辅助确定eps')
plt.show()

这张图里,曲线突然上升的那个点对应的距离,就是eps的合理取值。为什么?因为密集区域的点,它们的近邻距离都很小;到了稀疏区域,距离会突然变大。这个拐点就是密集和稀疏的分界线。

避坑指南: 我曾经在一个项目中直接用默认参数跑DBSCAN,结果所有用户都被归为噪声。后来发现是数据没有标准化。不同维度的量纲差异太大,导致距离计算完全失效。记住:聚类之前一定要做标准化!

4.4 用户分群实践:从数据到业务

好了,理论讲完了,咱们来点实际的。假设你手头有一份用户消费数据,包含以下字段:

  • 月均消费金额
  • 月均消费频次
  • 最近一次消费距今天数(R值)
  • 用户注册时长(月)

目标是:把用户分成3-5个有业务意义的群体。

4.4.1 第一步:数据预处理

先把数据标准化,然后用PCA降维到2维,方便可视化。这一步不是必须的,但能帮你直观看到聚类效果。

from sklearn.preprocessing import StandardScaler
from sklearn.decomposition import PCA

# 标准化
scaler = StandardScaler()
X_scaled = scaler.fit_transform(df[['消费金额', '消费频次', 'R值', '注册时长']])

# PCA降维
pca = PCA(n_components=2)
X_pca = pca.fit_transform(X_scaled)

4.4.2 第二步:跑K-Means

用肘部法则确定K=4,然后跑K-Means:

kmeans = KMeans(n_clusters=4, random_state=42)
labels = kmeans.fit_predict(X_scaled)

# 把聚类结果加回原始数据
df['cluster'] = labels

4.4.3 第三步:分析每个群体的特征

这一步最关键。不要只看聚类标签,要看每个群体在各个维度上的均值:

# 按聚类分组,计算均值
cluster_profile = df.groupby('cluster')[['消费金额', '消费频次', 'R值', '注册时长']].mean()
print(cluster_profile)

假设结果是这样的:

聚类 消费金额 消费频次 R值 注册时长 业务标签
0 高价值活跃用户
1 新注册低活跃用户
2 普通稳定用户
3 高价值沉睡用户

你看,聚类结果一下子就变得有业务意义了。高价值活跃用户要重点维护,高价值沉睡用户要召回,新用户要引导消费。每个群体都有对应的运营策略。

我的建议: 给每个聚类起个业务名字,别叫「聚类0」「聚类1」。名字起好了,业务方一看就懂,你的分析报告也就成功了一半。

4.5 K-Means vs DBSCAN:什么时候用哪个?

我个人的选择标准很简单:

  • 数据量大、簇是球形的、没有明显噪声: 用K-Means,又快又好
  • 数据形状不规则、有大量噪声、不知道有几个簇: 用DBSCAN

但说实话,在用户分群这个场景里,我大部分时候用K-Means。为什么?因为用户行为数据经过标准化后,往往呈现出比较规整的分布。而且K-Means的结果更容易向业务方解释——「我们把用户分成了4类,每类的特征是这样的」,简单直接。

DBSCAN更适合那些数据质量不高、或者簇的形状很诡异的场景。比如地理位置的聚类,用户活动范围可能是任意形状的,这时候DBSCAN就比K-Means靠谱得多。

4.6 本章小结

聚类分析是用户画像的基石。K-Means简单高效,适合大多数场景;DBSCAN灵活鲁棒,能处理复杂形状的数据。但无论用哪个方法,记住三点:

  1. 数据预处理比算法本身更重要
  2. 聚类结果一定要结合业务解释
  3. 没有完美的算法,只有合适的算法

嗯,这一章的内容就到这儿。聚类这东西,光看理论没用,你得动手跑一遍数据才能真正理解。下次遇到用户分群的需求,试试今天讲的这两个方法,你会发现数据真的会「说话」。


专注资料整理