Kmeans聚类 - 旅游企业客户洞察案例

1.案例说明

一般案例客户聚类分析流程(个人理解)

  1. 提取客户消费行为变量
  2. 对提取的行为变量进行正态化(使聚类结果更有商业解读意义),和Z-score处理(消除纲量)
  3. 对处理后的变量进行相关性分析
  4. 对处理后的变量进行主成分和因子分析(消除离群值)
  5. 对各因子基于业务理解进行解读
  6. 通过簇内离差平方和轮廓系数确定聚类簇的个数
  7. 对原数据在各因子上的得分进行聚类,并对结果进行解读
  8. 用客户个人属性变量及经济状态变量作为因变量,对分类结果构建决策树模型,探查各分类客户个人属性及经济状况

由于案例数据集没有客户消费行为数据,对客户的兴趣爱好状态属性及家庭情况状态属性分别进行聚类,并对聚类结果通过决策树解读,探究旅游企业客户类型,并对各类型客户做业务解读,并提出精准营销建议

2.查看数据,数据预处理

读取数据
os.chdir(r'C:\Users\Administrator\Desktop\Python_book\19Case\19_3Travel_clust')
df = pd.read_csv('data_travel.csv')
df.head()
df.info()

其中变量interested_reading为数值型,dtype显示为字符型,其包含错误值“.”,将其以0进行替换,代表该用户对阅读没有兴趣。

df.interested_reading.value_counts()
df['interested_reading'] = df['interested_reading'].replace('.',0).astype(int)
缺失值处理
#查看缺失情况
df.isnull().sum()/len(df)

缺失变量为连续变量,缺失率较小,填充中位数

misscol = ['interested_travel','computer_owner','HH_adults_num']
missvalue = {col:df[col].median() for col in misscol}
df.fillna(missvalue,inplace=True)

变量HH_has_children应为连续变量,将其二值化,其中NaN表示没有小孩,用0表示

df['HH_has_children'] = df['HH_has_children'].map({'N':0,np.NaN:0,'Y':1})
变量修改

变量interested_reading 分类水平只有0,1,2,3将其二值化

from sklearn.preprocessing import Binarizer
binarizer = Binarizer(threshold=1.5)
df['interested_reading'] = binarizer.fit_transform(df[['interested_reading']])

'interested_reading','interested_travel','interested_golf','interested_gambling','computer_owner'五个变量代表是否对休闲娱乐的兴趣,将其做变量转化以五个变量的合计代表客户对休闲娱乐爱好广度

interested = ['interested_reading','interested_travel','interested_golf','interested_gambling','computer_owner']
df['interested'] = df[interested].sum(axis=1)
df = df.drop(interested,axis=1)

重新定义预处理后的数据

data =df.copy()
#定义变量属性
#兴趣属性
interest = ['auto_member',
'interested_sport', 
'interested', 
'HH_dieting']
#家庭信息属性
household = ['age', 'marital', 'HH_adults_num', 'HH_has_children','HH_grandparent','home_value', 'risk_score', 'loan_ratio']

3.正态化和Z-score标准化处理

Z-score标准化处理
#连续变量
var_con = ['auto_member','interested_sport','age','HH_grandparent','home_value', 'risk_score', 'loan_ratio', 'HH_dieting']
#水平较低的连续变量作为有序等级变量
var_cat =['interested','HH_adults_num','marital']
#二分类变量不做处理
var_d = 'HH_has_children'

水平较少的变量,如变量interested,作为有序分类变量,不做分布转化(正态化),直接标准化处理

from sklearn.preprocessing import scale
data[var_cat] = scale(data[var_cat])

连续变量QuantileTransformer

为避免聚类后某一类别较多(可能占到90%以上),为使每个类别的客户均匀分配,将原始分类变量进行正太转化

# quantile_transform 函数提供了一个基于分位数函数的无参数转换,将数据映射到了零到一的均匀分布上
# output_distribution='normal' 将转换后的数据映射到正态分布:
from sklearn.preprocessing import QuantileTransformer
qt = QuantileTransformer(output_distribution='normal')
data[var_con] = qt.fit_transform(data[var_con])
data[var_con].hist(bins =20)
plt.show()
QuantileTransformer后分布

查看正态化和Z-score标准化处理后数据


4.相关性分析

sns.heatmap(data[interest].corr(),annot=True)
interest属性
sns.heatmap(data[household].corr(),annot=True,cmap='Blues')
household属性

进入聚类的各变量间具有一定相关性

