数据分析常规步骤(运营商客户流失分析)

1.项目简介

1.1数分目标

2.综合信息

2.1字段含义

2.2本篇代码内容

2.3将会看到

2.4涉及技术

3.数据概况

data = pd.read_csv('Telco-Customer-Churn.csv')
data.head()
data.shape
data.columns
data.info()
data.describe().T
sns.heatmap(data.isnull(),cmap = 'magma',cbar = False)
###
data.isnull():这个表达式检查data数据框架(可能是pandas的DataFrame)中的每个值,并返回一个布尔数据框架,其中True表示缺失值(NaN),False表示非缺失值。
sns.heatmap():这是Seaborn库中用于绘制热力图的函数。
cmap = 'magma':这指定了热力图的颜色映射方案。'magma'是一种颜色方案,它从蓝色(低值)渐变到红色(高值)。在缺失值的情况下,True会被视为高值,因此在热力图中缺失值会显示为红色或红色阴影。
cbar = False:这个参数设置为False,表示不要在热力图旁边显示颜色条(color bar)。颜色条通常用来表示颜色和数值之间的对应关系。
这行代码的作用是生成一个热力图,用来可视化数据框架中缺失值的位置,其中缺失值以红色显示,没有缺失值的地方以其他颜色显示,并且不显示颜色条。
###

3.1将字符串数据类型转换成浮点数数据类型 

# 计算'TotalCharges'列中每个元素的空格数
l1 = [len(i.split()) for i in data['TotalCharges']]

# 找出所有包含空格(实际上是空字符串或多于一个元素的情况)的索引位置
l2 = [i for i in range(len(l1)) if l1[i] != 1]
print('Index Positions with empty spaces : ', *l2)
###
*l2:使用星号操作符解包 l2 列表,这样就可以将列表中的每个索引作为单独的参数传递给 print 函数。
###

# 对于所有发现的有问题的索引,使用前一个索引位置的值进行替换
for i in l2:
    data.loc[i, 'TotalCharges'] = data.loc[(i-1), 'TotalCharges']

# 将'TotalCharges'列转换为浮点数,这里假设经过上述处理后所有值都可以转换
data['TotalCharges'] = data['TotalCharges'].astype(float)

这段代码的主要功能是处理 TotalCharges 列中的数据,确保所有值都能够被转换为浮点数(float)。它通过以下步骤实现这一点:

  1. 遍历 TotalCharges 列中的每个值。
  2. 尝试将每个值转换为浮点数。
    • 如果转换成功,该值就直接被更新为其浮点数形式。
    • 如果转换失败(通常是因为该值是空字符串或包含非数字字符,导致 ValueError),则会根据其位置采取不同的处理策略:
      • 如果当前值不是位于第一行,它会被替换为其前一个值。
      • 如果当前值位于第一行(即 i=0),由于没有前一个值可用,它会被设置为0或其他定义的默认值。
  3. 在所有 TotalCharges 值处理完成后,该列中的所有元素都将被安全转换为浮点数类型,因为所有非数字字符串已经被处理。

 

3.2对类别特征进行标签编码

#1.创建标签编码器
from sklearn.preprocessing import LabelEncoder
le = LabelEncoder()

# 2.创建数据副本以避免修改原始DataFrame
df1 = data.copy(deep=True)

# 3.筛选出文本数据特征列(即非数值型特征列)
text_data_features = [i for i in list(data.columns) if i not in list(data.describe().columns)]

#4.标签编码转换
print('Label Encoder Transformation')
for i in text_data_features:
    # 对每个文本数据特征列应用LabelEncoder
    df1[i] = le.fit_transform(df1[i])
    ###
    这里 le 是一个 LabelEncoder 对象。fit_transform 方法首先使用 df1[i](即当前列的数据)来
    训练 LabelEncoder(也就是学习如何将每个独特的文本标签转换成数字),然后使用这个训练好的 LabelEncoder 来转换整个列。
    ###

    print(i, ' : ', df1[i].unique(), ' = ', le.inverse_transform(df1[i].unique()))
    ###
    i:当前处理的列名。
    df1[i].unique():转换后该列中的唯一值(数值形式)。
    le.inverse_transform(df1[i].unique()):使用 inverse_transform 方法将数值形式的唯一值转换回原始的文本标签。
    ###
df1.describe()

 

3.3初步查看目标特征和其他特征平均值的关系

# 解决中文显示问题
plt.rcParams['font.sans-serif'] = ['SimHei']  # 设置字体为SimHei显示中文
plt.rcParams['axes.unicode_minus'] = False  # 解决负数轴标签显示问题

# 更新颜色方案
colors = ["#FF9999", "#66CCFF"]  # 浅粉红和浅蓝色


# 创建子图,采用新的figsize以更好地展示数据
fig, ax = plt.subplots(nrows=1, ncols=2, figsize=(10, 6))


# 为了更好地区分两个类别,使用不同的颜色映射
# 绘制流失客户数据的热图
sns.heatmap(churn[['mean']], annot=True, cmap=[colors[0]], linewidths=0.4, linecolor='black', cbar=False, fmt='.2f', ax=ax[0])

ax[0].set_title('流失客户')
###
使用 sns.heatmap 函数绘制流失客户数据的热图,只显示平均值列。参数解释如下:
churn[['mean']]:选择流失客户的平均值数据。
annot=True:在热图上显示数值。
cmap=[colors[0]]:使用浅粉红色作为颜色映射。
linewidths=0.4 和 linecolor='black':设置单元格边框的宽度和颜色。
cbar=False:不显示颜色条。
fmt='.2f':设置数值格式为两位小数。
ax=ax[0]:指定第一个子图用于绘制。
ax[0].set_title('流失客户'):设置第一个子图的标题。
###

