发布于 

K-means进行用户分层

RFM作为经典的用户分层模型,做用户分层确实非常合适,但是我看到大部分的文章仅仅只是介绍如何使用RFM做用户分层,却没有介绍最重要的一点,如何划分分界点,这里使用聚类算法对RFM指标进行分类。

源数据格式

uid,R,F,M

一、K-Means算法

K-Means 是一种非监督学习,解决的是聚类问题。 K 代表的是 K 类,Means 代表的是中心,主要计算过程就是通过计算点之间的距离确定K类之间的中心点,确定了中心点之后,也就完成了聚类。

二、K-Means 的工作原理

  1. 选取 K 个点作为初始的类中心点,这些点一般都是从数据集中随机抽取的;

  2. 将每个点分配到最近的类中心点,这样就形成了 K 个类,然后重新计算每个类的中心
    点;

  3. 重复第二步,直到类不发生变化,或者你也可以设置最大迭代次数,这样即使类中心点
    发生变化,但是只要达到最大迭代次数就会结束。

三、案例1:给亚洲球队做聚类

数据源格式如下:

Snipaste_2020-05-05@23_27

针对以上数据源,按照以下步骤进行聚类计算

1、数据规范化

数据变换是数据准备的重要环节,它通过数据平滑、数据聚集、数据概化和规范化等方式将数据转换成适用于数据挖掘的形式。

  1. 数据平滑:去除数据中的噪声,将连续数据离散化。这里可以采用分箱、聚类和回归的
    方式进行数据平滑,我会在后面给你讲解聚类和回归这两个算法;
  2. 数据聚集:对数据进行汇总,在 SQL 中有一些聚集函数可以供我们操作,比如 Max()
    反馈某个字段的数值最大值,Sum() 返回某个字段的数值总和;
  3. 数据概化:将数据由较低的概念抽象成为较高的概念,减少数据复杂度,即用更高的概
    念替代更低的概念。比如说上海、杭州、深圳、北京可以概化为中国。
  4. 数据规范化:使属性数据按比例缩放,这样就将原来的数值映射到一个新的特定区域
    中。常用的方法有最小—最大规范化、Z—score 规范化、按小数定标规范化等,我会
    在后面给你讲到这些方法的使用;
  5. 属性构造:构造出新的属性并添加到属性集中。这里会用到特征工程的知识,因为通过
    属性与属性的连接构造新的属性,其实就是特征工程。比如说,数据表中统计每个人的
    英语、语文和数学成绩,你可以构造一个“总和”这个属性,来作为新属性。这样“总
    和”这个属性就可以用到后续的数据挖掘计算中。

数据规范化的几种方法:

  1. Min-max 规范化
    Min-max 规范化方法是将原始数据变换到 [0,1] 的空间中。用公式表示就是:
    新数值 =(原数值 - 极小值)/(极大值 - 极小值)。
  2. Z-Score 规范化
    假设 A 与 B 的考试成绩都为 80 分,A 的考卷满分是 100 分(及格 60 分),B 的考卷满
    分是 500 分(及格 300 分)。虽然两个人都考了 80 分,但是 A 的 80 分与 B 的 80 分代
    表完全不同的含义。
    那么如何用相同的标准来比较 A 与 B 的成绩呢?Z-Score 就是用来可以解决这一问题的。
    我们定义:新数值 =(原数值 - 均值)/ 标准差。
    假设 A 所在的班级平均分为 80,标准差为 10。B 所在的班级平均分为 400,标准差为
    100。那么 A 的新数值 =(80-80)/10=0,B 的新数值 =(80-400)/100=-3.2。
    那么在 Z-Score 标准下,A 的成绩会比 B 的成绩好。
    我们能看到 Z-Score 的优点是算法简单,不受数据量级影响,结果易于比较。不足在于,
    它需要数据整体的平均值和方差,而且结果没有实际意义,只是用于比较。
  3. 小数定标规范化
    小数定标规范化就是通过移动小数点的位置来进行规范化。小数点移动多少位取决于属性
    A 的取值中的最大绝对值。
    举个例子,比如属性 A 的取值范围是 -999 到 88,那么最大绝对值为 999,小数点就会移
    动 3 位,即新数值 = 原数值 /1000。那么 A 的取值范围就被规范化为 -0.999 到 0.088。
    上面这三种是数值规范化中常用的几种方式。

这里采用Min-max 规范化,使用 新数值 =(原数值 - 极小值)/(极大值 - 极小值) 公式计算:

