17机器学习开放基础课程--Python数据可视化分析

Python 数据可视化分析

数据集

import warnings
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
sns.set()
warnings.filterwarnings('ignore')

df = pd.read_csv(
    'https://labfile.oss.aliyuncs.com/courses/1283/telecom_churn.csv')
df.head()

最后一个数据列 Churn 离网率 是我们的目标特征,它是布尔变量,其中 True 表示公司最终丢失了此客户,False 表示客户被保留。稍后,将构建基于其他特征预测 Churn 特征的模型。

单变量可视化

单变量(univariate)分析一次只关注一个变量。当我们独立地分析一个特征时,通常最关心的是该特征值的分布情况。
数量特征
数量特征(quantitative feature)的值为有序数值。这些值可能是离散的,例如整数,也可能是连续的,例如实数。
直方图和密度图
直方图依照相等的间隔将值分组为柱,它的形状可能包含了数据分布的一些信息,如高斯分布、指数分布等。当分布总体呈现规律性,但有个别异常值时,你可以通过直方图辨认出来。当你使用的机器学习方法预设了某一特定分布类型(通常是高斯分布)时,知道特征值的分布是非常重要的。

features = ['Total day minutes', 'Total intl calls']
df[features].hist(figsize=(10, 4))

上图表明,变量 Total day minutes 每日通话时长 呈高斯分布,而 Total intl calls 总国际呼叫数 显著右倾(它右侧的尾巴更长)。
密度图(density plots),也叫核密度图( kernel density estimate,KDE)是理解数值变量分布的另一个方法。它可以看成是直方图平滑( smoothed )的版本。相比直方图,它的主要优势是不依赖于柱的尺寸,更加清晰。

df[features].plot(kind='density', subplots=True, layout=(1, 2),
                  sharex=False, figsize=(10, 4), legend=False, title=features)

还可以使用 seaborn 的 distplot() 方法观测数值变量的分布。例如,Total day minutes 每日通话时长 的分布。默认情况下,该方法将同时显示直方图和密度图。

sns.distplot(df['Total intl calls'])

上图中直方图的柱形高度已进行归一化处理,表示的是密度而不是样本数。
箱型图
箱形图的主要组成部分是箱子(box),须(whisker)和一些单独的数据点(离群值),分别简单介绍如下:

image.png

sns.boxplot(x='Total intl calls', data=df)

上图表明,在该数据集中,大量的国际呼叫是相当少见的。
提琴形图
提琴形图和箱形图的区别是,提琴形图聚焦于平滑后的整体分布,而箱形图显示了单独样本的特定统计数据。
下图左侧是箱形图,右侧是提琴形图。

_, axes = plt.subplots(1, 2, sharey=True, figsize=(6, 4))
sns.boxplot(data=df['Total intl calls'], ax=axes[0])
sns.violinplot(data=df['Total intl calls'], ax=axes[1])

数据描述

df[features].describe()

describe() 的输出基本上是自解释性的,25%,50% 和 75% 是相应的百分数 percentiles。
类别特征和二元特征
类别特征(categorical features take)反映了样本的某个定性属性,它具有固定数目的值,每个值将一个观测数据分配到相应的组,这些组称为类别(category)。如果类别变量的值具有顺序,称为有序(ordinal)类别变量。
二元(binary)特征是类别特征的特例,其可能值有 2 个。
频率表
查看一下目标变量 Churn 离网率 的分布情况。首先,使用 value_counts() 方法得到一张频率表。

df['Churn'].value_counts()

该数据集的 Churn 有 2850 个属于 False(Churn==0),有 483 个属于 True(Churn==1),数据集中忠实客户(Churn==0)和不忠实客户(Churn==1)的比例并不相等。我们将在以后的文章中看到,这种数据不平衡的情况会导致建立的分类模型存在一定的问题。在这种情况下,构建分类模型可能需要加重对「少数数据(在这里是 Churn==1)分类错误」这一情况的惩罚。
条形图
频率表的图形化表示是条形图。

_, axes = plt.subplots(nrows=1, ncols=2, figsize=(12, 4))