# 绘制未流失客户数据的热图
sns.heatmap(not_churn[['mean']], annot=True, cmap=[colors[1]], linewidths=0.4, linecolor='black', cbar=False, fmt='.2f', ax=ax[1])
ax[1].set_title('未流失客户')
###
churn[['mean']] 和 churn['mean'] 都可以用来选择名为 ‘mean’ 的列,但它们返回的对象类型不同。
churn[['mean']] 返回 DataFrame,而 churn['mean'] 返回 Series。
churn[mean] 只有在 mean 是一个有效的变量时才可能正确,它也可能返回一个 Series,具体取决于 mean 变量的值。
###

# 优化布局
fig.tight_layout(pad=2)

4.数据探索分析EDA

4.1将特征划分为类别特征和数值特征

# col 存储 df1 中所有列的名称
col = list(df1.columns)

# 使用列表推导式分别生成分类特征和数值特征列表
categorical_features = [i for i in col if len(df1[i].unique()) <= 6]

numerical_features = [i for i in col if len(df1[i].unique()) > 6]


# 打印分类特征
print('Categorical Features:', *categorical_features)
# 打印数值特征
print('Numerical Features:', *numerical_features)
###
星号操作符 * 用来解包 categorical_features 和 numerical_features 列表,使得每个特征名称都能单独打印。
###

4.2目标特征可视化(饼图、立方图)

# 计算客户流失和非流失的比例
churn_rates = df1['Churn'].value_counts(normalize=True) * 100  # 直接计算百分比
###
value_counts()函数统计Churn列中每个值的出现次数。
normalize=True参数将统计结果转换为比例,即每个值占总数的比例。
* 100:将比例乘以100,转化为百分比格式。
###

# 创建绘图区域
fig, axs = plt.subplots(nrows=1, ncols=2, figsize=(20, 5))

# 绘制饼图
plt.subplot(1, 2, 1)#指定在1行2列的布局中,选择第1个子图进行绘制。
plt.pie(churn_rates, labels=['Not-Churn Customers', 'Churn Customers'], autopct='%1.1f%%', startangle=90, explode=(0.1, 0), colors=colors,
        wedgeprops={'edgecolor': 'black', 'linewidth': 1, 'antialiased': True})
###
使用plt.pie()函数绘制饼图。
churn_rates:饼图的数据,代表流失客户和非流失客户的比例。
labels=['Not-Churn Customers', 'Churn Customers']:设置饼图对应的标签,分别为“非流失客户”和“流失客户”。

autopct='%1.1f%%':在饼图中显示每个部分的百分比,保留一位小数。
%:这是一个格式化符号,表示格式化的开始。
1:这个数字表示整体宽度,包括小数点和百分号。如果数字的位数多于1,则完整显示数字。如果数字的位数少于1,那么会在数字前面填充空格。
1:这个数字表示小数点后的位数。
f:这是一个格式化代码,代表浮点数。
%%:用来表示百分号(%),因为在格式字符串中,% 需要用两个%来表示一个实际的百分号。

startangle=90:饼图从90度位置开始绘制。

explode=(0.1, 0):将第一个扇区(非流失客户)稍微突出,以增强视觉效果。
0.1:表示第一个扇区(通常对应第一个标签,如“非流失客户”)向外突出0.1个单位。
0:表示第二个扇区(通常对应第二个标签,如“流失客户”)不突出,保持在饼图的原位置。

colors=colors:指定扇区的颜色。
wedgeprops={'edgecolor': 'black', 'linewidth': 1, 'antialiased': True}:设置扇区的边框颜色为黑色,线宽为1,并启用抗锯齿功能。
wedgeprops:用于定义饼图扇区的绘图属性。
'edgecolor': 'black':设置扇区的边框颜色为黑色。
'linewidth': 1:设置边框的线宽为1个单位。
'antialiased': True:启用抗锯齿功能,使边缘更加平滑,视觉效果更佳。
###
plt.title('Churn - Not-Churn %')

# 绘制计数图
plt.subplot(1, 2, 2)
ax = sns.countplot(x='Churn', data=df1, palette=colors, edgecolor='black')

# 在条形图上添加数据标签
for rect in ax.patches:#遍历当前坐标轴 (ax) 中的所有矩形图形(条形)。
    ax.text(rect.get_x() + rect.get_width() / 2, rect.get_height() + 2, '%d' % rect.get_height(), ha='center', fontsize=11)
    ### 
    '%d' % rect.get_height():这是一个字符串格式化操作,旨在将条形的高度转换为字符串格式。
    %d 表示将一个整数格式化为十进制形式,rect.get_height()获取条形的高度(整数值)。   
    ha='center':这是 ax.text() 函数的一个参数,表示文本的水平对齐方式。
    'center':文本在 x 轴方向上居中显示。 
    fontsize=11:这是设置文本字体大小的参数。
    11:字体大小设置为 11 点。
    ###
ax.set_xticklabels(['Not-Churn Customers', 'Churn Customers'])

plt.title('Number of Churn - Not-Churn Customers')
plt.show()

4.3查看类别特征与目标变量的关系

4.3.1将类别特征分为3组

l1 = ['gender','SeniorCitizen','Partner','Dependents'] #客户信息
l2 = ['PhoneService','MultipleLines','InternetService','StreamingTV','StreamingMovies',
      'OnlineSecurity','OnlineBackup','DeviceProtection','TechSupport'] #支付服务
l3 = ['Contract','PaperlessBilling','PaymentMethod'] #支付信息

当数据集中包含大量分类特征时,将这些特征分成几个组是一个有效的策略。这样做可以帮助我们更有组织地分析数据,并根据不同特征组的特性来采用相应的数据预处理或特征工程策略。 

4.3.2分组1:包含客户基本信息的分类特征与目标特征的关系分布(柱状图)

fig, axs = plt.subplots(nrows=2, ncols=2, figsize=(20, 14))

#对 l1 列表中的每个特征进行迭代,i 是索引,feature 是特征名称。
for i, feature in enumerate(l1):
    plt.subplot(2, 2, i + 1)
    ax = sns.countplot(x=feature, data=data, hue="Churn", palette=colors, edgecolor='black')

