星巴克廣告宣傳策略探索

New-Starbucks-Logo-1200x969.jpg

背景介紹

本數(shù)據(jù)原是星巴克的面試數(shù)據(jù),包含 120,000 個數(shù)據(jù)點,按照 2:1 的比例劃分為訓(xùn)練文件和測試文件。數(shù)據(jù)模擬的實驗測試了一項廣告宣傳活動,看看該宣傳活動能否吸引更多客戶購買定價為 10 美元的特定產(chǎn)品。由于公司分發(fā)每份宣傳資料的成本為 0.15 美元,所以宣傳資料最好僅面向最相關(guān)的人群。每個數(shù)據(jù)點都有一列表示是否向某個人發(fā)送了產(chǎn)品宣傳資料,另一列表示此人最終是否購買了該產(chǎn)品。每個人還有另外 7 個相關(guān)特征,表示為 V1-V7。

優(yōu)化策略

通過訓(xùn)練數(shù)據(jù)了解 V1-V7 存在什么規(guī)律表明應(yīng)該向用戶分發(fā)宣傳資料。具體而言,目標是最大化兩項指標:

  • 增量響應(yīng)率 (IRR)

IRR 表示與沒有收到宣傳資料相比,因為推廣活動而購買產(chǎn)品的客戶增加了多少。從數(shù)學(xué)角度來說,IRR 等于推廣小組的購買者人數(shù)與購買者小組客戶總數(shù)的比例 (treatment) 減去非推廣小組的購買者人數(shù)與非推廣小組的客戶總數(shù)的比例 (control)。

IRR = \frac{purch_{treat}}{cust_{treat}} - \frac{purch_{ctrl}}{cust_{ctrl}}

  • 凈增量收入 (NIR)

NIR 表示分發(fā)宣傳資料后獲得(丟失)了多少收入。從數(shù)學(xué)角度來講,NIR 等于收到宣傳資料的購買者總?cè)藬?shù)的 10 倍減去分發(fā)的宣傳資料份數(shù)的 0.15 倍,再減去沒有收到宣傳資料的購買者人數(shù)的 10 倍。

NIR = (10\cdot purch_{treat} - 0.15 \cdot cust_{treat}) - 10 \cdot purch_{ctrl}

  • 測試策略
    實際推廣客戶與預(yù)測推廣客戶表格:
5.jpg

針對預(yù)測應(yīng)該包含推廣活動的個人比較指標,即第一象限和第二象限。由于收到宣傳資料的第一組客戶(在訓(xùn)練集中)是隨機收到的,因此第一象限和第二象限的參與者人數(shù)應(yīng)該大致相同。 比較第一象限與第二象限可以知道宣傳策略未來效果如何即可。 也就是說,我們對預(yù)測參與宣傳推廣活動的客戶應(yīng)用兩項指標計算,力爭使其最大化。

設(shè)計構(gòu)想

  • 根據(jù)增量響應(yīng)率和凈增量收入兩項指標計算可以看出:在未寄送傳單情況下購買人數(shù)為0時,兩項指標達到最大。即理想情況下:我們準確預(yù)測到相應(yīng)宣傳購買的所有顧客,精準寄送傳單。
  • 基于這個判斷,我們應(yīng)該選擇預(yù)測收到宣傳之后更可能會去購買的客戶,同時他最好在沒有收到宣傳時不太可能購買。
  • 本次使用兩個簡單的計算策略:
    1.預(yù)測在收到宣傳資料更可能會購買的客戶,參與推廣活動;
    2.預(yù)測在收到宣傳資料更傾向購買且未收到宣傳資料不會主動購買的客戶,參與推廣活動。

構(gòu)想實施

導(dǎo)入數(shù)據(jù)并查看

# 導(dǎo)入工具包
import numpy as np
import pandas as pd
import scipy as sp
import sklearn as sk

from matplotlib import pyplot as plt
import seaborn as sns
%matplotlib inline
# 加載數(shù)據(jù)
train_data = pd.read_csv('./training.csv')
test_data = pd.read_csv('./Test.csv')
# 查看訓(xùn)練集
train_data.head()
6.png
train_data.info()
7.png
train_data['purchase'].value_counts()

0  83494
1   1040

train_data['Promotion'].value_counts()

Yes  42364
No  42170

# 查看特征分布
feature_list = ['V1', 'V2', 'V3', 'V4', 'V5', 'V6','V7']
# 查看收到推廣信息并購買用戶的特征分布
train_data.query('Promotion == "Yes" and purchase == 1')[feature_list].hist(figsize=(12,12));
# 查看收到推廣未購買的客戶特征分布
train_data.query('Promotion=="Yes" and purchase==0')[feature_list].hist(figsize=(12,12));
# 查看未收到宣傳資料而購買的用戶特征
train_data.query('Promotion=="No" and purchase==1')[feature_list].hist(figsize=(12,12));
# 查看未收到宣傳資料也未購買的用戶特征
train_data.query('Promotion=="No" and purchase==0')[feature_list].hist(figsize=(12,12));

