欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Python聚类模型原理及应用--批发商客户分群

程序员文章站 2022-05-29 20:18:35
前言 在前面介绍的线性回归, 岭回归, Lasso回归, 逻辑回归均是监督学习, 下面将要介绍一种无监督学习—“聚类" 目录 1. 划分聚类 2. k-means算法 3. 优缺点及注意事项 4. 实际案例应用 正文 “物以类聚,人以群分”, 所谓聚类就是将相似的元素分到一"类"(有时也被称为"簇" ......

前言

在前面介绍的线性回归, 岭回归, lasso回归, 逻辑回归均是监督学习, 下面将要介绍一种无监督学习—“聚类"

目录

 

 

 

正文

 “物以类聚,人以群分”, 所谓聚类就是将相似的元素分到一"类"(有时也被称为"簇"或"集合"), 簇内元素相似程度高, 簇间元素相似程度低. 常用的聚类方法有划分聚类, 层次聚类, 密度聚类, 网格聚类, 模型聚类等. 我们这里重点介绍划分聚类.

1. 划分聚类

划分聚类, 就是给定一个样本量为n的数据集, 将其划分为k个簇(k<n), 每一个簇中至少包含一个样本点.

大部分的划分方法是基于距离的, 即簇内距离最小化, 簇间距离最大化. 常用的距离计算方法有: 曼哈顿距离和欧几里得距离. 坐标点(x1, y1)到坐标点(x2, y2)的曼哈顿距离和欧几里得距离分别表示为:

Python聚类模型原理及应用--批发商客户分群

为了达到全局最优解, 传统的划分法可能需要穷举所有可能的划分点, 这计算量是相当大的. 而在实际应用中, 通常会通过计算到均值或中心点的距离进行划分来逼近局部最优, 把计算到均值和到中心点距离的算法分别称为k-means算法和k-medoids算法, 在这里只介绍k-means算法.

2. k-means算法

k-means算法有时也叫快速聚类算法, 其大致流程为:

第一步: 随机选取k个点, 作为初始的k个聚类中心, 有时也叫质心.

第二步: 计算每个样本点到k个聚类中心的距离, 并将其分给距离最短的簇, 如果k个簇中均至少有一个样本点, 那么我们就说将所有样本点分成了k个簇.

第三步: 计算k个簇中所有样本点的均值, 将这k个均值作为k个新的聚类中心.

第四步: 重复第二步和第三步, 直到聚类中心不再改变时停止算法, 输出聚类结果.

显然, 初始聚类中心的选择对迭代时间和聚类结果产生的影响较大, 选不好的话很有可能严重偏离最优聚类. 在实际应用中, 通常选取多个不同的k值以及初始聚类中心, 选取其中表现最好的作为最终的初始聚类中心. 怎么算表现最好呢? 能满足业务需求的, 且簇内距离最小的.

簇内距离可以簇内离差平方和表示: 

Python聚类模型原理及应用--批发商客户分群

其中, k表示k个簇, nj表示第j个簇中的样本个数, xi表示样本, uj表示第j个簇的质心, k-means算法中质心可以表示为:

Python聚类模型原理及应用--批发商客户分群

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种产品上的类别上的消费支出.

Python聚类模型原理及应用--批发商客户分群

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, 呈现右偏分布

Python聚类模型原理及应用--批发商客户分群

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)

 Python聚类模型原理及应用--批发商客户分群

 可以看到以上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

 Python聚类模型原理及应用--批发商客户分群

在剔除一次异常值之后, 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

Python聚类模型原理及应用--批发商客户分群

可以看到现在的数据集中已经不存在异常了, 但是样本容量也从440大幅度下降为97, 因此这里不建议彻底删除.

4. 可视化分析

这里直接采用seaborn的pairplot方法

import seaborn as sns
sns.pairplot(del_df)

变量之间存在不同程度的相关关系, 以及在剔除异常值之后连续型变量依然表现为高度偏态分布.

Python聚类模型原理及应用--批发商客户分群

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()

 Python聚类模型原理及应用--批发商客户分群

根据肘部法则, 选择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则是一般客户.

Python聚类模型原理及应用--批发商客户分群

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(重要客户)在图像上则较稀疏, 数量较少.

Python聚类模型原理及应用--批发商客户分群

参考资料:

网易云课堂《吴恩达机器学习》

《数据分析与挖掘实战》

 

声明: 本文仅用于学习交流