用户流失预警项目总结

本文是对携程用户流失预测案例的一个总结,主要任务是对客户流失率进行建模分析,并挖掘出影响用户流失的关键因素

目录:

  • 项目介绍

  • 问题分析

  • 数据探索

  • 特征工程

  • 建模分析

  • 总结

一、项目介绍

携程作为中国领先的综合性旅行服务公司,每天向超过2.5亿会员提供全方位的旅行服务,在这海量的网站访问量中,我们可分析用户的行为数据来挖掘潜在的信息资源。其中,客户流失率是考量业务成绩的一个非常关键的指标。此次分析的目的是为了深入了解用户画像及行为偏好,找到最优算法,挖掘出影响用户流失的关键因素,从而更好地完善产品设计、提升用户体验。(项目及数据来源

二、问题分析

这个项目要解决的问题是关于用户流失的,在官方提供的字段和解释中,有一个label字段,这个是目标变量,也就是我们需要进行预测的值。label=1代表客户流失,label=0代表客户未流失,很显然这是个分类的预测问题。

对于本项目而言,最终的评价标准是要求在精确度达到97%的情况下,最大化召回率

从业务角度理解这个评价标准,携程作为一个大平台,用户量非常大,挽回用户所需成本较大,所以需要有一个很高的精确率来降低不必要的成本浪费,同时也要尽可能的挽回流失客户,所以需要尽可能高的召回率。

这里引申一下其他比赛常用的标准:如无特别说明,一般用的是PR曲线和ROC曲线。ROC曲线有一个突出优势,就是不受样本不均衡的影响(ROC曲线不受样本不均衡问题的影响

三、数据探索

1、数据总体情况

官方共提供2个数据集,分别为训练集userlostprob_train.txt和测试集userlostprob_test.txt。训练集为2016.05.15-2016.05.21期间一周的访问数据,测试集为2016.05.22-2016.05.28期间一周的访问数据。测试集不提供目标变量label,需自行预测。为保护客户隐私,不提供uid等信息。此外,数据经过了脱敏,和实际商品的订单量、浏览量、转化率等有一些差距,但是不会影响这个问题的可解性。

数据共有51个字段,除了目标变量label,还有50个特征。

训练集数据总览

目标变量label存在一定程度的不均衡,但是程度不大,因此可以用PR曲线做模型性能评估。

label值统计

观察数据集,并进行指标梳理。指标可以分为三类,一类是订单相关的指标,如入住日期、订单数、取消率等;一类是与客户行为相关的指标,如星级偏好、用户偏好价格等;还有一类是与酒店相关的指标,如酒店评分均值、酒店评分人数、平均价格等。

字段指标

这个数据集有大量的缺失值分布在各个特征中。51列中只有7列数据是完整的,arrival、sampleid、d、h、iforderpv_24h、sid、label。其他44列存在不同程度的缺失,其中historyvisit_7ordernum缺失率接近88%。后面会根据缺失情况,结合特征分布,选用合适的方法填充缺失值。

#缺失比例计算
na_rate = (len(df)-df.count())/len(df)
na_rate.sort_values(ascending=False,inplace=True)
x = df.shape[1] #用x代表数据列数

#作出各列缺失比例的条形图
a1 = pd.DataFrame(na_rate)
fig = plt.figure(figsize=(8,12))#图形大小,单位为英寸(1英寸=2.5cm)
plt.barh(range(x),a1[0], color= 'orange', alpha = 1)

# 添加轴标签
plt.xlabel('data_na_rate')

# 添加刻度标签
columns1=a1.index.values.tolist() # 列名称
plt.yticks(range(x),columns1)
for x,y in enumerate(a1[0]):
    plt.text(y,x,'{}{}'.format(round(y*100,2),'%'),va='center')

#设置X轴的刻度范围
plt.xlim([0, 1])

各列缺失率

2、各个特征的分布
查看所有数值型特征的分布情况,一方面有利于特征工程中根据数据分布合理选用处理方法,包括异常值、缺失值处理,连续特征离散化处理;另一方面有助于深入了解用户行为。

for i in range(0,50):
    plt.hist(df[df.columns[i]].dropna().get_values(),bins=30)
    plt.xlabel(df.columns[i])
    plt.show()

businessrate_pre

businessrate_pre2

cancelrate_pre

四、特征工程
数据和特征决定了机器学习效果的上限,而模型和算法只是逼近这个上限。特征工程是建模前的关键步骤,特征处理得好,可以提升模型的性能。
整个特征工程的任务主要包括:格式转换、缺失值处理、衍生特征、聚类特征、独热编码、标准化等。
1、时间特征处理

1)格式转换
时间特征不存在缺失值,可以先处理。访问日期d和入住日期arrival是字符串格式,需要进行格式转换。这里使用pandas中常用的时间函数to_datetime(),将字符串格式转换为日期格式。

df['d'] = pd.to_datetime(df['d'], format = '%Y-%m-%d')
#df['d'] = df['d'].astype('datetime64[D]')
df['arrival'] = pd.to_datetime(df['arrival'], format='%Y-%m-%d')

2)衍生特征
衍生特征是根据现有特征衍生出来的一些特征,比如访问日期和实际入住日期之间的差值,入住日期是周几,入住日期否为周末。在机器学习中,是否为周末这个特征往往是非常重要的。

