用戶流失預(yù)警項(xiàng)目總結(jié)

本文是對(duì)攜程用戶流失預(yù)測(cè)案例的一個(gè)總結(jié),主要任務(wù)是對(duì)客戶流失率進(jìn)行建模分析,并挖掘出影響用戶流失的關(guān)鍵因素

目錄:

  • 項(xiàng)目介紹

  • 問(wèn)題分析

  • 數(shù)據(jù)探索

  • 特征工程

  • 建模分析

  • 總結(jié)

一、項(xiàng)目介紹

攜程作為中國(guó)領(lǐng)先的綜合性旅行服務(wù)公司,每天向超過(guò)2.5億會(huì)員提供全方位的旅行服務(wù),在這海量的網(wǎng)站訪問(wèn)量中,我們可分析用戶的行為數(shù)據(jù)來(lái)挖掘潛在的信息資源。其中,客戶流失率是考量業(yè)務(wù)成績(jī)的一個(gè)非常關(guān)鍵的指標(biāo)。此次分析的目的是為了深入了解用戶畫(huà)像及行為偏好,找到最優(yōu)算法,挖掘出影響用戶流失的關(guān)鍵因素,從而更好地完善產(chǎn)品設(shè)計(jì)、提升用戶體驗(yàn)。(項(xiàng)目及數(shù)據(jù)來(lái)源

二、問(wèn)題分析

這個(gè)項(xiàng)目要解決的問(wèn)題是關(guān)于用戶流失的,在官方提供的字段和解釋中,有一個(gè)label字段,這個(gè)是目標(biāo)變量,也就是我們需要進(jìn)行預(yù)測(cè)的值。label=1代表客戶流失,label=0代表客戶未流失,很顯然這是個(gè)分類的預(yù)測(cè)問(wèn)題。

對(duì)于本項(xiàng)目而言,最終的評(píng)價(jià)標(biāo)準(zhǔn)是要求在精確度達(dá)到97%的情況下,最大化召回率

從業(yè)務(wù)角度理解這個(gè)評(píng)價(jià)標(biāo)準(zhǔn),攜程作為一個(gè)大平臺(tái),用戶量非常大,挽回用戶所需成本較大,所以需要有一個(gè)很高的精確率來(lái)降低不必要的成本浪費(fèi),同時(shí)也要盡可能的挽回流失客戶,所以需要盡可能高的召回率。

這里引申一下其他比賽常用的標(biāo)準(zhǔn):如無(wú)特別說(shuō)明,一般用的是PR曲線和ROC曲線。ROC曲線有一個(gè)突出優(yōu)勢(shì),就是不受樣本不均衡的影響(ROC曲線不受樣本不均衡問(wèn)題的影響

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

1、數(shù)據(jù)總體情況

官方共提供2個(gè)數(shù)據(jù)集,分別為訓(xùn)練集userlostprob_train.txt和測(cè)試集userlostprob_test.txt。訓(xùn)練集為2016.05.15-2016.05.21期間一周的訪問(wèn)數(shù)據(jù),測(cè)試集為2016.05.22-2016.05.28期間一周的訪問(wèn)數(shù)據(jù)。測(cè)試集不提供目標(biāo)變量label,需自行預(yù)測(cè)。為保護(hù)客戶隱私,不提供uid等信息。此外,數(shù)據(jù)經(jīng)過(guò)了脫敏,和實(shí)際商品的訂單量、瀏覽量、轉(zhuǎn)化率等有一些差距,但是不會(huì)影響這個(gè)問(wèn)題的可解性。

數(shù)據(jù)共有51個(gè)字段,除了目標(biāo)變量label,還有50個(gè)特征。

訓(xùn)練集數(shù)據(jù)總覽

目標(biāo)變量label存在一定程度的不均衡,但是程度不大,因此可以用PR曲線做模型性能評(píng)估。

label值統(tǒng)計(jì)

觀察數(shù)據(jù)集,并進(jìn)行指標(biāo)梳理。指標(biāo)可以分為三類,一類是訂單相關(guān)的指標(biāo),如入住日期、訂單數(shù)、取消率等;一類是與客戶行為相關(guān)的指標(biāo),如星級(jí)偏好、用戶偏好價(jià)格等;還有一類是與酒店相關(guān)的指標(biāo),如酒店評(píng)分均值、酒店評(píng)分人數(shù)、平均價(jià)格等。

字段指標(biāo)

這個(gè)數(shù)據(jù)集有大量的缺失值分布在各個(gè)特征中。51列中只有7列數(shù)據(jù)是完整的,arrival、sampleid、d、h、iforderpv_24h、sid、label。其他44列存在不同程度的缺失,其中historyvisit_7ordernum缺失率接近88%。后面會(huì)根據(jù)缺失情況,結(jié)合特征分布,選用合適的方法填充缺失值。

#缺失比例計(jì)算
na_rate = (len(df)-df.count())/len(df)
na_rate.sort_values(ascending=False,inplace=True)
x = df.shape[1] #用x代表數(shù)據(jù)列數(shù)

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

# 添加軸標(biāo)簽
plt.xlabel('data_na_rate')

# 添加刻度標(biāo)簽
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')

#設(shè)置X軸的刻度范圍
plt.xlim([0, 1])

各列缺失率

2、各個(gè)特征的分布
查看所有數(shù)值型特征的分布情況,一方面有利于特征工程中根據(jù)數(shù)據(jù)分布合理選用處理方法,包括異常值、缺失值處理,連續(xù)特征離散化處理;另一方面有助于深入了解用戶行為。

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

四、特征工程
數(shù)據(jù)和特征決定了機(jī)器學(xué)習(xí)效果的上限,而模型和算法只是逼近這個(gè)上限。特征工程是建模前的關(guān)鍵步驟,特征處理得好,可以提升模型的性能。
整個(gè)特征工程的任務(wù)主要包括:格式轉(zhuǎn)換、缺失值處理、衍生特征、聚類特征、獨(dú)熱編碼、標(biāo)準(zhǔn)化等。
1、時(shí)間特征處理

1)格式轉(zhuǎn)換
時(shí)間特征不存在缺失值,可以先處理。訪問(wèn)日期d和入住日期arrival是字符串格式,需要進(jìn)行格式轉(zhuǎn)換。這里使用pandas中常用的時(shí)間函數(shù)to_datetime(),將字符串格式轉(zhuǎn)換為日期格式。

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)衍生特征
衍生特征是根據(jù)現(xiàn)有特征衍生出來(lái)的一些特征,比如訪問(wèn)日期和實(shí)際入住日期之間的差值,入住日期是周幾,入住日期否為周末。在機(jī)器學(xué)習(xí)中,是否為周末這個(gè)特征往往是非常重要的。

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)
#查看用戶預(yù)定的與實(shí)際入住之間相隔的天數(shù)
df['booking_gap'] = (df['arrival'] -df['d']).map(lambda x: x.days).astype(int)

