酒店預訂網客戶流失分析案例

這次分析某酒店預訂網在2016-05-15至2016-05-21這一周內的預定信息,對其客戶流失概率進行建模分析,并對客戶進行用戶畫像和RFM模型分析,針對不同類別客戶進行個性化的服務,以減少客戶流失。
導入包

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns

# 解決坐標軸刻度負號亂碼
plt.rcParams['axes.unicode_minus'] = False
# 解決中文亂碼問題
plt.rcParams['font.sans-serif'] = ['Simhei']
#顯示全部特征
pd.set_option('display.max_columns', None)

%matplotlib inline

一、讀入數據

rawdata=pd.read_csv('userlostprob.txt',sep='\t')
rawdata.head()
原始數據

可以看到,原始數據是存在大量缺失值的。

# 查看數據維度
rawdata.shape

維度為(689945, 51)。總共689945條樣本數據,除去標簽列和id列,總共49個特征。

# 查看每列數據類型
rawdata.info()

可以看到,除了預定時間和入住時間2列為字符型之外,其余均為數值型,我們之后只需要將預定時間和入住時間處理為數值型。

# 查看數據缺失情況
rawdata.isnull().mean()

數據缺失值較多,特別是historyvisit_7ordernum缺失達到88%。

# 標簽分布
rawdata['label'].value_counts()

數據存在偏斜,但不平衡程度不大。

二、數據探索

1 預定日期和入住日期

# 訪問日期和入住日期
# 入住時間人數統計
arrival=rawdata[['arrival']]
arrival['counta']=1
arrival=arrival.groupby('arrival').sum().reset_index()
# 訪問時間人數統計
d=rawdata[['d']]
d['countd']=1
d=d.groupby('d').sum().reset_index()
# 合并入住時間和訪問時間人數
time_table=pd.merge(arrival,d,left_on='arrival',right_on='d',how='left')
time_table.fillna(0,inplace=True)
del time_table['d']

# 畫出日期與人數的關系圖
import matplotlib.pyplot as plt
plt.figure(figsize=(13, 5));
plt.style.use('bmh')

x=range(len(time_table));
y1=time_table['counta'].values;
y2=time_table['countd'].values;
z=time_table['arrival'].values;
plt.plot(x,y1,c="r",label='入住人數');
plt.bar(x,y2,align="center",label='訪問人數');
plt.xlabel('日期');
plt.ylabel('人數');
plt.xticks(x,z,fontsize=11,rotation=45);
plt.title('訪問和入住人數圖',fontsize=20)
plt.legend(fontsize=20)
# plt.show()
arrival & d

520那天預定人數和入住人數都達到峰值,因為情侶會出門“過節”。521之后入住人數就一路走低。后面有兩個小突起是周末。

2 訪問時間段

# 訪問時間段
plt.figure(figsize=(15, 6))
plt.style.use('seaborn-colorblind')

plt.hist(rawdata['h'].dropna(), bins = 50, edgecolor = 'k');
plt.xlabel('訪問時間'); 
plt.ylabel('人數'); 
plt.title('訪問時間段');
h

5點是訪問人數最少的時點,這個時候大家都在睡覺。5點過后訪問人數開始上升,在晚間9、10點的時間段,訪問人數是最多的。

3 客戶價值

# 客戶價值刻畫
import matplotlib.pyplot as plt
plt.figure(figsize=(20, 7))
plt.style.use('seaborn-colorblind')

x1=rawdata['customer_value_profit'].dropna()
x2=rawdata['ctrip_profits'].dropna()

plt.subplot(121)
plt.plot(x1,linewidth=0.5)
plt.title('客戶近1年價值')

plt.subplot(122)
plt.plot(x2,linewidth=0.5)
plt.title('客戶價值')
customer_value_profit & ctrip_profits

