项目介绍

【项目背景】

当下我国正处于从4G网络向5G网络转型的过程中。5G网络拥有高可靠、低时延、大带宽、大连接等特性,在赋智慧城市、远程医疗和自动驾驶等领域都有很好的应用效果。从4G时代迈向5G时代,有利于发展多种新兴产业,也有利于居民的生活质量,而提升移动端的5G用户数量正是4G时代向5G时代的转变中至关重要一步。

【项目目标】

(1)对用户的数据进行分析,针对5G业务形成认知,找到关键指标,并对如何推广5G业务提出建议。

(2)构建5G潜客识别模型。

本项目涵盖了一个数据分析/ 机器学习项目的大部分关键内容。

【主要内容】

(1)数据摸底+数据处理: 对数据集进行修改、了解和梳理,做缺失值处理和异常值处理。

(2)EDA: 以可视化、相关性分析等手段进行探索性数据分析,深入了解数据,总结了一些机器学习部分可以参考的信息,以及一些对5G业务的认知与建议。(重要的结论都汇总在“EDA总结”这一小节中。)

(3)特征工程和机器学习 :构建潜客识别模型。

p.s:具体的内容请看左边的目录。

【数据集简介】

使用的数据集来自中国移动“梧桐杯”大数据应用创新大赛重庆赛区的比赛题目,具体数据由重庆移动大数据平台提供。

原数据集分为1个训练集和2个预测集,预测集的目标变量的答案在网上暂时找不到,本项目仅使用了训练集(后称数据集)。数据集包含了重庆移动用户的43个特征变量和1个目标变量,总数据量为28W行。根据项目概述可将这44个变量按照业务上的含义分为10组,分别为用户标识相关字段,用户基础信息相关字段,消费行为信息相关字段,超套信息相关字段,宽带信息相关字段,签约信息相关字段,套餐信息,流量饱和度信息,其他信息相关字段,标签字段。

样本数据特征集的字段说明如下(以下内容截取子上方链接中的项目概述):在这里插入图片描述
在这里插入图片描述

# 导入所需的库
# 导入所需的库
import pandas as pd
import numpy as np
import missingno as msno
import matplotlib
import matplotlib.pyplot as plt
import matplotlib.ticker as mtick
import matplotlib.gridspec as gridspec
import seaborn as sns
import warnings
warnings.filterwarnings('ignore')
import time
import math
import copy
import sklearn
from collections import OrderedDict
from pylab import mpl
from scipy.stats.mstats import winsorize
from scipy.stats import shapiro
from sklearn.preprocessing import StandardScaler
from sklearn.preprocessing import LabelEncoder
from sklearn.preprocessing import OneHotEncoder
from sklearn import naive_bayes
# from sklearn.naive_bayes import CategoricalNB
from sklearn.naive_bayes import GaussianNB
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import RandomForestClassifier
from sklearn.naive_bayes import GaussianNB
from sklearn.neighbors import KNeighborsClassifier
from sklearn.svm import SVC
from sklearn.neural_network import MLPClassifier
from sklearn.ensemble import AdaBoostClassifier
from sklearn.ensemble import GradientBoostingClassifier
from sklearn.ensemble import ExtraTreesClassifier
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
from xgboost import XGBClassifier
from sklearn import metrics
from sklearn.metrics import roc_curve
from sklearn.metrics import roc_auc_score
from sklearn.metrics import plot_roc_curve
from sklearn.metrics import recall_score, confusion_matrix, precision_score, f1_score, accuracy_score, classification_report
from sklearn.metrics import precision_recall_curve
from sklearn.linear_model import LinearRegression
from sklearn.ensemble import StackingClassifier
# 导入数据
train1 = pd.read_csv(r'/home/mw/input/5G5G3453/train_set.csv')    # 训练集-特征数据
train2 = pd.read_csv(r'/home/mw/input/5G5G3453/train_label.csv')    # 训练集-标签数据
print(train1.shape, train2.shape)
print()
print(train1.columns)
print()
print(train2.columns)

在这里插入图片描述

# 合并2个训练集
df = pd.merge(train1,train2,how='inner',on='user_id')
pd.set_option('display.max_columns', 50) # 设置显示的最大列数
pd.set_option('display.max_rows', 14) # 设置显示的最大行数

数据摸底+数据处理

对数据集进行了解、修改和梳理,做缺失值处理和异常值处理。

数据摸底

检查数据集的行列数。

df.shape

检查各字段的字段名和数据类型。

df.info()

在这里插入图片描述
修改列名。(根据项目概述中给的信息)

# 用户标识相关字段
df.rename(columns = {'user_id': '用户标识', 'product_no': '用户号码'}, inplace = True)
# 用户基础信息相关字段
df.rename(columns = {'X1': '性别', 'X2': '年龄'                                       
                   , 'X3': '星级', 'X4': '在网时长', 'X5': '细分市场'}, inplace = True)
# 消费行为信息相关字段
df.rename(columns={'X6': '当月arpu', 'X7': '上月arpu', 'X8': '上上月arpu'    
                 , 'X9': '当月dou',  'X10': '上月dou',  'X11': '上上月dou'
                 , 'X12': '当月mou',  'X13': '上月mou',  'X14': '上上月mou'
                 , 'X15': '近三月平均arpu', 'X16': '近三月平均dou', 'X17': '近三月平均mou'}, inplace = True)
# 超套信息相关字段
df.rename(columns={'X18': '当月语音超套金额', 'X19': '上月语音超套金额', 'X20': '上上月语音超套金额'    
                 , 'X21': '当月流量超套金额', 'X22': '上月流量超套金额', 'X23': '上上月流量超套金额'}, inplace = True)
# 宽带信息相关字段
df.rename(columns={'X24': '是否本网宽带用户', 'X25': '是否异网宽带用户', 'X26': '宽带带宽'    
                 , 'X27': '宽带是否激活'}, inplace = True)
# 签约信息相关字段
df.rename(columns={'X28': '宽带捆绑签约标识', 'X29': '终端捆绑签约标识', 'X30': '话费签约标识'    
                 , 'X31': '套餐签约标识'}, inplace = True)
# 套餐信息相关字段
df.rename(columns={'X32': '用户总套餐价值(包含主题套餐和自选套餐等)', 'X33': '用户主资费套餐'}, inplace = True)
# 流量饱和度信息相关字段
df.rename(columns={'X34': '当月用户流量饱和度', 'X35': '上月用户流量饱和度', 'X36': '上上月月用户流量饱和度'}, inplace = True)
# 其他信息相关字段
df.rename(columns={'X37': '是否家庭用户', 'X38': '5G流量,空值表示未产生5G流量', 'X39': '终端类型'    
                 , 'X40': '当月是否低消保号用户',  'X41': '当月是否换机',  'X42': '居住地是否覆盖5g标识'
                 , 'X43': '工作地是否覆盖5G基站'}, inplace = True)
# 标签字段
df.rename(columns={'label': '标签'}, inplace = True)

进一步修改列名。

为了提高列名的一致性,将字段名‘宽带是否激活’修改为‘是否激活宽带’。

为了提高列名的一致性,将签约信息相关的4个字段名从‘xxx标识’修改为‘是否xxx’。

为了提高列名的一致性,将’居住地是否覆盖5g标识’字段重命名为’居住地是否覆盖5G’。将’工作地是否覆盖5G基站’字段重命名为’工作地是否覆盖5G’。

为了让列名更简洁,将字段名‘5G流量,空值表示未产生5G流量’,修改为‘5G流量’。

为了让列名更简洁、准确,将字段名‘用户总套餐价值(包含主题套餐和自选套餐等)’修改为‘用户总套餐价值’,将字段名‘用户主资费套餐’修改为‘用户主资费套餐价值’。

观察发现,字段名‘上上月月用户流量饱和度’存在错误,将它修改为‘上上月用户流量饱和度’。

df.rename(columns={'宽带是否激活': '是否激活宽带'}, inplace = True)

df.rename(columns={'宽带捆绑签约标识': '是否宽带捆绑签约', '终端捆绑签约标识': '是否终端捆绑签约'
        , '话费签约标识': '是否话费签约', '套餐签约标识': '是否套餐签约'}, inplace = True)

df.rename(columns={'居住地是否覆盖5g标识': '居住地是否覆盖5G', '工作地是否覆盖5G基站': '工作地是否覆盖5G'}, inplace = True)

df.rename(columns={'5G流量,空值表示未产生5G流量': '5G流量'}, inplace = True)

df.rename(columns={'用户总套餐价值(包含主题套餐和自选套餐等)': '用户总套餐价值', '用户主资费套餐': '用户主资费套餐价值'}, inplace = True)

df.rename(columns={'上上月月用户流量饱和度': '上上月用户流量饱和度'}, inplace = True)
df.columns

在这里插入图片描述
下面检查重复值。观察发现,每一条记录对应一个用户标识和一个用户号码,检查这两个字段的取值是否有重复。(两个字段的取值都相同则算存在重复值。)

s = df.groupby(['用户标识', '用户号码']).size()
n = len(s[s>1])

print("'用户标识', '用户号码'字段的重复值数量为:", n)

在这里插入图片描述经检查,本表不存在重复记录。
删除用不上的字段:‘用户标识’, ‘用户号码’。

del_col = ['用户标识', '用户号码']
df.drop(labels=del_col, axis=1, inplace = True)

# 检查是否成功删除
n=0
for i in del_col:
    try:
        df[i]
        n+=1
    except:
        continue
if n == 0:
    print('删除已完成')
else:
    print('删除出错')

删除已完成
查看最终的列名。

df.columns
for i in df.columns:
    print(df.groupby(i).size().sort_values(ascending=False))
    print()

在这里插入图片描述df.info()
在这里插入图片描述
经检查,各字段的数据类型与项目概述中描述的一致。

确定分类变量,数值变量,目标变量。

依据项目目标,把‘标签’字段设为目标变量,该字段表明了用户是否为5G用户。 下面将取值种类小于等于10种的变量划分为分类变量,大于10种的划分为数值变量。

col = list(df.columns)
cat_col = []
num_col = []

l = list(df.columns)
l.remove('标签')

for i in l:
    s = df.groupby(i).size()
    n = len(s)
    if n <= 10:
        cat_col.append(i)
    else:
        num_col.append(i)

print('分类变量为:', '\n', cat_col)
print()
print('数值变量为:', '\n', num_col)

在这里插入图片描述
按照项目描述中给出的思路,将数据集中的字段按照业务含义进行如下分组:

用户基础信息相关字段共有5个:‘性别’, ‘年龄’, ‘星级’, ‘在网时长’, ‘细分市场’ 。其中分类变量共有3个:‘性别’, ‘星级’, ‘细分市场’。数值变量共有2个:‘年龄’, ‘在网时长’。

消费行为信息相关字段共有12个:‘当月arpu’, ‘上月arpu’, ‘上上月arpu’, ‘当月dou’, ‘上月dou’, ‘上上月dou’, ‘当月mou’, ‘上月mou’, ‘上上月mou’, ‘近三月平均arpu’, ‘近三月平均dou’, ‘近三月平均mou’。它们全部为数值变量。

超套信息相关字段共有6个:‘当月语音超套金额’, ‘上月语音超套金额’, ‘上上月语音超套金额’, ‘当月流量超套金额’, ‘上月流量超套金额’, ‘上上月流量超套金额’。它们全部为数值变量。

宽带信息相关字段共有4个:‘是否本网宽带用户’, ‘是否异网宽带用户’, ‘宽带带宽’, ‘是否激活宽带’。它们全部为分类变量。

签约信息相关字段共有4个:[‘是否宽带捆绑签约’, ‘是否终端捆绑签约’, ‘是否话费签约’, ‘是否套餐签约’]。它们全部为分类变量。

套餐信息相关字段共有2个:[‘用户总套餐价值’, ‘用户主资费套餐价值’]。它们都是数值变量。

流量饱和度信息相关字段共有3个:[‘当月用户流量饱和度’, ‘上月用户流量饱和度’, ‘上上月用户流量饱和度’]。它们都是数值变量。

其他信息的相关字段共有7个:[‘是否家庭用户’, ‘5G流量’, ‘终端类型’, ‘当月是否低消保号用户’, ‘当月是否换机’, ‘居住地是否覆盖5G’, ‘工作地是否覆盖5G’]。 分类变量共有6个:[[‘是否家庭用户’, ‘终端类型’, ‘当月是否低消保号用户’, ‘当月是否换机’, ‘居住地是否覆盖5G’, ‘工作地是否覆盖5G’]]。 数值变量共有1个:[5G流量]。

标签字段分组只有一个字段’标签’,它是分类型变量,是本项目的目标变量。

缺失值处理、异常值处理

检查各字段的缺失值数量。

with pd.option_context('display.max_rows', None,):
   print('存在空值的字段及其空值数:')
   print()
   print(df.isnull().sum()[df.isnull().sum()>0])

在这里插入图片描述
经检查,数据集的部分字段存在缺失值。 下面对各个字段分组中的各个字段逐一进行缺失值处理和异常值处理。

对于分类变量,不做异常值检测。 对一些数值变量,在进行异常值检验后,决定保留这些异常值,原因如下: (1)认为这些异常值并非来自于某些错误(比如输入错误),而是代表着某些较为罕见的真实情况。 (2)在项目的EDA部分,排除这些罕见的数据会产生一些错误。 (3)在项目的机器学习部分,除了对异常值敏感的模型(如逻辑回归),我们也采用了一些对异常值不敏感的模型,例如随机森林。这样即使不排除异常值,也能做出不错的预测。 对于一些数值变量,在了解它的分布情况之后,直接跳过异常值检测和异常值处理。

用户基础信息相关字段

用户基础信息相关字段共有5个:‘性别’, ‘年龄’, ‘星级’, ‘在网时长’, ‘细分市场’ 。其中分类变量共有3个:‘性别’, ‘星级’, ‘细分市场’。数值变量共有2个:‘年龄’, ‘在网时长’

cat_var = ['性别', '星级', '细分市场']
num_var = ['年龄', '在网时长']
var = cat_var + num_var

观察字段的分布情况。

print("查询分类变量的所有取值,和每种取值对应的数量:")
for i in cat_var:
    print()
    print(df[i].value_counts(dropna = False))

在这里插入图片描述

print('观察数值变量的分布情况')
df[num_var].describe()

在这里插入图片描述

缺失值处理 检查缺失值。
print('检查缺失值值数量', end = '\n\n')
df[var].isnull().sum()

在这里插入图片描述

cols_with_missing = []

x = df[var].isnull().any(axis=0)
for i in x.index:
    if x[i] == True:
        cols_with_missing.append(i)

cols_with_missing

在这里插入图片描述查看这些字段中任意一个字段为空值的记录。

df[df['星级'].isnull()]

在这里插入图片描述

df[df['细分市场'].isnull()]

在这里插入图片描述对于每个有缺失值的字段ci,新建一个字段:ci_曾有缺失值。某行的ci字段如有缺失值,则该行的ci_曾有缺失值字段为True,否则为False。 对所有缺失值用众数进行填充。

for i in cols_with_missing:
    df[i + '_曾有缺失值'] = df[i].isnull()
df.fillna(value = {'星级': df['星级'].mode()[0]                  
, '细分市场': df['细分市场'].mode()[0]              
}, inplace = True )
print('检查缺失值数量', end = '\n\n')

df[var].isnull().sum()

在这里插入图片描述

异常值处理
print('观察数值变量的分布情况')
df[num_var].describe()

在这里插入图片描述观察本组的数值变量后,决定不对这些字段做异常值处理。