5.进行主成分和因子分析(消除离群值)

from sklearn.decomposition import PCA
pca = PCA()
pca.fit(data[interest])
print('累计方差贡献率',pca.explained_variance_ratio_.cumsum())
print('-------------')
print('方差贡献率越高,能解释的变异数据越多')

累计方差贡献率 [ 0.49025739 0.71629358 0.91078434 1. ]
方差贡献率越高,能解释的变异数据越多,可以选取2-3个因子
其中保留3个主成分可以解释91%的变异数据,选取3个因子

from fa_kit import FactorAnalysis
from fa_kit import plotting as fa_plot
fa = FactorAnalysis.load_data_samples(data[interest],preproc_demean = True,preproc_scale = True)
#数据导入和转换
fa.extract_components()
#抽取主成分
fa.find_comps_to_retain(method='top_n',num_keep=3)
#确认保留因子的数量,使用top_n法
fa.rotate_components(method='varimax')
#使用最大方差法进行因子旋转

查看各个因子在各变量上的权重

fas = pd.DataFrame(fa.comps['rot'].T,columns = interest)
fas
第一个因子在auto_member,interested_sport变量上权重较大,表示客户对户外运动的度量,定义为户外运动因子
第二个因子在HH_dieting,interested_sport变量上权重较大,表示客户选择保持身材的度量,定义为塑形因子
第三个因子在interested变量上权重较大,表示客户选择休闲娱乐的度量,定义为休闲娱乐因子

计算每个客户在各因子上的得分

score = pd.DataFrame(np.dot(data[interest],fa.comps['rot']))
score.columns=['户外运动因子','塑形因子','休闲娱乐因子']

6.确定聚类簇的个数

通过簇类离差平方和和silhouette_score系数确定最优的K值
轮廓系数允许容易造成内存不足,也可通过calinski_harabaz系数确定最优的K值

from sklearn.cluster import KMeans
from sklearn.metrics import calinski_harabaz_score
def cluster_plot(data):
    seed = []
    cs = []
    for k in range(2,10):
        model = KMeans(n_clusters=k,n_init=5,n_jobs=-1)
        model.fit(data)
        seed.append(model.inertia_)#离差平方和
        cs.append(calinski_harabaz_score(data,model.labels_))

    plt.subplot(221)
    plt.plot(range(2,10),seed)
    plt.subplot(222)
    plt.plot(range(2,10),cs)
cluster_plot(score)

分类类别越多,簇类离差平方越大,一般选取下降趋势最大的时作为K值选取范围,K=4后下降趋势变为平缓
calinski_harabaz系数在K=2时最高,以后逐渐下降
结合这两点我们选取k=3

7. 进行聚类分析并解读
model = KMeans(n_clusters=3,n_init=15,n_jobs=-1)
model.fit(score)
score['type'] = model.labels_
from pyecharts import Pie
attr = ['群体1','群体2','群体3']
v1 = score.groupby('type').count().iloc[:,1]
pie = Pie("各分类群体占比")
pie.add("", attr, v1, is_label_show=True,radius=[20, 60])
pie
#各群体占比

各分类占比较均匀,符合商业解读需求

data_hh = score.groupby('type').mean()
from pyecharts import Radar
c_schema=[]
for i in data_hh.columns:
    dict = {}
    dict['name'] = i
    dict['max'] = 2
    dict['min'] = -1
    c_schema.append(dict)
radar = Radar()
color = ['#50514F','#F25F5C','#FFE066']
radar.config(c_schema=c_schema,shape='circle')
for i in range(3):
    radar.add('群体%i'%(i+1),[data_hh.iloc[i]],is_area_show=True,area_opacity=0.6,area_color=color[i])
radar
聚类群体1 hh=0 偏爱健身,注重身材
聚类群体2 hh=1 家里宅
聚类群体3 hh=2 不太在意身形,偏向休闲娱乐
8. 使用决策树算法进行分群后的用户个人信息及状态信息特征探查与理解
import os
os.environ["PATH"] += os.pathsep + 'C:/Program Files (x86)/Graphviz2.38/bin/' 
df['hh'] = model.labels_
#使用决策树算法进行分群后的用户个人信息及状态信息特征探查与理解
from sklearn.tree import DecisionTreeClassifier
clf= DecisionTreeClassifier()
clf.fit(df[household],df['hh'])
import pydotplus
from IPython.display import Image
import sklearn.tree as tree
dot_hh = tree.export_graphviz(clf,out_file=None,feature_names=household,
                             class_names=['0','1','2'],
                             max_depth=2,filled=True,rounded=True,special_characters=True)