# 在条形图上添加数据标签
    for rect in ax.patches:
        ax.text(rect.get_x() + rect.get_width() / 2, rect.get_height() + 2, '%d' % rect.get_height(), ha='center', fontsize=11)
    
    plt.title(f'{feature} 与客户流失情况')

plt.tight_layout()
plt.show()

4.3.3分组2:包含客户签订的服务的分类特征与目标特征的关系分布(柱状图)

# 首先处理前两个图表
fig, axs = plt.subplots(nrows=1, ncols=2, figsize=(12, 5))
for i in range(2):
    plt.subplot(1, 2, i + 1)
    ax = sns.countplot(x=l2[i], data=data, hue="Churn", palette=colors, edgecolor='black')
    # 在条形图上添加数据标签
    for rect in ax.patches:
        ax.text(rect.get_x() + rect.get_width() / 2, rect.get_height() + 2, rect.get_height(), ha='center', fontsize=11)
    # 将标题设置为中文
    plt.title(f'{l2[i]} 与客户流失情况')



# 处理第三个图表,单独创建一个图形
fig, ax = plt.subplots(figsize=(6, 5))
sns.countplot(x=l2[2], data=data, hue="Churn", palette=colors, edgecolor='black')
    # 在条形图上添加数据标签
for rect in ax.patches:
    ax.text(rect.get_x() + rect.get_width() / 2, rect.get_height() + 2, rect.get_height(), ha='center', fontsize=11)
    # 将标题设置为中文
plt.title(f'{l2[2]} 与客户流失情况')



# 处理最后两个图表,再次创建图形
fig, axs = plt.subplots(nrows=1, ncols=2, figsize=(12, 5))
for i, feature in enumerate(l2[3:5]):  # 使用enumerate简化循环
    plt.subplot(1, 2, i + 1)
    ax = sns.countplot(x=feature, data=data, hue="Churn", palette=colors, edgecolor='black')
    # 在条形图上添加数据标签
    for rect in ax.patches:
        ax.text(rect.get_x() + rect.get_width() / 2, rect.get_height() + 2, rect.get_height(), ha='center', fontsize=11)
    # 将标题设置为中文
    plt.title(f'{feature} 与客户流失情况')

fig, axs = plt.subplots(nrows=2, ncols=2, figsize=(20, 14))  # 创建一个2x2的图表布局
axs = axs.flatten()  # 将2x2的图表布局扁平化处理,以便于遍历



# 遍历特定特征,绘制与客户流失之间的关系图
for i, feature in enumerate(l2[-4:]):
    ax = sns.countplot(x=feature, data=data, hue="Churn", palette=colors, edgecolor='black', ax=axs[i])
    ax.set_title(f'{feature} 与客户流失的关系')
    # 在每个柱状图的顶部添加数据标签
    for rect in ax.patches:
        height = rect.get_height()
        ax.text(rect.get_x() + rect.get_width() / 2, height + 2, f'{height}', ha='center', fontsize=11)

plt.tight_layout()  # 自动调整子图参数,以给定的填充

4.3.4分组3:包含支付信息的分类特征与目标特征的关系分布(柱状图)

# 初始化画布和子图布局
fig, axs = plt.subplots(nrows=1, ncols=3, figsize=(25, 7))

# 遍历特征,绘制与客户流失的关系图
for i, feature in enumerate(l3[:3]):
    ax = sns.countplot(x=feature, data=data, hue="Churn", palette=colors, edgecolor='black', ax=axs[i]) 
    # 在每个柱状图的顶部标注数量
    for rect in ax.patches:
        height = rect.get_height()
        ax.text(rect.get_x() + rect.get_width() / 2, height + 2, int(height), ha='center', fontsize=11)
    # 设置每个子图的标题,增加中文支持
    axs[i].set_title(f'{feature} 与客户流失的关系')

plt.tight_layout()  # 调整布局以避免重叠

4.4分类特征与正目标变量(流失案例)的关系分布(饼图)

重点关注流失客户

4.4.1分组1:客户信息

# 定义一个函数来计算并绘制饼图,减少重复代码
def calculate_and_plot_pie(data, column, labels, subplot_position, title, explode):
    counts = data[data['Churn'] == 1][column].value_counts(normalize=True) * 100
    ###
    data[data['Churn'] == 1]:这行代码从 data DataFrame 中选择那些 Churn 列值为1的行,即流失客户的数据。
    [column]:这行代码从上述筛选出的数据中选择 column 列的数据。
    .value_counts(normalize=True):这行代码计算 column 列中不同值的数量,并将结果进行归一化处理。
    归一化意味着每个值的数量会转换为其在数据集中的比例。
    * 100:这行代码将归一化的结果乘以100,以得到百分比形式的比例。
    ###
  plt.subplot(1, 4, subplot_position)
  plt.pie(counts, labels=labels, autopct='%1.1f%%', startangle=90, explode=explode, colors=colors,
            wedgeprops={'edgecolor': 'black', 'linewidth': 1, 'antialiased': True})
    ###
    counts:这是饼图的数据,它是一个 Series 对象,包含了归一化后的值,表示每个部分在总体中的比例。
    ###
  plt.title(title)

# 使用上述函数来绘制所有需要的饼图
fig, ax = plt.subplots(nrows=1, ncols=4, figsize=(15, 15))

calculate_and_plot_pie(df1, 'gender', ['Female', 'Male'], 1, 'Gender', (0.1, 0))
calculate_and_plot_pie(df1, 'SeniorCitizen', ['No', 'Yes'], 2, 'SeniorCitizen', (0, 0.1))
calculate_and_plot_pie(df1, 'Partner', ['No', 'Yes'], 3, 'Partner', (0.1, 0))
calculate_and_plot_pie(df1, 'Dependents', ['No', 'Yes'], 4, 'Dependents', (0.1, 0))

4.4.2分组2:客户签订的服务

手动计算比例

