数据可视化及初步探索

5291 字 · 650 阅读 · 2023 年 06 月 10 日

本文已更新,你可以访问 AI By Doing 以获得更好的阅读体验。
本篇文章需 特别授权许可,内容版权归作者所有,未经授权,禁止转载。

介绍

数据可视化是数据分析过程中必不可少的手段,它几乎贯穿于整个数据分析全程。这篇文章将介绍数据可视化中静态图形的绘制,并学习 Python 中两个十分出色的数据可视化模块 Matplotlib 和 Seaborn。最后,你会利用所学的可视化知识,对经典数据分析 iris 数据集进行初步的数据探索。

知识点

  • 可视化与数据挖掘的步骤
  • Matplotlib 绘制图形
  • Matplotlib 添加图形属性
  • 等高线的绘制
  • 泊松分布和正态分布的绘制
  • Seaborn 密度估计图的绘制
  • 单变量变量图的绘制
  • 热力图的绘制

可视化与数据挖掘

在数据挖掘过程中,可视化是一项重要的工作。它一般开始于拿到数据之后,直到得到完整的数据分析结论。也就是说,数据可视化贯穿于整个数据分析过程之中,而不是在某个阶段特定的一项操作。

  • 拿到数据之后:数据可视化可以让我们快速了解数据及分布。
  • 数据预处理中:数据可视化能让我们发现数据规律或异常,实施相应预处理手段。
  • 数据建模时:数据可视化能追逐模型指标变化。
  • 数据分析报告:数据可视化是分析报告必不可少的内容,一图抵千言。

数据可视化,简单来讲就是画图,但是画图却没有想象中的那么简单。数据可视化中往往会涉及到各种类型、各种样式、静态或动态等不同图形的绘制。除此之外,数据可视化的难点在于「不同情形下的不同可视化手段」,也就是什么情况下该画哪种图形。

这篇文章中,我们重点了解数据可视化的基本方法,以及在拿到数据之后,探索数据集时的常见操作。关于更多的可视化内容,我们将会在之后的实验中慢慢抛出。

Matplotlib 模块介绍

在日常的工作和学习过程中,我们都会遇到需要针对数据进行绘图的时候。谈起数据绘图,你应该最先想到的是 Excel。Excel 操作起来相对简单,提供的图形样式也可以满足大部分人日常绘图的需要。

但是,如果你处于工程、数据科学相关专业领域,使用 Excel 绘图就显得捉襟见肘了。首先,Excel 默认 提供的图形样式有限,其次是自定义程度不高。

很多时候,我们会使用到 Matlab 提供的绘图模块,又或者使用 Origin 进行绘图。这些软件固然都很不错,但均为商业软件,使用时需要获得授权。

Matplotlib 是支持 Python 语言的开源绘图库,因为其支持丰富的绘图类型、简单的绘图方式以及完善的接口文档,深受 Python 工程师、科研学者、数据工程师等各类人士的喜欢。Matplotlib 拥有着十分活跃的社区以及稳定的版本迭代,当我们使用 Python 进行数据分析并执行可视化时,Matplotlib 无疑是得心应手的工具之一。

Matplotlib 绘制线型图

折线图是在序列中表达数据变量的统计图表,通常用于发现数据集中的趋势,其暗示数据的变化往往包含时间或其他特定因素。

下面我们用 Matplotlib 来简单绘制一下折线图:

# 导入 Matplotlib 绘图模块
from matplotlib import pyplot as plt
%matplotlib inline

y = [1, 2, 3, 2, 1, 2, 3, 4, 5, 6, 5, 4, 3, 2, 1]

plt.plot(y)

第二行代码是从 Matplotlib 中导入了 pyplot 绘图模块,并将其简称为 pltpyplot 模块是 Matplotlib 最核心的模块,几乎所有样式的 2D 图形都是经过该模块绘制出来的,这里简称为 plt 是约定俗成的。

plt.plot()pyplot 模块下面的直线绘制(折线类)方法类。代码中,它取出 x 数据集中的内容,将其按大小打印到图中,并以直线连接每个点。

