【案例-數(shù)據(jù)挖掘】酒店預(yù)定客戶流失分析及預(yù)測(cè)

分析思路:

0、數(shù)據(jù)準(zhǔn)備
1、數(shù)據(jù)探索
2、特征工程
3、建模
4、RFM分析和用戶畫像

0、數(shù)據(jù)準(zhǔn)備

0.1 模塊及數(shù)據(jù)導(dǎo)入
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns

# 解決坐標(biāo)軸刻度負(fù)號(hào)亂碼
plt.rcParams['axes.unicode_minus'] = False
# 解決中文亂碼問題
plt.rcParams['font.sans-serif'] = ['Simhei']
%matplotlib inline
df=pd.read_csv(r'D:\Users\wuxiao\Desktop\userlostprob\userlostprob.txt',sep='\t')
df.head()

image.png

ps,如果仍然存在mac畫圖中文無法顯示的問題:https://blog.csdn.net/qq_21904665/article/details/78732696

0.2 數(shù)據(jù)項(xiàng)基本信息
image.png

數(shù)據(jù)維度為689945*51,label為標(biāo)簽列,1為未流失,0為流失(等會(huì)兒再確認(rèn)),samplied是id列?因?yàn)闆]有重復(fù)項(xiàng),其余有49個(gè)特征項(xiàng),下面將針對(duì)部分關(guān)鍵特征進(jìn)行分析。

  • d:預(yù)定日期
  • arrival:入住日期
  • h:訪問時(shí)間段
  • customer_value_profit:客戶近1年價(jià)值
  • ctrip_profits:客戶價(jià)值
  • consuming_capacity:消費(fèi)能力指數(shù)
  • price_sensitive:價(jià)格敏感指數(shù)
  • avgprice:入住酒店平均價(jià)格
  • starprefer:酒店星級(jí)偏好
  • ordernum_oneyear:年訂單數(shù)
  • ordercanceledprecent:訂單取消率
  • lasthtlordergap:距離上次預(yù)定的時(shí)間
  • sid:新客老客特征
  • hotelcr:酒店cr值
  • hoteluv:酒店uv值
  • commentnums:酒店點(diǎn)評(píng)數(shù)
  • novoters:酒店點(diǎn)評(píng)人數(shù)
  • cancelrate:酒店取消率
  • lowestprice:酒店最低價(jià)
image.png

數(shù)據(jù)存在偏斜,但不平衡程度不大。


image.png

數(shù)據(jù)缺失值較多,特別是historyvisit_7ordernum缺失達(dá)到88%。

一、數(shù)據(jù)探索

1.1 預(yù)定日期和入住日期
df_d=df.d.value_counts().to_frame().reset_index()
df_arrival=df.arrival.value_counts().to_frame().reset_index()
time_table=df_d.merge(df_arrival,how='outer',on='index')
time_table.fillna(0,inplace=True)
time_table.set_index('index',inplace=True)
time_table.sort_index(inplace=True)

x=time_table.index
y1=time_table.arrival
y2=time_table.d

plt.figure(figsize=(13,5))
plt.style.use('bmh')
plt.plot(x,y1,c="r",label='入住人數(shù)');
plt.bar(x,y2,align="center",label='預(yù)定人數(shù)');
plt.title('訪問和入住人數(shù)圖',fontsize=20)
plt.xticks(rotation=45,fontsize=13)
plt.xlabel('日期');
plt.ylabel('人數(shù)',fontsize=13);
plt.legend(fontsize=13)
image.png
  • 520那天預(yù)定人數(shù)和入住人數(shù)都達(dá)到峰值,因?yàn)榍閭H會(huì)出門“過節(jié)”。521之后入住人數(shù)就一路走低。后面有兩個(gè)小突起是周末。

1.2 訪問時(shí)間段

plt.figure(figsize=(15, 6))
plt.hist(df.h.dropna(), bins = 50, edgecolor = 'k');
#因?yàn)樽疃?4個(gè)時(shí)段,所以bins再大的話,只是調(diào)整方塊的間距了
plt.title('訪問時(shí)間段',fontsize=20);
plt.xticks(fontsize=15)
plt.yticks(fontsize=15)
plt.xlabel('訪問時(shí)間',fontsize=18); 
plt.ylabel('人數(shù)',fontsize=18); 
image.png
  • 5點(diǎn)是訪問人數(shù)最少的時(shí)點(diǎn),這個(gè)時(shí)候大家都在睡覺。5點(diǎn)過后訪問人數(shù)開始上升,在晚間9、10點(diǎn)的時(shí)間段,訪問人數(shù)是最多的。
1.3
plt.figure(figsize=(12, 4))

plt.subplot(121)
plt.plot(df.index,df.customer_value_profit,linewidth=0.5)
plt.title('客戶近1年價(jià)值')

plt.subplot(122)
plt.plot(df.index,df.ctrip_profits,linewidth=0.5)
plt.title('客戶價(jià)值')
image.png
  • 可以看到,“客戶近1年價(jià)值”和“客戶價(jià)值”兩個(gè)特征是非常相關(guān)的,都可以用來表示[客戶的價(jià)值]這么一個(gè)特征。同時(shí)可以看到,大部分的客戶價(jià)值都處在0-100這個(gè)范圍,但是有些客戶價(jià)值非常大,設(shè)置達(dá)到了600,這些客戶都可以在以后的分析中重點(diǎn)觀察,因?yàn)樗麄兪欠浅S小皟r(jià)值”的。
1.4 消費(fèi)能力指數(shù)
plt.figure(figsize=(12, 4))

plt.hist(df.consuming_capacity,bins=50,edgecolor='k')
plt.xlabel('消費(fèi)能力指數(shù)')
plt.ylabel('人數(shù)')
plt.title('消費(fèi)能力指數(shù)圖')
image.png
  • 可以看到,消費(fèi)能力指數(shù)的值范圍是0-100,相當(dāng)于對(duì)酒店客戶(及潛在客戶)的一個(gè)消費(fèi)能力進(jìn)行打分。指數(shù)值基本呈現(xiàn)一個(gè)正態(tài)分布的形狀,大部分人的消費(fèi)能力在30附近。當(dāng)然,我們同時(shí)可以看到,消費(fèi)能力達(dá)到近100的人數(shù)也非常多,說明在我們酒店的訪問和入住客戶中,有不在少數(shù)的群體是消費(fèi)水平非常高的,土豪還是多啊。
1.5 價(jià)格敏感指數(shù)
plt.figure(figsize=(12, 4))

plt.hist(df['price_sensitive'].dropna(), bins = 50, edgecolor = 'k');
plt.xlabel('價(jià)格敏感指數(shù)'); 
plt.ylabel('人數(shù)'); 
plt.title('價(jià)格敏感指數(shù)圖');
image.png
  • 價(jià)格敏感指數(shù),用來反映客戶對(duì)價(jià)格的一個(gè)在意程度??梢钥吹剑深^的極值現(xiàn)象,中間的分布屬于右偏的正太分布,大部分人的價(jià)格敏感指數(shù)比較低,也就是說,大部分客戶(及潛在客戶)是對(duì)價(jià)格不是很敏感的,并不會(huì)一味地去追求低價(jià)的酒店和房間,或許,酒店方面不需要在定價(jià)方面花費(fèi)太多的腦筋。當(dāng)然,我們也會(huì)發(fā)現(xiàn),100處的人數(shù)也并不少,還是存在一部分的群體對(duì)價(jià)格極度敏感的,如果是針對(duì)這一部分客戶,用一些打折優(yōu)惠的方式會(huì)有意想不到的成效。
1.6 入住酒店平均價(jià)格
plt.figure(figsize=(12, 4))
plt.subplot(121)
plt.hist(df.avgprice.dropna(),bins=50,edgecolor = 'k')
plt.xlabel('酒店價(jià)格'); 
plt.ylabel('偏好人數(shù)'); 
plt.title('酒店價(jià)格偏好');

plt.subplot(122)
plt.hist(df[df.avgprice<2000]['avgprice'].dropna(), bins = 50, edgecolor = 'k');
plt.xlabel('酒店價(jià)格'); 
plt.ylabel('偏好人數(shù)'); 
plt.title('2000元以內(nèi)酒店偏好');
image.png
  • 從左圖中可以知道,酒店平均價(jià)格范圍為1-6383元,但實(shí)際上酒店價(jià)格在1000以上的,選擇的人就非常少了,價(jià)格在2000元以上的酒店就更加是沒有人去選擇了,所以右圖展示了價(jià)格為2000元以下的酒店情況。右圖表明,消費(fèi)者對(duì)酒店價(jià)格的選擇,基本是一個(gè)正偏態(tài)的分布,大部分人會(huì)選擇的平均價(jià)格在300元左右(基本就是7天、如家這類吧)。
1.7 酒店星級(jí)偏好
plt.figure(figsize=(10, 4))
plt.hist(df.starprefer.dropna(), bins = 50, edgecolor = 'k')
plt.xlabel('星級(jí)偏好程度')
plt.ylabel('選擇人數(shù)');
plt.title('酒店星級(jí)偏好')
image.png
  • 分布有點(diǎn)不規(guī)律,尤其是40、60、80、100的分段存在極值情況,剔除這幾個(gè)分段,星級(jí)偏好主要集中在60~80之間。
1.8 用戶年訂單數(shù)
plt.figure(figsize=(12, 4))
plt.subplot(121)
plt.hist(df.ordernum_oneyear.dropna(), bins = 50, edgecolor = 'k');
plt.xlabel('年訂單數(shù)'); 
plt.ylabel('人數(shù)'); 
plt.title('客戶年訂單數(shù)分布');

plt.subplot(122)
plt.hist(df[df.ordernum_oneyear<100].ordernum_oneyear.dropna(), bins = 50, edgecolor = 'k');
plt.xlabel('年訂單數(shù)'); 
plt.ylabel('人數(shù)'); 
plt.title('年訂單數(shù)100單內(nèi)的分布');
image.png
  • 用戶年訂單數(shù)最高可達(dá)700+,但是大部分用戶的你按訂單數(shù)集中在0~20之間。