df['week2day'] = df['arrival'].map(lambda x: x.weekday())]
#查看用户入住的日期是否为周末
def is_weekend(a):
    if int(a) in [0,1,2,3,4]:
        return 0 #0代表是工作日
    else:
        return 1 #1代表是周末
df['is_weekend'] = df['week2day'].apply(is_weekend)
#查看用户预定的与实际入住之间相隔的天数
df['booking_gap'] = (df['arrival'] -df['d']).map(lambda x: x.days).astype(int)

2、异常值处理
观察到用户偏好价格delta_price1、delta_price2,以及当前酒店可订最低价lowestprice存在一些负值,理论上酒店的价格不可能为负。同时数据分布比较集中,因此采取中位数填充。而客户价值customer_value_profit、ctrip_profits也不应该为负值,这里将其填充为0。deltaprice_pre2_t1是酒店价格与对手价差均值,可以为负值,无需处理。

# 查看最小值为负值的特征
df_min=df.min().iloc[4:]
df_min[df_min<0]
存在负值的列
neg1=['delta_price1','delta_price2','lowestprice']
neg2=['customer_value_profit','ctrip_profits']
for col in neg1:
    df.loc[df[col]<0,col] = df[col].median()
for col in neg2:
    df.loc[df[col]<0,col] = 0

3、缺失值处理
缺失值全部为数值型数据,结合各个特征的含义及数据分布情况,进行以下处理:
1)针对一些不可预计的数据用-999填充缺失值

#针对不可预计的数据用-999填充NA
fillNauWith999 = ['ordercanncelednum',  # 取消订单数 242114
                  'landhalfhours',  # 24小时登陆时长 28633
                  'starprefer',  # 星级偏好 225053
                  "consuming_capacity",  # 消费能力指数 226108
                  'historyvisit_avghotelnum',  # 近3个月用户历史日均访问酒店数 302069
                  'delta_price1',  # 用户偏好价格-24小时浏览最多酒店价格
                  'ordernum_oneyear',  # 年订单数
                  'avgprice',  # 平均价格
                  'delta_price2',  # 用户偏好价格-24小时浏览酒店平均价格
                  'customer_value_profit',  # 客户近一年的价值
                  'ctrip_profits',  # 客户价值
                  'lasthtlordergap',  # 一年内距离上次下单时长 缺失值占242114条记录
                  'lastpvgap',  # 一年内距上次访问时长 缺失值共97127记录
                  'cr',  # 用户转化率
                  'decisionhabit_user' #用户决策习惯
                  ]

2)忽略两端极值的影响,可以把businessrate_pre、businessrate_pre2、cancelrate_pre等一些特征近似看作正态分布,使用平均值填充缺失值。

#正态分布使用平均值填充
fillNauWithMean = ['commentnums',  # 酒店评论数
                   'novoters',  # 酒店当前评论人数
                   'cancelrate',  # 当前酒店历史取消率 11718
                   'price_sensitive',  # 价格敏感指数
                   'hoteluv',  # 当前酒店历史UV
                   'hotelcr',  # 当前酒店历史转化率
                   'cr_pre',  # 24小时历史浏览次数最多酒店历史cr 29397
                   'lowestprice',  # 当前酒店可定最低价
                   'lowestprice_pre2',  # 24h 访问酒店可预定最低价
                   'customereval_pre2',  # 24小时历史浏览酒店客户评分均值 28633条记录缺失
                   'commentnums_pre',  # 24小时历史浏览次数最多酒店点评数
                   'commentnums_pre2',  # 24小时历史浏览酒店点评数均值
                   'cancelrate_pre',  # 24小时内已访问次数最多酒店历史取消率
                   'novoters_pre2',  # 24小时历史浏览酒店评分人数均值
                   'novoters_pre',  # 24小时历史浏览次数最多酒店评分人数
                   'deltaprice_pre2_t1',  # 24小时内已访问酒店价格与对手价差均值
                   'lowestprice_pre',  # 24小时内已访问次数最多酒店可订最低价
                   'uv_pre',  # 24小时历史浏览次数最多酒店历史uv
                   'uv_pre2',  # 24小时历史浏览酒店历史uv均值
                   'businessrate_pre',  # 24小时历史浏览次数最多酒店商务属性指数
                   'businessrate_pre2',  # 24小时内已访问酒店商务属性指数均值
                   'cityuvs',  # 昨日访问当前城市同入住日期的app uv数
                   'cityorders',  # 昨日提交当前城市同入住日期的app订单数
                   'visitnum_oneyear'  # 年访问次数
                     ]

3)对于以下4个特征值,系统填充NA时一般是因为不存在数据,所以直接使用0填充缺失值

#这部分数据系统填充NA是因为不存在数据,所以可以直接用0填充NA
fillfeatureswith0 = ['historyvisit_7ordernum', #近7天用户历史订单数
                  'historyvisit_totalordernum', #近1年用户历史订单数
                  'ordercanceledprecent', #用户一年内取消订单率
                  'historyvisit_visit_detailpagenum'  # 7天内访问酒店详情页数
                       ]

4)对于'firstorder_bu'第一次使用的客户,不存在流失的情况,所以将这一列丢弃

def missingvalue(data):
    for col in fillNauWith999:
        data[col] = data[col].fillna(-999)
    for col in fillNauWithMean:
        fillvalue = data[col].mean()
        data[col] = data[col].fillna(fillvalue)
    for col in fillfeatureswith0:
        data[col] = data[col].fillna(0)   
    return data
missingvalue(df)
df = df.drop(['firstorder_bu'],axis=1)

经过以上处理后,df.info()如下,可以看到已经没有缺失的列了:

填充缺失值后的df.info()

4、聚类特征
整个数据集中非常重要的两部分信息,一个是用户相关的数据,一个是酒店相关的数据。因此把这两类主体进行一个聚类,并把类的标签作为一个新的特征。这里使用KMeans的方法做聚类处理,分别将用户和酒店分成5个类别。

#标准化
ss = StandardScaler()
#用户聚类
user_group = df[['historyvisit_7ordernum','historyvisit_totalordernum','ordercanceledprecent','historyvisit_visit_detailpagenum','historyvisit_avghotelnum','lowestprice_pre']]
for i in range(len(user_group.columns)):
   user_group[user_group.columns[i]] = ss.fit_transform(user_group[user_group.columns[i]].values.reshape(-1,1))
#酒店聚类
hotel_group = df[['commentnums','novoters','cancelrate','hoteluv','hotelcr','lowestprice']]
for i in range(len(hotel_group.columns)):
   hotel_group[hotel_group.columns[i]] = ss.fit_transform(hotel_group[hotel_group.columns[i]].values.reshape(-1,1))
#K-means方法分五类,并将标签作为一个新的特征
df['user_type'] = KMeans(n_clusters=5, init='k-means++').fit_predict(user_group)
df['hotel_type'] = KMeans(n_clusters=5, init='k-means++').fit_predict(hotel_group)

5、连续特征离散化
在这个案例中,将某些数值型特征转换成类别呈现更有意义,比如用户决策习惯、星级偏好、平均价格、消费能力指数等,同一类别表现出相似的属性。同时可以使得算法减少噪声的干扰。而且在机器学习中,一般很少直接将连续值作为逻辑回归模型的特征输入。特征离散化以后,可以简化逻辑回归模型,降低了模型过拟合的风险。后面会用到逻辑回归模型,所以在这里还是先做离散化处理。
根据业务经验选择合适的连续型特征,在一定的数值范围内划分分区。

def deal_decisionhabit_user(x):
   if x==-999:
       return 0
   elif x<10:
       return 1
   elif x<30:
       return 2
   else:
       return 3