收到宣傳且購買的用戶特征

1.png

收到宣傳未購買的用戶特征

2.png

未收到宣傳購買的用戶特征

3.png

未收到宣傳未購買的用戶特征

4.png

發(fā)現(xiàn):
  • V2,V3特征分布略有不同;
  • V4的第一分類,V5的第二分類表現(xiàn)出一定差異;
  • 但整體來說,沒有特別顯著的差別;
  • 綜上,我們嘗試使用xgb這樣分類能力較強的集成算法。

策略一

  • 挑選收到推廣更可能購買的用戶參加活動,即將訓(xùn)練集中promotion==’yes‘且purchase==1的數(shù)據(jù)標簽設(shè)為1,其他為0,進行二分類。
數(shù)據(jù)預(yù)處理
# 備份數(shù)據(jù)
train = train_data.copy()
test = test_data.copy()
from sklearn import preprocessing
# 對V2,V3變量進行標準化
train['V2'] = preprocessing.scale(train['V2'])
train['V3'] = preprocessing.scale(train['V3'])
# 對V1、V4、V5、V6、V7進行one_hot編碼
dummy_fields = ['V1', 'V4', 'V5', 'V6','V7']
for V in dummy_fields:
    dummies = pd.get_dummies(train[V],prefix =V,drop_first = False)
    train = pd.concat([train,dummies],axis =1)
train = train.drop(dummy_fields,axis=1)
# 標記收到推送后購買的用戶為1,其他為0
train['response'] = 0
train.loc[(train['Promotion']=='Yes') & (train['purchase']==1),'response'] = 1
# 將train數(shù)據(jù)分為訓(xùn)練集和驗證集
from sklearn.model_selection import train_test_split
Train, Valid = train_test_split(train, test_size=0.2, random_state=0)
features = ['V2', 'V3', 'V1_0', 'V1_1', 'V1_2','V1_3', 'V4_1', 'V4_2','V5_1', 'V5_2', 
            'V5_3', 'V5_4', 'V6_1', 'V6_2','V6_3', 'V6_4', 'V7_1', 'V7_2']
X_train,X_valid = Train[features],Valid[features] 
y_train,y_valid = Train['response'],Valid['response']
Train.head(2)
# 觀察訓(xùn)練集標簽
y_train.value_counts()

8.png

對連續(xù)數(shù)據(jù)v2,v3進行標準化,其他分類特征one_hot編碼,使用策略1對收到推廣之后購買的客戶標簽記為1,其余記為0
標簽:

  • 0 : 67040
  • 1 : 587
    可以看出:這是一個標簽分布非常不平衡的數(shù)據(jù)集,0標簽是1標簽數(shù)據(jù)的110多倍
y_valid.value_counts()
  • 0 : 16773
  • 1 : 134
    驗證數(shù)據(jù)的比例甚至更加懸殊
直接使用xgboost分類
from xgboost import XGBClassifier
from sklearn import metrics
eval_set_1 = [(X_train, y_train), (X_valid, y_valid)]
model_1 = XGBClassifier(  learning_rate = 0.05,
                          max_depth = 8,
                          min_child_weight = 1,
                          scale_pos_weight = 114, # 通過權(quán)重調(diào)節(jié)數(shù)據(jù)標簽的不平衡,114是0標簽/1標簽的比值
                          objective = 'binary:logistic',
                          seed = 42,
                          gamma = 0.1,
                          silent = True,
                          n_jobs = -1,
                          n_estimators = 200
                           )
model_1.fit(X_train, y_train, eval_set=eval_set_1,
          eval_metric="auc", verbose=True, early_stopping_rounds=30)
valid_pred_1 = model_1.predict(X_valid, ntree_limit=model_1.best_ntree_limit)
sk.metrics.confusion_matrix(y_valid, valid_pred_1)
9.png

簡單評估下這個分類結(jié)果,在標簽比列為16773:134的數(shù)據(jù)集中,如果隨機進行選擇,我們期望應(yīng)該是就像拋硬幣,各占一半,也就是8387:8386:67:67,現(xiàn)在9057:7716:34:100的結(jié)果顯然超過這個最低要求,說明在xgb算法中設(shè)置scale_pos_weight = 114這個權(quán)重參數(shù)起到了一定的調(diào)節(jié)作用。