phoneservice = df1[df1['Churn'] == 1]['PhoneService'].value_counts()
phoneservice = [phoneservice[0] / sum(phoneservice) * 100, phoneservice[1] / sum(phoneservice) * 100] # No - Yes

multiplelines = df1[df1['Churn'] == 1]['MultipleLines'].value_counts()
multiplelines = [multiplelines[0] / sum(multiplelines) * 100,multiplelines[1] / sum(multiplelines) * 100, multiplelines[2] / sum(multiplelines) * 100] # No - No Phone Service - Yes 

internetservice = df1[df1['Churn'] == 1]['InternetService'].value_counts()
internetservice = [internetservice[0] / sum(internetservice) * 100,internetservice[1] / sum(internetservice) * 100, internetservice[2] / sum(internetservice) * 100] # DSL - Fiber Optic - No 

streamingtv = df1[df1['Churn'] == 1]['StreamingTV'].value_counts()
streamingtv = [streamingtv[0] / sum(streamingtv) * 100,streamingtv[1] / sum(streamingtv) * 100, streamingtv[2] / sum(streamingtv) * 100] # No - No Internet Service - Yes 

streamingmovies = df1[df1['Churn'] == 1]['StreamingMovies'].value_counts()
streamingmovies = [streamingmovies[0] / sum(streamingmovies) * 100,streamingmovies[1] / sum(streamingmovies) * 100, streamingmovies[2] / sum(streamingmovies) * 100] # No - No Internet Service - Yes 

ax,fig = plt.subplots(nrows = 1,ncols = 2,figsize = (8,8))

plt.subplot(1,2,1)
plt.pie(phoneservice,labels = ['No', 'Yes'],autopct='%1.1f%%',startangle = 90,explode = (0.1,0),colors = colors,
       wedgeprops = {'edgecolor' : 'black','linewidth': 1,'antialiased' : True})
plt.title('PhoneService');

plt.subplot(1,2,2)
plt.pie(multiplelines,labels = ['No','No Phone Service','Yes'],autopct='%1.1f%%',startangle = 90,explode = (0.1,0,0.1),colors = colors,
       wedgeprops = {'edgecolor' : 'black','linewidth': 1,'antialiased' : True})
plt.title('MultipleLines');

ax,fig = plt.subplots(nrows = 1,ncols = 3,figsize = (12,12))

plt.subplot(1,3,1)
plt.pie(internetservice,labels = ['DSL', 'Fiber Optic','No'],autopct='%1.1f%%',startangle = 90,explode = (0.1,0,0.1),colors = colors,
       wedgeprops = {'edgecolor' : 'black','linewidth': 1,'antialiased' : True})
plt.title('InternetService');

plt.subplot(1,3,2)
plt.pie(streamingtv,labels = ['No', 'No Internet Service','Yes'],autopct='%1.1f%%',startangle = 90,explode = (0.1,0,0.1),colors = colors,
       wedgeprops = {'edgecolor' : 'black','linewidth': 1,'antialiased' : True})
plt.title('StreamingTV');

plt.subplot(1,3,3)
plt.pie(streamingmovies,labels = ['No', 'No Internet Service','Yes'],autopct='%1.1f%%',startangle = 90,explode = (0.1,0,0.1),colors = colors,
       wedgeprops = {'edgecolor' : 'black','linewidth': 1,'antialiased' : True})
plt.title('StreamingMovies');

 手动计算比例并绘图

onlinesecurity = df1[df1['Churn'] == 1]['OnlineSecurity'].value_counts()
onlinesecurity = [onlinesecurity[0] / sum(onlinesecurity) * 100,onlinesecurity[1] / sum(onlinesecurity) * 100, onlinesecurity[2] / sum(onlinesecurity) * 100] # No - No Internet Service - Yes 

onlinebackup = df1[df1['Churn'] == 1]['OnlineBackup'].value_counts()
onlinebackup = [onlinebackup[0] / sum(onlinebackup) * 100,onlinebackup[1] / sum(onlinebackup) * 100, onlinebackup[2] / sum(onlinebackup) * 100] # No - No Internet Service - Yes 

deviceprotection = df1[df1['Churn'] == 1]['DeviceProtection'].value_counts()
deviceprotection = [deviceprotection[0] / sum(deviceprotection) * 100,deviceprotection[1] / sum(deviceprotection) * 100, deviceprotection[2] / sum(deviceprotection) * 100] # No - No Internet Service - Yes 

techsupport = df1[df1['Churn'] == 1]['TechSupport'].value_counts()
techsupport = [techsupport[0] / sum(techsupport) * 100,techsupport[1] / sum(techsupport) * 100, techsupport[2] / sum(techsupport) * 100] # No - No Internet Service - Yes 

ax,fig = plt.subplots(nrows = 1,ncols = 4,figsize = (15,15))

plt.subplot(1,4,1)
plt.pie(onlinesecurity,labels = ['No', 'No Internet Service','Yes'],autopct='%1.1f%%',startangle = 90,explode = (0.1,0,0.1),colors = colors,
       wedgeprops = {'edgecolor' : 'black','linewidth': 1,'antialiased' : True})
plt.title('OnlineSecurity');

plt.subplot(1,4,2)
plt.pie(onlinebackup,labels = ['No', 'No Internet Service','Yes'],autopct='%1.1f%%',startangle = 90,explode = (0.1,0.1,0),colors = colors,
       wedgeprops = {'edgecolor' : 'black','linewidth': 1,'antialiased' : True})
plt.title('OnlineBackup');

plt.subplot(1,4,3)
plt.pie(deviceprotection,labels = ['No', 'No Internet Service','Yes'],autopct='%1.1f%%',startangle = 90,explode = (0.1,0,0.1),colors = colors,
       wedgeprops = {'edgecolor' : 'black','linewidth': 1,'antialiased' : True})
plt.title('DeviceProtection');