def deal_starprefer(x):
   if x==-999:
       return 0
   elif x<50:
       return 1
   elif x<80:
       return 2
   else:
       return 3
def deal_avgprice(x):
   if  x==-999:
       return 0
   elif x< 300:
       return 1
   elif x<1000:
       return 2
   else:
       return 3
def deal_consuming_capacity(x):
   if  x==-999:
       return 0
   elif x< 50:
       return 1
   else:
       return 2

离散化之后的特征,以及酒店和用户这两个聚类特征,均为数值型,都需要转换为字符串型,以便接下来进行独热编码:

df['decisionhabit_user']=df['decisionhabit_user'].map(lambda x:str(deal_decisionhabit_user(int(x))))
df["starprefer"] = df["starprefer"].map(lambda x:str(deal_starprefer(int(x))))
df["consuming_capacity"] = df["consuming_capacity"].map(lambda x: str(deal_consuming_capacity(int(x))))
df['avgprice'] = df['avgprice'].map(lambda x: str(deal_avgprice(int(x))))
df[["user_type","hotel_type"]]=df[["user_type","hotel_type"]].applymap(str)

6、分类变量one-hot-encode
对分类变量进行独热编码,可以解决分类器不好处理属性数据的问题,编码后的特征都可以看做是连续的特征,并且在一定程度上也起到了扩充特征的作用。

enc = OneHotEncoder(handle_unknown='ignore')
enc.fit(df[['starprefer','consuming_capacity','avgprice','decisionhabit_user','user_type','hotel_type']])
a = enc.transform(df[['starprefer','consuming_capacity','avgprice','decisionhabit_user','user_type','hotel_type']]).toarray()
df = pd.concat([df,pd.DataFrame(a)], axis=1)
df = df.drop(['starprefer','consuming_capacity','avgprice','decisionhabit_user','user_type','hotel_type'],axis=1)

7、用户分组特征
由于数据集没有提供用户uid,需要根据已有特征对用户进行分组,生成用户标签usertag。这里采取了一种近似的方法,如果用户的某些行为特征相同,则认为是同一个用户的行为。后面需要根据用户标签分割数据集,同一个用户的信息不能同时出现在训练集和测试集中,否则模型会过拟合。
这里用于判断是否为同一用户行为的特征有:用户一年内取消订单数、近3个月用户历史日均访问酒店数、用户年订单数、客户价值_近1年、客户价值、用户转化率、年访问次数,并且使用hash函数处理字符串。

df['usertag']= df.ordercanncelednum.map(str) + df.historyvisit_avghotelnum.map(str) + df.ordernum_oneyear.map(str) + df.customer_value_profit.map(str) + df.ctrip_profits.map(str) + df.cr.map(str) + df.visitnum_oneyear.map(str)
df.usertag = df.usertag.map(lambda x: hash(x)) #生成哈希值

8、特征的相关系数
查看各特征与label之间的关系,并除去相关系数小于0.01的特征

processeddata = df.groupby('usertag').max()
corrdf = processeddata.corr()
delete_columns = []
for i in range(corrdf.shape[0]):
    if abs(corrdf.iloc[0,i]) < 0.01:
        delete_columns.append(processeddata.columns[i])
processeddata.drop(delete_columns,axis=1,inplace=True)

9、标准化处理
对于一些基于距离的模型,需要标准化处理,比如回归分析、神经网络、SVM。
而对于与距离计算无关的树模型,不需要标准化处理,比如决策树、随机森林等,因为树中节点的选择只关注当前特征在哪里切分对分类更好,即只在意特征内部的相对大小,而与特征间的相对大小无关。
这里还是标准化处理下,后面会用到不同的模型做对比。

#将label,usertag列除开进行标准化
df1 = pd.DataFrame(df_drop['label']) 
df2 = df_drop.iloc[:,1:-1] #需要标准化的列
df3 = pd.DataFrame(df_drop['usertag'])
df2_columns = df2.columns.tolist() #将df2的列名提取出来保存
scaler = preprocessing.StandardScaler().fit(df2)
df2 = scaler.transform(df2)
df2 = pd.DataFrame(df2,columns=df2_columns) #标准化处理后的数据是array,转换为DataFrame
df_new = pd.concat([df1,df2,df3],axis=1) 