消费行为信息相关字段
消费行为信息相关字段共有12个:‘当月arpu’, ‘上月arpu’, ‘上上月arpu’, ‘当月dou’, ‘上月dou’, ‘上上月dou’, ‘当月mou’, ‘上月mou’, ‘上上月mou’, ‘近三月平均arpu’, ‘近三月平均dou’, ‘近三月平均mou’。它们全部为数值变量。

cat_var = []
num_var = ['当月arpu', '上月arpu', '上上月arpu'
        , '当月dou', '上月dou', '上上月dou'
        , '当月mou', '上月mou', '上上月mou'
        , '近三月平均arpu', '近三月平均dou', '近三月平均mou']      
var = cat_var + num_var

观察字段的分布情况。

print('观察数值变量的分布情况')
df[num_var].describe()

在这里插入图片描述缺失值处理

检查缺失值。

print('检查缺失值值数量', end = '\n\n')
df[var].isnull().sum()

在这里插入图片描述

cols_with_missing = []

x = df[var].isnull().any(axis=0)
for i in x.index:
    if x[i] == True:
        cols_with_missing.append(i)

cols_with_missing

在这里插入图片描述
消费行为信息相关的12个字段的缺失值的数量相同,猜测这12个字段总是一起取空值。

n = df[cols_with_missing].isnull().all(axis=1).sum()  #.all()把一个bool dataframe沿列轴方向压缩成series。
x = 1
for i in cols_with_missing:
    if df[i].isnull().sum() != n:
        x = 0
        break
if x == 1:
    print('是的,这些字段总是一起取空值。')
else:
    print('不是,这些字段不总是一起取空值。')

在这里插入图片描述

上面的猜测得到证实,任意一条记录的这些字段的数据,总是一起取空值。

查看这些字段中任意一个字段为空值的记录。

df[df[var].isnull().any(axis=1)]

在这里插入图片描述

df.dropna(subset=cols_with_missing, axis = 0, inplace = True)
print('检查缺失值数量', end = '\n\n')

df[var].isnull().sum()

在这里插入图片描述

print('观察数值变量的分布情况')
df[num_var].describe()

在这里插入图片描述
下面用IQR方法进行异常值检测。把小于第一四分位数,或大于第三四分位数的数据判定为异常值。

outlier_index = {}
for i in var:    
    Percentile = np.percentile(df[i],[0,25,50,75,100])
    IQR = Percentile[3] - Percentile[1] 
    DownLimit = Percentile[1] - IQR*1.5
    UpLimit   = Percentile[3] + IQR*1.5
    LowerOutlierIndex = df[i]<DownLimit
    UpperOutlierIndex = df[i]>UpLimit
    outlier_index[i] = [LowerOutlierIndex, UpperOutlierIndex]

describes_lower_outlier = [df[outlier_index[i][0]][i].describe() for i in var]
describes_upper_outlier = [df[outlier_index[i][1]][i].describe() for i in var]

观察各列低于Q1-1.5IQR的数据的分布情况。

pd.concat(describes_lower_outlier, axis=1)

在这里插入图片描述
观察各列高于Q3+1.5IQR的数据的分布情况。

pd.concat(describes_upper_outlier, axis=1)

在这里插入图片描述决定将这些异常值全部保留。

超套信息相关字段

超套信息相关字段共有6个:‘当月语音超套金额’, ‘上月语音超套金额’, ‘上上月语音超套金额’, ‘当月流量超套金额’, ‘上月流量超套金额’, ‘上上月流量超套金额’。它们全部为数值变量。

cat_var = []
num_var = ['当月语音超套金额', '上月语音超套金额', '上上月语音超套金额', '当月流量超套金额', '上月流量超套金额', '上上月流量超套金额']
var = cat_var + num_var

观察字段的分布情况。

print('观察数值变量的分布情况')
df[num_var].describe()

在这里插入图片描述
缺失值处理

检查缺失值。

print('检查缺失值值数量', end = '\n\n')
df[var].isnull().sum()

在这里插入图片描述

cols_with_missing = []

x = df[var].isnull().any(axis=0)
for i in x.index:
    if x[i] == True:
        cols_with_missing.append(i)

cols_with_missing

超套信息相关的6个字段的缺失值的数量相同,猜测这6个字段总是同时取空值。

n = df[cols_with_missing].isnull().all(axis=1).sum()  #.all()把一个bool dataframe沿列轴方向压缩成series。
x = 1
for i in cols_with_missing:
    if df[i].isnull().sum() != n:
        x = 0
        break
if x == 1:
    print('是的,这些字段总是一起取空值。')
else:
    print('不是,这些字段不总是一起取空值。')

上面的猜测得到证实,任意一条记录的这些字段的数据,总是一起取空值。
查看这些字段中任意一个字段为空值的记录。

df[df[var].isnull().all(axis=1)]

在这里插入图片描述
把这些字段中任意一个字段为空值的记录删除。

df.dropna(subset=cols_with_missing, axis = 0, inplace = True)
print('检查缺失值数量', end = '\n\n')

df[var].isnull().sum()

在这里插入图片描述
异常值处理

print('观察数值变量的分布情况')
df[num_var].describe()

在这里插入图片描述采用缩尾法进行异常值检测。把小于第1百分位数,或大于第99百分位数的数据判定为异常值。

outlier_index = {}
for i in var:
    DownLimit = np.percentile(df[i], 1)
    UpLimit = np.percentile(df[i], 99)
    outlier_index[i] = [df[i]<DownLimit, df[i]>UpLimit]

describes_lower_outlier = [df[outlier_index[i][0]][i].describe() for i in num_var]
describes_upper_outlier = [df[outlier_index[i][1]][i].describe() for i in num_var]

观察各列低于第1百分位数的数据的分布情况。

pd.concat(describes_lower_outlier, axis=1)

在这里插入图片描述
观察各列高于第99百分位数的数据的分布情况。

pd.concat(describes_upper_outlier, axis=1)

在这里插入图片描述

l = np.array(range(1, 100)) / 100
df[num_var].describe(percentiles = l)

在这里插入图片描述
决定将这些异常值全部保留。

宽带信息相关字段

宽带信息相关字段共有4个:‘是否本网宽带用户’, ‘是否异网宽带用户’, ‘宽带带宽’, ‘是否激活宽带’。它们全部为分类变量。

cat_var = ['是否本网宽带用户', '是否异网宽带用户', '宽带带宽', '是否激活宽带']
num_var = []
var = cat_var + num_var

观察字段的分布情况。

print("查询分类变量的所有取值,和每种取值对应的数量:")
for i in cat_var:
    print()
    print(df[i].value_counts(dropna = False))

在这里插入图片描述
缺失值处理

检查缺失值。

print('检查缺失值值数量', end = '\n\n')
df[var].isnull().sum()

在这里插入图片描述

cols_with_missing = []

x = df[var].isnull().any(axis=0)
for i in x.index:
    if x[i] == True:
        cols_with_missing.append(i)

cols_with_missing

‘宽带带宽’, '是否激活宽带’这2个字段的缺失值的数量相同,猜测这2个字段总是一起取空值。

n = df[cols_with_missing].isnull().all(axis=1).sum()  #.all()把一个bool dataframe沿列轴方向压缩成series。
x = 1
for i in cols_with_missing:
    if df[i].isnull().sum() != n:
        x = 0
        break
if x == 1:
    print('是的,这些字段总是一起取空值。')
else:
    print('不是,这些字段不总是一起取空值。')

上面的猜测得到证实,任意一条记录的这些字段的数据,总是一起取空值。

查看这些字段中任意一个字段为空值的记录。

df[df[var].isnull().any(axis=1)]

在判断宽带信息相关字段的缺失值产生的原因时,遇到了一些困难,主要是这本分的字段涉及到了异网,即其他家运营商,部分字段的口径令人感到疑惑。在联合’是否宽带捆绑签约’字段做分析后,思路才变得清晰了起来。

print("'是否本网宽带用户', '是否异网宽带用户','是否激活宽带'字段的取值种类和其分布情况:", end='\n\n')
print(df.groupby(['是否本网宽带用户', '是否异网宽带用户', '是否激活宽带'], dropna=False).size(), end='\n\n')
print('============================')

print("'宽带带宽'字段为空值时,'是否本网宽带用户', '是否异网宽带用户'字段的分布情况:", end='\n\n')
print(df[df['宽带带宽'].isnull()].groupby(['是否本网宽带用户', '是否异网宽带用户'], dropna=False).size(), end='\n\n')
print('============================')

print("'宽带带宽'字段为0时,'是否本网宽带用户', '是否异网宽带用户'字段的分布情况:", end='\n\n')
print(df[df['宽带带宽']==0].groupby(['是否本网宽带用户', '是否异网宽带用户'], dropna=False).size(), end='\n\n')
print('============================')

print("'是否本网宽带用户', '是否异网宽带用户','是否宽带捆绑签约'字段的取值种类和其分布情况:", end='\n\n')
print(df.groupby(['是否本网宽带用户', '是否异网宽带用户', '是否宽带捆绑签约'], dropna=False).size(), end='\n\n')

在这里插入图片描述
先说结论:‘是否宽带捆绑签约’, ‘宽带带宽’, '是否激活宽带’这几个字段记录的是“是否在本公司xxx”,而没有记录在其他家运营商的宽带捆绑签约,宽带带宽和激活宽带的数据。

我们先观察上面代码块输出的最后一个表。这个表中’是否宽带捆绑签约’字段出现了缺失值,于是我先做了该字段的缺失值分析,最终认为该字段的缺失值都是由于输入等环节的疏漏产生的,由于缺失值的数量很小,决定把这些缺失值的相应记录删除。(具体的分析和执行详见下一小节)那么现在我们可以忽略这些缺失值了。

忽略缺失值之后我们发现,'是否本网宽带用户’为1且’是否异网宽带用户’为0的用户,他们的宽带捆绑签约率为99.40%。而’是否本网宽带用户’为0且’是否异网宽带用户’为1的用户,他们的宽带捆绑签约率仅有0.05%。这个巨大的差异是不正常的,本网宽带用户和异网宽带用户的习惯不应该有这么大的差别。一个合理的推测是,'是否宽带捆绑签约’字段统计的是“是否在本公司宽带捆绑签约”,这就能解释为什么不是本网宽带用户仅是异网宽带用户在这个字段99.95%都是0,他们是其他家运营商的宽带用户,自然绝大部分都没有和本公司进行签约而是和其他家运营商进行签约。这也很好地解释了,为什么’是否本网宽带用户’为1且’是否异网宽带用户’为1的用户,他们在’是否宽带捆绑签约’字段中1的比例仅有90%,这是因为那些在两边都购买了宽带服务的用户,可能会和其他家运营商签约而不和本公司签约。

带着这个思路回头看 ‘宽带带宽’, '是否激活宽带’这两个字段,如果把他们的统计口径是“是否在本公司xxx”,那么一切就很好理解了,这些空值代表的含义是:未在本公司开通宽带,不存在’宽带带宽’数据/ 没有激活宽带。

38276/(38276+230)
3/(3+5935)

0.0005052206130010104
最终的结论如下:

‘宽带带宽’字段的空值,代表的含义是未在本公司开通宽带,不存在’宽带带宽’数据。这些空值应被替换为’未开通本网宽带’。

'是否激活宽带’字段的空值,代表的含义是未在本公司开通宽带,不存在’是否激活宽带’的数据,但也可以说是未激活宽带。这些空值应被替换为0。

下面进行缺失值处理。

df.loc[df.是否本网宽带用户==0, '是否激活宽带']=0
df.loc[df.是否本网宽带用户==0, '宽带带宽']='未开通本网宽带'

检查一下替换是否正确。

print("'是否本网宽带用户', '是否异网宽带用户','是否激活宽带'字段的取值种类和其分布情况:", end='\n\n')
print(df.groupby(['是否本网宽带用户', '是否异网宽带用户', '是否激活宽带'], dropna=False).size(), end='\n\n')
print('============================')

print("'宽带带宽'为空值(修改前的空值,修改后'未开通本网宽带')时,'是否本网宽带用户', '是否异网宽带用户'字段的分布情况:", end='\n\n')
print(df[df['宽带带宽']=='未开通本网宽带'].groupby(['是否本网宽带用户', '是否异网宽带用户', '宽带带宽'], dropna=False).size(), end='\n\n')

在这里插入图片描述

print('检查缺失值数量', end = '\n\n')
print(df[var].isnull().sum())

print('====================================')

print("查询分类变量的分布情况:")
for i in cat_var:
    print()
    print(df[i].value_counts(dropna = False))

在这里插入图片描述
异常值处理

本分组内的字段全部为分类变量,决定不对它们做异常值检测和处理。

签约信息相关字段

签约信息相关字段共有4个:[‘是否宽带捆绑签约’, ‘是否终端捆绑签约’, ‘是否话费签约’, ‘是否套餐签约’]。它们全部为分类变量。

cat_var = ['是否宽带捆绑签约', '是否终端捆绑签约', '是否话费签约', '是否套餐签约']
num_var = []
var = cat_var + num_var
观察字段的分布情况。
print("查询分类变量的所有取值,和每种取值对应的数量:")
for i in cat_var:
    print()
    print(df[i].value_counts(dropna = False))

在这里插入图片描述
缺失值处理

检查缺失值。

print('检查缺失值值数量', end = '\n\n')
df[var].isnull().sum()

在这里插入图片描述

cols_with_missing = []

x = df[var].isnull().any(axis=0)
for i in x.index:
    if x[i] == True:
        cols_with_missing.append(i)

cols_with_missing

签约信息相关的4个字段的缺失值的数量相同,猜测这4个字段总是同时取空值。

n = df[cols_with_missing].isnull().all(axis=1).sum()  #.all()把一个bool dataframe沿列轴方向压缩成series。
x = 1
for i in cols_with_missing:
    if df[i].isnull().sum() != n:
        x = 0
        break
if x == 1:
    print('是的,这些字段总是一起取空值。')
else:
    print('不是,这些字段不总是一起取空值。')

上面的猜测得到证实,任意一条记录的这些字段的数据,总是一起取空值。

查看这些字段中任意一个字段为空值的记录。

df[df[var].isnull().any(axis=1)]

在这里插入图片描述
把这些字段中任意一个字段为空值的记录删除。

df.dropna(subset=cols_with_missing, axis = 0, inplace = True)
print('检查缺失值数量', end = '\n\n')

df[var].isnull().sum()

在这里插入图片描述
异常值处理

本分组内的字段全部为分类变量,决定不对它们做异常值检测和处理。

套餐信息相关字段

套餐信息相关字段共有2个:[‘用户总套餐价值’, ‘用户主资费套餐价值’]。它们都是数值变量。

cat_var = []
num_var = ['用户总套餐价值', '用户主资费套餐价值']  
var = cat_var + num_var

观察字段的分布情况。

print('观察数值变量的分布情况')
df[num_var].describe()

在这里插入图片描述
缺失值处理

检查缺失值。

print('检查缺失值值数量', end = '\n\n')
df[var].isnull().sum()
cols_with_missing = []

x = df[var].isnull().any(axis=0)
for i in x.index:
    if x[i] == True:
        cols_with_missing.append(i)

cols_with_missing

套餐信息相关的2个字段的缺失值的数量相同,猜测这2个字段总是同时取空值。

n = df[cols_with_missing].isnull().all(axis=1).sum()  #.all()把一个bool dataframe沿列轴方向压缩成series。
x = 1
for i in cols_with_missing:
    if df[i].isnull().sum() != n:
        x = 0
        break
if x == 1:
    print('是的,这些字段总是一起取空值。')
else:
    print('不是,这些字段不总是一起取空值。')

