K-Means聚类算法完整指南:从原理到实战
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'] < 15 and profile['purchase_count'] > 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值如何选、结果如何解读成业务语言。这三步做对了,聚类才有实际价值。
有问题欢迎评论区交流!