10、分割数据集
在使用数据集训练模型之前,我们需要先将整个数据集分为训练集、验证集、测试集。训练集是用来训练模型的,通过尝试不同的方法和思路使用训练集来训练不同的模型,再通过验证集使用交叉验证来挑选最优的模型,通过不断的迭代来改善模型在验证集上的性能,最后再通过测试集来评估模型的性能。
由于官方提供的数据已经划分好训练集和测试集,我们现在需要在原始训练集中划分出训练集和验证集,这里是70%划分为训练集,30%划分为验证集
那究竟依据什么特性进行划分呢?划分数据集需注意时间性、地域性、层次性(stratifiedKFold)。在做本地数据集划分的时候需要基于用户进行划分,也就是要保证划分前后的数据是满足独立同分布的。另外,由于提供的是一周的数据,时间序列特性不是很明显,所以没有按时间线对数据进行划分

def splitTrainTest(dataProcessed,percent=0.7): 
    splitnum=int(len(dataProcessed.index)* percent) #分割点:70%
    dataProcessed.sort_values(by="usertag") #按照'usertafe'进行排序
    dataProcessed.to_csv(r'F:\solo\processed.csv',sep=',',index=False)
    #前70%行生成训练集
    dataProcessed.iloc[:splitnum,].to_csv(r'F:\solo\processed_Train.csv', sep=',', index=False)
    #后30%行生成验证集
    dataProcessed.iloc[splitnum:, ].to_csv(r'F:\solo\processed_Test.csv', sep=',', index=False)
splitTrainTest(df_new,percent=0.7)

五、建模分析
对于一个分类问题,一般经常使用的模型有逻辑回归、随机森林、xgboost。在正常的情况下,xgboost会比随机森林效果更好,但是如果数据的噪声比较大的话,也会出现随机森林的效果更好的情况。为了比较不同模型在这个分类问题中的性能表现,这里使用了三个模型分别训练和评估。
导入包,使用sklearn库完成建模分析。

from sklearn.metrics import precision_recall_curve
from sklearn.metrics import accuracy_score
from sklearn import metrics
from sklearn import cross_validation
from sklearn.model_selection import GridSearchCV
from sklearn.feature_selection import SelectFromModel

导入处理后的数据集,并删除usertag标签。

train=open(r'F:\solo\processed_Train.csv')
test=open(r'F:\solo\processed_Test.csv')
trainData = pd.read_csv(train,sep=',').drop(["usertag"],axis=1)
testData = pd.read_csv(test,sep=',').drop(["usertag"],axis=1)

从训练集和测试集中分别提取特征和目标变量label,训练模型后,用测试集评估模型的性能。

train_X = trainData.iloc[:,1:]  # 特征从第1列开始选
train_Y = trainData.iloc[:,0]  # 第0列是label
test_X = testData.iloc[:,1:]
test_Y = testData.iloc[:,0]

1、逻辑回归模型

1)导入模型

from sklearn import linear_model
from sklearn.linear_model import LogisticRegression

(2)模型性能评估
输出准确率accuracy、AUC面积以及精确度precision≥0.97条件下的最大召回率recall。

lr = LogisticRegression()
lr.fit(train_X,train_Y)  # 训练模型
test_pred_lr = lr.predict_proba(test_X)[:,1]  # 预测为1的可能性
fpr_lr,tpr_lr,threshold = metrics.roc_curve(test_Y,test_pred_lr)
auc = metrics.auc(fpr_lr,tpr_lr)
score = metrics.accuracy_score(test_Y,lr.predict(test_X))  # 输入真实值和预测值
print([score,auc])  # 准确率、AUC面积
precision_lr, recall_lr, thresholds = precision_recall_curve(test_Y, test_pred_lr)
pr_lr = pd.DataFrame({"precision": precision_lr, "recall": recall_lr})
prc_lr = pr_lr[pr_lr.precision >= 0.97].recall.max()
print(prc_lr)  # 精确度≥0.97条件下的最大召回率

逻辑回归模型输出

逻辑回归模型过于简单,预测准确率比较低,在precision≥0.97的情况下,最大recall仅为0.0001。
2、随机森林模型

1)导入随机森林分类器

from sklearn.ensemble import RandomForestClassifier

(2)模型性能评估
输出准确率accuracy、AUC面积以及精确度precision≥0.97条件下的最大召回率recall。