1
2
3
4
5
from sklearn import preprocessing
min_max_scaler = preprocessing.MinMaxScaler()
minmax_x = min_max_scaler.fit_transform(df)
minmax_df = pd.DataFrame(minmax_x,index=df.index,columns=df.columns) #将0-1标准化之后的numpy数组再转换回dataframe
minmax_df

Snipaste_2020-05-05@23_37

2、确定 K 类及 K 类的中心点

此处确定3个类,K-Means算法随机指派初始中心点,通过后续不断的迭代获取最终的中心点。

这里随机选择中国、日本、韩国为三个类的中心点。

3、计算其余球队到该三个中心点的距离

欧氏距离、曼哈顿距离、切比雪夫距离、余弦距离四种有关距离的计算,欧氏距离是最常用的距离计算方式,这里选用该方式

欧式距离计算

计算过程

1
2
3
4
5
6
7
8
9
10
11
12
13
# 将随机选择的三个初始k点的值取出
array1 = minmax_df.loc['中国'].values
array2 = minmax_df.loc['曰本'].values
array3 = minmax_df.loc['韩国'].values
# 对三列分别计算到array1、2、3的欧式距离
s1 = minmax_df.apply(lambda x: np.sqrt(np.sum(np.square(x - array1 ))) ,axis=1)
s2 = minmax_df.apply(lambda x: np.sqrt(np.sum(np.square(x - array2 ))) ,axis=1)
s3 = minmax_df.apply(lambda x: np.sqrt(np.sum(np.square(x - array3 ))) ,axis=1)
# 合并第一次循环计算的结果
df_cy1 = pd.concat([s1,s2,s3],axis=1,keys=['中国','曰本','韩国'])
# 求每一行中最小值对应的列名,使用idxmin()函数,最大值是idxmax()
df_cy1['划分']=df_cy1.apply(lambda x:x.idxmin(),axis=1)
df_cy1

最后输出

Snipaste_2020-05-07@00_02

4、重新计算这三个类的中心点

最简单的方式就是取平均值,然后根据新的中心点按照距离远近重新分配球队的分类,再根据球队的分类更新中心点的位置。

1
2
3
4
5
6
7
# 重新计算这三个类的中心点,取这些点的平均值
a2 = df_cy1.iloc[:,0:3].loc[df_cy1['划分1']=='a'].values.mean()
b2 = df_cy1.iloc[:,0:3].loc[df_cy1['划分1']=='b'].values.mean()
c2 = df_cy1.iloc[:,0:3].loc[df_cy1['划分1']=='c'].values.mean()
print('a2 =',a2)
print('b2 =',b2)
print('c2 =',c2)

5、重复迭代以上过程

迭代到分类结果不再发生变化,可以得到以下的分类结果:

1
2
3
4
5
6
7
8
9
# 第二次循环,对三列分别计算到array21、22、23的欧式距离
s21 = minmax_df.apply(lambda x: np.sqrt(np.sum(np.square(x - a2 ))) ,axis=1)
s22 = minmax_df.apply(lambda x: np.sqrt(np.sum(np.square(x - b2 ))) ,axis=1)
s23 = minmax_df.apply(lambda x: np.sqrt(np.sum(np.square(x - c2 ))) ,axis=1)
# 合并第二次循环计算的结果
df_cy2 = pd.concat([s21,s22,s23],axis=1,keys=['a','b','c'])
# 求每一行中最小值对应的列名,使用idxmin()函数,最大值是idxmax()
df_cy2['划分2']=df_cy2.apply(lambda x:x.idxmin(),axis=1)
df_cy2

Snipaste_2020-05-10@23_17

这里存在疑问,划分4和划分5两个分类结果一直交替,不知道为什么会出现这种结果,这里暂且认为从划分4之后数据分类就不再变化。

从以上划分4的结果可以看到,中国、伊拉克、阿联酋、乌兹别克断坦是属于同一个类的。

6、使用sklearn种的k-means算法