plt.subplot(1,4,4)
plt.pie(techsupport,labels = ['No', 'No Internet Service','Yes'],autopct='%1.1f%%',startangle = 90,explode = (0.1,0,0.1),colors = colors,
       wedgeprops = {'edgecolor' : 'black','linewidth': 1,'antialiased' : True})
plt.title('TechSupport');

 

4.4.3分组3:支付

contract = df1[df1['Churn'] == 1]['Contract'].value_counts()
contract = [contract[0] / sum(contract) * 100, contract[1] / sum(contract) * 100, contract[2] / sum(contract) * 100] # Month-to-month - One year - Two year

paperlessbilling = df1[df1['Churn'] == 1]['PaperlessBilling'].value_counts()
paperlessbilling = [paperlessbilling[0] / sum(paperlessbilling) * 100,paperlessbilling[1] / sum(paperlessbilling) * 100] # No - Yes 

paymentmethod = df1[df1['Churn'] == 1]['PaymentMethod'].value_counts()
paymentmethod = [paymentmethod[0] / sum(paymentmethod) * 100, paymentmethod[1] / sum(paymentmethod) * 100, 
            paymentmethod[2] / sum(paymentmethod) * 100, paymentmethod[3] / sum(paymentmethod) * 100] 
            # Bank Transfer (automatic) - Credit Card (automatic) - Electronic check - Mailed check

ax,fig = plt.subplots(nrows = 1,ncols = 3,figsize = (12,12))

plt.subplot(1,3,1)
plt.pie(contract,labels = ['Month-to-month','One year','Two year'],autopct='%1.1f%%',startangle = 90,explode = (0.1,0.1,0.1),colors = colors,
       wedgeprops = {'edgecolor' : 'black','linewidth': 1,'antialiased' : True})
plt.title('Contract');

plt.subplot(1,3,2)
plt.pie(paperlessbilling,labels = ['No', 'Yes'],autopct='%1.1f%%',startangle = 90,explode = (0.1,0),colors = colors,
       wedgeprops = {'edgecolor' : 'black','linewidth': 1,'antialiased' : True})
plt.title('PaperlessBilling');

plt.subplot(1,3,3)
plt.pie(paymentmethod,labels = ['Bank Transfer (automatic)','Credit Card (automatic)','Electronic check','Mailed check'],autopct='%1.1f%%',startangle = 90,explode = (0.1,0,0.1,0),colors = colors,
       wedgeprops = {'edgecolor' : 'black','linewidth': 1,'antialiased' : True})
plt.title('PaymentMethod');

4.5数值特征与正目标变量(流失案例)的关系分布

4.5.1数值特征概况

# 设置图形大小和子图布局
fig, ax = plt.subplots(nrows=1, ncols=3, figsize=(15, 5))

# 遍历数值特征,为每个特征绘制分布图
for i, feature in enumerate(numerical_features):
    # 选择子图位置
    plt.subplot(1, 3, i + 1)
    # 绘制特征的分布图,使用指定的颜色
    sns.distplot(df1[feature], color=colors[0])
    # 设置图形标题,展示特征名称
    plt.title(f'分布:{feature}')

# 显示图形
plt.show()

4.5.2数值特征(月费用、总费用)与正目标变量(流失案例)的关系分布

# 为月费用和总费用分别创建分组列
df1['MonthlyCharges_Group'] = df1['MonthlyCharges'] // 5
###
这行代码将MonthlyCharges列中的每个值除以5,然后向下取整,创建一个新的分组列MonthlyCharges_Group。
###
df1['TotalCharges_Group'] = df1['TotalCharges'] // 500

# 设置绘图区域大小
fig, ax = plt.subplots(nrows=2, ncols=1, figsize=(20, 10))

# 遍历除第一个数值特征以外的其它特征(这里假设你想排除第一个数值特征)
for i, feature in enumerate(numerical_features[1:]):
    # 选择子图
    plt.subplot(2, 1, i + 1)
    # 绘制分组柱状图,明确指定x列名和颜色,根据是否流失进行分色
    sns.countplot(x=f'{feature}_Group', data=df1, hue='Churn', palette=colors, edgecolor='black')
    # 设置图例位置
    plt.legend(['未流失', '流失'], loc='upper left')
    # 设置标题
    plt.title(f'{feature}与流失关系');

# 显示绘图
plt.tight_layout()
plt.show()

4.6针对目标变量(Churn),分析数值特征Y与类别特征X的关系(箱型图)

请根据实际情况需要自行修改X、Y

# 初始化绘制2行2列的子图,设置总体图形大小
fig, axes = plt.subplots(nrows=2, ncols=2, figsize=(15, 10))

# 遍历l1列表中的每个元素,绘制箱线图
for i, feature in enumerate(l1):
    # 选择子图位置
    ax = plt.subplot(2, 2, i + 1)
    # 绘制箱线图,对比tenure与l1列表中的特征,按照Churn进行分组
    sns.boxplot(x=feature, y='tenure', data=data, hue='Churn', palette=colors)
    # 设置图形标题
    plt.title(f'tenure 与 {feature}')

# 显示图形
plt.tight_layout()
plt.show()

4.7针对目标特征(churn),分析数值特征内部之间的关系(散点图)

# 初始化3行1列的图形布局,并设置图形大小
fig, axes = plt.subplots(nrows=3, ncols=1, figsize=(15, 15))

# 设置一个计数器,用于控制绘图的子图位置
a = 0

# 遍历数值特征的所有可能组合
for i in range(len(numerical_features)):
    for j in range(i + 1, len(numerical_features)):  # 确保j>i,避免重复和自己比较
        if a < 3:  # 保证不超过3个子图的限制
            # 在指定的子图上绘制散点图
            sns.scatterplot(x=numerical_features[i], y=numerical_features[j], data=df1, hue='Churn', palette=colors, edgecolor='black', ax=axes[a])

            axes[a].legend(['No Churn', 'Churn'], loc='upper left')#在每个散点图的子图上添加图例,'upper left'表示图例将被放置在图表的左上角。

            title = numerical_features[i] + ' vs ' + numerical_features[j]
            axes[a].set_title(title)  # 设置图形标题
            a += 1  # 更新子图索引计数器