1.9 訂單取消率
plt.figure(figsize=(10, 4))
plt.hist(df.ordercanceledprecent.dropna(),bins=50,edgecolor = 'k')
plt.xlabel('訂單取消率')
plt.ylabel('人數(shù)')
plt.title('訂單取消率')
image.png
  • 存在大量的用戶訂單取消率為0,也存在部分極端用戶訂單取消率為1。
1.10 舉例上一次預(yù)定的時(shí)間
plt.figure(figsize=(10, 4))
plt.hist(df.lasthtlordergap.dropna(), bins = 50, edgecolor = 'k');
plt.xlabel('間隔時(shí)長(zhǎng)'); plt.ylabel('人數(shù)'); 
plt.title('距離上次預(yù)定的時(shí)間');

image.png
  • 從圖像上看,比較符合均值為0的正態(tài)分布。但是并不知道間隔時(shí)長(zhǎng)的單位是什么。
1.11 新老客流失率
s_table=df[['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的個(gè)數(shù),label求和剛好是流失人數(shù),相除則為流失率

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

plt.subplot(122)
plt.bar(s.sid,s.rate,align='center',tick_label=label,edgecolor = 'k')
plt.ylabel('流失率')
plt.title('新老客戶中的客戶流失率')
image.png
  • 新客大約占5.58%,老客的流失率較新客的流失率更高。
1.12 酒店轉(zhuǎn)換率
plt.figure(figsize=(10, 4))
plt.hist(df.hotelcr.dropna(), bins = 50, edgecolor = 'k');
plt.xlabel('酒店cr值');  
plt.title('酒店轉(zhuǎn)換率');
image.png
  • CR本來是網(wǎng)站轉(zhuǎn)化率(conversion rate)是指用戶進(jìn)行了相應(yīng)目標(biāo)行動(dòng)的訪問次數(shù)與總訪問次數(shù)的比率,應(yīng)當(dāng)是小于1的數(shù)據(jù),但這里酒店的cr處在1~2之間,不知道是怎么定義的。
1.13 酒店獨(dú)立訪客
plt.figure(figsize=(10, 4))
plt.hist(df.hoteluv.dropna(), bins = 50, edgecolor = 'k');
plt.xlabel('酒店uv值');  
plt.title('酒店歷史獨(dú)立訪客量');
image.png
  • PV(訪問量): 即Page View, 即頁面瀏覽量或點(diǎn)擊量,用戶每次刷新即被計(jì)算一次。UV(獨(dú)立訪客):即Unique Visitor,訪問您網(wǎng)站的一臺(tái)電腦客戶端為一個(gè)訪客。00:00-24:00內(nèi)相同的客戶端只被計(jì)算一次。umm...這里的酒店uv值,不太清楚代表的是什么,還是數(shù)據(jù)源的問題。。
1.14 當(dāng)前酒店點(diǎn)評(píng)數(shù)
plt.figure(figsize=(10, 4))
plt.hist(df.commentnums.dropna(), bins = 50, edgecolor = 'k');
plt.xlabel('點(diǎn)評(píng)數(shù)量');  
plt.title('酒店點(diǎn)評(píng)數(shù)');
image.png
1.15 當(dāng)前酒店評(píng)分人數(shù)
plt.figure(figsize=(10, 4))
plt.hist(df.novoters.dropna(), bins = 50, edgecolor = 'k');
plt.xlabel('點(diǎn)評(píng)人數(shù)');  
plt.title('酒店評(píng)分人數(shù)');
image.png
1.16 當(dāng)前酒店歷史訂單取消率
plt.figure(figsize=(10, 4))
plt.hist(df.cancelrate.dropna(), bins = 50, edgecolor = 'k');
plt.xlabel('訂單取消率');  
plt.title('酒店訂單取消率');
image.png
1.17 當(dāng)前酒店可訂最低價(jià)
plt.figure(figsize=(10, 4))
plt.plot(df.lowestprice.dropna())
plt.xlabel('酒店最低價(jià)');  
plt.title('酒店最低價(jià)');
image.png

二、特征工程

# 為了避免在原數(shù)據(jù)集上進(jìn)行修改操作,我們將df復(fù)制一份
df1=df.copy()
2.1 字符串處理

原數(shù)據(jù)中,arrival和d都是字符串格式,可以將其詳見得到“提前預(yù)定的天數(shù)”,并轉(zhuǎn)化為新的數(shù)值特征。

## 增加列
# 將兩個(gè)日期變量由字符串轉(zhuǎn)換為日期型格式
df1['arrival']=pd.to_datetime(df1['arrival'])
df1['d']=pd.to_datetime(df1['d'])
# 生成提前預(yù)定時(shí)間列
df1['day_advanced']=(df1['arrival']-df1['d']).dt.days

## 刪除列
df1=df1.drop(['d','arrival'],axis=1)

處理后:


image.png

ps:pd顯示的模式有數(shù)值模式/datetime/其他格式,所以是可以轉(zhuǎn)化成datetime格式再處理的。http://www.manongjc.com/article/3143.html,有空可以再研究

2.2 異常值處理

把用戶價(jià)值的兩個(gè)特征量customer_value_profit、ctrip_profits中的負(fù)值按0處理;把delta_price1、delta_price2、lowestprice中的負(fù)值按中位數(shù)處理。我個(gè)人也不知道為什么這么處理,因?yàn)槔锩婧芏嗵卣鞑⒉恢谰唧w含義。

filter1=['customer_value_profit','ctrip_profits']
filter2=['delta_price1','delta_price2','lowestprice']

for i in filter1:
    df1.loc[df1[i]<0,i]=0        ##用df1.loc[df1[i]<0][i]=0 會(huì)提示無法有點(diǎn)問題,所以還是得用前面的用法
    
for i in filter2:
    temp=df.delta_price1.mean()
    df1.loc[df1[i]<0,i]=temp    ##用df1.loc[df1[i]<0][i]=0 會(huì)提示無法有點(diǎn)問題,所以還是得用前面的用法

處理后:


image.png
2.3 缺失值處理

原數(shù)據(jù)中只有iforderpv_24h、sid、h、day_advanced這四個(gè)是不存在缺失的,其他的44個(gè)特征都是存在缺失值的,并且大部分的缺失值都挺多的,因此,我們接下來需要對(duì)缺失值進(jìn)行處理。

2.3.1 空值刪除

首先設(shè)定rate為0.2,如果某行或某列的數(shù)據(jù)缺失率超過(1-0.2)=0.8,則將其刪除:

# 刪除缺失值比例大于80%的行和列
print('刪除空值前數(shù)據(jù)維度是:{}'.format(df1.shape))
df1.dropna(axis=0,thresh=df1.shape[1]*0.2,inplace=True)
df1.dropna(axis=1,thresh=df1.shape[0]*0.2,inplace=True)
print('刪除空值后數(shù)據(jù)維度是:{}'.format(df1.shape))
image.png
  • 空值刪除操作后,樣本數(shù)據(jù)減少了100條,不算多,影響不大;特征值少了一個(gè),進(jìn)一步查看可以得知,historyvisit_7ordernum這一列被刪除了,因?yàn)檫@一列的缺失值比例高達(dá)88%,數(shù)據(jù)缺失過多,我們將其刪除。