sklearn 是 Python 的机器学习工具库,如果从功能上来划分,sklearn 可以实现分类、聚类、回归、降维、模型选择和预处理等功能。这里我们使用的是 sklearn 的聚类函数库,因此需要引用工具包:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
n_clusters: 即 K 值,一般需要多试一些 K 值来保证更好的聚类效果。你可以随机设置
一些 K 值,然后选择聚类效果最好的作为最终的 K 值;
init: 即初始值选择的方式,默认是采用优化过的 k-means++ 方式,你也可以自己指
定中心点,或者采用 random 完全随机的方式。自己设置中心点一般是对于个性化的数
据进行设置,很少采用。random 的方式则是完全随机的方式,一般推荐采用优化过的
k-means++ 方式;
max_iter: 最大迭代次数,如果聚类很难收敛的话,设置最大迭代次数可以让我们及
时得到反馈结果,否则程序运行时间会非常长;
n_init:初始化中心点的运算次数,默认是 10。程序是否能快速收敛和中心点的选择关
系非常大,所以在中心点选择上多花一些时间,来争取整体时间上的快速收敛还是非常
值得的。由于每一次中心点都是随机生成的,这样得到的结果就有好有坏,非常不确
定,所以要运行 n_init 次, 取其中最好的作为初始的中心点。如果 K 值比较大的时候,
你可以适当增大 n_init 这个值;
algorithm:k-means 的实现算法,有“auto” “full”“elkan”三种。一般来说建
议直接用默认的"auto"。简单说下这三个取值的区别,如果你选择"full"采用的是传统的
K-Means 算法,“auto”会根据数据的特点自动选择是选择“full”还是“elkan”。
我们一般选择默认的取值,即“auto” 。

最常用的是 fit 和 predict 这个两个函数。你可以单独使用 fit 函数和 predict 函数,也可以合并使用 fit_predict 函数。其中fit(data) 可以对 data 数据进行 k-Means 聚类。 predict(data) 可以针对 data 中的每个样本,计算最近的类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# coding: utf-8
from sklearn.cluster import KMeans
from sklearn import preprocessing #sklearn中的数据预处理
import pandas as pd
import numpy as np
# 输入数据
df = pd.read_excel('kmeans源数据.xlsx')
train_x = df[["2019年国际排名","2018世界杯","2015亚洲杯"]]
kmeans = KMeans(n_clusters=3)

# 0-1规范化
min_max_scaler=preprocessing.MinMaxScaler()
train_x=min_max_scaler.fit_transform(train_x)

# kmeans 算法训练+聚类结果出来
kmeans.fit(train_x)
predict_y = kmeans.predict(train_x)

# 合并聚类结果,插入到原数据中
result = pd.concat((df,pd.DataFrame(predict_y)),axis=1)
result.rename({0:u'聚类'},axis=1,inplace=True)
result

Snipaste_2020-05-10@23_40

结果和我们之前计算的结果一致。

四、使用k-means对rmf指标进行分类

1、生成数据并计算

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# coding: utf-8
# kmeans算法聚类并输出结果
from sklearn.cluster import KMeans
from sklearn import preprocessing #sklearn中的数据预处理
import pandas as pd
import numpy as np
# 输入数据
train_x = df[["R值","F值","M值"]]
kmeans = KMeans(n_clusters=4)

# 0-1标准化
min_max_scaler=preprocessing.MinMaxScaler()
train_x=min_max_scaler.fit_transform(train_x)

# kmeans 算法
kmeans.fit(train_x)
predict_y = kmeans.predict(train_x)

# 合并聚类结果,插入到原数据中
result = pd.concat([df,pd.DataFrame(data=predict_y,index=df.index)],axis=1,)
result.rename({0:u'聚类'},axis=1,inplace=True)
result

Snipaste_2020-05-16@17_45

2、分类之后的数据如何使用?

核心问题:分类之后需要依据分类结果划分值,怎么划分?

这里通过计算各群体特征的概率密度函数来看各个类中的分布情况:

研究一个随机变量,不只是要看它能取哪些值,更重要的是它取各种值的概率如何!

PDF的取值本身不是概率,它是一种趋势(密度)只有对连续随机变量的取值进行积分后才是概率,也就是说对于连续值确定它在某一点的概率是没有意义的;

rfm模型是连续型随机变量,所以这里使用概率密度函数来表示各个分类的结果分布,不写循环了,多写几遍代码了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
import scipy.stats as st
import matplotlib.pyplot as plt
plt.rcParams['font.sans-serif']=['SimHei']
plt.rcParams['axes.unicode_minus'] = False

# 1. 离散随机变量:概率质量函数(Probability Mass Function,PMF)
# PMF即离散随机变量在各特定取值上的概率
# 2. 连续随机变量:概率密度函数(Probability Density Function,PDF)
# PDF:连续随机变量的概率密度函数是描述这个随机变量的输出值,在某个特定取值点附近可能性的函数。
r0 = result[result['聚类']==0]['R值'].values
sr0 = st.norm.pdf(r0,loc=np.mean(r0),scale=np.std(r0))
f0 = result[result['聚类']==0]['F值'].values
sf0 = st.norm.pdf(f0,loc=np.mean(f0),scale=np.std(f0))
m0 = result[result['聚类']==0]['M值'].values
sm0 = st.norm.pdf(m0,loc=np.mean(m0),scale=np.std(m0))

r1 = result[result['聚类']==1]['R值'].values
sr1 = st.norm.pdf(r1,loc=np.mean(r1),scale=np.std(r1))
f1 = result[result['聚类']==1]['F值'].values
sf1 = st.norm.pdf(f1,loc=np.mean(f1),scale=np.std(f1))
m1 = result[result['聚类']==1]['M值'].values
sm1 = st.norm.pdf(m1,loc=np.mean(m1),scale=np.std(m1))

r2 = result[result['聚类']==2]['R值'].values
sr2 = st.norm.pdf(r2,loc=np.mean(r2),scale=np.std(r2))
f2 = result[result['聚类']==2]['F值'].values
sf2 = st.norm.pdf(f2,loc=np.mean(f2),scale=np.std(f2))
m2 = result[result['聚类']==2]['M值'].values
sm2 = st.norm.pdf(m2,loc=np.mean(m2),scale=np.std(m2))

r3 = result[result['聚类']==3]['R值'].values
sr3 = st.norm.pdf(r3,loc=np.mean(r3),scale=np.std(r3))
f3 = result[result['聚类']==3]['F值'].values
sf3 = st.norm.pdf(f3,loc=np.mean(f3),scale=np.std(f3))
m3 = result[result['聚类']==3]['M值'].values
sm3 = st.norm.pdf(m3,loc=np.mean(m3),scale=np.std(m3))

plt.figure(1,figsize=(40,40))
ax1 = plt.subplot(3,4,1)
ax2 = plt.subplot(3,4,2)
ax3 = plt.subplot(3,4,3)

ax4 = plt.subplot(3,4,4)
ax5 = plt.subplot(3,4,5)
ax6 = plt.subplot(3,4,6)
ax7 = plt.subplot(3,4,7)
ax8 = plt.subplot(3,4,8)
ax9 = plt.subplot(3,4,9)
ax10 = plt.subplot(3,4,10)
ax11 = plt.subplot(3,4,11)
ax12 = plt.subplot(3,4,12)

plt.sca(ax1)
plt.scatter(r0,sr0)
plt.xlim(min(df['R值']),max(df['R值']))
plt.xlabel('类别0',fontsize=40)
plt.sca(ax5)
plt.scatter(f0,sf0)
plt.xlim(min(df['F值']),max(df['F值']))
plt.sca(ax9)
plt.scatter(m0,sm0)
plt.xlim(min(df['M值']),max(df['M值']))

plt.sca(ax2)
plt.scatter(r1,sr1)
plt.xlim(min(df['R值']),max(df['R值']))
plt.xlabel('类别1',fontsize=40)
plt.sca(ax6)
plt.scatter(f1,sf1)
plt.xlim(min(df['F值']),max(df['F值']))
plt.sca(ax10)
plt.scatter(m1,sm1)
plt.xlim(min(df['M值']),max(df['M值']))

plt.sca(ax3)
plt.scatter(r2,sr2)
plt.xlim(min(df['R值']),max(df['R值']))
plt.xlabel('类别2',fontsize=40)
plt.sca(ax7)
plt.scatter(f2,sf2)
plt.xlim(min(df['F值']),max(df['F值']))
plt.sca(ax11)
plt.scatter(m2,sm2)
plt.xlim(min(df['M值']),max(df['M值']))

plt.sca(ax4)
plt.scatter(r3,sr3)
plt.xlim(min(df['R值']),max(df['R值']))
plt.xlabel('类别3',fontsize=40)
plt.sca(ax8)
plt.scatter(f3,sf3)
plt.xlim(min(df['F值']),max(df['F值']))
plt.sca(ax12)
plt.scatter(m3,sm3)
plt.xlim(min(df['M值']),max(df['M值']))

下载

对以上分组数据进行解读即可。

最后

本文并未涉及对K值的选取,故有疑问,自行搜索。

附上源文件地址:

K-MEANS聚类.html