2、異常值處理
觀察到用戶偏好價(jià)格delta_price1、delta_price2,以及當(dāng)前酒店可訂最低價(jià)lowestprice存在一些負(fù)值,理論上酒店的價(jià)格不可能為負(fù)。同時(shí)數(shù)據(jù)分布比較集中,因此采取中位數(shù)填充。而客戶價(jià)值customer_value_profit、ctrip_profits也不應(yīng)該為負(fù)值,這里將其填充為0。deltaprice_pre2_t1是酒店價(jià)格與對(duì)手價(jià)差均值,可以為負(fù)值,無(wú)需處理。

# 查看最小值為負(fù)值的特征
df_min=df.min().iloc[4:]
df_min[df_min<0]
存在負(fù)值的列
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、缺失值處理
缺失值全部為數(shù)值型數(shù)據(jù),結(jié)合各個(gè)特征的含義及數(shù)據(jù)分布情況,進(jìn)行以下處理:
1)針對(duì)一些不可預(yù)計(jì)的數(shù)據(jù)用-999填充缺失值

#針對(duì)不可預(yù)計(jì)的數(shù)據(jù)用-999填充NA
fillNauWith999 = ['ordercanncelednum',  # 取消訂單數(shù) 242114
                  'landhalfhours',  # 24小時(shí)登陸時(shí)長(zhǎng) 28633
                  'starprefer',  # 星級(jí)偏好 225053
                  "consuming_capacity",  # 消費(fèi)能力指數(shù) 226108
                  'historyvisit_avghotelnum',  # 近3個(gè)月用戶歷史日均訪問(wèn)酒店數(shù) 302069
                  'delta_price1',  # 用戶偏好價(jià)格-24小時(shí)瀏覽最多酒店價(jià)格
                  'ordernum_oneyear',  # 年訂單數(shù)
                  'avgprice',  # 平均價(jià)格
                  'delta_price2',  # 用戶偏好價(jià)格-24小時(shí)瀏覽酒店平均價(jià)格
                  'customer_value_profit',  # 客戶近一年的價(jià)值
                  'ctrip_profits',  # 客戶價(jià)值
                  'lasthtlordergap',  # 一年內(nèi)距離上次下單時(shí)長(zhǎng) 缺失值占242114條記錄
                  'lastpvgap',  # 一年內(nèi)距上次訪問(wèn)時(shí)長(zhǎng) 缺失值共97127記錄
                  'cr',  # 用戶轉(zhuǎn)化率
                  'decisionhabit_user' #用戶決策習(xí)慣
                  ]