4.8 EDA总结

5.特征工程

5.1对未显示正态分布的数值特征进行归一化

from sklearn.preprocessing import MinMaxScaler #, StandardScaler

# 实例化归一化处理器
mms = MinMaxScaler()#这行代码创建了一个MinMaxScaler的实例,并将其存储在变量mms中。

# 删除不需要的列
df1.drop(columns=['MonthlyCharges_Group', 'TotalCharges_Group'], inplace=True)

# 使用归一化处理特定的列
columns_to_scale = ['tenure', 'MonthlyCharges', 'TotalCharges']
df1[columns_to_scale] = mms.fit_transform(df1[columns_to_scale])
#fit_transform方法首先计算这些列的缩放比例,然后应用这些比例来缩放数据。

# 显示处理后的前几行数据以检查
df1.head()

5.1*对正态分布但其值与其他特征相比非常大或非常小的数值特征进行标准化

5.2计算所有特征(数值特征和类别特征)与目标特征的相关性(相关系数、热力图)

plt.figure(figsize = (20,5))
sns.heatmap(df1.corr(),cmap = colors,annot = True)

###
df1.corr()返回了df1数据集中各特征之间的相关系数矩阵,
cmap参数指定了颜色映射,用于表示不同相关系数的颜色范围,
annot参数设置为True表示在热力图上显示数值,
###
# 计算与'Churn'的相关性并排序
corr = df1.corrwith(df1['Churn']).sort_values(ascending=False).to_frame()
###
df1: 这是要计算相关性的DataFrame。在这个例子中,df1包含多个特征列,以及Churn列,这是我们要计算相关性的目标列。
df1['Churn']: 这是我们要计算相关性的目标Series。在这个例子中,它是指df1中的Churn列。
corrwith: 这是corrwith函数本身,它是pandas库中的一个内置函数,用于计算列与Series之间的相关性。
sort_values(ascending=False): 这是对相关性Series进行排序的代码。sort_values函数用于对Series进行排序,
而ascending=False参数确保了排序是按照降序进行的,即相关系数最高的特征排在最前面。
to_frame(): 这是将排序后的Series转换为一个DataFrame的代码。
to_frame函数将Series转换为DataFrame,其中包含一个列,列名为corr。
###
corr.columns = ['Correlations']  # 设置相关性列的列名

# 创建一个图形和子图对象,设置图形大小
fig, ax = plt.subplots(figsize=(5, 5))

# 绘制热力图,显示数值注释
sns.heatmap(corr, annot=True, cmap=colors, linewidths=0.4, linecolor='black', ax=ax)
###
annot=True参数表示在热力图上显示数值注释,
cmap参数指定了颜色映射,用于表示不同相关系数的颜色范围,
linewidths=0.4参数设置了热力图边框的宽度,
linecolor='black'参数设置了边框的颜色,
ax=ax参数表示将热力图绘制在之前创建的子图对象ax上。
###

# 设置图形标题
ax.set_title('与结果变量的相关性');

# 显示图形
plt.show()

 

5.2.1计算类别特征和目标特征的相关性(卡方检验、热力图)

from sklearn.feature_selection import SelectKBest
from sklearn.feature_selection import chi2,mutual_info_classif

# 提取类别特征和目标变量
features = df1.loc[:, categorical_features]

target = df1.loc[:, 'Churn']

# 使用卡方检验选择最佳特征
best_features = SelectKBest(score_func=chi2, k='all')
###
这行代码创建了一个SelectKBest类的实例,用于选择最佳特征。
score_func参数指定了用于计算特征得分的方法,这里使用的是卡方检验(chi2),
k参数指定了要选择的特征数量,这里设置为'all',表示选择所有的特征。
###

fit = best_features.fit(features, target)
###
这行代码使用best_features实例来计算特征得分,并将其存储在fit变量中。
fit对象包含了特征得分和特征选择的信息。
###

# 创建特征得分的DataFrame
featureScores = pd.DataFrame(data=fit.scores_, index=list(features.columns), columns=['Chi Squared Score'])
###
fit.scores_是特征得分的列表,
list(features.columns)是特征列的名称列表,它们被用作DataFrame的索引和列名。
###

# 设置图形大小,并绘制热力图显示特征得分
plt.subplots(figsize=(5, 5))
sns.heatmap(featureScores.sort_values(ascending=False, by='Chi Squared Score'), annot=True, cmap=colors, linewidths=0.4, linecolor='black', fmt='.2f')
plt.title('分类特征选择');
plt.show()

5.2.2计算数值特征和目标特征的相关性(ANOVA检验、热力图)

from sklearn.feature_selection import f_classif

# 提取数值特征和目标变量
features = df1.loc[:, numerical_features]
target = df1.loc[:, 'Churn']

# 利用ANOVA选择最佳特征
best_features = SelectKBest(score_func=f_classif, k='all')
###
这行代码创建了一个SelectKBest类的实例,用于选择最佳特征。
score_func参数指定了用于计算特征得分的方法,这里使用的是ANOVA(f_classif),
###

fit = best_features.fit(features, target)

# 将特征得分转换为DataFrame
featureScores = pd.DataFrame(data=fit.scores_, index=list(features.columns), columns=['ANOVA Score'])

# 创建图形对象,设置大小,并绘制热力图
plt.subplots(figsize=(5, 5))
sns.heatmap(featureScores.sort_values(ascending=False, by='ANOVA Score'), annot=True, cmap=colors, linewidths=0.4, linecolor='black', fmt='.2f')
plt.title('数值特征选择');
plt.show()

5.2.3删除和目标特征相关性小的特征

df1.drop(columns = ['PhoneService', 'gender','StreamingTV','StreamingMovies','MultipleLines','InternetService'],inplace = True)
df1.head()

