第一部分:
1.行业现状
在今天产品高度同质化的阶段,市场竞争不断加剧,企业与企业之间的竞争,主要集中在对客户的争夺上。“用户就是上帝”促使众多的企业不惜代价去争夺尽可能多的新客户。
但是,在企业不惜代价发展新用户的过程中,往往会忽视已有老用户的流失情况,结果就导致出现新用户在源源不断的增加,辛苦找来的老用户却在悄然无声的流失的窘状。
如何处理客户流失问题,成为一个非常重要的课题。
那么,我们如何从数据汇总挖掘出有价值的信息,来防止客户流失呢?
因为我们此次数据分析的主题是携程酒店客户流失分析,所以,我们先来简单了解一下行业情况:
携程网曾经占据在线酒店的主要市场,但是随着美团在2017年加入,格局逐很快被打破。
从下图可以看出:
行业TOP3的用户重合率比较低,用户差异度明显。
用户与市场竞争具有很大的相关性。
2.数据概况:
本数据集合userlostprob_data.csv,为携程网2016年5月16至21日期间一周的访问数据。
本数据集共有总的数据共有689945行,49列,包含样本id,label以及47个变量特征。
考虑到保护用户隐私,数据不提供用户id,并经过了数据脱敏处理,和实际的订单、浏览量、转化率有一些差距,但是并不影响问题的可解性。
3.项目目的:
除了前面提到的挖掘出影响用户流失的关键因素,还包括预测客户的转化效果以及用K-means对用户进行画像,并针对不同的用户类别,提出可行的营销建议。
第二部分:
1.理解数据
对理解数据,了解各个字段的业务含义是首先要做的事情。
userlostprob_data.csv 里面的变量比较多,最好进行分类了解。经过研究发现,此数据集的字段可以大概分成三种类别,分别是:客户行为指标,订单相关指标,酒店相关指标。
如下图:
当然,并不是简单的一个分类就可以完全了解所有的字段的,最好是逐个字段解析,一个比较好的办法就是每个字段按照自己的理解举出例子来理解。
事实上,当我们拿到的数据,里面的字段名称是英文的,而且英文字段由单词拼接而成,这增加了阅读和理解的难度,为了降低难度,我们可以把单词分开,然后再理解。
2.查看数据
2.1 引入约定
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
2.2 导入数据
import os
os.chdir(r'/Users/Desktop/data/')
data=pd.read_table('userlostprob.txt')
2.3 查看数据
data.head()
查看统计情况
data.describe()
查看缺失情况,并统计
data_count = data.count()
na_count = len(data) - data_count
na_rate = na_count/len(data) #缺失比例
a=na_rate.sort_values(ascending=False) #排序
a1=pd.DataFrame(a)
a1
绘图查看缺失情况
#用来正常显示中文标签
plt.rcParams['font.sans-serif']=['SimHei']
x=data.shape[1]
fig = plt.figure(figsize=(8,12))#图形大小
plt.barh(range(x),a1[0], color= 'steelblue', alpha = 1)
# 添加轴标签
plt.xlabel( '数据缺失占比')
# 添加刻度标签
columns1=a1.index.values.tolist() # 列名称
plt.yticks(range(x),columns1)
#设置X轴的刻度范围
plt.xlim([ 0, 1])
# 为每个条形图添加数值标签
for x,y in enumerate(a1[0]):
plt.text(y,x,'%.3f' %y,va= 'bottom')
plt.show()
从上图可以看出缺失值的分布情况:
数据集的缺失情况比较严重,有44列存在缺失情况,其中近7天用户历史订单数:historyvisit_7ordernum缺失为88%,20列缺失值在20-55%,23列缺失值占比小于20%。
3.数据预处理
3.1 衍生变量
添加新列:提前预定=入住时间-访问时间¶
#添加新列:提前预定
data['d'] = pd.to_datetime(data['d'])
data['arrival'] = pd.to_datetime(data['arrival'])
data['Advance booking']=(data['arrival']-data['d']).dt.days
3.2 删除缺失值比例88%的列historyvisit_7ordernum
data=data.drop(["historyvisit_7ordernum"],axis=1)
3.3 过滤无用的维度
filter_feature = ['sampleid','d','arrival'] # 过滤无用的维度
features = []
for x in data.columns: # 取特征
if x not in filter_feature:
features.append(x)
data_x = data[features] #共47个特征
data_y = data['label']
3.4 异常值负数的处理
customer_value_profit、ctrip_profits替换为0
delta_price1、delta_price2、lowestprice按中位数处理:
data_x.loc[data_x.ctrip_profits<0,'ctrip_profits'] = 0
data_x.loc[data_x.customer_value_profit<0,'customer_value_profit'] = 0
data_x.loc[data_x.delta_price1<0,'delta_price1'] = data_x['delta_price1'].median()
data_x.loc[data_x.lowestprice<0,'lowestprice'] = data_x['lowestprice'].median()
data_x.loc[data_x.delta_price2<0,'delta_price2'] = data_x['delta_price2'].median()
3.5缺失值填充
查看数据分布情况
import matplotlib.pyplot as plt
for i in range(0,47):
plt.figure(figsize=(2,1),dpi=100)
plt.hist(data_x[data_x.columns[i]].dropna().get_values())
plt.xlabel(data_x.columns[i])
plt.show()
示例趋于正态分布的字段,使用均值填充:
3个字段businessrate_pre2、cancelrate_pre、businessrate_pre
data_x['businessrate_pre2']=data_x['businessrate_pre2'].fillna(data_x['businessrate_pre2'].mean())
data_x['cancelrate_pre']=data_x['cancelrate_pre'].fillna(data_x['cancelrate_pre'].mean())
data_x['businessrate_pre']=data_x['businessrate_pre'].fillna(data_x['businessrate_pre'].mean())
右偏分布的字段,使用中位数填充:
def filling(data):
for i in range(0,47):
data_x[data_x.columns[i]]=data_x[data_x.columns[i]].fillna(data[data_x.columns[i]].median())
return data_x
filling(data_x)
3.6 检查缺失值填充情况
#检验填充缺失20%以下后缺失情况,并统计
data_count2 = data_x.count()
na_count2 = len(data_x) - data_count2
na_rate2 = na_count2/len(data_x) #缺失比例
aa2=na_rate2.sort_values(ascending=False) #排序
a3=pd.DataFrame(aa2)
a3
查看可知,缺失值数据已填充完毕。
3.7 极值处理
盖帽法处理极值:
data1 =np.array(data_x) #把数据转为array
#盖帽法 i是数据列数里的第几列, j是数据第i列里的第几个数
for i in range(0,len(data1[0])): #i从0到总列数循环
a=data1[:,i] #索引data1里的第i列并放入a
b=np.percentile(a,1) #计算a里的1%分位数
c=np.percentile(a,99) #计算a里的99%分位数
for j in range(0,len(data1[:,0])): #j从0到一列数据的总个数循环
if a[j]<b: #如果data1里的第i列里的第j个数小于1%分位数
a[j]=b #就把data1里的第i列里的第j个数换成1%分位数
elif a[j]>c: #如果data1里的第i列里的第j个数大于99%分位数
a[j]=c #就把data1里的第i列里的第j个数换成99%分位数
else:
a[j] #如果大于1%分位数小于99%分位数的值,则返回原值
# print(a[j])
data1
data1=pd.DataFrame(data1,columns=features) #换回DataFrame columns是每列数据的列名
#data1.dtypes 查看数据格式
data1.describe() #查看最大值最小值是否替换成功
data1.info()
plt.rcParams['font.sans-serif'] = ['SimHei'] #用来正常显示中文标签
plt.rcParams['axes.unicode_minus'] = False #用来正常显示负号
for i in range(0,47):
plt.figure(figsize=(4,8),dpi=100)
plt.boxplot(data1[data1.columns[i]].dropna().get_values())#画箱线图
plt.xlabel(data1.columns[i])
plt.show() #展示箱线图
#查看最小值是否不为负数
data1['ctrip_profits'].min()
data1['customer_value_profit'].min()
data1['delta_price1'].min()
data1['lowestprice'].min()
data1['delta_price2'].min()
第三部分
1.相关系数
生成相关系数矩阵并保存
corrdf = data1.corr()
corrdf
corrdf.to_csv( '相关性矩阵.csv' , index = False )
生成label与其他变量的相关系数,并降序排序
corrdf['label'].sort_values(ascending =False)
去除相关系数小于0.01的特征
delete_columns=[]
for i in range(1,corrdf.shape[0]):
if abs(corrdf.iloc[0,i])<0.01:
delete_columns.append(data1.columns[i])
data1.drop(delete_columns,axis=1,inplace=True)
去除x与x之间相关性大于0.9,且与y的相关性的绝对值比较小的特征
delete_columns2 = ["historyvisit_totalordernum","delta_price1","cityuvs"]
data1.drop(delete_columns2,axis=1,inplace= True)
2.标准化处理
为了使数据适应线性分类模型,对数据做标准化处理:
from sklearn.preprocessing import StandardScaler
y=data1.label
x=data1.drop('label',axis=1)
scaler = StandardScaler()
scaler.fit(x)
x= scaler.transform(x)
data1.head()
data1.describe()
第四部分
1.导入模型包,建模:
from sklearn.linear_model import LogisticRegression
from sklearn.ensemble import RandomForestClassifier
from sklearn.svm import SVC
#import xgboost as xgb
from sklearn import metrics
from sklearn.model_selection import train_test_split, GridSearchCV
x_train,x_test,y_train,y_test = train_test_split(x,y,test_size= .2,random_state=1234)
2.逻辑回归模型
lr = LogisticRegression()
lr.fit(x_train,y_train)
y_lr = lr.predict_proba(x_test)[:,1]
fpr_lr,tpr_lr,threshold_lr = metrics.roc_curve(y_test,y_lr)
auc_lr = metrics.auc(fpr_lr,tpr_lr)
score_lr = metrics.accuracy_score(y_test,lr.predict(x_test))
print([score_lr,auc_lr])
结果:[0.7400155084825603, 0.6995933154475564]
3.随机森林模型:
rfc = RandomForestClassifier()
rfc.fit(x_train,y_train)
y_rfc = rfc.predict_proba(x_test)[:,1]
fpr_rfc,tpr_rfc,threshold_rfc = metrics.roc_curve(y_test,y_rfc)
auc_rfc = metrics.auc(fpr_rfc,tpr_rfc)
score_rfc = metrics.accuracy_score(y_test,rfc.predict(x_test))
print([score_rfc,auc_rfc])
结果:[0.9061157048750262, 0.952069158341962]
用随机森林分析影响客户流失的因素:
importance = rfc.feature_importances_
indices = np.argsort(importance)[::-1]
features = data1.columns
for f in range(x.shape[1]):
print(("%2d) %-*s %f" % (f + 1, 30, features[f], importance[indices[f]])))
4.朴素贝叶斯模型:
from sklearn.naive_bayes import GaussianNB
gnb = GaussianNB()
gnb.fit(x_train,y_train)
y_gnb = gnb.predict_proba(x_test)[:,1]
fpr_gnb,tpr_gnb,threshold_gnb = metrics.roc_curve(y_test,y_gnb)
auc_gnb = metrics.auc(fpr_gnb,tpr_gnb)
score_gnb = metrics.accuracy_score(y_test,gnb.predict(x_test))
print([score_gnb,auc_gnb])
结果:[0.6506315720818326, 0.6686088984441764]
5.决策树模型:
from sklearn import tree
dtc = tree.DecisionTreeClassifier()
dtc.fit(x_train,y_train)
y_dtc = dtc.predict_proba(x_test)[:,1]
fpr_dtc,tpr_dtc,threshod_dtc= metrics.roc_curve(y_test,y_dtc)
metrics.accuracy_score(y_test,dtc.predict(x_test))
结果:0.8852082412366203
6.画图对比:
plt.rcParams['font.sans-serif']=['SimHei']
fig = plt.plot()
plt.plot(fpr_rfc,tpr_rfc,label='rfc')#随机森林
plt.plot(fpr_dtc,tpr_dtc,label='dtc')#决策树
plt.plot(fpr_lr,tpr_lr,label='lr')#逻辑回归
plt.plot(fpr_gnb,tpr_gnb,label='gnp')#朴素贝叶斯模型
#plt.plot(fpr_xgb,tpr_xgb,label='xgb')#XGBOOST
plt.legend(loc=0)#在合适的位置放置图例
plt.xlabel('False Positive Rate')
plt.ylabel('True Positive Rate')
plt.title('ROC carve');
plt.show()
第五部分
1.K-means用户画像:
dat=pd.concat((df['consuming_capacity'],df['customer_value_profit'],df['ordercanncelednum'],df['ordercanceledprecent'],df['ctrip_profits'],
df['historyvisit_7ordernum'],df['historyvisit_totalordernum'],
df['lastpvgap'],df['lasthtlordergap']),axis=1)
dat.to_csv('rfm',index=False)
from sklearn.cluster import KMeans
data=pd.read_csv('rfm')
Kmodel=KMeans(3)
Kmodel.fit(data)
Kmodel.cluster_centers_.to_csv()
结果:
低价值用户占比居多,可结合该群体流失情况分析流失客户因素,进行 该群体市场的开拓。
新用户,是潜在客户群体,可对该部分用户实施一些营销策略。
高价值用户和追求高品质用户,能给我们带来较优的收益,
可对这两类群体实施个性化营销策略。
3.高价值用户分析
用户描述:
商务人群订单数多,登陆时间长,访问次数多,提前预定时间短,但退单次数较多。
退单次数多的原因:
该类用户以商务人群居多,商务活动的不确定性较高,前一秒要出差,后一秒就不用了。
建议:
因为这类用户能给我们带来较优的收益,可对这类群体实施个性化营销策略。如:
Ø1.为客户提供更多差旅地酒店信息。
Ø2.推荐口碑好、性价比高的商务连锁酒店房源吸引用户。
Ø3.在非工作日的11点、17点等日间流量小高峰时段进行消息推送。
4.追求高品质用户分析
用户描述:
非商务类用户,这类用户的消费能力强,注重酒店星级,但对价格比较敏感。
价格敏感指数偏高原因:
价格敏感指数偏高,有可能是我们对高星级酒店做的价格优惠活动较多。
可适当减少对高星级酒店的价格优惠。
建议:
此类用户能给我们带来较优的收益,可对这类群体实施个性化营销策略。
Ø1.推送高端酒店以及当地的旅行资讯,吸引用户关注。
Ø2.和景区酒店代理商合作,针对此类用户制定个性化推荐。
Ø3.可在节假日前两、三星期定期推送国外高星级酒店。
5.新用户分析:
用户描述:
近期有下单,访问次数少,偏好低价格的酒店,对酒店的星级要求偏低,消费能力低。
建议:
Ø1.此类用户是潜在客户,建议把握用户初期体验(如初期消费有优惠、打卡活动等),还可以定期推送实惠的酒店给此类用户,以培养客户消费惯性为主。
Ø2.不建议花费过多营销预算。
6.低价值用户:
用户描述:
非旅游爱好者,这类客户属于低价值沉默客户,产品可按常规流失客户处理。
建议:
Ø1.由于这部分用户占比较多,可结合该群体流失情况分析流失客户因素,进行该群体市场的开拓。
Ø2.提供低价酒店促销活动为主。
Ø3.暂不对这部分客户这部分用户做特定渠道运营。
网友评论