Python用K-means聚类算法进行客户分群的实现


Posted in Python onAugust 23, 2020

一、背景

1.项目描述

  • 你拥有一个超市(Supermarket Mall)。通过会员卡,你用有一些关于你的客户的基本数据,如客户ID,年龄,性别,年收入和消费分数。
  • 消费分数是根据客户行为和购买数据等定义的参数分配给客户的。
  • 问题陈述:你拥有这个商场。想要了解怎么样的顾客可以很容易地聚集在一起(目标顾客),以便可以给营销团队以灵感并相应地计划策略。

2.数据描述

字段名 描述
CustomerID 客户编号
Gender 性别
Age 年龄
Annual Income (k$) 年收入,单位为千美元
Spending Score (1-100) 消费分数,范围在1~100

二、相关模块

import numpy as np
import pandas as pd
from pandas import plotting
import matplotlib.pyplot as plt
import seaborn as sns
import plotly.graph_objs as go
import plotly.offline as py
from sklearn.cluster import KMeans
import warnings
warnings.filterwarnings('ignore')

三、数据可视化

1.数据读取

io = '.../Mall_Customers.csv'
df = pd.DataFrame(pd.read_csv(io))
# 修改列名
df.rename(columns={'Annual Income (k$)': 'Annual Income', 'Spending Score (1-100)': 'Spending Score'}, inplace=True)
print(df.head())
print(df.describe())
print(df.shape)
print(df.count())
print(df.dtypes)

输出如下。

CustomerID Gender Age Annual Income Spending Score
0 1 Male 19 15 39
1 2 Male 21 15 81
2 3 Female 20 16 6
3 4 Female 23 16 77
4 5 Female 31 17 40
-----------------------------------------------------------------
CustomerID Age Annual Income Spending Score
count 200.000000 200.000000 200.000000 200.000000
mean 100.500000 38.850000 60.560000 50.200000
std 57.879185 13.969007 26.264721 25.823522
min 1.000000 18.000000 15.000000 1.000000
25% 50.750000 28.750000 41.500000 34.750000
50% 100.500000 36.000000 61.500000 50.000000
75% 150.250000 49.000000 78.000000 73.000000
max 200.000000 70.000000 137.000000 99.000000
-----------------------------------------------------------------
(200, 5)
CustomerID 200
Gender 200
Age 200
Annual Income 200
Spending Score 200
dtype: int64
-----------------------------------------------------------------
CustomerID int64
Gender object
Age int64
Annual Income int64
Spending Score int64
dtype: object

2.数据可视化

2.1 平行坐标图

  • 平行坐标图(Parallel coordinates plot)用于多元数据的可视化,将高维数据的各个属性(变量)用一系列相互平行的坐标轴表示, 纵向是属性值,横向是属性类别。
  • 若在某个属性上相同颜色折线较为集中,不同颜色有一定的间距,则说明该属性对于预标签类别判定有较大的帮助。
  • 若某个属性上线条混乱,颜色混杂,则可能该属性对于标签类别判定没有价值。
plotting.parallel_coordinates(df.drop('CustomerID', axis=1), 'Gender')
plt.title('平行坐标图', fontsize=12)
plt.grid(linestyle='-.')
plt.show()

Python用K-means聚类算法进行客户分群的实现

2.2 年龄/年收入/消费分数的分布

这里用了直方图和核密度图。(注:核密度图看的是(x<X)的面积,而不是高度)

sns.set(palette="muted", color_codes=True)  # seaborn样式
# 配置
plt.rcParams['axes.unicode_minus'] = False  # 解决无法显示符号的问题
sns.set(font='SimHei', font_scale=0.8)    # 解决Seaborn中文显示问题
# 绘图
plt.figure(1, figsize=(13, 6))
n = 0
for x in ['Age', 'Annual Income', 'Spending Score']:
  n += 1
  plt.subplot(1, 3, n)
  plt.subplots_adjust(hspace=0.5, wspace=0.5)
  sns.distplot(df[x], bins=16, kde=True)  # kde 密度曲线
  plt.title('{}分布情况'.format(x))
  plt.tight_layout()
plt.show()

如下图。从左到右分别是年龄、年收入和消费能力的分布情况。发现:

  • 年龄方面:[30,36]范围的客户是最多的另外,在[20,21]也不少,但是60岁以上的老年人是最不常来消费的。
  • 年收入方面:大部分的客户集中在[53,83]范围里,在15以下和105以上的很少。
  • 消费分数方面:消费分数在[40,55]的占了大多数,在[70,80]范围的次之。

