1.项目背景

客户流失是所有与消费者挂钩行业都会关注的点。因为发展一个新客户是需要一定成本的,一旦客户流失,除了浪费拉新成本,还需要花费更多的用户召回成本。
所以,电信行业在竞争日益激烈当下,如何挽留更多用户成为一项关键业务指标。为了更好运营用户,这就要求要了解流失用户的特征,分析流失原因,预测用户流失,确定挽留目标用户并制定有效方案。

2.明确分析问题

  1. 分析用户特征与流失的关系。
  2. 从整体情况看,流失用户普遍具有哪些特征?
  3. 尝试找到合适的模型预测流失用户。
  4. 针对性给出增加用户黏性、预防流失的建议。

3.数据理解

本数据集来源Kaggle上公开的某电信公司的用户数据,共包含7043条数据,共21个字段。

python流失预警模型 流失用户预测模型_坐标轴

4.数据清洗

数据清洗的“完全合一”规则:

  • 完整性:单条数据是否存在空值,统计的字段是否完善。
  • 全面性:观察某一列的全部数值,通过常识来判断该列是否有问题,比如:数据定义、单位标识、数据本身。
  • 合法性:数据的类型、内容、大小的合法性。比如数据中是否存在非ASCII字符,性别存在了未知,年龄超过了150等。
  • 唯一性:数据是否存在重复记录,因为数据通常来自不同渠道的汇总,重复的情况是常见的。行数据、列数据都需要是唯一的。

4.1数据导入

# 导入数据分析需要的包
import pandas as pd
import numpy as np
# 可视化包
import seaborn as sns
sns.set(style="whitegrid")
import matplotlib.pyplot as plt
%matplotlib inline
# 忽略警告信息
import warnings
warnings.filterwarnings('ignore')
# 显示中文
plt.rcParams["font.sans-serif"] = ["SimHei"]
plt.rcParams["axes.unicode_minus"] = False
# 导入数据集
df=pd.read_csv('./WA_Fn-UseC_-Telco-Customer-Churn.csv')
# 查看数据
df.head(5)

4.2查看数据类型

df.info()

python流失预警模型 流失用户预测模型_数据分析_02

MonthlyCharges和TotalCharges都为费用,类型都应该为数值,
但通过查看发现TotalCharges为object对象类型,需要转化数值类型。

#强制转换数字(包括字符串)
df['TotalCharges']=df['TotalCharges'].apply(pd.to_numeric,errors='coerce')
#df['TotalCharges'] = df['TotalCharges'].convert_objects(convert_numeric=True)
df['TotalCharges'].dtypes
# 输出结果
dtype('float64')
# convert_numeric=True 表示强制转换数字(包括字符串),不可转换的值Nan
df['MonthlyCharges']=df['MonthlyCharges'].convert_objects(convert_numeric=True)                                                     
df['MonthlyCharges'].dtypes
# 输出结果
dtype('float64')

4.3缺失值处理

# 查看缺失值
df.isnull().sum()

python流失预警模型 流失用户预测模型_python流失预警模型_03

# 定位缺失值所在行,查看具体情况
df[df['TotalCharges']!=df['TotalCharges']][['tenure','MonthlyCharges','TotalCharges']]

python流失预警模型 流失用户预测模型_数据_04


分析:

发现这11个用户tenure(入网时常)为0,推测是当月新入网用户。

根据一般经验,用户即使在注册的当月流失,也需缴纳当月费用。

因此将这11个用户入网时长改为1,将总消费额填充为月消费额,符合实际情况。

  • 将总消费额填充为月消费额
#将总消费额填充为月消费额
df.loc[:,'TotalCharges'].replace(np.nan,df.loc[:,'MonthlyCharges'],inplace=True)
#查看是否替换成功
df[df['tenure']==0][['tenure','MonthlyCharges','TotalCharges']]

python流失预警模型 流失用户预测模型_python流失预警模型_05

  • 将‘tenure’入网时长从0修改为1
# 将‘tenure’入网时长从0修改为1
df.loc[:,'tenure'].replace(to_replace=0,value=1,inplace=True)

4.4数据归一化处理