2)忽略兩端極值的影響,可以把businessrate_pre、businessrate_pre2、cancelrate_pre等一些特征近似看作正態(tài)分布,使用平均值填充缺失值。

#正態(tài)分布使用平均值填充
fillNauWithMean = ['commentnums',  # 酒店評(píng)論數(shù)
                   'novoters',  # 酒店當(dāng)前評(píng)論人數(shù)
                   'cancelrate',  # 當(dāng)前酒店歷史取消率 11718
                   'price_sensitive',  # 價(jià)格敏感指數(shù)
                   'hoteluv',  # 當(dāng)前酒店歷史UV
                   'hotelcr',  # 當(dāng)前酒店歷史轉(zhuǎn)化率
                   'cr_pre',  # 24小時(shí)歷史瀏覽次數(shù)最多酒店歷史cr 29397
                   'lowestprice',  # 當(dāng)前酒店可定最低價(jià)
                   'lowestprice_pre2',  # 24h 訪問(wèn)酒店可預(yù)定最低價(jià)
                   'customereval_pre2',  # 24小時(shí)歷史瀏覽酒店客戶評(píng)分均值 28633條記錄缺失
                   'commentnums_pre',  # 24小時(shí)歷史瀏覽次數(shù)最多酒店點(diǎn)評(píng)數(shù)
                   'commentnums_pre2',  # 24小時(shí)歷史瀏覽酒店點(diǎn)評(píng)數(shù)均值
                   'cancelrate_pre',  # 24小時(shí)內(nèi)已訪問(wèn)次數(shù)最多酒店歷史取消率
                   'novoters_pre2',  # 24小時(shí)歷史瀏覽酒店評(píng)分人數(shù)均值
                   'novoters_pre',  # 24小時(shí)歷史瀏覽次數(shù)最多酒店評(píng)分人數(shù)
                   'deltaprice_pre2_t1',  # 24小時(shí)內(nèi)已訪問(wèn)酒店價(jià)格與對(duì)手價(jià)差均值
                   'lowestprice_pre',  # 24小時(shí)內(nèi)已訪問(wèn)次數(shù)最多酒店可訂最低價(jià)
                   'uv_pre',  # 24小時(shí)歷史瀏覽次數(shù)最多酒店歷史uv
                   'uv_pre2',  # 24小時(shí)歷史瀏覽酒店歷史uv均值
                   'businessrate_pre',  # 24小時(shí)歷史瀏覽次數(shù)最多酒店商務(wù)屬性指數(shù)
                   'businessrate_pre2',  # 24小時(shí)內(nèi)已訪問(wèn)酒店商務(wù)屬性指數(shù)均值
                   'cityuvs',  # 昨日訪問(wèn)當(dāng)前城市同入住日期的app uv數(shù)
                   'cityorders',  # 昨日提交當(dāng)前城市同入住日期的app訂單數(shù)
                   'visitnum_oneyear'  # 年訪問(wèn)次數(shù)
                     ]

