客戶購買預測【分類模型的運用】
和鯨社群的一個新人賽,主要考察的是分類模型的運用。
主要嘗試了SVC,Logistic迴歸,隨機森林,LightGBM,透過網格搜尋調參,最終使用了隨機森林作為預測模型。線上第四期rank:27,AUC得分:0。91364870。
目前只提交了一次結果,還有很多提升最佳化的空間。
和鯨排行榜
以下是具體的分析處理過程。
案例背景說明
案例資料與一次葡萄牙銀行機構的營銷活動相關。銀行的客服人員至少需要聯絡客戶一次,以確認客戶是否有意願購買該銀行的產品(定期存款)。
任務是基本型別為分類任務,即透過客戶的一些行為特徵,來預測客戶是否購買該銀行的產品。
資料集說明
共18個可用欄位,其中y是因變數。資料說明來自網站截圖。
和鯨線上截圖資料說明
EDA,資料預處理,特徵工程
一、簡單的探索性分析
訓練集資訊一覽
載入資料後,簡單的看了一下,並沒有缺失資料。接下來,按照資料型別來分別觀察資料。
首先來看看分類資料
這邊我選擇了透過檢視每個分類變數的IV值。手動添加了一個signal值來表示變數更能預測0類還是1類
#定義了IV值計算的函式,並且把計算過程打印出來
def
woe_iv_compute
(
df
):
groups
=
df
。
iloc
[:,
0
]
labels
=
df
。
iloc
[:,
1
]
variables
=
np
。
unique
(
groups
)
groupname
=
df
。
columns
[
0
]
labelname
=
df
。
columns
[
1
]
all_0_count
=
(
df
[
labelname
]
==
0
)
。
sum
(
0
)
all_1_count
=
(
df
[
labelname
]
==
1
)
。
sum
(
0
)
woe_group
=
0
iv_group
=
0
iv_list
=
[]
for
item
in
variables
:
signal
=
‘+’
df_item
=
df
[
df
[
groupname
]
==
item
]
df_item_0_count
=
(
df_item
[
labelname
]
==
0
)
。
sum
(
0
)
df_item_1_count
=
(
df_item
[
labelname
]
==
1
)
。
sum
(
0
)
pi_0
=
df_item_0_count
/
all_0_count
pi_1
=
df_item_1_count
/
all_1_count
woe_i
=
np
。
log
(
pi_1
/
pi_0
)
iv_i
=
(
pi_1
-
pi_0
)
*
woe_i
woe_group
=
woe_group
+
woe_i
iv_group
=
iv_group
+
iv_i
if
pi_1
-
pi_0
<=
0
:
signal
=
‘-’
(
f
‘
{item}
_iv:’
,
signal
,
iv_i
)
iv_list
。
append
(
iv_i
)
(
‘woe:’
,
woe_group
)
(
‘iv:’
,
iv_group
)
return
iv_group
#探索分類變數的IV值
object_list
=
train_data
。
select_dtypes
(
“object”
)
for
item
in
object_list
:
(
f
‘
{item}
’
)
(
train_data
[
item
]
。
value_counts
())
woe_iv_compute
(
train_data
[[
item
,
‘y’
]])
(
‘-’
*
20
)
部分分類變數的IV值計算結果一覽
透過計算IV值,可以看到一些有趣的現象:retire的職業人群更可能購買產品;婚姻預測性較弱,單身非離異人群更可能購買產品;初等教育更不可能購買產品,預測能力比婚姻更強;是否違約的預測能力很弱;住房貸款強能預測是否會購買產品,其中沒有貸款的更可能購買……。
而每個分類變數的iv得分都在0.1以上,
於是我最終都保留了這些分類變數進入模型。
接下來,是對int型變數的一些探索:
首先是一個簡單的描述統計:
int型變數描述統計一覽
從Q1,Q2,Q3的取值來看,上述int型變數基本上都存在右偏。
還是具體的一個個來看。
age
利用y的取值,分開age資料,分別繪製age分佈圖
age_0 = train_data[train_data。y == 0][[‘age’]]
age_1 = train_data[train_data。y == 1][[‘age’]]
age_0。age。plot(kind= ‘kde’,label = ‘0’)
age_1。age。plot(kind= ‘kde’,label =‘1’)
plt。legend()
0/1人群的各自age分佈圖
從圖上也能看到,主要差距出現在20-60代更不可能購買,而60-80代更可能購買。
然後,我對age進行了分箱處理。為了找到age的最佳分箱,我這裡將分箱後的區間進行了IV值計算,選擇了分箱數折中但依然有較好的預測能力的箱子數。
#探索age的最佳分箱
bin_count = np。arange(4,12,1)
iv_s = []
#等頻分箱
‘’‘
for bin_c in bin_count:
age_y = pd。DataFrame(pd。qcut(train_data。age,bin_c))
age_y[’y‘] = train_data[’y‘]
print(’當前分箱數:‘,bin_c)
bin_c_iv = woe_iv_compute(age_y_2)
iv_s。append(bin_c_iv)
woe_iv_compute(age_y)
’‘’
#等距分箱
for bin_c in bin_count:
age_y_2 = pd。DataFrame(pd。cut(train_data。age,bin_c))
age_y_2[‘y’] = train_data[‘y’]
print(‘當前分箱數:’,bin_c)
bin_c_iv = woe_iv_compute(age_y_2)
iv_s。append(bin_c_iv)
sns。scatterplot(x = bin_count,y= iv_s)
分箱探索過程,和不同分箱的IV得分
9等距分箱得分
可以看到,9等距分箱具有比較好的預測能力,同時可以看到,60-77這個區間裡的人群,更容易購買活動產品。
balance【每年賬戶的平均餘額】
和age一樣的處理方式。
train_data。balance。plot(kind=‘box’)
balance箱線圖
可以看到,balance大部分值都集中在0-10000以內。
balance不同等頻分箱IV得分
8等頻分箱IV得分
最終,透過8等頻分箱,得到了比較好的結果。同時可以看到,隨著年餘額的增多,更可能購買產品,年餘額為負的更不可能購買產品。
其他int型變數,用上述的方式進行了處理。
此處省略過程,只給出部分可以得出的結論:
1、通訊時長【duration】在0-113的更不可能購買,在449以上的更可能購買
2、在本次活動中,與該客戶交流過的次數【campaign】,大於18次的,幾乎全部沒有購買可能。
建模調參
因為資料集是不平衡的資料集,於是採用了分層抽樣來得到訓練集和驗證集。
from sklearn。model_selection import StratifiedShuffleSplit
ss = StratifiedShuffleSplit(n_splits=1,train_size=0。75,random_state=42)
X_train_v = X_train。values
X_test_v = X_test。values
y_train_v = y_train。values
#先將訓練集分成訓練子集和驗證子集
for train_code,validate_code in ss。split(X_train,y_train):
train_x = X_train_v[train_code]
validate_x = X_train_v[validate_code]
train_y = y_train_v[train_code]
validate_y = y_train_v[validate_code]
接下來,就是呼叫多個模型調參的過程。
from sklearn。model_selection import GridSearchCV
import lightgbm as lgb
from sklearn。linear_model import LogisticRegression
from sklearn。tree import DecisionTreeClassifier
from sklearn。ensemble import RandomForestClassifier
from sklearn。svm import SVC
from sklearn。neighbors import KNeighborsClassifier
#邏輯迴歸
param = {“penalty”: [“l1”, “l2”, ], “C”: [0。1, 1,5,10], “solver”: [“liblinear”,“saga”]}
gs = GridSearchCV(estimator=LogisticRegression(), param_grid=param, cv=5, scoring=“roc_auc”)
gs。fit(X_train_v,y_train_v)
#隨機森林
param = {‘n_estimators’:list(np。arange(10,100,10)),
‘min_samples_leaf’:[2,3,4]}
gs = GridSearchCV(estimator=RandomForestClassifier(), param_grid=param, cv=2, scoring=“roc_auc”)
gs。fit(train_x,train_y)
print(gs。best_params_)
y_pred = gs。best_estimator_。predict(validate_x)
print(classification_report(validate_y,y_pred))
#lightGBM
param = {
‘learning_rate’: [0。01,0。03,0。05,0。1,0。3,0。5],
‘num_leaves’: [15,30,45,80],
‘n_estimators’: [30,60,90,120]}
gs = GridSearchCV(estimator=LGBMClassifier(), param_grid=param, cv=5, scoring=“roc_auc”)
gs。fit(train_x,train_y)
print(gs。best_params_)
y_pred = gs。best_estimator_。predict(validate_x)
print(classification_report(validate_y,y_pred))
寫了一個畫ROC曲線的方法,因為本次提交比對的是AUC值
#畫roc-auc曲線
def roc_auc_curve(X,y,model):
FPR,TPR,thresholds=roc_curve(y,model。predict_proba(X)[:,1],pos_label=1)
auc=roc_auc_score(y,model。predict_proba(X)[:,1])
plt。figure()
plt。plot(FPR,TPR,color=‘red’,label=‘ROC curve (auc = %0。2f)’%auc)
plt。plot([0,1],[0,1],color=‘black’,linestyle=‘——’)
plt。xlim([-0。05,1。05])
plt。ylim([-0。05,1。05])
plt。xlabel(‘False Positive Rate’)
plt。ylabel(‘Recall’)
plt。title(‘ROC-Curve’)
plt。legend(loc=‘lower right’)
下面是隨機森林的roc曲線圖
隨機森林roc
下面是隨機森林的各變數的重要性得分圖
隨機森林feature_importance圖
#樹的視覺化程式碼
estimator = best_tree。estimators_[5]
from sklearn。tree import export_graphviz
# 匯出為dot 檔案
export_graphviz(estimator, out_file=‘yourpath/tree。dot’,
feature_names = X_train。columns,
class_names = [‘0’,‘1’],
rounded = True, proportion = False,
precision = 2, filled = True)
結。