2.4缺失項(xiàng)補(bǔ)足

數(shù)據(jù)項(xiàng)基本滿足正態(tài)分布或者右偏分布,對(duì)正態(tài)分布項(xiàng),采用均值填充較合適,對(duì)右偏分布項(xiàng),采用中位數(shù)填充更合適。原數(shù)據(jù)中,businessrate_pre2、cancelrate_pre、businessrate_pre趨于正態(tài)分布,齊豫趨于右偏分布(這一點(diǎn)沒有詳細(xì)考證)。

filter_mean=['businessrate_pre2','cancelrate_pre','businessrate_pre']
for i in df1.columns:
    if i in filter_mean:
        df1[i].fillna(df1[i].mean(),inplace=True)
    else:
        df1[i].fillna(df1[i].median(),inplace=True)

ps:偏態(tài)分布https://zhidao.baidu.com/question/361765236.html

image.png
2.5 極值處理

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

for i in df1.columns:
    df1.loc[df1[i]<np.percentile(df1[i],1),i]=np.percentile(df1[i],1)
    df1.loc[df1[i]>np.percentile(df1[i],99),i]=np.percentile(df1[i],99)
2.3.4 相關(guān)性分析
# 用戶特征提取(分兩次提取,為了更好地顯示圖)
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']
# 生成用戶特征的相關(guān)性矩陣
corr_mat=df1[user_features].corr()

# 繪制用戶特征的相關(guān)性矩陣熱度圖
plt.figure(figsize=(12,12))
sns.heatmap(corr_mat, xticklabels=True, yticklabels=True, square=False, linewidths=.5, annot=True, cmap='Blues')
image.png