上面的猜测得到证实,任意一条记录的这2个字段的数据要么全不是空值,要么全是空值。

查看这些字段中任意一个字段为空值的记录。

df[df[var].isnull().any(axis=1)]

对于每个有缺失值的字段ci,新建一个字段:ci_曾有缺失值。某行的ci字段如有缺失值,则该行的ci_曾有缺失值字段为True,否则为False。 对所有缺失值用中位数进行填充。

for i in cols_with_missing:
    df[i + '_曾有缺失值'] = df[i].isnull()
df.fillna(value = {'用户总套餐价值': df['用户总套餐价值'].median()               
, '用户主资费套餐价值': df['用户主资费套餐价值'].median()           
}, inplace = True )
print('检查缺失值数量', end = '\n\n')

df[var].isnull().sum()

在这里插入图片描述
异常值处理

print('观察数值变量的分布情况')
df[num_var].describe()

在这里插入图片描述采用缩尾法进行异常值检测。把小于第1百分位数,或大于第99百分位数的数据判定为异常值。

outlier_index = {}
for i in var:
    DownLimit = np.percentile(df[i], 1)
    UpLimit = np.percentile(df[i], 99)
    outlier_index[i] = [df[i]<DownLimit, df[i]>UpLimit]

describes_lower_outlier = [df[outlier_index[i][0]][i].describe() for i in var]
describes_upper_outlier = [df[outlier_index[i][1]][i].describe() for i in var]

观察各列低于第1百分位数的数据的分布情况。
pd.concat(describes_lower_outlier, axis=1)
在这里插入图片描述观察各列高于第99百分位数的数据的分布情况。

pd.concat(describes_upper_outlier, axis=1)

在这里插入图片描述

l = np.array(range(1, 100)) / 100
df[num_var].describe(percentiles = l)

在这里插入图片描述
决定将这些异常值全部保留。

流量饱和度信息相关字段

流量饱和度信息相关字段共有3个:[‘当月用户流量饱和度’, ‘上月用户流量饱和度’, ‘上上月用户流量饱和度’]。它们都是数值变量。

cat_var = []
num_var = ['当月用户流量饱和度', '上月用户流量饱和度', '上上月用户流量饱和度']     
var = cat_var + num_var

观察字段的分布情况。

print('观察数值变量的分布情况')
df[num_var].describe()

在这里插入图片描述
缺失值处理

检查缺失值。

print('检查缺失值值数量', end = '\n\n')
df[var].isnull().sum()

在这里插入图片描述

cols_with_missing = []

x = df[var].isnull().any(axis=0)
for i in x.index:
    if x[i] == True:
        cols_with_missing.append(i)

cols_with_missing
print('观察有缺失值的这几个字段的取值情况。(下方表格的True和False表示是否为空值)')
i = df[cols_with_missing].isnull()
i.groupby(cols_with_missing, dropna=False).size()

在这里插入图片描述
查看这些字段中任意一个字段为空值的记录。

df[df[var].isnull().any(axis=1)]

在这里插入图片描述
观察’当月用户流量饱和度’字段为空值时,'当月dou’字段的取值情况。 观察’上月用户流量饱和度’字段为空值时,'上月dou’字段的取值情况。 观察’上上月用户流量饱和度’字段为空值时,'上上月dou’字段的取值情况。

df[((df['当月用户流量饱和度'].isnull()))][['当月dou']].describe()

在这里插入图片描述

df[((df['上月用户流量饱和度'].isnull()))][['上月dou']].describe()

在这里插入图片描述

df[((df['上上月用户流量饱和度'].isnull()))][['上上月dou']].describe()

在这里插入图片描述
对于每个有缺失值的字段ci,新建一个字段:ci_曾有缺失值。某行的ci字段如有缺失值,则该行的ci_曾有缺失值字段为True,否则为False。 对所有缺失值用众数进行填充。

for i in cols_with_missing:
    df[i + '_曾有缺失值'] = df[i].isnull()
df.fillna(value = {'当月用户流量饱和度': df['当月用户流量饱和度'].median()                  
, '上月用户流量饱和度': df['上月用户流量饱和度'].median()
, '上上月用户流量饱和度': df['上月用户流量饱和度'].median()             
}, inplace = True )
print('检查缺失值数量', end = '\n\n')

df[var].isnull().sum()

在这里插入图片描述
异常值处理

print('观察数值变量的分布情况')
df[num_var].describe()

在这里插入图片描述
采用缩尾法进行异常值检测。把小于第1百分位数,或大于第99百分位数的数据判定为异常值。

outlier_index = {}
for i in var:
    DownLimit = np.percentile(df[i], 1)
    UpLimit = np.percentile(df[i], 99)
    outlier_index[i] = [df[i]<DownLimit, df[i]>UpLimit]

describes_lower_outlier = [df[outlier_index[i][0]][i].describe() for i in var]
describes_upper_outlier = [df[outlier_index[i][1]][i].describe() for i in var]

观察各列低于第1百分位数的数据的分布情况。

pd.concat(describes_lower_outlier, axis=1)

在这里插入图片描述
观察各列高于第99百分位数的数据的分布情况。

pd.concat(describes_upper_outlier, axis=1)

在这里插入图片描述

l = np.array(range(1, 100)) / 100
df[num_var].describe(percentiles = l)

在这里插入图片描述
决定将这些异常值全部保留。

其他信息相关字段

其他信息的相关字段共有7个:[‘是否家庭用户’, ‘5G流量’, ‘终端类型’, ‘当月是否低消保号用户’, ‘当月是否换机’, ‘居住地是否覆盖5G’, ‘工作地是否覆盖5G’]。 分类变量共有6个:[‘是否家庭用户’, ‘终端类型’, ‘当月是否低消保号用户’, ‘当月是否换机’, ‘居住地是否覆盖5G’, ‘工作地是否覆盖5G’]。 数值变量共有1个:[‘5G流量’]。

cat_var = ['是否家庭用户', '终端类型', '当月是否低消保号用户', '当月是否换机', '居住地是否覆盖5G', '工作地是否覆盖5G']
num_var = ['5G流量']
var = cat_var + num_var

观察字段的分布情况。

print("查询分类变量的所有取值,和每种取值对应的数量:")
for i in cat_var:
    print()
    print(df[i].value_counts(dropna = False))

在这里插入图片描述

print('观察数值变量的分布情况')
df[num_var].describe()

在这里插入图片描述
缺失值处理

检查缺失值。

print('检查缺失值值数量', end = '\n\n')
df[var].isnull().sum()

在这里插入图片描述

cols_with_missing = []

x = df[var].isnull().any(axis=0)
for i in x.index:
    if x[i] == True:
        cols_with_missing.append(i)

cols_with_missing

查看这些字段中任意一个字段为空值的记录。

df[df['5G流量'].isnull()]

在这里插入图片描述
项目概述中对’5G流量’字段的描述为:5G流量,空值表示未产生5G流量。观察发现,'5G流量’字段的最小值大于0。 将’5G流量’字段的缺失值替换为0。

df.fillna(value = {'5G流量': 0                          
}, inplace = True )
print('检查缺失值数量', end = '\n\n')

df[var].isnull().sum()

在这里插入图片描述

print('观察数值变量的分布情况')
df[num_var].describe()

在这里插入图片描述
采用缩尾法进行异常值检测。把小于第1百分位数,或大于第99百分位数的数据判定为异常值。

outlier_index = {}
for i in var:
    DownLimit = np.percentile(df[i], 1)
    UpLimit = np.percentile(df[i], 99)
    outlier_index[i] = [df[i]<DownLimit, df[i]>UpLimit]

describes_lower_outlier = [df[outlier_index[i][0]][i].describe() for i in num_var]
describes_upper_outlier = [df[outlier_index[i][1]][i].describe() for i in num_var]

观察各列低于第1百分位数的数据的分布情况。

pd.concat(describes_lower_outlier, axis=1)

在这里插入图片描述
观察各列高于第99百分位数的数据的分布情况。

pd.concat(describes_upper_outlier, axis=1)

在这里插入图片描述

l = np.array(range(1, 100)) / 100
df[num_var].describe(percentiles = l)

在这里插入图片描述决定将这些异常值全部保留。

标签字段

标签字段分组只有一个字段’标签’,它是分类型变量,是本项目的目标变量。

观察字段的分布情况。

print(df['标签'].value_counts(dropna = False))
print('检查缺失值值数量', end = '\n\n')
df['标签'].isnull().sum()

异常值处理

'标签’字段为分类型目标变量,决定不对该字段做异常值检测和处理。

数据预处理总结
最后检查一遍缺失值。

print('含有缺失值的字段有:'
       , df.isnull().sum()[df.isnull().sum()>0])

在这里插入图片描述

EDA

以可视化、相关性分析等手段进行探索性数据分析。总结了一些机器学习部分可以参考的信息。针对5G业务形成认知,找到关键指标,并对如何推广5G业务提出建议。

这部分较为重要的发现或结论都加粗了。最后的总结部分记录了EDA的主要结论。

准备工作

mpl.rcParams['font.sans-serif'] = ['Microsoft YaHei'] # 让绘图时能正常显示中文
mpl.rcParams['axes.unicode_minus'] = False #用来正常显示负号
# plt.rcParams['font.sans-serif']=['SimHei'] #用来正常显示中文标签
# plt.rcParams['axes.unicode_minus']=False #用来正常显示负号
df_EDA = df[col].copy()

把分类字段的数据类型改为字符串。 对所有数值字段进行异常值处理,把低于第1百分位数或高于第99百分位数的数据判定为异常值,并用中位数对所有异常值进行替换。

for i in cat_col:
    df_EDA[i]=df_EDA[i].astype(str)
for i in num_col:
    outlier_index = {}
    DownLimit = np.percentile(df[i], 1)
    UpLimit = np.percentile(df[i], 99)
    outlier_index[i] = [df[i]<DownLimit, df[i]>UpLimit]
    
    df_EDA[i][outlier_index[i][0] | outlier_index[i][1]] = df_EDA[i].median()

相关性+feature importance

相关性分析

通过相关性分析,挖掘出对用户是否办理5G影响较大的属性。

df_dummies = pd.get_dummies(df_EDA)
df_dummies.columns

在这里插入图片描述

drop_col=['性别_先生', '是否本网宽带用户_0', '是否异网宽带用户_0', '是否激活宽带_0.0'
    , '是否宽带捆绑签约_0.0', '是否终端捆绑签约_0.0', '是否话费签约_0.0', '是否套餐签约_0.0'
    , '是否家庭用户_0', '当月是否低消保号用户_0', '当月是否换机_0', '居住地是否覆盖5G_0', '工作地是否覆盖5G_0']
df_dummies.drop(drop_col , axis=1, inplace=True)
plt.subplots(figsize=(25,25))
fig=sns.heatmap(df_dummies.corr(), annot=False, vmax=1, square=True, cmap="Blues", fmt='.2g', linewidths=1, linecolor='yellow')
# annot为热力图上显示数据;fmt='.2g'为数据保留两位有效数字,square呈现正方形,vmax最大值为1
plt.title('相关性热图', fontsize=18)

在这里插入图片描述

plt.figure(figsize=(5,21))
# df_dummies.corr()['标签'].sort_values().plot(kind='barh')
x=df_dummies.corr()['标签'].sort_values(ascending=False)
ax = sns.barplot(y=x.index, x=x.values, orient='h')
plt.axvline(x=0.1, linestyle="--", color="black")
plt.axvline(x=0.3, linestyle="--", color="black")
plt.axvline(x=0.5, linestyle="--", color="black")
plt.axvline(x=0.8, linestyle="--", color="black")
plt.axvline(x=-0.1, linestyle="--", color="black")
plt.axvline(x=-0.3, linestyle="--", color="black")
plt.axvline(x=-0.5, linestyle="--", color="black")
plt.title('各变量与目标变量之间的相关性', fontsize=14)

plt.show()

在这里插入图片描述

l2=list(df_dummies.corr()['标签'].sort_values().index)
print(l2)
x=['当月arpu', '上月arpu', '上上月arpu'
        , '当月dou', '上月dou', '上上月dou'
        , '当月mou', '上月mou', '上上月mou'
        , '近三月平均arpu', '近三月平均dou', '近三月平均mou']     

l_corr=list(df_dummies.corr()['标签'].sort_values().index[-21:]) + list(df_dummies.corr()['标签'].sort_values().index[0:1])
l_corr.remove('标签')
try:
    l_corr.remove('星级是否大于3')
except:
    pass
for i in x:
    try:
        l_corr.remove(i)
    except:
        print('==========================')
        print(i)
for i in ['arpu', 'dou', 'mou']:
    l_corr.append(i)

print(l_corr)

根据相关性分析,以下属性与用户办理5G有较大的正相关性:

arpu, ‘用户总套餐价值’, ‘用户主资费套餐价值’, dou, mou, 星级为4,宽带带宽为100, 激活宽带,是本网宽带用户,宽带捆绑签约, 是家庭用户。
Feature Importance

X, y = df_dummies.drop('标签', axis=1), df_dummies['标签']
# Fit RandomForest Classifier
clf = RandomForestClassifier(random_state=42)
clf = clf.fit(X, y)
# Plot features importances
imp = pd.Series(data=clf.feature_importances_, index=X.columns).sort_values(ascending=False)
plt.figure(figsize=(6, 20))
ax = sns.barplot(y=imp.index, x=imp.values, orient='h')
plt.axvline(x=0.02, linestyle="--", color="black")
plt.axvline(x=0.04, linestyle="--", color="black")
plt.axvline(x=0.06, linestyle="--", color="black")
plt.axvline(x=0.08, linestyle="--", color="black")
plt.axvline(x=0.10, linestyle="--", color="black")
plt.axvline(x=0.12, linestyle="--", color="black")
plt.title("Feature importance")

在这里插入图片描述

l_fi = list(imp[:15].index)
print(l_fi)
l_fi = ['arpu', '用户总套餐价值', '用户主资费套餐价值', 'dou', 'mou', '是否家庭用户_1']
['当月arpu', '上月arpu', '近三月平均arpu', '上上月arpu', '用户总套餐价值', '用户主资费套餐价值', '近三月平均dou', '当月mou', '当月dou', '上月dou', '是否家庭用户_1', '上上月dou', '上上月mou', '近三月平均mou', '上月mou']

根据随机森林算法给出的feature importance,以下属性对用户是否办理5G有较大的影响: arpu, ‘用户总套餐价值’, ‘用户主资费套餐价值’, dou, mou, '年龄, ‘是否家庭用户’。

可视化分析

下面对整个数据集的各个字段进行可视化分析,主要完成了以下任务:

(1)了解各个字段的分布。

(2)前面我们已经通过分析相关性和feature importance找到了一些对办理5G有较强影响的用户属性,下面通过可视化的角度寻找对5G办理有较强影响的用户属性。

(3)获得一些业务层面的理解。

(4)获得一些启发,在后面的部分有针对性地做进一步分析。

绘制目标变量的饼图和柱状图,发现这是一个非均衡数据集,5G用户为少数类别,占比20.1%。

l = list(df_EDA['标签'].value_counts())
circle = [l[0] / sum(l) * 100, l[1] / sum(l) * 100]

fig = plt.subplots(figsize = (10,4))
plt.subplot(1,2,2)
plt.pie(circle,labels = ['非5G用户', '5G用户'], autopct = '%1.1f%%', startangle = 90, explode = (0.1,0),
       wedgeprops = {'edgecolor' : 'black','linewidth': 1,'antialiased' : True})
plt.title('5G用户 - 非5G用户 %', fontsize=14, fontweight="bold");

