K-Means聚类算法完整指南:从原理到实战

0 阅读12分钟

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值如何选、结果如何解读成业务语言。这三步做对了,聚类才有实际价值。

有问题欢迎评论区交流!