《利用Python进行数据分析·第2版》第9章 绘图和可视化

2022-09-29 13:08:14

信息可视化(也叫绘图)是数据分析中最重要的工作之一。它可能是探索过程的一部分,例如,帮助我们找出异常值、必要的数据转换、得出有关模型的 idea 等。另外,做一个可交互的数据可视化也许是工作的最终目标。Python 有许多库进行静态或动态的数据可视化,但我这里重要关注于 matplotlib(http://matplotlib.org/)和基于它的库。

matplotlib 是一个用于创建出版质量图表的桌面绘图包(主要是 2D 方面)。该项目是由 John Hunter 于 2002 年启动的,其目的是为 Python 构建一个 MATLAB 式的绘图接口。matplotlib 和 IPython 社区进行合作,简化了从 IPython shell(包括现在的 Jupyter notebook)进行交互式绘图。matplotlib 支持各种操作系统上许多不同的 GUI 后端,而且还能将图片导出为各种常见的矢量(vector)和光栅(raster)图:PDF、SVG、JPG、PNG、BMP、GIF 等。除了几张,本书中的大部分图都是用它生成的。

随着时间的发展,matplotlib 衍生出了多个数据可视化的工具集,它们使用 matplotlib 作为底层。其中之一是 seaborn(http://seaborn.pydata.org/),本章后面会学习它。

学习本章代码案例的最简单方法是在 Jupyter notebook 进行交互式绘图。在 Jupyter notebook 中执行下面的语句:

%matplotlib notebook

9.1 matplotlib API 入门

matplotlib 的通常引入约定是:

In [11]:import matplotlib.pyplotas plt

在 Jupyter 中运行 %matplotlib notebook(或在 IPython 中运行 %matplotlib),就可以创建一个简单的图形。如果一切设置正确,会看到图 9-1:

In [12]:import numpyas np

In [13]: data = np.arange(10)

In [14]: data
Out[14]: array([0,1,2,3,4,5,6,7,8,9])

In [15]: plt.plot(data)

虽然 seaborn 这样的库和 pandas 的内置绘图函数能够处理许多普通的绘图任务,但如果需要自定义一些高级功能的话就必须学习 matplotlib API。

笔记:虽然本书没有详细地讨论 matplotlib 的各种功能,但足以将你引入门。matplotlib 的示例库和文档是学习高级特性的最好资源。

Figure 和 Subplot

matplotlib 的图像都位于 Figure 对象中。你可以用 plt.figure 创建一个新的 Figure:

In [16]: fig = plt.figure()

如果用的是 IPython,这时会弹出一个空窗口,但在 Jupyter 中,必须再输入更多命令才能看到。plt.figure 有一些选项,特别是 figsize,它用于确保当图片保存到磁盘时具有一定的大小和纵横比。

不能通过空 Figure 绘图。必须用 add_subplot 创建一个或多个 subplot 才行:

In [17]: ax1 = fig.add_subplot(2,2,1)

这条代码的意思是:图像应该是 2×2 的(即最多 4 张图),且当前选中的是 4 个 subplot 中的第一个(编号从 1 开始)。如果再把后面两个 subplot 也创建出来,最终得到的图像如图 9-2 所示:

In [18]: ax2 = fig.add_subplot(2,2,2)

In [19]: ax3 = fig.add_subplot(2,2,3)

提示:使用 Jupyter notebook 有一点不同,即每个小窗重新执行后,图形会被重置。因此,对于复杂的图形,,你必须将所有的绘图命令存在一个小窗里。

这里,我们运行同一个小窗里的所有命令:

fig = plt.figure()
ax1 = fig.add_subplot(2,2,1)
ax2 = fig.add_subplot(2,2,2)
ax3 = fig.add_subplot(2,2,3)

如果这时执行一条绘图命令(如 plt.plot([1.5, 3.5, -2, 1.6])),matplotlib 就会在最后一个用过的 subplot(如果没有则创建一个)上进行绘制,隐藏创建 figure 和 subplot 的过程。因此,如果我们执行下列命令,你就会得到如图 9-3 所示的结果:

In [20]: plt.plot(np.random.randn(50).cumsum(),'k--')

"k--" 是一个线型选项,用于告诉 matplotlib 绘制黑色虚线图。上面那些由 fig.add_subplot 所返回的对象是 AxesSubplot 对象,直接调用它们的实例方法就可以在其它空着的格子里面画图了,如图 9-4 所示:

In [21]: _ = ax1.hist(np.random.randn(100), bins=20, color='k', alpha=0.3)

In [22]: ax2.scatter(np.arange(30), np.arange(30) +3 * np.random.randn(30))

你可以在 matplotlib 的文档中找到各种图表类型。

创建包含 subplot 网格的 figure 是一个非常常见的任务,matplotlib 有一个更为方便的方法 plt.subplots,它可以创建一个新的 Figure,并返回一个含有已创建的 subplot 对象的 NumPy 数组:

In [24]: fig, axes = plt.subplots(2,3)

In [25]: axes
Out[25]: 
array([[<matplotlib.axes._subplots.AxesSubplot object at0x7fb626374048>,
        <matplotlib.axes._subplots.AxesSubplot object at0x7fb62625db00>,
        <matplotlib.axes._subplots.AxesSubplot object at0x7fb6262f6c88>],
       [<matplotlib.axes._subplots.AxesSubplot object at0x7fb6261a36a0>,
        <matplotlib.axes._subplots.AxesSubplot object at0x7fb626181860>,
        <matplotlib.axes._subplots.AxesSubplot object at0x7fb6260fd4e0>]], dtype
=object)

这是非常实用的,因为可以轻松地对 axes 数组进行索引,就好像是一个二维数组一样,例如 axes[0,1]。你还可以通过 sharex 和 sharey 指定 subplot 应该具有相同的 X 轴或 Y 轴。在比较相同范围的数据时,这也是非常实用的,否则,matplotlib 会自动缩放各图表的界限。有关该方法的更多信息,请参见表 9-1。

调整 subplot 周围的间距

默认情况下,matplotlib 会在 subplot 外围留下一定的边距,并在 subplot 之间留下一定的间距。间距跟图像的高度和宽度有关,因此,如果你调整了图像大小(不管是编程还是手工),间距也会自动调整。利用 Figure 的 subplots_adjust 方法可以轻而易举地修改间距,此外,它也是个顶级函数:

subplots_adjust(left=None, bottom=None, right=None, top=None,
                wspace=None, hspace=None)

wspace 和 hspace 用于控制宽度和高度的百分比,可以用作 subplot 之间的间距。下面是一个简单的例子,其中我将间距收缩到了 0(如图 9-5 所示):

fig, axes = plt.subplots(2,2, sharex=True, sharey=True)for iin range(2):for jin range(2):
        axes[i, j].hist(np.random.randn(500), bins=50, color='k', alpha=0.5)
plt.subplots_adjust(wspace=0, hspace=0)

不难看出,其中的轴标签重叠了。matplotlib 不会检查标签是否重叠,所以对于这种情况,你只能自己设定刻度位置和刻度标签。后面几节将会详细介绍该内容。

颜色、标记和线型

matplotlib 的 plot 函数接受一组 X 和 Y 坐标,还可以接受一个表示颜色和线型的字符串缩写。例如,要根据 x 和 y 绘制绿色虚线,你可以执行如下代码:

ax.plot(x, y,'g--')

这种在一个字符串中指定颜色和线型的方式非常方便。在实际中,如果你是用代码绘图,你可能不想通过处理字符串来获得想要的格式。通过下面这种更为明确的方式也能得到同样的效果:

ax.plot(x, y, linestyle='--', color='g')

常用的颜色可以使用颜色缩写,你也可以指定颜色码(例如,'#CECECE')。你可以通过查看 plot 的文档字符串查看所有线型的合集(在 IPython 和 Jupyter 中使用 plot?)。

线图可以使用标记强调数据点。因为 matplotlib 可以创建连续线图,在点之间进行插值,因此有时可能不太容易看出真实数据点的位置。标记也可以放到格式字符串中,但标记类型和线型必须放在颜色后面(见图 9-6):

In [30]:from numpy.randomimport randn

In [31]: plt.plot(randn(30).cumsum(),'ko--')

还可以将其写成更为明确的形式:

plot(randn(30).cumsum(), color='k', linestyle='dashed', marker='o')

在线型图中,非实际数据点默认是按线性方式插值的。可以通过 drawstyle 选项修改(见图 9-7):

In [33]: data = np.random.randn(30).cumsum()

In [34]: plt.plot(data,'k--', label='Default')
Out[34]: [<matplotlib.lines.Line2D at0x7fb624d86160>]

In [35]: plt.plot(data,'k-', drawstyle='steps-post', label='steps-post')
Out[35]: [<matplotlib.lines.Line2D at0x7fb624d869e8>]

In [36]: plt.legend(loc='best')

你可能注意到运行上面代码时有输出 <matplotlib.lines.Line2D at ...>。matplotlib 会返回引用了新添加的子组件的对象。大多数时候,你可以放心地忽略这些输出。这里,因为我们传递了 label 参数到 plot,我们可以创建一个 plot 图例,指明每条使用 plt.legend 的线。

笔记:你必须调用 plt.legend(或使用 ax.legend,如果引用了轴的话)来创建图例,无论你绘图时是否传递 label 标签选项。

刻度、标签和图例

对于大多数的图表装饰项,其主要实现方式有二:使用过程型的 pyplot 接口(例如,matplotlib.pyplot)以及更为面向对象的原生 matplotlib API。

pyplot 接口的设计目的就是交互式使用,含有诸如 xlim、xticks 和 xticklabels 之类的方法。它们分别控制图表的范围、刻度位置、刻度标签等。其使用方式有以下两种:

  • 调用时不带参数,则返回当前的参数值(例如,plt.xlim() 返回当前的 X 轴绘图范围)。
  • 调用时带参数,则设置参数值(例如,plt.xlim([0,10]) 会将 X 轴的范围设置为 0 到 10)。

所有这些方法都是对当前或最近创建的 AxesSubplot 起作用的。它们各自对应 subplot 对象上的两个方法,以 xlim 为例,就是 ax.get_xlim 和 ax.set_xlim。我更喜欢使用 subplot 的实例方法(因为我喜欢明确的事情,而且在处理多个 subplot 时这样也更清楚一些)。当然你完全可以选择自己觉得方便的那个。

设置标题、轴标签、刻度以及刻度标签

为了说明自定义轴,我将创建一个简单的图像并绘制一段随机漫步(如图 9-8 所示):

In [37]: fig = plt.figure()

In [38]: ax = fig.add_subplot(1,1,1)

In [39]: ax.plot(np.random.randn(1000).cumsum())

要改变 x 轴刻度,最简单的办法是使用 set_xticks 和 set_xticklabels。前者告诉 matplotlib 要将刻度放在数据范围中的哪些位置,默认情况下,这些位置也就是刻度标签。但我们可以通过 set_xticklabels 将任何其他的值用作标签:

In [40]: ticks = ax.set_xticks([0,250,500,750,1000])

In [41]: labels = ax.set_xticklabels(['one','two','three','four','five'],
   ....:                             rotation=30, fontsize='small')

rotation 选项设定 x 刻度标签倾斜 30 度。最后,再用 set_xlabel 为 X 轴设置一个名称,并用 set_title 设置一个标题(见图 9-9 的结果):

In [42]: ax.set_title('My first matplotlib plot')
Out[42]: <matplotlib.text.Text at0x7fb624d055f8>

In [43]: ax.set_xlabel('Stages')

Y 轴的修改方式与此类似,只需将上述代码中的 x 替换为 y 即可。轴的类有集合方法,可以批量设定绘图选项。前面的例子,也可以写为:

props = {'title':'My first matplotlib plot','xlabel':'Stages'
}
ax.set(**props)

添加图例

图例(legend)是另一种用于标识图表元素的重要工具。添加图例的方式有多种。最简单的是在添加 subplot 的时候传入 label 参数:

In [44]:from numpy.randomimport randn

In [45]: fig = plt.figure(); ax = fig.add_subplot(1,1,1)

In [46]: ax.plot(randn(1000).cumsum(),'k', label='one')
Out[46]: [<matplotlib.lines.Line2D at0x7fb624bdf860>]

In [47]: ax.plot(randn(1000).cumsum(),'k--', label='two')
Out[47]: [<matplotlib.lines.Line2D at0x7fb624be90f0>]

In [48]: ax.plot(randn(1000).cumsum(),'k.', label='three')
Out[48]: [<matplotlib.lines.Line2D at0x7fb624be9160>]

在此之后,你可以调用 ax.legend() 或 plt.legend() 来自动创建图例(结果见图 9-10):

In [49]: ax.legend(loc='best')

legend 方法有几个其它的 loc 位置参数选项。请查看文档字符串(使用 ax.legend?)。

loc 告诉 matplotlib 要将图例放在哪。如果你不是吹毛求疵的话,"best" 是不错的选择,因为它会选择最不碍事的位置。要从图例中去除一个或多个元素,不传入 label 或传入 label='nolegend'即可。(中文第一版这里把 best 错写成了 beat)

注解以及在 Subplot 上绘图

除标准的绘图类型,你可能还希望绘制一些子集的注解,可能是文本、箭头或其他图形等。注解和文字可以通过 text、arrow 和 annotate 函数进行添加。text 可以将文本绘制在图表的指定坐标 (x,y),还可以加上一些自定义格式:

ax.text(x, y,'Hello world!',
        family='monospace', fontsize=10)

注解中可以既含有文本也含有箭头。例如,我们根据最近的标准普尔 500 指数价格(来自 Yahoo!Finance)绘制一张曲线图,并标出 2008 年到 2009 年金融危机期间的一些重要日期。你可以在 Jupyter notebook 的一个小窗中试验这段代码(图 9-11 是结果):

from datetimeimport datetime

fig = plt.figure()
ax = fig.add_subplot(1,1,1)

data = pd.read_csv('examples/spx.csv', index_col=0, parse_dates=True)
spx = data['SPX']

spx.plot(ax=ax, style='k-')

crisis_data = [
    (datetime(2007,10,11),'Peak of bull market'),
    (datetime(2008,3,12),'Bear Stearns Fails'),
    (datetime(2008,9,15),'Lehman Bankruptcy')
]for date, labelin crisis_data:
    ax.annotate(label, xy=(date, spx.asof(date) +75),
                xytext=(date, spx.asof(date) +225),
                arrowprops=dict(facecolor='black', headwidth=4, width=2,
                                headlength=4),
                horizontalalignment='left', verticalalignment='top')# Zoom in on 2007-2010
ax.set_xlim(['1/1/2007','1/1/2011'])
ax.set_ylim([600,1800])

ax.set_title('Important dates in the 2008-2009 financial crisis')

这张图中有几个重要的点要强调:ax.annotate 方法可以在指定的 x 和 y 坐标轴绘制标签。我们使用 set_xlim 和 set_ylim 人工设定起始和结束边界,而不使用 matplotlib 的默认方法。最后,用 ax.set_title 添加图标标题。

更多有关注解的示例,请访问 matplotlib 的在线示例库。

图形的绘制要麻烦一些。matplotlib 有一些表示常见图形的对象。这些对象被称为块(patch)。其中有些(如 Rectangle 和 Circle),可以在 matplotlib.pyplot 中找到,但完整集合位于 matplotlib.patches。

要在图表中添加一个图形,你需要创建一个块对象 shp,然后通过 ax.add_patch(shp) 将其添加到 subplot 中(如图 9-12 所示):

fig = plt.figure()
ax = fig.add_subplot(1,1,1)

rect = plt.Rectangle((0.2,0.75),0.4,0.15, color='k', alpha=0.3)
circ = plt.Circle((0.7,0.2),0.15, color='b', alpha=0.3)
pgon = plt.Polygon([[0.15,0.15], [0.35,0.4], [0.2,0.6]],
                   color='g', alpha=0.5)

ax.add_patch(rect)
ax.add_patch(circ)
ax.add_patch(pgon)

如果查看许多常见图表对象的具体实现代码,你就会发现它们其实就是由块 patch 组装而成的。

将图表保存到文件

利用 plt.savefig 可以将当前图表保存到文件。该方法相当于 Figure 对象的实例方法 savefig。例如,要将图表保存为 SVG 文件,你只需输入:

plt.savefig('figpath.svg')

文件类型是通过文件扩展名推断出来的。因此,如果你使用的是. pdf,就会得到一个 PDF 文件。我在发布图片时最常用到两个重要的选项是 dpi(控制 “每英寸点数” 分辨率)和 bbox_inches(可以剪除当前图表周围的空白部分)。要得到一张带有最小白边且分辨率为 400DPI 的 PNG 图片,你可以:

plt.savefig('figpath.png', dpi=400, bbox_inches='tight')

savefig 并非一定要写入磁盘,也可以写入任何文件型的对象,比如 BytesIO:

from ioimport BytesIO
buffer = BytesIO()
plt.savefig(buffer)
plot_data = buffer.getvalue()

表 9-2 列出了 savefig 的其它选项。

matplotlib 配置

matplotlib 自带一些配色方案,以及为生成出版质量的图片而设定的默认配置信息。幸运的是,几乎所有默认行为都能通过一组全局参数进行自定义,它们可以管理图像大小、subplot 边距、配色方案、字体大小、网格类型等。一种 Python 编程方式配置系统的方法是使用 rc 方法。例如,要将全局的图像默认大小设置为 10×10,你可以执行:

plt.rc('figure', figsize=(10,10))

rc 的第一个参数是希望自定义的对象,如'figure'、'axes'、'xtick'、'ytick'、'grid'、'legend'等。其后可以跟上一系列的关键字参数。一个简单的办法是将这些选项写成一个字典:

font_options = {'family' :'monospace','weight' :'bold','size'   :'small'}
plt.rc('font', **font_options)

要了解全部的自定义选项,请查阅 matplotlib 的配置文件 matplotlibrc(位于 matplotlib/mpl-data 目录中)。如果对该文件进行了自定义,并将其放在你自己的. matplotlibrc 目录中,则每次使用 matplotlib 时就会加载该文件。

下一节,我们会看到,seaborn 包有若干内置的绘图主题或类型,它们使用了 matplotlib 的内部配置。

9.2 使用 pandas 和 seaborn 绘图

matplotlib 实际上是一种比较低级的工具。要绘制一张图表,你组装一些基本组件就行:数据展示(即图表类型:线型图、柱状图、盒形图、散布图、等值线图等)、图例、标题、刻度标签以及其他注解型信息。

在 pandas 中,我们有多列数据,还有行和列标签。pandas 自身就有内置的方法,用于简化从 DataFrame 和 Series 绘制图形。另一个库 seaborn(https://seaborn.pydata.org/),由 Michael Waskom 创建的静态图形库。Seaborn 简化了许多常见可视类型的创建。

提示:引入 seaborn 会修改 matplotlib 默认的颜色方案和绘图类型,以提高可读性和美观度。即使你不使用 seaborn API,你可能也会引入 seaborn,作为提高美观度和绘制常见 matplotlib 图形的简化方法。

线型图

Series 和 DataFrame 都有一个用于生成各类图表的 plot 方法。默认情况下,它们所生成的是线型图(如图 9-13 所示):

In [60]: s
  • 作者:白夜鬼魅
  • 原文链接:https://baiye.blog.csdn.net/article/details/80884118
    更新时间:2022-09-29 13:08:14