携程客户流失分析

第一部分:

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.暂不对这部分客户这部分用户做特定渠道运营。

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

推荐阅读更多精彩内容

  • -- coding: utf-8 -- """Created on Sun Dec 23 01:10:07 201...
    NickyChu阅读 2,304评论 0 0
  • X[:,0]是numpy中数组的一种写法,取数组的索引,表示对一个二维数组,取该二维数组第一维中的所有数据,第二维...
    当安东尼遇到玛丽阅读 2,124评论 0 1
  • 假设你去随机问很多人一个很复杂的问题,然后把它们的答案合并起来。通常情况下你会发现这个合并的答案比一个专家的答案要...
    城市中迷途小书童阅读 2,396评论 0 1
  • 1简答政府进行价格管制的基本方式以及经济后果 在竞争市场中,均衡价格是在市场需求和供给共同作用下自发形成的,但有时...
    神盾局洗盾员阅读 1,634评论 0 0
  • 明天就要考试了,计划让儿子今晚好好复习复习,把课程都滤一遍。可刚看了几道语文题,儿子说他不学了,我问他为...
    四年级二班张育豪爸爸阅读 128评论 0 0