Python用K-means聚类算法进行客户分群的实现

2.3年龄/年收入/消费分数的柱状图

这里使用的是柱状图,和直方图不同的是:xxx轴上的每一个刻度对应的是一个离散点,而不是一个区间。

plt.figure(1, figsize=(13, 6))
k = 0
for x in ['Age', 'Annual Income', 'Spending Score']:
  k += 1
  plt.subplot(3, 1, k)
  plt.subplots_adjust(hspace=0.5, wspace=0.5)
  sns.countplot(df[x], palette='rainbow', alpha=0.8)
  plt.title('{}分布情况'.format(x))
  plt.tight_layout()
plt.show()

如下图。从上到下分别是年龄、年收入和消费能力的柱状图。发现:

  • 年龄方面:[27,40]范围的客户居多。其中,32岁的客户是商城的常客,55,、56、64、69岁的用户却很少。总的来说,年龄较大的人群较少,年龄较少的人群较多。
  • 年收入方面:年收入在54和78的频数是最多的。其他在各个收入的客户频数看起来相差不太大。
  • 消费分数方面:消费分数在42的客户数是最多的,56次之。有的客户的分数甚至达到了99,而分数为1的客户也存在,没有分数为0的客户。

Python用K-means聚类算法进行客户分群的实现

2.4不同性别用户占比

df_gender_c = df['Gender'].value_counts()
p_lables = ['Female', 'Male']
p_color = ['lightcoral', 'lightskyblue']
p_explode = [0, 0.05]
# 绘图
plt.pie(df_gender_c, labels=p_lables, colors=p_color, explode=p_explode, shadow=True, autopct='%.2f%%')
plt.axis('off')
plt.legend()
plt.show()

如下饼图。女性以56%的份额居于领先地位,而男性则占整体的44%。特别是当男性人口相对高于女性时,这是一个比较大的差距。

Python用K-means聚类算法进行客户分群的实现

2.5 两两特征之间的关系

# df_a_a_s = df.drop(['CustomerID'], axis=1)
sns.pairplot(df, vars=['Age', 'Annual Income', 'Spending Score'], hue='Gender', aspect=1.5, kind='reg')
plt.show()

pairplot主要展现的是属性(变量)两两之间的关系(线性或非线性,有无较为明显的相关关系)。注意,我对男、女性的数据点进行了区分(但是感觉数据在性别上的差异不大呀?)。如下组图所示:

  • 对角线上的图是各个属性的核密度分布图。
  • 非对角线的图是两个不同属性之间的相关图。看得出年收入和消费能力之间有较为明显的相关关系。
  • kind 参数设置为 reg 会为非对角线上的散点图拟合出一条回归直线,更直观地显示变量之间的关系。

Python用K-means聚类算法进行客户分群的实现

2.6 两两特征之间的分布

# 根据分类变量分组绘制一个纵向的增强箱型图
plt.rcParams['axes.unicode_minus'] = False  # 解决无法显示符号的问题
sns.set(font='SimHei', font_scale=0.8)    # 解决Seaborn中文显示问题
sns.boxenplot(df['Gender'], df['Spending Score'], palette='Blues')
# x:设置分组统计字段,y:数据分布统计字段
sns.swarmplot(x=df['Gender'], y=df['Spending Score'], data=df, palette='dark', alpha=0.5, size=6)
plt.title('男女性的消费能力比较', fontsize=12)
plt.show()
  • 如下图使用了增强箱图,可以通过绘制更多的分位数来提供数据分布的信息,适用于大数据。
  • 男性的消费得分集中在[25,70],而女性的消费得分集中在[35,75],一定程度上说明了女性在购物方面表现得比男性好。

Python用K-means聚类算法进行客户分群的实现

# 根据分类变量分组绘制一个纵向的增强箱型图
plt.rcParams['axes.unicode_minus'] = False  # 解决无法显示符号的问题
sns.set(font='SimHei', font_scale=0.8)    # 解决Seaborn中文显示问题
sns.boxenplot(df['Gender'], df['Spending Score'], palette='Blues')
# x:设置分组统计字段,y:数据分布统计字段
sns.swarmplot(x=df['Gender'], y=df['Spending Score'], data=df, palette='dark', alpha=0.5, size=6)
plt.title('男女性的消费能力比较', fontsize=12)
plt.show()