sns.countplot(x='Churn', data=df, ax=axes[0])
sns.countplot(x='Customer service calls', data=df, ax=axes[1])

条形图和直方图的区别如下:
直方图适合查看数值变量的分布,而条形图用于查看类别特征。
直方图的 X 轴是数值;条形图的 X 轴可能是任何类型,如数字、字符串、布尔值。
直方图的 X 轴是一个笛卡尔坐标轴;条形图的顺序则没有事先定义。

上左图清晰地表明了目标变量的失衡性。上右图则表明大部分客户最多打了 2-3 个客服电话就解决了他们的问题。不过,既然想要预测少数数据的分类(Churn==1),我们可能对少数不满意的客户的表现更感兴趣。所以让我们尝试一下更有趣的可视化方法:多变量可视化,看能否对预测有所帮助。

多变量可视化

多变量(multivariate)图形可以在单张图像中查看两个以上变量的联系,和单变量图形一样,可视化的类型取决于将要分析的变量的类型。
相关矩阵
相关矩阵可揭示数据集中的数值变量的相关性。

# 丢弃非数值变量
numerical = list(set(df.columns) -
                 set(['State', 'International plan', 'Voice mail plan',
                      'Area code', 'Churn', 'Customer service calls']))
# 计算和绘图
corr_matrix = df[numerical].corr()
sns.heatmap(corr_matrix)

上图中,Total day charge 日话费总额 是直接基于 Total day minutes 电话的分钟数 计算得到,它被称为因变量。除了 Total day charge 外,还有 3 个因变量:Total eve charge,Total night charge,Total intl charge。这 4 个因变量并不贡献任何额外信息,我们直接去除。

numerical = list(set(numerical) - set(['Total day charge', 'Total eve charge', 
                                       'Total night charge', 'Total intl charge']))

散点图
散点图(scatter plot)将两个数值变量的值显示为二维空间中的笛卡尔坐标(Cartesian coordinate)。

plt.scatter(df['Total day minutes'], df['Total night minutes'])

我们得到了两个正态分布变量的散点图,看起来这两个变量并不相关,因为上图的形状和轴是对齐的。
seaborn 库的 jointplot() 方法在绘制散点图的同时会绘制两张直方图

sns.jointplot(x='Total day minutes', y='Total night minutes',
              data=df, kind='scatter')

jointplot() 方法还可以绘制平滑过的散点直方图。

sns.jointplot('Total day minutes', 'Total night minutes', data=df,
              kind="kde", color="g")

上图基本上就是核密度图的双变量版本。
散点图矩阵
在某些情形下,我们可能想要绘制如下所示的散点图矩阵(scatterplot matrix)。它的对角线包含变量的分布,并且每对变量的散点图填充了矩阵的其余部分。

%config InlineBackend.figure_format = 'png'
sns.pairplot(df[numerical])

数量和类别
为了让图形更有趣一点,可以尝试从数值和类别特征的相互作用中得到预测 Churn 的新信息,更具体地,让我们看看输入变量和目标变量 Churn 的关系。

sns.lmplot('Total day minutes', 'Total night minutes',
           data=df, hue='Churn', fit_reg=False)

看起来不忠实客户偏向右上角,也就是倾向于在白天和夜间打更多电话的客户。当然,这不是非常明显,我们也不会基于这一图形下任何确定性的结论。
现在,创建箱形图,以可视化忠实客户(Churn=0)和离网客户(Churn=1)这两个互斥分组中数值变量分布的统计数据。

# 有时我们可以将有序变量作为数值变量分析
numerical.append('Customer service calls')

fig, axes = plt.subplots(nrows=3, ncols=4, figsize=(10, 7))
for idx, feat in enumerate(numerical):
    ax = axes[int(idx / 4), idx % 4]
    sns.boxplot(x='Churn', y=feat, data=df, ax=ax)
    ax.set_xlabel('')
    ax.set_ylabel(feat)
fig.tight_layout()