对Churn 列中的值 Yes和 No分别用 1和 0替换,方便后续处理

df['Churn'].replace( 'Yes', 1,inplace = True)
df['Churn'].replace( 'No',  0,inplace = True)
df['Churn'].head()
# 保存数据集
df.to_csv('./Telecom_customer churn1.csv')

5.流失用户特征分析

根据用户主要特征。将用户属性分为三类,用户属性、服务属性及合同属性

python流失预警模型 流失用户预测模型_数据_06

  • 查看客户总体流失情况
# 查看总体客户流失情况
churnvalue = df["Churn"].value_counts()
labels = df["Churn"].value_counts().index
plt.pie(churnvalue,
        labels=["未流失","流失"],
        explode=(0.1,0),
        autopct='%.2f%%', 
        shadow=True,)
plt.title("客户流失率比例",size=24)
plt.show()

python流失预警模型 流失用户预测模型_坐标轴_07


分析:

从饼形图中看出,流失客户占总客户数的四分之一比例,流失率达26.54%

5.1用户属性分析

fig, axes = plt.subplots(2, 2, figsize=(12,12))
plt.subplot(2,2,1) 
# palette参数表示设置颜色
gender=sns.countplot(x='gender',hue="Churn",data=df,palette="Pastel2") 
plt.xlabel("性别",fontsize=16)
plt.ylabel('count',fontsize=16)
plt.tick_params(labelsize=12)     # 设置坐标轴字体大小
plt.title("Churn by Gender",fontsize=18)

plt.subplot(2,2,2)
seniorcitizen=sns.countplot(x="SeniorCitizen",hue="Churn",data=df,palette="Pastel2")
plt.xlabel("年龄(0代表是年轻人,1代表是老年人)")
plt.title("Churn by Senior Citizen",fontsize=18)
plt.ylabel('count',fontsize=16)
plt.tick_params(labelsize=12)   # 设置坐标轴字体大小

plt.subplot(2,2,3)
partner=sns.countplot(x="Partner",hue="Churn",data=df,palette="Pastel2")
plt.xlabel("婚姻状态(yes代表已婚,no代表未婚)")
plt.title("Churn by Partner",fontsize=18)
plt.ylabel('count',fontsize=16)
plt.tick_params(labelsize=12)   # 设置坐标轴字体大小

plt.subplot(2,2,4)
dependents=sns.countplot(x="Dependents",hue="Churn",data=df,palette="Pastel2")
plt.xlabel("经济能力(yes代表经济独立,no代表未独立)")
plt.title("Churn by Dependents",fontsize=18)
plt.ylabel('count',fontsize=16)
plt.tick_params(labelsize=12)   # 设置坐标轴字体大小
plt.tight_layout()
plt.show()

python流失预警模型 流失用户预测模型_python流失预警模型_08


分析:

  • 用户流失与性别基本无关,差异性较小;
  • 年老客户用户量相对较少,但流失率较高;
  • 未婚用户流失率高于已婚用户;
  • 经济能力未独立的用户流失率高于经济能力独立用户

5.2服务属性

查看网络安全服务、在线备份业务、设备保护业务、技术支持服务、
网络电视、网络电影和无互联网服务对客户流失率的影响。

covariables=["OnlineSecurity", "OnlineBackup", "DeviceProtection", "TechSupport", "StreamingTV", "StreamingMovies"]
fig,axes=plt.subplots(2,3,figsize=(20,12))
for i, item in enumerate(covariables):
    '''
    0,'OnlineSecurit'
    1,'OnlineBackup'
    '''
    plt.subplot(2,3,(i+1))
    ax=sns.countplot(x=item,hue="Churn",data=df,palette="Set2",order=["Yes","No","No internet service"])
    plt.xlabel(str(item),fontsize=16)
    plt.tick_params(labelsize=14)     # 设置坐标轴字体大小
    plt.title("Churn by "+ str(item),fontsize=20)
    i=i+1
plt.tight_layout()
plt.show()

python流失预警模型 流失用户预测模型_python流失预警模型_09


分析:

在这六个服务属性中,开通相关服务的用户流失率较低,

说明有这些方面需求的客户对此服务还比较满意,客户群体也比较稳定。