# 創(chuàng)建獲得增量響應(yīng)率和凈增量收入的函數(shù)
def get_irr_nir(y_pred,df_valid=Valid):
    # 選取預(yù)測為1作為計算樣本
    df_pro = df_valid.iloc[np.where(y_pred==1)]
    
    cust_tre = df_pro.loc[df_pro['Promotion']=='Yes',:].shape[0]
    cust_con = df_pro.shape[0] - cust_tre
    purch_tre = df_pro.loc[df_pro['Promotion']=='Yes', 'purchase'].sum()
    purch_con = df_pro.loc[df_pro['Promotion']=='No', 'purchase'].sum()
    
    irr = purch_tre/cust_tre - purch_con/cust_con
    nir = 10*purch_tre - 0.15*cust_tre - 10*purch_con
    return irr,nir
irr,nir = get_irr_nir(valid_pred_1,Valid)
print('IRR: %.4f' % irr)
print('NIR: %.4f' % nir )

計算驗證集指標

  • IRR: 0.0187
  • NIR: 145.0000
將模型應(yīng)用到測試數(shù)據(jù)
df = test_data[['V1', 'V2', 'V3', 'V4', 'V5', 'V6', 'V7']]
# 對df數(shù)據(jù)進行標準化
V_col = ['V2', 'V3']
for v in V_col:
    df[v] = preprocessing.scale(df[v])
# 對V1、V4、V5、V6、V7進行one_hot編碼
dummy_fields = ['V1', 'V4', 'V5', 'V6','V7']
for V in dummy_fields:
    dummies = pd.get_dummies(df[V],prefix =V,drop_first = False)
    df = pd.concat([df,dummies],axis =1)
df = df.drop(dummy_fields,axis=1)

# 使用模型預(yù)測并輸出結(jié)果
target_pred_1 = model_1.predict(df,ntree_limit=model_1.best_ntree_limit)
irr,nir = get_irr_nir(target_pred_1,test)
print('IRR: %.4f' % irr)
print('NIR: %.4f' % nir )

輸出結(jié)果

  • irr: 0.0180
  • nir: 274.5
    這個結(jié)果似乎還不錯,但還不是特別令人滿意,再嘗試下其他方法
smote過采樣處理
# 使用smote方法過采樣
from imblearn.over_sampling import SMOTE
sm = SMOTE(random_state=42)
X_train_over, y_train_over = sm.fit_sample(X_train, y_train)
X_train_over = pd.DataFrame(X_train_over, columns=features)
y_train_over = pd.Series(y_train_over)
y_train_over.value_counts()
  • 1: 67040
  • 0: 67040
    經(jīng)過過采樣處理之后的標簽數(shù)量持平,這里簡單介紹一下過采樣和smote方法
    10.png

    過采樣就是制作與原數(shù)據(jù)相似的或者相同的數(shù)據(jù),增大其數(shù)量占比
    11.png

    smote是一種過采樣方法,通過隨機選擇需要過采樣的數(shù)據(jù)點,使用k近鄰算法匹配附近的同類樣本,并在他們之間添加新的樣本
過采樣之后使用xgb預(yù)測
eval_set_2 = [(X_train_over, y_train_over), (X_valid, y_valid)]
model_2 = XGBClassifier(learning_rate = 0.05,
                          max_depth = 8,
                          min_child_weight = 1,
                          objective = 'binary:logistic',
                          seed = 42,
                          gamma = 0.1,
                          silent = True,
                          n_estimators=200)
model_2.fit(X_train_over, y_train_over, eval_set=eval_set_2,
          eval_metric="auc", verbose=True, early_stopping_rounds=30)
valid_pred_2 = model_2.predict(X_valid, ntree_limit=model_2.best_ntree_limit)
sk.metrics.confusion_matrix(y_valid, valid_pred_2)
12.png
irr,nir = get_irr_nir(valid_pred_2,Valid)
print('IRR: %.4f' % irr)
print('NIR: %.4f' % nir )
  • IRR: 0.0207
  • NIR: 179.4500
    比之前略有提升,繼續(xù)看測試結(jié)果
# 使用模型預(yù)測并輸出結(jié)果
target_pred_2 = model_1.predict(df,ntree_limit=model_2.best_ntree_limit)
irr,nir = get_irr_nir(target_pred_2,test)
print('IRR: %.4f' % irr)
print('NIR: %.4f' % nir )
  • IRR: 0.0205
  • NIR: 435.05
    在測試集的表現(xiàn)較之前有很大提升

