《产品营销模型之建置及预测》赛题说明
最终成绩:0.8225
背景:
A公司有一款在线服务的P产品,公司的营销通路是100%的网络媒介。A公司希望提供30天免费的P产品后,期望顾客能正式签约购买P产品之服务。但A公司发现‚每隔1~2天便对数以万计的顾客发送电子营销文宣,不但购买率低下,甚至造成诸多客诉。同时,客户之预期获利是以人工经验评估之,没有量化或模型工具之协助,不晓得到底应该使用广告全投放还是机器学习模型来做投放?
目标:
A公司希望发掘用户购买产品的行为习惯,建立产品精准营销模型,对有意向的客户进行精准营销,增加收入,减少开支。
评分标准:
我们通过混淆矩阵(Confusion matrix)来评价分类模型的准确率。准确率越高,说明正确预测出响应营销效果越好。
提交结果:
选手以训练数据为基础,建立产品营销模型,并上传一个测试结果的档案results.csv,文档中只有两个字段,分别是客户ID以及预测客户是否会购买(Predicted_Results)的结果。
字段说明
代码
"""准备工具"""
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
#查看缺失值
import missingno as msno
#时间模块
import time
from sklearn.model_selection import train_test_split,GridSearchCV
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import classification_report,roc_auc_score,accuracy_score
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import RandomForestClassifier
import xgboost
from sklearn.naive_bayes import GaussianNB
import gc
import warnings
warnings.filterwarnings('ignore')
#解决显示中文错误
import matplotlib as mpl
mpl.rcParams['font.sans-serif'] = [u'SimHei']
mpl.rcParams['axes.unicode_minus'] = False
pd.set_option('display.max_columns',None)
pd.set_option('display.max_rows',None)
"""写入文件"""
train_data = pd.read_csv('df_training.csv',na_values='?',names=['客户ID','产品使用分数','用户地区','性别','年龄','使用累计时间','点数余额','产品服务使用量','是否为使用信用卡付月费','是否为活跃用户','估计薪资','购买与否'],header=0)
ID不是特征不使用,购买与否为要预测的标签,查看缺失值,判断是否为离散型数据
train_data.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 6000 entries, 0 to 5999
Data columns (total 12 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 客户ID 6000 non-null int64
1 产品使用分数 4165 non-null float64
2 用户地区 4167 non-null object
3 性别 4156 non-null object
4 年龄 4179 non-null float64
5 使用累计时间 4207 non-null float64
6 点数余额 4232 non-null float64
7 产品服务使用量 4188 non-null float64
8 是否为使用信用卡付月费 4118 non-null float64
9 是否为活跃用户 4181 non-null float64
10 估计薪资 4161 non-null float64
11 购买与否 6000 non-null int64
dtypes: float64(8), int64(2), object(2)
memory usage: 562.6+ KB
这里认为nunique值大于15为连续性变量,反之则为离散变量
con = [] #收集连续型数据
dis = [] #收集离散型数据
for col in train_data.columns[1:-1]:
print(col)
print(train_data[col].value_counts())
print('缺失值比例',train_data[col].isnull().sum()/train_data.shape[0])
if train_data[col].nunique() > 15:
print('连续型')
con.append(col)
else:
print('离散型')
dis.append(col)
print()
先处理离散型数据,统一将空值填充为一个不影响的值,独热后将该列的独热列删除
data = train_data.copy()
data = data.fillna('999') #用xxx代表空值,便于独热后区分
hot_col = ['area','sex','time','service','monthly','active'] #独热时的名字
#将每一列进行独热编码,删除掉‘999’的独热列
area_hot = pd.get_dummies(data[dis[0]],prefix=hot_col[0])
area_hot = area_hot.drop('area_999',axis=1) #删
sex_hot = pd.get_dummies(data[dis[1]],prefix=hot_col[1])
sex_hot = sex_hot.drop('sex_999',axis=1) #删
time_hot = pd.get_dummies(data[dis[2]],prefix=hot_col[2])
time_hot = time_hot.drop('time_999',axis=1) #删
service_hot = pd.get_dummies(data[dis[3]],prefix=hot_col[3])
service_hot = service_hot.drop('service_999',axis=1) #删
monthly_hot = pd.get_dummies(data[dis[4]],prefix=hot_col[4])
monthly_hot = monthly_hot.drop('monthly_999',axis=1) #删
active_hot = pd.get_dummies(data[dis[5]],prefix=hot_col[5])
active_hot = active_hot.drop('active_999',axis=1) #删
数据格式转化,字符数据数值化
for col in dis[2:]:
data[col] = data[col].astype('float32')
for i in con:
data[i] = data[i].astype('float32')
# 可以使用LabelEncoder
data['用户地区'] = data['用户地区'].replace('Taipei',0)
data['用户地区'] = data['用户地区'].replace('Taichung',1)
data['用户地区'] = data['用户地区'].replace('Tainan',2)
data['用户地区'] = data['用户地区'].astype('float32')
data['性别'] = data['性别'].replace('Male',0)
data['性别'] = data['性别'].replace('Female',1)
data['性别'] = data['性别'].astype('float32')
对离散型数据进行缺失值填充,利用xgboost,用其他所有列对该列进行预测
for i in range(len(dis)):
start = time.time()
print('当前特征列为:%s'%(dis[i]))
print('第一步:构造特征与标签--------------------')
res = data[dis[i]]
y_train = res[res!=999]
#将其余的独热编码后的列和连续型数据组合在一起来做训练
com = [area_hot,sex_hot,time_hot,service_hot,monthly_hot,active_hot,data[con],data[dis[i]]]
df = pd.concat(com,axis=1)
#分为训练测试集
x_train = df[df[dis[i]]!=999].iloc[:,:-1]
x_test = df[df[dis[i]]==999].iloc[:,:-1]
print('第二步:建模与预测填充--------------------')
xgb = xgboost.XGBClassifier()
xgb.fit(x_train,y_train)
y_res = xgb.predict(x_test)
data[dis[i]][data[dis[i]]==999] = y_res
end = time.time()
print('运行时间:%s'%(end-start))
print()
for i in range(len(con)):
start = time.time()
print('当前特征列为:%s'%(con[i]))
print('第一步:构造特征与标签--------------------')
res = data[con[i]]
y_train = res[res!=999]
com = [area_hot,sex_hot,time_hot,service_hot,monthly_hot,active_hot,data[con]]
df = pd.concat(com,axis=1)
x_train = df[df[con[i]]!=999].drop(con[i],axis=1)
x_test = df[df[con[i]]==999].drop(con[i],axis=1)
print('第二步:建模与预测填充--------------------')
xgb = xgboost.XGBClassifier()
xgb.fit(x_train,y_train)
res_y = xgb.predict(x_test)
data[con[i]][data[con[i]]==999] = res_y
end = time.time()
print('运行时间:%s'%(end-start))
上面一步将离散型数据填充完成,接下来用离散型数据填充连续性数据
for i in range(len(con)):
start = time.time()
print('当前特征列为:%s'%(con[i]))
print('第一步:构造特征与标签--------------------')
res = data[con[i]]
y_train = res[res!=999]
com = [area_hot,sex_hot,time_hot,service_hot,monthly_hot,active_hot,data[con]]
df = pd.concat(com,axis=1)
x_train = df[df[con[i]]!=999].drop(con[i],axis=1)
x_test = df[df[con[i]]==999].drop(con[i],axis=1)
print('第二步:建模与预测填充--------------------')
xgb = xgboost.XGBClassifier()
xgb.fit(x_train,y_train)
res_y = xgb.predict(x_test)
data[con[i]][data[con[i]]==999] = res_y
end = time.time()
print('运行时间:%s'%(end-start))
利用matplotlib,seaborn,pyechars可视化工具,查看各特征的分布,查看各特征与标签之前的关系,查看特征与特征之间的关系
"""查看各特征是否有异常值"""
for i in range(len(data.columns)):
plt.figure(figsize=(10,58))
plt.subplot(len(data.columns),1,i+1)
sns.boxplot(data[data.columns[i]])
年龄有异常值,对年龄大于60的数据进行单独查看,图中显示异常的部分只占总体的 3.9%,从数据上看,年龄符合人类正常寿命
"""离散型特征与标签"""
for i in range(len(dis)):
sns.catplot(x=dis[i],y='购买与否',data=data,kind='bar',aspect=2.5)
用户地区,性别,使用累计时间,产品服务用量,是否为活跃用户对标签有比较明显的区分
"""连续型数据分箱查看与标签之间的关系"""
for i in range(len(con)):
name = '%s_10'%(con[i])
data[name] = pd.cut(data[con[i]],bins=10)
t = data.groupby(name,as_index=False)[['购买与否']].count().groupby(name,as_index=False)[['购买与否']].sum()
print(t)
sns.catplot(x=name,y='购买与否',data=t,kind='point',aspect=2.5)
plt.xticks(rotation=30)
plt.show()
产品使用分数,年龄对标签有比较明显的区分
"""查看相关系数"""
plt.figure(figsize=(16,16))
sns.heatmap(data.iloc[:,:-4].corr(),annot=True)
使用XGBOOST建模预测
"""选择比较好的特征"""
data_t = data[['用户地区','性别','使用累计时间','产品服务使用量','是否为活跃用户','产品使用分数','年龄','购买与否']]
"""划分数据集"""
x_train,x_test,y_train,y_test = train_test_split(data_t.iloc[:,:-1],data_t.iloc[:,-1],test_size=0.2,random_state=42)
"""利用网格搜索搜索超参数"""
xgb = xgboost.XGBClassifier()
gs = GridSearchCV(xgb,{'max_depth':[2,4,6,7,9,10],'n_estimators':[50,100,150,200],'learning_rate':[1,0.1,0.01,0.001],'n_jobs':[10]},cv=10,refit=True)
gs.fit(x_train,y_train)
pre = gs.predict(x_test)
print(gc.best_params_)
print(accuracy_score(y_test,g))
{'learning_rate': 0.1, 'max_depth': 2, 'n_estimators': 150, 'n_jobs': 10}
0.8225