graph_hh = pydotplus.graph_from_dot_data(dot_hh)
Image(graph_hh.create_png())
群体0特征,祖父是否健在可能性低于657.5(无长辈)
群体1特征,贷款比例低于69,
群体2特征,已婚可能性低于7.5(未婚)
9. 对家庭属性进行聚类分析及解读
from sklearn.decomposition import PCA
pca = PCA()
pca.fit(data[household])
print('累计方差贡献率',pca.explained_variance_ratio_.cumsum())
#保留4个主成分能解释87%的数据变异,选取4个因子
#其中保留3个主成分可以解释91%的变异数据,选取3个因子
from fa_kit import FactorAnalysis
from fa_kit import plotting as fa_plot
fa = FactorAnalysis.load_data_samples(data[household],preproc_demean = True,preproc_scale = True)
#数据导入和转换
fa.extract_components()
#抽取主成分
fa.find_comps_to_retain(method='top_n',num_keep=4)
#确认保留因子的数量,使用top_n法
fa.rotate_components(method='varimax')
#使用最大方差法进行因子旋转
fas = pd.DataFrame(fa.comps['rot'].T,columns = household)
fas
#第一个因子在年龄,祖辈建在可能性上权重较大,定义为长寿因子
#第二个因子在房屋价值上,以及贷款比率权重较大,定义为经济状况因子
#第三个因子在婚姻可能性,家庭成员数量上权重较大,定义为组建家庭因子
#第四个因子在信用风险权重较大,定义为信用风险因子
score1 = pd.DataFrame(np.dot(data[household],fa.comps['rot']))
score1.columns=['长寿因子','经济状况因子','组建家庭因子','信用风险因子']
#计算每个客户在各因子上的得分
#聚类K值的选择
#通过簇类离差平方和和silhouette_score系数确定最优的K值
#轮廓系数允许容易造成内存不足,也可通过calinski_harabaz系数确定最优的K值
cluster_plot(score1)
#分类类别越多,簇类离差平方越大,一般选取下降趋势最大的时作为K值选取范围,K=4后下降趋势变为平缓
#calinski_harabaz系数在K=2时最高,以后逐渐下降
#结合这两点我们选取k=3
model1 = KMeans(n_clusters=3,n_init=15)
model1.fit(score1)
score1['type'] = model1.labels_
from pyecharts import Pie
attr = ['群体1','群体2','群体3']
v1 = score1.groupby('type').count().iloc[:,1]
pie = Pie("各分类群体占比")
pie.add("", attr, v1, is_label_show=True,radius=[20, 60])
pie
#各群体占比为较为理想的占比方式
data_hb = score1.groupby('type').mean()
data_hb
聚类群体1 hb=0 经济状况较差,单身
聚类群体2 hb=1 长寿,信用风险较高
聚类群体3 hb=2 经济状况较好,有家有口

将两个聚类结果放在一起解读,生成9个簇

ana = df.groupby(['hh','hb']).mean()
#便于解读变量转化为中文
ana.columns = ['年龄','房屋价值','贷款比率','信用风险','已婚估计','运动偏爱','祖辈健在估计','偏爱节食','估计年龄','驾驶俱乐部估计','有否孩子','家庭成员人数','休闲兴趣广度']
c_schema=[]
for i in ana.columns:
    dict = {}
    dict['name'] = i
    dict['max'] = ana[i].values.max()
    dict['min'] = ana[i].values.min()
    c_schema.append(dict)
radar = Radar()
radar.config(c_schema=c_schema,shape='circle')
for i in range(9):
    radar.add('群体%i'%(i+1),[ana.iloc[i].values.tolist()],is_area_show=True,area_opacity=0.6)
radar

群体1 经济状况较差,单身,偏爱健身,注重身材


群体2 偏爱健身,注重身材,长寿,信用风险较高

群体3 偏爱健身,注重身材,长寿经济状况较好,有家有口

群体4 家里宅,经济状况较差,单身,低价值客户

群体5 不愿意运动,长寿,信用风险较高

群体6 不太爱运动,长寿经济状况较好,有家有口

群体7 不太在意身形,偏向休闲娱乐,经济状况较差,单身

群体8 不太在意身形,偏向休闲娱乐,长寿,信用风险较高

群体9 不太在意身形,偏向休闲娱乐,经济状况较好,有家有口

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容