可以看到,“客戶近1年價值”和“客戶價值”兩個特征是非常相關的,都可以用來表示[客戶的價值]這么一個特征。同時可以看到,大部分的客戶價值都處在0-100這個范圍,但是有些客戶價值非常大,設置達到了600,這些客戶都可以在以后的分析中重點觀察,因為他們是非常有“價值”的。

4 消費能力指數

# 消費能力指數
import matplotlib.pyplot as plt
plt.figure(figsize=(10, 6))
plt.style.use('seaborn-colorblind')

plt.hist(rawdata['consuming_capacity'].dropna(), bins = 50, edgecolor = 'k');
plt.xlabel('消費能力指數'); 
plt.ylabel('人數'); 
plt.title('消費能力指數圖');
consuming_capacity

可以看到,消費能力指數的值范圍是0-100,相當于對酒店客戶(及潛在客戶)的一個消費能力進行打分。指數值基本呈現一個正態分布的形狀,大部分人的消費能力在30附近。當然,我們同時可以看到,消費能力達到近100的人數也非常多,說明在我們酒店的訪問和入住客戶中,有不在少數的群體是消費水平非常高的,土豪還是多啊。

5 價格敏感指數

# 價格敏感指數
import matplotlib.pyplot as plt
plt.figure(figsize=(10, 6))

plt.style.use('seaborn-colorblind')
plt.hist(rawdata['price_sensitive'].dropna(), bins = 50, edgecolor = 'k');
plt.xlabel('價格敏感指數'); plt.ylabel('人數'); 
plt.title('價格敏感指數圖');
price_sensitive

價格敏感指數,用來反映客戶對價格的一個在意程度。可以看到,出去兩頭的極值現象,中間的分布是一個右偏(正偏態)的數據,大部分人的價格敏感指數比較低,也就是說,大部分客戶(及潛在客戶)是對價格不是很敏感的,并不會一味地去追求低價的酒店和房間,或許,酒店方面不需要在定價方面花費太多的腦筋。當然,我們也會發現,100處的人數也并不少,還是存在一部分的群體對價格極度敏感的,如果是針對這一部分客戶,用一些打折優惠的方式會有意想不到的成效。

6 入住酒店平均價格

# 酒店價格偏好
plt.figure(figsize=(20, 7))
plt.style.use('seaborn-colorblind')

plt.subplot(121)
plt.hist(rawdata['avgprice'].dropna(), bins = 50, edgecolor = 'k');
plt.xlabel('酒店價格'); 
plt.ylabel('偏好人數'); 
plt.title('酒店價格偏好');

plt.subplot(122)
plt.hist(rawdata[rawdata['avgprice']<2000]['avgprice'].dropna(), bins = 50, edgecolor = 'k');
plt.xlabel('酒店價格'); 
plt.ylabel('偏好人數'); 
plt.title('2000元以內酒店偏好');
avgprice

我們的avgprice酒店平均價格指標的范圍是1-6383元,這是當然了,還是有土豪會去住這種近萬元的酒店的。但是,我們從左圖可以看到,酒店價格在1000以上的,選擇的人就非常少了,價格在2000元以上的酒店就更加是沒有人去選擇了,繪制在圖中已經就是看不到了。為了排除“土豪”們的數據,更加具體地看一看我們“小屁民”的酒店選擇傾向,我們選擇了酒店價格在2000元以下的數據重新繪制一張圖,如右圖。右圖中可以更加明顯地看出,消費者對酒店價格的選擇,基本是一個正偏態的分布,大部分人會選擇的平均價格在300元左右(基本就是7天、如家這類吧)。

7 酒店星級偏好

# 酒店星級偏好
plt.style.use('bmh')
plt.figure(figsize=(10, 6))

plt.hist(rawdata['starprefer'].dropna(), bins = 50, edgecolor = 'k');
plt.xlabel('星級偏好程度'); plt.ylabel('選擇人數'); 
plt.title('酒店星級偏好');
starprefer

8 用戶年訂單數

# 用戶年訂單數
plt.figure(figsize=(20, 7))
plt.style.use('seaborn-colorblind')

plt.subplot(121)
plt.hist(rawdata['ordernum_oneyear'].dropna(), bins = 50, edgecolor = 'k');
plt.xlabel('年訂單數'); 
plt.ylabel('人數'); 
plt.title('客戶年訂單數分布');

plt.subplot(122)
plt.hist(rawdata[rawdata['ordernum_oneyear']<100]['ordernum_oneyear'].dropna(), bins = 50, edgecolor = 'k');
plt.xlabel('年訂單數'); 
plt.ylabel('人數'); 
plt.title('年訂單數100單內的分布');
ordernum_oneyear

9 訂單取消率

# 訂單取消率
plt.style.use('bmh')
plt.figure(figsize=(10, 6))

plt.hist(rawdata['ordercanceledprecent'].dropna(), bins = 50, edgecolor = 'k');
plt.xlabel('訂單取消率'); plt.ylabel('人數'); 
plt.title('訂單取消率');
ordercanceledprecent

10 距離上一次預定的時間

# 距離上一次預定的時間
plt.style.use('bmh')
plt.figure(figsize=(10, 6))

plt.hist(rawdata['lasthtlordergap'].dropna(), bins = 50, edgecolor = 'k');
plt.xlabel('間隔時長'); plt.ylabel('人數'); 
plt.title('距離上次預定的時間');
lasthtlordergap

這里的間隔時長我們并不清楚是個什么衡量單位,可能是一個時間戳計算而來的數字。但是通過上圖,我們還是可以觀察出,預定間隔時間越長的人數是遞減的,相當多數的人訂酒店還是比較頻繁的,對于酒店方而言,可能意味著酒店的品質還可以,“熟客”會經常性地選擇預定本酒店。

11 s_id會話

# 會話描述
# 生成會話表,flag是新客和老客的總人數,rate是新客和老客中最終預定的比率
s_table=rawdata[['label','sid']]
s_table['sid']=np.where(s_table['sid']==1,1,0)
s_table['flag']=1
s=s_table.groupby('sid').sum().reset_index()
s['rate']=s['label']/s['flag']                       # flag求和剛好是sid為0和1的個數,label求和剛好是流失人數,相除則為流失率

# 繪制柱狀圖
plt.figure(figsize=(15, 7))
plt.style.use('seaborn-colorblind')
label=("老客","新訪")

plt.subplot(121)
percent=[s['flag'][0]/s['flag'].sum(),s['flag'][1]/s['flag'].sum()]
colors=['steelblue','lightskyblue']
plt.pie(percent,autopct='%.2f%%',labels=label,colors=colors)
plt.title('新老客戶占比')

plt.subplot(122)
plt.bar(s['sid'],s['rate'],align="center",tick_label=label,hatch="http:///",edgecolor = 'k')
plt.ylabel('流失率'); 
plt.title('新老客戶中的客戶流失率')
s_id

會話id即SessionID,sessionID用來判斷是同一次會話,是服務器分配給訪問者的一個id。sid值為1,我們就可以認為用戶是第一次訪問酒店界面,即將其標記為“新訪”(新的訪問者)。上圖左圖展示了“新訪”和“老客”的分布情況,右圖展示了“新訪”和“老客”中最終會預定的一個比例。可以看到,老客的預定概率比新客的預定概率稍微高一點。

12 酒店轉換率

plt.style.use('bmh')
plt.figure(figsize=(10, 6))

plt.hist(rawdata['hotelcr'].dropna(), bins = 50, edgecolor = 'k');
plt.xlabel('酒店cr值');  
plt.title('酒店轉換率');
hotelcr

13 酒店獨立訪客

# 當前酒店歷史uv
plt.style.use('bmh')
plt.figure(figsize=(10, 6))

plt.hist(rawdata['hoteluv'].dropna(), bins = 50, edgecolor = 'k');
plt.xlabel('酒店uv值');  
plt.title('酒店歷史獨立訪客量');
hoteluv

14 當前酒店點評數

# 當前酒店點評數
plt.style.use('bmh')
plt.figure(figsize=(10, 6))

plt.hist(rawdata['commentnums'].dropna(), bins = 50, edgecolor = 'k');
plt.xlabel('點評數量');  
plt.title('酒店點評數');
commentnums

15 當前酒店評分人數

# 當前酒店評分人數
plt.style.use('bmh')
plt.figure(figsize=(10, 6))

plt.hist(rawdata['novoters'].dropna(), bins = 50, edgecolor = 'k');
plt.xlabel('點評人數');  
plt.title('酒店評分人數');
novoters

16 當前酒店歷史訂單取消率

# 當前酒店歷史取消率
plt.style.use('bmh')
plt.figure(figsize=(10, 6))

plt.hist(rawdata['cancelrate'].dropna(), bins = 50, edgecolor = 'k');
plt.xlabel('訂單取消率');  
plt.title('酒店訂單取消率');
cancelrate

17 當前酒店可訂最低價

# 當前酒店可訂最低價
plt.style.use('bmh')
plt.figure(figsize=(10, 6))

plt.plot(rawdata['lowestprice'].dropna())
plt.xlabel('酒店最低價');  
plt.title('酒店最低價');
lowestprice

酒店的信息特征基本都是呈現右偏的。

三、特征工程

# 為了避免在原數據集上進行修改操作,我們將rawdata復制一份
rawdf=rawdata.copy()

1 列值處理

字符串類型的特征都需要處理成數值型才能建模。本例只有兩個日期是字符串的,將其相減得到“提前預定的天數”,作為新的特征。

## 增加列
# 將兩個日期變量由字符串轉換為日期型格式
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)

2 異常值處理

我們在之前數據可視化的過程中,發現有一些特征值中是存在異常值的,比如用戶偏好價格會出現絕對值非常大的負值。因此,我們需要對這些異常值進行一定的處理。

# 將customer_value_profit、ctrip_profits中的負值按0處理
# 將delta_price1、delta_price2、lowestprice中的負值按中位數處理(之后可以試一試眾數的效果)
filter1=['customer_value_profit','ctrip_profits']
filter2=['delta_price1','delta_price2','lowestprice']

for f in filter1:
    rawdf.loc[rawdf[f]<0,f] = 0

for f in filter2:
    rawdf.loc[rawdf[f]<0,f] = rawdf[f].median()

3 缺失值處理

我們之前查看過缺失值情況,特征值中只有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))

可以看到,空值刪除操作后,樣本數據減少了100條,不算多,影響不大;特征值少了一個,進一步查看可以得知,historyvisit_7ordernum這一列被刪除了,因為這一列的缺失值比例高達88%,數據缺失過多,我們將其刪除。
接下來進行缺失值的填充。趨于正態分布的字段,使用均值填充: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)

4 極值處理

有些特征明顯有異常大和異常小的值,這里分別用1%和99%分位數替換超過上下限的值。

# 極值處理
for col in rawdf.columns:
    percent1=np.percentile(rawdf[col],1)       # 該列的1%分位數
    percent99=np.percentile(rawdf[col],99)       # 該列的99%分位數
    
    rawdf.loc[rawdf[col]<percent1,col]=percent1    # 小于1%分位數的,用1%分位數填充
    rawdf.loc[rawdf[col]>percent99,col]=percent99    # 大于99%分位數的,用99%分位數填充

5 相關性分析

分別生成用戶行為特征的相關性矩陣和酒店信息特征的相關性矩陣。

# 用戶特征的相關性分析
# 用戶特征提取
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()

# 繪制用戶特征的相關性矩陣熱度圖
# plt.matshow(corr_mat1,cmap='plasma')        #matshow不能顯示數字,只能用seaborn了
fig,ax = plt.subplots(figsize=(18, 12))
sns.heatmap(corr_mat, xticklabels=True, yticklabels=True, square=False, linewidths=.5, annot=True, cmap='Blues')
用戶行為特征相關性矩陣

從上面的相關性矩陣熱度圖上,我們可以一目了然地看到用戶行為數據中各個特征之間的相關性大小,顏色越深,則表示相關性程度越大。

我們可以很清楚地從上圖看到,不少特征之間是存在著高相關性的,除去對角線上的相關性為1之外,我們可以看到,ordernum_oneyear和historyvisit_totalordernum的相關性高達0.93,因為它們都是表示用戶1年內的訂單數,我們選擇其中名字更好識別的ordernum_oneyear作為用戶年訂單數的特征。

除此之外,decisionhabit_user和historyvisit_avghotelnum相關性達到了0.89,說明也是高度相關的,說明可能用戶的決策習慣就是根據用戶近3個月的日均訪問數來設定的,我們可以通過PCA提取一個主成分用來表示用戶近期的日均訪問量。

再就是customer_value_profit和ctrip_profits這兩個特征之間相關性達到了0.85,這兩個特征我們在上面的數據可視化中就有提到,表示的是不同時間長度下衡量的客戶價值,必然是高度相關的,我們可以用PCA的方法提取出一個主成分來代表客戶價值這么一個信息。

avgprice和consuming_capacity之間的相關性達到了0.91,同時starprefer與consuming_capacity相關性0.71,starprefer與avgprice相關性0.66,都比較高。這三個特征我們在數據可視化的部分也有提過,它們都代表了消費者的一個消費水平,消費能力越大,愿意或者說是會去選擇的酒店的平均價格就會越高,對酒店的星級要求也會越高。在這里,我們將相關性太大的avgprice和consuming_capacity抽象為用戶的消費水平。

delta_price1和delta_price2的相關性高達0.91,可以抽象出一個指標叫做“用戶偏好價格”。

# 酒店信息特征的相關性分析
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='Blues')
酒店信息特征相關性矩陣

novoters和commentnums相關性高達0.99。前者是當前點評人數,后者是當前點評數,可以抽象出“酒店熱度”指標;

novoters_pre和commentnums_pre相關性高達0.99,可以抽象出“24小時內瀏覽次數最多的酒店熱度”指標;

novoters_pre2和commentnums_pre2相關性高達0.99,可以抽象出“24小時內瀏覽酒店平均熱度”指標;

cancelrate和hoteluv相關性0.76,和commentnums相關性0.84,和novoters相關性0.85,酒店的“人氣”高,說明訪問的頻繁,歷史取消率可能也會高一點。

uv_pre和uv_pre2相關性高達0.9;businessrate_pre和businessrate_pre2相關性高達0.84;commentnums_pre和commentnums_pre2相關性高達0.82;novoters_pre和novoters_pre2相關性高達0.83。這些指標之間都是“瀏覽最多的酒店的數據”和“瀏覽酒店的平均數據”的關系,相關性高是正常的,暫時不用抽象出其他的指標。

6 降維

# 之前我們通過相關性分析得出的重復列,刪除
delete_columns = ['historyvisit_totalordernum','cityuvs']
rawdf.drop(delete_columns,axis=1,inplace= True)

print('刪除后數據維度是:{}'.format(rawdf.shape))
from sklearn.decomposition import PCA

# 定義降維函數,將我們抽象出的特征代替原本的幾個特征
def PCA_transform(df,col,new_col,n=1):
    pca=PCA(n_components=n)
    pca.fit(df[col])
    df[new_col]=pca.transform(df[col])                                          # 添加新生成列
    rawdf.drop(col,axis=1,inplace= True)                              # 刪除原來的特征列

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小時內瀏覽酒店的平均熱度

PCA_transform(rawdf,c_value,'c_value')
PCA_transform(rawdf,consume_level,'consume_level')
PCA_transform(rawdf,price_prefer,'price_prefer')
PCA_transform(rawdf,hotel_hot,'hotel_hot')
PCA_transform(rawdf,hotel_hot_pre,'hotel_hot_pre')
PCA_transform(rawdf,hotel_hot_pre2,'hotel_hot_pre2')

print('PCA降維后數據維度是:{}'.format(rawdf.shape))

PCA降維后數據維度是:(689845, 40)。

7 數據標準化

因為后面的建模中要用到LR、SVM這些需要計算距離的分類模型,所以我們需要先將數據進行標準化處理。

# 數據標準化
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=13)

1 邏輯回歸

from sklearn.linear_model import LogisticRegression
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([score_lr,auc_lr])

[0.7389196123766933, 0.6979154122462893]

2 樸素貝葉斯

from sklearn.naive_bayes import GaussianNB
from sklearn import metrics

gnb = GaussianNB()                                                  # 建立高斯模型
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)                              # 模型準確率
score_gnb = metrics.accuracy_score(y_test,y_pred)
print([score_gnb,auc_gnb])

[0.6323376990483369, 0.6653944945916186]

3 支持向量機

from sklearn.svm import SVC
from sklearn import metrics

svc = SVC(kernel='rbf',C=1.0).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([score_svc,auc_svc])

[0.7704774260884691, 0.7776290866238482]

4 決策樹

from sklearn import tree
from sklearn import metrics

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([score_dtc,auc_dtc])

[0.8756749704643797, 0.8479299034443152]

5 隨機森林

from sklearn.ensemble import RandomForestClassifier
from sklearn import metrics

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([score_rfc,auc_rfc])

[0.8934543266965768, 0.9377285634331807]

6 XGBoost

import xgboost as xgb
from sklearn import metrics

# 讀入訓練數據集和測試集
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([score_xgb,auc_xgb])

[0.8921134457740507, 0.9417563281093095]

7 模型比較

畫出ROC曲線

plt.style.use('bmh')
plt.figure(figsize=(13,10))

plt.plot(fpr_lr,tpr_lr,label='lr: 69.79%')                             # 邏輯回歸
plt.plot(fpr_gnb,tpr_gnb,label='gnp:66.54%')                          # 樸素貝葉斯模型
plt.plot(fpr_svc,tpr_svc,label='svc:77.76%')                          # 支持向量機模型
plt.plot(fpr_dtc,tpr_dtc,label='dtc:84.79%')                          # 決策樹
plt.plot(fpr_rfc,tpr_rfc,label='rfc:93.77%')                          # 隨機森林
plt.plot(fpr_xgb,tpr_xgb,label='xgb:94.18%')                          # XGBoost

plt.legend(loc='lower right',prop={'size':25})
plt.xlabel('偽陽率')
plt.ylabel('真陽率')
plt.title('ROC曲線')
plt.show()
ROC曲線

可以看到,貝葉斯表現最差,邏輯回歸的表現也不是很好,說明數據不是線性可分的。隨機森林和xgboost的表現差不多,都很好,二者的AUC值都在0.9以上,分類效果可以說相當不錯了。
可以調用xgb畫出重要特征圖。

from xgboost import plot_importance

# plt.figure(figsize=(13,10))
fig,ax = plt.subplots(figsize=(15,15))
plot_importance(bst,height=0.5,ax=ax,max_num_features=40)
特征重要性

重要的特征:24小時內是否訪問訂單填寫頁(24小時內是否訪問訂單填寫頁)、近3個月用戶歷史日均訪問酒店數(historyvisit_avghotelnum)、當前酒店轉換率(hotelcr)、當前酒店歷史訂單取消率(ordercanceledprecent)、星級偏好(starprefer)、用戶歷史取消率(cancelrate)、 7天內訪問酒店詳情頁數(historyvisit_visit_detailpagenum)、價格敏感指數(price_sensitive)、當前酒店訪客量(hoteluv)、瀏覽最多的酒店商務屬性(businessrate_pre)。

五、RFM分析和用戶畫像

1 RFM分析

RFM模型,即為:
R(Rencency):最近一次消費
F(Frequency):消費頻率
M(Monetary):消費金額



在本案例中,我們選擇lasthtlordergap(距離上次下單的時長)、ordernum_oneyear(用戶年訂單數)、consume_level(用戶消費水平)分別作為R、F、M的值,對我們的用戶群體進行聚類。

rfm = rawdf[['lasthtlordergap','ordernum_oneyear','consume_level']]
rfm.rename(columns={'lasthtlordergap':'recency','ordernum_oneyear':'frequency','consume_level':'monetary'},inplace=True)
rfm.head()

# 進行歸一化
# 數據標準化
from sklearn.preprocessing import MinMaxScaler

scaler = MinMaxScaler()
scaler.fit(rfm)
rfm = scaler.transform(rfm)

# 分箱
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

le = LabelEncoder().fit(rfm['R'])
rfm['R']=le.transform(rfm['R'])
le = LabelEncoder().fit(rfm['F'])
rfm['F']=le.transform(rfm['F'])
le = LabelEncoder().fit(rfm['M'])
rfm['M']=le.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)

我們可以看一下按照RFM劃分的8類客戶占比。

import matplotlib.pyplot as plt

tmp = rfm0.groupby('Label of Customer').size()

fig, ax = plt.subplots(figsize=(10,10))
colors=['deepskyblue','steelblue','lightskyblue','aliceblue','skyblue','cadetblue','cornflowerblue','dodgerblue']
ax.pie(tmp.values, radius=1,autopct='%1.1f%%',pctdistance=0.75,colors=colors)
ax.pie([1], radius=0.6,colors='w')
ax.set(aspect="equal", title='客戶細分情況')
plt.legend(tmp.index,bbox_to_anchor=(1, 1), loc='best', borderaxespad=0.)
plt.show()

2 用戶畫像

其實我們并不想將用戶分的這么細,并且我們其實有挺多的用戶行為特征數據,所以也并不想僅用RFM這3個指標進行分析。所以,我們接下來用K-Means聚類的方法將用戶分為3類,觀察不同類別客戶的特征。

# 選取出幾個刻畫用戶的重要指標
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,random_state=13)                                     # 建立KMean模型
Kmeans.fit(user_attributes)                                                     # 訓練模型
k_char=Kmeans.cluster_centers_                                                  # 得到每個分類的質心
personas=pd.DataFrame(k_char.T,index=user_feature,columns=['0類','1類','2類'])  # 用戶畫像表
personas
fig,ax = plt.subplots(figsize=(4, 8))
sns.heatmap(personas, xticklabels=True, yticklabels=True, square=False, linewidths=.5, annot=True, cmap='Blues')

我們可以看到,聚出來的3類用戶有各自非常明顯的特征:
可以看到,2類中藍色格子明顯最多,它的R(lasthtlordergap)為-0.17非常小,F(ordernum_oneyear)為1.1比較高了,M(consume_level)為1.3也幾乎是最高的。很明顯,2類客戶為我們的“高價值客戶”;而1類中幾乎都是白格子,無論是客戶價值還是消費水平值都是最低的,很明顯,這一類我們將其歸為“低價值客戶”;剩下的0類我們將其稱為“中等群體”。
將3類用戶可視化。

# 畫餅圖
# with plt.xkcd():
import matplotlib.pyplot as plt
plt.figure(figsize=(9,9))

class_k=list(Kmeans.labels_)                          # 每個類別的用戶個數
percent=[class_k.count(0)/len(user_attributes),class_k.count(1)/len(user_attributes),class_k.count(2)/len(user_attributes)]   # 每個類別用戶個數占比