通常,plot() 接受两个参数。一个代表横坐标的数值,另一个代表纵坐标的数值。但是,当你只传入一个参数 y 的时候,它会默认代表纵坐标的值,而横坐标的值会从 0n-1ny 数据长度。

# 运行下面代码,输出一致的图形
x = [i for i in range(len(y))]  # 生成与 y 等长度 x
plt.plot(x, y)

Matplotlib 绘图时,当点非常密集时,折线就会变成光滑的曲线。例如,我们绘制正弦函数的曲线。

import numpy as np

# 在 -2PI 和 2PI 之间等间距生成 1000 个值,也就是 X 坐标
X = np.linspace(-2*np.pi, 2*np.pi, 1000)
# 计算 y 坐标
y = np.sin(X)

plt.plot(X, y)

你还可以通过改变产生数据的数量来观察图像的变化。

X = np.linspace(-5*np.pi, 5*np.pi, 10)
y = np.sin(X)

plt.plot(X, y)

上面从同一个函数关系中产生数据集,但是绘制的图像却相差甚远。原因是,第二个数据集中数量点太少,获得的信息不足描述 $y$ 和 $x$ 之间的关系。这现象反映一个这样的道理,当你拥有更大的数据集,你能更容易地通过可视化手段发现藏在数据背后的规律。

Matplotlib 添加图形属性

当你学会线型图绘制之后,可能想到改变图形的属性。例如,更改图形的尺寸、添加图例等。Matplotlib 提供的面向对象 API 使用起来非常简单,但是下面不再直接使用 plt.plot,而是定义一个绘图对象 fig, axes = plt.subplots()

# 生成示例数据
x = np.linspace(0, 10, 20)
y = x * x + 2

fig, axes = plt.subplots()
axes.plot(x, y, 'r')

上面的绘图代码中,你可能会对 figureaxes 产生疑问。Matplotlib 的 API 设计的非常符合常理,在这里,figure 相当于绘画用的画板,而 axes 则相当于铺在画板上的画布。我们将图像绘制在画布上,于是就有了 plotset_xlabel 等操作。

那么,如果想要调节画布尺寸和显示大小,只需要向 plt.subplots 中添加参数即可。

# 通过 figsize 调节尺寸, dpi 调节显示精度
fig, axes = plt.subplots(figsize=(16, 9), dpi=50)

axes.plot(x, y, 'r')

当然,你还可能需要绘制图名称、坐标轴名称、图例等信息。

# 绘制包含图标题、坐标轴标题以及图例的图形
fig, axes = plt.subplots()

axes.set_xlabel('x label')
axes.set_ylabel('y label')
axes.set_title('title')

axes.plot(x, x**2)
axes.plot(x, x**3)
axes.legend(["y = x**2", "y = x**3"], loc=2)

图例中的 loc 参数标记图例位置,1,2,3,4 依次代表:右上角、左上角、左下角,右下角;0 代表自适应。

同样,我们可以绘制子图。向 plt.subplots 中添加参数 nrowsncols 参数即可。

fig, axes = plt.subplots(nrows=1, ncols=2)  # 子图为 1 行,2 列

axes[0].plot(x, y, 'r')
axes[1].plot(x, y, 'b')

Matplotlib 其他常见图形

除了线型图,Matplotlib 还支持绘制散点图、柱状图等其他常见图形。例如:

# 绘制散点图、梯步图、条形图、面积图
n = np.array([0, 1, 2, 3, 4, 5])

fig, axes = plt.subplots(1, 4, figsize=(16, 5))

axes[0].scatter(x, x + 0.25*np.random.randn(len(x)))
axes[0].set_title("scatter")

axes[1].step(n, n**2, lw=2)
axes[1].set_title("step")

axes[2].bar(n, n**2, align="center", width=0.5, alpha=0.5)
axes[2].set_title("bar")

axes[3].fill_between(x, x**2, x**3, color="green", alpha=0.5)
axes[3].set_title("fill_between")

绘制直方图

n = np.random.randn(100000)
fig, axes = plt.subplots(1, 2, figsize=(12, 4))

axes[0].hist(n)
axes[0].set_title("Default histogram")
axes[0].set_xlim((min(n), max(n)))