plt.subplot(1,2,1)
ax = sns.countplot(data = df_EDA, x='标签')
for rect in ax.patches:
    ax.text(rect.get_x() + rect.get_width() / 2, rect.get_height() + 2, rect.get_height(), horizontalalignment='center', fontsize = 11)
ax.set_xticklabels(['非5G用户','5G用户']) 
plt.title('5G用户 - 非5G用户数量', fontsize=14, fontweight="bold");

plt.show()

在这里插入图片描述

plt.figure(figsize=(24, 44))
for i, j in enumerate(df_EDA, 1):
    plt.subplot(11,4,i)
    ax = sns.histplot(df_EDA[j], kde=True)
    ax.set_xlabel(j, fontsize = 12)
    ax.set_ylabel('数量', fontsize = 14)
    plt.title(f"分布:{j}", fontsize=14, fontweight="bold")

plt.tight_layout()
plt.plot()

在这里插入图片描述

fig = plt.figure(figsize = (24,20))
for i,j in enumerate(cat_col, 1):
    plt.subplot(5, 4, i)
    ax = sns.countplot(x=j, data = df_EDA)
    for rect in ax.patches:
        ax.text(
            rect.get_x() + rect.get_width() / 2
            , rect.get_height() + 2
            , round(rect.get_height())
            , horizontalalignment='center', fontsize = 11) 
    ax.set_xlabel(j, fontsize = 12)
    ax.set_ylabel('数量', fontsize = 14)
    plt.title(f'计数柱状图:{j}', fontsize=14, fontweight="bold")
plt.tight_layout()
plt.show()

在这里插入图片描述

fig = plt.figure(figsize = (24,28))
for i,j in enumerate(num_col, 1):
    plt.subplot(7, 4, i)
    ax=sns.distplot(df_EDA[j])
    ax.set_xlabel(j, fontsize=12)
    ax.set_ylabel('密度', fontsize=12)
    plt.title(f'分布:{j}', fontsize=14, fontweight="bold")
plt.tight_layout()
plt.show()

在这里插入图片描述

fig = plt.figure(figsize=(24,12))
for i,j in enumerate(num_col, 1):
    plt.subplot(4, 7, i)
    ax = sns.boxplot(y=df_EDA[j])
    ax.set_ylabel(j, fontsize=12)
    plt.title(f'盒须图:{j}', fontsize=14, fontweight="bold")
plt.tight_layout()
plt.show()

在这里插入图片描述
用p值检验数值变量是否符合正态分布,发现所有数值变量均不符合正态分布。

x=0
for i in num_col:
    stat,p = shapiro(df_EDA[i])
    if p>0.01:
        print(f"{i}可能正态分布")
        x+=1

if x==0:
    print('所有数值变量均不符合正态分布')

所有数值变量均不符合正态分布

fig = plt.figure(figsize = (24, 36))
for i,j in enumerate(cat_col, 1):
    ax=plt.subplot(9, 2, i)
    sns.countplot(x=j, data = df_EDA, hue='标签')
    for rect in ax.patches:
        ax.text(rect.get_x() + rect.get_width() / 2, rect.get_height() + 2, rect.get_height(), horizontalalignment='center', fontsize = 11)
    ax.set_xlabel(j, fontsize=12)
    ax.set_ylabel('数量', fontsize=12)
    plt.title(f'{j}-标签计数图', fontsize=14, fontweight="bold")
plt.tight_layout()
plt.show()

在这里插入图片描述

fig = plt.figure(figsize = (24, 36))
for i,j in enumerate(cat_col, 1):
    ax=plt.subplot(9, 2, i)
    sns.countplot(x='标签', data = df_EDA, hue=j)
    for rect in ax.patches:
        ax.text(rect.get_x() + rect.get_width() / 2, rect.get_height() + 2, rect.get_height(), horizontalalignment='center', fontsize = 11)
    ax.set_xlabel('标签', fontsize=12)
    ax.set_ylabel('数量', fontsize=12)
    plt.title(f'{j}-标签计数图', fontsize=14, fontweight="bold")
plt.tight_layout()
plt.show()

在这里插入图片描述

plt.figure(figsize=(24,20))
for i,j in enumerate(cat_col, 1):
    ax=plt.subplot(5, 4, i)
    sns.histplot(df_EDA.loc[df_EDA['标签']==0, j], stat='percent', alpha=0.4, label='非5G用户')
    sns.histplot(df_EDA.loc[df_EDA['标签']==1, j], stat='percent', alpha=0.4, label='5G用户')
    handles, labels = plt.gca().get_legend_handles_labels()  #删除重复的图例。
    by_label = OrderedDict(zip(labels, handles))
    plt.legend(by_label.values(), by_label.keys())
    ax.set_xlabel(j, fontsize = 12)
    ax.set_ylabel('百分比', fontsize = 12)
    plt.title('分布:{}'.format(j), fontsize=14, fontweight="bold")
plt.tight_layout()
plt.show()

在这里插入图片描述

def show_values(axs, orient="v", space=.01):
    def _single(ax):
        if orient == "v":
            for p in ax.patches:
                _x = p.get_x() + p.get_width() / 2
                _y = p.get_y() + p.get_height() + (p.get_height()*0.01)
                value = '{:.2f}'.format(p.get_height())
                ax.text(_x, _y, value, ha="center") 
        elif orient == "h":
            for p in ax.patches:
                _x = p.get_x() + p.get_width() + float(space)
                _y = p.get_y() + p.get_height() - (p.get_height()*0.5)
                value = '{:.1f}'.format(p.get_width())
                ax.text(_x, _y, value, ha="left")

    if isinstance(axs, np.ndarray):
        for idx, ax in np.ndenumerate(axs):
            _single(ax)
    else:
        _single(axs)

plt.figure(figsize=(24, 20))
for i,j in enumerate(cat_col,1):
    plt.subplot(5, 4, i)    
    x = df_EDA[j].value_counts(dropna = False)
    for k in x.index:
        x[k] = round(
            ((df_EDA[j]==k) & (df_EDA['标签']==1)).sum() / x[k]
            , 4)
    ax=sns.barplot(x=x.index, y=x.values)
    plt.axhline(y=0.2, linestyle="--", color="black") # 整个数据集的平均5G办理率
    show_values(ax)
    plt.title('5G办理率:{}'.format(j), fontsize=14, fontweight="bold")
plt.tight_layout()
plt.show()

在这里插入图片描述以用户的平均5G办理率(20.1%)为参考,观察上面的柱状图,找到了一些对5G办理率有明显影响的分类型属性。(过滤掉了样本量太少的属性)

较大提升5G办理率的属性:'星级’大于3级,是本网宽带用户,激活宽带,'宽带带宽’为100或200,宽带捆绑签约,终端捆绑签约,话费签约,终端类型为2。

较大降低5G办理率的属性:'星级’小于3级,不是本网宽带用户,未激活宽带,没有宽带捆绑签约,不是家庭用户,终端类型为0。

较小提升5G办理率的属性: 先生(男性),集团用户,家庭用户。

较小降低5G办理率的属性:女士(女性),农村用户,校园用户,是异网宽带用户,没有套餐签约。

总结一下观察到的信息,并针对性地提出一些建议:

5G用户和非5G用户在宽带相关的属性上有明显的差异: (1)是本网宽带用户/ 激活宽带/ 宽带捆绑签约极大提升了用户的5G办理率。 (2)'宽带带宽’为100或200的用户办理5G的比例远高于平均水平。另外办理了某些种类的’宽带带宽’的用户虽有很高的5G办理比例,但因其总数太少,参考价值不大。 (3)异网宽带用户的5G办理率低于平均水平。这是非常有意思的,因为其它所有和宽带相关的属性,是/有/签约/激活就意味着高于平均水平的5G办理率。猜测’标签’字段(即是否5G用户)只统计在本网办理5G的用户,而异网宽带用户可能因为宽带捆绑签约等原因倾向于在异网办理5G,从而不在本网办理5G。

签约会极大地提升5G办理率。 进行宽带捆绑签约/ 终端捆绑签约/ 话费签约的用户办理5G的比例远高于平均水平,进行套餐签约的用户的5G办理率也比平均水平更高。猜测在这些签约中有绑定5G服务。

低消保号用户的5G办理率超过50%,远高于一般用户。低消保号用户的人数较少(约占总用户数的1%),建议找业务弄清楚这背后的原因,是样本的问题,还是运营商给不活跃的用户自动更换了5G套餐?

当月换机的用户办理5G的比例高于整体用户的5G办理率,猜测是有部分用户换上了支持5G的手机从而考虑更换5G套餐。可以确定的是,换机的当月是用户考虑办理5G业务的一个窗口期,应在用户换机之后,做及时的、有针对性的营销。

比较意外的是,居住地是否覆盖5G,'工作地是否覆盖5G’对办理5G并没有很大的影响。 一方面,居住地和工作地的5G覆盖率较低,都不到20%。另一方面,居住地覆盖5G/ 工作地覆盖5G只提升了3%的5G办理率,按照提升/减少的5G办理率来看,这两个字段甚至是对办理5G影响最小的几个分类字段之一。回看前面的相关性分析,这两个字段与目标变量的相关系数在0和0.1之间,属于无关变量。5G的低覆盖率,可能会降低用户对5G的关注和使用。另外,需要进一步跟进调查看看为什么5G的覆盖并没有带来显著的5G办理比例的提升。是因为宣传不到位虽然覆盖了5G但用户不知道,还是因为用户的5G办理多是在捆绑签约等场景下完成,5G用户对5G本身并不怎么关心,办理5G业务主要是受捆绑的套餐的影响,而受5G覆盖的影响较小?

fig = plt.figure(figsize = (24, 52))
for i,j in enumerate(num_col, 1):
    ax=plt.subplot(13, 2, i)
    sns.distplot(df_EDA[df_EDA['标签']==1][j], label='5G用户')
    sns.distplot(df_EDA[df_EDA['标签']==0][j], label='非5G用户')
    ax.legend(loc='best')
    ax.set_xlabel(j, fontsize=12)
    ax.set_ylabel('密度', fontsize=12) 
    plt.title('分布:{}'.format(j), fontsize=14, fontweight="bold")
plt.tight_layout()
plt.show()

在这里插入图片描述

fig = plt.figure(figsize=(24,28))
for i,j in enumerate(num_col, 1):
    plt.subplot(7, 4, i)
    ax = sns.boxplot(data=df_EDA,x='标签', y=df_EDA[j])
    ax.set_ylabel(j, fontsize=12)
    ax.set_xlabel('标签', fontsize=12)
    plt.title(f'盒须图:{j}', fontsize=14, fontweight="bold")
plt.tight_layout()
plt.show()

在这里插入图片描述

观察上面的分布图,有以下发现:

(1)5G用户明显高于非5G用户的属性:arpu, dou, mou, ‘用户总套餐价值’, ‘用户主资费套餐价值’。其中mou的差距相对较小,其余字段的差距则非常显著。

(2)5G用户的流量饱和度略微高于非5G用户。

(3)5G用户的语音超套金额略微低于非5G用户。

(4)5G用户和非5G用户之间差异很小的属性:‘年龄’, ‘在网时长’, ‘流量超套金额’, ‘5G流量’。

(5)比较意外的是,5G用户和非5G用户在5G流量这个属性上的差异较小。这意味着很多5G用户并不使用5G流量,而是仍然使用4G流量。推测这一现象是因为5G配套的应用的还没有足够完善或者还没有流行起来,这可能是5G业务推广的主要瓶颈。

(6)在近三月平均arpu为70-80这个区间,5G用户和非5G用户有着接近的密度,可加强对这个区间内的非5G用户的营销,他们有较大的可能称为5G用户。
在这里插入图片描述
总结对5G办理影响较大的属性
前面我们通过以下三种方式寻找对5G办理影响较大的属性:(1)相关性分析(2)feature importance分析(3)可视化分析。下面对得到的结果进行汇总。

l=[]
for i in (l_corr, l_fi, l_visual):
    for j in i:
        if j not in l:
            l.append(j)
d={'相关性分析':l_corr, 'feature importance分析':l_fi, '可视化分析':l_visual}
df1 = pd.DataFrame(index=l, columns=['相关性分析', 'feature importance分析', '可视化分析', 'Score'])
for i in df1.index:
    for j in df1.columns:
        if j != 'Score':
            df1.loc[i,j]=(i in d[j])
        else:
            df1.loc[i,j]=df1.loc[i, '相关性分析':'可视化分析'].sum()


pd.set_option('display.max_rows', None)
df1.sort_values('Score', ascending=False)

在这里插入图片描述

pd.set_option('display.max_rows', 14)

进一步分析对5G办理较大影响较大的部分属性

试图回答以下问题:有一些字段似乎彼此有着很高的相关性和相似度,如arpu相关的四个字段和套餐价值相关的两个字段,它们是否确实高度正相关且高度相似?我们能否在认知上对他们进行精简/合并?

pd.set_option('display.max_rows', 21) # 设置显示的最大行数
arpu与套餐价值

这部分试图理清arpu相关字段和套餐价值相关字段之间的关系,从而对它们形成较为精简的认知。

fig = plt.figure(figsize = (7, 4))
ax=sns.distplot(df_EDA['用户总套餐价值'], label='用户总套餐价值')
sns.distplot(df_EDA['用户主资费套餐价值'], label='用户主资费套餐价值')
ax.legend(loc='best')
ax.set_xlabel('价值', fontsize=12)
ax.set_ylabel('密度', fontsize=12) 
plt.title('分布:套餐价值', fontsize=14, fontweight="bold")
plt.tight_layout()
plt.show()

在这里插入图片描述

l1=['当月arpu', '上月arpu', '上上月arpu', '近三月平均arpu']
l2=[]
for i in l1:
    for j in l1:
        l2.append((i, j))

fig=plt.figure(figsize=(24, 16))
for i,j in enumerate(l2, 1):
    ax=plt.subplot(len(l1), len(l1), i)
    if j[0]==j[1]:
        sns.distplot(df_EDA[j[0]])
        ax.set_xlabel(j[0], fontsize=12)
        ax.set_ylabel('密度', fontsize=12)
        plt.title('分布:{}'.format(j[0]), fontsize=14)
    else:
        sns.distplot(df_EDA[j[0]], label=j[0])
        sns.distplot(df_EDA[j[1]], label=j[1])
        ax.legend(loc='best')
        ax.set_xlabel('套餐价值', fontsize=12)
        ax.set_ylabel('密度', fontsize=12)
        plt.title('分布:{}和{}'.format(j[0], j[1]), fontsize=14)
plt.tight_layout()
plt.show()

在这里插入图片描述

plt.figure(figsize=(15,21))
ax = plt.subplot(1, 3, 1)
x = df_dummies.corr()['用户总套餐价值'].sort_values(ascending=False)
sns.barplot(y=x.index, x=x.values, orient='h')
plt.axvline(x=0.1, linestyle="--", color="black")
plt.axvline(x=0.3, linestyle="--", color="black")
plt.axvline(x=0.5, linestyle="--", color="black")
plt.axvline(x=0.8, linestyle="--", color="black")
plt.axvline(x=-0.1, linestyle="--", color="black")
plt.axvline(x=-0.3, linestyle="--", color="black")
plt.axvline(x=-0.5, linestyle="--", color="black")
plt.title("各变量与'用户总套餐价值'之间的相关性", fontsize=14)

ax = plt.subplot(1, 3, 2)
x = df_dummies.corr()['用户主资费套餐价值'].sort_values(ascending=False)
sns.barplot(y=x.index, x=x.values, orient='h')
plt.axvline(x=0.1, linestyle="--", color="black")
plt.axvline(x=0.3, linestyle="--", color="black")
plt.axvline(x=0.5, linestyle="--", color="black")
plt.axvline(x=0.8, linestyle="--", color="black")
plt.axvline(x=-0.1, linestyle="--", color="black")
plt.axvline(x=-0.3, linestyle="--", color="black")
plt.axvline(x=-0.5, linestyle="--", color="black")
plt.title("各变量与'用户主资费套餐价值'之间的相关性", fontsize=14)

ax = plt.subplot(1, 3, 3)
x = df_dummies.corr()['近三月平均arpu'].sort_values(ascending=False)
sns.barplot(y=x.index, x=x.values, orient='h')
plt.axvline(x=0.1, linestyle="--", color="black")
plt.axvline(x=0.3, linestyle="--", color="black")
plt.axvline(x=0.5, linestyle="--", color="black")
plt.axvline(x=0.8, linestyle="--", color="black")
plt.axvline(x=-0.1, linestyle="--", color="black")
plt.axvline(x=-0.3, linestyle="--", color="black")
plt.axvline(x=-0.5, linestyle="--", color="black")
plt.title("各变量与'近三月平均arpu'之间的相关性", fontsize=14)

plt.tight_layout()
plt.show()

在这里插入图片描述

plt.figure(figsize=(15,21))
ax = plt.subplot(1, 3, 1)
x = df_dummies.corr()['当月arpu'].sort_values(ascending=False)
sns.barplot(y=x.index, x=x.values, orient='h')
plt.axvline(x=0.1, linestyle="--", color="black")
plt.axvline(x=0.3, linestyle="--", color="black")
plt.axvline(x=0.5, linestyle="--", color="black")
plt.axvline(x=0.8, linestyle="--", color="black")
plt.axvline(x=-0.1, linestyle="--", color="black")
plt.axvline(x=-0.3, linestyle="--", color="black")
plt.axvline(x=-0.5, linestyle="--", color="black")
plt.title("各变量与'当月arpu'之间的相关性", fontsize=14)

ax = plt.subplot(1, 3, 2)
x = df_dummies.corr()['上月arpu'].sort_values(ascending=False)
sns.barplot(y=x.index, x=x.values, orient='h')
plt.axvline(x=0.1, linestyle="--", color="black")
plt.axvline(x=0.3, linestyle="--", color="black")
plt.axvline(x=0.5, linestyle="--", color="black")
plt.axvline(x=0.8, linestyle="--", color="black")
plt.axvline(x=-0.1, linestyle="--", color="black")
plt.axvline(x=-0.3, linestyle="--", color="black")
plt.axvline(x=-0.5, linestyle="--", color="black")
plt.title("各变量与'上月arpu'之间的相关性", fontsize=14)

ax = plt.subplot(1, 3, 3)
x = df_dummies.corr()['上上月arpu'].sort_values(ascending=False)
sns.barplot(y=x.index, x=x.values, orient='h')
plt.axvline(x=0.1, linestyle="--", color="black")
plt.axvline(x=0.3, linestyle="--", color="black")
plt.axvline(x=0.5, linestyle="--", color="black")
plt.axvline(x=0.8, linestyle="--", color="black")
plt.axvline(x=-0.1, linestyle="--", color="black")
plt.axvline(x=-0.3, linestyle="--", color="black")
plt.axvline(x=-0.5, linestyle="--", color="black")
plt.title("各变量与'上上月arpu'之间的相关性", fontsize=14)

plt.tight_layout()
plt.show()

在这里插入图片描述
段,都可以看做是在反映用户的流量使用情况。

p.s:上面的认知并不严谨并不绝对正确,但足够好用。一方面这种简化后形成的认知让我们的分析更便捷更清晰,另一方面在实际业务场景中上述理解完全可以处理好绝大部分的情况。

结合之前的可视化分析与相关性分析,我们可以形成一个认知:5G用户往往使用较多流量。

mou

这部分试图理清mou相关字段之间的关系,从而对它们形成较为精简的认知。

l1=['当月mou', '上月mou', '上上月mou', '近三月平均mou']
l2=[]
for i in l1:
    for j in l1:
        l2.append((i, j))

fig=plt.figure(figsize=(24, 16))
for i,j in enumerate(l2, 1):
    ax=plt.subplot(len(l1), len(l1), i)
    if j[0]==j[1]:
        sns.distplot(df_EDA[j[0]])
        ax.set_xlabel(j[0], fontsize=12)
        ax.set_ylabel('密度', fontsize=12)
        plt.title('分布:{}'.format(j[0]), fontsize=14)
    else:
        sns.distplot(df_EDA[j[0]], label=j[0])
        sns.distplot(df_EDA[j[1]], label=j[1])
        ax.legend(loc='best')
        ax.set_xlabel('套餐价值', fontsize=12)
        ax.set_ylabel('密度', fontsize=12)
        plt.title('分布:{}和{}'.format(j[0], j[1]), fontsize=14)
plt.tight_layout()
plt.show()

在这里插入图片描述

plt.figure(figsize=(20,21))
ax = plt.subplot(1, 4, 1)
x = df_dummies.corr()['当月mou'].sort_values(ascending=False)
sns.barplot(y=x.index, x=x.values, orient='h')
plt.axvline(x=0.1, linestyle="--", color="black")
plt.axvline(x=0.3, linestyle="--", color="black")
plt.axvline(x=0.5, linestyle="--", color="black")
plt.axvline(x=0.8, linestyle="--", color="black")
plt.axvline(x=-0.1, linestyle="--", color="black")
plt.axvline(x=-0.3, linestyle="--", color="black")
plt.axvline(x=-0.5, linestyle="--", color="black")
plt.title("各变量与'当月mou'之间的相关性", fontsize=14)

ax = plt.subplot(1, 4, 2)
x = df_dummies.corr()['上月mou'].sort_values(ascending=False)
sns.barplot(y=x.index, x=x.values, orient='h')
plt.axvline(x=0.1, linestyle="--", color="black")
plt.axvline(x=0.3, linestyle="--", color="black")
plt.axvline(x=0.5, linestyle="--", color="black")
plt.axvline(x=0.8, linestyle="--", color="black")
plt.axvline(x=-0.1, linestyle="--", color="black")
plt.axvline(x=-0.3, linestyle="--", color="black")
plt.axvline(x=-0.5, linestyle="--", color="black")
plt.title("各变量与'上月mou'之间的相关性", fontsize=14)

ax = plt.subplot(1, 4, 3)
x = df_dummies.corr()['上上月mou'].sort_values(ascending=False)
sns.barplot(y=x.index, x=x.values, orient='h')
plt.axvline(x=0.1, linestyle="--", color="black")
plt.axvline(x=0.3, linestyle="--", color="black")
plt.axvline(x=0.5, linestyle="--", color="black")
plt.axvline(x=0.8, linestyle="--", color="black")
plt.axvline(x=-0.1, linestyle="--", color="black")
plt.axvline(x=-0.3, linestyle="--", color="black")
plt.axvline(x=-0.5, linestyle="--", color="black")
plt.title("各变量与'上上月mou'之间的相关性", fontsize=14)

ax = plt.subplot(1, 4, 4)
x = df_dummies.corr()['近三月平均mou'].sort_values(ascending=False)
sns.barplot(y=x.index, x=x.values, orient='h')
plt.axvline(x=0.1, linestyle="--", color="black")
plt.axvline(x=0.3, linestyle="--", color="black")
plt.axvline(x=0.5, linestyle="--", color="black")
plt.axvline(x=0.8, linestyle="--", color="black")
plt.axvline(x=-0.1, linestyle="--", color="black")
plt.axvline(x=-0.3, linestyle="--", color="black")
plt.axvline(x=-0.5, linestyle="--", color="black")
plt.title("各变量与'近三月平均mou'之间的相关性", fontsize=14)

plt.tight_layout()
plt.show()

在这里插入图片描述
从上面的图片中我们发现:‘当月mou’约等于’上月mou’约等于’上上月mou’约等于’近三月平均mou’。、

我们不妨在认知上做如下精简:用’近三月平均mou’代表mou相关的4个字段。mou相关字段中的任意一个字段,都可以看做是在反映用户的语音使用情况。

p.s:上面的认知并不严谨并不绝对正确,但足够好用。一方面这种简化后形成的认知让我们的分析更便捷更清晰,另一方面在实际业务场景中上述理解完全可以处理好绝大部分的情况。

结合之前的可视化分析与相关性分析,我们可以形成一个认知:5G用户往往使用较多的语音

宽带
df_dummies.columns

在这里插入图片描述
下面把宽带相关的重要字段拎出来做一份热力图。

x=['是否本网宽带用户_1', '是否异网宽带用户_1','是否宽带捆绑签约_1.0', '是否激活宽带_1.0',
        '宽带带宽_100.0', '宽带带宽_200.0', '宽带带宽_60.0',
        '标签']

plt.subplots(figsize=(10,10))
fig=sns.heatmap(df_dummies[x].corr(), annot=True, vmax=1, square=True, cmap="Blues", fmt='.2g', linewidths=1, linecolor='yellow')
# annot为热力图上显示数据;fmt='.2g'为数据保留两位有效数字,square呈现正方形,vmax最大值为1
plt.title('相关性热图', fontsize=18)

在这里插入图片描述

df_EDA['是否本网宽带用户'] = df_EDA['是否本网宽带用户'].astype(int)
df_EDA['是否异网宽带用户'] = df_EDA['是否异网宽带用户'].astype(int)
x=df_EDA.groupby(['是否本网宽带用户', '是否异网宽带用户']).size()
fig=plt.figure(figsize=(12,4))

plt.subplot(1, 2, 1)
for i,j in x.index:
    z=(df_EDA['标签']==1) & (df_EDA['是否本网宽带用户']==i) & (df_EDA['是否异网宽带用户']==j)
    x[(i,j)]= z.sum() / x[(i,j)]
ax=x.plot(kind='bar')
ax.set_xlabel('(是否本网宽带用户, 是否异网宽带用户)', fontsize=14)
ax.set_ylabel('5G办理率', fontsize=14)
plt.axhline(y=0.2, linestyle="--", color="black") # 整个数据集的平均5G办理率
plt.title('5G办理率-(是否本网宽带用户, 是否异网宽带用户)', fontsize=14)

plt.subplot(1, 2, 2)
ax=df_EDA.groupby(['是否本网宽带用户', '是否异网宽带用户']).size().plot(kind='bar')
for rect in ax.patches:
    ax.text(rect.get_x() + rect.get_width() / 2, rect.get_height() + 2, rect.get_height(), horizontalalignment='center', fontsize = 11)
ax.set_xlabel('(是否本网宽带用户, 是否异网宽带用户)', fontsize=14)
ax.set_ylabel('数量', fontsize=14)
plt.title('(是否本网宽带用户, 是否异网宽带用户)计数柱状图', fontsize=14)

plt.show()

在这里插入图片描述
下面研究一下本网宽带用户,异网宽带用户和未办理宽带的用户的arpu的分布。

p.s:想到要做这样的分析是因为在后面针对一些重点属性的的相关性分析中,宽带相关字段总是和arpu,套餐价值一起出现。

fig = plt.figure(figsize = (21, 4))
ax=plt.subplot(1,3,1)
sns.distplot(df_EDA[df_EDA['是否本网宽带用户']==1]['近三月平均arpu'], label='本网宽带用户')
sns.distplot(df_EDA[df_EDA['是否异网宽带用户']==1]['近三月平均arpu'], label='异网宽带用户')
ax.legend(loc='best')
ax.set_xlabel('近三月平均arpu', fontsize=12)
ax.set_ylabel('密度', fontsize=12) 
plt.title('分布:近三月平均arpu', fontsize=14, fontweight="bold")

ax=plt.subplot(1,3,2)
sns.distplot(df_EDA[df_EDA['是否本网宽带用户']==1]['近三月平均arpu'], label='本网宽带用户')
sns.distplot(df_EDA[(df_EDA['是否本网宽带用户']==0) & (df_EDA['是否异网宽带用户']==0)]['近三月平均arpu'], label='未办理宽带的用户')
ax.legend(loc='best')
ax.set_xlabel('近三月平均arpu', fontsize=12)
ax.set_ylabel('密度', fontsize=12) 
plt.title('分布:近三月平均arpu', fontsize=14, fontweight="bold")

ax=plt.subplot(1,3,3)
sns.distplot(df_EDA[df_EDA['是否异网宽带用户']==1]['近三月平均arpu'], label='异网宽带用户')
sns.distplot(df_EDA[(df_EDA['是否本网宽带用户']==0) & (df_EDA['是否异网宽带用户']==0)]['近三月平均arpu'], label='未办理宽带的用户')
ax.legend(loc='best')
ax.set_xlabel('近三月平均arpu', fontsize=12)
ax.set_ylabel('密度', fontsize=12) 
plt.title('分布:近三月平均arpu', fontsize=14, fontweight="bold")

plt.tight_layout()
plt.show()

在这里插入图片描述

plt.figure(figsize=(5,21))
x = df_dummies.corr()['是否本网宽带用户_1'].sort_values(ascending=False)
sns.barplot(y=x.index, x=x.values, orient='h')
plt.axvline(x=0.1, linestyle="--", color="black")
plt.axvline(x=0.3, linestyle="--", color="black")
plt.axvline(x=0.5, linestyle="--", color="black")
plt.axvline(x=0.8, linestyle="--", color="black")
plt.axvline(x=-0.1, linestyle="--", color="black")
plt.axvline(x=-0.3, linestyle="--", color="black")
plt.axvline(x=-0.5, linestyle="--", color="black")
plt.title("各变量与'是否本网宽带用户_1'之间的相关性", fontsize=14)
plt.show()

在这里插入图片描述对宽带相关字段,可以针对5G业务得出以下认知:

(1)从5G业务的角度,用户可被分为互不重合的两种:本网宽带用户,非本网宽带用户。

(2)本网宽带用户的5G办理率远高于平均水平,非本网宽带用户的5G办理率远低于平均水平。

(3)本网宽带用户中,宽带带宽为100的用户会特别倾向于办理5G,宽带带宽为200的用户会倾向于办理5G,宽带带宽为60的用户办理5G的倾向为平均水平。

(4)本网宽带用户等价于激活宽带,约等于宽带捆绑签约。

(5)非本网宽带用户,等价于未激活宽带,等价于没有本网宽带带宽(‘宽带带宽’字段为’未开通本网宽带’)。

p.s:上面的认知并不严谨并不绝对正确,但足够好用。一方面这种简化后形成的认知让我们的分析更便捷更清晰,另一方面在实际业务场景中上述理解完全可以处理好绝大部分的情况。

观察到’是否本网宽带用户’与用户的消费水平相关字段有较强相关性,下面看看’是否本网宽带用户’字段分别为0,1时,'近三月平均arpu’的分布情况

plt.figure(figsize = (12, 4))
ax=sns.kdeplot(data=df_EDA[df_EDA['是否本网宽带用户']==0], x='近三月平均arpu', fill=True, label='不是本网宽带用户')
sns.kdeplot(data=df_EDA[df_EDA['是否本网宽带用户']==1], x='近三月平均arpu', fill=True, label='是本网宽带用户')
ax.legend(loc='best')
plt.title('分布:{}'.format("近三月平均arpu"))

plt.show()

在这里插入图片描述观察上图时感觉上图有些像’标签’字段分别为0,1时,'近三月平均arpu’的分布密度图。下面画图一看,果然有些像。

plt.figure(figsize = (12, 8))

plt.subplot(2,1,1)
ax=sns.kdeplot(data=df_EDA[df_EDA['是否本网宽带用户']==0], x='近三月平均arpu', fill=True, label='不是本网宽带用户')
sns.kdeplot(data=df_EDA[df_EDA['是否本网宽带用户']==1], x='近三月平均arpu', fill=True, label='是本网宽带用户')
ax.legend(loc='best')
plt.title('分布:{}'.format("近三月平均arpu"))

plt.subplot(2,1,2)
ax=sns.kdeplot(data=df_EDA[df_EDA['标签']==0], x='近三月平均arpu', fill=True, label='不是5G用户')
sns.kdeplot(data=df_EDA[df_EDA['标签']==1], x='近三月平均arpu', fill=True, label='是5G用户')
ax.legend(loc='best')
plt.title('分布:{}'.format("近三月平均arpu"))

plt.tight_layout()
plt.show()

在这里插入图片描述

def show_values(axs, orient="v", space=.01):
    def _single(ax):
        if orient == "v":
            for p in ax.patches:
                _x = p.get_x() + p.get_width() / 2
                _y = p.get_y() + p.get_height() + (p.get_height()*0.01)
                value = '{:.2f}'.format(p.get_height())
                ax.text(_x, _y, value, ha="center") 
        elif orient == "h":
            for p in ax.patches:
                _x = p.get_x() + p.get_width() + float(space)
                _y = p.get_y() + p.get_height() - (p.get_height()*0.5)
                value = '{:.1f}'.format(p.get_width())
                ax.text(_x, _y, value, ha="left")

    if isinstance(axs, np.ndarray):
        for idx, ax in np.ndenumerate(axs):
            _single(ax)
    else:
        _single(axs)

plt.figure(figsize=(10,5))

plt.subplot(1,2,1)
x=df_EDA.loc[df_EDA['标签']==1, '是否本网宽带用户'].value_counts(dropna=False) / (df_EDA['标签']==1).sum()
ax=sns.barplot(x=x.index, y=x.values)
ax.set_xlabel('是否本网宽带用户', fontsize=12)
ax.set_xlabel('比例', fontsize=12)
plt.title('5G用户中,本网宽带用户与非本网宽带用户的比例')
show_values(ax)

plt.subplot(1,2,2)
x=df_EDA.loc[:, '是否本网宽带用户'].value_counts(dropna=False) / df_EDA.shape[0]
ax=sns.barplot(x=x.index, y=x.values)
ax.set_xlabel('是否本网宽带用户', fontsize=12)
ax.set_xlabel('比例', fontsize=12)
plt.title('全部用户中,本网宽带用户与非本网宽带用户的比例')
show_values(ax)

plt.show()

在这里插入图片描述本网宽带用户的特点 相对显著的特点:消费较高

结合之前的可视化分析与相关性分析,我们可以形成如下认知:大部分5G用户都是本网宽带用户,本网宽带用户的5G办理率远高于平均水平。

家庭用户
df_EDA['是否本网宽带用户'] = df_EDA['是否本网宽带用户'].astype(str)
df_EDA['是否异网宽带用户'] = df_EDA['是否异网宽带用户'].astype(str)
plt.figure(figsize=(5,21))
x = df_dummies.corr()['是否家庭用户_1'].sort_values(ascending=False)
sns.barplot(y=x.index, x=x.values, orient='h')
plt.axvline(x=0.1, linestyle="--", color="black")
plt.axvline(x=0.3, linestyle="--", color="black")
plt.axvline(x=0.5, linestyle="--", color="black")
plt.axvline(x=0.8, linestyle="--", color="black")
plt.axvline(x=-0.1, linestyle="--", color="black")
plt.axvline(x=-0.3, linestyle="--", color="black")
plt.axvline(x=-0.5, linestyle="--", color="black")
plt.title("各变量与'是否家庭用户_1'的相关性", fontsize=14)
plt.show()

在这里插入图片描述

def show_values(axs, orient="v", space=.01):
    def _single(ax):
        if orient == "v":
            for p in ax.patches:
                _x = p.get_x() + p.get_width() / 2
                _y = p.get_y() + p.get_height() + (p.get_height()*0.01)
                value = '{:.2f}'.format(p.get_height())
                ax.text(_x, _y, value, ha="center") 
        elif orient == "h":
            for p in ax.patches:
                _x = p.get_x() + p.get_width() + float(space)
                _y = p.get_y() + p.get_height() - (p.get_height()*0.5)
                value = '{:.1f}'.format(p.get_width())
                ax.text(_x, _y, value, ha="left")

    if isinstance(axs, np.ndarray):
        for idx, ax in np.ndenumerate(axs):
            _single(ax)
    else:
        _single(axs)

plt.figure(figsize=(10,5))

plt.subplot(1,2,1)
x=df_EDA.loc[df_EDA['标签']==1, '是否家庭用户'].value_counts(dropna=False) / (df_EDA['标签']==1).sum()
ax=sns.barplot(x=x.index, y=x.values)
ax.set_xlabel('是否家庭用户', fontsize=12)
ax.set_xlabel('比例', fontsize=12)
plt.title('5G用户中,家庭用户与非家庭用户的比例')
show_values(ax)

plt.subplot(1,2,2)
x=df_EDA.loc[:, '是否家庭用户'].value_counts(dropna=False) / df_EDA.shape[0]
ax=sns.barplot(x=x.index, y=x.values)
ax.set_xlabel('是否家庭用户', fontsize=12)
ax.set_xlabel('比例', fontsize=12)
plt.title('全部用户中,家庭用户与非家庭用户的比例')
show_values(ax)

plt.show()

在这里插入图片描述家庭用户的特点 相对显著的特点:是本网宽带用户。

结合之前的可视化分析与相关性分析,我们可以形成如下认知:88%的5G用户都是家庭用户。非家庭用户的5G办理率远低于平均水平。

针对多种分类型属性的用户特点分析

基于上一节获得的精简后的业务认知,对那些对5G办理率影响较大的分类型属性,尝试找出具备这些属性的用户的特点。

df_EDA['星级']=df_EDA['星级'].astype(float)
df_dummies['星级是否大于3']=0
df_dummies['星级是否大于3'][df_EDA['星级']>3]=1
df_dummies['星级是否大于3']=df_dummies['星级是否大于3'].astype(int)
df_dummies.columns

在这里插入图片描述

df_EDA['星级']=df_EDA['星级'].astype(str)
plt.figure(figsize=(5,21))
x = df_dummies.corr()['星级是否大于3'].sort_values(ascending=False)
sns.barplot(y=x.index, x=x.values, orient='h')
plt.axvline(x=0.1, linestyle="--", color="black")
plt.axvline(x=0.3, linestyle="--", color="black")
plt.axvline(x=0.5, linestyle="--", color="black")
plt.axvline(x=0.8, linestyle="--", color="black")
plt.axvline(x=-0.1, linestyle="--", color="black")
plt.axvline(x=-0.3, linestyle="--", color="black")
plt.axvline(x=-0.5, linestyle="--", color="black")
plt.title("各变量与'星级是否大于3'之间的相关性", fontsize=14)
plt.show()

在这里插入图片描述
星级大于3的用户的特点

相对显著的特点:消费较高。

相对不显著的特点:通话时间较长,是本网宽带用户。

plt.figure(figsize=(10,21))

ax=plt.subplot(1,2,1)
x = df_dummies.corr()['终端类型_0'].sort_values(ascending=False)
sns.barplot(y=x.index, x=x.values, orient='h')
plt.axvline(x=0.1, linestyle="--", color="black")
plt.axvline(x=0.3, linestyle="--", color="black")
plt.axvline(x=0.5, linestyle="--", color="black")
plt.axvline(x=0.8, linestyle="--", color="black")
plt.axvline(x=-0.1, linestyle="--", color="black")
plt.axvline(x=-0.3, linestyle="--", color="black")
plt.axvline(x=-0.5, linestyle="--", color="black")
plt.title("各变量与'终端类型_0'之间的相关性", fontsize=14)

ax=plt.subplot(1,2,2)
x = df_dummies.corr()['终端类型_2'].sort_values(ascending=False)
sns.barplot(y=x.index, x=x.values, orient='h')
plt.axvline(x=0.1, linestyle="--", color="black")
plt.axvline(x=0.3, linestyle="--", color="black")
plt.axvline(x=0.5, linestyle="--", color="black")
plt.axvline(x=0.8, linestyle="--", color="black")
plt.axvline(x=-0.1, linestyle="--", color="black")
plt.axvline(x=-0.3, linestyle="--", color="black")
plt.axvline(x=-0.5, linestyle="--", color="black")
plt.title("各变量与'终端类型_2'之间的相关性", fontsize=14)

plt.tight_layout()
plt.show()

在这里插入图片描述
终端类型0与arpu和dou成负相关,猜测该终端的定位较为低端,或在使用上有一定限制(类似之前的小灵通)。

终端类型2与5G流量和当月换机有较强的正相关性,猜测该终端类型是当前主推的,且和5G有关。
终端类型为0的用户的特点

相对不显著的特点:消费较低,流量使用较少。

终端类型为2的用户的特点

相对不显著的特点:5G流量使用较多,当月换机。

plt.figure(figsize=(5,21))
x = df_dummies.corr()['是否终端捆绑签约_1.0'].sort_values(ascending=False)
sns.barplot(y=x.index, x=x.values, orient='h')
plt.axvline(x=0.1, linestyle="--", color="black")
plt.axvline(x=0.3, linestyle="--", color="black")
plt.axvline(x=0.5, linestyle="--", color="black")
plt.axvline(x=0.8, linestyle="--", color="black")
plt.axvline(x=-0.1, linestyle="--", color="black")
plt.axvline(x=-0.3, linestyle="--", color="black")
plt.axvline(x=-0.5, linestyle="--", color="black")
plt.title("各变量与'是否终端捆绑签约_1.0'之间的相关性", fontsize=14)
plt.show()

在这里插入图片描述
终端捆绑签约的用户的特点

相对不显著的特点:是本网宽带用户,消费较高。

plt.figure(figsize=(5,21))
x = df_dummies.corr()['是否话费签约_1.0'].sort_values(ascending=False)
sns.barplot(y=x.index, x=x.values, orient='h')
plt.axvline(x=0.1, linestyle="--", color="black")
plt.axvline(x=0.3, linestyle="--", color="black")
plt.axvline(x=0.5, linestyle="--", color="black")
plt.axvline(x=0.8, linestyle="--", color="black")
plt.axvline(x=-0.1, linestyle="--", color="black")
plt.axvline(x=-0.3, linestyle="--", color="black")
plt.axvline(x=-0.5, linestyle="--", color="black")
plt.title("各变量与'是否话费签约_1.0'之间的相关性", fontsize=14)
plt.show()

在这里插入图片描述
话费签约的用户的特点

未观察到明显的特点。

其他分析

这一部分凭感觉和兴趣画了些图,挑了些一些点做分析。很多内容和5G业务的关系不大

x = np.arange(0, 1, 0.05)
print(df_EDA.loc[df_EDA['标签']==1, '5G流量'].describe(percentiles=x))

print('=============================================')
x = np.arange(0, 1, 0.01)
print(df_EDA.loc[df_EDA['标签']==0, '5G流量'].describe(percentiles=x))

print('=============================================')
print(df_EDA.loc[(df_EDA['标签']==1) & (df_EDA['5G流量']>0), '5G流量'].describe())

在这里插入图片描述

plt.figure(figsize=(5,21))
x = df_dummies.corr()['5G流量'].sort_values(ascending=False)
sns.barplot(y=x.index, x=x.values, orient='h')
plt.axvline(x=0.1, linestyle="--", color="black")
plt.axvline(x=0.3, linestyle="--", color="black")
plt.axvline(x=0.5, linestyle="--", color="black")
plt.axvline(x=0.8, linestyle="--", color="black")
plt.axvline(x=-0.1, linestyle="--", color="black")
plt.axvline(x=-0.3, linestyle="--", color="black")
plt.axvline(x=-0.5, linestyle="--", color="black")
plt.title("各变量与'5G流量'之间的相关性", fontsize=14)
plt.show()

在这里插入图片描述
超过90%的5G用户,未使用5G流量。(数据集中没把’5G流量’的口径写清楚,猜测这个字段表示的是当月的5G流量)这可能是因为缺少配套的5G应用场景,用户缺乏使用5G的动力。也可能是因为5G的费用较高,用户不愿意为网速的提升掏这样一笔额外的金额。

不到4%的非5G用户,使用了5G流量。

plt.figure(figsize = (16, 4))


plt.subplot(121)
df1 = df_EDA.loc[df_EDA['当月语音超套金额']>0, :]
ax=sns.distplot(df1.loc[df1['标签']==0, '当月语音超套金额'], label='非5G用户')
sns.distplot(df1.loc[df1['标签']==1, '当月语音超套金额'], label='5G用户')
ax.legend(loc='best')
plt.title('分布:{}(当月语音超套金额大于0的用户)'.format("当月语音超套金额"))

plt.subplot(122)
df1 = df_EDA.loc[df_EDA['当月流量超套金额']>0, :]
ax=sns.distplot(df1.loc[df1['标签']==0, '当月流量超套金额'], label='非5G用户')
sns.distplot(df1.loc[df1['标签']==1, '当月流量超套金额'], label='5G用户')
ax.legend(loc='best')
plt.title('分布:{}(当月流量超套金额大于0的用户)'.format("当月流量超套金额"))

plt.show()

在这里插入图片描述

print('左一左二两幅图的相关性都是在当月语音超套金额大于0的用户中计算的。')
print('右一右二两幅图的相关性都是在当月流量超套金额大于0的用户中计算的。')


plt.figure(figsize=(20,21))

plt.subplot(141)
x = df_dummies[df_EDA['当月语音超套金额']>0].corr()['标签'].sort_values(ascending=False)
sns.barplot(y=x.index, x=x.values, orient='h')
plt.axvline(x=0.1, linestyle="--", color="black")
plt.axvline(x=0.3, linestyle="--", color="black")
plt.axvline(x=0.5, linestyle="--", color="black")
plt.axvline(x=0.8, linestyle="--", color="black")
plt.axvline(x=-0.1, linestyle="--", color="black")
plt.axvline(x=-0.3, linestyle="--", color="black")
plt.axvline(x=-0.5, linestyle="--", color="black")
plt.title("各变量与'标签'之间的相关性", fontsize=14)

plt.subplot(142)
x = df_dummies[df_EDA['当月语音超套金额']>0].corr()['当月语音超套金额'].sort_values(ascending=False)
sns.barplot(y=x.index, x=x.values, orient='h')
plt.axvline(x=0.1, linestyle="--", color="black")
plt.axvline(x=0.3, linestyle="--", color="black")
plt.axvline(x=0.5, linestyle="--", color="black")
plt.axvline(x=0.8, linestyle="--", color="black")
plt.axvline(x=-0.1, linestyle="--", color="black")
plt.axvline(x=-0.3, linestyle="--", color="black")
plt.axvline(x=-0.5, linestyle="--", color="black")
plt.title("各变量与'当月语音超套金额'之间的相关性", fontsize=14)

plt.subplot(143)
x = df_dummies[df_EDA['当月流量超套金额']>0].corr()['标签'].sort_values(ascending=False)
sns.barplot(y=x.index, x=x.values, orient='h')
plt.axvline(x=0.1, linestyle="--", color="black")
plt.axvline(x=0.3, linestyle="--", color="black")
plt.axvline(x=0.5, linestyle="--", color="black")
plt.axvline(x=0.8, linestyle="--", color="black")
plt.axvline(x=-0.1, linestyle="--", color="black")
plt.axvline(x=-0.3, linestyle="--", color="black")
plt.axvline(x=-0.5, linestyle="--", color="black")
plt.title("各变量与'标签'之间的相关性", fontsize=14)

plt.subplot(144)
x = df_dummies[df_EDA['当月流量超套金额']>0].corr()['当月流量超套金额'].sort_values(ascending=False)
sns.barplot(y=x.index, x=x.values, orient='h')
plt.axvline(x=0.1, linestyle="--", color="black")
plt.axvline(x=0.3, linestyle="--", color="black")
plt.axvline(x=0.5, linestyle="--", color="black")
plt.axvline(x=0.8, linestyle="--", color="black")
plt.axvline(x=-0.1, linestyle="--", color="black")
plt.axvline(x=-0.3, linestyle="--", color="black")
plt.axvline(x=-0.5, linestyle="--", color="black")
plt.title("各变量与'当月流量超套金额'之间的相关性", fontsize=14)

plt.tight_layout()
plt.show()

在这里插入图片描述
即便把范围缩小到当月语音超套金额大于0的用户,当月流量超套金额对5G办理的影响也比较小。

即便把范围缩小到当月流量超套金额大于0的用户,当月流量超套金额对5G办理的影响也比较小。

plt.figure(figsize = (8, 4))

df1 = df_EDA.loc[df_EDA['当月用户流量饱和度']>0, :]
ax=sns.distplot(df1.loc[df1['标签']==0, '当月用户流量饱和度'], label='非5G用户')
sns.distplot(df1.loc[df1['标签']==1, '当月用户流量饱和度'], label='5G用户')
ax.legend(loc='best')
plt.title('分布:{}(当月用户流量饱和度大于0的用户)'.format("当月用户流量饱和度"))

plt.show()

在这里插入图片描述

print('以下两幅图的相关性都是在当月语音超套金额大于0的用户中计算的。')

plt.figure(figsize=(10,21))

plt.subplot(121)
x = df_dummies[df_EDA['当月用户流量饱和度']>0].corr()['标签'].sort_values(ascending=False)
sns.barplot(y=x.index, x=x.values, orient='h')
plt.axvline(x=0.1, linestyle="--", color="black")
plt.axvline(x=0.3, linestyle="--", color="black")
plt.axvline(x=0.5, linestyle="--", color="black")
plt.axvline(x=0.8, linestyle="--", color="black")
plt.axvline(x=-0.1, linestyle="--", color="black")
plt.axvline(x=-0.3, linestyle="--", color="black")
plt.axvline(x=-0.5, linestyle="--", color="black")
plt.title("各变量与'标签'之间的相关性", fontsize=14)

plt.subplot(122)
x = df_dummies[df_EDA['当月用户流量饱和度']>0].corr()['当月用户流量饱和度'].sort_values(ascending=False)
sns.barplot(y=x.index, x=x.values, orient='h')
plt.axvline(x=0.1, linestyle="--", color="black")
plt.axvline(x=0.3, linestyle="--", color="black")
plt.axvline(x=0.5, linestyle="--", color="black")
plt.axvline(x=0.8, linestyle="--", color="black")
plt.axvline(x=-0.1, linestyle="--", color="black")
plt.axvline(x=-0.3, linestyle="--", color="black")
plt.axvline(x=-0.5, linestyle="--", color="black")
plt.title("各变量与'当月用户流量饱和度'之间的相关性", fontsize=14)

plt.tight_layout()
plt.show()

在这里插入图片描述
即便把范围缩小到当月用户流量饱和度大于0的用户,当月用户流量饱和度对5G办理的影响也非常小。

def show_values(axs, orient="v", space=.01):
    def _single(ax):
        if orient == "v":
            for p in ax.patches:
                _x = p.get_x() + p.get_width() / 2
                _y = p.get_y() + p.get_height() + (p.get_height()*0.01)
                value = '{:.2f}'.format(p.get_height())
                ax.text(_x, _y, value, ha="center") 
        elif orient == "h":
            for p in ax.patches:
                _x = p.get_x() + p.get_width() + float(space)
                _y = p.get_y() + p.get_height() - (p.get_height()*0.5)
                value = '{:.1f}'.format(p.get_width())
                ax.text(_x, _y, value, ha="left")

    if isinstance(axs, np.ndarray):
        for idx, ax in np.ndenumerate(axs):
            _single(ax)
    else:
        _single(axs)

x = df_EDA[(df_EDA['居住地是否覆盖5G']=='1') & (df_EDA['工作地是否覆盖5G']=='1')]['标签'].value_counts(dropna = False) / ((df_EDA['居住地是否覆盖5G']=='1') & (df_EDA['工作地是否覆盖5G']=='1')).sum()
plt.figure(figsize=(5,5))
ax=sns.barplot(x=x.index, y=x.values)
show_values(ax)
plt.title('5G用户与非5G用户比例(范围是居住地和工作地都覆盖5G的用户)')
plt.show()

在这里插入图片描述即使是在居住地和工作地都覆盖5G的用户中,5G办理率也仅有22%。5G覆盖对5G办理的影响非常小。

fig = plt.figure(figsize = (5, 5))
ax = sns.countplot(x='细分市场', data=df_EDA, hue='终端类型')
plt.title('星级-性别数量')
plt.show()

在这里插入图片描述观察上面的柱形图发现,各个细分市场的用户使用最多的都是终端类型1。农村用户更多使用终端类型0,更少使用终端类型2,集团用户则正好相反。

x = df_EDA['终端类型'].value_counts()
labels = [i for i in x.index]

plt.pie(x, labels=labels, explode=(0, 0.1, 0.2), autopct='%.2f%%', pctdistance=0.6)
plt.title("终端类型-饼图") 
plt.show()

在这里插入图片描述
观察上面饼图发现,用户使用的终端类型以1型为主

df1=df_EDA
sns.pairplot(df1[['近三月平均arpu', '近三月平均dou', '近三月平均mou', '标签']], hue='标签', size=15)

plt.show()

在这里插入图片描述观察上面的双色散点图发现:

低消费高流量使用的用户办理5G的比率很低。

中等消费的用户,语音使用明显多余低消费用户,但二者在流量使用上没有明显的差别。

使用流量最多的那部分用户,使用语音较少。使用流量最少的那部分用户,使用语音较多。

fig = plt.figure(figsize = (15,15))

plt.subplot(3, 1, 1)
sns.scatterplot(x = '当月dou', y = '当月用户流量饱和度', data = df_EDA, hue = '标签', edgecolor = 'black', size=20)
plt.legend(['5G用户', '非5G用户'], loc = 'upper left',)
title = '当月dou' + ' vs ' + '当月用户流量饱和度'
plt.title(title)

plt.subplot(3, 1, 2)
sns.scatterplot(x = '上月dou', y = '上月用户流量饱和度', data = df_EDA, hue = '标签', edgecolor = 'black', size=20)
plt.legend(['5G用户', '非5G用户'], loc = 'upper left',)
title = '上月dou' + ' vs ' + '上月用户流量饱和度'
plt.title(title)

plt.subplot(3, 1, 3)
sns.scatterplot(x = '上上月dou', y = '上上月用户流量饱和度', data = df_EDA, hue = '标签', edgecolor = 'black', size=20)
plt.legend(['5G用户', '非5G用户'], loc = 'upper left',)
title = '上上月dou' + ' vs ' + '上上月用户流量饱和度'
plt.title(title)

plt.tight_layout()
plt.show()

在这里插入图片描述
上面的散点图都有几条明显的从原点出发的线段。推测这些线段代表着某些热门的流量额度,在有相同流量额度的用户中,dou和流量饱和度成正比,他们的数据在散点图中形成了从原点出发的线段。

df1=df_EDA

fig = plt.figure(figsize = (24,12))
sns.scatterplot(x = '当月arpu', y = '当月dou', data = df1, hue = '标签', edgecolor = 'black', size=20)
plt.legend(['5G用户', '非5G用户'], loc = 'upper left',)
title = '当月arpu' + ' vs ' + '当月dou' +'(随机抽10000条记录)'
plt.title(title)
plt.show()

在这里插入图片描述
发现上图中有一些垂直于横坐标的线段。猜测这代表着使用某些热门的主资费套餐的用户,他们除了使用主资费套餐之外,很少有额外的消费,所以他们的arpu高度集中在某几个值附近,从而形成了一些竖直的线段。下面找到了人数最多的’用户总套餐价值’和’用户主资费套餐价值’的取值,几个热门的价值的取值,和上面的散点图中的竖直线段的横坐标大致吻合。上述猜测得到了初步证实。

pd.set_option('display.max_rows', 10)
print(df_EDA.groupby('用户总套餐价值').size().sort_values(ascending=False))
print(df_EDA.groupby('用户主资费套餐价值').size().sort_values(ascending=False))
pd.set_option('display.max_rows', 14)

在这里插入图片描述

fig = plt.figure(figsize = (10, 5))
ax = sns.barplot(x = '星级', y = '近三月平均dou', data = df_EDA, errorbar='ci') # 误差棒是置信区间
plt.title('')
plt.show()

在这里插入图片描述
观察上面待误差棒的条形图发现,星级为5,6,7的置信区间较长,这是因为它们的的样本比较少,因此同为95%置信度时,它们的估计更不可靠,置信区间更长。

plt.figure(figsize = (12, 4))
ax=sns.kdeplot(data=df_EDA[df_EDA['是否套餐签约']=='0.0'], x='年龄', fill=True, label='非套餐签约用户')
sns.kdeplot(data=df_EDA[df_EDA['是否套餐签约']=='1.0'], x='年龄', fill=True, label='套餐签约用户')
ax.legend(loc='best')
plt.title('分布:{}'.format("年龄"))

plt.show()

在这里插入图片描述
观察上面的双色密度图发现,年龄对于是否套餐签约的影响很小。

df1 = df_EDA.loc[df_EDA['在网时长']>1, :]

plt.figure(figsize = (12, 4))
ax=sns.kdeplot(data=df1[df1['标签']==0], x='在网时长', fill=True, label='非5G用户')
sns.kdeplot(data=df1[df1['标签']==1], x='在网时长', fill=True, label='5G用户')
ax.legend(loc='best')
plt.title('分布:{}(在网时长大于1的用户)'.format("在网时长"))

plt.show()

在这里插入图片描述观察上面的双色密度图发现,在那些在网时长大于1的用户中,5G用户的在网时长比非5G用户略微高一些。

plt.figure(figsize=(10, 5))
sns.boxplot(data=df_EDA, x='终端类型', y='近三月平均arpu')
plt.title('性别 vs ' + '性别')
plt.show()

在这里插入图片描述
观察上面的箱型图发现: (1)终端类型为0的用户,消费水平远低于终端类型为1或2的用户。 (2)终端类型为2的用户,消费水平略微高于终端类型为1的用户
fig = plt.figure(figsize=(16,5))

# 设置一个网格(grid),行数为1,列数为2,宽度比例为3:1
gs = gridspec.GridSpec(nrows=1, ncols=2, width_ratios=[1, 4])


ax0 = fig.add_subplot(gs[0, 0])
sns.violinplot(x = '标签', y =  '近三月平均mou', data = df_EDA)

ax0 = fig.add_subplot(gs[0, 1])
sns.violinplot(x = '细分市场', y =  '近三月平均mou', hue='标签', data = df_EDA)

plt.tight_layout()
plt.show()

在这里插入图片描述
观察上面的小提琴图发现,集团用户语音使用较多,校园用户语音使用较少。

流量相关字段的异常数据分析

在做数据处理的时候,观察到有的记录’当月流量饱和度’为0.00,'当月dou’却大于0。感到有些疑惑,在此做进一步观察和分析。

'当月dou’和’当月流量饱和度’这两个字段,如果: (1)两个都为0。正常! (2)都不为0,那么就看计算出来的总流量额度有无异常。 (3)如果一个为0,一个大于0:(a)没有取近似值,就是0。相关记录是出现了错误或是特殊情况。(b)有取近似值,其实是小于0.01的某值,那就还是计算总流量看有无异常。

检查近三个月,dou和流量饱和度同时为0的记录数。

print(((df['当月dou']==0) &  (df['当月用户流量饱和度']==0)).sum())
print(((df['上月dou']==0) &  (df['上月用户流量饱和度']==0)).sum())
print(((df['上上月dou']==0) &  (df['上上月用户流量饱和度'

在这里插入图片描述
对于近三个月,dou和流量饱和度同时大于0的记录,计算这些记录对应的总流量额度,未观察到异常。

df1 = df.loc[((df['当月dou']>0) &  (df['当月用户流量饱和度']>0)), ['当月dou', '当月用户流量饱和度']]
df2 = df1['当月dou']*100 / df1['当月用户流量饱和度']
print('================================================')
print(df2.describe())

df1 = df.loc[((df['上月dou']>0) &  (df['上月用户流量饱和度']>0)), ['上月dou', '上月用户流量饱和度']]
df2 = df1['上月dou']*100 / df1['上月用户流量饱和度']
print('================================================')
print(df2.describe())

df1 = df.loc[((df['上上月dou']>0) &  (df['上上月用户流量饱和度']>0)), ['上上月dou', '上上月用户流量饱和度']]
df2 = df1['上上月dou']*100 / df1['上上月用户流量饱和度']
print('================================================')
print(df2.describe())

在这里插入图片描述
下面分析’当月dou’和’当月用户流量饱和度’这两个字段一个为0,一个不为0的记录。这时可能有几种情况: (1)在数据录入等环节出了错。 (2)没出错,该条记录代表的某种特殊情况。 (3)字段中的0是取近似数的结果,观察发现这两个字段都保留到0.01,那么字段中的0可能代表的是小于0.01的某个值,在取近似数时被记录为0。

dou相关字段的平均数都在2000左右。《中国移动互联网发展报告(2021)》中提到“2020年全年移动互联网接入流量消费达1656亿GB,比上年增长35.7%。全年移动互联网月户均流量(DOU)达10.35GB/户·月”。合理推测本数据中dou相关字段的单位是MB。

下面进行具体的分析。

df[ (df['当月dou']==0) &  (df['当月用户流量饱和度']>0) ][['当月用户流量饱和度' ]].describe()

在这里插入图片描述在这里插入图片描述

观察发现,有172条记录的’当月dou’字段为0,且’当月用户流量饱和度’大于0。

这些记录的很可能是情况(1),即在数据输入等环节出现了错误,因为相关记录的数量非常少所以可能性还是挺高的。其次,也有可能是情况(2),即这些记录代表着某些特殊情况。不管是情况(1)还是情况(2),都需要与业务方或与数据工程师进行沟通后才能确认。

这些记录不太可能普遍是情况(3),即字段中的0是取近似数的结果。假设是情况(3),那么这172条记录中的’当月dou’都小于0.01,而有超过一半记录的’当月用户流量饱和度’大于21%,那么就有超过86名用户他们的总流量额度小于0.048MB。这是不符合常识的,没有套餐会定这么小的总流量额度。所以这些记录不太可能普遍是情况(3),他们中只有’当月用户流量饱和度’较低的那些记录有可能是情况(3)。

df[ (df['当月用户流量饱和度']==0) &  (df['当月dou']>0) ][['当月dou' ]].describe()

在这里插入图片描述在这里插入图片描述
观察发现,有87023条记录的’当月用户流量饱和度’为0,且’当月dou’大于0。

这些记录不太可能是情况(1),即在数据输入等环节出现了错误,因为相关记录的数量非常多。其次,也有可能是情况(2),即这些记录代表着某些特殊情况。不管是情况(1)还是情况(2),都需要与业务方或与数据工程师进行沟通后才能确认。

这些记录不太可能普遍是情况(3),即字段中的0是取近似数的结果。假设是情况(3),那么这87023条记录中的’当月用户流量饱和度’都小于0.01%,有超过一半记录的’当月dou’大于786.5,那么就有超过43511名用户(即占比超过30%的用户)的总流量额度大于7865000MB。这是不符合常识的。所以这些记录不太可能普遍是情况(3),他们中只有’当月dou’较低的那些记录有可能是情况(3)。

上面对超过80000条记录’当月dou’和’当月用户流量饱和度’这两个字段一个为0一个不为0这一现象的几种可能情况做了具体的分析。分析后并未能完全排除前面提到的3种可能情况的任何一种,需要与业务/数据工程的同事做确认后才能做出判断。

对于上月和上上月的dou与流量饱和度,也是类似的分析过程和结果,在此就不多赘述,仅把数据附在下面。

df[ (df['上月dou']==0) &  (df['上月用户流量饱和度']>0) ][['上月用户流量饱和度' ]].describe()

在这里插入图片描述

df[ (df['上月用户流量饱和度']==0) &  (df['上月dou']>0) ][['上月dou' ]].describe()

在这里插入图片描述

df[ (df['上上月dou']==0) &  (df['上上月用户流量饱和度']>0) ][['上上月用户流量饱和度' ]].describe()

在这里插入图片描述

df[ (df['上上月用户流量饱和度']==0) &  (df['上上月dou']>0) ][['上上月dou' ]].describe()

在这里插入图片描述

EDA总结

总结一下EDA部分的主要结论,结论涉及两个方面(1)机器学习部分可以参考的信息(2)针对5G业务的认知、核心指标与建议。
机器学习部分可以参考的信息

(1)这是一个非均衡数据集(imbalanced data),我们主要关注的是目标变量为1(是5G用户),而它的占比只有20.1%,是少数类别。为了让模型能更好地预测少数类别,在特征工程部分可以采用一些方法对数据进行处理,如欠采样,过采样,或SMOTE。

(2)数值变量都不符合正态分布,特征工程中可以参考这点进行特征缩放。

(3)随机森林算法给出的feature importance给出了一些不重要的特征,相关系分析中发现了一些具有较高相关性的特征,在特征工程中可以参考这些进行特征筛选。

针对5G业务的认知、核心指标与建议

(1)5G用户往往消费较高,且使用较多的流量和语音。

(2)88%的5G用户是家庭用户(全部用户中,家庭用户的比例是62%)。非家庭用户的5G办理率远低于平均水平。

(3)59%的5G用户是本网宽带用户(全部用户中,本网宽带用户的比例是28%),本网宽带用户的5G办理率远高于平均水平,非本网宽带用户的5G办理率远低于平均水平。

(4)当前5G业务的核心指标:近三月平均arpu(或消费相关的6个字段中的任意一个),近三月平均dou(或dou相关的4个字段中的任意一个),近三月平均mou(或mou相关的4个字段中的任意一个),是否家庭用户,是否本网宽带用户。

(5)其它较大提升5G办理率的属性:'星级’大于3级,终端捆绑签约,话费签约,终端类型为2。

(6)其它较大降低5G办理率的属性:'星级’小于3级,终端类型为0。

(7)要推广5G业务,可以依照已有的路径,通过推广本网宽带业务、增加家庭用户以及增加终端捆绑签约等,铺开5G业务。

(8)当前5G业务的主要瓶颈是,5G基站的覆盖率较低,用户也缺乏使用5G的需求。要推广5G业务,可以加快基站建设提高5G覆盖率,也可以寻找新的5G用户增长点,比如开发和宣传5G的配套应用吸引用户办理5G业务(类似短视频和直播吸引用户办理4G业务)。

(9)宽带业务是一场你死我活的战争,本网宽带用户和异网宽带用户有较强的互斥性,用户办理了异网宽带就几乎不会办理本网宽带,进而不倾向于成为本网的5G用户。建议把与其它运营商竞争宽带业务作为重要的目标,针对性地展开营销,这有助于本网5G业务的展开。

(10)当月换机的用户办理5G的比例高于整体用户的5G办理率,猜测是有部分用户换上了支持5G的手机从而考虑更换5G套餐。可以确定的是,换机的当月是用户考虑办理5G业务的一个窗口期,应在用户换机之后,做及时的、有针对性的营销。

(11)‘居住地是否覆盖5G’,'工作地是否覆盖5G’对5G办理的影响很小。一方面,居住地和工作地的5G覆盖率较低,都不到20%。另一方面,居住地覆盖5G/ 工作地覆盖5G/ 居住地和工作地都覆盖5G只提升了不到4%的5G办理率,提升幅度很小。5G的低覆盖率,可能会降低用户对5G的关注和使用。另外需要进一步跟进调查,看看为什么5G的覆盖并没有带来显著的5G办理比例的提升。是因为宣传不到位虽然覆盖了5G但用户不知道,还是因为用户的5G办理多是在捆绑签约等场景下完成,5G用户对5G本身并不怎么关心,办理5G业务主要是受捆绑的套餐的影响,而受5G覆盖的影响较小?

(12)超过90%的5G用户,未使用5G流量。(数据集中没把’5G流量’的口径写清楚,猜测这个字段表示的是当月的5G流量)。这可能是因为5G覆盖率较低。也可能是因为缺少配套的5G应用场景,用户缺乏使用5G的动力。还可能是因为5G的费用较高,高于用户对网络升级的心理价格。

(13)低消保号用户的5G办理率超过50%,远高于一般用户。低消保号用户的人数较少(约占总用户数的1%),建议找业务弄清楚这背后的原因,是样本的问题,还是运营商给不活跃的用户自动更换了5G套餐?

(14)在近三月平均arpu为70-80这个区间,5G用户和非5G用户有着接近的密度,可加强对这个区间内的非5G用户的营销,他们有较大的可能称为5G用户。

特征工程

准备供机器学习使用的特征。

生成特征

df_fe = df.copy()
df_fe['近三月平均语音超套金额'] = df_fe[['当月语音超套金额','上月语音超套金额' , '上上月语音超套金额']].mean(axis=1)
df_fe['近三月平均流量超套金额'] = df_fe[['当月流量超套金额','上月流量超套金额' , '上上月流量超套金额']].mean(axis=1)
df_fe['近三月平均用户流量饱和度'] = df_fe[['当月用户流量饱和度','上月用户流量饱和度' , '上上月用户流量饱和度']].mean(axis=1)

df_fe['上月arpu增长'] = (df_fe['上月arpu'] - df_fe['上上月arpu']) 
df_fe['当月arpu增长'] = (df_fe['当月arpu'] - df_fe['上月arpu'])
df_fe['上月dou增长'] = (df_fe['上月dou'] - df_fe['上上月dou'])
df_fe['当月dou增长'] = (df_fe['当月dou'] - df_fe['上月dou'])
df_fe['上月mou增长'] = (df_fe['上月mou'] - df_fe['上上月mou'])
df_fe['当月mou增长'] = (df_fe['当月mou'] - df_fe['上月mou'])
df_fe['上月语音超套金额增长'] = (df_fe['上月语音超套金额'] - df_fe['上上月语音超套金额'])
df_fe['当月语音超套金额增长'] = (df_fe['当月语音超套金额'] - df_fe['上月语音超套金额'])
df_fe['上月流量超套金额增长'] = (df_fe['上月流量超套金额'] - df_fe['上上月流量超套金额'])
df_fe['当月流量超套金额增长'] = (df_fe['当月流量超套金额'] - df_fe['上月流量超套金额'])
df_fe['上月用户流量饱和度增长'] = (df_fe['上月用户流量饱和度'] - df_fe['上上月用户流量饱和度'])
df_fe['当月用户流量饱和度增长'] = (df_fe['当月用户流量饱和度'] - df_fe['上月用户流量饱和度'])

# df_fe['上月arpu增长幅度'] = (df_fe['上月arpu'] - df_fe['上上月arpu']) / df_fe['上上月arpu']
# df_fe['当月arpu增长幅度'] = (df_fe['当月arpu'] - df_fe['上月arpu']) / df_fe['上月arpu']
# df_fe['上月dou增长幅度'] = (df_fe['上月dou'] - df_fe['上上月dou']) / df_fe['上上月dou']
# df_fe['当月dou增长幅度'] = (df_fe['当月dou'] - df_fe['上月dou']) / df_fe['上月dou']
# df_fe['上月mou增长幅度'] = (df_fe['上月mou'] - df_fe['上上月mou']) / df_fe['上上月mou']
# df_fe['当月mou增长幅度'] = (df_fe['当月mou'] - df_fe['上月mou']) / df_fe['上月mou']
# df_fe['上月语音超套金额增长幅度'] = (df_fe['上月语音超套金额'] - df_fe['上上月语音超套金额']) / df_fe['上上月语音超套金额']
# df_fe['当月语音超套金额增长幅度'] = (df_fe['当月语音超套金额'] - df_fe['上月语音超套金额']) / df_fe['上月语音超套金额']
# df_fe['上月流量超套金额增长幅度'] = (df_fe['上月流量超套金额'] - df_fe['上上月流量超套金额']) / df_fe['上上月流量超套金额']
# df_fe['当月流量超套金额增长幅度'] = (df_fe['当月流量超套金额'] - df_fe['上月流量超套金额']) / df_fe['上月流量超套金额']
# df_fe['上月用户流量饱和度增长幅度'] = (df_fe['上月用户流量饱和度'] - df_fe['上上月用户流量饱和度']) / df_fe['上上月用户流量饱和度']
# df_fe['当月用户流量饱和度增长幅度'] = (df_fe['当月用户流量饱和度'] - df_fe['上月用户流量饱和度']) / df_fe['上月用户流量饱和度']
feature = list(df_fe.columns)
feature.remove('标签')
cat_feature = cat_col
num_feature = [i for i in feature if i not in cat_feature]     
print(num_feature)

在这里插入图片描述

特征选择

# Threshold for removing correlated variables
threshold = 0.9

# Absolute value correlation matrix
corr_matrix = df_fe.corr().abs()
corr_matrix.head()

在这里插入图片描述

upper = corr_matrix.where(np.triu(np.ones(corr_matrix.shape), k=1).astype(np.bool))
upper.head()

在这里插入图片描述

to_drop = [column for column in upper.columns if any(upper[column] > threshold)]

print('There are %d columns to remove.' % (len(to_drop)))

df_fe = df_fe.drop(columns = to_drop)
feature = list(df_fe.columns)
feature.remove('标签')
cat_feature = [i for i in cat_feature if i not in to_drop]
num_feature = [i for i in num_feature if i not in to_drop]

特征标准化

df_fe[num_feature] = StandardScaler().fit_transform(df_fe[num_feature])

特征编码

ordinal_cat_feature=['星级']
nominal_cat_feature = [i for i in cat_feature if i not in ordinal_cat_feature]
for i in ordinal_cat_feature:
    df_fe[i] = LabelEncoder().fit_transform(df_fe[i])
X = pd.get_dummies(df_fe, drop_first = True).drop(columns='标签')
y = df_fe['标签']
X

在这里插入图片描述

机器学习

构建潜客识别模型(二分类分离器)。

准备工作

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42, stratify=y)
def plot_PR_curve(classifier, X_test, y_test):
    y_score = classifier.predict_proba(X_test)[:, 1]
    #calculate precision and recall
    precision, recall, thresholds = precision_recall_curve(y_test, y_score)

    #create precision recall curve
    fig, ax = plt.subplots()
    ax.plot(recall, precision, color='purple')

    #add axis labels to plot
    ax.set_title('Precision-Recall Curve')
    ax.set_ylabel('Precision')
    ax.set_xlabel('Recall')

    #display plot
    plt.show()

def plot_confusion_matrix(y_test, y_pred):
    cm = confusion_matrix(y_test, y_pred)
    names = ['True Neg','False Pos','False Neg','True Pos']
    counts = [value for value in cm.flatten()]
    percentages = ['{0:.2%}'.format(value) for value in cm.flatten()/np.sum(cm)]
    labels = [f'{v1}\n{v2}\n{v3}' for v1, v2, v3 in zip(names,counts,percentages)]
    labels = np.asarray(labels).reshape(2,2)
    sns.heatmap(cm,annot = labels,cmap = 'Blues',fmt ='')

def model_evaluation(classifier, X_test, y_test):

    y_pred = classifier.predict(X_test)
    
    plot_confusion_matrix(y_test, y_pred)
    print('ROC_AUC_SCORE: ', roc_auc_score(y_test, y_pred))
    report = classification_report(y_test, y_pred)
    print(report)
    
    plot_roc_curve(classifier, X_test, y_test)

    plot_PR_curve(classifier, X_test, y_test)

模型训练

Logistic Regression
start_time = time.time()

LogisticRegressionClassifier = LogisticRegression()
LogisticRegressionClassifier.fit(X_train, y_train)

model_evaluation(LogisticRegressionClassifier, X_test, y_test)

print('共计耗时:{}s'.format(math.ceil(time.time()-start_time)))

在这里插入图片描述
在这里插入图片描述

plt.figure(figsize=(10,5))

plt.subplot(1, 2, 1)
weights = pd.Series(LogisticRegressionClassifier.coef_[0],
                 index=X.columns.values)
weights.sort_values(ascending = False)[:10].plot(kind='bar')
plt.title('逻辑回归分类器给出的特征权重')

plt.subplot(1, 2, 2)
weights = pd.Series(LogisticRegressionClassifier.coef_[0],
                 index=X.columns.values)
weights.sort_values(ascending = False)[-10:].plot(kind='bar')
plt.title('逻辑回归分类器给出的特征权重')

plt.show()

在这里插入图片描述

KNN
start_time = time.time()

KNNClassifier = KNeighborsClassifier()
KNNClassifier.fit(X_train, y_train)

model_evaluation(KNNClassifier, X_test, y_test)

print('共计耗时:{}s'.format(math.ceil(time.time()-start_time)))

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

Decision Tree
start_time = time.time()

DecisionTreeClassifier = DecisionTreeClassifier(random_state=42)
DecisionTreeClassifier.fit(X_train, y_train)

model_evaluation(DecisionTreeClassifier, X_test, y_test)

print('共计耗时:{}s'.format(math.ceil(time.time()-start_time)))

在这里插入图片描述在这里插入图片描述

Random Forest
start_time = time.time()

RandomForestClassifier = RandomForestClassifier(random_state=42)
RandomForestClassifier.fit(X_train, y_train)

model_evaluation(RandomForestClassifier, X_test, y_test)

print('共计耗时:{}s'.format(math.ceil(time.time()-start_time)))

在这里插入图片描述在这里插入图片描述

XGboost
start_time = time.time()

XGboostClassifier = XGBClassifier(random_state=42)
XGboostClassifier.fit(X_train, y_train)

model_evaluation(XGboostClassifier, X_test, y_test)

print('共计耗时:{}s'.format(math.ceil(time.time()-start_time)))

在这里插入图片描述
在这里插入图片描述

机器学习总结

在这里插入图片描述
最终部署的模型可以将阈值调低,牺牲精确率换取召回率。识别5G潜在用户的目的,是为了对他们进行营销,故而我们在成本允许的情况下,可以扩大营销的范围,让营销活动覆盖到更多的潜在5G用户,哪怕这会把更高比例的非5G潜在客户的用户含括在内。

部署了最终模型之后,就可以对被模型判断为5G客户的用户进行精细化的5G营销。比如面对于是本网宽带用户的5G潜客,可以宣传宽带和5G捆绑的套餐,对于消费较高的用户,可以着重宣传5G带来的网络服务的提升。

Logo

长江两岸老火锅,共聚山城开发者!We Want You!

更多推荐