电信用户流失预测

电信用户流失预测

1.案例概述

任务描述:随着电信行业的不断发展,运营商们越来越重视如何扩大其客户群体。据研究,获取新客户所需的成本远高于保留现有客户的成本,因此为了满足在激烈竞争中的优势,保留现有客户成为一大挑战。对电信行业而言,可以通过数据挖掘等方式来分析可能影响客户决策的各种因素,以预测他们是否会产生流失(停用服务、转投其他运营商等)。
数据集:来自Kaggle平台。数据集一共提供了7043条用户样本,每条样本包含21列属性,由多个维度的客户信息以及用户是否最终流失的标签组成,客户信息具体如下:

基本信息:包括性别、年龄、经济情况、入网时间等;

开通业务信息:包括是否开通电话业务、互联网业务、网络电视业务、技术支持业务等;

签署的合约信息:包括合同年限、付款方式、每月费用、总费用等。

方法概述:电信用户流失预测中,运营商最为关心的是客户的召回率,即在真正流失的样本中,我们预测到多少条样本。其策略是宁可把未流失的客户预测为流失客户而进行多余的留客行为,也不漏掉任何一名真正流失的客户。预测的主要步骤主要有:数据预处理、可视化分析、特征工程、模型预测、模型评估、分析与决策。

电信用户流失预测

2.数据预处理

首先查看数据集中是否有重复值,缺失值。

dupNum = data.shape[0] - data.drop_duplicates().shape[0]
print("数据集中有%s列重复值" % dupNum)';
数据集中有0列重复值
data.isnull().any()
customerID          False
gender              False
SeniorCitizen       False
Partner             False
Dependents          False
tenure              False
PhoneService        False
MultipleLines       False
InternetService     False
OnlineSecurity      False
OnlineBackup        False
DeviceProtection    False
TechSupport         False
StreamingTV         False
StreamingMovies     False
Contract            False
PaperlessBilling    False
PaymentMethod       False
MonthlyCharges      False
TotalCharges        False
Churn               False
dtype: bool

数据值中无缺失值

异常值处理

data.describe()

电信用户流失预测
数据无异常值

3.可视化分析

3.1流失客户占比

### 性别、是否老年人、是否有配偶、是否有家属等特征对客户流失的影响
baseCols = ['gender', 'SeniorCitizen', 'Partner', 'Dependents']

for i in baseCols:
    cnt = pd.crosstab(data[i], data['Churn'])    # 构建特征与目标变量的列联表
    cnt.plot.bar(stacked=True)    # 绘制堆叠条形图,便于观察不同特征值流失的占比情况
    plt.show()    # 展示图像

电信用户流失预测

由图可知:性别对客户流失基本没有影响;年龄对客户流失有影响,老年人流失占比高于年轻人;是否有配偶对客户流失有影响,无配偶客户流失占比高于有配偶客户;是否有家属对客户流失有影响,无家属客户流失占比高于有家属客户。

3.2观察流失率与入网月数的关系

# 折线图
groupDf = data[['tenure', 'Churn']]    # 只需要用到两列数据
groupDf['Churn'] = groupDf['Churn'].map({'Yes': 1, 'No': 0})    # 将正负样本目标变量改为1和0方便计算
pctDf = groupDf.groupby(['tenure']).sum() / groupDf.groupby(['tenure']).count()    # 计算不同入网月数对应的流失率
pctDf = pctDf.reset_index()    # 将索引变成列

plt.figure(figsize=(10, 5))
plt.plot(pctDf['tenure'], pctDf['Churn'], label='Churn percentage')    # 绘制折线图
plt.legend()    # 显示图例
plt.show() 
### 观察流失率与入网月数的关系
# 折线图
groupDf = data[['tenure', 'Churn']]    # 只需要用到两列数据
groupDf['Churn'] = groupDf['Churn'].map({'Yes': 1, 'No': 0})    # 将正负样本目标变量改为1和0方便计算
pctDf = groupDf.groupby(['tenure']).sum() / groupDf.groupby(['tenure']).count()    # 计算不同入网月数对应的流失率
pctDf = pctDf.reset_index()    # 将索引变成列

plt.figure(figsize=(10, 5))
plt.plot(pctDf['tenure'], pctDf['Churn'], label='Churn percentage')    # 绘制折线图
plt.legend()    # 显示图例
plt.show()

电信用户流失预测
电信用户流失预测
由图可知:流失率随着时间的推移呈现出下降趋势,当入网超过2个月时流失率超过留存率,这个阶段可视为用户适应期。

3.3 业务特征对客户流失影响