axes[1].hist(n, cumulative=True, bins=50)
axes[1].set_title("Cumulative detailed histogram")
axes[1].set_xlim((min(n), max(n)))

绘制雷达图:

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

ax = fig.add_axes([0.0, 0.0, .6, .6], polar=True)
t = np.linspace(0, 2 * np.pi, 100)

ax.plot(t, t, color='blue', lw=3)

Matplotlib 绘制复杂图形

Matplotlib 可以绘制出很多复杂图形,当然这需要根据实际需求确定,绘图的代码也会复杂很多。例如,我们这里尝试绘制一个等高线图。

alpha = 0.7
phi_ext = 2 * np.pi * 0.5


def flux_qubit_potential(phi_m, phi_p):
    return 2 + alpha - 2 * np.cos(phi_p) * np.cos(phi_m) - alpha * np.cos(phi_ext - 2*phi_p)


phi_m = np.linspace(0, 2*np.pi, 100)
phi_p = np.linspace(0, 2*np.pi, 100)
X, Y = np.meshgrid(phi_p, phi_m)
Z = flux_qubit_potential(X, Y).T

fig, ax = plt.subplots()

cnt = ax.contour(Z, cmap=plt.cm.RdBu, vmin=abs(Z).min(),
                 vmax=abs(Z).max(), extent=[0, 1, 0, 1])

Matplotlib 绘制风格图形

xkcd 是一个非常有趣的漫画网站,其作品大多充斥着浪漫、讽刺、数学等元素。

Matplotlib 官方提供了模仿 xkcd 漫画风格的绘图样式,你可以通过 plt.xkcd() 来绘制 xkcd 风格的图像。

with plt.xkcd():
    plt.hist(np.sin(np.linspace(0, 10)))
    plt.title('Sample Data')

Seaborn 模块介绍

Matplotlib 应该是基于 Python 语言最优秀的绘图库了,但是它也有一个十分令人头疼的问题,那就是太过于复杂了。3000 多页的官方文档,上千个方法以及数万个参数,属于典型的你可以用它做任何事,但又无从下手。

尤其是,当你像通过 Matplotlib 调出非常漂亮的效果时,往往会伤透脑筋,非常麻烦。

Seaborn 基于 Matplotlib 核心库进行了更高级的 API 封装,可以让你轻松地画出更漂亮的图形。而 Seaborn 的漂亮主要体现在配色更加舒服、以及图形元素的样式更加细腻。

列举一下 Seaborn 的几个特点:

  1. 内置数个经过优化的样式效果。

  2. 增加调色板工具,可以很方便地为数据搭配颜色。

  3. 单变量和双变量分布绘图更为简单,可用于对数据子集相互比较。

  4. 对独立变量和相关变量进行回归拟合和可视化更加便捷。

  5. 对数据矩阵进行可视化,并使用聚类算法进行分析。

  6. 基于时间序列的绘制和统计功能,更加灵活的不确定度估计。

  7. 基于网格绘制出更加复杂的图像集合。

除此之外, Seaborn 对 Matplotlib 和 Pandas 的数据结构高度兼容,非常适合作为数据挖掘过程中的可视化工具。

使用 Seaborn 优化 Matplotlib 图形

我们可以通过 Seaborn 对 Matplotlib 绘图完成样式快速优化。以前面的折线图代码为例,在 plt.plot(y) 这行代码前添加上sns.set(style='xx'),指定一个预设样式,能让画出来的图形更加精美。

style 有 5 个参数可选:

  • darkgrid 黑色网格
  • whitegrid 白色网格
  • white 白色背景
  • ticks 加上刻度的白色背景
import seaborn as sns

y = [1, 2, 3, 2, 1, 2, 3, 4, 5, 6, 5, 4, 3, 2, 1]

# 将画图风格设置为 darkgrid
sns.set(style='darkgrid')

plt.plot(y)
plt.show()

可以发现,相比于 Matplotlib 默认的纯白色背景,Seaborn 默认的浅灰色网格背景看起来的确要细腻舒适一些。而柱状图的色调、坐标轴的字体大小也都有一些变化。Seaborn 支持的预设样式有 darkgrid, whitegrid, dark, white, ticks,关于它们的效果,你可以亲自动手试一试