No internetserive这部分客户群体流失率基本相同,且相对较低。

5.3合同属性

  • 查看合同签订方式对用户流失的影响
fig = plt.figure(figsize=(12,8))
sns.barplot(x="Contract",
            y="Churn", 
            data=df, 
            palette="Pastel1", 
            order= ['Month-to-month', 
                    'One year', 'Two year'])
plt.title("Churn by Contract type",fontsize=20)
plt.xlabel('Contract',fontsize=16)
plt.ylabel('Churn',fontsize=16)
plt.tick_params(labelsize=13)     # 设置坐标轴字体大小
plt.show()

python流失预警模型 流失用户预测模型_python流失预警模型_10


分析:

从柱状图中看出,合同期限越长,流失率越低,一部分原因是选择长期合同的客户原本就比较稳定,

另外一个原因是长期合同具有长期约束率,在客户签订合同时,可以针对一次签订长期合同的客户进行优惠促销。

  • 查看付款方式对用户流失率的影响
plt.figure(figsize=(12,8))
sns.barplot(x="PaymentMethod",y="Churn", data=df, palette="Pastel1", order= ['Bank transfer (automatic)', 'Credit card (automatic)', 'Electronic check','Mailed check'])
plt.title("Churn by PaymentMethod type",fontsize=20)
plt.xlabel('PaymentMethod',fontsize=16)
plt.ylabel('Churn',fontsize=16)
plt.tick_params(labelsize=13)     # 设置坐标轴字体大小
plt.show()

python流失预警模型 流失用户预测模型_数据_11


分析:

在四种支付方式中,使用Electronic check的用户流失率最高,其他三种支付方式基本持平,因此可以推断电子账单在设计上影响用户体验。

  • 查看正常用户与流失用户在月消费金额上的差别
plt.figure(figsize=(10,6))
g = sns.FacetGrid(data = df,hue = 'Churn', height=4, aspect=3)
g.map(sns.distplot,'MonthlyCharges',norm_hist=True)
g.add_legend()
plt.ylabel('density',fontsize=16)
plt.xlabel('MonthlyCharges',fontsize=16)
plt.tick_params(labelsize=13)     # 设置坐标轴字体大小
plt.tight_layout()
plt.show()

python流失预警模型 流失用户预测模型_数据分析_12


分析:

从上图看出,月消费额大约在70-110这个区间内的用户流失率相对较高。

  • 查看正常用户与流失用户在总消费金额上的差别
g = sns.FacetGrid(data = df,hue = 'Churn', height=4, aspect=3)
g.map(sns.distplot,'TotalCharges',norm_hist=True)
g.add_legend()
plt.ylabel('density',fontsize=16)
plt.xlabel('TotalCharges',fontsize=16)
plt.tick_params(labelsize=13)     # 设置坐标轴字体大小
plt.show()

python流失预警模型 流失用户预测模型_数据分析_13


分析:

从上图看出,总消费金额越多,客户流失率会相对较低,

说明当客户对此产品形成一定消费习惯后,忠诚度会升高。

6.用户流失预测

对数据集进一步清洗和提取特征,通过特征选取对数据进行降维,采用机器学习模型应用于测试数据集,
然后对构建的分类模型准确性进行分析。

7 数据预处理

#导入数据分析需要的包
import pandas as pd
import numpy as np
#可视化包
import seaborn as sns
sns.set(style="whitegrid")
import matplotlib.pyplot as plt
%matplotlib inline
#忽略警告信息
import warnings
warnings.filterwarnings('ignore')
plt.rcParams["font.sans-serif"] = ["SimHei"]
plt.rcParams["axes.unicode_minus"] = False
#导入数据集
df = pd.read_csv('./Telecom_customer churn1.csv')
# CustomerID表示每个客户的标识,对后续建模不影响,这里选择删除CustomerID列
customerID=df['customerID']
df.drop("customerID",axis=1, inplace=True)
df.drop('Unnamed: 0',axis=1,inplace=True)
df.head()

7.1 特征编码