# 电话业务
posDf = data[data['PhoneService'] == 'Yes']
negDf = data[data['PhoneService'] == 'No']

fig = plt.figure(figsize=(10,4)) # 建立图像

ax1 = fig.add_subplot(121)
p1 = posDf['Churn'].value_counts()
ax1.pie(p1,labels=['No','Yes'],autopct='%1.2f%%',explode=(0,0.1))
ax1.set_title('Churn of (PhoneService = Yes)')

ax2 = fig.add_subplot(122)
p2 = negDf['Churn'].value_counts()
ax2.pie(p2,labels=['No','Yes'],autopct='%1.2f%%',explode=(0,0.1))
ax2.set_title('Churn of (PhoneService = No)')

plt.tight_layout(pad=0.5)    # 设置子图之间的间距
plt.show() # 展示饼状图

电信用户流失预测
由图可知:是否开通电话用户,对客户流失影响小

3.4 合约特征对客户流失影响

# 合约期限
df1 = data[data['Contract'] == 'Month-to-month']
df2 = data[data['Contract'] == 'One year']
df3 = data[data['Contract'] == 'Two year']

fig = plt.figure(figsize=(15,4)) # 建立图像

ax1 = fig.add_subplot(131)
p1 = df1['Churn'].value_counts()
ax1.pie(p1,labels=['No','Yes'],autopct='%1.2f%%',explode=(0,0.1))
ax1.set_title('Churn of (Contract = Month-to-month)')

ax2 = fig.add_subplot(132)
p2 = df2['Churn'].value_counts()
ax2.pie(p2,labels=['No','Yes'],autopct='%1.2f%%',explode=(0,0.1))
ax2.set_title('Churn of (Contract = One year)')

ax3 = fig.add_subplot(133)
p3 = df3['Churn'].value_counts()
ax3.pie(p3,labels=['No','Yes'],autopct='%1.2f%%',explode=(0,0.1))
ax3.set_title('Churn of (Contract = Two year)')

plt.tight_layout(pad=0.5)    # 设置子图之间的间距
plt.show() # 展示饼状图

电信用户流失预测
由图可知:合约期越长,用户流失率越低。

4.特征工程

4.1 特征提取

### 数值特征标准化
from sklearn.preprocessing import StandardScaler    # 导入标准化库

'''
注:

新版本的sklearn库要求输入数据是二维的,而例如data['tenure']这样的Series格式本质上是一维的
如果直接进行标准化,可能报错 "ValueError: Expected 2D array, got 1D array instead"
解决方法是变一维的Series为二维的DataFrame,即多加一组[],例如data[['tenure']]
'''
scaler = StandardScaler()
data[['tenure']] = scaler.fit_transform(data[['tenure']])
data[['MonthlyCharges']] = scaler.fit_transform(data[['MonthlyCharges']])
data[['TotalCharges']] = scaler.fit_transform(data[['TotalCharges']])

data[['tenure', 'MonthlyCharges', 'TotalCharges']].head()    # 观察此时的数值特征

4.2 特征选择

# 删去无用特征 'customerID'、'gender'、 'PhoneService'、'StreamingTV'和'StreamingMovies'
data = data.drop(['customerID', 'gender', 'PhoneService', 'StreamingTV', 'StreamingMovies'], axis=1)
nu_fea = data[['tenure', 'MonthlyCharges', 'TotalCharges']]    # 选择连续型数值特征计算相关系数
nu_fea = list(nu_fea)    # 特征名列表
pearson_mat = data[nu_fea].corr(method='spearman')    # 计算皮尔逊相关系数矩阵

plt.figure(figsize=(8,8)) # 建立图像
sns.heatmap(pearson_mat, square=True, annot=True, cmap="YlGnBu")    # 用热度图表示相关系数矩阵
plt.show() # 展示热度图

# 模型预测