使用 Seaborn 绘制图形

Seaborn 支持常见的五类不同样式的图形绘制,你可以通过下方的思维导图了解。

接下来,我们使用 Seaborn API 来绘制常见的图形。

直方图

我们可以通过 seaborn.distplot 绘制单变量的直方图。下面,我们通过构造两个不同分布的数据集,并以画图展示。

seaborn.distplot(a, bins=None, hist=True, kde=True, rug=False, fit=None, hist_kws=None, kde_kws=None, rug_kws=None, fit_kws=None, color=None, vertical=False, norm_hist=False, axlabel=None, label=None, ax=None)

首先,我们来构造 正态分布 的数据集。

x = np.random.normal(size=500)

sns.distplot(x)

泊松分布公式

$$ f(k; \lambda)=\frac{\lambda^k e^{-\lambda}}{k!}$$

你还可以根据 泊松分布 公式,构造参数为 1 , 大小为 1000 的泊松分布数据集,并画出其分布图。

poisson = np.random.poisson(1, 300)

sns.distplot(poisson)

你可以发现,seaborn.distplot 不仅绘制了直方图,还自动添加了变化趋势曲线。

组合图

seaborn.jointplot 可以用来绘制单变量和双变量的组合图。

seaborn.jointplot(x, y, data=None, kind='scatter', stat_func=None, color=None, height=6, ratio=5, space=0.2, dropna=True, xlim=None, ylim=None, joint_kws=None, marginal_kws=None, annot_kws=None, **kwargs)

下面我们利用多元正态分布的公式和 NumPy 提供的函数来构造数据集,然后用 joinplot() 可视化两个变量之间的关系。

# 根据公式,构造数据集
mean = [0, 0]
cov = [[1, 0], [0, 10]] # 协方差矩阵
x, y = np.random.multivariate_normal(mean, cov, 300).T

# 画出 x, y 的联合分布图
sns.jointplot(x=x, y=y)

核密度估计图

核密度估计是在概率论中用来估计未知的密度函数,属于非参数检验方法之一。通过核密度估计图可以比较直观的看出数据样本本身的分布特征。

seaborn.kdeplot(data, data2=None, shade=False, vertical=False, kernel='gau', bw='scott', gridsize=100, cut=3, clip=None, legend=True, cumulative=False, shade_lowest=True, cbar=False, cbar_ax=None, cbar_kws=None, ax=None, **kwargs)
  • data : 一维数组
  • shade : 是否保留阴影面积
  • cut : 数轴极限数值的多少
  • cumulative : 是否绘制累积分布

kdeplot 主要是用于绘制单变量或变量的核密度估计图。先看看单变量的效果。

# shade 参数表示是否需要曲线阴影面积
sns.kdeplot(x, shade=True)

我们可以将三个变量的密度图在同一个图画出来,以便比较它们之间分布的差别。

sns.kdeplot(x, label='x')
sns.kdeplot(y, label='y')
sns.kdeplot(poisson, label='poisson')

$x$ 和 $ poisson $ 变量的联合密度图:

# x 和 poisson 变量的联合密度图
sns.jointplot(x=x, y=poisson, kind='kde')

$x$ 和 $y$ 的联合概率密度图:

sns.jointplot(x=x, y=y, kind='kde')

计数图

计数图 countplot 是一类可以对数据集分类的直方图,使用图形显示每个分类中的数量,同时也可以比较类别间数量差。

seaborn.countplot(x=None, y=None, hue=None, data=None, order=None, hue_order=None, orient=None, color=None, palette=None, saturation=0.75, dodge=True, ax=None, **kwargs)
  • x, y : 变量的名字
  • hue : 分类变量的名字
  • data : 数据集,DataFrame 类型

下面,我们利用一个练习数据集 planets 中的 year 特征,分类计算每年的数据是多少。

planets = sns.load_dataset("planets")

planets.head(5)

为了显示效果,我们选取了 2005 年至今的年份计数,看数据集相应年份有多少数据点。