特征主要分为连续特征和离散特征,其中离散特征根据特征之间是否有大小关系又细分为两类。

  • 连续特征:“tenure”、“MonthlyCharges”、“TotalCharges”,一般采用归一标准化方式处理。
  • 离散特征:特征之间没有大小关系,如:PaymentMethod:[bank transfer,credit card,electronic check,mailed check],付费方式之间没有大小关系,一般采用one-hot编码。
  • 离散特征:特征之间有大小关联,则采用数值映射。本数据集无此类特征。

7.1.1 连续特征编码

对tenure(在网时长),MonthlyCharges(月费用),TotalCharges(总费用)三个连续特征进行标准化处理,
使特征数据标准为1,均值为0,符合标准的正态分布,降低数值特征过大对预测结果的影响。

from sklearn.preprocessing import StandardScaler  
# 实例化一个转换器类
scaler = StandardScaler(copy=False)
# 调用fit_transform,其作用是先拟合数据,然后转化它将其转化为标准形式
scaler.fit_transform(df[['tenure','MonthlyCharges','TotalCharges']])
#利用transform函数实现标准化,作用是通过找中心和缩放等实现标准化
df[['tenure','MonthlyCharges','TotalCharges']]=scaler.transform(df[['tenure','MonthlyCharges','TotalCharges']])
df[['tenure','MonthlyCharges','TotalCharges']].head()

python流失预警模型 流失用户预测模型_python_14

7.1.2 离散特征编码

# 查看对象类型字段中存在的值
def uni(columnlabel):
    # unique函数去除其中重复的元素,返回唯一值
    print(columnlabel,"--" ,df[columnlabel].unique())
    
dfobject=df.select_dtypes(['object'])
for i in range(0,len(dfobject.columns)):
    uni(dfobject.columns[i])

python流失预警模型 流失用户预测模型_python流失预警模型_15


结合服务属性,发现No internet service(无互联网服务)对客户流失率影响很小,

这些客户不使用任何互联网产品,因此可以将No internet service 和 No 是一样的效果,

可以使用 No 替代 No internet service

df.replace(to_replace='No internet service', value='No', inplace=True)
df.replace(to_replace='No phone service', value='No', inplace=True)
#再次查看
for i in range(0,len(dfobject.columns)):
    uni(dfobject.columns[i])

python流失预警模型 流失用户预测模型_数据分析_16

7.1.3 使用Scikit-learn标签编码,将离散特征转换为整数编码

# 编码转换
from sklearn.preprocessing import LabelEncoder  
def labelencode(columnlabel):
    df[columnlabel] = LabelEncoder().fit_transform(df[columnlabel])
    
for i in range(0,len(dfobject.columns)):
    labelencode(dfobject.columns[i])
    
for i in range(0,len(dfobject.columns)):
    uni(dfobject.columns[i])

python流失预警模型 流失用户预测模型_python流失预警模型_17

7.2 数据相关性分析

电信用户是否流失与各变量之间的相关性

plt.figure(figsize=(16,8))
df.corr()['Churn'].sort_values(ascending = False).plot(kind='bar')
plt.tick_params(labelsize=14)     # 设置坐标轴字体大小
plt.xticks(rotation=45)         # 设置x轴文字转向
plt.title("Correlations between Churn and variables",fontsize=20)
plt.show()

python流失预警模型 流失用户预测模型_坐标轴_18


分析:

从上图可以直观看出,PhoneService 、gender这两个变量与churn目标变量相关性最弱。

7.3 特征选取

特征值选取时,去除相关性较弱的PhoneService 、gender两个变量。这里目标变量是churn。

# 特征选取
dropFea = ['gender','PhoneService']
df.drop(dropFea, inplace=True, axis =1)
# 深拷贝
X=df.copy()
X.drop(['Churn'],axis=1, inplace=True)
y=df["Churn"]
#查看预处理后的数据
X.head()

8 构建模型

8.1 建立训练数据集和测试数据集

from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X,y,test_size = 0.3, random_state = 0)
print("原始训练集包含样本数量: ", len(X_train))
print("原始测试集包含样本数量:  ", len(X_test))
print("原始样本总数: ", len(X_train)+len(X_test))

#输出结果
原始训练集包含样本数量:  4930
原始测试集包含样本数量:   2113
原始样本总数:  7043

8.2 构建多个分类器

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              # 决策树分类器
from xgboost import XGBClassifier
from sklearn.ensemble import AdaBoostClassifier
from sklearn.ensemble import GradientBoostingClassifier   

from sklearn.metrics import precision_score, recall_score, f1_score
from sklearn.metrics import confusion_matrix
from sklearn.model_selection import GridSearchCV  # 网格搜索
# 使用分类算法
Classifiers=[
            ["Random Forest",RandomForestClassifier()],
            ["Support Vector Machine",SVC()],
            ["LogisticRegression",LogisticRegression()],
            ["KNN",KNeighborsClassifier(n_neighbors=5)],
            ["Naive Bayes",GaussianNB()],
            ["Decision Tree",DecisionTreeClassifier()],
            ["AdaBoostClassifier", AdaBoostClassifier()],
            ["GradientBoostingClassifier", GradientBoostingClassifier()],
            ["XGB", XGBClassifier()]
 
]

8.3 训练模型

Classify_result=[]
names=[]
prediction=[]
for name,classifier in Classifiers:
    classifier=classifier
    classifier.fit(X_train,y_train)
    y_pred=classifier.predict(X_test)
    recall=recall_score(y_test,y_pred)
    precision=precision_score(y_test,y_pred)
    f1score = f1_score(y_test, y_pred)
    class_eva=pd.DataFrame([recall,precision,f1score])
    Classify_result.append(class_eva)
    name=pd.Series(name)
    names.append(name)
    y_pred=pd.Series(y_pred)
    prediction.append(y_pred)

8.4 评估模型

召回率(recall)的含义是:原本为对的当中,预测为对的比例(值越大越好,1为理想状态)
精确率、精度(precision)的含义是:预测为对的当中,原本为对的比例(值越大越好,1为理想状态)
F1分数(F1-Score)指标综合了Precision与Recall的产出的结果
F1-Score的取值范围从0到1的,1代表模型的输出最好,0代表模型的输出结果最差。

classifier_names=pd.DataFrame(names)
# 转成列表
classifier_names=classifier_names[0].tolist()
result=pd.concat(Classify_result,axis=1)
result.columns=classifier_names
result.index=["recall","precision","f1score"]
result

python流失预警模型 流失用户预测模型_数据_19

8.5 实施方案

由于没有预测数据集,选择最后10条数为例进行预测。

# 提取customerID
pred_id = customerID.tail(10)
# 提取预测数据集特征(如果有预测数据集,可以一并进行数据清洗和特征提取)
pred_x = X.tail(10)

# 使用上述得到的最优模型
model = LogisticRegression	()
model.fit(X_train,y_train)
pred_y = model.predict(pred_x) # 预测值

# 预测结果
predDf = pd.DataFrame({'customerID':pred_id, 'Churn':pred_y})
print(predDf)

python流失预警模型 流失用户预测模型_数据分析_20

9 结论和建议

流失用户特征:

  • 用户属性:老年用户,未婚用户、经济未独立的用户更容易流失;
  • 服务属性:未开通相关附加增值服务的用户更容易流失;
  • 合同属性:签订的合同期较短,电子账单支付、月租费约70-110元的客户容易流失。

建议:
根据预测模型,构建一个高流失率的用户列表。通过用户调研推出一个最小可行化产品功能,并邀请种子用户进行试用。

  • 用户属性:针对老年用户、无亲属、无伴侣用户的特征退出定制服务如亲属套餐、温暖套餐等,一方面加强与其它用户关联度,另一方对特定用户提供个性化服务;
  • 服务属性:针对新入网用户,可以在购买时赠送增值服务体验资格,增强用户对增值服务的认知度;对存量老用户,可以根据用户行为特征,对于有购买这部分服务潜在用户,进行重点宣传推广,促使用户进一步购买,加强用户对产品的黏性;
  • 合同属性:针对单月合同用户,建议推出年合同付费折扣活动,将月合同用户转化为年合同用户,提高用户在网时长,以达到更高的用户留存。 针对采用电子支票支付用户,建议定向推送其它支付方式的优惠券,引导用户改变支付方式。