3)對(duì)于以下4個(gè)特征值,系統(tǒng)填充NA時(shí)一般是因?yàn)椴淮嬖跀?shù)據(jù),所以直接使用0填充缺失值

#這部分?jǐn)?shù)據(jù)系統(tǒng)填充NA是因?yàn)椴淮嬖跀?shù)據(jù),所以可以直接用0填充NA
fillfeatureswith0 = ['historyvisit_7ordernum', #近7天用戶歷史訂單數(shù)
                  'historyvisit_totalordernum', #近1年用戶歷史訂單數(shù)
                  'ordercanceledprecent', #用戶一年內(nèi)取消訂單率
                  'historyvisit_visit_detailpagenum'  # 7天內(nèi)訪問(wèn)酒店詳情頁(yè)數(shù)
                       ]

4)對(duì)于'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)

經(jīng)過(guò)以上處理后,df.info()如下,可以看到已經(jīng)沒(méi)有缺失的列了:

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

4、聚類特征
整個(gè)數(shù)據(jù)集中非常重要的兩部分信息,一個(gè)是用戶相關(guān)的數(shù)據(jù),一個(gè)是酒店相關(guān)的數(shù)據(jù)。因此把這兩類主體進(jìn)行一個(gè)聚類,并把類的標(biāo)簽作為一個(gè)新的特征。這里使用KMeans的方法做聚類處理,分別將用戶和酒店分成5個(gè)類別。

#標(biāo)準(zhǔn)化
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方法分五類,并將標(biāo)簽作為一個(gè)新的特征
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、連續(xù)特征離散化
在這個(gè)案例中,將某些數(shù)值型特征轉(zhuǎn)換成類別呈現(xiàn)更有意義,比如用戶決策習(xí)慣、星級(jí)偏好、平均價(jià)格、消費(fèi)能力指數(shù)等,同一類別表現(xiàn)出相似的屬性。同時(shí)可以使得算法減少噪聲的干擾。而且在機(jī)器學(xué)習(xí)中,一般很少直接將連續(xù)值作為邏輯回歸模型的特征輸入。特征離散化以后,可以簡(jiǎn)化邏輯回歸模型,降低了模型過(guò)擬合的風(fēng)險(xiǎn)。后面會(huì)用到邏輯回歸模型,所以在這里還是先做離散化處理。
根據(jù)業(yè)務(wù)經(jīng)驗(yàn)選擇合適的連續(xù)型特征,在一定的數(shù)值范圍內(nèi)劃分分區(qū)。

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

離散化之后的特征,以及酒店和用戶這兩個(gè)聚類特征,均為數(shù)值型,都需要轉(zhuǎn)換為字符串型,以便接下來(lái)進(jìn)行獨(dú)熱編碼:

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
對(duì)分類變量進(jìn)行獨(dú)熱編碼,可以解決分類器不好處理屬性數(shù)據(jù)的問(wèn)題,編碼后的特征都可以看做是連續(xù)的特征,并且在一定程度上也起到了擴(kuò)充特征的作用。

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、用戶分組特征
由于數(shù)據(jù)集沒(méi)有提供用戶uid,需要根據(jù)已有特征對(duì)用戶進(jìn)行分組,生成用戶標(biāo)簽usertag。這里采取了一種近似的方法,如果用戶的某些行為特征相同,則認(rèn)為是同一個(gè)用戶的行為。后面需要根據(jù)用戶標(biāo)簽分割數(shù)據(jù)集,同一個(gè)用戶的信息不能同時(shí)出現(xiàn)在訓(xùn)練集和測(cè)試集中,否則模型會(huì)過(guò)擬合。
這里用于判斷是否為同一用戶行為的特征有:用戶一年內(nèi)取消訂單數(shù)、近3個(gè)月用戶歷史日均訪問(wèn)酒店數(shù)、用戶年訂單數(shù)、客戶價(jià)值_近1年、客戶價(jià)值、用戶轉(zhuǎn)化率、年訪問(wèn)次數(shù),并且使用hash函數(shù)處理字符串。

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、特征的相關(guān)系數(shù)
查看各特征與label之間的關(guān)系,并除去相關(guān)系數(shù)小于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、標(biāo)準(zhǔn)化處理
對(duì)于一些基于距離的模型,需要標(biāo)準(zhǔn)化處理,比如回歸分析、神經(jīng)網(wǎng)絡(luò)、SVM。
而對(duì)于與距離計(jì)算無(wú)關(guān)的樹(shù)模型,不需要標(biāo)準(zhǔn)化處理,比如決策樹(shù)、隨機(jī)森林等,因?yàn)闃?shù)中節(jié)點(diǎn)的選擇只關(guān)注當(dāng)前特征在哪里切分對(duì)分類更好,即只在意特征內(nèi)部的相對(duì)大小,而與特征間的相對(duì)大小無(wú)關(guān)。
這里還是標(biāo)準(zhǔn)化處理下,后面會(huì)用到不同的模型做對(duì)比。

#將label,usertag列除開(kāi)進(jìn)行標(biāo)準(zhǔn)化
df1 = pd.DataFrame(df_drop['label']) 
df2 = df_drop.iloc[:,1:-1] #需要標(biāo)準(zhǔn)化的列
df3 = pd.DataFrame(df_drop['usertag'])
df2_columns = df2.columns.tolist() #將df2的列名提取出來(lái)保存
scaler = preprocessing.StandardScaler().fit(df2)
df2 = scaler.transform(df2)
df2 = pd.DataFrame(df2,columns=df2_columns) #標(biāo)準(zhǔn)化處理后的數(shù)據(jù)是array,轉(zhuǎn)換為DataFrame
df_new = pd.concat([df1,df2,df3],axis=1) 

10、分割數(shù)據(jù)集
在使用數(shù)據(jù)集訓(xùn)練模型之前,我們需要先將整個(gè)數(shù)據(jù)集分為訓(xùn)練集、驗(yàn)證集、測(cè)試集。訓(xùn)練集是用來(lái)訓(xùn)練模型的,通過(guò)嘗試不同的方法和思路使用訓(xùn)練集來(lái)訓(xùn)練不同的模型,再通過(guò)驗(yàn)證集使用交叉驗(yàn)證來(lái)挑選最優(yōu)的模型,通過(guò)不斷的迭代來(lái)改善模型在驗(yàn)證集上的性能,最后再通過(guò)測(cè)試集來(lái)評(píng)估模型的性能。
由于官方提供的數(shù)據(jù)已經(jīng)劃分好訓(xùn)練集和測(cè)試集,我們現(xiàn)在需要在原始訓(xùn)練集中劃分出訓(xùn)練集和驗(yàn)證集,這里是70%劃分為訓(xùn)練集,30%劃分為驗(yàn)證集
那究竟依據(jù)什么特性進(jìn)行劃分呢?劃分?jǐn)?shù)據(jù)集需注意時(shí)間性、地域性、層次性(stratifiedKFold)。在做本地?cái)?shù)據(jù)集劃分的時(shí)候需要基于用戶進(jìn)行劃分,也就是要保證劃分前后的數(shù)據(jù)是滿足獨(dú)立同分布的。另外,由于提供的是一周的數(shù)據(jù),時(shí)間序列特性不是很明顯,所以沒(méi)有按時(shí)間線對(duì)數(shù)據(jù)進(jìn)行劃分