rfc = RandomForestClassifier(n_estimators=200) #迭代200次
rfc.fit(train_X,train_Y)  # 训练模型
test_pred_rfc = rfc.predict_proba(test_X)[:,1]  # 预测为1的可能性
fpr_rfc,tpr_rfc,thre_rfchold = metrics.roc_curve(test_Y,test_pred_rfc)
auc = metrics.auc(fpr_rfc,tpr_rfc)
score = metrics.accuracy_score(test_Y,rfc.predict(test_X))  # 输入真实值和预测值
print([score,auc])  # 准确率、AUC面积
precision_rfc, recall_rfc, thresholds = precision_recall_curve(test_Y, test_pred_rfc)
pr_rfc = pd.DataFrame({"precision": precision_rfc, "recall": recall_rfc})
prc_rfc = pr_rfc[pr_rfc.precision >= 0.97].recall.max()
print(prc_rfc)  # 精确度≥0.97条件下的最大召回率

随机森林输出

对于这个项目,随机森林模型表现较好,迭代200次以后模型准确率0.900,在precision≥0.97的情况下,最大recall已经可以达到0.623
3)特征重要性
使用feature_importance方法,可以得到特征的重要性排序。当然,还可以使用plot_importance方法,默认的importance_type=“weight”,将其设置为“gain”,可以得到和feature_importance方法相同的结果。

#特征重要性
importance = rfc.feature_importances_
indices = np.argsort(importance)[::-1]  # np.argsort()返回数值升序排列的索引,[::-1]表示倒序
features = train_X.columns
label = []
for f in range(train_X.shape[1]):
    print("%2d) %3d %20s (%.4f)" %(f+1,indices[f],features[indices[f]], importance[indices[f]]))
    label.append(features[indices[f]])
# 作图
plt.figure(figsize=(8,13))
plt.title('Feature importance')
plt.barh(y=range(train_X.shape[1]),width=importance[indices],color='blue')
plt.yticks(range(train_X.shape[1]),label)
plt.show()

特征重要性排序

在排名前15个特征中
用户相关的指标:年访问次数、一年内距上次访问时长、访问时间点、用户转化率、一年内距离上次下单时长、提前预定时间、客户价值。
酒店相关的指标:24小时内已访问酒店商务属性指数均值、24小时历史浏览次数最多酒店历史独立访客数、24小时内已经访问酒店可订最低价均值、24小时历史浏览酒店历史独立访客户均值、24小时内已访问次数最多酒店可订最低价
城市相关的指标:昨日访问当前城市同入住日期的app uv数字、昨日提交当前城市同入住日期的app订单数
3、xgboost模型

1)导入分类器

#导入xgboost分类器
import xgboost as xgb
from xgboost.sklearn import XGBClassifier

2)模型调参
使用GridSearchCV(网格搜索)的方法调节xgboost模型的参数,主要的影响参数有树的最大深度、最小叶子节点样本权重和、惩罚项系数gamma、使用数据占比、使用特征占比。这里分步调节,分别代入param_test1,2,3来寻找最优参数。

param_test1 = {
#首要的就是调整树的深度、以及每个叶子节点的个数
'max_depth': range(3, 10, 2),
'min_child_weight': range(1, 6, 2)}

param_test2 = {
#步长
'gamma': [i / 10.0 for i in range(0, 5)]}

param_test3 = {
#colsample_bytree每棵树随机采样的列数占比
#subsample 样本随机采样的比例
'subsample': [i / 10.0 for i in range(6, 10)],
'colsample_bytree': [i / 10.0 for i in range(6, 10)]}

gsearch1 = GridSearchCV(estimator = XGBClassifier( learning_rate =0.1, n_estimators=1000, max_depth=5,
min_child_weight=1, gamma=0, subsample=0.8,  colsample_bytree=0.8,
objective= 'binary:logistic',scale_pos_weight=1, seed=27),
param_grid = param_test3,   scoring='roc_auc',n_jobs=1,iid=False, cv=5)
gsearch1.fit(train_X ,train_Y )
means = gsearch1.cv_results_['mean_test_score']
params = gsearch1.cv_results_['params']
print(means, params)
# 模型最好的分数、模型最好的参数、模型最好的评估器
print(gsearch1.best_score_ ,gsearch1.best_params_,gsearch1.best_estimator_)

(3)模型性能评估
使用上一步找到的最优参数组合,代入模型进行训练和评估。输出准确率accuracy、AUC面积以及精确度precision≥0.97条件下的最大召回率recall。