其实,下面这一部分也包含了上面的信息。

  • 年龄方面:男性分布较为均匀,20多岁的比较多;女性的年龄大部分集中在20+~30+这个范围,整体上较为年轻?
  • 收入方面:男性略胜一筹

Python用K-means聚类算法进行客户分群的实现

四、K-means聚类分析

0.手肘法简介

核心指标

误差平方和(sum of the squared errors,SSE)是所有样本的聚类误差反映了聚类效果的好坏,公式如下:

Python用K-means聚类算法进行客户分群的实现

核心思想

  • 随着聚类数k 的增大,样本划分会更加精细,每个簇的聚合程度会逐渐提高,那么SSE会逐渐变小。
  • 当k 小于真实聚类数时,由于k 的增大会大幅增加每个簇的聚合程度,故SSE的下降幅度会很大。
  • 当k到达真实聚类数时,再增加k所得到的聚合程度回报会迅速变小,所以SSE的下降幅度会骤减。然后随着k值的继续增大而趋于平缓,也就是说SSE和k的关系图是一个手肘的形状,而这个肘部对应的k值就是数据的真实聚类数。

1.基于年龄和消费分数的聚类

所需要的数据有‘Age'和‘Spending Score'。

df_a_sc = df[['Age', 'Spending Score']].values
# 存放每次聚类结果的误差平方和
inertia1 = []

使用手肘法确定最合适的kkk值。

for n in range(1, 11):
  # 构造聚类器
  km1 = (KMeans(n_clusters=n,    # 要分成的簇数,int类型,默认值为8
         init='k-means++',  # 初始化质心,k-means++是一种生成初始质心的算法
         n_init=10,      # 设置选择质心种子次数,默认为10次。返回质心最好的一次结果(好是指计算时长短)
         max_iter=300,    # 每次迭代的最大次数
         tol=0.0001,     # 容忍的最小误差,当误差小于tol就会退出迭代
         random_state=111,  # 随机生成器的种子 ,和初始化中心有关
         algorithm='elkan')) # 'full'是传统的K-Means算法,'elkan'是采用elkan K-Means算法
  # 用训练数据拟合聚类器模型
  km1.fit(df_a_sc)
  # 获取聚类标签
  inertia1.append(km1.inertia_)

绘图确定kkk值,这里将kkk确定为4。

plt.figure(1, figsize=(15, 6))
plt.plot(np.arange(1, 11), inertia1, 'o')
plt.plot(np.arange(1, 11), inertia1, '-', alpha=0.7)
plt.title('手肘法图', fontsize=12)
plt.xlabel('聚类数'), plt.ylabel('SSE')
plt.grid(linestyle='-.')
plt.show()

通过如下图,确定kkk=4。

Python用K-means聚类算法进行客户分群的实现

确定kkk=4后。重新构建kkk=4的K-means模型,并且绘制聚类图。

km1_result = (KMeans(n_clusters=4, init='k-means++', n_init=10, max_iter=300,
           tol=0.0001, random_state=111, algorithm='elkan'))
# 先fit()再predict(),一次性得到聚类预测之后的标签
y1_means = km1_result.fit_predict(df_a_sc)
# 绘制结果图
plt.scatter(df_a_sc[y1_means == 0][:, 0], df_a_sc[y1_means == 0][:, 1], s=70, c='blue', label='1', alpha=0.6)
plt.scatter(df_a_sc[y1_means == 1][:, 0], df_a_sc[y1_means == 1][:, 1], s=70, c='orange', label='2', alpha=0.6)
plt.scatter(df_a_sc[y1_means == 2][:, 0], df_a_sc[y1_means == 2][:, 1], s=70, c='pink', label='3', alpha=0.6)
plt.scatter(df_a_sc[y1_means == 3][:, 0], df_a_sc[y1_means == 3][:, 1], s=70, c='purple', label='4', alpha=0.6)
plt.scatter(km1_result.cluster_centers_[:, 0], km1_result.cluster_centers_[:, 1], s=260, c='gold', label='质心')
plt.title('聚类图(K=4)', fontsize=12)
plt.xlabel('年收入(k$)')
plt.ylabel('消费分数(1-100)')
plt.legend()
plt.grid(linestyle='-.')
plt.show()

效果如下,基于年龄和消费能力这两个参数,可以将用户划分成4类。

Python用K-means聚类算法进行客户分群的实现

2.基于年收入和消费分数的聚类

所需要的数据

df_ai_sc = df[['Annual Income', 'Spending Score']].values
# 存放每次聚类结果的误差平方和
inertia2 = []

