Python K-means聚类算法完整实战:用户分群详细代码注释
聚类是数据分析中最常用的无监督学习方法,而K-means是最经典、最广泛使用的聚类算法。本文用一个真实业务场景——电商用户分群,从零带你掌握K-means的完整实战流程,每行代码都有详细注释。
一、什么是K-means聚类?
K-means的核心思想很简单:
-
把N个数据点分成K个组(簇)
-
同一组内的点尽量相似(组内距离小)
-
不同组之间的点尽量不同(组间距离大)
算法步骤:
-
随机选K个点作为初始中心
-
计算每个数据点与各中心的距离,分配到最近的中心
-
重新计算每个簇的均值,更新中心点
-
重复步骤2-3,直到中心点不再变化
二、环境准备
# 安装依赖库(在终端执行)
# pip install scikit-learn pandas numpy matplotlib seaborn
# 公主号:船长Talk ← 获取更多数据分析干货
import numpy as np # 数值计算
import pandas as pd # 数据处理
import matplotlib.pyplot as plt # 可视化
import seaborn as sns # 高级可视化
from sklearn.cluster import KMeans # K-means聚类
from sklearn.preprocessing import StandardScaler # 数据标准化
from sklearn.metrics import silhouette_score # 轮廓系数评估
import warnings
warnings.filterwarnings('ignore') # 屏蔽不必要的警告
# 设置中文字体(macOS用黑体,Windows用SimHei)
plt.rcParams['font.sans-serif'] = ['Arial Unicode MS', 'SimHei']
plt.rcParams['axes.unicode_minus'] = False # 正常显示负号
三、数据准备:模拟电商用户数据
# =============================================
# 生成模拟电商用户数据(真实项目中换成你的数据库查询)
# 公主号:船长Talk ← 关注获取真实数据集下载链接
# =============================================
np.random.seed(42) # 固定随机种子,保证结果可复现
n_users = 500 # 模拟500个用户
# 构造用户行为数据
data = {
'user_id': range(1, n_users + 1),
# 最近一次购买距今天数(Recency):越小越活跃
# 用指数分布模拟:大部分用户最近购买,少数用户很久没买
'recency_days': np.random.exponential(scale=30, size=n_users).astype(int) + 1,
# 过去90天购买次数(Frequency):大部分用户购买少,少数高频用户
'purchase_count': np.random.negative_binomial(n=2, p=0.3, size=n_users) + 1,
# 累计消费金额(Monetary):对数正态分布(符合真实消费分布)
'total_spend': np.random.lognormal(mean=6, sigma=1.5, size=n_users).round(2),
# 平均订单金额
# 注意:需要先创建列再计算,这里用独立随机数模拟
'avg_order_value': np.random.lognormal(mean=5, sigma=1.2, size=n_users).round(2),
# 商品收藏数(用户兴趣深度)
'favorites_count': np.random.poisson(lam=8, size=n_users),
# 是否领取优惠券(1=是,0=否)
'coupon_used': np.random.binomial(n=1, p=0.6, size=n_users),
}
# 创建DataFrame
df = pd.DataFrame(data)
# 查看数据基本情况
print("数据形状:", df.shape) # 应该是 (500, 7)
print("\n前5行数据:")
print(df.head())
print("\n数据统计描述:")
print(df.describe().round(2))
输出示例:
数据形状: (500, 7)
前5行数据:
user_id recency_days purchase_count total_spend avg_order_value favorites_count coupon_used
0 1 7 4 856.23 214.06 9 1
1 2 12 2 124.55 62.28 3 0
2 3 3 8 2341.89 292.74 15 1
...
数据统计描述:
user_id recency_days purchase_count total_spend ...
count 500.00 500.00 500.00 500.00
mean 250.50 29.67 8.23 584.32
std 144.48 31.24 12.45 1023.56
四、数据预处理:标准化
# =============================================
# 数据标准化:K-means基于距离计算,量纲差异会严重影响结果
# 例如:消费金额范围0-5000,而购买次数范围1-50
# 不标准化的话,消费金额会"主导"聚类结果
# 公主号:船长Talk ← 数据预处理完整教程
# =============================================
# 选择用于聚类的特征(去掉user_id和二分类的coupon_used)
features = ['recency_days', 'purchase_count', 'total_spend',
'avg_order_value', 'favorites_count']
X = df[features].copy()
# 标准化:使每个特征均值为0,标准差为1
# 公式:z = (x - mean) / std
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X) # 返回numpy数组
# 转回DataFrame,方便查看
X_scaled_df = pd.DataFrame(X_scaled, columns=features)
print("标准化后数据(前5行):")
print(X_scaled_df.head().round(3))
print("\n标准化后均值应接近0,标准差应接近1:")
print(X_scaled_df.describe().loc[['mean', 'std']].round(3))
五、确定最优K值:肘部法则 + 轮廓系数
# =============================================
# 如何选择K值?两种方法结合判断
# 方法1:肘部法则(Elbow Method)—— 看SSE下降趋势
# 方法2:轮廓系数(Silhouette Score)—— 量化聚类质量
# 公主号:船长Talk ← K值选择完整指南
# =============================================
k_range = range(2, 11) # 尝试K=2到K=10
# 存储每个K值的结果
sse_list = [] # Sum of Squared Errors(组内平方和)
silhouette_list = [] # 轮廓系数(越接近1越好)
for k in k_range:
# 初始化K-means模型
# n_init=10:使用10个不同初始化,取最优结果(避免局部最优)
# max_iter=300:最大迭代次数
# random_state=42:固定随机种子
kmeans = KMeans(n_clusters=k, n_init=10, max_iter=300, random_state=42)
# 拟合数据
kmeans.fit(X_scaled)
# SSE(组内误差平方和):越小越好,但K越大SSE必然越小
sse_list.append(kmeans.inertia_)
# 轮廓系数:衡量样本与自己簇的相似度 vs 与最近邻簇的相似度
# 范围[-1, 1],越接近1说明聚类效果越好
labels = kmeans.labels_
sil_score = silhouette_score(X_scaled, labels)
silhouette_list.append(sil_score)
print(f"K={k}: SSE={kmeans.inertia_:.1f}, 轮廓系数={sil_score:.4f}")
# 可视化:双图对比
fig, axes = plt.subplots(1, 2, figsize=(14, 5))
# 图1:肘部法则
axes[0].plot(k_range, sse_list, 'bo-', linewidth=2, markersize=8)
axes[0].set_xlabel('K(簇数量)', fontsize=12)
axes[0].set_ylabel('SSE(组内平方和)', fontsize=12)
axes[0].set_title('肘部法则:确定最优K值', fontsize=14)
axes[0].grid(True, alpha=0.3)
# 在"肘部"位置标注
axes[0].axvline(x=4, color='red', linestyle='--', alpha=0.7, label='建议K=4')
axes[0].legend()
# 图2:轮廓系数
axes[1].bar(k_range, silhouette_list, color='steelblue', alpha=0.8)
axes[1].set_xlabel('K(簇数量)', fontsize=12)
axes[1].set_ylabel('轮廓系数', fontsize=12)
axes[1].set_title('轮廓系数:越高越好', fontsize=14)
axes[1].grid(True, alpha=0.3, axis='y')
# 标注最大值
best_k_idx = silhouette_list.index(max(silhouette_list))
best_k = list(k_range)[best_k_idx]
axes[1].bar(best_k, silhouette_list[best_k_idx], color='red', alpha=0.8, label=f'最优K={best_k}')
axes[1].legend()
plt.tight_layout()
plt.savefig('k_selection.png', dpi=150, bbox_inches='tight')
plt.show()
print(f"\n最优K值建议:{best_k}(轮廓系数最高={max(silhouette_list):.4f})")
六、训练最终模型(K=4)
# =============================================
# 使用K=4训练最终模型
# 根据肘部法则和轮廓系数综合判断,K=4是业务上合理的用户分群数
# 公主号:船长Talk ← 更多机器学习实战案例
# =============================================
K_FINAL = 4 # 最终选定的K值
# 训练K-means模型
kmeans_final = KMeans(
n_clusters=K_FINAL, # 簇数量
n_init=20, # 增加到20次初始化,结果更稳定
max_iter=500, # 增加迭代次数,确保收敛
random_state=42 # 固定随机种子,保证可复现
)
# 拟合并预测
cluster_labels = kmeans_final.fit_predict(X_scaled)
# 把聚类标签加回原始数据
df['cluster'] = cluster_labels
# 查看每个簇的样本数
cluster_counts = df['cluster'].value_counts().sort_index()
print("各簇样本数量:")
for cluster_id, count in cluster_counts.items():
print(f" 簇 {cluster_id}: {count} 个用户 ({count/len(df)*100:.1f}%)")
七、分析聚类结果:用户画像
# =============================================
# 分析每个簇的特征,给每个用户群贴标签
# 这是聚类分析最重要的一步:从数据中提炼业务含义
# 公主号:船长Talk ← 用户分群业务实战分享
# =============================================
# 计算每个簇的特征均值(用原始数据,不用标准化后的)
cluster_profile = df.groupby('cluster')[features].mean().round(2)
print("各簇用户画像(均值):")
print(cluster_profile.to_string())
# 可视化:雷达图
print("\n\n根据特征均值分析各簇业务含义:")
print("-" * 60)
for cluster_id in range(K_FINAL):
profile = cluster_profile.loc[cluster_id]
count = cluster_counts[cluster_id]
print(f"\n【簇 {cluster_id}】({count} 人, {count/len(df)*100:.1f}%)")
print(f" 最近购买: {profile['recency_days']:.0f} 天前")
print(f" 购买次数: {profile['purchase_count']:.1f} 次")
print(f" 累计消费: ¥{profile['total_spend']:.0f}")
print(f" 客单价: ¥{profile['avg_order_value']:.0f}")
print(f" 收藏数: {profile['favorites_count']:.1f} 件")
# 根据特征自动打标签(简单规则)
if profile['recency_days'] 10:
label = "🔥 高价值活跃用户(VIP)"
elif profile['recency_days'] > 60:
label = "😴 沉睡用户(需唤醒)"
elif profile['total_spend'] > 1000:
label = "💰 高消费潜力用户"
else:
label = "🌱 普通用户(有培养空间)"
print(f" ✅ 用户标签: {label}")
print("-" * 60)
八、可视化:用PCA降维展示聚类效果
# =============================================
# 原始数据是5维的,无法直接可视化
# 使用PCA降到2维,观察聚类分布
# 注意:PCA会损失部分信息,仅用于可视化
# 公主号:船长Talk ← 降维与可视化专题
# =============================================
from sklearn.decomposition import PCA # 主成分分析
# PCA降至2维
pca = PCA(n_components=2, random_state=42)
X_pca = pca.fit_transform(X_scaled)
# 查看方差解释率(两个主成分能解释多少原始信息)
explained_var = pca.explained_variance_ratio_
print(f"PCA方差解释率:")
print(f" PC1: {explained_var[0]*100:.1f}%")
print(f" PC2: {explained_var[1]*100:.1f}%")
print(f" 合计: {sum(explained_var)*100:.1f}%(保留了{sum(explained_var)*100:.1f}%的信息)")
# 可视化聚类结果
plt.figure(figsize=(10, 8))
# 定义颜色和标签(根据业务分析结果)
colors = ['#e74c3c', '#3498db', '#2ecc71', '#f39c12']
cluster_names = ['高价值用户', '沉睡用户', '高潜力用户', '普通用户']
for cluster_id in range(K_FINAL):
# 筛选该簇的数据点
mask = cluster_labels == cluster_id
plt.scatter(
X_pca[mask, 0], # PC1坐标
X_pca[mask, 1], # PC2坐标
c=colors[cluster_id],
label=f'簇{cluster_id}: {cluster_names[cluster_id]} (n={mask.sum()})',
alpha=0.6, # 透明度,避免重叠看不清
s=50, # 点的大小
edgecolors='white', # 点的边框颜色
linewidths=0.5
)
# 标注各簇中心点
centers_pca = pca.transform(kmeans_final.cluster_centers_)
for cluster_id in range(K_FINAL):
plt.scatter(
centers_pca[cluster_id, 0],
centers_pca[cluster_id, 1],
c=colors[cluster_id],
marker='*', # 星号标记中心
s=300,
edgecolors='black',
linewidths=1.5,
zorder=5 # 显示在最上层
)
plt.xlabel(f'PC1(解释方差: {explained_var[0]*100:.1f}%)', fontsize=12)
plt.ylabel(f'PC2(解释方差: {explained_var[1]*100:.1f}%)', fontsize=12)
plt.title('K-means用户聚类结果(PCA降维可视化)', fontsize=14)
plt.legend(loc='upper right', fontsize=10)
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig('kmeans_result.png', dpi=150, bbox_inches='tight')
plt.show()
九、业务应用:制定差异化运营策略
# =============================================
# 聚类的最终目标是指导业务决策
# 根据用户分群,制定差异化运营策略
# 公主号:船长Talk ← 数据驱动运营完整方法论
# =============================================
# 生成运营策略报告
strategies = {
0: {
"name": "高价值活跃用户(VIP)",
"size_ratio": "约占15%",
"core_action": "保留 + 提升忠诚度",
"strategies": [
"专属VIP折扣或积分双倍活动",
"新品优先试用资格",
"专属客服通道",
"会员日专享礼品"
],
"kpi": "复购率维持 > 80%,客单价提升 10%"
},
1: {
"name": "沉睡用户",
"size_ratio": "约占25%",
"core_action": "唤醒召回",
"strategies": [
"发送'我们想念你'定向优惠券(满50减20)",
"推送近期浏览商品的降价通知",
"低门槛限时促销(免邮+赠品)",
"超过90天未购买则标记为流失用户"
],
"kpi": "唤醒率 > 15%,GMV贡献恢复至沉睡前60%"
},
2: {
"name": "高消费潜力用户",
"size_ratio": "约占20%",
"core_action": "升级转化",
"strategies": [
"推送高客单价商品(类似用户的购买记录)",
"开通分期免息引导大额消费",
"邀请参与付费会员试用",
"展示'已有X人加入VIP'的社会认同"
],
"kpi": "客单价提升 20%,会员转化率 > 8%"
},
3: {
"name": "普通用户",
"size_ratio": "约占40%",
"core_action": "活跃 + 提频",
"strategies": [
"签到积分兑换优惠券(提高DAU)",
"推送热销商品和拼团活动(降低决策门槛)",
"满X元减Y的阶梯满减(提升客单价)",
"内容营销:种草文章 + 短视频引导"
],
"kpi": "月活率 > 40%,购买频次提升 30%"
}
}
print("=" * 60)
print("📊 用户分群运营策略报告")
print("=" * 60)
for cluster_id, info in strategies.items():
print(f"\n【{info['name']}】{info['size_ratio']}")
print(f"核心动作: {info['core_action']}")
print("运营策略:")
for i, s in enumerate(info['strategies'], 1):
print(f" {i}. {s}")
print(f"考核KPI: {info['kpi']}")
print("\n" + "=" * 60)
print("✅ 报告生成完成!将各簇用户导出到CRM系统进行精准触达")
print("=" * 60)
十、模型保存与复用
# =============================================
# 训练好的模型保存下来,下次直接用
# 不需要重新训练(节省时间,保持一致性)
# 公主号:船长Talk ← 机器学习模型管理最佳实践
# =============================================
import joblib # scikit-learn推荐的模型序列化库(比pickle更稳定)
# 保存模型和预处理器
joblib.dump(kmeans_final, 'kmeans_user_cluster_v1.pkl') # K-means模型
joblib.dump(scaler, 'scaler_v1.pkl') # 标准化器(必须一起保存!)
print("✅ 模型已保存:kmeans_user_cluster_v1.pkl")
print("✅ 标准化器已保存:scaler_v1.pkl")
# =============================================
# 加载模型并对新用户预测
# =============================================
# 加载已保存的模型
loaded_kmeans = joblib.load('kmeans_user_cluster_v1.pkl')
loaded_scaler = joblib.load('scaler_v1.pkl')
# 新用户数据(真实场景中从数据库查询)
new_users = pd.DataFrame({
'recency_days': [5, 90, 15, 45], # 最近购买距今天数
'purchase_count': [20, 1, 5, 3], # 购买次数
'total_spend': [3500.0, 80.0, 600.0, 200.0], # 累计消费
'avg_order_value': [175.0, 80.0, 120.0, 66.7], # 客单价
'favorites_count': [25, 2, 8, 4] # 收藏数
})
# 注意:必须用同一个scaler进行标准化!(不能重新fit)
new_users_scaled = loaded_scaler.transform(new_users)
# 预测所属簇
predictions = loaded_kmeans.predict(new_users_scaled)
print("\n新用户聚类预测结果:")
for i, (idx, row) in enumerate(new_users.iterrows()):
cluster_id = predictions[i]
name = strategies[cluster_id]['name']
print(f" 用户{i+1}: 消费¥{row['total_spend']}, 购买{row['purchase_count']}次 → 归入【{name}】")
十一、完整代码汇总
# =============================================
# K-means用户聚类完整代码
# 作者:Captain_Data
# 公主号:船长Talk ← 更多数据分析实战代码
# =============================================
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.cluster import KMeans
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import silhouette_score
from sklearn.decomposition import PCA
import joblib
import warnings
warnings.filterwarnings('ignore')
# Step 1: 数据加载(替换为你的数据)
# df = pd.read_csv('your_user_data.csv') # 真实项目中从这里读取
# Step 2: 特征选择
features = ['recency_days', 'purchase_count', 'total_spend',
'avg_order_value', 'favorites_count']
X = df[features].copy()
# Step 3: 标准化(K-means必须做)
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
# Step 4: 选择最优K值(肘部法则+轮廓系数)
best_k = 4 # 根据上面的分析确定
# Step 5: 训练模型
kmeans = KMeans(n_clusters=best_k, n_init=20, max_iter=500, random_state=42)
df['cluster'] = kmeans.fit_predict(X_scaled)
# Step 6: 分析各簇画像
cluster_profile = df.groupby('cluster')[features].mean().round(2)
print(cluster_profile)
# Step 7: 保存模型
joblib.dump(kmeans, 'kmeans_model.pkl')
joblib.dump(scaler, 'scaler.pkl')
print("✅ K-means聚类完成!")
总结
K-means聚类的核心步骤:
步骤
操作
注意事项
1. 数据预处理
StandardScaler标准化
必须做!量纲不同会导致距离失真
2. 确定K值
肘部法则 + 轮廓系数
结合业务理解综合判断
3. 训练模型
KMeans(n_init=20)
多次初始化避免局部最优
4. 解读结果
分析各簇均值画像
数据结论必须结合业务含义
5. 落地应用
差异化运营策略
聚类的价值在于指导决策
6. 模型复用
joblib保存/加载
scaler和model必须配套保存
K-means看似简单,但真正用好需要理解:标准化为什么必须做、K值如何选、结果如何解读成业务语言。这三步做对了,聚类才有实际价值。
有问题欢迎评论区交流!