Python聚类模型原理及应用--批发商客户分群
2019-01-08 08:24:25来源:博客园 阅读 ()
前言
在前面介绍的线性回归, 岭回归, Lasso回归, 逻辑回归均是监督学习, 下面将要介绍一种无监督学习—“聚类"
目录
1. 划分聚类
2. k-means算法
3. 优缺点及注意事项
4. 实际案例应用
正文
“物以类聚,人以群分”, 所谓聚类就是将相似的元素分到一"类"(有时也被称为"簇"或"集合"), 簇内元素相似程度高, 簇间元素相似程度低. 常用的聚类方法有划分聚类, 层次聚类, 密度聚类, 网格聚类, 模型聚类等. 我们这里重点介绍划分聚类.
1. 划分聚类
划分聚类, 就是给定一个样本量为N的数据集, 将其划分为K个簇(K<N), 每一个簇中至少包含一个样本点.
大部分的划分方法是基于距离的, 即簇内距离最小化, 簇间距离最大化. 常用的距离计算方法有: 曼哈顿距离和欧几里得距离. 坐标点(x1, y1)到坐标点(x2, y2)的曼哈顿距离和欧几里得距离分别表示为:
为了达到全局最优解, 传统的划分法可能需要穷举所有可能的划分点, 这计算量是相当大的. 而在实际应用中, 通常会通过计算到均值或中心点的距离进行划分来逼近局部最优, 把计算到均值和到中心点距离的算法分别称为K-MEANS算法和K-MEDOIDS算法, 在这里只介绍K-MEANS算法.
2. K-MEANS算法
K-MEANS算法有时也叫快速聚类算法, 其大致流程为:
第一步: 随机选取K个点, 作为初始的K个聚类中心, 有时也叫质心.
第二步: 计算每个样本点到K个聚类中心的距离, 并将其分给距离最短的簇, 如果k个簇中均至少有一个样本点, 那么我们就说将所有样本点分成了K个簇.
第三步: 计算K个簇中所有样本点的均值, 将这K个均值作为K个新的聚类中心.
第四步: 重复第二步和第三步, 直到聚类中心不再改变时停止算法, 输出聚类结果.
显然, 初始聚类中心的选择对迭代时间和聚类结果产生的影响较大, 选不好的话很有可能严重偏离最优聚类. 在实际应用中, 通常选取多个不同的K值以及初始聚类中心, 选取其中表现最好的作为最终的初始聚类中心. 怎么算表现最好呢? 能满足业务需求的, 且簇内距离最小的.
簇内距离可以簇内离差平方和表示:
其中, K表示K个簇, nj表示第j个簇中的样本个数, xi表示样本, uj表示第j个簇的质心, K-means算法中质心可以表示为:
3. 优缺点及注意事项
优点:
1. 原理简单, 计算速度快
2. 聚类效果较理想.
缺点:
1. K值以及初始质心对结果影响较大, 且不好把握.
2. 在大数据集上收敛较慢.
3. 由于目标函数(簇内离差平方和最小)是非凸函数, 因此通过迭代只能保证局部最优.
4. 对于离群点较敏感, 这是由于其是基于均值计算的, 而均值易受离群点的影响.
5. 由于其是基于距离进行计算的, 因此通常只能发现"类圆形"的聚类.
注意事项:
1. 由于其是基于距离进行计算的, 因此通常需要对连续型数据进行标准化处理来缩小数据间的差异.(对于离散型, 则需要进行one-hot编码)
2. 如果采用欧几里得距离进行计算的话, 单位的平方和的平方根是没有意义的, 因此通常需要进行无量纲化处理
4. 实际案例应用
1. 数据来源及背景
数据来源: http://archive.ics.uci.edu/ml/machine-learning-databases/00292/, 它是某批发经销商的客户数据, 其中包含440个样本以及8个特征,
下面将通过划分聚类中的k-means算法对这些客户进行细分.
2. 数据概览
1. 前2行和后2行
import pandas as pd df = pd.read_csv(r'D:\aPython\Data\DataVisualization\Wholesale customers data.csv') pd.set_option('display.max_rows', 4) df
8个字段分别表示批发商客户: 渠道, 地区, 以及每年分别在新鲜产品, 奶制品, 食品杂货, 冷冻产品, 洗涤剂和纸制品, 熟食这6种产品上的类别上的消费支出.
2. 查看数据整体信息
df.info()
<class 'pandas.core.frame.DataFrame'> RangeIndex: 440 entries, 0 to 439 Data columns (total 8 columns): Channel 440 non-null int64 Region 440 non-null int64 Fresh 440 non-null int64 Milk 440 non-null int64 Grocery 440 non-null int64 Frozen 440 non-null int64 Detergents_Paper 440 non-null int64 Delicassen 440 non-null int64 dtypes: int64(8) memory usage: 27.6 KB
没有缺失值, 且均为整数型.
3. 描述性统计
df.describe()
有两种渠道来源, 和三种地区, 各自分别占多少还需要进一步探索.
新鲜产品每年支出: 范围3~112151, 中位数为8504, 均值为12000, 呈现右偏分布
奶制品每年支出: 范围55~73498, 中位数为3627, 均值为5796, 呈现右偏分布
食品杂货每年支出: 范围3~92780, 中位数为4756, 均值为7951, 呈现右偏分布
冷冻产品每年支出: 范围25~60869, 中位数为1526, 均值为3072, 呈现右偏分布
洗涤剂和纸制品每年支出: 范围3~40827, 中位数为817, 均值为2881, 呈现右偏分布
熟食每年支出: 范围3~47943, 中位数为965, 均值为1525, 呈现右偏分布
4. 偏态和峰态
for i in range(2, 8): name = df.columns[i] print(name) print(' 偏态系数为 {0}, 峰态系数为 {1}'.format(df[name].skew(), df[name].kurt()))
Fresh 偏态系数为 2.561322751927935, 峰态系数为 11.536408493056006 Milk 偏态系数为 4.053754849210881, 峰态系数为 24.669397750673077 Grocery 偏态系数为 3.5874286903915453, 峰态系数为 20.914670390919653 Frozen 偏态系数为 5.9079856924559575, 峰态系数为 54.68928069737255 Detergents_Paper 偏态系数为 3.6318506306913645, 峰态系数为 19.009464335418212 Delicassen 偏态系数为 11.151586478906117, 峰态系数为 170.69493933454066
可以看到均表现为尖峰, 高度偏态分布.
3. 数据预处理
没有缺失值, 因此不用缺失值处理
1. 异常值
在处理异常值之前, 先来通过箱线图看看异常值.
import seaborn as sns import matplotlib.pyplot as plt def get_boxplot(data, start, end): fig, ax = plt.subplots(1, end-start, figsize=(24, 4)) for i in range(start, end): sns.boxplot(y=data[data.columns[i]], data=data, ax=ax[i-start]) get_boxplot(df, 2, 8)
可以看到以上6个连续型变量均有不同程度的异常值, 由于k-means算法对异常值较敏感, 因此选择剔除它
def drop_outlier(data, start, end): for i in range(start, end): field = data.columns[i] Q1 = np.quantile(data[field], 0.25) Q3 = np.quantile(data[field], 0.75) deta = (Q3 - Q1) * 1.5 data = data[(data[field] >= Q1 - deta) & (data[field] <= Q3 + deta)] return data del_df = drop_outlier(df, 2, 8) print("原有样本容量:{0}, 剔除后样本容量:{1}".format(df.shape[0], del_df.shape[0])) get_boxplot(del_df, 2, 8)
原有样本容量:440, 剔除后样本容量:318
在剔除一次异常值之后, 6个连续变量的波动幅度也都都大致接近, 你可能会问为什么还有异常值存在? 现在的异常值是相对于新数据集产生的, 而我们把原数据集中的异常值已经剔除了, 通常来说, 对于异常值只需剔除一次即可, 如果彻底剔除的话, 样本容量可能会有大幅度的变化, 比如:
df_new = df.copy() #直到第10次的时候图像上才没有出现异常值 for i in range(10): df_new = drop_outlier(df_new, 2, 8) print("原有样本容量:{0}, 彻底剔除后样本容量:{1}".format(df.shape[0], df_new.shape[0])) get_boxplot(df_new, 2, 8)
原有样本容量:440, 彻底剔除后样本容量:97
可以看到现在的数据集中已经不存在异常了, 但是样本容量也从440大幅度下降为97, 因此这里不建议彻底删除.
4. 可视化分析
这里直接采用seaborn的pairplot方法
import seaborn as sns sns.pairplot(del_df)
变量之间存在不同程度的相关关系, 以及在剔除异常值之后连续型变量依然表现为高度偏态分布.
5. 特征工程
1. 离散型变量
将离散型变量处理成哑变量.
del_df['Channel'] = del_df.Channel.astype(str) del_df['Region'] = del_df.Region.astype(str) del_df = pd.get_dummies(del_df)
2. 连续型变量
由于连续型变量的数值范围有大有小, 为消除其对聚类结果的影响, 这里采用z-score进行归一化处理
for i in range(6): field = del_df.columns[i] del_df[field] = del_df[field].apply(lambda x: (x - del_df[field].mean()) / del_df[field].mean())
6. 聚类模型
1. 构建K=2的聚类模型
from sklearn.cluster import KMeans km = KMeans(n_clusters=2, random_state=10) km.fit(del_df) print(km.cluster_centers_) print(km.labels_)
[[ 0.08057098 -0.36005276 -0.42021772 0.11899282 -0.66737726 -0.10885484 0.97333333 0.02666667 0.2 0.10222222 0.69777778] [-0.19492979 0.8710954 1.01665578 -0.28788585 1.61462241 0.26335849 0.13978495 0.86021505 0.10752688 0.07526882 0.8172043 ]] [1 1 0 1 1 1 0 1 1 0 1 1 1 0 1 1 0 0 0 1 0 0 0 0 0 0 0 1 1 1 0 1 1 1 0 0 1 0 0 1 0 1 1 1 1 0 0 1 0 0 1 0 0 0 0 1 1 0 1 0 0 1 0 1 0 0 0 1 1 1 0 0 1 1 0 1 0 0 0 0 0 0 0 0 0 0 1 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 1 1 1 0 0 1 1 0 0 0 1 0 0 1 0 0 0 0 1 0 0 0 1 1 1 0 0 1 0 1 0 0 0 0 0 1 0 0 0 1 1 1 0 0 0 1 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 0 0 0 0 0 0 0 0 0 1 0 1 0 0 0 0 0 1 0 0 0 1 1 0 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 1 1 1 0 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 1 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 1 1 0 1 0 1 1 1 1 0 1 1 0 0 1 1 0 0 0 0 0 0 0 0]
将客户分为2类到底合不合适呢? 通过迭代的方式来选择.
2. 迭代选择合适的k值
import matplotlib.pyplot as plt K = range(1, 10) sse = [] for k in K: km = KMeans(n_clusters=k, random_state=10) km.fit(del_df) sse.append(km.inertia_) plt.figure(figsize=(8, 6)) plt.plot(K, sse, '-o', alpha=0.7) plt.xlabel("K") plt.ylabel("SSE") plt.show()
根据肘部法则, 选择K=2, 也就是说将客户分成两群.
3. 客户分群
from pandas.plotting import parallel_coordinates #训练模型 km = KMeans(n_clusters=2, random_state=10) km.fit(del_df) centers = km.cluster_centers_ labels = km.labels_ customer = pd.DataFrame({'0': centers[0], "1": centers[1]}).T customer.columns = del_df.keys() df_median = pd.DataFrame({'2': del_df.median()}).T customer = pd.concat([customer, df_median]) customer["category"] = ["customer_1", "customer_2", 'median'] #绘制图像 plt.figure(figsize=(12, 6)) parallel_coordinates(customer, "category", colormap='flag'') plt.xticks(rotation = 15) plt.show()
从6种产品每年消费支出来看, 客户群1在冷冻产品上最高, 在洗涤剂和纸制品上最低, 而客户群2则在冷冻产品上最低, 在洗涤剂和纸制品上最高, 且客户群2在6种产品的消费支出均高于中位数水平, 因此客户群2为重要客户, 客户群1则是一般客户.
4. 最终分群结果
#将聚类后的标签加入数据集 del_df['category'] = labels del_df['category'] = np.where(del_df.category == 0, 'customer_1', 'customer_2') customer = pd.DataFrame({'0': centers[0], "1": centers[1]}).T customer["category"] = ['customer_1_center', "customer_2_center"] customer.columns = del_df.keys() del_df = pd.concat([del_df, customer]) #对6类产品每年消费水平进行绘制图像 df_new = del_df[['Fresh', 'Milk', 'Grocery', 'Frozen', 'Detergents_Paper', 'Delicassen', 'category']] plt.figure(figsize=(18, 6)) parallel_coordinates(df_new, "category", colormap='cool') plt.xticks(rotation = 15) plt.show()
通过下图可以看到, 最终的聚类效果较理想, 其中客户群1(一般客户)在图像上表现较为密集, 代表其数量多, 而客户群2(重要客户)在图像上则较稀疏, 数量较少.
参考资料:
网易云课堂《吴恩达机器学习》
《数据分析与挖掘实战》
声明: 本文仅用于学习交流
标签:
版权申明:本站文章部分自网络,如有侵权,请联系:west999com@outlook.com
特别注意:本站所有转载文章言论不代表本站观点,本站所提供的摄影照片,插画,设计作品,如需使用,请与原作者联系,版权归原作者所有
上一篇:Python|一文简单看懂 深度&广度 优先算法
下一篇:flask二
- python3基础之“术语表(2)” 2019-08-13
- python3 之 字符串编码小结(Unicode、utf-8、gbk、gb2312等 2019-08-13
- Python3安装impala 2019-08-13
- 小白如何入门 Python 爬虫? 2019-08-13
- python_字符串方法 2019-08-13
IDC资讯: 主机资讯 注册资讯 托管资讯 vps资讯 网站建设
网站运营: 建站经验 策划盈利 搜索优化 网站推广 免费资源
网络编程: Asp.Net编程 Asp编程 Php编程 Xml编程 Access Mssql Mysql 其它
服务器技术: Web服务器 Ftp服务器 Mail服务器 Dns服务器 安全防护
软件技巧: 其它软件 Word Excel Powerpoint Ghost Vista QQ空间 QQ FlashGet 迅雷
网页制作: FrontPages Dreamweaver Javascript css photoshop fireworks Flash