同理,使用手肘法确定合适的kkk值。

for n in range(1, 11):
  # 构造聚类器
  km2 = (KMeans(n_clusters=n, init='k-means++', n_init=10, max_iter=300, tol=0.0001, random_state=111, algorithm='elkan'))
  # 用训练数据拟合聚类器模型
  km2.fit(df_ai_sc)
  # 获取聚类标签
  inertia2.append(km2.inertia_)
# 绘制手肘图确定K值
plt.figure(1, figsize=(15, 6))
plt.plot(np.arange(1, 11), inertia1, 'o')
plt.plot(np.arange(1, 11), inertia1, '-', alpha=0.7)
plt.title('手肘法图', fontsize=12)
plt.xlabel('聚类数'), plt.ylabel('SSE')
plt.grid(linestyle='-.')
plt.show()

通过如下图,确定kkk=5。

Python用K-means聚类算法进行客户分群的实现

确定kkk=5后。重新构建kkk=5的K-means模型,并且绘制聚类图

km2_result = (KMeans(n_clusters=5, init='k-means++', n_init=10, max_iter=300,
           tol=0.0001, random_state=111, algorithm='elkan'))
# 先fit()再predict(),一次性得到聚类预测之后的标签
y2_means = km2_result.fit_predict(df_ai_sc)
# 绘制结果图
plt.scatter(df_ai_sc[y2_means == 0][:, 0], df_ai_sc[y2_means == 0][:, 1], s=70, c='blue', label='1', alpha=0.6)
plt.scatter(df_ai_sc[y2_means == 1][:, 0], df_ai_sc[y2_means == 1][:, 1], s=70, c='orange', label='2', alpha=0.6)
plt.scatter(df_ai_sc[y2_means == 2][:, 0], df_ai_sc[y2_means == 2][:, 1], s=70, c='pink', label='3', alpha=0.6)
plt.scatter(df_ai_sc[y2_means == 3][:, 0], df_ai_sc[y2_means == 3][:, 1], s=70, c='purple', label='4', alpha=0.6)
plt.scatter(df_ai_sc[y2_means == 4][:, 0], df_ai_sc[y2_means == 4][:, 1], s=70, c='green', label='5', alpha=0.6)
plt.scatter(km2_result.cluster_centers_[:, 0], km2_result.cluster_centers_[:, 1], s=260, c='gold', label='质心')
plt.title('聚类图(K=5)', fontsize=12)
plt.xlabel('年收入(k$)')
plt.ylabel('消费分数(1-100)')
plt.legend()
plt.grid(linestyle='-.')
plt.show()

效果如下,基于年收入和消费能力这两个参数,可以将用户划分成如下5类:

  • 群体1 \Rightarrow⇒目标用户:这类客户年收入高,而且高消费。
  • 群体2 \Rightarrow⇒普通用户:年收入与消费得分中等水平。
  • 群体3 \Rightarrow⇒高消费用户:年收入水平较低,但是却有较强烈的消费意愿,舍得花钱。
  • 群体4 \Rightarrow⇒节俭用户:年收入高但是消费意愿不强烈。群体5 \Rightarrow⇒谨慎用户:年收入和消费意愿都较低。

Python用K-means聚类算法进行客户分群的实现

3.基于年龄、收入和消费分数的聚类所需要的数据

df_a_ai_sc = df[['Age', 'Annual Income', 'Spending Score']].values

聚类,kkk=5。

km3 = KMeans(n_clusters=5, init='k-means++', max_iter=300, n_init=10, random_state=0)
km3.fit(df_a_ai_sc)

绘图。

df['labels'] = km3.labels_
# 绘制3D图
trace1 = go.Scatter3d(
  x=df['Age'],
  y=df['Spending Score'],
  z=df['Annual Income'],
  mode='markers',
   marker=dict(
    color=df['labels'],
    size=10,
    line=dict(
      color=df['labels'],
      width=12
    ),
    opacity=0.8
   )
)
df_3dfid = [trace1]

layout = go.Layout(
  margin=dict(
    l=0,
    r=0,
    b=0,
    t=0
  ),
  scene=dict(
      xaxis=dict(title='年龄'),
      yaxis=dict(title='消费分数(1-100)'),
      zaxis=dict(title='年收入(k$)')
    )
)

fig = go.Figure(data=df_3dfid, layout=layout)
py.offline.plot(fig)

效果如下。

Python用K-means聚类算法进行客户分群的实现

