携程作为中国领先的综合性旅行服务公司,每天向超过2.5亿会员提供全方位的旅行服务,在这海量的网站访问量中,我们可分析用户的行为数据来挖掘潜在的信息资源,用K-means对用户进行画像,并针对不同的用户类别,提出可行的营销建议。
2.1数据集来源:
数据集包括:训练集和测试集。训练集为2016.05.15-2016.05.21期间一周的访问数据,测试集为2016.05.22-2016.05.28期间一周的访问数据。本篇文章主要讨论的是聚类分析,所以只用训练集。
#导入基础包
%matplotlib inline
import pandas as pd
import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt
plt.rcParams['font.sans-serif']=['SimHei'] #用来正常显示中文标签
plt.rcParams['axes.unicode_minus']=False #用来正常显示负号
#读取数据
df=pd.read_csv('./userlostprob_train.txt',sep='\t')
df.describe()
通过查看数据类型,发现所有字段中只有d、arrival两个字符串格式,分别代表预定时间和入住时间,都为yyyy-mm-dd日期形式,将其相减可得到“提前预定的天数”,得到新的衍生变量特征
# 转为日期型格式
df['arrival']=pd.to_datetime(df['arrival'])
df['d']=pd.to_datetime(df['d'])
# 相减得到“提前预定天数”列
df['day_advanced']=(df['arrival']-df['d']).dt.days
# 删除原有列
df=df.drop(['d','arrival'],axis=1)
通过描述统计观察发现,delta_price1、delta_price2、lowestprice、customer_value_profit、ctrip_profits这几个变量最小值为负值,需要对其处理。同时,结合四分位和极值,发现有极大或极小的异常值,如decisionhabit_user、historyvisit_avghotelnum等,较多字段都存在异常值,对所有字段一并进行处理。
for col in ['delta_price1','delta_price2','lowestprice']:
df.loc[df[col]<0,col]=df[col].median() # 填充中位数
for col in ['customer_value_profit','ctrip_profits']:
df.loc[df[col]<0,col]=0 # 填充0
#极值处理
for i in df.columns:
df.loc[df[i]<np.percentile(df[i],1),i]=np.percentile(df[i],1)
df.loc[df[i]>np.percentile(df[i],99),i]=np.percentile(df[i],99)
df.isnull().sum()
特征值中只有iforderpv_24h、sid、h、day_advanced这四个是不存在缺失的,其他的44个特征都是存在缺失值的,并且大部分的缺失值都挺多的,因此需要对缺失值进行处理
利用dropna(thresh=n)过滤方式,删除行列缺失值大于80%的数据。这里重点理解thresh参数的用法。
# 删除缺失值比例大于80%的行和列
print('删除空值前数据维度是:{}'.format(df.shape))
df.dropna(axis=0,thresh=df.shape[1]*0.2,inplace=True)
df.dropna(axis=1,thresh=df.shape[0]*0.2,inplace=True)
print('删除空值后数据维度是:{}'.format(df.shape))
对缺失值补充前,先查看一下各变量的数值分布。
df.hist(figsize=(20,20))
plt.savefig('hist.png')
通过上图看出’businessrate_pre’,‘businessrate_pre2’,‘cancelrate_pre’,'customereval_pre2 '这些字段大体服从正态分布,可以用均值填充;其余字段大都呈右偏态分布,右偏分布就不可以用均值填充了,因为会受到极值的影响,但中位数不太受异常值或者极值的影响,使用中位数填充比较合适。
filter_mean=['businessrate_pre','businessrate_pre2','cancelrate_pre','customereval_pre2 ']
for i in df.columns:
if i in filter_mean:
df[i].fillna(df[i].mean(),inplace=True)
else:
df[i].fillna(df[i].median(),inplace=True)
通过上面数据描述分析,数据集中还存在极值,过大或者过小的值会对模型分析造成影响,这里通过截断填充的方式,分别对极小值和极大值进行处理。
for i in df.columns:
#小于1%分位数的用1%分位数填充
df.loc[df[i]<np.percentile(df[i],1),i]=np.percentile(df[i],1)
# 大于99%分位数的用99%分位数填充
df.loc[df[i]>np.percentile(df[i],99),i]=np.percentile(df[i],99)
RFM模型是衡量客户价值和客户创利能力的重要工具和手段,其有三个指标:最近一次消费时间间隔(Recency),消费频率(Frequency),消费金额(Monetary)。本数据集中三个指标并不都是直接给出,需要进行分析提取。
consume_level=['avgprice','consuming_capacity'] # 合并字段作为消费水平
from sklearn.decomposition import PCA #利用PCA主成分分析无监督的降维方法
pca=PCA(n_components=1) #n_components设置为1
df['consume_level']=pca.fit_transform(df[consume_level]) #返回降维后的数据
df.drop(consume_level,axis=1,inplace=True) #删除冗余列
#字段重名
rfm = df[['lasthtlordergap','ordernum_oneyear','consume_level']]
rfm.rename(columns={'lasthtlordergap':'recency','ordernum_oneyear':'frequency','consume_level':'monetary'},inplace=True)
#利用MinMaxScaler进行归一化处理
from sklearn.preprocessing import MinMaxScaler
scaler = MinMaxScaler()
scaler.fit(rfm)
rfm = pd.DataFrame(scaler.transform(rfm),columns=['recency','frequency','monetary'])
#分箱
rfm['R']=pd.qcut(rfm["recency"], 2)
rfm['F']=pd.qcut(rfm["frequency"], 2)
rfm['M']=pd.qcut(rfm["monetary"], 2)
# 根据分箱情况进行编码,二分类可以直接用标签编码方式
from sklearn.preprocessing import LabelEncoder
rfm['R']=LabelEncoder().fit(rfm['R']).transform(rfm['R'])
rfm['F']=LabelEncoder().fit(rfm['F']).transform(rfm['F'])
rfm['M']=LabelEncoder().fit(rfm['M']).transform(rfm['M'])
#定义RFM模型,需要特别注意的是,R值代表距离上次消费时间间隔,值越小客户价值越高,与F和M值正好相反。
def get_label(r,f,m):
if (r==0)&(f==1)&(m==1):
return '高价值客户'
if (r==1)&(f==1)&(m==1):
return '重点保持客户'
if((r==0)&(f==0)&(m==1)):
return '重点发展客户'
if (r==1)&(f==0)&(m==1):
return '重点挽留客户'
if (r==0)&(f==1)&(m==0):
return '一般价值客户'
if (r==1)&(f==1)&(m==0):
return '一般保持客户'
if (r==0)&(f==0)&(m==0):
return '一般发展客户'
if (r==1)&(f==0)&(m==0):
return '潜在客户'
def RFM_convert(df):
df['Label of Customer']=df.apply(lambda x:get_label(x['R'],x['F'],x['M']),axis=1)
df['R']=np.where(df['R']==0,'高','低')
df['F']=np.where(df['F']==1,'高','低')
df['M']=np.where(df['M']==1,'高','低')
return df[['R','F','M','Label of Customer']]
rfm1=RFM_convert(rfm)
rfm1.head(5)
value_counts=rfm1["Label of Customer"].value_counts().values
labels=rfm1["Label of Customer"].value_counts().index
explode=[0.1,0.1,0.1,0,0,0,0,0]
color=['deepskyblue','steelblue','lightskyblue','aliceblue','skyblue','cadetblue','cornflowerblue','dodgerblue']
plt.figure(figsize=(10, 7))
plt.pie(x=value_counts,labels=labels,autopct='%.2f%%',explode=explode,colors=color,wedgeprops={'linewidth':0.5,'edgecolor':'black'},
textprops={'fontsize':12,'color':'black'})
plt.legend(labels,bbox_to_anchor=(1, 1), loc='best', borderaxespad=0.7)
plt.title('客户类别细分情况')
plt.show()
通过饼形图观察看出,一般发展客户数最多,占比40.72%,其次是潜在客户,高价值客户。
上面RFM模型只用到数据集中lasthtlordergap、ordernum_oneyear、avgprice、consuming_capacity这几个直接相关变量,但这些变量并不能完全涵盖用户特征,所以,接下来用K-Means聚类的方法引入其他变量进一步探究分析,观察不同类别客户的特征。
观察整个数据集可以大体分为两个类别:用户信息和酒店信息。用户信息,即主体是用户,如consuming_capacity (消费能力指数)、price_sensitive(价格敏感指数)、starprefer(星级偏好)等,这些变量主要描述的是用户信息;酒店信息,即主体是酒店,如hotelcr (当前酒店历史cr),commentnums (当前酒店点评数)、novoters (当前酒店评分人数)等,这些变量主要描述的酒店信息。
# 用户特征提取
user_features=['visitnum_oneyear','starprefer','sid','price_sensitive','ordernum_oneyear','ordercanncelednum','ordercanceledprecent','lastpvgap',
'lasthtlordergap','landhalfhours','iforderpv_24h','historyvisit_totalordernum','historyvisit_avghotelnum','h',
'delta_price2','delta_price1','decisionhabit_user','customer_value_profit','ctrip_profits','cr','consume_level']
#生成用户特征的相关性矩阵
user_corr=df[user_features].corr()
#绘制用户特征的相关性矩阵热度图
fig,ax = plt.subplots(figsize=(18, 12))
sns.heatmap(user_corr, xticklabels=True, yticklabels=True, square=False, linewidths=.5, annot=True, cmap="YlGnBu")
PCA降维可以在尽量保证“信息量不丢失”的情况下,对原始特征进行降维,也就是尽可能将原始特征往具有最大投影信息量的维度上进行投影,将原特征投影到这些维度上,使降维后信息量损失最小。
#删除historyvisit_totalordernum列
delete_columns = ['historyvisit_totalordernum']
df.drop(delete_columns,axis=1,inplace= True)
#PCA主成分分析
from sklearn.decomposition import PCA
#定义降维函数
def PCA_transform(df,col,new_col,n=1):
pca=PCA(n_components=n)
pca.fit(df[col])
df[new_col]=pca.transform(df[col]) # 添加新生成列
df.drop(col,axis=1,inplace= True) # 删除原来的特征列
#选择特征
price_prefer=['delta_price1','delta_price2'] # 用户价格偏好
visit_num=['decisionhabit_user','historyvisit_avghotelnum'] #用户访问数
c_value=['customer_value_profit','ctrip_profits'] # 用户价值
#应用函数
PCA_transform(df,price_prefer,'price_prefer')
PCA_transform(df,visit_num,'visit_num')
PCA_transform(df,c_value,'c_value'
K-Means算法是一种基于划分的无监督聚类算法,它以 k 为参数,把 n 个数据对象分成 k 个簇,使簇内具有较高的相似度,而簇间的相似度较低。
# 选取刻画用户的重要指标
user_feature = ['consume_level','c_value','day_advanced','h','lasthtlordergap','lastpvgap',
'landhalfhours','ordernum_oneyear','ordercanceledprecent','price_sensitive','price_prefer','sid','starprefer','visit_num']
user_attributes = df[user_feature]
# 数据标准化
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
scaler.fit(user_attributes)
user_attributes = scaler.transform(user_attributes)
#K-Means聚类
from sklearn.cluster import KMeans
Kmeans=KMeans(n_clusters=3,random_state=13) # 建立KMean模型
Kmeans.fit(user_attributes) # 训练模型
k_char=Kmeans.cluster_centers_ # 得到每个分类的质心
personas=pd.DataFrame(k_char.T,index=user_feature,columns=['0类','1类','2类']) # 用户画像表
personas
#绘制热力图
fig,ax = plt.subplots(figsize=(5, 10))
sns.heatmap(personas, xticklabels=True, yticklabels=True, square=False, linewidths=.5, annot=True, cmap="YlGnBu")
#绘制饼形图
class_k=list(Kmeans.labels_) # 每个类别的用户个数
percent=[class_k.count(0)/len(user_attributes),class_k.count(1)/len(user_attributes),class_k.count(2)/len(user_attributes)] # 每个类别用户个数占比
fig, ax = plt.subplots(figsize=(11,11))
colors=['chocolate','sandybrown','peachpuff']
types=['中价值用户','高价值用户','低价值用户']
ax.pie(percent,radius=1,autopct='%.2f%%',pctdistance=0.75,colors=colors,labels=types)
ax.pie([1], radius=0.6,colors='w')
plt.show()
从饼形图中看出,高价值用户和低价值用户占比比较接近,都在15%左右;处于中间的中价值用户占比最高,占比约70%。
基于上面聚类分析得到的用户特征,分别对不同价值用户给出运营建议: