携程作为中国领先的综合性旅行服务公司,每天向超过2.5亿会员提供全方位的旅行服务,在这海量的网站访问量中,我们可分析用户的行为数据来挖掘潜在的信息资源。其中,客户流失率是考量业务成绩的一个非常关键的指标。此次竞赛的目的是为了深入了解用户画像及行为偏好,找到最优算法,挖掘出影响用户流失的关键因素,从而更好地完善产品设计、提升用户体验!
一、数据来源及说明
此次使用训练集数据userlostprob_train.txt,为2016.05.15-2016.05.21期间一周的访问数据。为保护客户隐私,已经将数据经过了脱敏,和实际商品的订单量、浏览量、转化率等有一些差距,但不影响问题的可解性。
将特征信息整理分为三组:订单、客户行为、酒店,相关说明如下:
image.png
二、数据导入及数据探索
import numpy as np
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
from sklearn.decomposition import PCA
%matplotlib inline
df = pd.read_csv(r"./userlostprob_train.txt",sep="\t",encoding="UTF-8")
df.head()
image.png
查看数据类型:df.info()
d和arrival字段需要处理
image.png
查看行列统计:df.shape
(689945, 51)
有689945行数据,51个字段
查看缺失值占比:df.isna.mean()
数据的缺失情况比较严重,有44列存在不同程度的缺失,最多的特征列缺失比例接近88%
image.png
查看标记统计:df['label'].value_counts()
0代表用户没有流失,1为已经流失
image.png
查看描述统计:df.describe().T
1.有些特征列存在极值的情况,后面在进行缺失值填充的时候需要注意分布形态;
2.极值需要处理,减小异常点对预测的影响;
3.有些特征存在负值,需要进行处理
image.png
三、特征工程
3.1 数据预处理
3.1.1 离散型数据处理
将访问时间与到达时间相减得到提前预定天数作为新的特征
然后将无用特征删除
df['arrival']=pd.to_datetime(df['arrival'])
df['d']=pd.to_datetime(df['d'])
df['day_advanced']=(df['arrival']-df['d']).dt.days
df.drop(['firstorder_bu','sampleid','d','arrival'],axis=1,inplace=True)
3.1.2 异常值处理
将customer_value_profit、ctrip_profits中的负值按0处理
将delta_price1、delta_price2、lowestprice中的负值按中位数处理
fillAbnormalWith0 = ['customer_value_profit','ctrip_profits']
fillAbnormalWithMedian = ['delta_price1','delta_price2','lowestprice']
for col in fillAbnormalWith0:
df.loc[df[col]<0,col] = 0
for col in fillAbnormalWithMedian:
df.loc[df[col]<0,col] = df[col].median()
3.1.3 缺失值处理
3.1.3.1 删除缺失值大于80%的特征
数据缺失值过多,信息失真,故而删除
df.drop(['historyvisit_7ordernum'],axis=1,inplace=True)
3.1.3.2 缺失率>40%的特征列 填充为0
为了保留原有特征包含的信息,对缺失值较多的特征列没有采用删除的手段,而是采取了将缺失值填充为0的措施。
fillNaWith0 = ['historyvisit_totalordernum', #近1年用户历史订单数
'historyvisit_avghotelnum', #近3个月用户历史日均访问酒店数
'ordercanceledprecent', #用户一年内取消订单率
'historyvisit_visit_detailpagenum'] # 7天内访问酒店详情页数
for col in fillNaWith0:
df.loc[df[col].isna(),col] = 0
3.1.3.3 缺失率>30%的特征列 填充为-999
缺失率大于30%仍属于缺失值比较高的情况,为了减弱填充值对原有信息的影响,这里选择填充没有实际含义的高维极值-999。
fillNaWith999 = ['ordercanncelednum', # 取消订单数
'starprefer', # 星级偏好
"consuming_capacity", # 消费能力指数
'delta_price1', # 用户偏好价格-24小时浏览最多酒店价格
'price_sensitive', # 价格敏感指数
'ordernum_oneyear', # 年订单数
'avgprice', # 平均价格
'delta_price2', # 用户偏好价格-24小时浏览酒店平均价格
'customer_value_profit', # 客户近一年的价值
'ctrip_profits', # 客户价值
'lasthtlordergap', # 一年内距离上次下单时长
'cr' ] # 用户转化率
for col in fillNaWith999:
df.loc[df[col].isna(),col] = -999
3.1.3.4 缺失率<30%的特征列 填充为均值或中位数
缺失值小于30%的特征列根据其分布形态不同选择填充不同的值,
分布近似于正态分布的特征列填充为均值,呈偏态分布的特征则填充为中位数。
fillNaWithMedian =['landhalfhours', # 24小时登陆时长
'lastpvgap', # 一年内距上次访问时长
'commentnums', # 酒店评论数
'novoters', # 酒店当前评论人数
'cancelrate', # 当前酒店历史取消率
'hoteluv', # 当前酒店历史UV
'hotelcr', # 当前酒店历史转化率
'cr_pre', # 24小时历史浏览次数最多酒店历史cr
'lowestprice', # 当前酒店可定最低价
'lowestprice_pre2', # 24h 访问酒店可预定最低价
'commentnums_pre', # 24小时历史浏览次数最多酒店点评数
'commentnums_pre2', # 24小时历史浏览酒店点评数均值
'novoters_pre2', # 24小时历史浏览酒店评分人数均值
'novoters_pre', # 24小时历史浏览次数最多酒店评分人数
'deltaprice_pre2_t1', # 24小时内已访问酒店价格与对手价差均值
'lowestprice_pre', # 24小时内已访问次数最多酒店可订最低价
'uv_pre', # 24小时历史浏览次数最多酒店历史uv
'uv_pre2', # 24小时历史浏览酒店历史uv均值
'businessrate_pre2', # 24小时内已访问酒店商务属性指数均值
'cityuvs', # 昨日访问当前城市同入住日期的app uv数
'cityorders', # 昨日提交当前城市同入住日期的app订单数
'visitnum_oneyear' ] # 年访问次数
fillNaWithMean = [ 'businessrate_pre', # 24小时历史浏览次数最多酒店商务属性指数
'customereval_pre2', # 24小时历史浏览酒店客户评分均值
'cancelrate_pre'] # 24小时内已访问次数最多酒店历史取消率
for col in fillNaWithMedian:
df.loc[df[col].isna(),col] = df[col].median()
for col in fillNaWithMean:
df.loc[df[col].isna(),col] = df[col].mean()
3.1.4 极值处理
有些特征明显有异常大和异常小的值,这里使用99%分位数替换。
for i in df.columns:
df.loc[df[i]>np.percentile(df[i],99),i]=np.percentile(df[i],99)
3.1.5 特殊列处理
'decisionhabit_user'用户决策习惯列缺失值也比较多,但是绝大部分值位于40以下,可以40为分界点,将特征列转换为二值形式
df['decisionhabit_user'] = df['decisionhabit_user'].map(lambda x:1 if x>40 else 0)
3.2 相关性分析
3.2.1 按照用户特征和酒店特征分组
# 用户特征
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']
# 酒店特征
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_user = df[user_features].corr()
# 生成用户特征的相关性矩阵
corr_hotel = rawdf[hotel_features].corr()
3.2.2 绘制热力图
3.2.2.1用户信息特征的相关性矩阵热度图
fig,ax = plt.subplots(figsize=(18, 12))
sns.heatmap(corr_user, xticklabels=True, yticklabels=True, square=False, linewidths=.5, annot=True, cmap='Oranges')
plt.savefig('./Photo/用户特征的相关性分析.jpg',dpi=400, bbox_inches='tight')
plt.show()
image.png
3.2.2.2 酒店信息特征的相关性矩阵热度图
fig,ax = plt.subplots(figsize=(18, 12))
sns.heatmap(corr_hotel, xticklabels=True, yticklabels=True, square=False, linewidths=.5, annot=True, cmap='Oranges_r')
plt.savefig('./Photo/酒店信息特征的相关性分析.jpg',dpi=400, bbox_inches='tight')
plt.show()
image.png
3.3 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小时内浏览酒店的平均热度
print('PCA降维前数据维度是:{}'.format(df.shape))
pca=PCA(n_components=1)
df['c_value']=pca.fit_transform(df[c_value])
df['consume_level']=pca.fit_transform(df[consume_level])
df['price_prefer']=pca.fit_transform(df[price_prefer])
df['hotel_hot']=pca.fit_transform(df[hotel_hot])
df['hotel_hot_pre']=pca.fit_transform(df[hotel_hot_pre])
df['hotel_hot_pre2']=pca.fit_transform(df[hotel_hot_pre2])
df.drop(c_value,axis=1,inplace=True)
df.drop(consume_level,axis=1,inplace=True)
df.drop(price_prefer,axis=1,inplace=True)
df.drop(hotel_hot,axis=1,inplace=True)
df.drop(hotel_hot_pre,axis=1,inplace=True)
df.drop(hotel_hot_pre2,axis=1,inplace=True)
df.drop('historyvisit_totalordernum',axis=1,inplace=True) # 把重复列删除
print('PCA降维后数据维度是:{}'.format(df.shape))
PCA降维前数据维度是:(689945, 48)
PCA降维后数据维度是:(689945, 41)
3.4 数据标准化
from sklearn.preprocessing import StandardScaler
y=df['label']
x=df.drop('label',axis=1)
scaler = StandardScaler()
scaler.fit(x)
X= scaler.transform(x)
四、建模与模型评估
4.1 数据划分
# 导入相关模块
from sklearn.model_selection import GridSearchCV
from sklearn.model_selection import train_test_split
X_train,X_test,y_train,y_test = train_test_split(X,y,test_size= 0.2,random_state=2020)
4.2 逻辑回归
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))
image.png
4.3 朴素贝叶斯
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))
image.png
4.4 SVM
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))
image.png
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))
image.png
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))
image.png
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))
image.png
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()
image.png
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()
image.png
后期可以交由运营部门重点监控这些指标。