上面的图表表明,两组之间分歧最大的分布是这三个变量:Total day minutes 日通话分钟数、Customer service calls 客服呼叫数、Number vmail messages 语音邮件数。在后续的课程中,我们将学习如何使用随机森林(Random Forest)或梯度提升(Gradient Boosting)来判定特征对分类的重要性,届时可以清晰地看到,前两个特征对于离网预测模型而言确实非常重要。
创建箱型图和提琴形图,查看忠实客户和不忠实客户的日通话分钟数。

_, axes = plt.subplots(1, 2, sharey=True, figsize=(10, 4))

sns.boxplot(x='Churn', y='Total day minutes', data=df, ax=axes[0])
sns.violinplot(x='Churn', y='Total day minutes', data=df, ax=axes[1])

上图表明,不忠实客户倾向于打更多的电话。
我们还可以发现一个有趣的信息:平均而言,离网客户是通讯服务更活跃的用户。或许是他们对话费不满意,所以预防离网的一个可能措施是降低通话费。当然,公司需要进行额外的经济分析,以查明这样做是否真的有利。
当想要一次性分析两个类别维度下的数量变量时,可以用 seaborn 库的 * catplot()* 函数。例如,在同一图形中可视化 Total day minutes 日通话分钟数 和两个类别变量(Churn 和 Customer service calls)的相互作用。

sns.catplot(x='Churn', y='Total day minutes', col='Customer service calls',
            data=df[df['Customer service calls'] < 8], kind="box",
            col_wrap=4, height=3, aspect=.8)

上图表明,从第 4 次客服呼叫开始,Total day minutes 日通话分钟数 可能不再是客户离网(Churn==1)的主要因素。也许,除了我们之前猜测的话费原因,还有其他问题导致客户对服务不满意,这可能会导致日通话分钟数更少。
类别与类别
变量 Customer service calls 客服呼叫数 的重复值很多,因此,既可以看成数值变量,也可以看成有序类别变量。之前已通过计数图(count plot)查看过它的分布了,现在我们感兴趣的是这一有序特征和目标变量 Churn 离网率 之间的关系。

sns.countplot(x='Customer service calls', hue='Churn', data=df)

上图表明,呼叫客服达到 4 次以上后,离网率显著增加了。
使用 countplot() 方法查看 Churn 离网率 和二元特征 International plan 国际套餐、Voice mail plan 语音邮件套餐 的关系。

_, axes = plt.subplots(1, 2, sharey=True, figsize=(10, 4))

sns.countplot(x='International plan', hue='Churn', data=df, ax=axes[0])
sns.countplot(x='Voice mail plan', hue='Churn', data=df, ax=axes[1])

上图表明,开通国际套餐后,离网率会高很多,即 International plan 是否开通国际套餐 是一个重要的特征。我们在 Vocie mail plan 语音邮件套餐 特征上没有观察到类似的效果。
交叉表
除了使用图形进行类别分析之外,还可以使用统计学的传统工具:交叉表(cross tabulation),即使用表格形式表示多个类别变量的频率分布。通过它可以查看某一列或某一行以了解某个变量在另一变量的作用下的分布情况。
通过交叉表查看 Churn 离网率 和类别变量 State 州 的关系。

pd.crosstab(df['State'], df['Churn']).T

上表显示,State 州 有 51 个不同的值,并且每个州只有 3 到 17 个客户抛弃了运营商。通过 groupby() 方法计算每个州的离网率,由高到低排列。

df.groupby(['State'])['Churn'].agg(
    [np.mean]).sort_values(by='mean', ascending=False).T

上表显示,新泽西和加利福尼亚的离网率超过了 25%,夏威夷和阿拉斯加的离网率则不到 6%。然而,这些结论是基于极少的样本得出的,可能仅适用于这一特定数据集,不太具有泛用性。

全局数据集可视化