def splitTrainTest(dataProcessed,percent=0.7): 
    splitnum=int(len(dataProcessed.index)* percent) #分割點(diǎn):70%
    dataProcessed.sort_values(by="usertag") #按照'usertafe'進(jìn)行排序
    dataProcessed.to_csv(r'F:\solo\processed.csv',sep=',',index=False)
    #前70%行生成訓(xùn)練集
    dataProcessed.iloc[:splitnum,].to_csv(r'F:\solo\processed_Train.csv', sep=',', index=False)
    #后30%行生成驗(yàn)證集
    dataProcessed.iloc[splitnum:, ].to_csv(r'F:\solo\processed_Test.csv', sep=',', index=False)
splitTrainTest(df_new,percent=0.7)

五、建模分析
對(duì)于一個(gè)分類問(wèn)題,一般經(jīng)常使用的模型有邏輯回歸、隨機(jī)森林、xgboost。在正常的情況下,xgboost會(huì)比隨機(jī)森林效果更好,但是如果數(shù)據(jù)的噪聲比較大的話,也會(huì)出現(xiàn)隨機(jī)森林的效果更好的情況。為了比較不同模型在這個(gè)分類問(wèn)題中的性能表現(xiàn),這里使用了三個(gè)模型分別訓(xùn)練和評(píng)估。
導(dǎo)入包,使用sklearn庫(kù)完成建模分析。

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

導(dǎo)入處理后的數(shù)據(jù)集,并刪除usertag標(biāo)簽。

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)

從訓(xùn)練集和測(cè)試集中分別提取特征和目標(biāo)變量label,訓(xùn)練模型后,用測(cè)試集評(píng)估模型的性能。

train_X = trainData.iloc[:,1:]  # 特征從第1列開(kāi)始選
train_Y = trainData.iloc[:,0]  # 第0列是label
test_X = testData.iloc[:,1:]
test_Y = testData.iloc[:,0]

1、邏輯回歸模型

1)導(dǎo)入模型

from sklearn import linear_model
from sklearn.linear_model import LogisticRegression

(2)模型性能評(píng)估
輸出準(zhǔn)確率accuracy、AUC面積以及精確度precision≥0.97條件下的最大召回率recall。

lr = LogisticRegression()
lr.fit(train_X,train_Y)  # 訓(xùn)練模型
test_pred_lr = lr.predict_proba(test_X)[:,1]  # 預(yù)測(cè)為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))  # 輸入真實(shí)值和預(yù)測(cè)值
print([score,auc])  # 準(zhǔn)確率、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條件下的最大召回率

邏輯回歸模型輸出

邏輯回歸模型過(guò)于簡(jiǎn)單,預(yù)測(cè)準(zhǔn)確率比較低,在precision≥0.97的情況下,最大recall僅為0.0001。
2、隨機(jī)森林模型

1)導(dǎo)入隨機(jī)森林分類器

from sklearn.ensemble import RandomForestClassifier

(2)模型性能評(píng)估
輸出準(zhǔn)確率accuracy、AUC面積以及精確度precision≥0.97條件下的最大召回率recall。

rfc = RandomForestClassifier(n_estimators=200) #迭代200次
rfc.fit(train_X,train_Y)  # 訓(xùn)練模型
test_pred_rfc = rfc.predict_proba(test_X)[:,1]  # 預(yù)測(cè)為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))  # 輸入真實(shí)值和預(yù)測(cè)值
print([score,auc])  # 準(zhǔn)確率、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條件下的最大召回率

隨機(jī)森林輸出