count_year = sns.countplot(x="year", data=planets[planets['year'] > 2005])

Seaborn 绘制子图

你可能会想使用 Seaborn 绘制子图,那么应该怎么做了。实际上,因为 Seaborn 来源于 Matplotlib,它们之间的界限非常模糊,很多都可以关联使用。例如,你可以类比 Matplotlib 绘制子图的方法来绘制 Seaborn 子图。

例如,我们把上面的直方图和和密度估计图绘制成横向拼接的子图。

fig, axes = plt.subplots(ncols=2, nrows=1, figsize=(12, 5))

# 使用 ax 参数指定 axes 对象
sns.distplot(x, ax=axes[0])
sns.kdeplot(x, ax=axes[1])

鸢尾花数据集探索

鸢尾花数据集是数据挖掘领域一个非常经典的分类数据集。接下来,我们就以这个数据集为基础,进行可视化探索。

在绘图之前,我们先熟悉一下 iris 鸢尾花数据集。数据集总共 150 行,由 5 列组成。分别代表:萼片长度、萼片宽度、花瓣长度、花瓣宽度、花的类别。其中,前四列均为数值型数据,最后一列花的分类为三种,分别是:Iris Setosa、Iris Versicolour、Iris Virginica。

导入这个数据集很简单,这里我们通过 Seaborn 内置的数据集类直接导入。

iris_data = sns.load_dataset('iris')  # 导入 iris_data 数据集
iris_data.head()

单变量探索

单变量探索就是,挖掘每个特征数据本身的规律。例如,自身分布、均值、方差、众数、中位数、百分比等。

iris_data['species'].value_counts()

利用 sns.distplot 查看各个特征自身的分布:

sns.distplot(iris_data['sepal_length'], color='red')
sns.distplot(iris_data['sepal_width'], color='blue')
sns.distplot(iris_data['petal_length'], color='green')
sns.distplot(iris_data['petal_width'], color='black')

从图中可以直观看出,四个特征属性的中位数、众数等、取值范围等统计信息。

多变量探索

我们可以利用散点图、折线图、二维分布图等,探索两两变量之间的关系。下面,以 sepal_lengthsepal_width 属性为例,探索两个变量的关系。

sns.jointplot(x=iris_data['sepal_length'], y=iris_data['sepal_width'])

你还可以使用 pairplot,画出整个数据集特征的两两关系。

sns.pairplot(iris_data)

这里,我们可以定义一些参数,将图画得更好看一些。

sns.pairplot(iris_data, hue="species", diag_kind="kde")

seaborn.lmplot() 是一个非常有用的方法,它会在绘制二维散点图时,自动完成回归拟合。

sns.lmplot(x='sepal_length', y='sepal_width', hue='species', data=iris_data)

sns.lmplot() 里的 x, y 分别代表横纵坐标的列名。hue= 代表按照 species,即花的类别分类显示,而 data= 自然就是关联到数据集了。

由于 Seaborn 对 Pandas 的 DataFrame 数据格式高度兼容,所以一切变得非常简单。绘制出来的图也自动带有图例,并进行了线型回归拟合,还给出了置信区间。

seaborn.heatmap() 主要是用于绘制热力图,也就类似于色彩矩阵。还记得我们在「数据清洁与预处理」章节中介绍过的皮尔逊相关系数吗?用 seaborn.heatmap() 绘制出来,可比看数据要直观很多。

sns.heatmap(iris_data.corr(), square=True, annot=True)  # corr() 函数计算皮尔逊相关系数

小结

本节实验对 Matplotlib 和 Seaborn 的使用进行了学习,掌握了常用图形的绘制方法,已经能满足数据分析过程中的基本需要。除此之外,我们使用 Seaborn 对经典的 iris 数据集进行了探索,对数据的分布有了直观的了解。由于数据可视化充分融入在数据分析中,所以通过一个实验来学习不太现实。不同图形的绘制,以及不同可视化方法的不同用法,将在后续的实验中逐步介绍。

本篇文章需 特别授权许可,内容版权归作者所有,未经授权,禁止转载。

系列文章