五、小结

  • 主要是为了记录下K-means学习过程,而且之前也参与了一个项目用到了K-means算法。
  • 如何进行特征旋是一个需要考虑的问题,我这里尝试了三种不同的方案。然后,确定k 值是另一个重要的问题。我这个用了“手肘法”,但是可以配合“轮廓系数”综合判断。
  • 还有许多地方不够详细。另外,如果有考虑不严谨的地方,欢迎批评指正!

到此这篇关于Python用K-means聚类算法进行客户分群的实现的文章就介绍到这了,更多相关Python K-means客户分群内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

Python 相关文章推荐
Pyhton中防止SQL注入的方法
Feb 05 Python
简单谈谈Python流程控制语句
Dec 04 Python
python数据类型_字符串常用操作(详解)
May 30 Python
Python模拟用户登录验证
Sep 11 Python
python+pygame简单画板实现代码实例
Dec 13 Python
Python Web程序部署到Ubuntu服务器上的方法
Feb 22 Python
python爬虫之urllib3的使用示例
Jul 09 Python
Python常用爬虫代码总结方便查询
Feb 25 Python
Python实现合并excel表格的方法分析
Apr 13 Python
python与字符编码问题
May 24 Python
python中pivot()函数基础知识点
Jan 03 Python
python编写函数注意事项总结
Mar 29 Python
pandas参数设置的实用小技巧
Aug 23 #Python
Python如何把字典写入到CSV文件的方法示例
Aug 23 #Python
Python tkinter界面实现历史天气查询的示例代码
Aug 23 #Python
套娃式文件夹如何通过Python批量处理
Aug 23 #Python
python进度条显示-tqmd模块的实现示例
Aug 23 #Python
基于python tkinter的点名小程序功能的实例代码
Aug 22 #Python
python+selenium 简易地疫情信息自动打卡签到功能的实现代码
Aug 22 #Python
You might like
自己做矿石收音机
2021/03/02 无线电
我的论坛源代码(三)
2006/10/09 PHP
关于mysql 字段的那个点为是定界符
2007/01/15 PHP
PHP运行出现Notice : Use of undefined constant 的完美解决方案分享
2012/03/05 PHP
PHP抽象类 介绍
2012/06/13 PHP
ThinkPHP基本的增删查改操作实例教程
2014/08/22 PHP
tp5.1 框架路由操作-URL生成实例分析
2020/05/26 PHP
javascript对talbe进行动态添加、删除、验证实现代码
2012/03/29 Javascript
Javascript自定义排序 node运行 实例
2013/06/05 Javascript
关于js中for in的缺陷浅析
2013/12/02 Javascript
js封装可使用的构造函数继承用法分析
2015/01/28 Javascript
jQuery新窗口打开外链接
2016/07/21 Javascript
jQuery实现定位滚动条位置
2016/08/05 Javascript
ExtJS 4.2 Grid组件单元格合并的方法
2016/10/12 Javascript
常用JS图片滚动(无缝、平滑、上下左右滚动)代码大全(推荐)
2016/12/20 Javascript
纯js实现倒计时功能
2017/01/06 Javascript
jQuery选择器_动力节点Java学院整理
2017/07/05 jQuery
js+html5实现手机九宫格密码解锁功能
2018/07/30 Javascript
vue+webpack中配置ESLint
2018/11/07 Javascript
weui中的picker使用js进行动态绑定数据问题
2019/11/06 Javascript
js实现选项卡效果
2020/03/07 Javascript
JS PHP字符串截取函数实现原理解析
2020/08/29 Javascript
python每次处理固定个数的字符的方法总结
2013/01/29 Python
Python是编译运行的验证方法
2015/01/30 Python
Python实现的合并两个有序数组算法示例
2019/03/04 Python
学python需要去培训机构吗
2020/07/01 Python
CSS3教程(1):什么是CSS3
2009/04/02 HTML / CSS
Staples英国官方网站:办公用品一站式采购
2017/10/06 全球购物
澳大利亚UGG工厂直销:Australian Ugg Boots
2017/10/14 全球购物
科颜氏法国官网:Kiehl’s法国
2019/08/20 全球购物
乐观大学生的自我评价
2014/01/10 职场文书
给实习单位的感谢信
2014/02/01 职场文书
文明餐桌活动方案
2014/02/11 职场文书
校长师德表现自我评价
2015/03/05 职场文书
解决persistence.xml配置文件修改存放路径的问题
2022/02/24 Java/Android
css常用字体属性与背景属性介绍
2022/02/28 HTML / CSS