策略二

  • 挑選收到推廣之后更可能購買且未收到推廣更不可能購買的用戶。即分別訓(xùn)練兩個模型進行預(yù)測,一個在收到推廣的數(shù)據(jù)集上訓(xùn)練,一個未收到推廣數(shù)據(jù)集上訓(xùn)練,分別得到購買的可能性:Ptreat 和 Pcont,計算他們的差值deltaP = Ptreat - Pcont,選擇deltaP前30%的用戶。
數(shù)據(jù)預(yù)處理
# 選取兩個模型的訓(xùn)練和驗證數(shù)據(jù)
train_treat = Train[Train['Promotion']=='Yes']
train_cont = Train[Train['Promotion']=='No']
valid_treat = Valid[Valid['Promotion']=='Yes']
valid_cont = Valid[Valid['Promotion']=='No']

X_tt = train_treat[features_2]
X_tc = train_cont[features_2]
y_tt = train_treat['purchase']
y_tc = train_cont['purchase']
X_val_tt = valid_treat[features_2]
X_val_tc = valid_cont[features_2]
y_val_tt = valid_treat['purchase']
y_val_tc = valid_cont['purchase']

# 對訓(xùn)練數(shù)據(jù)使用smote方法過采樣
#from imblearn.over_sampling import SMOTE
#sm = SMOTE(random_state=42)
X_tt_over, y_tt_over = sm.fit_sample(X_tt, y_tt)
X_tt_over = pd.DataFrame(X_tt_over, columns=features)
y_tt_over = pd.Series(y_tt_over)
X_tc_over, y_tc_over = sm.fit_sample(X_tc, y_tc)
X_tc_over = pd.DataFrame(X_tc_over, columns=features)
y_tc_over = pd.Series(y_tc_over)
模型訓(xùn)練

收到宣傳用戶模型

eval_set_3 = [(X_tt_over, y_tt_over), (X_val_tt, y_val_tt)]
model_3 = XGBClassifier(learning_rate = 0.05,
                          max_depth = 8,
                          min_child_weight = 1,
                          objective = 'binary:logistic',
                          seed = 42,
                          gamma = 0.1,
                          silent = True,
                          n_estimators=200)
model_3.fit(X_tt_over, y_tt_over, eval_set=eval_set_3,
          eval_metric="auc", verbose=True, early_stopping_rounds=30)
valid_pred_3 = model_3.predict(X_val_tt, ntree_limit=model_3.best_ntree_limit)
sk.metrics.confusion_matrix(y_val_tt, valid_pred_3)
13.png

未收到宣傳用戶模型

eval_set_4 = [(X_tc_over, y_tc_over), (X_val_tc, y_val_tc)]
model_4 = XGBClassifier(learning_rate = 0.01,
                          max_depth = 7,
                          min_child_weight = 5,
                          objective = 'binary:logistic',
                          seed = 42,
                          gamma = 0.2,
                          silent = True,
                          n_estimators=200)
model_4.fit(X_tc_over, y_tc_over, eval_set=eval_set_4,
          eval_metric="auc", verbose=True, early_stopping_rounds=30)
14.png
# 使用模型預(yù)測概率
p_treat = model_3.predict_proba(df, ntree_limit=model_3.best_ntree_limit)[:,1]
p_cont = model_4.predict_proba(df, ntree_limit=model_4.best_ntree_limit)[:,1]
# 計算概率差值
delta_p = p_treat - p_cont
# 計算70%分位數(shù)
cut_num = np.percentile(delta_p,70)
# 選擇用戶
test_pred = np.where(delta_p > cut_num,1,0)
# 計算指標
irr,nir = get_irr_nir(test_pred,test)
print('IRR: %.4f' % irr)
print('NIR: %.4f' % nir )
  • IRR: 0.0229
  • NIR: 457.4000
    策略二獲得了更高指標分數(shù),甚至沒有進行迭代調(diào)優(yōu),沒有收到推廣的用戶模型auc值僅達到0.54,但它仍然輕松超過了策略一的分數(shù),不過策略二直接使用了策略一的部分參數(shù)設(shè)置,可以說是在策略一基礎(chǔ)上的一個發(fā)展策略

總結(jié)

本次星巴克客戶宣傳推廣策略的探索,其實是一個很有代表性的問題,商家謀求精準定位潛在營銷對象,實施精準推廣宣傳,降低轉(zhuǎn)化成本和行為成本,但是用戶特征往往差異很小,極難做出精確分辨,尤其是類似數(shù)據(jù)很不平衡的分類問題,準確率是一個誤導(dǎo)指標,xgboost的scale_pos_weight權(quán)重指標和SMOTE過采樣方法提供了一個比較好的解決方案,可以幫助快速實現(xiàn)分類性能的提升,但最重要的還是策略的內(nèi)在邏輯,好的方法與好的邏輯相結(jié)合,才能獲得好的結(jié)果。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

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

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