# import the libraries we might need
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.impute import SimpleImputer
from matplotlib.pyplot import MultipleLocator
import xgboost as xgb
from xgboost import XGBClassifier
from sklearn.model_selection import train_test_split
from sklearn.model_selection import KFold
from sklearn.preprocessing import LabelEncoder
from sklearn.impute import SimpleImputer
from sklearn.preprocessing import PolynomialFeatures
from sklearn.preprocessing import StandardScaler
from sklearn.feature_selection import VarianceThreshold
from sklearn.feature_selection import SelectFromModel
from sklearn.utils import shuffle
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import classification_report, confusion_matrix
from sklearn.metrics import roc_curve, auc
from sklearn import metrics #Additional scklearn functions
from sklearn.model_selection import GridSearchCV, cross_validate #Perforing grid search
from matplotlib.legend_handler import HandlerLine2D
# basic set up
sns.set_style('darkgrid')
%matplotlib inline
from matplotlib.pylab import rcParams
rcParams['figure.figsize'] = 12, 8
plt.rcParams['font.sans-serif']=['SimHei']
plt.rcParams['axes.unicode_minus'] = False
pd.set_option('display.max_columns', 100)
# find the path
import os
print(os.path.abspath('.'))
# load the data
path = r'C:\Users\gzjgz\OneDrive\Documents\rawdata.xls'
df_key_customer_info = pd.read_excel(path,sheet_name = '车主')
df_repurchased = pd.read_excel(path,sheet_name = '车辆')
df_service_request = pd.read_excel(path,sheet_name = '工单')
# check the data
df_key_customer_info.head()
df_repurchased.head()
df_service_request.head()
先看看数据有多少行列吧
print('关键客户基本信息表的行列为:',df_key_customer_info.shape)
print('关键客户重购表的行列为:',df_repurchased.shape)
print('关键客户服务请求表的行列为:',df_service_request.shape)
再看看有没有重复的行
df_key_customer_info.drop_duplicates()
df_key_customer_info.shape
df_repurchased.drop_duplicates()
df_repurchased.shape
df_service_request.drop_duplicates()
df_service_request.shape
非常好!我们没有重复的行存在。那再看看数据的基本信息吧!
df_key_customer_info.info()
通过以上的信息我们可以分析出客户不愿意透露自己的婚姻状况、职业类别、兴趣爱好、驾照相关信息,是否是大/VIP客户。
往往我们可以通过客户的一部分代表性的信息来做一些特征选择,比如我们可对客户基本数据进行总结,大概包括:
先去掉用处不大的信息,再进行特征筛选
df_key_customer_info.drop(['姓名拼音',
'英文名称',
'婚姻状态',
'职业大类',
'职业小类',
'兴趣爱好',
'计划考驾照的时间',
'取得驾照年月',
'是否VIP客户',
'区号',
'分机'], axis = 1, inplace=True)
df_key_customer_info.info()
我们先定义一个数据质量检测函数!
def data_check(data, create_new_data = True):
# 缺失值占比
print('#### 数据缺失值百分比 ####')
percent = 100*data.isnull().sum()/ data.shape[0]
print(percent)
if create_new_data == True:
# 我们把缺失值小于10%大于0的行去掉
threshold = 10
idx = np.where((percent < threshold) & (percent > 0))
col_drop = pd.DataFrame(percent).T.columns[idx]
new_data = data.dropna(axis=0, subset=col_drop)
print('\n#### 新数据缺失值百分比 ####')
percent = 100*new_data.isnull().sum()/ new_data.shape[0]
print(percent)
return new_data
df_key_customer_info = data_check(df_key_customer_info)
df_key_customer_info.head()
这里我们有了客户的省份和城市作为地理位置的代表信息,可以将址区和邮编剔除,并且由于是否为大客户的缺失值占比超过99%,也需要把它剔除。
df_key_customer_info.drop(['址区','邮编','是否大客户'],axis = 1,inplace=True)
data_check(df_key_customer_info,create_new_data=False)
那么接下来就要对缺失值进行填充,比如我们可以
df_key_customer_info['家庭年收入']
df_key_customer_info['家庭年收入'].value_counts()
df_key_customer_info['家庭成员人数'].value_counts()
df_key_customer_info['家庭成员人数'] = df_key_customer_info['家庭成员人数'].replace(['One','Two','Three','Four','Five','Six','Seven And Above'],
[1,2,3,4,5,6,7])
df_key_customer_info['家庭成员人数'] = df_key_customer_info['家庭成员人数'].replace('No Answer',3)
df_key_customer_info['家庭成员人数'] = df_key_customer_info['家庭成员人数'].fillna(method = 'pad')
sns.countplot(df_key_customer_info['家庭成员人数'])
可以看出大概90%的客户家庭人数是3人。
再来看看客户的年龄分布
df_key_customer_info['年龄'] = 2020 - df_key_customer_info['生日'].apply(lambda x:x.year) # 对timestamp进行年份转换
df_key_customer_info['年龄'] = df_key_customer_info['年龄'].interpolate() # 对数据缺失值进行插值拟合
plt.figure(figsize = [12,8])
sns.distplot(df_key_customer_info['年龄'])
df_key_customer_info['年龄'][0] = df_key_customer_info['年龄'].mean()
可以看出客户的年龄分布集中在35到50中间,是很合理的,峰值出现在40岁左右
null_idx = df_key_customer_info['是否拥有驾照'][pd.isnull(df_key_customer_info['是否拥有驾照'])].index
df_key_customer_info['是否拥有驾照'][pd.isnull(df_key_customer_info['是否拥有驾照'])] = df_key_customer_info['年龄'][null_idx].apply(lambda x: 'Y' if x > 30 else 'N')
df_key_customer_info['是否拥有驾照'].value_counts()
# 根据年龄对教育程度进行预测(由于75%人不愿回答)
no_answer_idx = df_key_customer_info['教育程度'][df_key_customer_info['教育程度'] == '没有回答'].index
def education(age):
if (age >= 20) & (age <= 30):
return '硕士'
elif (age > 30) & (age <= 43):
return '本科'
elif (age > 43) & (age <= 55):
return '大专'
else:
return '高中'
df_key_customer_info['教育程度'][df_key_customer_info['教育程度'] == '没有回答'] = df_key_customer_info['年龄'][no_answer_idx].apply(education)
通过计算,客户的家庭年收入大概在7.6万左右,由于不愿回答或没有回答的人数太多,我们需要进行填充。
df_key_customer_info['教育程度'] = df_key_customer_info['教育程度'].fillna(method = 'pad')
df_key_customer_info['家庭年收入'] = df_key_customer_info['家庭年收入'].replace(['没有回答','不想说/不愿回答','5万元及以下','5-8万元(含8万)', '8-12万元(含12万)','12-18万元(含18万)','18-27万元(含27万)','27-45万元(含45万)','45万元以上'],[65000,100000,50000,65000,100000,150000,230000,360000,500000])
df_key_customer_info['家庭年收入'] = df_key_customer_info['家庭年收入'].fillna(method = 'ffill')
df_key_customer_info['家庭年收入'][0] = 500000
df_key_customer_info['家庭年收入'][1] = 500000
df_key_customer_info['职位'] = df_key_customer_info['职位'].fillna(method = 'ffill')
df_key_customer_info['行业'] = df_key_customer_info['行业'].fillna(method = 'ffill')
那么最后整理出来的客户信息数据如下
# 缺失值验证
df_key_customer_info.isnull().sum()
# 去掉不需要的列
col_to_drop = ['姓名','生日','手机']
df_key_customer_info = df_key_customer_info.drop(col_to_drop,axis = 1)
df_key_customer_info = df_key_customer_info.dropna()
df_key_customer_info.isnull().sum()
df_key_customer_info = df_key_customer_info.rename(columns={'车主id':'车主ID'})
df_key_customer_info.index = df_key_customer_info['车主ID']
df_key_customer_info = df_key_customer_info.drop('车主ID',axis = 1)
print('最终客户数据的维度是:',df_key_customer_info.shape)
df_key_customer_info.head()
来写一个辅助函数,画出每个特征的饼状图来帮助分析
def mypie(data,feature,top_n = 10):
plt.figure(figsize=(12,8))
labels = data[feature].value_counts()[:top_n].index
plt.pie(data[feature].value_counts()[:top_n],labels = labels,autopct='%3.2f%%')
mypie(df_key_customer_info,'性别')
可以看出客户71.75%集中在男性群体上。
mypie(df_key_customer_info,'教育程度')
根据客户的年龄分布以及饼状图的结果来看,38.11%的客户是本科学历,36.68%的客户是大专学历,11.42%的客户是高中学历。硕士、博士级以上只占了3%左右。
说明客户的教育水平大多是集中在大专和本科,其总占比达到75%左右。
mypie(df_key_customer_info,'职位')
根据上图所示,我们有23.56%的客户不愿意透露自己的职业,而也有24.5%左右的客户是企业高管等高层职位,有21.23%的客户是一般职员或者技术人员,而事业单位或者正负官员占比是最少的,不到1%,所以客户的职位有一些两级分化,总裁等级的职位和一般职员的占比不分伯仲。
mypie(df_key_customer_info,'行业')
根据饼状图,33.85%的客户都集中在电气、电源、仪器制造行业。而其他行业。例如:家居、建筑、汽车、政府、金融、教育等,比重都差距不大,其中也有23%的人拒绝回答自己从事的行业。
mypie(df_key_customer_info,'省份')
客户在上海市(直辖市)的总数量是最高的,达到15.96%,但是也和江苏省的14.96%、山东省的14.72%相差不多,山西省占比5%是最少的,在南北地区分布来看,客户分布数量基本持平。
mypie(df_key_customer_info,'城市')
大部分的客户都分布在上海市和北京市,总占比达到50%左右。其中上海市占比最高,达到34.36%,北京市排在第二,达到了15.29%。
plt.figure(figsize=(12,8))
labels = df_key_customer_info['家庭年收入'].value_counts().index
plt.pie(df_key_customer_info['家庭年收入'].value_counts(),labels = labels,autopct='%3.2f%%')
47.5%的客户家庭年收入水平在65000元左右,27.7%的客户家庭年收入在8-12万元,并且也造成了一些两极分化的情况,年收入5万元以下和年收入在12-18万或18-27万的客户基本持平,都在6%左右。也说明年收入哪怕在5万元以下的情况,对汽车的需求也是比较高的,而我们的客户群体不集中与高收入人群,普遍集中在中等偏低收入水平的客户,总共占比大概在86%左右(针对5-15万年收入群体)。
客户(车主)基本数据囊括了其个人基本信息、教育程度、职业行业、收入水平以及地理分布。我通过对数据的质检以及描述性分析,对客户数据的各个维度都进行了探索性分析以及数据可视化,最终,我得出的结论如下:
先来看看客户回购数据的基本信息
df_repurchased.info()
可以看出基本没有什么缺失值,尤其是车型、购买日期和开票价格。
df_repurchased = data_check(df_repurchased,create_new_data=True)
这样我们就把缺失值少于10%的所有行全部去掉。
df_repurchased = df_repurchased.drop_duplicates()
df_repurchased.shape
找出没有重新购买的所有顾客和其信息
d = pd.DataFrame(df_repurchased['车主ID'].value_counts())
idx = d[d['车主ID']==1].index
df_repurchased_test = pd.DataFrame(columns=df_repurchased.columns)
for i in range(len(idx)):
df_repurchased_test = df_repurchased_test.append(df_repurchased[df_repurchased['车主ID'] == idx[i]])
df_repurchased_test
可以看出我们有486行数据代表没有重购的情况,我们将把他们作为分析的对象。
d = pd.DataFrame(df_repurchased['车主ID'].value_counts())
idx = d[d['车主ID'] > 1].index
df_repurchased_train = pd.DataFrame(columns=df_repurchased.columns)
for i in range(len(idx)):
df_repurchased_train = df_repurchased_train.append(df_repurchased[df_repurchased['车主ID'] == idx[i]])
df_repurchased_train
剩下的6747行数据作为我们的分析训练数据,我想通过分析这些客户的回购数据来为还未重购的客户进行推荐!
df_repurchased_train.sort_values(by = ['车主ID','购买日期'])
df_repurchased_train['市场车型'].describe()
percent = (100*df_repurchased_train['市场车型'].value_counts()/df_repurchased_train.shape[0])[:30].sum()
print('我们要选取最受客户欢迎的前30种车,对于总数的占比达到:',percent)
idx = pd.DataFrame((100*df_repurchased_train['市场车型'].value_counts()/df_repurchased_train.shape[0])[:30]).index
df_repurchased_train_filter = pd.DataFrame(columns=df_repurchased.columns)
for i in range(len(idx)):
df_repurchased_train_filter = df_repurchased_train_filter.append(df_repurchased_train[df_repurchased_train['市场车型'] == idx[i]])
df_repurchased_train_filter['市场车型'].value_counts()
mypie(df_repurchased_train_filter,'市场车型',top_n = 30)
可以看出占主导地位的车型是全新帕萨特(11.33%)
df_repurchased_train_filter = df_repurchased_train_filter.sort_values(by = ['车主ID','购买日期'])
df_repurchased_train_filter
df = df_repurchased_train
df = df.sort_values(by = ['车主ID','购买日期'])
df['价格差异'] = df['开票价格'].groupby(df['车主ID']).diff()
plt.figure(figsize = [12,8])
df_price_diff = pd.DataFrame(df['价格差异'].groupby(df['车主ID']).sum())
sns.distplot(df_price_diff)
通过对于重购客户的回购车辆价格和首次购车价格的差值分布情况来看,绝大部分的客户在购车的价格上是基本持平或者会倾向于买稍微贵一点的车。
df['开票价格'].max()
df_price_diff.describe()
可以看出平均来讲,重购车辆的价格会比之前高出46000元左右,中位数大概在7000元左右,最大价格差要高出了220多万,最少比之前低了48万多,所以这个差异变动还是比较大的!
再看一下他们的购车时间间隔的情况
df['时间差异'] = df['购买日期'].groupby(df['车主ID']).diff()
plt.figure(figsize = [12,8])
df_time_diff = pd.DataFrame(df['时间差异'].groupby(df['车主ID']).sum())
df_time_diff['时间差异'] = df_time_diff['时间差异'].apply(lambda x: x.days)
sns.distplot(df_time_diff['时间差异'])
我们放大一下
df_time_diff['时间差异'].describe()
plt.figure(figsize = [12,8])
sns.distplot(df_time_diff['时间差异'][df_time_diff['时间差异']<2175])
可以看出,非常奇怪的是有不少客户在买了第一辆车没过几天就重购了,甚至有
print('当天重购的比率是:',100*sum(df_time_diff['时间差异']==0)/df_time_diff.shape[0])
print('当月重购的比率是:',100*sum((df_time_diff['时间差异']>0) & (df_time_diff['时间差异']<=30))/df_time_diff.shape[0])
print('当季重购的比率是:',100*sum((df_time_diff['时间差异']>0) & (df_time_diff['时间差异']<=120))/df_time_diff.shape[0])
print('当年重购的比率是:',100*sum((df_time_diff['时间差异']>0) & (df_time_diff['时间差异']<=365))/df_time_diff.shape[0])
print('第二年重购的比率是:',100*sum((df_time_diff['时间差异']>365) & (df_time_diff['时间差异']<=365*2))/df_time_diff.shape[0])
print('第三年重购的比率是:',100*sum((df_time_diff['时间差异']>365*2) & (df_time_diff['时间差异']<=365*3))/df_time_diff.shape[0])
print('第四年重购的比率是:',100*sum((df_time_diff['时间差异']>365*3) & (df_time_diff['时间差异']<=365*4))/df_time_diff.shape[0])
print('第五年重购的比率是:',100*sum((df_time_diff['时间差异']>365*4) & (df_time_diff['时间差异']<=365*5))/df_time_diff.shape[0])
print('第六年重购的比率是:',100*sum((df_time_diff['时间差异']>365*5) & (df_time_diff['时间差异']<=365*6))/df_time_diff.shape[0])
print('第七年重购的比率是:',100*sum((df_time_diff['时间差异']>365*6) & (df_time_diff['时间差异']<=365*7))/df_time_diff.shape[0])
print('第八年重购的比率是:',100*sum((df_time_diff['时间差异']>365*7) & (df_time_diff['时间差异']<=365*8))/df_time_diff.shape[0])
print('第九年重购的比率是:',100*sum((df_time_diff['时间差异']>365*8) & (df_time_diff['时间差异']<=365*9))/df_time_diff.shape[0])
print('第十年重购的比率是:',100*sum((df_time_diff['时间差异']>365*9) & (df_time_diff['时间差异']<=365*10))/df_time_diff.shape[0])
y = [4.37, 4.21, 8.08, 15.97, 11.10, 11.31, 11.70, 11.04, 9.93, 7.51, 5.50, 4.28, 2.90]
x = [0,1/12,1/4,1,2,3,4,5,6,7,8,9,10]
plt.figure(figsize = [12,8])
plt.plot(x,y,c='red')
plt.xlabel('第n年重购')
plt.ylabel('百分比')
x_major_locator=MultipleLocator(1)
y_major_locator=MultipleLocator(1)
ax=plt.gca()
ax.xaxis.set_major_locator(x_major_locator)
ax.yaxis.set_major_locator(y_major_locator)
plt.xlim(-0.1,10)
#把x轴的刻度范围设置为-0.5到11,因为0.5不满一个刻度间隔,所以数字不会显示出来,但是能看到一点空白
plt.ylim(-0.1,20)
如图所示,我发现在当天重购和当月重购的比率是很接近的,都是4%左右,而大部分的客户选择在第一年内重购(占比16%),在第二、三、四、五年里重购的比重也十分相似,都在11%左右,而随着时间的推移,重购比率也是一直在下降,选择第十年重购的客户仅仅占2%左右。
接下来,我要判断客户的积极程度(高中低)时间区间 消费水平(高中低)消费区间 把车分档次(按卖出的百分比最后进行推荐)制作class 合并之前表格 选出特征 带入xgboost建模
现在我们要刻画用户的积极程度:
def active(x):
if x <= 365:
return 1
elif (x > 365) & (x <= 365*3):
return 2
elif (x > 365*3) & (x <= 365*5):
return 3
elif (x > 365*5) & (x <= 365*7):
return 4
elif (x > 365*7) & (x <= 365*9):
return 5
elif x > 365*9:
return 6
df_time_diff['积极程度'] = df_time_diff['时间差异'].apply(lambda x: active(x))
mypie(df_time_diff, '积极程度')
再根据购车价格把市场车型进行分类,根据价格区间把对应车型号汇总。
plt.figure(figsize = [12,8])
df_car_price = df['开票价格'].groupby(df['市场车型']).mean().sort_values()
df_car_price.plot()
x_major_locator=MultipleLocator(10)
y_major_locator=MultipleLocator(50000)
ax=plt.gca()
ax.xaxis.set_major_locator(x_major_locator)
ax.yaxis.set_major_locator(y_major_locator)
plt.xlim(-1,76)
#把x轴的刻度范围设置为-0.5到11,因为0.5不满一个刻度间隔,所以数字不会显示出来,但是能看到一点空白
plt.ylim(-1,700000)
plt.title('不同车型的价格曲线')
df_car_price[(df_car_price >= 50000)&(df_car_price < 100000)].count()
df_car_price.describe()
可以看出,我们一共有76种车型,平均价格在18万3千元左右。
最贵的车款是途锐,均价在639000元左右,我把30-60万以内的车款有7种,定为6级; 20-30万以内的车款有20种,定义为4级; 10-20万以内的车款有38种,定义为2级; 5-10万以内的车款有12种,定义为0级。
所以针对不同类型的客户,我们根据其基本信息、购买力、消费意愿以及积极程度来预测他们的回购情况,并相应的推荐车款。
比如一位客户在分析过后应该为其选择在第3级的车款,也就是在20万到30万之间,但是里面有20种车款供选择,我认为可以根据每款车的销量在其中占比来划分权重,给到客户一个比率,并给出车款之间的比较数据供其参考。
pd.DataFrame(df_car_price)
def price_level(x):
if x >= 300000:
return 6
elif (x>=200000) & (x<300000):
return 4
elif (x>=100000) & (x<200000):
return 2
elif x <100000:
return 0
df['车款评级'] = df['开票价格'].apply(lambda x: price_level(x))
df.head()
df['时间差异'] = df['时间差异'].apply(lambda x: x.days)
col_to_use = ['车主ID', '市场车型', '开票价格','购买日期', 'ASSET_REF_EXPR', 'OU_ABREV', 'OU_NAME','价格差异', '时间差异', '车款评级']
df[col_to_use].groupby(df['车主ID']).mean()
df['ASSET_REF_EXPR'].value_counts()
d = df.dropna()
train = d[col_to_use].groupby(d['车主ID']).max()
train = train.drop('车主ID',axis = 1)
train['购买年份'] = train['购买日期'].apply(lambda x: x.year)
train['购买月份'] = train['购买日期'].apply(lambda x: x.month)
train['购买日'] = train['购买日期'].apply(lambda x: x.day)
train = train.drop('购买日期',axis = 1)
train = train.rename(columns={'ASSET_REF_EXPR':'品牌',
'OU_ABREV':'所购买省份',
'OU_NAME':'所购买公司'})
train.head()
train
df[col_to_use].groupby(df['车主ID']).mean()
train['开票价格'] = df[col_to_use].groupby(df['车主ID']).mean()['开票价格']
train['价格差异'] = df[col_to_use].groupby(df['车主ID']).mean()['价格差异']
train['时间差异'] = df[col_to_use].groupby(df['车主ID']).mean()['时间差异']
train['车款评级'] = df[col_to_use].groupby(df['车主ID']).mean()['车款评级']
train.head()
df_key_customer_info.head()
下面把两个表格合并起来,信息交互。
df1 = train
df2 = df_key_customer_info
df_merge = df1.merge(df2,on = '车主ID',how = 'inner')
df_merge.shape
df_merge.info()
df_merge['年龄'] = df_merge['年龄'].apply(lambda x: round(x,2))
df_merge.head()
这里,我们把车款评级作为target,先来看看类别的平衡度
data = df_merge
data['车款评级'] = round(data['车款评级'])
plt.figure(figsize = [12,8])
plt.title('车款评级直方图')
sns.countplot(data['车款评级'])
可以看出车款评级结果的分布比较均匀。
分割成训练集和测试集
train,test = train_test_split(data,test_size = 0.33,random_state = 24)
print('训练数据集的维度是:',train.shape)
print('测试数据集的维度是:',test.shape)
下面要对categorical变量进行encoding处理,对其进行适当的编码转换,从而为带入模型做准备。
最终我们处理好的训练数据中有21个客户特征,我们要对其进行筛选以及编码。
注意到:
所以我写了target encoding的相关代码,帮助处理这种情况,在避免维度爆炸的情况下,尽量保存客户的信息,并以比率的形式展现。
data.head()
# target encoding
def add_noise(series, noise_level):
return series * (1 + noise_level * np.random.randn(len(series)))
def target_encode(trn_series=None,
tst_series=None,
target=None,
min_samples_leaf=1,
smoothing=1,
noise_level=0):
assert len(trn_series) == len(target)
assert trn_series.name == tst_series.name
temp = pd.concat([trn_series, target], axis=1)
# Compute target mean
averages = temp.groupby(by=trn_series.name)[target.name].agg(["mean", "count"])
# Compute smoothing
smoothing = 1 / (1 + np.exp(-(averages["count"] - min_samples_leaf) / smoothing))
# Apply average function to all target data
prior = target.mean()
# The bigger the count the less full_avg is taken into account
averages[target.name] = prior * (1 - smoothing) + averages["mean"] * smoothing
averages.drop(["mean", "count"], axis=1, inplace=True)
# Apply averages to trn and tst series
ft_trn_series = pd.merge(
trn_series.to_frame(trn_series.name),
averages.reset_index().rename(columns={'index': target.name, target.name: 'average'}),
on=trn_series.name,
how='left')['average'].rename(trn_series.name + '_mean').fillna(prior)
# pd.merge does not keep the index so restore it
ft_trn_series.index = trn_series.index
ft_tst_series = pd.merge(
tst_series.to_frame(tst_series.name),
averages.reset_index().rename(columns={'index': target.name, target.name: 'average'}),
on=tst_series.name,
how='left')['average'].rename(trn_series.name + '_mean').fillna(prior)
# pd.merge does not keep the index so restore it
ft_tst_series.index = tst_series.index
return add_noise(ft_trn_series, noise_level), add_noise(ft_tst_series, noise_level)
data.columns
col_to_encode = ['市场车型','所购买省份','所购买公司','行业','省份','城市']
for col in col_to_encode:
train_encoded, test_encoded = target_encode(train[col],
test[col],
target=train['车款评级'],
min_samples_leaf=100,
smoothing=10,
noise_level=0.01)
train[col] = train_encoded
test[col] = test_encoded
接下来对其它有阶级区分度的特征进行标签编码(label encoding),他们之间是有顺序的,例如:大学本科 $>$ 大专,所以前者编码为1,后者为0
train['品牌'] = train['品牌'].replace({'大众':1,'斯柯达':0})
test['品牌'] = test['品牌'].replace({'大众':1,'斯柯达':0})
train['职位'] = train['职位'].replace({'不想说/不愿回答':0,
'其它':1,
'自由职业者':2,
'事业单位/政府职员或官员':3,
'一般职员/技术人员':4,
'专业人士':5,
'部门经理/主管/高级技术人员':6,
'总裁/总经理/总监/企业高管':7,
'公司拥有者(老板)/合伙人':8})
test['职位'] = test['职位'].replace({'不想说/不愿回答':0,
'其它':1,
'自由职业者':2,
'事业单位/政府职员或官员':3,
'一般职员/技术人员':4,
'专业人士':5,
'部门经理/主管/高级技术人员':6,
'总裁/总经理/总监/企业高管':7,
'公司拥有者(老板)/合伙人':8})
train['教育程度'] = train['教育程度'].replace({'不想说/不愿回答':0,
'其它':1,
'初中':2,
'高中':3,
'大专':4,
'本科':5,
'硕士':6,
'博士及以上':7})
test['教育程度'] = test['教育程度'].replace({'不想说/不愿回答':0,
'其它':1,
'初中':2,
'高中':3,
'大专':4,
'本科':5,
'硕士':6,
'博士及以上':7})
train['性别'] = train['性别'].replace({'女':0,'男':1})
test['性别'] = test['性别'].replace({'女':0,'男':1})
train.columns
train['是否拥有驾照'] = train['是否拥有驾照'].replace({'N':0,'Y':1})
test['是否拥有驾照'] = test['是否拥有驾照'].replace({'N':0,'Y':1})
train['性别'] = train['性别'].replace('没有回答',np.nan)
test['性别'] = test['性别'].replace('没有回答',np.nan)
train['性别'] = train['性别'].fillna(method = 'ffill')
test['性别'] = test['性别'].fillna(method = 'ffill')
train.head()
test.head()
train.columns
plt.figure(figsize=(20,7))
col = ['市场车型', '开票价格', '品牌', '所购买省份', '所购买公司', '价格差异', '时间差异', '车款评级', '购买年份',
'购买月份', '购买日', '性别', '教育程度', '职位', '行业', '家庭年收入', '家庭成员人数', '是否拥有驾照',
'省份', '城市', '年龄']
sns.heatmap(train[col].corr(), cmap = 'Blues',linewidths = 0.1,annot = True)
通过热图可以发现特征之间的相关性,很明显,车款评级和市场车型、开票价格、购买年份等因素较为相关。
plt.figure(figsize = [12,8])
sns.lmplot(x = "购买年份", y = "开票价格", data = train, hue = '车款评级', palette="Set1", scatter_kws = {"alpha":0.3})
可看出等级越高的车,随着购买年份的推移,开票价格也越贵。
plt.figure(figsize = [12,8])
sns.lmplot(x = "职位", y = "行业", data = train, hue = '车款评级', palette="Set1", scatter_kws = {"alpha":0.3})
可看出评级最低和最高的两款车型下,客户的职位和行业相关度最高。
下面对数据进行归一化
train.columns
scaler = StandardScaler()
feature_col = ['市场车型', '开票价格', '品牌', '所购买省份', '所购买公司', '价格差异', '时间差异', '购买年份',
'购买月份', '购买日', '性别', '教育程度', '职位', '行业', '家庭年收入', '家庭成员人数', '是否拥有驾照',
'省份', '城市', '年龄']
train_scale = pd.DataFrame(scaler.fit_transform(train.drop(['车款评级'], axis=1)), columns = feature_col)
test_scale = pd.DataFrame(scaler.transform(test.drop(['车款评级'], axis=1)), columns = feature_col)
train_scale
train['车款评级'].values
train_scale['车款评级'] = train['车款评级'].values
X = train_scale.drop('车款评级',axis=1)
y = train["车款评级"]
def modelfit(alg, dtrain, predictors, useTrainCV=True, cv_folds=5, early_stopping_rounds=50, target = '车款评级'):
'''
alg: 算法
dtrain: 训练集包含target
predictors: 特征column
'''
if useTrainCV:
xgb_param = alg.get_xgb_params()
xgtrain = xgb.DMatrix(dtrain[predictors].values, label=dtrain[target].values)
cvresult = xgb.cv(xgb_param, xgtrain, num_boost_round=alg.get_params()['n_estimators'], nfold=cv_folds,
metrics='mlogloss', early_stopping_rounds=early_stopping_rounds)
alg.set_params(n_estimators=cvresult.shape[0])
#Fit the algorithm on the data
alg.fit(dtrain[predictors], dtrain[target],eval_metric='mlogloss')
#Predict training set:
dtrain_predictions = alg.predict(dtrain[predictors])
dtrain_predprob = alg.predict_proba(dtrain[predictors])[:,1]
#Print model report:
print ("\nModel Report")
print ("Accuracy : %.4g" % metrics.accuracy_score(dtrain[target].values, dtrain_predictions))
print(confusion_matrix(dtrain_predictions,dtrain[target].values))
print(classification_report(dtrain_predictions,dtrain[target].values))
pd.DataFrame(alg.predict_proba(dtrain[predictors])).plot()
plt.show()
sns.heatmap(pd.DataFrame(confusion_matrix(dtrain_predictions,dtrain[target].values)), annot=True, cmap='inferno', fmt="d")
plt.show()
feat_imp = pd.Series(alg.get_booster().get_fscore()).sort_values(ascending=False)
feat_imp.plot(kind='bar', title='Feature Importances')
plt.ylabel('Feature Importance Score')
predictors = [x for x in train_scale.columns[:-1]]
xgb1 = XGBClassifier(
learning_rate =0.1,
n_estimators=60,
max_depth=4,
min_child_weight=1,
gamma=0,
subsample=0.8,
colsample_bytree=0.8,
objective= 'multi:softmax',
num_class = 7,
nthread=4,
seed=2)
modelfit(xgb1, train_scale, predictors, target = '车款评级')
根据XGBoost的分类结果,准确度达到了99%,意味着我们能较为准确地通过对客户的数据特征,对其可能会回购的车款级别做出预测,并且准确率达到99%。
例如:我们知道了客户的第一次购车价格,购车的时间差,所处行业等等信息,可以来推测他们可能会购买的车型,如果分到了第2等级的车款,我们会从此等级的车里为其推荐,并根据每款车的销量加权,首推排名前5的车款进行推荐。
df_service_request.shape
客户服务数据仅有1447条,之前的两个数据都是3300条以上,所以很难将其合并,会丢失掉很多数据,也说明50%的客户没有申请服务
df_service_request.columns
mypie(df_service_request,'服务品牌')
# 服务品牌为大众和斯柯达
mypie(df_service_request,'工单状态')
# 处理完成比例高达98%,说明服务效果很好,客户满意度不错。
mypie(df_service_request,'性质分类')
# 咨询业务占比达到84%,投诉比例仅占15.1%,说明客户反馈相对不错
mypie(df_service_request,'业务分类')
# 业务主要集中在流转、分配以及售后,所以有必要提升工单的流转分配效率,84%的业务都来自于此。
mypie(df_service_request,'工单一级分类')
# 工单一级分类主要包括大众俱乐部相关业务、维修保养以及产品技术。技术问题的服务仅占10%,而关于大众俱乐部相关事宜占比接近30%,可能需要更多相关业务支持。
mypie(df_service_request,'工单二级分类')
# 工单二级分类主要包括保修问题、大众售后以及配件问题,由于斯柯达的业务比重相对较小,工单二级分类也仅占3.3%,位列最后。
mypie(df_service_request,'工单三级分类')
# 关于工单三级分类,30%左右的问题是关于订货和配件问题,其他问题基本均匀分布,都是关于不同型号的汽车问题。
我认为通过对客户反馈的统计分析,能更好的了解客户偏好和业务侧重点,能够更好的流转和分配。
客户(车主)回购数据囊括了其市场车型、购买日期、开票价格、所购地址等等。我通过数据分析、数据挖掘以及机器学习建模,对客户的重购可能购买的车款进行评级和预测,最终,我得出的结论如下: