Python 数据可视化分析


介绍

在机器学习领域中,可视化是十分重要的。在开始一项新任务时,通过可视化手段探索数据能更好地帮助人们把握数据的要点。在分析模型表现和模型报告的结果时,可视化能使分析显得更加生动鲜明。有时候,为了理解复杂的模型,我们还可以将高维空间映射为视觉上更直观的二维或三维图形。

总而言之,可视化是一个相对快捷的从数据中挖掘信息的手段。本文将使用 Pandas、Matplotlib、seaborn 等流行的库,带你上手可视化。

知识点

  • 单变量可视化的常用方法
  • 多变量可视化的常用方法
  • t-SNE

数据集

首先使用 import 载入相关依赖。

In [1]:
import warnings
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
sns.set()
warnings.filterwarnings('ignore')

在第一篇文章中,我们使用的是某电信运营商的客户离网数据集,本次实验仍旧使用这个数据集。

In [2]:
df = pd.read_csv(
    '../../data/telecom_churn.csv')
In [3]:
df.head()
Out[3]:
State Account length Area code International plan Voice mail plan Number vmail messages Total day minutes Total day calls Total day charge Total eve minutes Total eve calls Total eve charge Total night minutes Total night calls Total night charge Total intl minutes Total intl calls Total intl charge Customer service calls Churn
0 KS 128 415 No Yes 25 265.1 110 45.07 197.4 99 16.78 244.7 91 11.01 10.0 3 2.70 1 False
1 OH 107 415 No Yes 26 161.6 123 27.47 195.5 103 16.62 254.4 103 11.45 13.7 3 3.70 1 False
2 NJ 137 415 No No 0 243.4 114 41.38 121.2 110 10.30 162.6 104 7.32 12.2 5 3.29 0 False
3 OH 84 408 Yes No 0 299.4 71 50.90 61.9 88 5.26 196.9 89 8.86 6.6 7 1.78 2 False
4 OK 75 415 Yes No 0 166.7 113 28.34 148.3 122 12.61 186.9 121 8.41 10.1 3 2.73 3 False

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

单变量可视化

单变量(univariate)分析一次只关注一个变量。当我们独立地分析一个特征时,通常最关心的是该特征值的分布情况。下面考虑不同统计类型的变量,以及相应的可视化工具。

数量特征

数量特征(quantitative feature)的值为有序数值。这些值可能是离散的,例如整数,也可能是连续的,例如实数。

直方图和密度图

直方图依照相等的间隔将值分组为柱,它的形状可能包含了数据分布的一些信息,如高斯分布、指数分布等。当分布总体呈现规律性,但有个别异常值时,你可以通过直方图辨认出来。当你使用的机器学习方法预设了某一特定分布类型(通常是高斯分布)时,知道特征值的分布是非常重要的。

最简单的查看数值变量分布的方法是使用 DataFrame 的 方法绘制直方图。

In [4]:
features = ['Total day minutes', 'Total intl calls']
df[features].hist(figsize=(10, 4))
Out[4]:
array([[<matplotlib.axes._subplots.AxesSubplot object at 0x7f59f62f72b0>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x7f59f62bcb00>]],
      dtype=object)

上图表明,变量 Total day minutes 每日通话时长 呈高斯分布,而 Total intl calls 总国际呼叫数 显著右倾(它右侧的尾巴更长)。

密度图(density plots),也叫核密度图( ,KDE)是理解数值变量分布的另一个方法。它可以看成是直方图平滑( )的版本。相比直方图,它的主要优势是不依赖于柱的尺寸,更加清晰。

让我们为上面两个变量创建密度图。

In [5]:
df[features].plot(kind='density', subplots=True, layout=(1, 2),
                  sharex=False, figsize=(10, 4), legend=False, title=features)
Out[5]:
array([[<matplotlib.axes._subplots.AxesSubplot object at 0x7f59f399e0f0>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x7f59f395a9e8>]],
      dtype=object)

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

In [6]:
sns.distplot(df['Total intl calls'])
Out[6]:
<matplotlib.axes._subplots.AxesSubplot at 0x7f59f695ce80>

上图中直方图的柱形高度已进行归一化处理,表示的是密度而不是样本数。

箱型图

箱形图的主要组成部分是箱子(box),须(whisker)和一些单独的数据点(离群值),分别简单介绍如下:

  • 箱子显示了分布的四分位距,它的长度由 $25th \, (\text{Q1,下四分位数})$ 和 $75th \, (\text{Q3,上四分位数})$ 决定,箱中的水平线表示中位数 ($50\%$)。
  • 须是从箱子处延伸出来的线,它们表示数据点的总体散布,具体而言,是位于区间 $(\text{Q1} - 1.5 \cdot \text{IQR}, \text{Q3} + 1.5 \cdot \text{IQR})$的数据点,其中 $\text{IQR} = \text{Q3} - \text{Q1}$,也就是四分位距。
  • 离群值是须之外的数据点,它们作为单独的数据点,沿着中轴绘制。