5.3数据平衡(确保数据集中的正类和负类样本数量大致相等)

5.3.1使用SMOTE方法进行过采样(增加目标变量的少数类样本至多数类样本的数量)以平衡数据集

import imblearn
from collections import Counter
from imblearn.over_sampling import SMOTE
from imblearn.under_sampling import RandomUnderSampler
from imblearn.pipeline import Pipeline

# 使用SMOTE方法进行过采样以平衡数据集
over = SMOTE(sampling_strategy=1)
###
这行代码创建了一个SMOTE类的实例,用于处理不平衡数据集。
sampling_strategy=1参数表示我们希望少数类(在这个例子中是类别1)的样本数量与
多数类(在这个例子中是类别0)的样本数量相等。
###

# 提取特征变量和目标变量
f1 = df1.iloc[:, :13].values#这行代码提取了df1数据集中前13列的特征数据,并将它们转换为一个NumPy数组。
t1 = df1.iloc[:, 13].values

# 应用SMOTE算法
f1, t1 = over.fit_resample(f1, t1)
###
这行代码使用之前创建的SMOTE实例对特征变量f1和目标变量t1进行过采样。
fit_resample方法会根据SMOTE对象的sampling_strategy参数调整数据集,以确保少数类的样本数量与多数类的样本数量相等。
###

# 输出经过重采样后的目标变量分布
print(Counter(t1))
###
这行代码使用Counter函数统计了目标变量t1中每个类别的样本数量,并打印出来。
Counter是一个计数器,它可以快速计算序列中每个元素的出现次数。
###

6.建模

import seaborn as sns
from sklearn.model_selection import train_test_split, cross_val_score, GridSearchCV, RepeatedStratifiedKFold
from sklearn.metrics import confusion_matrix, roc_auc_score, roc_curve, classification_report, accuracy_score, RocCurveDisplay, precision_recall_curve
from sklearn.ensemble import RandomForestClassifier  # 示例,你可以替换成任何其他分类器

6.1划分数据集为训练集和测试集

# 假设 f1 是你的特征数据,t1 是你的目标变量
x_train, x_test, y_train, y_test = train_test_split(f1, t1, test_size=0.20, random_state=2)
###
使用train_test_split函数将特征数据f1和目标变量t1划分为训练集和测试集。
test_size=0.20表示测试集的大小为20%,
random_state=2用于设置随机种子,以确保结果的可重复性。
###

6.2定义分类器函数,用RUC_AUC得分来评价分类器性能,绘制ROC曲线

def model(classifier, x_train, y_train, x_test, y_test):
    classifier.fit(x_train, y_train)#使用训练特征和标签来训练分类器
    prediction = classifier.predict(x_test)#使用测试特征来预测标签
    cv = RepeatedStratifiedKFold(n_splits=10, n_repeats=3, random_state=1)
    ###
    这行代码创建了一个RepeatedStratifiedKFold对象,用于执行重复分层K折交叉验证。
    n_splits=10表示数据集将被分为10个折叠,
    n_repeats=3表示每个折叠将被重复3次,
    random_state=1用于设置随机种子。
    ###
    
    print("Cross Validation Score: ", '{0:.2%}'.format(cross_val_score(classifier, x_train, y_train, cv=cv, scoring='roc_auc').mean()))
    ###
    这行代码打印了分类器在交叉验证集上的ROC AUC得分。
    {0}:这是一个占位符,它会被后面的数字替换。在这个例子中,它表示我们要格式化的数字。
    :.2:这是一个格式化说明符,表示小数点后的精度。在这个例子中,它表示保留两位小数。
    %:这是一个格式化说明符,表示将数字转换为百分比形式。
    .format表示转化为字符串格式
    
    cross_val_score函数用于计算分类器的性能得分,
    scoring='roc_auc'参数指定使用ROC AUC评分。
    cv=cv参数指定了交叉验证的参数,即RepeatedStratifiedKFold对象。
    .mean()方法计算了交叉验证得分的平均值。
    ###
    
    print("ROC_AUC Score: ", '{0:.2%}'.format(roc_auc_score(y_test, prediction)))
    ###
    使用了roc_auc_score函数来计算预测标签prediction和真实标签y_test之间的ROC AUC得分。
    ###
    
    # ROC 曲线
    RocCurveDisplay.from_estimator(classifier, x_test, y_test)
    plt.title('ROC_AUC_Plot')
    plt.show()

6.3绘制混沌矩阵

#混沌矩阵
    def model_evaluation(classifier, x_test, y_test):
    # Confusion Matrix
    cm = confusion_matrix(y_test, classifier.predict(x_test))
    ###
    confusion_matrix:这是sklearn.metrics模块中的一个函数,用于计算混淆矩阵。
    classifier.predict(x_test):使用分类器对测试数据集的特征进行预测,返回预测的标签。
    cm:这是一个变量,用于存储由confusion_matrix函数返回的混淆矩阵。
    ###
    
    names = ['True Neg', 'False Pos', 'False Neg', 'True Pos']
    counts = [value for value in cm.flatten()]
    ###
    counts:这是一个列表推导式,用于创建一个列表。
    cm.flatten():将混淆矩阵cm转换为一个一维数组。
    value:这是一个循环变量,用于遍历cm.flatten()返回的一维数组中的每个元素。
    [value for value in cm.flatten()]:这将创建一个列表,包含了一维数组中所有的元素,即混淆矩阵中每个部分的计数。
    ###
    
    percentages = ['{0:.2%}'.format(value) for value in cm.flatten() / np.sum(cm)]
    ###
    percentages:这是一个列表推导式,用于创建一个列表,包含每个混淆矩阵部分占总数的百分比。
    '{0:.2%}'.format(value):这是一个字符串格式化表达式,用于将数字value格式化为百分比,并保留两位小数。
    value in cm.flatten() / np.sum(cm):这里计算了每个混淆矩阵部分的值除以混淆矩阵所有值之和,得到百分比。
    ###
    
    labels = [f'{v1}\n{v2}\n{v3}' for v1, v2, v3 in zip(names, counts, percentages)]
    ###
    labels:这是一个列表推导式,用于创建一个新的列表,每个元素是一个字符串,包含了名称、计数和百分比。
    f'{v1}\n{v2}\n{v3}':这是一个格式化字符串,用于创建一个新的字符串,其中\n表示换行符,用于分隔名称、计数和百分比。
    v1, v2, v3:这些是循环变量,用于遍历由zip(names, counts, percentages)返回的元组。
    zip(names, counts, percentages):这是一个函数,用于将多个列表(或元组)组合成一个元组列表。
    ###
    
    labels = np.asarray(labels).reshape(2, 2)
    ###
    labels:这是前面创建的列表,现在将被转换并重新赋值。
    np.asarray(labels):这是一个NumPy函数,用于将列表labels转换为NumPy数组。
    .reshape(2, 2):这是NumPy数组的方法,用于重新调整数组的形状为2行2列,以匹配混淆矩阵的形状。
    ###
    
    sns.heatmap(cm, annot=labels, cmap='Blues', fmt='', square=True)
    ###
    sns.heatmap:这是Seaborn库中的函数,用于绘制热力图。
    cm:这是前面计算得到的混淆矩阵。
    annot=labels:这个参数指定了在每个热力图单元上显示的注释。这里使用的是之前创建的labels数组,它包含了名称、计数和百分比。
    cmap='Blues':这个参数指定了颜色映射(colormap)为’Blues’,这意味着热力图的颜色将基于蓝色调。
    fmt='':这个参数指定了数字的格式化字符串。在这里设置为空字符串意味着不应用任何特定的格式化。
    square=True:这个参数确保热力图的每个单元格都是正方形。
    ###
    
    plt.ylabel('Actual label')
    plt.xlabel('Predicted label')
    plt.title('Confusion Matrix')
    plt.show()
    
    # Classification Report
    print(classification_report(y_test, classifier.predict(x_test)))

7.引入分类器建模

7.1 XGBT

from xgboost import XGBClassifier

classifier_xgb = XGBClassifier(learning_rate= 0.01,max_depth = 3,n_estimators = 1000)
###
classifier_xgb:这是一个变量,用于存储创建的XGBoost分类器实例。
XGBClassifier():这是创建XGBoost分类器实例的构造函数。
learning_rate=0.01:这是构造函数的一个参数,指定了学习率(也称为收缩率)。
较小的学习率通常会导致模型训练更慢,但有助于提高模型的泛化能力。默认值为0.1。
max_depth=3:这是构造函数的一个参数,指定了树的最大深度。
增加树的最大深度可以使模型更复杂,可能会提高对训练数据的拟合,但也可能导致过拟合。默认值为6。
n_estimators=1000:这是构造函数的一个参数,指定了要构建的决策树的数量。
增加树的数量可以提高模型的性能,但也可能导致训练时间增加。默认值为100。
###

将XGBT应用于分类器函数中 

model(classifier_xgb,x_train,y_train,x_test,y_test)

绘制混沌矩阵

model_evaluation(classifier_xgb,x_test,y_test)

7.2 LGBM

from lightgbm import LGBMClassifier

classifier_lgbm = LGBMClassifier(learning_rate= 0.01,max_depth = 3,n_estimators = 1000)

model(classifier_lgbm,x_train,y_train,x_test,y_test)
model_evaluation(classifier_lgbm,x_test,y_test)

 

7.3随机森林random forest

from sklearn.ensemble import RandomForestClassifier

classifier_rf = RandomForestClassifier(max_depth = 4,random_state = 0)
###
classifier_rf:这是一个变量,用于存储创建的随机森林分类器实例。
RandomForestClassifier():这是sklearn.ensemble模块中的一个类,用于实现随机森林算法。
随机森林是一种集成学习方法,它通过构建多个决策树并进行投票来提高预测的准确性和稳定性。
max_depth = 4:这是RandomForestClassifier构造函数的一个参数,用于指定决策树的最大深度。
在这里,max_depth被设置为4,这意味着每个决策树的最大深度为4层。限制树的深度有助于防止过拟合。
random_state = 0:这是RandomForestClassifier构造函数的一个参数,用于设置随机数生成器的种子。
设置random_state的目的是为了确保结果的可重复性,即每次运行代码时都能得到相同的随机森林模型。
在这里,random_state被设置为0。
###

model(classifier_rf,x_train,y_train,x_test,y_test)
model_evaluation(classifier_rf,x_test,y_test)

 

7.4决策树decision tree

from sklearn.tree import DecisionTreeClassifier

classifier_dt = DecisionTreeClassifier(random_state = 1000,max_depth = 4,min_samples_leaf = 1)
###
classifier_dt:这是一个变量,用于存储创建的决策树分类器实例。
DecisionTreeClassifier():这是sklearn.tree模块中的一个类,用于实现决策树算法。
决策树是一种用于分类和回归的非参数监督学习方法。
random_state = 1000:这是DecisionTreeClassifier构造函数的一个参数,用于设置随机数生成器的种子。
设置random_state的目的是为了确保结果的可重复性,即每次运行代码时都能得到相同的决策树模型。
在这里,random_state被设置为1000。
max_depth = 4:这是DecisionTreeClassifier构造函数的一个参数,用于指定决策树的最大深度。
在这里,max_depth被设置为4,这意味着决策树的最大深度为4层。限制树的深度有助于防止过拟合。
min_samples_leaf = 1:这是DecisionTreeClassifier构造函数的一个参数,用于指定叶子节点所需的最小样本数。
在这里,min_samples_leaf被设置为1,这意味着叶子节点可以只有一个样本。
增加min_samples_leaf的值可以防止模型在训练数据上过度拟合。
###

model(classifier_dt,x_train,y_train,x_test,y_test)
model_evaluation(classifier_dt,x_test,y_test)

 

8.数据分析结论

  • 6
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值