可以看出,不少特征存在強(qiáng)相關(guān)性:

  • ordernum_oneyear和historyvisit_totalordernum的相關(guān)性高達(dá)0.93,因?yàn)樗鼈兌际潜硎居脩?年內(nèi)的訂單數(shù),我們選擇其中名字更好識(shí)別的ordernum_oneyear作為用戶年訂單數(shù)的特征。
  • decisionhabit_user和historyvisit_avghotelnum相關(guān)性達(dá)到了0.89,說明也是高度相關(guān)的,說明可能用戶的決策習(xí)慣就是根據(jù)用戶近3個(gè)月的日均訪問數(shù)來設(shè)定的,我們可以通過PCA提取一個(gè)主成分用來表示用戶近期的日均訪問量。
  • customer_value_profit和ctrip_profits這兩個(gè)特征之間相關(guān)性達(dá)到了0.85,這兩個(gè)特征我們?cè)谏厦娴臄?shù)據(jù)可視化中就有提到,表示的是不同時(shí)間長(zhǎng)度下衡量的客戶價(jià)值,必然是高度相關(guān)的,我們可以用PCA的方法提取出一個(gè)主成分來代表客戶價(jià)值這么一個(gè)信息。
  • avgprice和consuming_capacity之間的相關(guān)性達(dá)到了0.91,同時(shí)starprefer與consuming_capacity相關(guān)性0.71,starprefer與avgprice相關(guān)性0.66,都比較高。這三個(gè)特征我們?cè)跀?shù)據(jù)可視化的部分也有提過,它們都代表了消費(fèi)者的一個(gè)消費(fèi)水平,消費(fèi)能力越大,愿意或者說是會(huì)去選擇的酒店的平均價(jià)格就會(huì)越高,對(duì)酒店的星級(jí)要求也會(huì)越高。可以考慮將這幾個(gè)變量進(jìn)行PCA降維。
  • delta_price1和delta_price2的相關(guān)性高達(dá)0.91,同時(shí)和avgprice的相關(guān)性也大于0.7,針對(duì)這幾個(gè)指標(biāo)可以抽象出一個(gè)指標(biāo)叫做“用戶偏好價(jià)格”。
# 用戶特征提取(分兩次提取)
user_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']
# 生成用戶特征的相關(guān)性矩陣
corr_mat=df1[user_features].corr()

# 繪制用戶特征的相關(guān)性矩陣熱度圖
plt.figure(figsize=(12,12))
sns.heatmap(corr_mat, xticklabels=True, yticklabels=True, square=False, linewidths=.5, annot=True, cmap='Blues')
image.png
  • novoters和commentnums相關(guān)性高達(dá)0.99,前者是當(dāng)前點(diǎn)評(píng)人數(shù),后者是當(dāng)前點(diǎn)評(píng)數(shù),可以抽象出“酒店熱度”指標(biāo);
  • novoters_pre和commentnums_pre相關(guān)性高達(dá)0.99,可以抽象出“24小時(shí)內(nèi)瀏覽次數(shù)最多的酒店熱度”指標(biāo);
  • novoters_pre2和commentnums_pre2相關(guān)性高達(dá)0.99,可以抽象出“24小時(shí)內(nèi)瀏覽酒店平均熱度”指標(biāo);
  • cancelrate和hoteluv相關(guān)性0.76,和commentnums相關(guān)性0.84,和novoters相關(guān)性0.85,酒店的“人氣”高,說明訪問的頻繁,歷史取消率可能也會(huì)高一點(diǎn)。
  • uv_pre和uv_pre2相關(guān)性高達(dá)0.9;businessrate_pre和businessrate_pre2相關(guān)性高達(dá)0.84;commentnums_pre和commentnums_pre2相關(guān)性高達(dá)0.82;novoters_pre和novoters_pre2相關(guān)性高達(dá)0.83。這些指標(biāo)之間都是“瀏覽最多的酒店的數(shù)據(jù)”和“瀏覽酒店的平均數(shù)據(jù)”的關(guān)系,相關(guān)性高是正常的,暫時(shí)不用抽象出其他的指標(biāo)。
2.6 降維
c_value=['customer_value_profit','ctrip_profits']                   # 用戶價(jià)值
consume_level=['avgprice','consuming_capacity']                     # 用戶消費(fèi)水平
price_prefer=['delta_price1','delta_price2']                        # 用戶偏好價(jià)格
hotel_hot=['commentnums','novoters']                                # 酒店熱度
hotel_hot_pre=['commentnums_pre','novoters_pre']                    # 24小時(shí)內(nèi)瀏覽次數(shù)最多的酒店熱度
hotel_hot_pre2=['commentnums_pre2','novoters_pre2']                 # 24小時(shí)內(nèi)瀏覽酒店的平均熱度

from sklearn.decomposition import PCA
pca=PCA(n_components=1)
df1['c_value']=pca.fit_transform(df1[c_value])
df1['consume_level']=pca.fit_transform(df1[consume_level])
df1['price_prefer']=pca.fit_transform(df1[price_prefer])
df1['hotel_hot']=pca.fit_transform(df1[hotel_hot])
df1['hotel_hot_pre']=pca.fit_transform(df1[hotel_hot_pre])
df1['hotel_hot_pre2']=pca.fit_transform(df1[hotel_hot_pre2])

df1.drop(c_value,axis=1,inplace=True)
df1.drop(consume_level,axis=1,inplace=True)
df1.drop(price_prefer,axis=1,inplace=True)
df1.drop(hotel_hot,axis=1,inplace=True)
df1.drop(hotel_hot_pre,axis=1,inplace=True)
df1.drop(hotel_hot_pre2,axis=1,inplace=True)
df1.drop('historyvisit_totalordernum',axis=1,inplace=True)  ###把重復(fù)的一列刪了
df1.drop('sampleid',axis=1,inplace=True)   ###把id列刪了
image.png

ps: pca用法http://www.itdecent.cn/p/8642d5ea5389

2.7 數(shù)據(jù)標(biāo)準(zhǔn)化
# 數(shù)據(jù)標(biāo)準(zhǔn)化
from sklearn.preprocessing import StandardScaler