上面我们一直在研究数据集的不同方面(facet),通过猜测有趣的特征并一次选择少量特征进行可视化。如果我们想一次性显示所有特征并仍然能够解释生成的可视化,该怎么办?
降维
为了从整体上查看一个数据集,需要在不损失很多数据信息的前提下,降低用于可视化的维度。这一任务被称为降维(dimensionality reduction)。降维是一个无监督学习(unsupervised learning)问题,因为它需要在不借助任何监督输入(如标签)的前提下,从数据自身得到新的低维特征。
主成分分析(Principal Component Analysis, PCA)是一个著名的降维方法,但主成分分析的局限性在于,它是线性(linear)算法,这意味着对数据有某些特定的限制。
t-SNE
它的基本思路很简单:为高维特征空间在二维平面(或三维平面)上寻找一个投影,使得在原本的 n 维空间中相距很远的数据点在二维平面上同样相距较远,而原本相近的点在平面上仍然相近。

from sklearn.manifold import TSNE
from sklearn.preprocessing import StandardScaler

去除 State 州 和 Churn 离网率 变量,然后用 pandas.Series.map() 方法将二元特征的「Yes」/「No」转换成数值。 :

X = df.drop(['Churn', 'State'], axis=1)
X['International plan'] = X['International plan'].map({'Yes': 1, 'No': 0})
X['Voice mail plan'] = X['Voice mail plan'].map({'Yes': 1, 'No': 0})

使用 StandardScaler() 方法来完成归一化数据,即从每个变量中减去均值,然后除以标准差。

scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)

构建 t-SNE

tsne = TSNE(random_state=17)
tsne_repr = tsne.fit_transform(X_scaled)

以图形的方式可视化

plt.scatter(tsne_repr[:, 0], tsne_repr[:, 1], alpha=.5)

根据离网情况给 t-SNE 表示加上色彩(蓝色表示忠实用户,黄色表示不忠实用户),形成离网情况散点图。

plt.scatter(tsne_repr[:, 0], tsne_repr[:, 1],
            c=df['Churn'].map({False: 'blue', True: 'orange'}), alpha=.5)

离网客户集中在低维特征空间的一小部分区域。为了更好地理解这一图像,可以使用剩下的两个二元特征,即 International plan 国际套餐 和 Voice mail plan 语音邮件套餐 给图像着色,蓝色代表二元特征的值为 Yes,黄色代表二元特征的值为 No。

_, axes = plt.subplots(1, 2, sharey=True, figsize=(12, 5))

for i, name in enumerate(['International plan', 'Voice mail plan']):
    axes[i].scatter(tsne_repr[:, 0], tsne_repr[:, 1],
                    c=df[name].map({'Yes': 'orange', 'No': 'blue'}), alpha=.5)
    axes[i].set_title(name)

通过上面 3 张图,我们就可以更直观的分析客户的离网原因了。
t-SNE 的缺陷:
计算复杂度高。如果你有大量样本,你应该使用 Multicore-TSNE。
随机数种子的不同会导致图形大不相同,这给解释带来了困难。通常而言,你不应该基于这些图像做出任何决定性的结论,因为它可能和单纯的猜测差不多。当然,t-SNE 图像中的某些发现可能会启发一个想法,这个想法可以通过更全面深入的研究得到确认。

心血管疾病数据探索分析

初步数据分析

import pandas as pd
import numpy as np
import seaborn as sns
import matplotlib
import matplotlib.pyplot as plt
import matplotlib.ticker
from matplotlib import rcParams
import warnings
warnings.filterwarnings('ignore')

sns.set()
sns.set_context(
    "notebook",
    font_scale=1.5,
    rc={
        "figure.figsize": (11, 8),
        "axes.titlesize": 18
    }
)

rcParams['figure.figsize'] = 11, 8

df = pd.read_csv(
    'https://labfile.oss.aliyuncs.com/courses/1283/mlbootcamp5_train.csv', sep=';')
print('Dataset size: ', df.shape)
df.head()

数据均为医学检查时收集,主要包含 3 种类别的特征:
Objective Feature: 基础事实数据。
Examination Feature: 体检结果。
Subjective Feature: 患者给出的主观信息。

探索数据值分布情况。

df_uniques = pd.melt(frame=df, value_vars=['gender', 'cholesterol',
                                           'gluc', 'smoke', 'alco',
                                           'active', 'cardio'])