fig, ax = plt.subplots(figsize=(10,10))
colors=['aliceblue','steelblue','lightskyblue']
types=['中等群體','低價值用戶','高價值用戶']
ax.pie(percent,radius=1,autopct='%.2f%%',pctdistance=0.75,colors=colors,labels=types)
ax.pie([1], radius=0.6,colors='w')
plt.show()

我們可以看到,在我們的客戶群體中,“低價值客戶”的占比非常之大,中等人群占比最小。我們的“高價值客戶”占比17.34%。

3 用戶分析

3.1 高價值用戶分析

2類客戶描述:
消費水平高,客戶價值大,追求高品質,對酒店星級要求高,訪問頻率和預定頻率都較高,提前預定的時間都較短,決策一般都較快(日均訪問數少),訂單取消率較高,可以分析出這類客戶商務屬性偏重,可能隨時要出差,因此都不會提前預定,可能出差隨時會取消,因此酒店取消率也會更高一點。sid的值較大,說明高價值客戶群體多集中在老客戶中。價格敏感度較高,說明可能比較要求性價比。h值非常小,可能訪問和預定時間多在半夜或是清晨。
這部分客戶對于我們而言是非常重要的,因此我們需要對其實施個性化的營銷:
1、為客戶提供更多差旅酒店信息。
2、多推薦口碑好、性價比高的商務酒店。
3、推薦時間集中在半夜或是清晨。

3.2 中等群體分析

0類客戶描述:
消費水平和客戶價值都偏低,對酒店品質也不太追求,訪問和預定頻率也都較高,提前預定的時間是三類中最長的,最值得注意的是,0類客戶中有兩個顏色非常深的藍色格子,是用戶決策和近3個月的日均訪問數。可以看出,這類客戶通常很喜歡逛酒店界面,在決定要訂哪家酒店前通常會花費非常多的時間進行瀏覽才能做出選擇,并且一般都會提前很久訂好房。我們可以給這類客戶打上“謹慎”的標簽。我們可以合理推斷,這一類客戶,可能預定酒店的目的多為出門旅行。
針對這部分客戶,我們需要:
1、盡可能多地進行推送,因為此類客戶通常比較喜歡瀏覽。
2、推送當地旅游資訊,因為這類客戶旅游出行的概率較大。
3、多推薦價格相對實惠的酒店。

3.3 低價值用戶分析

1類客戶描述:
消費水平和客戶價值極低,對酒店品質不追求,偏好價格較低,決策時間很短,訪問和預定頻率很低,sid值很低,說明新客戶居多。
針對這部分客戶,我們需要:
1、不建議花費過多營銷成本,但因為新用戶居多,屬于潛在客戶,可以維持服務推送。
2、推送的內容應多為大減價、大酬賓、跳樓價之類的。
3、此類用戶占比居多,可進一步進行下沉分析,開拓新的時長。

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 國家電網公司企業標準(Q/GDW)- 面向對象的用電信息數據交換協議 - 報批稿:20170802 前言: 排版 ...
    庭說閱讀 11,123評論 6 13
  • Swift1> Swift和OC的區別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴謹 對...
    cosWriter閱讀 11,136評論 1 32
  • ORA-00001: 違反唯一約束條件 (.) 錯誤說明:當在唯一索引所對應的列上鍵入重復值時,會觸發此異常。 O...
    我想起個好名字閱讀 5,429評論 0 9
  • 昨晚第一次嘗試給自己染發。 騷動的內心總是會讓我對很多事情好奇,在自己勇氣能承受的范圍之內盡力去嘗試。所以,這次給...
    雨文_yuwencc1009閱讀 320評論 0 1
  • 你笑,全世界會陪你一起笑。你哭,只有你一個人自己哭。我們身邊發生的每一件事都是我們吸引過來的。今天的課讓我再次想到...
    牛歡Vincent閱讀 363評論 0 0