![v2-fc6f2d663c6ab3cdfdc3b56d0ce41ad4_1440w.jpg?source=172ae18b](http://img-01.proxy.5ce.com/view/image?&type=2&guid=8157dbaf-4c2f-eb11-8da9-e4434bdf6706&url=https://pic4.zhimg.com/v2-fc6f2d663c6ab3cdfdc3b56d0ce41ad4_1440w.jpg?source=172ae18b)
一、背景
介绍有关会员营销预测的实际应用。会员部门造作会员营销时,希望通过数据预测在下一次营销活动时,响应活动会员的具体名单和响应率,一次制定针对性的营销策略。
本预测模型可用于业务部门周期性自动执行,结合现有的辅助决策平台筛选会员数据,满足营销需要。
二、涉及技术
数据预处理:二值化标志转化OneHotEncoder、基于方差分析的特征选择的数据降维SelectPercentile结合f_classif。
数据建模:管道方法Pipe、交叉验证cross_val_score配合StratifiedKFold和自定义得分计算方法、集成分类算法AdaBosstClassifier。
数据库:time、Numpy、Pandas和Sklearn,其中Sklearn是数据建模的核心。
重点:利用管道方法将多个数据处理的环节结合起来,形成处理管道,然后针对该对象做交叉验证并得到不同参数下检验效果,辅助于参数设置。
三、数据特征
数据来源:
链接:
https://pan.baidu.com/s/1e_HtRASWeVL3pqrFu6N3Zwpan.baidu.com提取码:b1sp
orde.xlsx,包括2个sheet:sheet1为本次案例的训练集,sheet2为本次案例的预测集。以下是数据概况:
- 特征变量数:13。
- 数据记录数:sheet1中的训练集数据记录数为39999,sheet2中的预 测集数据记录数为8843。
- 是否有NA值:有。
- 是否有异常值:无。
以下是本数据集的13个特征变量,包括:
- age:年龄,整数型变量。
- total_pageviews:总页面浏览量,整数型变量。
- edu:教育程度,分类型变量,值域[1,10]。
- edu_ages:受教育年限,整数型变量。
- user_level:用户等级,分类型变量,值域[1,7]。
- industry:用户行业划分,分类型变量,值域[1,15]。
- value_level:用户价值度分类,分类型变量,值域[1,6]。
- act_level:用户活跃度分类,分类型变量,值域[1,5]。
- sex:性别,值域为1或0。
- blue_money:历史订单的蓝券用券订单金额(优惠券的一种), 整数型变量。
- red_money:历史订单的红券用券订单金额(优惠券的一种),整 数型变量。
- work_hours:工作时间长度,整数型变量。
- region:地区,分类型变量,值域[1,41]。
目标变量response,1代表用户有响应,0代表用户未响应。
四、案例分析
# 描述:案例-基于AdaBoost的营销响应预测
导入库
import time
import numpy as np
import pandas as pd
from sklearn.discriminant_analysis import LinearDiscriminantAnalysis
from sklearn.ensemble import AdaBoostClassifier, ExtraTreesClassifier
from sklearn.feature_selection import RFE
from sklearn.metrics import accuracy_score
from sklearn.model_selection import cross_val_score, StratifiedKFold
from sklearn.pipeline import Pipeline, FeatureUnion
StratifiedKFold,cross_val_score:用来做交叉检验,前者用来将数据分为训练集和测试集;后者用来交叉检验。这里选择的 StratifiedKFold能够有效结合分类样本标签做数据集分割,而不是完全的随机选择和分割。这种方式在应对分类样本不均衡时尤为有效。
Linear Discriminant Analysis:线性判别分析(LDA)是一种用来实现两个或者多个对象特征分类方法。
SelectPercentile,f_classif:前者用来做特征选择的数量控制,后者 用来确定特征选择的得分计算标准。
AdaBoostClassifier是集成算法,用来做分类模型训练。
Recursive feature elimination(RFE):RFE递归特征消除的主要思想是反复构建模型,然后选出最好的(或者最差的)特征(根据系数来选),把选出来的特征放到一边,然后在剩余的特征上重复这个过程,直到遍历了所有的特征。在这个过程中被消除的次序就是特征的排序。
Pipeline:是一个“管道”,目的是将不同的环节结合起来应用,这 常用于多个流程和环节的反复性操作。例如本案例中,将特征选择和集 成算法结合起来形成一个“管道”对象,然后针对该对象训练不同参数下 对应交叉检验的结果。
FeatureUnion:把若干个transformer object组合成一个新的estimators。这个新的transformer组合了他们的输出,一个FeatureUnion对象接受一个transformer对象列表。
accuracy_score:准确率评估指标,用于分类算法。
1、基本状态查看
# 基本状态查看
def set_summary(df):
'''
查看数据集的记录数、维度数、前2条数据、描述性统计和数据类型
:param df: 数据框
:return: 无
'''
print('Data Overview')
print('Records: {0}tDimension{1}'.format(df.shape[0], (df.shape[1] - 1))) # 打印数据集X形状
print('-' * 30)
print(df.head(2)) # 打印前2条数据
print('-' * 30)
print('Data DESC')
print(df.describe()) # 打印数据基本描述性信息
print('Data Dtypes')
print(df.dtypes) # 打印数据类型
print('-' * 60)
打印返回值:
Data Overview
Records: 39999 Dimension113
------------------------------
age total_pageviews edu ... label_99 label_100 response
0 39.0 77516.0 1.0 ... 0 1 0
1 50.0 83311.0 1.0 ... 0 1 0
2 38.0 215646.0 2.0 ... 0 1 0
3 53.0 234721.0 2.0 ... 0 0 0
4 28.0 338409.0 1.0 ... 0 1 0
[5 rows x 114 columns]
------------------------------
Data DESC
age total_pageviews ... label_100 response
count 39998.000000 3.999800e+04 ... 39999.000000 39999.000000
mean 38.589654 1.895136e+05 ... 0.495212 0.239606
std 13.663490 1.053109e+05 ... 0.499983 0.426848
min 17.000000 1.228500e+04 ... 0.000000 0.000000
25% 28.000000 1.175282e+05 ... 0.000000 0.000000
50% 37.000000 1.783410e+05 ... 0.000000 0.000000
75% 48.000000 2.372685e+05 ... 1.000000 0.000000
max 90.000000 1.484705e+06 ... 1.000000 1.000000
[8 rows x 114 columns]
Data Dtypes
age float64
total_pageviews float64
edu float64
edu_ages float64
user_level float64
industry float64
value_level int64
act_level float64
sex float64
blue_money int64
red_money float64
work_hours int64
region float64
label_1 int64
label_2 int64
label_3 int64
label_4 int64
label_5 int64
label_6 int64
label_7 int64
label_8 int64
label_9 int64
label_10 int64
label_11 int64
label_12 int64
label_13 int64
label_14 int64
label_15 int64
label_16 int64
label_17 int64
...
label_72 int64
label_73 int64
label_74 int64
label_75 int64
label_76 int64
label_77 int64
label_78 int64
label_79 int64
label_80 int64
label_81 int64
label_82 int64
label_83 int64
label_84 int64
label_85 int64
label_86 int64
label_87 int64
label_88 int64
label_89 int64
label_90 int64
label_91 int64
label_92 int64
label_93 int64
label_94 int64
label_95 int64
label_96 int64
label_97 int64
label_98 int64
label_99 int64
label_100 int64
response int64
Length: 114, dtype: object
------------------------------------------------------------
2、缺失值查看
# 缺失值审查
def na_summary(df):
'''
查看数据集的缺失数据列、行记录数
:param df: 数据框
:return: 无
'''
na_cols = df.isnull().any(axis=0) # 每一列是否具有缺失值
print('NA Cols:')
print(na_cols) # 查看具有缺失值的列
print('-' * 30)
print('valid records for each Cols:')
print(df.count()) # 查看每一列有效值(非NA)的记录数
print('-' * 30)
print('Total number of NA lines is: {0}'.format(df.isnull().any(axis=1).sum())) # 查看具有缺失值的行总记录数
print('-' * 30)
打印返回值:
NA Cols:
age True
total_pageviews True
edu True
edu_ages True
user_level True
industry True
value_level False
act_level True
sex True
blue_money False
red_money True
work_hours False
region True
label_1 False
label_2 False
label_3 False
label_4 False
label_5 False
label_6 False
label_7 False
label_8 False
label_9 False
label_10 False
label_11 False
label_12 False
label_13 False
label_14 False
label_15 False
label_16 False
label_17 False
...
label_72 False
label_73 False
label_74 False
label_75 False
label_76 False
label_77 False
label_78 False
label_79 False
label_80 False
label_81 False
label_82 False
label_83 False
label_84 False
label_85 False
label_86 False
label_87 False
label_88 False
label_89 False
label_90 False
label_91 False
label_92 False
label_93 False
label_94 False
label_95 False
label_96 False
label_97 False
label_98 False
label_99 False
label_100 False
response False
Length: 114, dtype: bool
------------------------------
3、类样本均衡查看
# 类样本均衡审查
def label_summary(df):
'''
查看每个类的样本量分布
:param df: 数据框
:return: 无
'''
print('Labels samples count:')
print(df['value_level'].groupby(df['response']).count()) # 以response为分类汇总维度对value_level列计数统计
print('-' * 60)
打印返回值:
Labels samples count:
response
0 30415
1 9584
Name: value_level, dtype: int64
------------------------------------------------------------
4、数据预处理
# 数据预处理
# NA值替换
def na_replace(df):
'''
将数据集中的NA值使用自定义方法替换
:param df: 数据框
:return: NA值替换后的数据框
'''
na_rules = {'age': df['age'].mean(), #mean()函数求平均值
'total_pageviews': df['total_pageviews'].mean(),
'edu': df['edu'].median(), #median()函数求中值
'edu_ages': df['edu_ages'].median(),
'user_level': df['user_level'].median(),
'industry': df['user_level'].median(),
'act_level': df['act_level'].median(),
'sex': df['sex'].median(),
'red_money': df['red_money'].mean(),
'region': df['region'].median()
} # 字典:定义各个列数据转换方法
df = df.fillna(na_rules) # 使用指定方法填充缺失值
print('Check NA exists:')
print((df.isnull().any().sum())) # 查找是否还有缺失值
print(('-' * 30))
return df
打印返回值:
Check NA exists:
0
------------------------------
5、基于pipe管道的特征组合及模型训练
# 基于pipe的特征组合及模型训练
def pipeline_model(X, y=None, project_pipeline=None, train=True):
'''
建立一个包含特征组合以及模型训练的复合pipeline,实现基于管道的特征筛选、组合与模型训练一体化
:param X: 特征集
:param y: 预测目标集
:param project_pipeline: pipeline对象,训练阶段获取,默认为None
:param train: 所处阶段,默认为True
:return: 训练阶段返回pipeline对象,预测阶段返回预测值
'''
if train: # 如果是训练阶段
# 建立pipeline中用到的模型对象
model_etc = ExtraTreesClassifier() # ExtraTree,用于EFE的模型对象
model_rfe = RFE(model_etc) # 使用RFE方法提取重要特征
model_lda = LinearDiscriminantAnalysis() # LDA模型对象
model_adaboost = AdaBoostClassifier() # 分类对象
# 构建带有嵌套的pipeline
project_pipeline = Pipeline([
('feature_union', FeatureUnion( # 组合特征pipeline
transformer_list=[
('model_rfe', model_rfe), # 通过RFE中提取特征
('model_lda', model_lda), # 通过LDA提取特征
],
transformer_weights={ # 建立不同特征模型的权重
'model_rfe': 1, # RFE模型权重
'model_lda': 0.8, # LDA模型权重
},
)),
('model_adaboost', model_adaboost), # adaboost模型对象
])
# 设置参数值
project_pipeline.set_params(
feature_union__model_rfe__estimator__n_estimators=20) # ExtraTreesClassifier中n_estimators值
project_pipeline.set_params(
feature_union__model_rfe__estimator__n_jobs=-1) # ExtraTreesClassifier中n_jobs值
project_pipeline.set_params(
feature_union__model_rfe__n_features_to_select=20) # RFE中n_features_to_select值
project_pipeline.set_params(feature_union__model_lda__n_components=1) # LDA中n_components值
project_pipeline.set_params(feature_union__n_jobs=-1) # FeatureUnion中n_jobs值
# project_pipeline.get_params() # 打印pipline参数详情
# pipeline交叉检验
num = 4 # 交叉检验次数
cv = StratifiedKFold(num) # 设置交叉检验
score_list = list() # 建立空列表,用于存放交叉检验得分
time_list = list() # 建立空列表,用于存储时间
n_estimators_range = [50, 100, 150] # 设置pipeline中adaboost的n_estimators值域
for parameter in n_estimators_range: # 遍历每个参数值
t1 = time.time() # 记录交叉检验开始的时间
print(('set parameters: %s' % parameter)) # 打印当前模型使用的参数
project_pipeline.set_params(model_adaboost__n_estimators=parameter) # 通过管道设置分类模型参数
score_tmp = cross_val_score(project_pipeline, X, y, scoring='accuracy',
cv=cv) # 使用交叉检验计算得分
t2 = time.time() # 记录交叉检验结束时间
time_list.append(t2 - t1) # 计算交叉检验时间并追加到列表
score_list.append(score_tmp) # 将得分追加到列表
# 组合交叉检验得分和详情数据
data_mat = np.hstack(
(np.array([n_estimators_range, time_list]).T, np.array(score_list))) # 将时间与得分组合
socre_cols = ['n_estimators', 'time']
socre_cols.extend([''.join(['score', str(i)]) for i in range(num)])
score_pd = pd.DataFrame(data_mat, columns=socre_cols) # 建立数据框
score_pd['score_mean'] = score_pd.iloc[:, 2:-1].mean(axis=1) # 计算得分均值
score_pd['score_std'] = score_pd.iloc[:, 2:-2].std(axis=1) # 计算得分std
print('pipeline score details:')
print((score_pd.round(4))) # 打印每个参数得到的交叉检验指标数据,只保留2位小数
print(('-' * 60))
# 将最优参数设置到模型中,并训练pipeline
project_pipeline.set_params(model_adaboost__n_estimators=50) # 设置最优参数值
project_pipeline.fit(X, y) # 训练pipeline模型
return project_pipeline # 返回训练过的pipeline模型对象
else:
return project_pipeline.predict(X), project_pipeline.predict_proba(X) # 返回预测值及概率
打印返回数据:
pipeline score details:
n_estimators time score0 ... score3 score_mean score_std
0 50.0 318.9157 0.9110 ... 0.7940 0.9040 0.0143
1 100.0 328.4791 0.9138 ... 0.7970 0.9068 0.0137
2 150.0 328.1585 0.9146 ... 0.8027 0.9077 0.0135
6、数据应用
# 加载数据集
raw_data = pd.read_excel('order.xlsx', sheet_name=0) # 读出Excel的第一个sheet
X = raw_data.drop('response', axis=1) # 分割X
y = raw_data['response'] # 分割y
# 数据审查和预处理
set_summary(raw_data) # 基本状态查看
na_summary(raw_data) # 缺失值审查
label_summary(raw_data) # 类样本均衡审查
X_t = na_replace(X) # 替换缺失值
打印返回值:
Data Overview
Records: 8843 Dimension112
------------------------------
age total_pageviews edu edu_ages ... label_97 label_98 label_99 label_100
0 61 243019 10 1 ... 1 0 0 0
1 33 215596 4 5 ... 1 1 1 0
2 25 31350 2 10 ... 0 0 0 1
3 23 246965 2 10 ... 1 1 1 0
4 28 99838 1 13 ... 1 1 0 0
[5 rows x 113 columns]
------------------------------
Data DESC
age total_pageviews ... label_99 label_100
count 8843.000000 8.843000e+03 ... 8843.000000 8843.000000
mean 38.884428 1.903636e+05 ... 0.493385 0.499265
std 13.917154 1.069146e+05 ... 0.499985 0.500028
min 17.000000 1.349200e+04 ... 0.000000 0.000000
25% 28.000000 1.177010e+05 ... 0.000000 0.000000
50% 37.000000 1.775960e+05 ... 0.000000 0.000000
75% 48.000000 2.395390e+05 ... 1.000000 1.000000
max 90.000000 1.490400e+06 ... 1.000000 1.000000
[8 rows x 113 columns]
Data Dtypes
age int64
total_pageviews int64
edu int64
edu_ages int64
user_level float64
industry float64
value_level int64
act_level int64
sex int64
blue_money int64
red_money int64
work_hours int64
region float64
label_1 int64
label_2 int64
label_3 int64
label_4 int64
label_5 int64
label_6 int64
label_7 int64
label_8 int64
label_9 int64
label_10 int64
label_11 int64
label_12 int64
label_13 int64
label_14 int64
label_15 int64
label_16 int64
label_17 int64
...
label_71 int64
label_72 int64
label_73 int64
label_74 int64
label_75 int64
label_76 int64
label_77 int64
label_78 int64
label_79 int64
label_80 int64
label_81 int64
label_82 int64
label_83 int64
label_84 int64
label_85 int64
label_86 int64
label_87 int64
label_88 int64
label_89 int64
label_90 int64
label_91 int64
label_92 int64
label_93 int64
label_94 int64
label_95 int64
label_96 int64
label_97 int64
label_98 int64
label_99 int64
label_100 int64
Length: 113, dtype: object
------------------------------------------------------------
NA Cols:
age False
total_pageviews False
edu False
edu_ages False
user_level True
industry True
value_level False
act_level False
sex False
blue_money False
red_money False
work_hours False
region True
label_1 False
label_2 False
label_3 False
label_4 False
label_5 False
label_6 False
label_7 False
label_8 False
label_9 False
label_10 False
label_11 False
label_12 False
label_13 False
label_14 False
label_15 False
label_16 False
label_17 False
...
label_71 False
label_72 False
label_73 False
label_74 False
label_75 False
label_76 False
label_77 False
label_78 False
label_79 False
label_80 False
label_81 False
label_82 False
label_83 False
label_84 False
label_85 False
label_86 False
label_87 False
label_88 False
label_89 False
label_90 False
label_91 False
label_92 False
label_93 False
label_94 False
label_95 False
label_96 False
label_97 False
label_98 False
label_99 False
label_100 False
Length: 113, dtype: bool
------------------------------
valid records for each Cols:
age 8843
total_pageviews 8843
edu 8843
edu_ages 8843
user_level 8841
industry 8841
value_level 8843
act_level 8843
sex 8843
blue_money 8843
red_money 8843
work_hours 8843
region 8838
label_1 8843
label_2 8843
label_3 8843
label_4 8843
label_5 8843
label_6 8843
label_7 8843
label_8 8843
label_9 8843
label_10 8843
label_11 8843
label_12 8843
label_13 8843
label_14 8843
label_15 8843
label_16 8843
label_17 8843
...
label_71 8843
label_72 8843
label_73 8843
label_74 8843
label_75 8843
label_76 8843
label_77 8843
label_78 8843
label_79 8843
label_80 8843
label_81 8843
label_82 8843
label_83 8843
label_84 8843
label_85 8843
label_86 8843
label_87 8843
label_88 8843
label_89 8843
label_90 8843
label_91 8843
label_92 8843
label_93 8843
label_94 8843
label_95 8843
label_96 8843
label_97 8843
label_98 8843
label_99 8843
label_100 8843
Length: 113, dtype: int64
------------------------------
Total number of NA lines is: 7
------------------------------
Check NA exists:
0
# 分类模型训练
project_pipeline = pipeline_model(X_t, y) # 获得最佳分类模型参数信息
新数据集做预测
# 新数据集做预测
new_data = pd.read_excel('order.xlsx', sheetname=1) # 读取要预测的数据集
final_reponse = new_data['final_response'] # 获取最终的目标变量值
new_data = new_data.drop('final_response', axis=1) # 获得预测的输入变量X
set_summary(new_data) # 基本状态查看
na_summary(new_data) # 缺失值审查
new_X_t = na_replace(new_data) # 替换缺失值
new_X_final, new_X_pro = pipeline_model(new_X_t, project_pipeline=project_pipeline,
train=False) # 对数据集做特征选择
输出
# 输出预测值以及预测概率
predict_labels = pd.DataFrame(new_X_final, columns=['labels']) # 获得预测标签
predict_labels_pro = pd.DataFrame(new_X_pro, columns=['pro1', 'pro2']) # 获得预测概率
predict_pd = pd.concat((new_data, predict_labels, predict_labels_pro), axis=1) # 将预测标签、预测数据和原始数据X合并
print('Predict info')
print(predict_pd.head(5)) # 打印前5条结果
print('-' * 60)
打印返回值:
Predict info
age total_pageviews edu edu_ages ... label_100 labels pro1 pro2
0 61 243019 10 1 ... 0 0 0.521405 0.478595
1 33 215596 4 5 ... 0 0 0.518151 0.481849
2 25 31350 2 10 ... 1 0 0.515863 0.484137
3 23 246965 2 10 ... 0 0 0.527564 0.472436
4 28 99838 1 13 ... 0 0 0.509676 0.490324
[5 rows x 116 columns]
------------------------------------------------------------
将预测结果写入:
# 将预测结果写入Excel
writer = pd.ExcelWriter('order_predict_result.xlsx') # 创建写入文件对象
predict_pd.to_excel(writer, 'Sheet1') # 将数据写入sheet1
writer.save() # 保存文件
与实际效果相比较:
# 后续--与实际效果的比较
print('final accuracy: {0}'.format(accuracy_score(final_reponse, predict_labels)))
打印返回值:
final accuracy: 0.8279995476648196
结论:0.827的预测准确率很高,可具有会员数据运营参考价值。