使用 seaborn 的 boxplot() 方法绘制箱形图。

In [7]:
sns.boxplot(x='Total intl calls', data=df)
Out[7]:
<matplotlib.axes._subplots.AxesSubplot at 0x7f59e9c3cc50>

上图表明,在该数据集中,大量的国际呼叫是相当少见的。

提琴形图

我们最后考虑的分布图形是提琴形图(violin plot)。提琴形图和箱形图的区别是,提琴形图聚焦于平滑后的整体分布,而箱形图显示了单独样本的特定统计数据。

使用 violinplot() 方法绘制提琴形图。下图左侧是箱形图,右侧是提琴形图。

In [8]:
_, 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])
Out[8]:
<matplotlib.axes._subplots.AxesSubplot at 0x7f59e9b6ba20>

数据描述

除图形工具外,还可以使用 DataFrame 的 方法来获取分布的精确数值统计。

In [9]:
df[features].describe()
Out[9]:
Total day minutes Total intl calls
count 3333.000000 3333.000000
mean 179.775098 4.479448
std 54.467389 2.461214
min 0.000000 0.000000
25% 143.700000 3.000000
50% 179.400000 4.000000
75% 216.400000 6.000000
max 350.800000 20.000000

describe() 的输出基本上是自解释性的,25%,50% 和 75% 是相应的百分数

类别特征和二元特征

类别特征(categorical features take)反映了样本的某个定性属性,它具有固定数目的值,每个值将一个观测数据分配到相应的组,这些组称为类别(category)。如果类别变量的值具有顺序,称为有序(ordinal)类别变量。

二元(binary)特征是类别特征的特例,其可能值有 2 个。

频率表

让我们查看一下目标变量 Churn 离网率 的分布情况。首先,使用 方法得到一张频率表。

In [10]:
df['Churn'].value_counts()
Out[10]:
False    2850
True      483
Name: Churn, dtype: int64

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

条形图

频率表的图形化表示是条形图。创建条形图最简单的方法是使用 seaborn 的 函数。让我们来画出两个分类变量的分布。

In [11]:
_, 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])
Out[11]:
<matplotlib.axes._subplots.AxesSubplot at 0x7f59e9a97400>

条形图和直方图的区别如下:

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

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

多变量可视化

多变量(multivariate)图形可以在单张图像中查看两个以上变量的联系,和单变量图形一样,可视化的类型取决于将要分析的变量的类型。

先来看看数量变量之间的相互作用。

相关矩阵

相关矩阵可揭示数据集中的数值变量的相关性。这一信息很重要,因为有一些机器学习算法(比如,线性回归和逻辑回归)不能很好地处理高度相关的输入变量。

首先,我们使用 DataFrame 的 方法计算出每对特征间的相关性。接着,我们将所得的相关矩阵(correlation matrix)传给 seaborn 的 方法,该方法根据提供的数值,渲染出一个基于色彩编码的矩阵。

In [12]:
# 丢弃非数值变量
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)
Out[12]:
<matplotlib.axes._subplots.AxesSubplot at 0x7f59e97352e8>

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

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

散点图

散点图(scatter plot)将两个数值变量的值显示为二维空间中的笛卡尔坐标(Cartesian coordinate)。通过 matplotlib 库的 方法可以绘制散点图。

In [14]:
plt.scatter(df['Total day minutes'], df['Total night minutes'])
Out[14]:
<matplotlib.collections.PathCollection at 0x7f59e86a3e48>

我们得到了两个正态分布变量的散点图,看起来这两个变量并不相关,因为上图的形状和轴是对齐的。

seaborn 库的 方法在绘制散点图的同时会绘制两张直方图,某些情形下它们可能会更有用。

In [15]:
sns.jointplot(x='Total day minutes', y='Total night minutes',
              data=df, kind='scatter')
Out[15]:
<seaborn.axisgrid.JointGrid at 0x7f59e8658dd8>

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

In [16]:
sns.jointplot('Total day minutes', 'Total night minutes', data=df,
              kind="kde", color="g")
Out[16]:
<seaborn.axisgrid.JointGrid at 0x7f59e852f5c0>

上图基本上就是之前讨论过的核密度图的双变量版本。

散点图矩阵

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

In [17]:
%config InlineBackend.figure_format = 'png'
sns.pairplot(df[numerical])
Out[17]:
<seaborn.axisgrid.PairGrid at 0x7f59e8389240>