df_uniques = pd.DataFrame(df_uniques.groupby(['variable',
                                              'value'])['value'].count()) \
    .sort_index(level=[0, 1]) \
    .rename(columns={'value': 'count'}) \
    .reset_index()

sns.catplot(x='variable', y='count', hue='value',
            data=df_uniques, kind='bar', height=12)

按目标值分割数据集,这样往往可以通过绘图结果快速找出相对重要的特征。

df_uniques = pd.melt(frame=df, value_vars=['gender', 'cholesterol',
                                           'gluc', 'smoke', 'alco',
                                           'active'], id_vars=['cardio'])
df_uniques = pd.DataFrame(df_uniques.groupby(['variable', 'value',
                                              'cardio'])['value'].count()) \
    .sort_index(level=[0, 1]) \
    .rename(columns={'value': 'count'}) \
    .reset_index()

sns.catplot(x='variable', y='count', hue='value',
            col='cardio', data=df_uniques, kind='bar', height=9)

可以看到胆固醇和葡萄糖水平对目标变量影响明显较大。

进一步观察

数据集中有多少男性和女性?由于 gender 特征没有说明男女,你需要通过分析身高计算得出。

df.groupby('gender')['height'].mean()

数据集中男性和女性,哪个群体饮酒的频次更高?

df.groupby('gender')['alco'].mean()

数据集中男性和女性吸烟者所占百分比的差值是多少?

df.groupby('gender')['smoke'].mean()

数据集中吸烟者和非吸烟者的年龄中位数之间的差值(以月计)近似是多少?你需要尝试确定出数据集中 age 合理的表示单位(1 年为 365.25 天)。

df.groupby('smoke')['age'].median() / 365.25
(df[df['smoke'] == 0]['age'].median() -
 df[df['smoke'] == 1]['age'].median()) / 365.25 * 12

风险量表图

欧洲心脏病学会的网站上给出了 SCORE scale 量表。它可以用于计算未来 10 年心血管疾病死亡的风险。

image.png

右上角的矩形,也就是 60 到 65 岁的吸烟男性的子集。其中,矩形的左下角看到一个值 9,在右上角看到 47。量表意味着对于收缩压(纵坐标)低于 120 (正常血压)的性别年龄组的人来说,心血管疾病的风险估计比收缩压为 [160, 180)[160,180) 的患者(高血压)低 5 倍(47/9)。
接下来,让我们结合量表,并利用挑战数据集进行计算。这里需要注意的是,量表中胆固醇(Cholesterol)水平和挑战数据单位不太一样,我们使用对应关系为:4 mmol/l → 1, 5-7 mmol/l → 2, 8 mmol/l → 3。
计算 [60,65) 年龄区间下,较健康人群(胆固醇类别 1,收缩压低于 120)与高风险人群(胆固醇类别为 3,收缩压 [160,180))各自心血管病患所占比例。并最终求得二者比例的近似倍数。

df['age_years'] = (df['age'] / 365.25).round().astype('int')
df['age_years'].max()

选择 [60,65) 年龄区间:

smoking_old_men = df[(df['gender'] == 2) & (df['age_years'] >= 60)
                     & (df['age_years'] < 65) & (df['smoke'] == 1)]
smoking_old_men[(smoking_old_men['cholesterol'] == 1) &
                (smoking_old_men['ap_hi'] < 120)]['cardio'].mean()

胆固醇类别为 1,收缩压低于 120,心血管疾病患者的比例为 26%。

smoking_old_men[(smoking_old_men['cholesterol'] == 3) &
                (smoking_old_men['ap_hi'] >= 160) &
                (smoking_old_men['ap_hi'] < 180)]['cardio'].mean()

胆固醇类别为 3,收缩压 [160,180),心血管疾病患者的比例为 86%。
所以,挑战数据给出的比例大约是 0.86/0.26≈3 倍,而量表给出的是 5 倍。当然,这依赖与给定年龄组中的病人比例。

BMI 指数分析

创建一个新的特征 BMI,BMI 为身高体重指数,其可以反映体重的标准情况,计算公式为:


image.png

