一、概述
1.1 项目背景:
客户是企业的重要资源,也是企业的无形资产,客户的流失,也就意味着资产的流失,因此进行流失分析是十分重要的,进行客户流失分析的目的,就是阻止或者避免客户的流失,特高企业的盈利水平和竞争力。
1.2 目的:
深入了解用户画像及行为偏好,挖掘出影响用户流失的关键因素,并通过算法预测客户访问的转化结果,从而更好地完善产品设计、提升用户体验。
1.3 数据说明:
此次数据是携程用户一周的访问数据,为保护客户隐私,已经将数据经过了脱敏,和实际商品的订单量、浏览量、转化率等有一些差距,不影响问题的可解性。
二、读取数据
2.1 理解数据
可以看到变量比较的多,先进行分类,除去目标变量label,此数据集的字段可以分成三个类别:订单相关指标、客户行为相关指标、酒店相关指标。
2.2 导入相关库及数据
import numpy as np
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
%matplotlib inline
# 显示全部特征
pd.set_option('display.max_columns', None)
df = pd.read_csv(r"./userlostprob.txt",sep="\t",encoding="UTF-8")
df.head()
# 查看数据维度
df.shape
总共689945条样本数据,除去标签和id列,总共49个特征。
# 查看数据类型
df.info()
# 查看缺失值占比
df.isnull().mean()
# 查看标签分布
df['label'].value_counts()
# 描述性统计
df.describe()
三、特征工程
# 将数据copy一份
rawdf=df.copy()
3.1 数据预处理
3.1.1 数据类型转换
字符串类型的特征需要处理成数值型才能建模,将arrival和d相减得到"提前预定的天数",作为新的特征.
## 增加列
# 将两个日期变量由字符串转换为日期型格式
rawdf['arrival']=pd.to_datetime(rawdf['arrival'])
rawdf['d']=pd.to_datetime(rawdf['d'])
# 生成提前预定时间列(衍生变量)
rawdf['day_advanced']=(rawdf['arrival']-rawdf['d']).dt.days
## 删除列
rawdf=rawdf.drop(['sampleid','d','arrival'],axis=1)
3.1.2 异常值处理
- 将customer_value_profit、ctrip_profits中的负值按0处理
- 将delta_price1、delta_price2、lowestprice中的负值按中位数处理
filter_one=['customer_value_profit','ctrip_profits']
filter_two=['delta_price1','delta_price2','lowestprice']
for f in filter_one:
rawdf.loc[rawdf[f]<0,f] = 0
for f in filter_two:
rawdf.loc[rawdf[f]<0,f] = rawdf[f].median()
rawdf[['customer_value_profit','ctrip_profits','delta_price1','delta_price2','lowestprice']].describe()
3.1.3 缺失值处理
3.1.3.1 空值删除
特征值中只有iforderpv_24h、sid、h、day_advanced这四个是不存在缺失的,其他的44个特征都是存在缺失值的,并且大部分的缺失值都挺多的,因此需要对缺失值进行处理.
# 定义删除空值行列的函数
def nan_drop(df, axi, rate=0.5):
df.dropna(axis=axi,thresh=df.shape[1-axi]*rate,inplace=True)
# 删除缺失值比例大于80%的行和列
print('删除空值前数据维度是:{}'.format(rawdf.shape))
nan_drop(rawdf,axi=0,rate=0.2)
nan_drop(rawdf,axi=1,rate=0.2)
print('删除空值后数据维度是:{}'.format(rawdf.shape))
3.1.3.2 缺失值填充
趋于正态分布的字段,使用均值填充:businessrate_pre2、cancelrate_pre、businessrate_pre;偏态分布的字段,使用中位数填充.
def nan_fill(df):
filter_mean=['businessrate_pre2','cancelrate_pre','businessrate_pre']
for col in df.columns:
if col in filter_mean:
df[col]=df[col].fillna(df[col].mean())
else:
df[col]=df[col].fillna(df[col].median())
return df
rawdf=nan_fill(rawdf)
3.1.4 极值处理
有些特征明显有异常大和异常小的值,这里分别使用1%和99%分位数替换超过上下限的值.
for i in rawdf.columns:
rawdf.loc[rawdf[i]<np.percentile(rawdf[i],1),i]=np.percentile(rawdf[i],1)
rawdf.loc[rawdf[i]>np.percentile(rawdf[i],99),i]=np.percentile(rawdf[i],99)
3.2 相关性分析
# 用户特征的相关性分析
# 用户特征提取
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','consuming_capacity','avgprice']
# 生成用户特征的相关性矩阵
corr_mat=rawdf[user_features].corr()
# 绘制用户特征的相关性矩阵热度图
fig,ax = plt.subplots(figsize=(18, 12))
sns.heatmap(corr_mat, xticklabels=True, yticklabels=True, square=False, linewidths=.5, annot=True, cmap='Oranges')
plt.savefig('./Photo/用户特征的相关性分析.jpg',dpi=400, bbox_inches='tight')
plt.show()
# 酒店信息特征的相关性分析
hotel_features=['hotelcr','hoteluv','commentnums','novoters','cancelrate','lowestprice','cr_pre','uv_pre','uv_pre2','businessrate_pre',
'businessrate_pre2','customereval_pre2','commentnums_pre','commentnums_pre2','cancelrate_pre','novoters_pre','novoters_pre2',
'deltaprice_pre2_t1','lowestprice_pre','lowestprice_pre2','firstorder_bu','historyvisit_visit_detailpagenum']
# 生成用户特征的相关性矩阵
corr_mat1=rawdf[hotel_features].corr()
fig,ax = plt.subplots(figsize=(18, 12))
sns.heatmap(corr_mat1, xticklabels=True, yticklabels=True, square=False, linewidths=.5, annot=True, cmap='Oranges_r')
plt.savefig('./Photo/酒店信息特征的相关性分析.jpg',dpi=400, bbox_inches='tight')
plt.show()
3.3 降维
3.3.1 主成分分析法(PCA)
c_value=['customer_value_profit','ctrip_profits'] # 用户价值
consume_level=['avgprice','consuming_capacity'] # 用户消费水平
price_prefer=['delta_price1','delta_price2'] # 用户偏好价格
hotel_hot=['commentnums','novoters'] # 酒店热度
hotel_hot_pre=['commentnums_pre','novoters_pre'] # 24小时内浏览次数最多的酒店热度
hotel_hot_pre2=['commentnums_pre2','novoters_pre2'] # 24小时内浏览酒店的平均热度
from sklearn.decomposition import PCA
pca=PCA(n_components=1)
rawdf['c_value']=pca.fit_transform(rawdf[c_value])
rawdf['consume_level']=pca.fit_transform(rawdf[consume_level])
rawdf['price_prefer']=pca.fit_transform(rawdf[price_prefer])
rawdf['hotel_hot']=pca.fit_transform(rawdf[hotel_hot])
rawdf['hotel_hot_pre']=pca.fit_transform(rawdf[hotel_hot_pre])
rawdf['hotel_hot_pre2']=pca.fit_transform(rawdf[hotel_hot_pre2])
rawdf.drop(c_value,axis=1,inplace=True)
rawdf.drop(consume_level,axis=1,inplace=True)
rawdf.drop(price_prefer,axis=1,inplace=True)
rawdf.drop(hotel_hot,axis=1,inplace=True)
rawdf.drop(hotel_hot_pre,axis=1,inplace=True)
rawdf.drop(hotel_hot_pre2,axis=1,inplace=True)
rawdf.drop('historyvisit_totalordernum',axis=1,inplace=True) ###把重复的一列删了
print('PCA降维后数据维度是:{}'.format(rawdf.shape))
3.4 数据标准化
# 数据标准化
from sklearn.preprocessing import StandardScaler
y=rawdf['label']
x=rawdf.drop('label',axis=1)
scaler = StandardScaler()
scaler.fit(x)
X= scaler.transform(x)
四、建模与模型评估
数据集的划分原则上应当在缺失值和异常值处理之前就进行,也就是说从数据预处理阶段开始,验证集和测试集就不应参与到模型构建的各个阶段中来,而应仅使用训练集得到的信息,这样才能有效严谨的保证模型验证和评估不会出现潜在的过拟合问题。
from sklearn.model_selection import train_test_split, GridSearchCV
X_train,X_test,y_train,y_test = train_test_split(X,y,test_size= 0.2,random_state=420)
4.1 逻辑回归
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import classification_report
from sklearn import metrics
lr = LogisticRegression() # 实例化一个LR模型
lr.fit(X_train,y_train) # 训练模型
y_prob = lr.predict_proba(X_test)[:,1] # 预测1类的概率
y_pred = lr.predict(X_test) # 模型对测试集的预测结果
fpr_lr,tpr_lr,threshold_lr = metrics.roc_curve(y_test,y_prob) # 获取真阳率、伪阳率、阈值
auc_lr = metrics.auc(fpr_lr,tpr_lr) # AUC得分
score_lr = metrics.accuracy_score(y_test,y_pred) # 模型准确率
print('模型准确率为:{0},AUC得分为:{1}'.format(score_lr,auc_lr))
print('============================================================')
print(classification_report(y_test,y_pred,labels=None,target_names=None,sample_weight=None, digits=2))
4.2 朴素贝叶斯
from sklearn.naive_bayes import GaussianNB
gnb = GaussianNB() # 实例化一个LR模型
gnb.fit(X_train,y_train) # 训练模型
y_prob = gnb.predict_proba(X_test)[:,1] # 预测1类的概率
y_pred = gnb.predict(X_test) # 模型对测试集的预测结果
fpr_gnb,tpr_gnb,threshold_gnb = metrics.roc_curve(y_test,y_prob) # 获取真阳率、伪阳率、阈值
auc_gnb = metrics.auc(fpr_gnb,tpr_gnb) # AUC得分
score_gnb = metrics.accuracy_score(y_test,y_pred) # 模型准确率
print('模型准确率为:{0},AUC得分为:{1}'.format(score_gnb,auc_gnb))
print('============================================================')
print(classification_report(y_test, y_pred, labels=None, target_names=None, sample_weight=None, digits=2))
4.3 支持向量机
from sklearn.svm import SVC
svc = SVC(kernel='rbf',C=1,max_iter=100).fit(X_train,y_train)
y_prob = svc.decision_function(X_test) # 决策边界距离
y_pred = svc.predict(X_test) # 模型对测试集的预测结果
fpr_svc,tpr_svc,threshold_svc = metrics.roc_curve(y_test,y_prob) # 获取真阳率、伪阳率、阈值
auc_svc = metrics.auc(fpr_svc,tpr_svc) # 模型准确率
score_svc = metrics.accuracy_score(y_test,y_pred)
print('模型准确率为:{0},AUC得分为:{1}'.format(score_svc,auc_svc))
print('============================================================')
print(classification_report(y_test, y_pred, labels=None, target_names=None, sample_weight=None, digits=2))
4.4 决策树
from sklearn import tree
dtc = tree.DecisionTreeClassifier() # 建立决策树模型
dtc.fit(X_train,y_train) # 训练模型
y_prob = dtc.predict_proba(X_test)[:,1] # 预测1类的概率
y_pred = dtc.predict(X_test) # 模型对测试集的预测结果
fpr_dtc,tpr_dtc,threshod_dtc= metrics.roc_curve(y_test,y_prob) # 获取真阳率、伪阳率、阈值
score_dtc = metrics.accuracy_score(y_test,y_pred)
auc_dtc = metrics.auc(fpr_dtc,tpr_dtc)
print('模型准确率为:{0},AUC得分为:{1}'.format(score_dtc,auc_dtc))
print('============================================================')
print(classification_report(y_test,y_pred,labels=None,target_names=None,sample_weight=None, digits=2))
4.5 随机森林
from sklearn.ensemble import RandomForestClassifier
rfc = RandomForestClassifier() # 建立随机森林分类器
rfc.fit(X_train,y_train) # 训练随机森林模型
y_prob = rfc.predict_proba(X_test)[:,1] # 预测1类的概率
y_pred=rfc.predict(X_test) # 模型对测试集的预测结果
fpr_rfc,tpr_rfc,threshold_rfc = metrics.roc_curve(y_test,y_prob) # 获取真阳率、伪阳率、阈值
auc_rfc = metrics.auc(fpr_rfc,tpr_rfc) # AUC得分
score_rfc = metrics.accuracy_score(y_test,y_pred) # 模型准确率
print('模型准确率为:{0},AUC得分为:{1}'.format(score_rfc,auc_rfc))
print('============================================================')
print(classification_report(y_test,y_pred,labels=None,target_names=None,sample_weight=None, digits=2))
4.6 XGBoost
import xgboost as xgb
# 读入训练数据集和测试集
dtrain=xgb.DMatrix(X_train,y_train)
dtest=xgb.DMatrix(X_test)
# 设置xgboost建模参数
params={'booster':'gbtree','objective': 'binary:logistic','eval_metric': 'auc',
'max_depth':8,'gamma':0,'lambda':2,'subsample':0.7,'colsample_bytree':0.8,
'min_child_weight':3,'eta': 0.2,'nthread':8,'silent':1}
# 训练模型
watchlist = [(dtrain,'train')]
bst=xgb.train(params,dtrain,num_boost_round=500,evals=watchlist)
# 输入预测为正类的概率值
y_prob=bst.predict(dtest)
# 设置阈值为0.5,得到测试集的预测结果
y_pred = (y_prob >= 0.5)*1
# 获取真阳率、伪阳率、阈值
fpr_xgb,tpr_xgb,threshold_xgb = metrics.roc_curve(y_test,y_prob)
auc_xgb = metrics.auc(fpr_xgb,tpr_xgb) # AUC得分
score_xgb = metrics.accuracy_score(y_test,y_pred) # 模型准确率
print('模型准确率为:{0},AUC得分为:{1}'.format(score_xgb,auc_xgb))
print('============================================================')
print(classification_report(y_test,y_pred,labels=None,target_names=None,sample_weight=None, digits=2))
4.7 模型比较
plt.style.use('bmh')
plt.figure(figsize=(13,10))
plt.plot(fpr_lr,tpr_lr,label='lr: {0:.3f}'.format(score_lr)) # 逻辑回归
plt.plot(fpr_gnb,tpr_gnb,label='gnb:{0:.3f}'.format(score_gnb)) # 朴素贝叶斯
plt.plot(fpr_svc,tpr_svc,label='svc:{0:.3f}'.format(score_svc)) # 支持向量机
plt.plot(fpr_dtc,tpr_dtc,label='dtc:{0:.3f}'.format(score_dtc)) # 决策树
plt.plot(fpr_rfc,tpr_rfc,label='rfc:{0:.3f}'.format(score_rfc)) # 随机森林
plt.plot(fpr_rfc,tpr_rfc,label='xgb:{0:.3f}'.format(score_xgb)) # XGBoost
plt.legend(loc='lower right',prop={'size':25})
plt.xlabel('伪阳率')
plt.ylabel('真阳率')
plt.title('ROC曲线')
plt.savefig('./Photo/模型比较图.jpg',dpi=400, bbox_inches='tight')
plt.show()
4.8 重要特征
from xgboost import plot_importance
fig,ax = plt.subplots(figsize=(15,15))
plot_importance(bst,height=0.5,ax=ax,max_num_features=40,color='chocolate')
plt.savefig('./Photo/重要特征图.jpg',dpi=400, bbox_inches='tight')
plt.show()
重要的特征:24小时内是否访问订单填写页(24小时内是否访问订单填写页)、近3个月用户历史日均访问酒店数(historyvisit_avghotelnum)、当前酒店转换率(hotelcr)、当前酒店历史订单取消率(ordercanceledprecent)、星级偏好(starprefer)、用户历史取消率(cancelrate)、 7天内访问酒店详情页数(historyvisit_visit_detailpagenum)、价格敏感指数price_sensitive)、当前酒店访客量(hoteluv)、浏览最多的酒店商务属性(businessrate_pre)。
五、 RFM模型和用户画像
5.1 RFM分析
RFM模型,即为:
- R(Rencency):最近一次消费
- F(Frequency):消费频率
- M(Monetary):消费金额
选择lasthtlordergap(距离上次下单的时长)、ordernum_oneyear(用户年订单数)、consume_level(用户消费水平)分别作为R、F、M的值,对我们的用户群体进行聚类.
rfm = rawdf[['lasthtlordergap','ordernum_oneyear','consume_level']]
# 归一化
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'])
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']]
rfm0=RFM_convert(rfm)
rfm0.head(10)
temp=rfm0.groupby('Label of Customer').size()
plt.figure(figsize=(12,18))
colors=['orangered','lightsalmon','sienna','seashell','chocolate','peru','sandybrown','peachpuff']
plt.pie(temp,radius=1,autopct='%.1f%%',pctdistance=0.75,colors=colors)
plt.pie([1],radius=0.6,colors='w')
plt.title('客户细分情况')
plt.legend(temp.index)
plt.savefig('./Photo/客户细分情况.jpg',dpi=400, bbox_inches='tight')
plt.show()
- 潜在客户占比达12.3%,这类客户是rmf指标均不是很好的客户,有待开发;
- 高价值客户11%,重点保持客户10.1%,重点发展客户7%,这是要重点关注的客户群体.
5.2 用户画像
# 选取出几个刻画用户的重要指标
user_feature = ['decisionhabit_user','ordercanncelednum','ordercanceledprecent','consume_level','starprefer','lasthtlordergap','lastpvgap','h','sid',
'c_value','landhalfhours','price_sensitive','price_prefer','day_advanced','historyvisit_avghotelnum','ordernum_oneyear']
user_attributes = rawdf[user_feature]
user_attributes.head()
# 数据标准化
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
scaler.fit(user_attributes)
user_attributes = scaler.transform(user_attributes)
from sklearn.cluster import KMeans
Kmeans=KMeans(n_clusters=3) # 建立KMean模型
Kmeans.fit(user_attributes) # 训练模型
k_char=Kmeans.cluster_centers_ # 得到每个分类的质心
personas=pd.DataFrame(k_char.T,index=user_feature,columns=['0类','1类','2类']) # 用户画像表
plt.figure(figsize=(5,10))
sns.heatmap(personas, xticklabels=True, yticklabels=True, square=False, linewidths=.5, annot=True, cmap='Oranges')
plt.savefig('./Photo/用户画像表.jpg',dpi=400, bbox_inches='tight')
plt.show()
2类用户的R(lasthtlordergap)为-0.17非常小(R越小越好,这里是反的),F(ordernum_oneyear)为1.1比较高了,M(consume_level)为1.3也几乎是最高的.很明显,2类客户为我们的"高价值客户";而0类中几乎都是白格子,无论是客户价值还是消费水平值都是最低的,很明显,这一类我们将其归为"低价值客户";剩下的1类我们将其称为"中等群体".
plt.figure(figsize=(9,9))
class_k=list(Kmeans.labels_) # 每个类别的用户个数
percent=[class_k.count(1)/len(user_attributes),class_k.count(0)/len(user_attributes),class_k.count(2)/len(user_attributes)] # 每个类别用户个数占比
fig, ax = plt.subplots(figsize=(10,10))
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.savefig('./Photo/用户画像.jpg',dpi=400, bbox_inches='tight')
plt.show()
5.3 用户画像分析
5.3.1 高价值用户分析
消费水平高,客户价值大,追求高品质,对酒店星级要求高,访问频率和预定频率都较高,提前预定的时间都较短,决策一般都较快(日均访问数少),订单取消率较高,可以分析出这类客户商务属性偏重,可能随时要出差,因此都不会提前预定,可能出差随时会取消,因此酒店取消率也会更高一点。sid的值较大,说明高价值客户群体多集中在老客户中。价格敏感度较高,说明可能比较要求性价比。h值非常小,可能访问和预定时间多在半夜或是清晨。
这部分客户对于我们而言是非常重要的,因此我们需要对其实施个性化的营销:
- 1、为客户提供更多差旅地酒店信息;
- 2、多推荐口碑好、性价比高的商务酒店连锁酒店房源吸引用户;
- 3、在非工作日的11点、17点等日间流量小高峰时段进行消息推送。
5.3.2 中等群体分析
消费水平和客户价值都偏低,对酒店品质也不太追求,访问和预定频率也都较高,提前预定的时间是三类中最长的,最值得注意的是,0类客户中有两个颜色非常深的蓝色格子,是用户决策和近3个月的日均访问数。可以看出,这类客户通常很喜欢逛酒店界面,在决定要订哪家酒店前通常会花费非常多的时间进行浏览才能做出选择,并且一般都会提前很久订好房。我们可以给这类客户打上“谨慎”的标签。我们可以合理推断,这一类客户,可能预定酒店的目的多为出门旅行。
针对这部分客户,我们需要:
- 1、在节假日前两、三星期定期推送国外高星级酒店,尽可能多地进行推送,因为此类客户通常比较喜欢浏览;
- 2、推送高端酒店以及当地的旅行资讯,吸引用户关注,因为这类客户旅游出行的概率较大;
- 3、和景区酒店代理商合作,针对此类用户制定个性化推荐,多推荐价格相对实惠的酒店。
5.3.3 低价值用户分析
消费水平和客户价值极低,对酒店品质不追求,偏好价格较低,决策时间很短,访问和预定频率很低,sid值很低,说明新客户居多。
针对这部分客户,我们需要:
- 1、不建议花费过多营销成本,但因为新用户居多,属于潜在客户,建议把握用户初期体验(如初期消费有优惠、打卡活动等),还可以定期推送实惠的酒店给此类用户,以培养客户消费惯性为主;
- 2、推送的内容应多为大减价、大酬宾、跳楼价之类的;
- 3、由于这部分用户占比较多,可结合该群体流失情况分析流失客户因素,进行该群体市场的开拓,进一步进行下沉分析,开拓新的时长。