y=df1['label']
x=df1.drop('label',axis=1)

scaler = StandardScaler()
X= scaler.fit_transform(x)   #先用fit求得訓(xùn)練數(shù)據(jù)的標(biāo)準(zhǔn)差和均值,再用transform將數(shù)據(jù)轉(zhuǎn)化成

sklearn中標(biāo)準(zhǔn)化模塊的用法:https://blog.csdn.net/u011734144/article/details/84066784
幾種標(biāo)準(zhǔn)化的方法:
http://www.itdecent.cn/p/fa73a07cd750
考慮到基本都是負(fù)荷正態(tài)分布或者偏態(tài)分布的,所以這里是標(biāo)準(zhǔn)化為標(biāo)準(zhǔn)正態(tài)分布,否則的話,采用min-max標(biāo)準(zhǔn)化等其他方法可能會(huì)更好。
標(biāo)準(zhǔn)化后的數(shù)據(jù)形態(tài):

image.png

三、建模

先拆分訓(xùn)練集和數(shù)據(jù)集

from sklearn import model_selection

X_train,X_test,y_train,y_test = model_selection.train_test_split(X,y,test_size= 0.2,random_state=1)

PS:random_state相當(dāng)于添加隨機(jī)數(shù)種子進(jìn)行標(biāo)記http://www.itdecent.cn/p/4deb2cb2502f

3.1 邏輯回歸
from sklearn.linear_model import LogisticRegression
from sklearn import metrics
from sklearn.metrics import classification_report

lr = LogisticRegression()                                        # 實(shí)例化一個(gè)LR模型
lr.fit(X_train,y_train)                                          # 訓(xùn)練模型
y_prob = lr.predict_proba(X_test)[:,1]                           # 預(yù)測(cè)1類的概率
y_pred = lr.predict(X_test)                                      # 模型對(duì)測(cè)試集的預(yù)測(cè)結(jié)果
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)                 # 模型準(zhǔn)確率


print('模型準(zhǔn)確率為:{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
  • 此時(shí)的模型準(zhǔn)確率是以分為0類、1類的概率大小進(jìn)行分類的,所以理論上通過調(diào)整分類閾值,可以達(dá)到更高的精度。
  • 在不對(duì)閾值進(jìn)行調(diào)整情況下,從混淆矩陣中可以看出,1類的recall偏小,表明更容易被分為0類,這種情況對(duì)應(yīng)的是ROC曲線中的左下方低點(diǎn),分為0類的閾值應(yīng)該調(diào)大,分為1類的閾值應(yīng)該調(diào)低。
3.2 樸素貝葉斯
from sklearn.naive_bayes import GaussianNB
from sklearn import metrics
from sklearn.metrics import classification_report

gnb = GaussianNB()                                                # 實(shí)例化一個(gè)LR模型
gnb.fit(X_train,y_train)                                          # 訓(xùn)練模型
y_prob = gnb.predict_proba(X_test)[:,1]                           # 預(yù)測(cè)1類的概率
y_pred = gnb.predict(X_test)                                      # 模型對(duì)測(cè)試集的預(yù)測(cè)結(jié)果
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)                 # 模型準(zhǔn)確率


print('模型準(zhǔn)確率為:{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

查看了一下預(yù)測(cè)的分為1類和0類的概率:
image.png

所以這里的貝葉斯概率并不是真實(shí)概率(真實(shí)概率為所有獨(dú)立變量概率的成績(jī),理論上是一個(gè)很小很小的值,而不會(huì)是一個(gè)mean值在0.5左右的值)。
有空可以研究下他的算法代碼

3.3 支持向量機(jī)
from sklearn.svm import SVC
from sklearn import metrics
from sklearn.metrics import classification_report

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)                                        # 模型對(duì)測(cè)試集的預(yù)測(cè)結(jié)果
fpr_svc,tpr_svc,threshold_svc = metrics.roc_curve(y_test,y_prob)     # 獲取真陽率、偽陽率、閾值
auc_svc = metrics.auc(fpr_svc,tpr_svc)                              # 模型準(zhǔn)確率
score_svc = metrics.accuracy_score(y_test,y_pred)

print('模型準(zhǔn)確率為:{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
  • 不設(shè)置max_iter會(huì)陷入死循環(huán),說明一直無法找到最優(yōu)解平面。而且無論是rbf核還是多項(xiàng)式核,整體的預(yù)測(cè)精度都很低。
3.4 決策樹
from sklearn import tree
from sklearn import metrics
from sklearn.metrics import classification_report

dtc = tree.DecisionTreeClassifier()                              # 建立決策樹模型
dtc.fit(X_train,y_train)                                         # 訓(xùn)練模型
y_prob = dtc.predict_proba(X_test)[:,1]                          # 預(yù)測(cè)1類的概率
y_pred = dtc.predict(X_test)                                     # 模型對(duì)測(cè)試集的預(yù)測(cè)結(jié)果 
fpr_dtc,tpr_dtc,threshod_dtc= metrics.roc_curve(y_test,y_prob)   # 獲取真陽率、偽陽率、閾值               
auc_dtc = metrics.auc(fpr_dtc,tpr_dtc)                           # AUC得分
score_dtc = metrics.accuracy_score(y_test,y_pred)                # 模型準(zhǔn)確率

print('模型準(zhǔn)確率為:{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
3.5 隨機(jī)森林
from sklearn.ensemble import RandomForestClassifier
from sklearn import metrics
from sklearn.metrics import classification_report

rfc = RandomForestClassifier()                                     # 建立隨機(jī)森林分類器
rfc.fit(X_train,y_train)                                           # 訓(xùn)練隨機(jī)森林模型
y_prob = rfc.predict_proba(X_test)[:,1]                            # 預(yù)測(cè)1類的概率
y_pred=rfc.predict(X_test)                                         # 模型對(duì)測(cè)試集的預(yù)測(cè)結(jié)果
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)                  # 模型準(zhǔn)確率

print('模型準(zhǔn)確率為:{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
3.6 模型比較
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))                                             # 支持向量機(jī)模型
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))                          # 隨機(jī)森林

plt.legend(loc='lower right',prop={'size':25})
plt.xlabel('誤診率')
plt.ylabel('靈敏度')
plt.title('ROC曲線')
image.png

隨機(jī)森林勝出!

調(diào)用決策樹畫圖:
http://ywtail.github.io/2017/06/08/sklearn%E5%86%B3%E7%AD%96%E6%A0%91%E5%8F%AF%E8%A7%86%E5%8C%96/
本來想畫圖,但是又卡了,可能是因?yàn)樽兞刻嗔耍胂胍彩?。?/p>

四、RFM分析和用戶畫像

4.1 RFM分析

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


image.png

在本案例中,我們選擇lasthtlordergap(距離上次下單的時(shí)長(zhǎng))、ordernum_oneyear(用戶年訂單數(shù))、consume_level(用戶消費(fèi)水平)分別作為R、F、M的值,對(duì)我們的用戶群體進(jìn)行聚類。

rfm = df1[['lasthtlordergap','ordernum_oneyear','consume_level']]  #consume_level是PCA后的特征變量

#歸一化
from sklearn.preprocessing import MinMaxScaler
scaler = MinMaxScaler()
scaler.fit(rfm)
rfm = pd.DataFrame(scaler.transform(rfm),columns=['recency','frequency','monetary'])

# 分箱
rfm['R']=pd.qcut(rfm["recency"], 2)
rfm['F']=pd.qcut(rfm["frequency"], 2)
rfm['M']=pd.qcut(rfm["monetary"], 2)

# 根據(jù)分箱情況編碼
from sklearn.preprocessing import LabelEncoder

#從0開始編碼的,所以這里直接編碼是可以的
rfm['R']=LabelEncoder().fit(rfm['R']).transform(rfm['R'])      #這里需要注意,R為距離上次下單的市場(chǎng),越小則代表價(jià)值越高,所以這一點(diǎn)是反的
rfm['F']=LabelEncoder().fit(rfm['F']).transform(rfm['F'])
rfm['M']=LabelEncoder().fit(rfm['M']).transform(rfm['M'])

def get_label(r,f,m):
    if (r==0)&(f==1)&(m==1):
        return '高價(jià)值客戶'
    if (r==1)&(f==1)&(m==1):
        return '重點(diǎn)保持客戶'
    if((r==0)&(f==0)&(m==1)):
        return '重點(diǎn)發(fā)展客戶'
    if (r==1)&(f==0)&(m==1):
        return '重點(diǎn)挽留客戶'
    if (r==0)&(f==1)&(m==0):
        return '一般價(jià)值客戶'
    if (r==1)&(f==1)&(m==0):
        return '一般保持客戶'
    if (r==0)&(f==0)&(m==0):
        return '一般發(fā)展客戶'
    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)
image.png

各類客戶的占比:

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

plt.figure(figsize=(12,12))
colors=['deepskyblue','steelblue','lightskyblue','aliceblue','skyblue','cadetblue','cornflowerblue','dodgerblue']
plt.pie(temp,radius=1,autopct='%.1f%%',pctdistance=0.75,colors=colors)
plt.pie([1],radius=0.6,colors='w')   ##可以用這種方式畫空心
plt.title('客戶細(xì)分情況')
plt.legend(temp.index)
image.png
  • 潛在客戶占比達(dá)12。3%,這類客戶是rmf指標(biāo)均不是很好的客戶,有待開發(fā);
  • 高價(jià)值客戶11%,重點(diǎn)保持客戶10.1%,重點(diǎn)發(fā)展客戶7%,這是要重點(diǎn)關(guān)注的客戶群體。
4.2 用戶畫像

其實(shí)我們并不想將用戶分的這么細(xì),并且我們其實(shí)有挺多的用戶行為特征數(shù)據(jù),所以也并不想僅用RFM這3個(gè)指標(biāo)進(jìn)行分析。所以,我們接下來用K-Means聚類的方法將用戶分為3類,觀察不同類別客戶的特征。