正常 BMI 指数一般在 18.5 到 25 之间。
女性的平均 BMI 指数高于男性。 健康和不饮酒男性中,BMI 比健康不饮酒女性更接近正常值。

df['BMI'] = df['weight'] / (df['height'] / 100) ** 2
df['BMI'].median()
df.groupby('gender')['BMI'].median()
df.groupby(['gender', 'alco', 'cardio'])['BMI'].median().to_frame()

数据清洗

请按照以下列举的项目,过滤掉数据中统计有误的部分:
血压特征中,舒张压高于收缩压的样本。
身高特征中,低于 2.5% 分位数的样本。
身高特征中,高于 97.5% 分位数的样本。
体重特征中,低于 2.5% 分位数的样本。
体重特征中,高于 97.5% 分位数的样本。

filtered_df = df[(df['ap_lo'] <= df['ap_hi']) &
                 (df['height'] >= df['height'].quantile(0.025)) &
                 (df['height'] <= df['height'].quantile(0.975)) &
                 (df['weight'] >= df['weight'].quantile(0.025)) &
                 (df['weight'] <= df['weight'].quantile(0.975))]

清洗掉的数据占原数据总量的近似百分比?

1 - filtered_df.shape[0] / df.shape[0]

数据可视化分析

使用 heatmap() 绘制特征之间的皮尔逊相关性系数矩阵。

# 计算相关性系数矩阵
df = filtered_df.copy()

corr = df.corr(method='pearson')

# 创建一个 Mask 来隐藏相关矩阵的上三角形
mask = np.zeros_like(corr, dtype=np.bool)
mask[np.triu_indices_from(mask)] = True

# 绘制图像
f, ax = plt.subplots(figsize=(12, 9))
sns.heatmap(corr, mask=mask, vmax=1, center=0, annot=True, fmt='.1f',
            square=True, linewidths=.5, cbar_kws={"shrink": .5})

Height, Smoke与性别的相关性更强

男女身高分布

绘制身高和性别之间的小提琴图 violinplot()

df_melt = pd.melt(frame=df, value_vars=['height'], id_vars=['gender'])

plt.figure(figsize=(12, 10))
ax = sns.violinplot(
    x='variable',
    y='value',
    hue='gender',
    palette="muted",
    split=True,
    data=df_melt,
    scale='count',
    scale_hue=False
)

绘制身高和性别之间的核密度图 kdeplot
通过核密度图可以更清楚地看到性别之间的差异,但却无法得到每个性别对应的具体人数。

sns.FacetGrid(df, hue="gender", height=12) \
   .map(sns.kdeplot, "height").add_legend()

大多数情况下,皮尔逊相关性指数可以看出特征之间的相关程度。不过,这里我们进一步绘制 * Spearman's rank correlation coefficient* 斯皮尔曼等级相关系数对应的图像。它利用单调方程评价两个统计变量的相关性,是用于衡量两个变量的依赖性的非参数指标。
使用 heatmap() 绘制特征之间的斯皮尔曼等级相关系数矩阵。

# 计算斯皮尔曼等级相关系数
corr = df[['id', 'age', 'height', 'weight',
           'ap_hi', 'ap_lo', 'cholesterol',
           'gluc']].corr(method='spearman')

# 创建一个 Mask 来隐藏相关矩阵的上三角形
mask = np.zeros_like(corr, dtype=np.bool)
mask[np.triu_indices_from(mask)] = True

f, ax = plt.subplots(figsize=(12, 10))

# 绘制图像
sns.heatmap(corr, mask=mask, vmax=1, center=0, annot=True, fmt='.2f',
            square=True, linewidths=.5, cbar_kws={"shrink": .5})

Ap_hi, Ap_lo具有最强的 Spearman 相关性

年龄可视化

请使用 countplot() 绘制年龄分布计数图,横坐标为年龄,纵坐标为对应的人群数量。

sns.countplot(x="age_years", hue='cardio', data=df)

55岁以下,,心血管疾病患者人数首次超过无心血管疾病患者人数

你可能感兴趣的:(17机器学习开放基础课程--Python数据可视化分析)