数据挖掘案例——药店流失会员预测:什么样的会员容易流失?
一、背景
随着药店市场刮起了兼并重组风潮,大量资金涌入医药市场,药店行业竞争进入白热化。同时,“互联网+”及“大数据”为药店引来了新的机遇和挑战,大部分药店都在从传统零售行业转入“服务型”零售,实行“会员制”,进行会员差异化的管理和服务是其中最关键的一环。
本文主要利用药店会员销售数据,筛选出其中“已流失会员”进行挖掘分析,探索“已流失会员”的会员属性及购买行为——具有哪些特征的会员易流失?
我挑选了一家在二线城市的连锁药店进行分析,这家连锁2018年年销售额差不多在6700万,其中,会员的销售额占比在60%左右,流失会员(近6个月未到店购药的会员)占比39.10%。从会员趋势的堆积图上看流失会员的占比在逐步提升。
那么这些流失会员的特征是什么呢?
二、流失会员定义
首先,我们需要来定义流失会员,并按照条件对流失会员进行打标。
我们定义
-
流失会员:年购买次数在4次(含4次)以上,近6个月未购药。
-
留存会员:年购买次数在4次(含4次)以上,近6个月有购药。
为什么是4次,以下是2018年全年会员购物次数频率图,全年大于等于4次总计13709人,占比45.49%。鉴于药品是低频消费,我们认为一年4次的消费是比较稳定的会员。
三、数据集描述
该数据是收集了2018年会员年消费次数在4次以上的会员购药数据,该数据集有27个变量,6380个数据样本。具体变量的解析如下图:0~25号变量为特征变量,26号变量为标签变量。
pd.merge(dataset_column,pd.DataFrame(columns,columns=['标签']),how='inner',on='标签')
输出:
columns=['uid','year','sex','maxdate_diff_now','carddate_diff_now','paidmoney_one_year','paidmoney_x_one_year',
'paidmoney_z_one_year','paidmoney_b_one_year','paidmoney_q_one_year','paidmoney_y_one_year','paidmoney_w_one_year'
,'paidmoney_s_one_year','paidmoney_r_one_year','paidmoney_t_one_year','paidmoney_4_one_year','paidmoney_5_one_year'
,'paidmoney_9_one_year','quantity_one_year','count_one_year','mindatediff_one_year','maxdatediff_one_year'
,'avgdatediff_one_year','paidmoney_one_order','quantity_one_order','count_one_order','label']
dataset_=dataset.loc[:,columns]
dataset_.head()
四、基本分析思路
以上研究的问题是一个二分类(流失会员or留存会员)问题,且上面的特征变量除了性别,都为数值型变量,对于二分类各个变量的比较,在统计学的分析当中,我们可以总结为以下两种:
- 数值型数据:均值比较——方差分析检验显著性
- 分类型数据:频数分布比较(交叉分析)——卡方分析检验显著性
各个特征变量做完分析之后,我们去研究变量对结果的权重。对于此问题,我们可以运用多个分类模型去训练数据,选出其中模型精度最高的模型,输出变量的重要程度排行。接下来我们就可以根据变量的重要程度来逐一归纳流失会员特征了。
详细的步骤,见下:
五、python库导入
可以为分析过程用到的包:
#1、数据处理
import pandas as pd
import numpy as np
from scipy import stats
from scipy.stats import chi2_contingency #卡方检验
import numpy as np
import Ipynb_importer #自定义的导入notebook函数的启动包
import mysql_connect #自定义的连接数据库的包
#2、可视化
import matplotlib.pyplot as plt
import seaborn as sns
plt.rcParams['font.family']='SimHei'
plt.rcParams['axes.unicode_minus']=False
#3、特征工程
import sklearn
from sklearn import preprocessing #数据预处理模块
from sklearn.preprocessing import LabelEncoder #标签转码
from sklearn.preprocessing import StandardScaler #归一化
from sklearn.model_selection import train_test_split #数据分区
from sklearn.decomposition import PCA
#4、分类算法
from sklearn.ensemble import RandomForestClassifier #随机森林
from sklearn.svm import SVC,LinearSVC #支持向量机
from sklearn.linear_model import LogisticRegression #逻辑回归
from sklearn.neighbors import KNeighborsClassifier #KNN算法
from sklearn.naive_bayes import GaussianNB #朴素贝叶斯
from sklearn.tree import DecisionTreeClassifier #决策树
#5、集成算法
import xgboost as xgb
from xgboost import XGBClassifier
from sklearn.ensemble import AdaBoostClassifier
from sklearn.ensemble import GradientBoostingClassifier
#6、模型评价
from sklearn.metrics import classification_report,precision_score,recall_score,f1_score #分类报告
from sklearn.metrics import confusion_matrix #混淆矩阵
from sklearn.metrics import silhouette_score #轮廓系数
from sklearn.model_selection import GridSearchCV #交叉验证
from sklearn.metrics import make_scorer
from sklearn.ensemble import VotingClassifier #投票
六、数据清洗
1、缺失值处理
缺失的部分主要与销售金额相关,都为float类型。且存在大量的缺失值,部分超过90%。
根据实际的业务需求,我们暂时只对缺失值补0。都不删除,先看最后的效果,如果变量影响模型效果,可将缺失值较多的变量删除,变量的缺失值占比如下:
#缺失值占比
dataset_.isnull().sum()/len(dataset_)
输出:
#对缺失值补0
dataset_.fillna(0,inplace=True)
dataset_.info()
输出:
2、检测重复值
未检测到重复的样本。
dataset_.duplicated().sum()
输出:
0
3、检测数值类型
数值类型符合实际需求,暂时不用替换。
注意:数据要是转换为float,要重新检测缺失值。
dataset_.info()
输出:
4、检测标签分布
流失客户样本占比35.4%,留存客户样本占比64.6%,样本不均衡,后续进行模型训练的时候需要注意。
解决样本不均衡有以下方法可以选择:
-
分层抽样
-
过抽样
-
欠抽样
label_value=dataset_['label'].value_counts()
labels=label_value.index
plt.figure(figsize=(5,5))
plt.pie(label_value,labels=labels, explode=(0.1,0),autopct='%1.1f%%', shadow=True)
plt.title("流失客户占比高达%s%%" %((label_value['lost']/label_value.sum()*100).round(2)))
plt.show()
输出:
5、检测变量编码
对于分类数据,编码的方式有很多种,归纳总结,最常见的方式基本为两种:
-
标签编码:代表性的算法例如sklearn中的LabelEncoder()、pandas中的factorize,或者通过replace和map函数进行替换。
-
独热编码:代表性的算法例如sklearn中的OneHotEncoder(),pandas中的get_dummies。
两者的区别在于:标签编码所产生的结果,在数据上会有大小之分,而在具体的算法,分类的数据,一般是没有数值上的差异的。 而独热编码正好填补了这个缺陷,所以当我们进行简单分析时,一般标签编码即可,但是需要模型训练的数据,一般是要进行独热编码处理的。
print(dataset_['sex'].value_counts())
print(dataset_['label'].value_counts())
输出:
女 3704
男 2676
Name: sex, dtype: int64
no_lost 4121
lost 2259
Name: label, dtype: int64
通过检测变量,我们发现需要对'sex','label'变量进行再编码,否则影响模型训练,为了直观,我们用最基础的语法map函数进行编码。
#map方法
dataset_['sex']=dataset_['sex'].map({'男':1,'女':0})
dataset_['label']=dataset_['label'].map({'lost':1,'no_lost':0})
print(dataset_['sex'].value_counts())
print(dataset_['label'].value_counts())
#pandas的方法
#dataset_['sex']=pd.factorize(dataset_['sex'])[0]
#dataset_['label']=pd.factorize(dataset_['label'],)[0]
输出:
0 3704
1 2676
Name: sex, dtype: int64
0 4121
1 2259
Name: label, dtype: int64
'sex'字段:1代表男性,0代表女性;'label'字段:1代表lost(流失客户),0代表no_lost(留存客户)。
6、根据业务的变量转换
paidmoney_x_one_year,paidmoney_z_one_year ,paidmoney_b_one_year,paidmoney_q_one_year ,paidmoney_y_one_year, paidmoney_w_one_year ,paidmoney_s_one_year ,paidmoney_r_one_year,paidmoney_4_one_year ,paidmoney_5_one_year ,paidmoney_9_one_year这11个字段现在是数值型,其表示的是细分领域(西药、中成药、保健品、医疗器械、)的销售金额,根据实际的业务场景,此处我们建议将其转换为百分比,各字段同除于paidmoney_one_year。
dataset_1=dataset_.copy()
list_paidmoney=['paidmoney_x_one_year','paidmoney_z_one_year','paidmoney_b_one_year','paidmoney_q_one_year','paidmoney_y_one_year','paidmoney_w_one_year','paidmoney_s_one_year','paidmoney_r_one_year','paidmoney_4_one_year','paidmoney_5_one_year','paidmoney_9_one_year']
for i in list_paidmoney:
dataset_1[i]=dataset_1[i]/dataset_1['paidmoney_one_year']
dataset_1.head()
输出:
因为篇幅过长,仅展示部分数据。
七、筛选特征
1、相关系数矩阵
去掉uid,计算相关系数矩阵:
#提取特征
feature=dataset_1.iloc[:,1:27]
#相关系数矩阵
corr=feature.corr().round(2)
#相关系数矩阵的可视化
plt.figure(figsize=(15,12))
ax = sns.heatmap(corr, xticklabels=corr.columns, yticklabels=corr.columns,
linewidths=0.2, cmap="RdYlGn",annot=True)
plt.title("相关系数矩阵")
输出:
2、查看label和特征变量之间的相关性
plt.figure(figsize=(15,6))
corr['label'].sort_values(ascending=False).plot(kind='bar')
plt.title('label(用户是否流失)与变量间的相关性 ')
输出:
从相关系数排行上看,我们看到maxdate_diff_now(最晚购药日期距今时间)和label存在高度相关,从业务上考虑,最后一次购药时间距今越长,越容易流失,所以我们将其删除。另外,我们也将paidmoney_4_one_year删除。
feature=feature.drop(['maxdate_diff_now','paidmoney_4_one_year'],axis=1)
feature.info()
输出:
另外,从“图——label(用户是否流失)与变量间的相关性”看:mindatediff_one_year(最小时间间隔)、quantity_one_year(年销售量)相关性几乎为0,故这两个维度可以忽略。
[‘paidmoney_t_one_year’,‘carddate_diff_now’,‘maxdatediff_one_year’,'avgdatediff_one_year',‘year’, ‘paidmoney_r_one_year’, ‘paidmoney_s_one_year’, ‘sex’,
‘paidmoney_q_one_year’, ‘paidmoney_y_one_year’, ‘paidmoney_4_one_year’, ‘paidmoney_w_one_year’,
‘paidmoney_5_one_year’,‘paidmoney_b_one_year’,‘paidmoney_one_year’,‘paidmoney_9_one_year’,‘count_one_year’,‘quantity_one_order’,‘paidmoney_z_one_year’,‘paidmoney_x_one_year’]等都有较高的相关性,将以上维度合并成一个列表list_columns,然后进行频数。
上一篇: 我的Python学习笔记_Day15 面向对象编程1
下一篇: jekins持续集成