對(duì)于這個(gè)項(xiàng)目,隨機(jī)森林模型表現(xiàn)較好,迭代200次以后模型準(zhǔn)確率0.900,在precision≥0.97的情況下,最大recall已經(jīng)可以達(dá)到0.623
3)特征重要性
使用feature_importance方法,可以得到特征的重要性排序。當(dāng)然,還可以使用plot_importance方法,默認(rèn)的importance_type=“weight”,將其設(shè)置為“gain”,可以得到和feature_importance方法相同的結(jié)果。

#特征重要性
importance = rfc.feature_importances_
indices = np.argsort(importance)[::-1]  # np.argsort()返回?cái)?shù)值升序排列的索引,[::-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個(gè)特征中
用戶相關(guān)的指標(biāo):年訪問(wèn)次數(shù)、一年內(nèi)距上次訪問(wèn)時(shí)長(zhǎng)、訪問(wèn)時(shí)間點(diǎn)、用戶轉(zhuǎn)化率、一年內(nèi)距離上次下單時(shí)長(zhǎng)、提前預(yù)定時(shí)間、客戶價(jià)值。
酒店相關(guān)的指標(biāo):24小時(shí)內(nèi)已訪問(wèn)酒店商務(wù)屬性指數(shù)均值、24小時(shí)歷史瀏覽次數(shù)最多酒店歷史獨(dú)立訪客數(shù)、24小時(shí)內(nèi)已經(jīng)訪問(wèn)酒店可訂最低價(jià)均值、24小時(shí)歷史瀏覽酒店歷史獨(dú)立訪客戶均值、24小時(shí)內(nèi)已訪問(wèn)次數(shù)最多酒店可訂最低價(jià)
城市相關(guān)的指標(biāo):昨日訪問(wèn)當(dāng)前城市同入住日期的app uv數(shù)字、昨日提交當(dāng)前城市同入住日期的app訂單數(shù)
3、xgboost模型

1)導(dǎo)入分類器

#導(dǎo)入xgboost分類器
import xgboost as xgb
from xgboost.sklearn import XGBClassifier

2)模型調(diào)參
使用GridSearchCV(網(wǎng)格搜索)的方法調(diào)節(jié)xgboost模型的參數(shù),主要的影響參數(shù)有樹(shù)的最大深度、最小葉子節(jié)點(diǎn)樣本權(quán)重和、懲罰項(xiàng)系數(shù)gamma、使用數(shù)據(jù)占比、使用特征占比。這里分步調(diào)節(jié),分別代入param_test1,2,3來(lái)尋找最優(yōu)參數(shù)。

param_test1 = {
#首要的就是調(diào)整樹(shù)的深度、以及每個(gè)葉子節(jié)點(diǎn)的個(gè)數(shù)
'max_depth': range(3, 10, 2),
'min_child_weight': range(1, 6, 2)}

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

param_test3 = {
#colsample_bytree每棵樹(shù)隨機(jī)采樣的列數(shù)占比
#subsample 樣本隨機(jī)采樣的比例
'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)
# 模型最好的分?jǐn)?shù)、模型最好的參數(shù)、模型最好的評(píng)估器
print(gsearch1.best_score_ ,gsearch1.best_params_,gsearch1.best_estimator_)

(3)模型性能評(píng)估
使用上一步找到的最優(yōu)參數(shù)組合,代入模型進(jìn)行訓(xùn)練和評(píng)估。輸出準(zhǔn)確率accuracy、AUC面積以及精確度precision≥0.97條件下的最大召回率recall。

# 使用上一步找到的最優(yōu)參數(shù)組合,代入模型進(jìn)行訓(xùn)練和評(píng)估
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)  # 訓(xùn)練模型
test_pred_xgb = model.predict_proba(test_X)[:,1]  # 預(yù)測(cè)為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))  # 輸入真實(shí)值和預(yù)測(cè)值
print([score,auc])  # 準(zhǔn)確率、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條件下的最大召回率

得到的模型準(zhǔn)確率0.898,在precision≥0.97的情況下,最大recall可以達(dá)到0.527。

xgboost模型最大召回率

(4)特征重要性
從xgboost模型也可以得到影響用戶流失的特征,按照重要性排序,排名前10的特征有:
24小時(shí)內(nèi)是否訪問(wèn)訂單填寫(xiě)頁(yè)、提前預(yù)訂時(shí)間、用戶轉(zhuǎn)化率、近7天用戶歷史訂單數(shù)、用戶消費(fèi)能力指數(shù)、用戶決策習(xí)慣、用戶年訂單數(shù)、訪問(wèn)時(shí)間點(diǎn)、用戶星級(jí)偏好、年訪問(wèn)次數(shù)等。
使用隨機(jī)森林模型和xgboost模型得到的在top10特征差異較大,重合的特征只有3個(gè):訪問(wèn)時(shí)間點(diǎn)、年訪問(wèn)次數(shù)、用戶轉(zhuǎn)化率。

#特征重要性
importance = model.feature_importances_
indices = np.argsort(importance)[::-1]  # np.argsort()返回?cái)?shù)值升序排列的索引,[::-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曲線
接下來(lái)看下隨機(jī)森林和xgboost模型的ROC曲線和PR曲線,綜合比較模型性能。
ROC曲線

PR曲線

這兩個(gè)模型的ROC曲線和PR曲線差異不大,總體而言隨機(jī)森林模型比xgboost模型表現(xiàn)好。從評(píng)定標(biāo)準(zhǔn)來(lái)看,隨機(jī)森林的召回率(0.623)比xgboost模型召回率(0.527)高一些。認(rèn)為可能是因?yàn)閿?shù)據(jù)缺失較多,造成了噪音比較大。

六、總結(jié)

1、特征工程
缺失值和異常值處理是關(guān)鍵,根據(jù)數(shù)據(jù)和模型選擇是否需要獨(dú)熱編碼和標(biāo)準(zhǔn)化,按照業(yè)務(wù)經(jīng)驗(yàn)合理構(gòu)造衍生特征和聚類特征。篩選特征的方法有很多種,比如方差、卡方值、相關(guān)系數(shù)等,這里用了樹(shù)模型的特征重要性。特征工程決定了機(jī)器學(xué)習(xí)效果的上限,模型優(yōu)化只能無(wú)限接近這個(gè)上限。

2、模型對(duì)比結(jié)果
使用邏輯回歸、隨機(jī)森林和xgboost三種模型做對(duì)比分析,按照評(píng)定標(biāo)準(zhǔn),在精確度≥0.97的條件下,隨機(jī)森林模型的性能最優(yōu),召回率可以達(dá)到0.636。該模型可以直接上線用于用戶流失預(yù)測(cè)。

3、影響用戶流失的關(guān)鍵因素
從模型表現(xiàn)上看,隨機(jī)森林效果最優(yōu)。根據(jù)特征重要性排序,提取影響用戶流失的最關(guān)鍵因素。其中用戶相關(guān)的指標(biāo)有:年訪問(wèn)次數(shù)、訪問(wèn)時(shí)間點(diǎn)、一年內(nèi)距上次訪問(wèn)時(shí)長(zhǎng)、用戶轉(zhuǎn)化率、一年內(nèi)距離上次下單時(shí)長(zhǎng)。酒店相關(guān)的指標(biāo)有:24小時(shí)內(nèi)已訪問(wèn)酒店商務(wù)屬性指數(shù)均值、24小時(shí)內(nèi)已訪問(wèn)酒店可訂最低價(jià)均值、24小時(shí)歷史瀏覽次數(shù)最多酒店歷史uv、24小時(shí)內(nèi)已訪問(wèn)次數(shù)最多酒店可訂最低價(jià)、24小時(shí)歷史瀏覽酒店歷史uv均值。城市相關(guān)的指標(biāo):昨日提交當(dāng)前城市同入住日期的app訂單數(shù)、昨日訪問(wèn)當(dāng)前城市同入住日期的app uv數(shù)。

代碼附件
提取碼:k88c

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。