# 選取出幾個(gè)刻畫用戶的重要指標(biāo)
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 = df1[user_feature]
user_attributes.head()

# 數(shù)據(jù)標(biāo)準(zhǔn)化
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)                                                     # 建立KMean模型
Kmeans.fit(user_attributes)                                                     # 訓(xùn)練模型
k_char=Kmeans.cluster_centers_                                                  # 得到每個(gè)分類的質(zhì)心
personas=pd.DataFrame(k_char.T,index=user_feature,columns=['0類','1類','2類'])  # 用戶畫像表

plt.figure(figsize=(5,10))
sns.heatmap(personas, xticklabels=True, yticklabels=True, square=False, linewidths=.5, annot=True, cmap='Blues')
image.png
  • 2類用戶的R(lasthtlordergap)為-0.17非常?。≧越小越好,這里是反的),F(xiàn)(ordernum_oneyear)為1.1比較高了,M(consume_level)為1.3也幾乎是最高的。很明顯,2類客戶為我們的“高價(jià)值客戶”;而0類中幾乎都是白格子,無論是客戶價(jià)值還是消費(fèi)水平值都是最低的,很明顯,這一類我們將其歸為“低價(jià)值客戶”;剩下的1類我們將其稱為“中等群體”。
plt.figure(figsize=(9,9))

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

fig, ax = plt.subplots(figsize=(10,10))
colors=['aliceblue','steelblue','lightskyblue']
types=['中等群體','低價(jià)值用戶','高價(jià)值用戶']
ax.pie(percent,radius=1,autopct='%.2f%%',pctdistance=0.75,colors=colors,labels=types)
ax.pie([1], radius=0.6,colors='w')
image.png
  • 可以看到,“低價(jià)值客戶”的占比非常之大,中等人群占比最小。
4.3 用戶畫像分析
  • 高價(jià)值用戶分析(2類用戶)
    消費(fèi)水平高,客戶價(jià)值大,追求高品質(zhì),對(duì)酒店星級(jí)要求高,訪問頻率和預(yù)定頻率都較高,提前預(yù)定的時(shí)間都較短,決策一般都較快(日均訪問數(shù)少),訂單取消率較高,可以分析出這類客戶商務(wù)屬性偏重,可能隨時(shí)要出差,因此都不會(huì)提前預(yù)定,可能出差隨時(shí)會(huì)取消,因此酒店取消率也會(huì)更高一點(diǎn)。sid的值較大,說明高價(jià)值客戶群體多集中在老客戶中。價(jià)格敏感度較高,說明可能比較要求性價(jià)比。h值非常小,可能訪問和預(yù)定時(shí)間多在半夜或是清晨。
    這部分客戶對(duì)于我們而言是非常重要的,因此我們需要對(duì)其實(shí)施個(gè)性化的營(yíng)銷:
    1、為客戶提供更多差旅酒店信息。
    2、多推薦口碑好、性價(jià)比高的商務(wù)酒店。
    3、推薦時(shí)間集中在半夜或是清晨。
  • 中等價(jià)值用戶分析(1類用戶)
    消費(fèi)水平和客戶價(jià)值都偏低,對(duì)酒店品質(zhì)也不太追求,訪問和預(yù)定頻率也都較高,提前預(yù)定的時(shí)間是三類中最長(zhǎng)的,最值得注意的是,0類客戶中有兩個(gè)顏色非常深的藍(lán)色格子,是用戶決策和近3個(gè)月的日均訪問數(shù)??梢钥闯?,這類客戶通常很喜歡逛酒店界面,在決定要訂哪家酒店前通常會(huì)花費(fèi)非常多的時(shí)間進(jìn)行瀏覽才能做出選擇,并且一般都會(huì)提前很久訂好房。我們可以給這類客戶打上“謹(jǐn)慎”的標(biāo)簽。我們可以合理推斷,這一類客戶,可能預(yù)定酒店的目的多為出門旅行。
    針對(duì)這部分客戶,我們需要:
    1、盡可能多地進(jìn)行推送,因?yàn)榇祟惪蛻敉ǔ1容^喜歡瀏覽。
    2、推送當(dāng)?shù)芈糜钨Y訊,因?yàn)檫@類客戶旅游出行的概率較大。
    3、多推薦價(jià)格相對(duì)實(shí)惠的酒店。
  • 低價(jià)值用戶分析(0類用戶)
    消費(fèi)水平和客戶價(jià)值極低,對(duì)酒店品質(zhì)不追求,偏好價(jià)格較低,決策時(shí)間很短,訪問和預(yù)定頻率很低,sid值很低,說明新客戶居多。
    針對(duì)這部分客戶,我們需要:
    1、不建議花費(fèi)過多營(yíng)銷成本,但因?yàn)樾掠脩艟佣啵瑢儆跐撛诳蛻?,可以維持服務(wù)推送。
    2、推送的內(nèi)容應(yīng)多為大減價(jià)、大酬賓、跳樓價(jià)之類的。
    3、此類用戶占比居多,可進(jìn)一步進(jìn)行下沉分析,開拓新的市場(chǎng)。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容