# 使用上一步找到的最优参数组合,代入模型进行训练和评估
model = XGBClassifier(base_score=0.5, booster='gbtree', colsample_bylevel=1,
               colsample_bytree=0.8, gamma=0, learning_rate=0.1, max_delta_step=0,
               max_depth=9, min_child_weight=1, missing=None, n_estimators=1000,
               objective='binary:logistic', random_state=0,reg_alpha=0, 
               reg_lambda=1, scale_pos_weight=1, seed=27, silent=True,
               subsample=0.8)
model.fit(train_X ,train_Y)  # 训练模型
test_pred_xgb = model.predict_proba(test_X)[:,1]  # 预测为1的可能性
fpr_xgb,tpr_xgb,threshold = metrics.roc_curve(test_Y,test_pred_xgb)
auc = metrics.auc(fpr_xgb,tpr_xgb)
score = metrics.accuracy_score(test_Y,model.predict(test_X))  # 输入真实值和预测值
print([score,auc])  # 准确率、AUC面积
precision_xgb, recall_xgb, thresholds = precision_recall_curve(test_Y, test_pred_xgb)
pr_xgb = pd.DataFrame({"precision": precision_xgb, "recall": recall_xgb})
prc_xgb = pr_xgb[pr_xgb.precision >= 0.97].recall.max()
print(prc_xgb)  # 精确度≥0.97条件下的最大召回率

得到的模型准确率0.898,在precision≥0.97的情况下,最大recall可以达到0.527。

xgboost模型最大召回率

(4)特征重要性
从xgboost模型也可以得到影响用户流失的特征,按照重要性排序,排名前10的特征有:
24小时内是否访问订单填写页、提前预订时间、用户转化率、近7天用户历史订单数、用户消费能力指数、用户决策习惯、用户年订单数、访问时间点、用户星级偏好、年访问次数等。
使用随机森林模型和xgboost模型得到的在top10特征差异较大,重合的特征只有3个:访问时间点、年访问次数、用户转化率。

#特征重要性
importance = model.feature_importances_
indices = np.argsort(importance)[::-1]  # np.argsort()返回数值升序排列的索引,[::-1]表示倒序
features = train_X.columns
label = []
for f in range(train_X.shape[1]):
    print("%2d) %3d %20s (%.4f)" %(f+1,indices[f],features[indices[f]], importance[indices[f]]))
    label.append(features[indices[f]])
# 作图
plt.figure(figsize=(8,13))
plt.title('Feature importance')
plt.barh(y=range(train_X.shape[1]),width=importance[indices],color='blue')
plt.yticks(range(train_X.shape[1]),label)
plt.show()

xgboost特征重要性

4、ROC曲线和PR曲线
接下来看下随机森林和xgboost模型的ROC曲线和PR曲线,综合比较模型性能。
ROC曲线

PR曲线

这两个模型的ROC曲线和PR曲线差异不大,总体而言随机森林模型比xgboost模型表现好。从评定标准来看,随机森林的召回率(0.623)比xgboost模型召回率(0.527)高一些。认为可能是因为数据缺失较多,造成了噪音比较大。

六、总结

1、特征工程
缺失值和异常值处理是关键,根据数据和模型选择是否需要独热编码和标准化,按照业务经验合理构造衍生特征和聚类特征。筛选特征的方法有很多种,比如方差、卡方值、相关系数等,这里用了树模型的特征重要性。特征工程决定了机器学习效果的上限,模型优化只能无限接近这个上限。

2、模型对比结果
使用逻辑回归、随机森林和xgboost三种模型做对比分析,按照评定标准,在精确度≥0.97的条件下,随机森林模型的性能最优,召回率可以达到0.636。该模型可以直接上线用于用户流失预测。

3、影响用户流失的关键因素
从模型表现上看,随机森林效果最优。根据特征重要性排序,提取影响用户流失的最关键因素。其中用户相关的指标有:年访问次数、访问时间点、一年内距上次访问时长、用户转化率、一年内距离上次下单时长。酒店相关的指标有:24小时内已访问酒店商务属性指数均值、24小时内已访问酒店可订最低价均值、24小时历史浏览次数最多酒店历史uv、24小时内已访问次数最多酒店可订最低价、24小时历史浏览酒店历史uv均值。城市相关的指标:昨日提交当前城市同入住日期的app订单数、昨日访问当前城市同入住日期的app uv数。

代码附件
提取码:k88c

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