交叉验证
```javascript
# K折交叉验证代码
# from sklearn.cross_validation import KFold
from sklearn.model_selection import KFold

def kFold_cv(X, y, classifier, **kwargs):
    """
    :param X: 特征
    :param y: 目标变量
    :param classifier: 分类器
    :param **kwargs: 参数
    :return: 预测结果
    """
    kf = KFold(n_splits=5, shuffle=True) 
    y_pred = np.zeros(len(y))    # 初始化y_pred数组
    
    for train_index, test_index in kf.split(X):  
        X_train = X[train_index]    
        X_test = X[test_index]
        y_train = y[train_index]    # 划分数据集
        clf = classifier(**kwargs)    
        clf.fit(X_train, y_train)    # 模型训练
        y_pred[test_index] = clf.predict(X_test)    # 模型预测
    
    return y_pred  

from sklearn.ensemble import RandomForestClassifier as RF    # 随机森林

# X = data.iloc[:, :-1].as_matrix()
X = data.iloc[:, :-1].iloc[:,:].values # Kagging
y = data.iloc[:, -1].values

rf_pred = kFold_cv(X, y, RF)

5. 模型评估

对电信用户流失预测问题,通常更关心真正流失的用户,因此需要寻找一个能够较好地衡量这一现象的评价指标。观察下面的混淆矩阵:
电信用户流失预测
精确率代表的意义是:在所有我们预测为流失的样本中,真正流失的样本数;召回率代表的意义则是:在真正流失的样本中,我们预测到多少条样本。

# 特征重要性
X = data.iloc[:, :-1].values
y = data.iloc[:, -1].values

kf = KFold(n_splits=5, shuffle=True, random_state=0)
y_pred = np.zeros(len(y))    # 初始化y_pred数组
clf = RF()

for train_index, test_index in kf.split(X):
    X_train = X[train_index]    
    X_test = X[test_index]
    y_train = y[train_index]    # 划分数据集
    clf.fit(X_train, y_train)    # 模型训练
    y_pred[test_index] = clf.predict(X_test)    # 模型预测
    
feature_importances = pd.DataFrame(clf.feature_importances_,
                                   index = data.columns.drop(['Churn']),
                                    columns=['importance']).sort_values('importance', ascending=False)
feature_importances    # 查看特征重要性

电信用户流失预测

6.分析与决策

6.1 结合用户画像
在可视化阶段,可以发现较易流失的客户在各个特征的用户画像如下:

基本信息
老年人
未婚
无家属
入网时间不长,特别是2个月之内

开通业务
开通光纤网络
未开通在线安全、在线备份、设备保护、技术支持等互联网增值业务

签订合约
合约期限较短,特别是逐月付费客户最易流失
采用电子结算(多为按月支付)
采用电子支票
每月费用较高,特别是70~110之间
总费用较低(侧面反应入网时间较短)

根据用户画像,可以从各个方面推出相应活动以求留下可能流失的客户:
对老人推出亲情套餐等优惠
对未婚、无家属的客户推出暖心套餐等优惠
对新入网用户提供一定时期的优惠活动,直至客户到达稳定期
提高电话服务、光纤网络、网络电视、网络电影等的客户体验,尝试提高用户的留存率,避免客户流失
对能够帮助客户留存的在线安全、在线备份、设备保护、技术支持等互联网增值业务,加大宣传推广力度
对逐月付费用户推出年费优惠活动
对使用电子结算、电子支票的客户,推出其他支付方式的优惠活动
对每月费用在70~110之间推出一定的优惠活动

6.2 结合模型

在模型预测阶段,可以结合预测出的概率值决定对哪些客户进行重点留存:

'''# 预测客户流失的概率值
def prob_cv(X, y, classifier, **kwargs):
    """
    :param X: 特征
    :param y: 目标变量
    :param classifier: 分类器
    :param **kwargs: 参数
    :return: 预测结果
    """
    kf = KFold(n_splits=5, random_state=0)
    y_pred = np.zeros(len(y))    
    
    for train_index, test_index in kf.split(X):
        X_train = X[train_index]    
        X_test = X[test_index]
        y_train = y[train_index]    
        clf = classifier(**kwargs)    
        clf.fit(X_train, y_train)    
        y_pred[test_index] = clf.predict_proba(X_test)[:,1]    # 注:此处预测的是概率值
    
    return y_pred  
prob = prob_cv(X, y, RF)    # 预测概率值
prob = np.round(prob, 1)    # 对预测出的概率值保留一位小数,便于分组观察

# 合并预测值和真实值
probDf = pd.DataFrame(prob)
churnDf = pd.DataFrame(y)
df1 = pd.concat([probDf, churnDf], axis=1)
df1.columns = ['prob', 'churn']

df1 = df1[:7043]    # 只取原始数据集的7043条样本进行决策
df1.head(10)
group = df1.groupby(['prob'])
cnt = group.count()    # 每种概率值对应的样本数
true_prob = group.sum() / group.count()    # 真实流失率
df2 = pd.concat([cnt,true_prob], axis=1).reset_index()
df2.columns = ['prob', 'cnt', 'true_prob']

df2'''

电信用户流失预测
预测流失率越大的客户越有可能发生真正的流失,需对预测结果大于0.9的客户进行重点留存。

上一篇:逻辑回归


下一篇:(原)理解码率控制模式(x264,x265,vpx)