Matplotlib-绘图秘籍-全-

Matplotlib 绘图秘籍(全)

原文:Matplotlib Plotting Cookbook

协议:CC BY-NC-SA 4.0

零、前言

matplotlib 是用于绘图的 Python 模块,并且是科学 Python 模块套件的组件。 matplotlib 允许您使用全面的 API 轻松定制专业级图形,以自定义图形的各个方面。 在本书中,我们将介绍不同类型的图形以及如何调整图形以满足您的需求。 秘籍是正交的,您将能够非常快速地编写自己的解决方案。

这本书涵盖的内容

第 1 章,“第一步”介绍了使用 matplotlib 的基础知识。 基本图形类型以最少的示例介绍。

第 2 章,“自定义颜色和样式”涵盖了如何控制图形的颜色和样式-包括标记,线条粗细,线条图案以及使用颜色表为图的几个项目着色。

第 3 章,“处理标注”涵盖如何标注图形-这包括添加轴域图例,箭头,文本框和形状。

第 4 章,“处理图形”涵盖了如何准备复杂图形-这包括合成多个图形,控制长宽比,轴范围和坐标系。

第 5 章,“处理文件输出”涵盖位图或向量格式的文件输出。 对诸如透明度,分辨率和多页之类的问题进行了详细研究。

第 6 章,“处理地图”涵盖了绘制类似矩阵的数据-包括地图,颤动图和流图。

第 7 章,“处理 3D 图形”涵盖了 3D 图-包括散点图,线图,表面图和条形图。

第 8 章,“用户界面”涵盖了一组用户界面集成解决方案,从简单,极简到复杂。

这本书需要什么

本书中的示例是为 Matplotlib 1.2 和 Python 2.7 或 3 编写的。

大多数示例都依赖于 NumPy 和 SciPy。 一些示例需要 SymPy,而另一些示例则需要 LaTeX。

这本书适合谁

这本书适合有一些 Python 和科学背景的读者。

约定

在本书中,您会发现许多可以区分不同类型信息的文本样式。 以下是这些样式的一些示例,并解释了其含义。

文本中的代码字,数据库表名称,文件夹名称,文件名,文件扩展名,路径名,虚拟 URL,用户输入和 Twitter 句柄如下所示:“我们可以通过使用include指令来包含其他上下文。”

代码块设置如下:

[default]
exten => s,1,Dial(Zap/1|30)
exten => s,2,Voicemail(u100)
exten => s,102,Voicemail(b100)
exten => i,1,Voicemail(s0)

当我们希望引起您对代码块特定部分的注意时,相关的行或项目将以粗体显示:

[default]
exten => s,1,Dial(Zap/1|30)
exten => s,2,Voicemail(u100)
exten => s,102,Voicemail(b100)
exten => i,1,Voicemail(s0)

任何命令行输入或输出的编写方式如下:

## cp /usr/src/asterisk-addons/configs/cdr_mysql.conf.sample
 /etc/asterisk/cdr_mysql.conf

新术语重要词以粗体显示。 您在屏幕上看到的单词,例如在菜单或对话框中,将以如下形式出现:“单击下一步按钮将您移至下一个屏幕”。

注意

警告或重要提示会出现在这样的框中。

提示

提示和技巧如下所示。

一、第一步

在本章中,我们将介绍:

  • 安装 matplotlib
  • 绘制一条曲线
  • 使用 NumPy
  • 绘制多条曲线
  • 从文件数据绘制曲线
  • 绘制点
  • 绘制条形图
  • 绘制多个条形图
  • 绘制堆叠的条形图
  • 绘制背对背条形图
  • 绘制饼图
  • 绘制直方图
  • 绘制箱形图
  • 绘制三角剖分

简介

matplotlib 使科学绘图非常简单。 matplotlib 并不是使图形绘制变得容易的首次尝试。 matplotlib 带来的是一种在易用性和功能之间取得平衡的现代解决方案。 matplotlib 是 Python(一种编程语言)的模块。 在本章中,我们将简要概述使用 matplotlib 的感觉。 简约秘籍用于介绍 matplotlib 建立的原理。

安装 matplotlib

在使用 matplotlib 尝试之前,需要先安装它。 在这里,我们介绍了一些技巧,以使 Matplotlib 正常运行并没有太多麻烦。

操作步骤

我们有三种可能的场景:您可能正在使用 Linux,OSX 或 Windows。

Linux

大多数 Linux 发行版默认都安装了 Python,并在其标准包列表中提供了 matplotlib。 因此,您要做的就是使用发行版的包管理器自动安装 matplotlib。 除了 matplotlib,我们强烈建议您安装 NumPy,SciPy 和 SymPy,因为它们应该可以一起使用。 以下列表包含启用不同 Linux 版本中可用的默认包的命令:

  • Ubuntu :默认的 Python 包是为 python 2.7 编译的。 在命令终端中,输入以下命令:

    sudo apt-get install python-matplotlib python-numpy python-scipy python-sympy
    
    
  • ArchLinux:默认的 Python 包是针对 Python 3 编译的。 在命令终端中,输入以下命令:

    sudo pacman -S python-matplotlib python-numpy python-scipy python-sympy
    
    

    如果您更喜欢使用 Python 2.7,请在包名称中将python替换为python2

  • Fedora:默认的 Python 包是针对 Python 2.7 编译的。 在命令终端中,输入以下命令:

    sudo yum install python-matplotlib numpy scipy sympy
    
    

    注意

    还有其他安装这些包的方法。 在本章中,我们提出了最简单,最无缝的方法。

Windows 和 OSX

Windows 和 OSX 没有用于软件安装的标准包系统。 我们有两个选择-使用现成的自安装包或从代码源编译 matplotlib。 第二种选择涉及更多工作。 值得努力安装最新的最新版本的 matplotlib。 因此,在大多数情况下,使用现成的包装是更实际的选择。

现成的包装有个选择:Anaconda,Entought Canopy,Algorete Loopy 等! 所有这些包都一次性提供了 Python,SciPy,NumPy,matplotlib 等(文本编辑器和精美的交互式 shell)。 实际上,所有这些系统都安装了自己的包管理器,从那里可以像在典型的 Linux 发行版上那样安装/卸载其他包。 为了简洁起见,我们将仅提供 Enthought Canopy 的说明。 所有其他系统都在线提供大量文档,因此安装它们应该不会有太大问题。

因此,让我们通过执行以下步骤来安装 Enthought Canopy:

  1. 下载 Enthought Canopy 安装程序。 您可以选择免费的 Express 版本。 该网站可以猜测您的操作系统并为您建议合适的安装程序。
  2. 运行 Enthought Canopy 安装程序。 如果您不想与其他用户共享已安装的软件,则无需成为管理员即可安装包。
  3. 安装时,只需单击下一步,即可保留默认设置。 您可以在这个页面中找到有关安装过程的其他信息。

而已! 您将安装 Python 2.7,NumPy,SciPy 和 matplotlib 并准备运行。

绘制一条曲线

HelloWorld 的初始示例! 绘图软件的通常是要显示一条简单的曲线。 我们将保持这种传统。 它还将使您大致了解 matplotlib 的工作方式。

准备

您需要安装 Python(v2.7 或 v3)和 matplotlib。 您还需要一个文本编辑器(任何文本编辑器都可以)和一个命令终端来键入和运行命令。

操作步骤

让我们从任何绘图软件提供的最常见和最基本的图形之一,曲线开始。 在另存为plot.py的文本文件中,我们具有以下代码:

import matplotlib.pyplot as plt

X = range(100)
Y = [value ** 2 for value in X]

plt.plot(X, Y)
plt.show()

假设您已经安装了和 Python,并且现在可以使用 Python 解释该脚本。 如果您不熟悉 Python,那么确实是我们那里的 Python 脚本! 在命令终端中,使用以下命令在保存plot.py的目录中运行脚本:

python plot.py

这样做将打开一个窗口,如以下屏幕截图所示:

How to do it...

窗口显示曲线Y = X ** 2,其中X[0, 99]范围内。 您可能已经注意到,该窗口有几个图标,其中一些如下:

  • How to do it...:此图标打开一个对话框,可让您将图形另存为图片文件。 您可以将其另存为位图图片或向量图片。
  • How to do it...:使用此图标可以翻译和缩放图形。 单击它,然后将鼠标移到图形上。 单击鼠标左键将根据鼠标移动来平移图形。 单击鼠标右键将修改图形的比例。
  • How to do it...:此图标会将图形恢复到初始状态,取消您之前可能已应用的任何平移或缩放。

工作原理

假设您对 Python 还不是很熟悉,那么让我们分析上一节中演示的脚本。

第一行告诉 Python 我们正在使用matplotlib.pyplot模块。 为了节省输入时间,我们将名称plt等同于matplotlib.pyplot。 这是一种非常常见的做法,您会在 matplotlib 代码中看到。

第二行创建一个名为X的列表,所有整数值从 0 到 99。range函数用于生成连续数字。 您可以运行交互式 Python 解释器,如果使用 Python 2,则键入命令range(100),如果使用 Python 3,则键入命令list(range(100))。这将显示从 0 到 99 的所有整数值的列表。 ,sum(range(100))将计算 0 到 99 之间的整数之和。

第三行创建一个名为Y的列表,列表X中的所有值均平方。 通过将函数应用于另一个列表的每个成员来构建新列表是一个 Python 习惯用法,名为列表推导式。 列表Y将以相同顺序包含列表X的平方值。 因此Y将包含 0、1、4、9、16、25,依此类推。

第四条线绘制一条曲线,其中曲线点的 x 坐标在列表X中给出,而曲线点的 y 坐标在列表Y中给出。 请注意,列表名称可以是您喜欢的任何名称。

最后一行显示结果,您将在运行脚本时在窗口中看到该结果。

更多

那么到目前为止我们学到了什么? 与诸如 gnuplot 之类的绘图包不同,matplotlib 并不是专门用于绘图的命令解释器。 与 Matlab 不同,matplotlib 也不是用于绘图的集成环境。 matplotlib 是用于绘图的 Python 模块。 使用 Python 脚本描述图形,这些脚本依赖于 matplotlib 提供的(相当大)功能集。

因此,matplotlib 背后的理念是利用现有语言 Python。 理由是 Python 是一种完整的,设计良好的通用编程语言。 将 matplotlib 与其他包结合使用不会涉及任何技巧和黑客,而仅涉及 Python 代码。 这是因为有许多用于 Python 的包可以执行几乎所有任务。 例如,要绘制存储在数据库中的数据,您将使用数据库包读取数据并将其提供给 matplotlib。 要生成大量统计图形,您将使用科学计算包,例如 SciPy 和 Python 的 I/O 模块。

因此,与许多绘图包不同,matplotlib 是非常正交的,它仅作图而仅作图。 如果您想从文件中读取输入或进行一些简单的中间计算,则必须使用 Python 模块和一些粘合代码来实现。 幸运的是,Python 是一种非常流行的语言,易于掌握并且拥有庞大的用户群。 我们将一点一点地证明这种方法的强大功能。

使用 NumPy

使用 matplotlib 不需要 NumPy 。 但是,许多 matplotlib 技巧,代码示例和示例都使用 NumPy。 简短介绍 NumPy 的用法将向您显示原因。

准备

除了安装 Python 和 matplotlib 外,还安装了 NumPy。 您有一个文本编辑器和一个命令终端。

操作步骤

让我们绘制另一条曲线sin(x),其中x[0,2 * pi]区间内。 与先前脚本的唯一区别是我们生成点坐标的部分。 输入以下脚本并将其保存为sin-1.py

import math
import matplotlib.pyplot as plt

T = range(100)
X = [(2 * math.pi * t) / len(T) for t in T]
Y = [math.sin(value) for value in X]

plt.plot(X, Y)
plt.show()

然后,键入以下脚本并将其保存为sin-2.py

import numpy as np
import matplotlib.pyplot as plt
X = np.linspace(0, 2 * np.pi, 100)
Y = np.sin(X)

plt.plot(X, Y)
plt.show()

运行sin-1.pysin-2.py都会准确显示以下图形:

How to do it...

工作原理

第一个脚本sin-1.py仅使用 Python 的标准库生成正弦曲线的坐标。 以下几点描述了我们在上一节的脚本中执行的步骤:

  1. 我们创建了一个列表T,其编号为 0 到 99,我们的曲线将绘制 100 个点。
  2. 我们通过简单地重新缩放T中存储的值来计算 x 坐标,以使 x 从 0 变为2pirange()内置函数只能生成整数值)。
  3. 与第一个示例一样,我们生成了 y 坐标。

第二个脚本sin-2.pysin-1.py完全相同,其结果是相同的。 但是,由于sin-2.py使用 NumPy 包,因此它稍短一些并且更易于阅读。

提示

NumPy 是用于科学计算的 Python 包。 matplotlib 可以在没有 NumPy 的情况下工作,但是使用 NumPy 可以节省大量的时间和精力。 NumPy 包提供了一个功能强大的多维数组对象和许多用于操纵它的函数。

NumPy 包

sin-2.py中,X列表现在是一维 NumPy 数组,在 0 和2pi之间具有 100 个均匀间隔的值。 这是函数numpy.linspace的目的。 可以说,这比我们在sin-1.py中进行的计算更加方便。 Y列表也是一维 NumPy 数组,其值是根据X的坐标计算的。 NumPy 函数适用于整个数组,就像它们适用于单个值一样。 同样,不需要像在sin-1.py中那样一目了然地显式计算这些值。 与纯 Python 版本相比,我们的代码较短但可读性强。

更多

NumPy 可以一次对整个数组执行操作,从而在生成曲线坐标时为我们节省了很多工作。 此外,使用 NumPy 最有可能导致代码快于纯 Python 代码。 更容易阅读和更快的代码,不喜欢什么? 下面是一个示例,其中我们使用200点在[-3, 2]区间绘制二项式x^2 - 2x + 1

import numpy as np
import matplotlib.pyplot as plt

X = np.linspace(-3, 2, 200)
Y = X ** 2 - 2 * X + 1.

plt.plot(X, Y)
plt.show()

运行前面的脚本将为我们提供如下图所示的结果:

There's more...

同样,我们可以用纯 Python 进行绘制,但是可以说它不那么容易阅读。 尽管可以在不使用 NumPy 的情况下使用 matplotlib,但这两者构成了强大的组合。

绘制多条曲线

我们绘制曲线的原因之一是比较那些曲线。 他们匹配吗? 它们在哪里匹配? 他们在哪里不匹配? 它们相关吗? 图形可以帮助您快速做出判断,以便进行更彻底的调查。

操作步骤

让我们以[0, 2pi]间隔显示sin(x)cos(x),如下所示:

import numpy as np
import matplotlib.pyplot as plt

X = np.linspace(0, 2 * np.pi, 100)
Ya = np.sin(X)
Yb = np.cos(X)

plt.plot(X, Ya)
plt.plot(X, Yb)
plt.show()

前面的脚本将为我们提供如下图所示的结果:

How to do it...

工作原理

两条曲线以不同的颜色显示,由 matplotlib 自动拾取。 我们对一条曲线使用一个函数call plt.plot(); 因此,我们必须在这里两次调用plt.plot()。 但是,我们仍然只需要调用一次plt.show()。 函数calls plt.plot(X, Ya)plt.plot(X, Yb)可以看作是意图的声明。 我们想将这两套点链接在一起,并为每套点分别绘制一条曲线。

matplotlib 将仅记录此意图,但不会进行任何绘制。 但是,plt.show()曲线将发出信号,表明我们要绘制到目前为止已描述的内容。

更多

这种延迟渲染机制对于 matplotlib 至关重要。 您可以声明渲染的时间以及适合的时间。 仅当您调用 plt.show()时,图形才会呈现。 为了说明这一点,让我们看下面的脚本,该脚本呈现一个钟形曲线,以及该曲线在每个点的斜率:

import numpy as np
import matplotlib.pyplot as plt

def plot_slope(X, Y):
  Xs = X[1:] - X[:-1]
  Ys = Y[1:] - Y[:-1]
  plt.plot(X[1:], Ys / Xs)

X = np.linspace(-3, 3, 100)
Y = np.exp(-X ** 2)

plt.plot(X, Y)
plot_slope(X, Y)

plt.show()

上面的脚本将产生以下图形:

There's more...

其中一个函数plt.plot()是在plot_slope函数内部完成的,该函数对图形的呈现没有任何影响,因为plt.plot()只是声明了我们要呈现的内容,但尚未执行呈现 。 当编写具有很多曲线的复杂图形的脚本时,这非常有用。 您可以使用适当的编程语言的所有功能(循环,函数调用等)来组成图形。

根据文件数据绘制曲线

如前面所述,matplotlib 仅处理绘图。 如果要绘制存储在文件中的数据,则必须使用 Python 代码读取文件并提取所需的数据。

操作步骤

假设我们将时间序列存储在名为my_data.txt的纯文本文件中,如下所示:

0  0
1  1
2  4
4 16
5 25
6 36

一种用于读取和绘制数据的极简纯 Python 方法如下:

import matplotlib.pyplot as plt

X, Y = [], []
for line in open('my_data.txt', 'r'):
  values = [float(s) for s in line.split()]
  X.append(values[0])
  Y.append(values[1])

plt.plot(X, Y)
plt.show()

该脚本与my_data.txt中存储的数据一起,将产生以下图形:

How to do it...

工作原理

以下是有关的上述脚本工作原理的一些解释:

  • X, Y = [], []行将坐标XY的列表初始化为空列表。
  • for line in open('my_data.txt', 'r')行定义了一个循环,该循环将迭代文本文件my_data.txt的每一行。 每次迭代时,将从文本文件中提取的当前行作为字符串存储在变量行中。
  • values = [float(s) for s in line.split()]行将当前行分隔为空字符以形成令牌字符串。 然后将这些标记解释为浮点值。 这些值存储在列表值中。
  • 然后,在接下来的两行X.append(values[0])Y.append(values[1])中,values中存储的值将添加到列表XY中。

下面的等价于的单行读取文本文件可能会给熟悉 Python 的人带来微笑:

import matplotlib.pyplot as plt

with open('my_data.txt', 'r') as f:
  X, Y = zip(*[[float(s) for s in line.split()] for line in f])

plt.plot(X, Y)
plt.show()

更多

在我们的数据加载代码中,请注意,没有进行任何认真的检查或错误处理。 无论如何,一个人可能还记得一个好的程序员就是一个懒惰的程序员。 确实,由于 NumPy 经常与 matplotlib 一起使用,为什么不在这里使用它呢? 运行以下脚本以启用 NumPy:

import numpy as np
import matplotlib.pyplot as plt

data = np.loadtxt('my_data.txt')

plt.plot(data[:,0], data[:,1])
plt.show()

这与上一节中所示的单行代码一样短,但更易于阅读,它将处理许多纯 Python 代码无法处理的错误情况。 以下几点描述了前面的脚本:

  • numpy.loadtxt()函数读取文本文件并返回 2D 数组。 对于 NumPy,二维数组不是列表列表,它们是真实的,成熟的矩阵。
  • 变量data是 NumPy 2D 数组,它给我们带来的好处是能够将矩阵的行和列作为 1D 数组进行操作。 实际上,在plt.plot(data[:,0], data[:,1])行中,我们将第一列数据作为 x 坐标,将第二列数据作为 y 坐标。 此符号特定于 NumPy。

除了使代码更短,更简单之外,使用 NumPy 还带来了其他优势。 对于大文件,使用 NumPy 会明显更快(NumPy 模块主要用 C 编写),并且将整个数据集存储为 NumPy 数组也可以节省内存。 最后,使用 NumPy 可以轻松支持数字数据的其他常见文件格式(CVS 和 Matlab)。

作为演示到目前为止所看到的所有内容的一种方式,让我们考虑以下任务。 一个文件包含 N 列值,描述 N-1 条曲线。 第一列包含 x 坐标,第二列包含第一条曲线的 y 坐标,第三列包含第二条曲线的 y 坐标,依此类推。 我们要显示这些 N–1 曲线。 我们将使用以下代码来做到这一点:

import numpy as np
import matplotlib.pyplot as plt

data = np.loadtxt('my_data.txt')
for column in data.T:
  plt.plot(data[:,0], column)

plt.show()

文件my_data.txt应包含以下内容:

0 0 6
1 1 5
2 4 4
4 16 3
5 25 2
6 36 1

然后我们得到下图:

There's more...

我们通过两个技巧毫不费力地完成了这项工作。 在 NumPy 表示法中,data.T是 2D 数组数据的转置视图—行被视为列,列被视为行。 同样,我们可以通过执行for row in data遍历多维数组的行。 因此,执行for column in data.T将遍历数组的列。 用几行代码,我们得到了一个相当通用的绘图通用脚本。

绘制点

显示曲线时,我们隐含地假设一个点紧随另一个点-我们的数据是时间序列。 当然,并非总是如此。 数据的一个点可以彼此独立。 表示此类数据的一种简单方法是简单地显示这些点而无需链接它们。

操作步骤

以下脚本显示 1024 个点,其坐标是从[0, 1]间隔中随机绘制的:

import numpy as np
import matplotlib.pyplot as plt

data = np.random.rand(1024, 2)

plt.scatter(data[:,0], data[:,1])
plt.show()

前面的脚本将产生以下图形:

How to do it...

工作原理

函数plt.scatter()的工作原理与plt.plot()完全相同,将点的 x 和 y 坐标作为输入参数。 但是,每个点仅用一个标记显示。 不要被这种简单性所迷惑-plt.scatter()是一个丰富的命令。 通过使用其许多可选参数,我们可以实现许多不同的效果。 我们将在第 2 章,“自定义颜色和样式”和第 3 章,“处理标注”中进行介绍。

绘制条形图

条形图是绘图包的常见主食,甚至 matplotlib 也有。

操作步骤

条形图的专用函数是pyplot.bar()。 我们将通过执行以下脚本来启用此函数:

import matplotlib.pyplot as plt

data = [5., 25., 50., 20.]

plt.bar(range(len(data)), data)
plt.show()

上面的脚本将产生以下图形:

How to do it...

工作原理

对于列表数据中的每个值,均显示一个垂直条。 pyplot.bar()函数接收两个参数-每个条形的 x 坐标和每个条形的高度。 在这里,我们为每个小节使用坐标 0、1、2 等,这是range(len(data))的目的。

更多

通过可选参数,pyplot.bar()提供了一种控制钢筋厚度的方法。 此外,我们还可以使用pyplot.bar()的双胞胎兄弟pyplot.barh()获得单杠。

条形的厚度

默认情况下,钢筋的厚度为 0.8 单位。 因为我们在每个单位长度上都放置了一个小节,所以它们之间的距离为 0.2。 当然,您可以摆弄这个厚度参数。 例如,通过将其设置为 1:

import matplotlib.pyplot as plt

data = [5., 25., 50., 20.]

plt.bar(range(len(data)), data, width = 1.)
plt.show()

前面的简约脚本将产生以下图形:

The thickness of a bar

现在,这些条之间没有缝隙。 matplotlib 条形图函数pyplot.bar()将无法处理条形的位置和厚度。 程序员负责。 这种灵活性使您可以在条形图上创建许多变体。

单杠

如果您更喜欢水平条,请使用 barh()函数,该函数与bar()严格等效,除了提供水平条而不是垂直条:

import matplotlib.pyplot as plt

data = [5., 25., 50., 20.]

plt.barh(range(len(data)), data)
plt.show()

上面的脚本将产生以下图形:

Horizontal bars

绘制多个条形图

比较多个数量和更改一个变量时,我们可能需要一个条形图,其中一个数量值具有一种颜色的条形。

操作步骤

我们可以通过玩这些条的粗细和位置来绘制多个条形图:

import numpy as np
import matplotlib.pyplot as plt

data = [[5., 25., 50., 20.],
  [4., 23., 51., 17.],
  [6., 22., 52., 19.]]

X = np.arange(4)
plt.bar(X + 0.00, data[0], color = 'b', width = 0.25)
plt.bar(X + 0.25, data[1], color = 'g', width = 0.25)
plt.bar(X + 0.50, data[2], color = 'r', width = 0.25)

plt.show()

上面的脚本将产生以下图形:

How to do it...

工作原理

data变量包含三个系列的四个值。 上面的脚本将显示四个条形图的三个条形图。 条的厚度为 0.25 个单位。 每个条形图将从上一个移位 0.25 个单位。 为了清楚起见,添加了颜色。 在第 2 章“自定义颜色和样式”中将详细介绍该主题。

更多

上一节中显示的代码非常繁琐,因为我们通过手动移动三个条形图来重复自己。 通过使用以下代码,我们可以做得更好:

import numpy as np
import matplotlib.pyplot as plt

data = [[5., 25., 50., 20.],
  [4., 23., 51., 17.],
  [6., 22., 52., 19.]]

color_list = ['b', 'g', 'r']
gap = .8 / len(data)
for i, row in enumerate(data):
  X = np.arange(len(row))
  plt.bar(X + i * gap, row,
    width = gap,
    color = color_list[i % len(color_list)])

plt.show()

在这里,我们使用循环for i, row in enumerate(data)遍历数据的每一行。 迭代器enumerate返回当前行及其索引。 通过列表推导完成为一个条形图生成每个条形图的位置。 该脚本将产生与前一个脚本相同的结果,但是如果我们添加数据的行或列,则不需要任何更改。

绘制堆叠的条形图

当然,可以通过使用pyplot.bar()函数中的特殊参数来堆叠条形图。

操作步骤

以下脚本相互堆叠两个条形图:

import matplotlib.pyplot as plt

A = [5., 30., 45., 22.]
B = [5., 25., 50., 20.]

X = range(4)

plt.bar(X, A, color = 'b')
plt.bar(X, B, color = 'r', bottom = A)
plt.show()

上面的脚本将产生以下图形:

How to do it...

工作原理

pyplot.bar()函数的可选bottom 参数允许您指定钢筋的起始值。 它不是从零到一个值,而是从下到上。 首次调用pyplot.bar()会绘制蓝色条形图。 对pyplot.bar()的第二次调用绘制了红色条,红色条的底部在蓝色条的顶部。

更多

当堆叠两个以上的值集时,代码会变得不那么漂亮,如下所示:

import numpy as np
import matplotlib.pyplot as plt

A = np.array([5., 30., 45., 22.])
B = np.array([5., 25., 50., 20.])
C = np.array([1.,  2.,  1.,  1.])
X = np.arange(4)

plt.bar(X, A, color = 'b')
plt.bar(X, B, color = 'g', bottom = A)
plt.bar(X, C, color = 'r', bottom = A + B)

plt.show()

对于第三个条形图,我们必须将底值计算为A + B,即AB的按系数求和。使用 NumPy 有助于保持代码紧凑但可读。 但是,此代码相当重复,并且仅适用于三个堆叠的条形图。 我们可以使用以下代码做得更好:

import numpy as np
import matplotlib.pyplot as plt

data = np.array([[5., 30., 45., 22.],
  [5., 25., 50., 20.],
  [1.,  2.,  1.,  1.]]

color_list = ['b', 'g', 'r']

X = np.arange(data.shape[1])
for i in range(data.shape[0]):
  plt.bar(X, data[i],
    bottom = np.sum(data[:i], axis = 0),
    color = color_list[i % len(color_list)])

plt.show()

在这里,我们将数据存储在 NumPy 数组中,一张条形图一行。 我们遍历每一行数据。 对于第i行,bottom参数接收第i行之前所有行的总和。 通过这种方式编写脚本,可以在更改输入数据时以最小的努力堆叠任意数量的条形图。

There's more...

绘制背对背条形图

一个简单但有用的技巧是同时背对背显示两个条形图。 想一想人口的年龄金字塔,它显示了不同年龄范围内的人数。 左侧显示男性人口,而右侧显示女性人口。

操作步骤

想法是使用一个简单的技巧来制作两个条形图,即一个条的长度/高度可以为负!

import numpy as np
import matplotlib.pyplot as plt

women_pop = np.array([5., 30., 45., 22.])
men_pop     = np.array( [5., 25., 50., 20.])
X = np.arange(4)

plt.barh(X, women_pop, color = 'r')
plt.barh(X, -men_pop, color = 'b')
plt.show()

上面的脚本将产生以下图形:

How to do it...

工作原理

照常绘制女性人群的条形图(红色)。 但是,男性人口的条形图(蓝色)的条形图向左延伸而不是向右延伸。 实际上,蓝色条形图的条形长度为负值。 而不是编辑输入值,我们使用列表推导来否定男性人口条形图的值。

绘制饼图

为了比较数量的相对重要性,没有什么比一个好的旧饼图饼图更好了。

操作步骤

专用的饼图函数pyplot.pie()将完成此工作。 我们将在以下代码中使用此函数:

import matplotlib.pyplot as plt

data = [5, 25, 50, 20]

  plt.pie(data)
plt.show()

前面的简单脚本将显示以下饼图:

How to do it...

工作原理

pyplot.pie()函数仅将值列表作为输入。 注意,输入数据是一个列表。 它可能是一个 NumPy 数组。 您不必调整数据,使其总计为 1 或 100。您只需为 matplolib 提供值,它将自动计算饼图的相对面积。

绘制直方图

直方图是概率分布的图形表示。 实际上,直方图只是条形图的一种。 我们可以轻松使用 matplotlib 的条形图函数并进行一些统计以生成直方图。 但是,直方图非常有用,以至于 matplotlib 为其提供了函数。 在本秘籍中,我们将了解如何使用此直方图函数。

操作步骤

以下脚本从正态分布中绘制1000值,然后生成具有 20 个桶的直方图:

import numpy as np
import matplotlib.pyplot as plt

X = np.random.randn(1000)

plt.hist(X, bins = 20)
plt.show()

每次我们运行脚本时,由于数据集是随机生成的,因此直方图会有所变化。 上面的脚本将显示以下图形:

How to do it...

工作原理

pyplot.hist()函数将值列表作为输入。 值的范围将分为相等大小的桶(默认为 10 个桶)。 pyplot.hist()函数将生成一个条形图,一个条带表示一个箱。 一格的高度是相应仓中跟随的值的数量。 箱的数量由可选参数箱确定。 通过将可选参数normed设置为True,可以对钢筋高度进行标准化,并且所有钢筋高度的总和等于 1。

绘制箱形图

箱形图允许您方便地显示一组值的中位数,四分位数,最大值和最小值,从而比较值的分布。

操作步骤

以下脚本显示了从正态分布中抽取的 100 个随机值的箱形图:

import numpy as np
import matplotlib.pyplot as plt

data = np.random.randn(100)

plt.boxplot(data)
plt.show()

将会出现一个箱形图,代表我们从随机分布中提取的样本。 由于代码使用随机生成的数据集,因此每次运行脚本时,生成的图形都会略有变化。

前面的脚本将显示以下图形:

How to do it...

工作原理

data = [random.gauss(0., 1.) for i in range(100)]变量从正态分布中生成 100 个值。 出于演示目的,通常从文件中读取或从其他数据计算出这些值。 plot.boxplot()函数取一组值并自己计算平均值,中位数和其他统计量。 以下几点描述了前面的箱形图:

  • 红色条形是分布的中位数。
  • 蓝色框包含从下四分位数到上四分位数的 50% 的数据。 因此,该框位于数据中位数的中心。
  • 下胡须从下四分位数延伸到 1.5 IQR 内的最小值。
  • 上胡须从上四分位开始延伸到 1.5 IQR 以内的最大值。
  • 胡须以外的值用十字标记显示。

更多

要在单个图形中显示多个箱形图,对每个箱形图调用一次pyplot.boxplot()将不起作用。 它将简单地将箱形图相互绘制,从而制作出混乱的,不可读的图形。 但是,我们可以通过一次调用pyplot.boxplot()来绘制多个箱形图,如下所示:

import numpy as np
import matplotlib.pyplot as plt

data = np.random.randn(100, 5)

plt.boxplot(data)
plt.show()

上面的脚本显示以下图形:

There's more...

pyplot.boxplot()函数接受列表列表作为输入,为每个子列表绘制一个箱形图。

绘制三角剖分

三角剖分在处理空间位置时出现。 除了显示点与邻域关系之间的距离之外,三角剖分图可以方便地表示地图。 matplotlib 为三角剖分提供了大量支持。

操作步骤

与前面的示例一样,以下几行代码就足够了:

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.tri as tri

data = np.random.rand(100, 2)

triangles = tri.Triangulation(data[:,0], data[:,1])

plt.triplot(triangles)
plt.show()

每次运行脚本时,您都会看到不同的三角剖分,因为三角剖分的点云是随机生成的。

前面的脚本显示以下图形:

How to do it...

工作原理

我们导入matplotlib.tri模块,该模块提供了一些辅助函数来计算点的三角剖分。 在此示例中,出于演示目的,我们使用以下代码生成随机的点云:

data = np.random.rand(100, 2)

我们使用以下代码来计算三角剖分并将其存储在三角形的变量中:

triangles = tri.Triangulation(data[:,0], data[:,1])

pyplot.triplot()函数仅将三角形作为输入并显示三角剖分结果。

二、自定义颜色和样式

在本章中,我们将介绍:

  • 定义自己的颜色
  • 对散点图使用自定义颜色
  • 对条形图使用自定义颜色
  • 对饼图使用自定义颜色
  • 对箱形图使用自定义颜色
  • 对散点图使用颜色表
  • 对条形图使用颜色表用
  • 控制线条图案和粗细
  • 控制填充图案
  • 控制标记的样式
  • 控制标记的大小
  • 创建自己的标记
  • 更好地控制标记
  • 创建自己的配色方案

简介

matplotlib 可用的所有绘图都带有其默认样式。 虽然这对于原型制作很方便,但我们的最终图形将需要与默认样式有所不同。 您可能只需要使用灰度级,或者遵循现有的配色方案,或更一般地说,遵循现有的可视化图表。 matplotlib 在设计时考虑了灵活性。 正如本章的秘籍将说明的那样,很容易适应 matplotlib 图形的样式。

定义自己的颜色

matplotlib 使用的默认颜色相当淡淡。 对于方便的颜色,我们可能会有自己的偏好。 我们可能希望使图形遵循预定义的配色方案,以便它们很好地适合文档或网页。 更加务实的是,我们可能只需要为将要在黑白打印机上打印的文档制作图形。 在本秘籍中,我们将了解如何定义自己的颜色。

准备

在 matplotlib 中定义颜色的方法有多种。 其中一些如下:

  • 三元组:这些颜色可以描述为实值三元组-颜色的红色,蓝色和绿色分量。 分量必须在[0, 1]间隔内。 因此,Python 语法(1.0, 0.0, 0.0)将编码纯净的鲜红色,而(1.0, 0.0, 1.0)则显示为强粉色。

  • 四元组:这些作为三元组,并且第四部分定义透明度值。 此值也应在[0, 1]间隔内。 将图形渲染到图片文件时,使用透明颜色可以使图形与背景融合。 当制作可在网页上滑动或结束的图形时,这特别有用。

  • 预定义名称:matplotlib 将标准 HTML 颜色名称解释为实际颜色。 例如,字符串red将被接受为一种颜色,并将被解释为亮红色。 几种颜色具有一个字母别名,如下表所示:

    别名 颜色
    b 蓝色
    g 绿色
    r 红色
    c 青色
    m 品红
    y 黄色
    k 黑色
    w 白色
  • HTML 颜色字符串:matplotlib 可以将 HTML 颜色字符串解释为实际颜色。 这样的字符串定义为#RRGGBB,其中RRGGBB是十六进制的红色,绿色和蓝色分量的 8 位值。

  • 灰度字符串:matplotlib 将浮点值的字符串表示形式解释为灰色阴影,例如0.75用于中等浅灰色。

操作步骤

设置线形图的颜色是通过如下设置pyplot.plot()函数的参数颜色(或等效的快捷方式c)来完成的:

import numpy as np
import matplotlib.pyplot as plt

def pdf(X, mu, sigma):
  a = 1\. / (sigma * np.sqrt(2\. * np.pi))
  b = -1\. / (2\. * sigma ** 2)
  return a * np.exp(b * (X - mu) ** 2)

X = np.linspace(-6, 6, 1000)

for i in range(5):
  samples = np.random.standard_normal(50)
  mu, sigma = np.mean(samples), np.std(samples)
  plt.plot(X, pdf(X, mu, sigma), color = '.75')

plt.plot(X, pdf(X, 0., 1.), color = 'k')
plt.show()

上面的脚本将产生类似于下一个脚本的图形,其中显示五个浅灰色,钟形曲线和一个黑色曲线:

How to do it...

工作原理

在此示例中,我们从正态分布生成五组 50 个样本。 对于这五组中的每组,我们以浅灰色绘制估计的概率密度。 正态分布概率密度以黑色显示。 在那里,使用黑色的快捷方式k对颜色进行编码。

对散点图使用自定义颜色

就像控制线形图一样,我们可以控制用于散点图的颜色。 在本秘籍中,我们将看到如何使用两种方法来控制散点图的颜色。

准备

散点图函数pyplot.scatter() 提供以下两个选项,可通过其color参数或其快捷方式c控制点的颜色:

  • 所有点的通用颜色:如果颜色参数是有效的 matplotlib 颜色定义,则所有点将以该颜色显示。
  • 每个点的单独颜色:如果颜色参数是有效 matplotlib 颜色定义的序列,则第i个点将以第i种颜色出现。 当然,我们必须为每个点提供所需的颜色。

操作步骤

在以下脚本中,我们显示了从两个双变量高斯分布中得出的两组点AB。 每套都有自己的颜色。 我们两次调用pyplot.scatter(),每个点集调用一次,如以下脚本所示:

import numpy as np
import matplotlib.pyplot as plt

A = np.random.standard_normal((100, 2))
A += np.array((-1, -1)) # Center the distrib. at <-1, -1>

B = np.random.standard_normal((100, 2))
B += np.array((1, 1)) # Center the distrib. at <1, 1>

plt.scatter(A[:,0], A[:,1], color = '.25')
plt.scatter(B[:,0], B[:,1], color = '.75')
plt.show()

上面的脚本将产生以下图形:

How to do it...

因此,在此示例中,完全像pyplot.plot()中那样使用自定义颜色。 在下面的脚本中,情况会有所不同。 我们从文本文件 Fisher 的鸢尾数据集加载数组,该文件可从这里获得。 其内容如下所示:

4.6,3.2,1.4,0.2,Iris-setosa
5.3,3.7,1.5,0.2,Iris-setosa
5.0,3.3,1.4,0.2,Iris-setosa
7.0,3.2,4.7,1.4,Iris-versicolor
6.4,3.2,4.5,1.5,Iris-versicolor

数据集的每个点都存储在以逗号分隔的值列表中。 给出每个点标签的最后一列是一个字符串,可以采用三个可能的值-Iris-virginicaIris-versicolorIris-Vertosa。 我们使用 NumPy 的numpy.loadtxt函数读取此文件。 点的颜色将取决于它们的标签,我们将仅通过一次调用pyplot.scatter()来显示它们,如下所示:

import numpy as np
import matplotlib.pyplot as plt

label_set = (
  b'Iris-setosa',
  b'Iris-versicolor',
  b'Iris-virginica',
)

def read_label(label):
  return label_set.index(label)

data = np.loadtxt('iris.data.txt',
                             delimiter = ',',
                             converters = { 4 : read_label })

color_set = ('.00', '.50', '.75')
color_list = [color_set[int(label)] for label in data[:,4]]

plt.scatter(data[:,0], data[:,1], color = color_list)
plt.show()

前面的脚本将产生以下图:

How to do it...

工作原理

对于的三个可能的标签,我们分配一种唯一的颜色。 颜色在color_set中定义,标签在label_set中定义。 label_set中的第i个标签与color_set中的第i个颜色相关联。

我们将标签列表label_list转换为颜色列表color_list,并使用列表推导特性。 然后,我们只需调用一次pyplot.scatter(),即可显示所有点及其颜色。 我们可以通过三个单独的调用来完成此操作,但是这将需要更多代码,而没有明显的收益。

两个点可能具有相同的坐标,但具有不同的标签。 在这种情况下,显示的颜色将是绘制的最新点的颜色。 使用透明颜色,重叠点的颜色将融合在一起。

更多

就像color参数控制点的颜色一样,edgecolor参数控制点的边缘的颜色。 它严格适用于color参数-您可以为每个点边缘设置相同的颜色,或分别控制边缘颜色,如下所示:

import numpy as np
import matplotlib.pyplot as plt

data = np.random.standard_normal((100, 2))

plt.scatter(data[:,0], data[:,1], color = '1.0', edgecolor='0.0')
plt.show()

上面的脚本将产生以下图形:

There's more...

对条形图使用自定义颜色

条形图经常在网页和演示文稿中使用,其中人们通常必须遵循既定的配色方案。 因此,必须对它们的颜色进行良好的控制。 在本秘籍中,我们将看到如何用我们自己的颜色为条形图着色。

操作步骤

在第 1 章“第一步”中,我们已经了解了如何制作条形图。 控制使用哪种颜色的效果与用于线形图和散点图的方法相同,即通过一个可选参数。 在此示例中,我们从文件中加载国家人口的年龄金字塔,如下所示:

import numpy as np
import matplotlib.pyplot as plt

women_pop = np.array([5., 30., 45., 22.])
men_pop     = np.array([5., 25., 50., 20.])

X = np.arange(4)
plt.barh(X, women_pop, color = '.25')
plt.barh(X, -men_pop, color = '.75')

plt.show()

上面的脚本显示了一个条形图,其中有男性年龄划分,另一条形图是妇女。 女士显示为深灰色,而男士显示为浅灰色,如下图所示:

How to do it...

工作原理

pyplot.bar()pyplot.barh()函数的工作严格类似于pyplot.scatter()。 我们只需要设置可选参数color即可。 参数edgecolor也可用。

更多

在此示例中,我们显示条形图,并根据其表示的值对条形进行着色。 [0, 24][25, 49][50, 74][75, 100]范围内的值将以不同的灰色阴影显示。 颜色列表是使用列表理解来构建的,如下所示:

import numpy as np
import matplotlib.pyplot as plt

values = np.random.random_integers(99, size = 50)

color_set = ('.00', '.25', '.50', '.75')
color_list = [color_set[(len(color_set) * val) // 100] for val in values]
plt.bar(np.arange(len(values)), values, color = color_list)
plt.show()

条形图图表的条形图根据其高度着色,如下图所示:

There's more...

如果我们对值进行排序,则条形图将形成四个不同的带,如下图所示:

There's more...

对饼图使用自定义颜色

与条形图一样,饼图也用于配色方案可能非常重要的环境中。 饼图着色的工作原理与条形图中的类似。 在本秘籍中,我们将看到如何用我们自己的颜色给饼图上色。

操作步骤

函数pyplot.pie() 接受颜色列表作为可选参数,如以下脚本中的所示:

import numpy as np
import matplotlib.pyplot as plt

values = np.random.rand(8)
color_set = ('.00', '.25', '.50', '.75')

plt.pie(values, colors = color_set)
plt.show()

上面的脚本将产生以下饼图:

How to do it...

工作原理

饼图使用colors参数接受颜色列表(请注意,它是colors而不是color)。 但是,颜色列表没有输入值列表那么多的元素。 如果颜色少于值,则pyplot.pie()会简单地在颜色列表中循环显示。 在前面的示例中,我们给出了四种颜色的列表,以对由八个值组成的饼图上色。 因此,每种颜色将使用两次。

对箱形图使用自定义颜色

箱形图是科学出版物的常见主要特征。 彩色的箱形图没有问题。 但是,您可能只需要使用黑白。 在本秘籍中,我们将了解如何在箱图中使用自定义颜色。

操作步骤

每个创建特定图形的函数都会返回一些值-它们是构成图形的低级图形基元。 大多数时候,我们不会费心去获取那些返回值。 但是,操作这些低级绘图基元可以进行一些微调,例如对箱形图的自定义配色方案。

使箱形图显示为全黑比应该的要复杂一些,如以下脚本所示:

import numpy as np
import matplotlib.pyplot as plt
values = np.random.randn(100)

b = plt.boxplot(values)
for name, line_list in b.iteritems():
  for line in line_list:
    line.set_color('k')

plt.show()

上面的脚本生成以下图形:

How to do it...

工作原理

绘图函数返回一个字典。 字典的关键字是图形元素的名称。 对于箱形图,此类元素将是中位数,传单,胡须,盒子和盖子。 与这些键中的每个键相关联的值是一系列低级图形图元(线,形状等)。 在脚本中,我们迭代作为箱形图一部分的每个图形基元,并将其颜色设置为黑色。 使用相同的方法,您可以使用自己的配色方案渲染箱形图。

对散点图使用颜色表

当使用多种颜色时,一个一个定义一个颜色是很麻烦的。 而且,建立一套好的色彩本身就是一个问题。 在某些情况下,颜色表 可以解决这些问题。 颜色表使用一种变量到一个值(对应一种颜色)的连续函数来定义颜色。 matplotlib 提供了几种常见的颜色表; 它们大多数是连续的色带。 在本秘籍中,我们将了解如何使用颜色表对散点图进行颜色着色。

操作步骤

颜色表在,,matplotib.cm模块中定义。 该模块提供函数来创建和使用颜色表。 它还提供了预定义颜色表的详尽选择。

函数pyplot.scatter()接受color参数的值列表。 提供颜色表(带有cmap参数)时,这些值将被解释为颜色表索引,如下所示:

import numpy as np
import matplotlib.cm as cm
import matplotlib.pyplot as plt

N = 256
angle  = np.linspace(0, 8 * 2 * np.pi, N)
radius = np.linspace(.5, 1., N)

X = radius * np.cos(angle)
Y = radius * np.sin(angle)

plt.scatter(X, Y, c = angle, cmap = cm.hsv)
plt.show()

前面的脚本将生成点的彩色螺旋,如下图所示:

How to do it...

工作原理

在此脚本中,我们绘制了点的螺旋线。 点的颜色是角度变量的函数,从颜色表中获取颜色。 matplotlib.cm模块中提供了大量颜色表。 hsv贴图包含所有光谱,这使得花哨的彩虹主题更加丰富。 对于科学的可视化,考虑到感知到的颜色强度,其他颜色表更合适,例如PuOr图。 与PuOr映射相同的脚本将为我们提供以下结果:

How it works...

对条形图使用颜色表

pyplot.scatter()函数具有对颜色表的内置支持。 我们稍后将发现的其他一些绘图函数也具有这种支持。 但是,某些函数(例如 pyplot.bar())不会将颜色表用作绘图条形图的输入。 在本秘籍中,我们将了解如何使用颜色表为条形图着色。

matplotlib 具有辅助函数,可从颜色表显式生成颜色。 例如,我们可以使用条形所代表的值的函数为条形图的条形着色。

操作步骤

我们将在本秘籍的中使用matplotlib.cm模块,就像在先前的秘籍中一样。 这一次,我们将直接使用颜色表对象,而不是让渲染函数自动使用它。 我们还将需要matplotlib.colors模块,其中包含与颜色相关的工具函数,如以下脚本所示:

import numpy as np
import matplotlib.cm as cm
import matplotlib.colors as col
import matplotlib.pyplot as plt

values = np.random.random_integers(99, size = 50)

cmap = cm.ScalarMappable(col.Normalize(0, 99), cm.binary)

plt.bar(np.arange(len(values)), values, color = cmap.to_rgba(values))
plt.show()

上面的脚本将生成一个条形图,其中条形的颜色取决于其高度,如下图所示:

How to do it...

工作原理

我们首先创建颜色表cmap,以便将[0, 99]范围范围内的值映射到matplotlib.cm.binary颜色表的颜色。 然后,函数cmap.to_rgba将值列表转换为颜色列表。 因此,尽管pyplot.bar不支持颜色表,但是使用颜色表不涉及复杂的代码; 有一些功能可以简化这一过程。

请注意,如果对值列表进行了排序,则此处使用的颜色表的连续方面将变得显而易见,如下图所示:

How it works...

控制线条图案和粗细

在为黑白文档创建图形时,我们仅限于灰度级。 在实践中,通常我们可以合理使用最多三个灰度等级。 但是,使用不同的线型可以实现一些多样性。 在本秘籍中,我们将了解如何控制线条图案和粗细。

操作步骤

与颜色一样,线条样式由pyplot.plot()的可选参数控制,如以下脚本所示:

import numpy as np
import matplotlib.pyplot as plt

def pdf(X, mu, sigma):
  a = 1\. / (sigma * np.sqrt(2\. * np.pi))
  b = -1\. / (2\. * sigma ** 2)
  return a * np.exp(b * (X - mu) ** 2)

X = np.linspace(-6, 6, 1024)

plt.plot(X, pdf(X, 0., 1.),   color = 'k', linestyle = 'solid')
plt.plot(X, pdf(X, 0.,  .5),  color = 'k', linestyle = 'dashed')
plt.plot(X, pdf(X, 0.,  .25), color = 'k', linestyle = 'dashdot')

plt.show()

前面的脚本将产生以下图形:

How to do it...

工作原理

在此示例中,我们使用pyplot.plot()linestyle参数控制三个不同曲线的线型。 可以使用以下线条样式:

  • 实线
  • 虚线
  • 点线
  • 点划线

更多

线条样式设置不限于限于pyplot.plot(); 实际上,任何由线条组成的图形都可以进行此类设置。 此外,您还可以控制线的粗细。

其他图类型的线型

linestyle参数可用于所有涉及线渲染的命令。 例如,我们可以修改用于条形图的折线图,如下所示:

import numpy as np
import matplotlib.pyplot as plt

N = 8
A = np.random.random(N)
B = np.random.random(N)
X = np.arange(N)

plt.bar(X, A, color = '.75')
plt.bar(X, A + B, bottom = A, color = 'w', linestyle = 'dashed')

plt.show()

上面的脚本将产生以下图形:

The line style with other plot types

线宽

同样,linewidth参数将更改线的粗细。 默认情况下,厚度设置为 1 个单位。 玩弄线条的粗细可以帮助强调一条特定的曲线。 以下是使用linewidth参数设置线宽的脚本:

import numpy as np
import matplotlib.pyplot as plt

def pdf(X, mu, sigma):
  a = 1\. / (sigma * np.sqrt(2\. * np.pi))
  b = -1\. / (2\. * sigma ** 2)
  return a * np.exp(b * (X - mu) ** 2)

X = np.linspace(-6, 6, 1024)
for i in range(64):
  samples = np.random.standard_normal(50)
  mu, sigma = np.mean(samples), np.std(samples)
  plt.plot(X, pdf(X, mu, sigma), color = '.75', linewidth = .5)

plt.plot(X, pdf(X, 0., 1.), color = 'y', linewidth = 3.)
plt.show()

下图是上述脚本的结果,从 50 个样本中估计出 64 个估计的高斯 PDF概率密度函数),并显示为细灰色曲线 。 用于绘制样本的高斯分布显示为粗黑曲线。

The line width

控制填充样式

matplotlib 提供相当有限的支持,以用图案填充表面。 对于线条图案,为黑白打印准备图形时可能会有所帮助。 在本秘籍中,我们将研究如何用图案填充表面。

操作步骤

让我们用条形图演示填充模式的用法,如下所示:

import numpy as np
import matplotlib.pyplot as plt

N = 8
A = np.random.random(N)
B = np.random.random(N)
X = np.arange(N)

plt.bar(X, A, color = 'w', hatch = 'x')
plt.bar(X, A + B, bottom = A, color = 'w', hatch = '/')

plt.show()

上面的脚本生成以下图形:

How to do it...

工作原理

填充体积的渲染函数(例如pyplot.bar())接受可选参数hatch。 此参数可以采用以下值:

  • /
  • \
  • |
  • -
  • +
  • x
  • o
  • O
  • .
  • *

每个值对应于不同的阴影图案。 color参数将控制图案的背景颜色,而edgecolor参数将控制阴影线的颜色。

控制标记的样式

在第 1 章“第一步”中,我们看到了如何将曲线的点显示为点。 此外,散点图表示数据集的每个点。 事实证明,matplotlib 提供了多种形状以用其他种类的标记替换点。 在本秘籍中,我们将了解如何设置标记的样式。

准备

可以通过以下各种方式将标记指定为:

  • 预定义标记:它们可以是预定义形状,可以将表示为[0, 8]范围内的数字,也可以包含一些字符串
  • 顶点列表:这是值对的列表,用作形状的路径的坐标
  • 正多边形:它将正 N 边形表示为带有旋转角度的三元组(N, 0, 角度)
  • 正多角星:它将正 N 角星表示为带有旋转角度的三元组(N, 1, 角度)

操作步骤

让我们以一个脚本显示两个具有两种不同颜色的点集。 现在,我们将所有点显示为黑色,但标记不同,如下所示:

import numpy as np
import matplotlib.pyplot as plt

A = np.random.standard_normal((100, 2))
A += np.array((-1, -1))
B = np.random.standard_normal((100, 2))
B += np.array((1, 1))

plt.scatter(A[:,0], A[:,1], color = 'k', marker = 'x')
plt.scatter(B[:,0], B[:,1], color = 'k', marker = '^')

plt.show()

将会出现两个高斯点云,每个云使用不同的标记,如下图所示:

How to do it...

工作原理

在此脚本中,我们将的两个散点图的颜色都设置为黑色。 使用marker参数,我们为每个集合指定一个不同的标记。

color参数不同,marker参数不接受标记规范列表作为输入。 因此,我们不能使用单个调用pyplot.scatter()来显示带有不同标记的几组点。 我们需要区分每种标记类型的点,并对每个标记使用单独的pyplot.scatter()调用,如下所示:

import numpy as np
import matplotlib.pyplot as plt

label_list = (
  b'Iris-setosa',
  b'Iris-versicolor',
  b'Iris-virginica',
)

def read_label(label):
  return label_list.index(label)

data = np.loadtxt('iris.data.txt',
  delimiter = ',',
  converters = { 4 : read_label })

marker_set = ('^', 'x', '.')
for i, marker in enumerate(marker_set):
  data_subset = numpy.asarray([x for x in data if x[4] == i])
  plt.scatter(data_subset[:,0], data_subset[:,1],
    color = 'k',
    marker = marker)

plt.show()

数据集中的每个簇都有其自己的标记,如下图所示:

How it works...

此示例与之前的示例相似,在该示例中,我们加载数据集,然后根据标签显示每个点。 但是,在这里,我们将每个标签的点分开。 然后,我们遍历地图的每个条目,并为每个点子集调用pyplot.scatter()

更多

也可以使用相同的marker参数为pyplot.plot()访问标记样式。 对每个数据点使用一个标记可能是一个问题,因为它将显示比我们想要的更多的点。 markevery参数允许您为每个 N 点仅显示一个标记,如以下脚本所示:

import numpy as np
import matplotlib.pyplot as plt

X = np.linspace(-6, 6, 1024)
Y1 = np.sinc(X)
Y2 = np.sinc(X) + 1
plt.plot(X, Y1, marker = 'o', color = '.75')
plt.plot(X, Y2, marker = 'o', color = 'k', markevery = 32)

plt.show()

上面的脚本生成以下图形:

There's more...

控制标记的大小

从先前的秘籍中可以看出,我们可以控制标记的样式; 控制它们的大小也遵循相同的原则。 在本秘籍中,我们将了解如何控制标记大小。

操作步骤

标记的大小与其他标记属性的控制方式相同,具有专用的可选参数,如以下脚本所示:

import numpy as np
import matplotlib.pyplot as plt
A = np.random.standard_normal((100, 2))
A += np.array((-1, -1))
B = np.random.standard_normal((100, 2))
B += np.array((1, 1))

plt.scatter(B[:,0], B[:,1], c = 'k', s = 100.)
plt.scatter(A[:,0], A[:,1], c = 'w', s = 25.)

plt.show()

前面的脚本产生以下图形:

How to do it...

在此示例中,我们显示两组不同大小的点。 标记的大小由pyplot.scatter()的参数s设置的。 奇怪的是,它设置标记的表面积而不是其半径。

因为大小是实际表面积而不是半径,所以它们遵循二次方的变化-较大四倍的标记将具有两倍大的半径。

更多

pyplot.scatter()函数还将列表作为s参数的输入-每个点的大小,如以下脚本所示:

import numpy as np
import matplotlib.pyplot as plt

M = np.random.standard_normal((1000, 2))
R = np.sum(M ** 2, axis = 1)

plt.scatter(M[:, 0], M[:, 1], c = 'w', marker = 's', s = 32\. * R)
plt.show()

上面的脚本生成以下图形:

There's more...

在此脚本中,我们根据双变量高斯分布绘制了随机点。 点的半径取决于其距原点的距离。

pyplot.plot()函数还可以借助markersize(或其快捷方式ms)参数来更改标记的大小。 此参数不接受值列表作为输入。

创建自己的标记

matplotlib 提供了多种标记形状。 但是您可能找不到适合您的特定需求的东西。 例如,您可能希望使用动物剪影,公司徽标等。 在本秘籍中,我们将看到如何定义自己的标记形状。

操作步骤

matplotlib 将形状描述为路径-链接在一起的点序列。 因此,要定义我们自己的标记形状,我们必须提供一系列点。 在以下脚本示例中,我们将定义一个十字形形状:

import numpy as np
import matplotlib.path as mpath
from matplotlib import pyplot as plt

shape_description = [
  ( 1.,  2., mpath.Path.MOVETO),
  ( 1.,  1., mpath.Path.LINETO),
  ( 2.,  1., mpath.Path.LINETO),
  ( 2., -1., mpath.Path.LINETO),
  ( 1., -1., mpath.Path.LINETO),
  ( 1., -2., mpath.Path.LINETO),
  (-1., -2., mpath.Path.LINETO),
  (-1., -1., mpath.Path.LINETO),
  (-2., -1., mpath.Path.LINETO),
  (-2.,  1., mpath.Path.LINETO),
  (-1.,  1., mpath.Path.LINETO),
  (-1.,  2., mpath.Path.LINETO),
  ( 0.,  0., mpath.Path.CLOSEPOLY),
]

u, v, codes = zip(*shape_description)
my_marker = mpath.Path(np.asarray((u, v)).T, codes)
data = np.random.rand(8, 8)
plt.scatter(data[:,0], data[:, 1], c = '.75', marker = my_marker, s = 64)
plt.show()

上面的脚本生成以下图形:

How to do it...

工作原理

使用标记渲染图形的所有pyplot函数都有一个可选参数,即marker。 我们在前面的秘籍中已经看到,参数可以是用于选择预定义的 matplotlib 标记之一的字符串。 但是marker参数也可以是Path的实例。 Path对象在matplotlib.path模块中定义。

Path对象的构造器将坐标列表和指令列表作为输入; 每个坐标一个指令。 我们没有使用两个单独的坐标和指令列表,而是使用单个列表shape_description,将坐标和指令融合在一起。 一点点代码用于操纵shape_description并将坐标和指令的单独列表馈送到Path构造器,如下所示:

u, v, codes = zip(*shape_description)
my_marker = mpath.Path(np.asarray((u, v)).T, codes)

形状由光标的移动来描述。 我们使用以下三种类型的指令:

  • MOVETO:该指令将光标移动到指定坐标; 没有画线。
  • LINETO:在画一条线的同时将光标移动到指定的坐标。
  • CLOSEPOLY:它不会执行任何操作,它将关闭路径。 此指令将结束您的路径。

从理论上讲,任何形状都是可能的,您只需要描述其路径即可。 实际上,如果您希望使用复杂的形状(例如,公司的徽标),则必须进行一些转换工作。 matplotlib 不提供从流行的向量文件格式(例如 SVG)到Path对象的转换例程。

更好地控制标记

标记上可以进行精细控制,例如边缘颜色,内部颜色等。 例如,可以使用与曲线颜色不同颜色的标记来绘制曲线。 在本秘籍中,我们将研究如何对标记的外观进行精细控制。

操作步骤

我们已经了解了有关设置标记的形状,颜色和大小的可选参数。 如以下脚本所示,还有很多其他的玩法:

import numpy as np
import matplotlib.pyplot as plt

X = np.linspace(-6, 6, 1024)
Y = np.sinc(X)

plt.plot(X, Y,
  linewidth = 3.,
  color = 'k',
  markersize = 9,
  markeredgewidth = 1.5,
  markerfacecolor = '.75',
  markeredgecolor = 'k',
  marker = 'o',
  markevery = 32)
plt.show()

出于可读性考虑,对pyplot.plot()的调用分为几行-每个可选参数一行。 上面的脚本将产生以下图形:

How to do it...

工作原理

此示例演示了markeredgecolormarkerfacecolormarkeredgewidth参数的使用,它们分别控制边缘颜色,内部颜色和标记的线宽。 所有可以使用标记的渲染函数(例如pyplot.plot)都接受这些可选参数。

创建您自己的配色方案

matplotlib 使用的默认颜色是指对于印刷文档而言,它适合合理地发布。 因此,默认情况下背景为白色,而标签,轴和其他标注显示为黑色。 在不同的使用上下文中,您可能希望使用不同的配色方案。 例如,将图形的背景变为黑色,标注为白色。 在本秘籍中,我们将展示如何更改 matplotlib 的默认设置。

操作步骤

在 matplotlib 中,可以分别处理各种对象,例如轴域,图形和标签。 一件一件地更改所有这些对象的颜色设置将非常麻烦。 幸运的是,所有 matplotlib 对象都从集中式配置对象中选择其默认颜色。

在以下脚本中,我们使用 matplotlib 的集中式配置来具有黑色背景和白色标注:

import numpy as np
import matplotlib as mpl
from matplotlib import pyplot as plt

mpl.rc('lines', linewidth = 2.)
mpl.rc('axes', facecolor = 'k', edgecolor = 'w')
mpl.rc('xtick', color = 'w')
mpl.rc('ytick', color = 'w')
mpl.rc('text', color = 'w')
mpl.rc('figure', facecolor = 'k', edgecolor ='w')
mpl.rc('axes', color_cycle = ('w', '.5', '.75'))

X = np.linspace(0, 7, 1024)

plt.plot(X, np.sin(X))
plt.plot(X, np.cos(X))
plt.show()

上面的脚本生成以下图形:

How to do it...

工作原理

matplotlib模块具有用作集中配置的rc对象。 每个 matplotlib 对象都将从该rc对象中选择其默认设置。 rc对象包含一组属性和关联的值。 例如,mpl.rc('lines'linewidth = 2.)会将属性lines.linewidth设置为 2; 默认情况下,线条的宽度现在为 2。 在这里,我们将图形的背景设置为黑色(使用figure.facecoloraxes.facecolor属性),而将所有标注设置为白色(使用figure.edgecoloraxes.edgecolortext.colorxtick.colorytick.color属性)。 我们还使用axes.color_cycle属性重新定义了 matplotlib 自动选择的颜色。 有关 matplotlib 属性的很好参考,请访问这里

更多

现在,我们知道如何更改 matplotlib 的默认设置以适合我们的口味。 但是,如果我们希望所有脚本都使用这些设置,则必须复制并粘贴它们。 这很不方便。 幸运的是,默认设置可以保存在matplotlibrc文件中。 maptplotlibrc文件是纯文本文件,其中包含属性及其相应的值; 每行一个属性。 以下是此秘籍的matplotlibrc格式设置:

lines.linewidth : 2
axes.facecolor : black
axes.edgecolor : white
xtick.color : white
ytick.color : white
text.color : white
figure.facecolor : black
figure.edgecolor : white
axes.color_cycle : white, #808080, #b0b0b0

如果在当前目录(即从中启动脚本的目录)中找到了matplotlibrc文件,它将覆盖 matplotlib 的默认设置。

您也可以将matplotlibrc文件保存在特定位置以进行自己的默认设置。 在交互式 Python Shell 中,运行以下命令:

import matplotlib
mpl.get_configdir()

此命令将显示可放置matplotlibrc文件的位置,以便那些设置将是您自己的默认设置。

三、处理标注

在本章中,我们将介绍以下主题:

  • 添加标题
  • 使用 LaTeX 样式的符号
  • 在每个轴上添加标签
  • 添加文本
  • 添加箭头
  • 添加图例
  • 添加网格
  • 添加线条
  • 添加形状
  • 控制刻度线间距
  • 控制刻度标签

简介

使您的图表不言自明是一种很好的做法。 但是,很难在没有任何标注的情况下使一些曲线和点不言自明。 人们应该如何读取垂直和水平轴? 该框和该曲线代表哪个数量? matplotlib 提供了很多标注图形的可能性,我们将在本章中进行探讨。

添加标题

让我们从简单的事情开始:为图形添加标题。

操作步骤

以下代码将添加到图形标题:

import numpy as np
import matplotlib.pyplot as plt

X = np.linspace(-4, 4, 1024)
Y = .25 * (X + 4.) * (X + 1.) * (X - 2.)

plt.title('A polynomial')
plt.plot(X, Y, c = 'k')
plt.show()

在这里,我们绘制一条简单的曲线,并在图形的顶部添加一个标题,该标题显示在图形的顶部:

How to do it...

工作原理

简单地通过 pyplot.title()函数完成,该函数将一个字符串作为参数并设置整个图形的标题。

使用 LaTeX 样式的符号

现在我们可以标注数字。 但是,在科学和工程方面,以前演示的解决方案受到一个令人讨厌的限制。 我们不能使用数学符号! 或者,可以吗? 在本秘籍中,我们将了解如何使用 LaTeX 在图中显示和数学脚本。

准备

您需要在计算机上安装可运行的 LaTeX 设置,以便 matplotlib 可以解释 LaTeX 样式的表示法以呈现数学文本。 否则,您将无法尝试此秘籍。 您可以在 LaTeX Wikibook 上找到有关安装 LaTeX 的有用说明。

提示

LaTeX

LaTeX 是学术界广泛使用的文档准备系统。 与 Microsoft Word 或 LibreOffice Writer 等文档编辑器不同,LaTeX 用户无法看到最终文档在编辑时的外观。 文档被描述为文本和存储在纯文本文件中的命令的混合。 然后,LaTeX 将解释文档描述以呈现文档。 LaTeX 是一个相当大的环境。 LaTeX 具有用于描述数学文本的特定语言。 这种语言非常流行,以至于简单地编写公式而不是呈现它们已成为事实上的标准。 例如,在科学和工程界,LaTeX 的公式语言通常用于在电子邮件和论坛中编写数学文本。

操作步骤

使用 LaTeX 渲染一些文本非常简单:

import numpy as np
import matplotlib.pyplot as plt

X = np.linspace(-4, 4, 1024)
Y = .25 * (X + 4.) * (X + 1.) * (X - 2.)

plt.title('$f(x)=\\frac{1}{4}(x+4)(x+1)(x-2)$')
plt.plot(X, Y, c = 'k')
plt.show()

该脚本完全按照我们之前的秘籍进行操作:在顶部显示一个带有标题的图形。 但是,正如秘籍的标题可能暗示的那样,该标题使用 LaTeX 渲染,使我们可以使用数学表示法。

How to do it...

工作原理

与通常设置标题的方式的唯一区别是pyplot.title()提供的字符串。 字符串以$字符开头和结尾; 这是为了通知 matplotlib 将文本解释和呈现为 LaTeX 样式的数学文本。 然后,字符串内容只是数学文本的标准 LaTeX 语言。

LaTeX 语言严重依赖转义字符\,该字符也恰好是 Python 的字符串转义字符。 因此,在 LaTeX 文本中使用一个\字符的地方,请在 Python 字符串中放入两个。 为避免弄乱转义字符,您可以在字符串前面添加r,并且不需要任何转义字符。 因此,'$f(x)=\\frac{1}{4}(x+4)(x+1)(x-2)$'r'$f(x)=\frac{1}{4}(x+4)(x+1)(x-2)$'是等效的。

注意

您不懂数学文字的 LaTeX 语言吗? 不用担心,您可以快速学习! 在 matplotlib 上下文中,您可以在这个页面中找到权威指南。 可以在这个页面上找到相当完整的教程。

此 LaTeX 标注函数不限于标题。 它可以用于任何标注。 在这里,我们仅在标题文本上进行演示。

在每个轴上添加标签

除了标题,图形轴的正确描述有助于用户理解图形。 在本秘籍中,我们将向您展示如何在图形的每个轴旁边获取标签。

操作步骤

添加此类标注非常简单,如以下示例所示:

import numpy as np
import matplotlib.pyplot as plt

X = np.linspace(-4, 4, 1024)
Y = .25 * (X + 4.) * (X + 1.) * (X - 2.)

plt.title('Power curve for airfoil KV873')
plt.xlabel('Air speed')
plt.ylabel('Total drag')

plt.plot(X, Y, c = 'k')
plt.show()

该图将与本章第一个秘籍中获得的图相同。 但是,两个轴域都具有图例。

How to do it...

工作原理

我们使用 pyplot.xlabel()pyplot.ylabel()函数分别为的水平轴和的垂直轴添加描述。 至于pyplot.title()函数,该函数接受 LaTeX 表示法。 这些函数可用于任何图形。 您将使用相同的函数来标注散点图,直方图等。

添加文本

到目前为止,我们已经了解了如何在预设位置(例如标题和轴)设置文本。 在本秘籍中,我们将了解如何使用文本框在任何位置添加文本。

操作步骤

matplotlib 具有灵活的函数,称为 pyplot.text(),该函数显示文本:

import numpy as np
import matplotlib.pyplot as plt
X = np.linspace(-4, 4, 1024)
Y = .25 * (X + 4.) * (X + 1.) * (X - 2.)

plt.text(-0.5, -0.25, 'Brackmard minimum')

plt.plot(X, Y, c = 'k')
plt.show()

该脚本在曲线旁边显示文本:

How to do it...

工作原理

我们使用pyplot.text()函数来获取位置和要显示的文本。 位置在图形坐标中给出,指定文本的左边框和垂直基线的位置。

更多

matplotlib 的文本呈现非常灵活。 让我们探索可用的重要选项。

对齐控制

文本由框限制。 此框用于使文本与传递给pyplot.text()的坐标相对对齐。 使用verticalalignmenthorizontalalignment参数(相应的快捷方式分别为vaha),我们可以控制对齐方式。

垂直对齐选项如下:

  • 'center':相对于文本框的中心

  • 'top':相对于文本框的上侧

  • 'bottom':相对于文本框的下侧

  • 'baseline':相对于文本的基线

    Alignment control

水平对齐选项如下:

  • 'center':相对于文本框的中心

  • 'left':相对于文本框的左侧

  • 'right':相对于文本框的右侧

    Alignment control

边界框控件

pyplot.text()函数支持bbox参数,其中将字典作为输入。 该词典定义了文本框的各种设置。 这是一个例子:

import numpy as np
import matplotlib.pyplot as plt

X = np.linspace(-4, 4, 1024)
Y = .25 * (X + 4.) * (X + 1.) * (X - 2.)

box = {
  'facecolor'  : '.75',
  'edgecolor' : 'k',
  'boxstyle'    : 'round'
}

plt.text(-0.5, -0.20, 'Brackmard minimum', bbox = box)

plt.plot(X, Y, c='k')
plt.show()

前面的代码将给出以下输出:

Bounding box control

将传递给bbox参数的字典定义了以下键值对:

  • 'facecolor':这是用于包装盒的颜色。 它将用于设置背景和边缘颜色
  • 'edgecolor':这是用于盒子形状边缘的颜色
  • 'alpha':用于设置透明度级别,以使框与背景融合
  • 'boxstyle':设置框的样式,可以是'round''square'
  • 'pad':如果'boxstyle'设置为'square',则定义文本和框的两边之间的填充量

添加箭头

添加文本框可以帮助您标注图形。 但是,要显示图片的特定部分,使用箭头没有什么可比的。 在本秘籍中,我们将向您展示如何在图形上添加箭头。

操作步骤

matplotlib 具有使用pyplot.annotate()函数绘制箭头的功能,如以下代码片段所示:

import numpy as np
import matplotlib.pyplot as plt

X = np.linspace(-4, 4, 1024)
Y = .25 * (X + 4.) * (X + 1.) * (X - 2.)

plt.annotate('Brackmard minimum',
ha = 'center', va = 'bottom',
xytext = (-1.5, 3.),
xy = (0.75, -2.7),
arrowprops = { 'facecolor' : 'black', 'shrink' : 0.05 })

plt.plot(X, Y)
plt.show()

该脚本用文本和箭头标注一条曲线,如下图所示:

How to do it...

工作原理

pyplot.annotate()函数显示与pyplot.text()相同行的文本。 但是,也会显示一个箭头。 要显示的文本是第一个参数。 xy参数指定箭头的目的地。 xytext参数指定文本位置。 与pyplot.text()相似,可以通过horizontalalignmentverticalalignment参数进行文本对齐。 shrink参数控制箭头的端点和箭头本身之间的间隙。

箭头的方向由传递给 arrowprops参数的字典控制:

  • 'arrowstyle':参数''<-''''<''''-''''wedge''''simple''"fancy"控制箭头的样式

  • 'facecolor':这是箭头所使用的颜色。 它将用于设置背景和边缘颜色

  • 'edgecolor':这是箭头形状边缘使用的颜色

  • 'alpha': 这用于设置透明度,以便箭头与背景融合

    How it works...

添加图例

没有自己的传说,一个合适的图表是不完整的。 matplotlib 提供了一种以最少的精力生成图例的方法。 在本秘籍中,我们将看到如何在图上添加图例。

操作步骤

对于此秘籍,我们使用pyplot.legend()函数以及label可选参数:

import numpy as np
import matplotlib.pyplot as plt

X = np.linspace(0, 6, 1024)
Y1 = np.sin(X)
Y2 = np.cos(X)

plt.xlabel('X')
plt.ylabel('Y')

plt.plot(X, Y1, c = 'k',  lw = 3.,              label = 'sin(X)')
plt.plot(X, Y2, c = '.5', lw = 3., ls = '--', label = 'cos(X)')

plt.legend()
plt.show()

上面的代码提供以下输出:

How to do it...

工作原理

每个pyplot函数都有可选的label参数,以命名元素,例如图形的曲线,直方图等。 matplotlib 跟踪这些标签。 pyplot.legend()函数将渲染图例。 图例是从标签自动生成的。

更多

pyplot.legend函数具有几个有趣的参数来控制图例方面:

  • 'loc':这是图例的位置。 默认值为'best',它将自动放置它。 其他有效值是'upper left''lower left''lower right''right''center left''center right''lower center''upper center''center'
  • 'shadow':可以是TrueFalse,并使用阴影效果渲染图例。
  • 'fancybox':可以是TrueFalse,并用圆角框显示图例。
  • 'title':这将通过带有作为参数传递的标题的图例进行渲染。
  • 'ncol':强制传递的值是图例的列数。

添加网格

在准备图形时,我们可能需要快速猜测图形任何部分的坐标。 在图形上添加网格是提高图形可读性的自然方法。 在本秘籍中,我们将看到如何在图形上添加网格。

操作步骤

matplotlib 的网格功能由 pyplot.grid()函数控制。

import numpy as np
import matplotlib.pyplot as plt

X = np.linspace(-4, 4, 1024)
Y = .25 * (X + 4.) * (X + 1.) * (X - 2.)

plt.plot(X, Y, c = 'k')
plt.grid(True, lw = 2, ls = '--', c = '.75')
plt.show()

该脚本将显示在背景中带有网格的曲线。 网格与坐标轴图例的刻度对齐,如下图所示:

How to do it...

工作原理

添加网格就像一样简单,就像用True作为参数调用pyplot.grid()函数一样。 网格由线条组成,因此pyplot.grid()接受线条样式参数,例如linewidthlinestylecolor。 这些参数将应用于绘制网格的线。

添加线条

当您有非常具体的需求时,matplotlib 提供的数据可能对您没有太大帮助。 matplotlib 制作的所有图形均由基本图元组成。 在演示如何更改箱形图的颜色时,我们提到大多数 matplotlib 绘图函数都会返回线条和形状的集合。 现在,我们将演示如何直接使用基本图元。

操作步骤

以下脚本将显示由独立行构成的简单但美观的模式:

import matplotlib.pyplot as plt

N = 16
for i in range(N):
  plt.gca().add_line(plt.Line2D((0, i), (N - i, 0), color = '.75'))

plt.grid(True)
plt.axis('scaled')
plt.show()

上面的代码提供以下输出:

How to do it...

工作原理

在此脚本中,我们绘制了 16 条独立的线。 pyplot.Line2D()函数创建一个新的Line2D对象。 强制性参数是该行的端点。 可选参数是我们之前针对基于线的图形所见的所有参数。 因此,您可以使用linestylelinewidthmarkermarkersizecolor等。

pyplot.Line2D()函数创建了该行,但是除非您明确要求,否则不会渲染该行; 这是使用pyplot.gca().add_line()完成的。 pyplot.gca()函数返回负责跟踪渲染内容的对象。 调用gca().add_line()只是表示我们要渲染一条线。

需要使用pyplot.axis('scaled')函数以确保图形使用统一的比例尺:与 xy 轴上使用的比例尺相同。 这与默认行为'tight'形成对比,其中默认值 matplotlib 将为 xy 轴赋予不同的比例,以使图形尽可能紧密地适合显示表面。 此函数将在第 4 章,“处理图形”中介绍。

添加形状

要使用基本图元制作自己的图形,线条是很好的起点,但是您很可能需要更多形状。 渲染形状的工作方式与渲染线相同。 在本秘籍中,我们将向您展示如何在图形中添加形状。

操作步骤

在以下脚本中,我们创建并渲染几个形状。 标注指示哪个部分呈现哪种形状:

import matplotlib.patches as patches
import matplotlib.pyplot as plt

## Circle
shape = patches.Circle((0, 0), radius = 1., color = '.75')
plt.gca().add_patch(shape)

## Rectangle
shape = patches.Rectangle((2.5, -.5), 2., 1., color = '.75')
plt.gca().add_patch(shape)

## Ellipse
shape = patches.Ellipse((0, -2.), 2., 1., angle = 45., color = '.75')
plt.gca().add_patch(shape)

## Fancy box
shape = patches.FancyBboxPatch((2.5, -2.5), 2., 1., boxstyle = 'sawtooth', color = '.75')
plt.gca().add_patch(shape)

## Display all
plt.grid(True)
plt.axis('scaled')
plt.show()

输出中显示了四种不同的形状,如以下屏幕截图所示:

How to do it...

工作原理

无论显示哪种形状,原理都相同。 在内部,形状被描述为在 matplotlib API 中称为“补丁”的路径。 matplotlib.patches模块中提供了几种形状的路径。 实际上,该模块包含用于所有附图的补丁。 与线条一样,创建路径不足以渲染它。 您将必须表示要渲染它。 这是通过pyplot.gca().add_patch()完成的。

许多路径构造器都可用。 让我们回顾一下示例中使用的那些:

  • Circle:将其中心坐标和半径作为参数
  • Ractangle:将其左下角的坐标及其大小作为参数
  • Ellipse:将其中心坐标和两个轴的半长作为参数
  • FancyBox :这就像一个矩形,但带有一个附加的boxstyle参数('larrow''rarrow''round''round4''roundtooth''sawtooth''square'

更多

除了预定义的形状,我们还可以使用多边形定义任意形状。

使用多边形

多边形仅比路径复杂,并且由点列表定义:

import numpy as np
import matplotlib.patches as patches
import matplotlib.pyplot as plt

theta = np.linspace(0, 2 * np.pi, 8)
points = np.vstack((np.cos(theta), np.sin(theta))).transpose()

plt.gca().add_patch(patches.Polygon(points, color = '.75'))

plt.grid(True)
plt.axis('scaled')
plt.show()

前面的代码提供以下多边形作为输出:

Working with polygons

matplotlib.patches.Polygon()构造器将坐标列表作为输入,即多边形的顶点。

使用路径属性

所有路径都有我们先前已经探讨过的几个属性:linewidthlinestyleedgecolorfacecolorhatch等,如下所示:

import numpy as np
import matplotlib.patches as patches
import matplotlib.pyplot as plt

theta = np.linspace(0, 2 * np.pi, 6)
points = np.vstack((np.cos(theta), np.sin(theta))).transpose()

plt.gca().add_patch(plt.Circle((0, 0), radius = 1., color = '.75'))
plt.gca().add_patch(plt.Polygon(points, closed=None, fill=None, lw = 3., ls = 'dashed', edgecolor = 'k'))

plt.grid(True)
plt.axis('scaled')
plt.show()

下图是上述代码的输出:

Working with path attributes

在这里,我们使用具有虚线边缘(ls = 'dashed')的非填充(fill = None)多边形绘制多边形轮廓,而不必创建多个线对象。 仅通过使用路径的属性就可以实现许多效果。

控制刻度线间距

在 matplotlib 中,刻度在图形的两个轴上都是小标记。 到目前为止,我们让 matplotlib 处理轴域图例上刻度线的位置。 正如我们将在本秘籍中看到的那样,我们可以手动覆盖此机制。

操作步骤

在此脚本中,我们将操纵 x 轴上的刻度线之间的间隙:

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.ticker as ticker

X = np.linspace(-15, 15, 1024)
Y = np.sinc(X)

ax = plt.axes()
ax.xaxis.set_major_locator(ticker.MultipleLocator(5))
ax.xaxis.set_minor_locator(ticker.MultipleLocator(1))

plt.plot(X, Y, c = 'k')
plt.show()

现在,在通常的刻度之间可以看到较小的刻度:

How to do it...

工作原理

我们强制水平刻度线以 5 个单位显示。 此外,我们还添加了小刻度,以 1 个单位的步长出现。 为此,我们执行以下步骤:

  1. 我们获得了Axes对象的实例:管理图形轴域的对象。 这是ax = plot.axes()的目的。
  2. 对于 x 轴(ax.xaxis),我们为主要和次要刻度线设置了一个Locator实例。

更多

如果我们希望添加网格,可以考虑较小的刻度,如下所示:

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.ticker as ticker

X = np.linspace(-15, 15, 1024)
Y = np.sinc(X)
ax = plt.axes()
ax.xaxis.set_major_locator(ticker.MultipleLocator(5))
ax.xaxis.set_minor_locator(ticker.MultipleLocator(1))

plt.grid(True, which='both')
plt.plot(X, Y)
plt.show()

前面的代码代码段给出以下输出:

There's more...

如前所示,我们可以使用pyplot.grid()添加一个网格。 此函数采用可选参数which。 它可以接受三个值:'minor''major''both'。 它确定应在哪个刻度上显示网格。

控制刻度标签

刻度标签是图形空间中的坐标。 尽管在很多情况下都有意义,但这并不总是足够的。 例如,让我们想象一个条形图,该条形图显示 10 个国家的中位数收入。 我们希望看到每个条形下的国家名称,而不是条形的坐标。 对于时间序列,我们希望看到日期,而不是一些抽象坐标。 matplotlib 为此提供了一个全面的 API。 在本秘籍中,我们将了解如何控制刻度标签。

操作步骤

使用标准的 matplotlib 刻度 API,可以如下设置条形图(或任何其他类型的图形)的刻度:

import numpy as np
import matplotlib.ticker as ticker
import matplotlib.pyplot as plt

name_list = ('Omar', 'Serguey', 'Max', 'Zhou', 'Abidin')
value_list = np.random.randint(0, 99, size = len(name_list))
pos_list = np.arange(len(name_list))

ax = plt.axes()
ax.xaxis.set_major_locator(ticker.FixedLocator((pos_list)))
ax.xaxis.set_major_formatter(ticker.FixedFormatter((name_list)))

plt.bar(pos_list, value_list, color = '.75', align = 'center')
plt.show()

条形图的每个条形都有自己的刻度和图例:

How to do it...

工作原理

我们已经看到了ticker.Locator来生成刻度线的位置。 ticker.Formatter对象实例将为刻度生成标签。 我们在这里使用的Formatter实例是FixedFormatter,它将从字符串列表中获取标签。 然后,使用Formatter实例设置 x 轴。 对于此特定示例,我们还使用FixedLocator来确保每个小节位于一个刻度的中间。

更多

我们几乎没有触及这个话题的表面。 关于刻度的更多信息。

一种创建带有固定标签的条形图的更简单方法

对于条形图固定标签的特殊情况,我们可以利用快捷方式的优势:

import numpy as np
import matplotlib.pyplot as plt

name_list = ('Omar', 'Serguey', 'Max', 'Zhou', 'Abidin')
value_list = np.random.randint(0, 99, size = len(name_list))
pos_list = np.arange(len(name_list))

plt.bar(pos_list, value_list, color = '.75', align = 'center')
plt.xticks(pos_list, name_list)
plt.show()

前面的代码代码段给出了以下条形图:

A simpler way to create bar charts with fixed labels

而不是使用股票代号 API,我们使用pyplot.xticks()函数为一组固定的代号提供了修复标签。 该函数将位置列表和名称列表作为参数。 结果与前面的示例相同; 它更短,更容易记住。

高级标签生成

如果代码 API 的重点是我们周围有快捷方式怎么办? 报价器 API 的效果要好于,而不是为每个报价显示固定标签,如下所示:

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.ticker as ticker

def make_label(value, pos):
  return '%0.1f%%' % (100\. * value)

ax = plt.axes()
ax.xaxis.set_major_formatter(ticker.FuncFormatter(make_label))

X = np.linspace(0, 1, 256)
plt.plot(X, np.exp(-10 * X), c ='k')
plt.plot(X, np.exp(-5 * X), c= 'k', ls = '--')

plt.show()

上面的代码提供以下输出:

Advanced label generation

在此示例中,滴答是由自定义函数make_label生成的。 此函数将刻度的坐标作为输入并生成字符串。 在这里,一个百分比。 无论 matplotlib 决定显示多少滴答声,我们都可以为其生成正确的标签。 这比给出固定的字符串列表更加灵活。 这里唯一的新东西是FuncFormatter,它是将函数作为参数的格式化程序。

将生成标签的实际任务委派给函数的这种方法称为委托 。 我们的代表是make_label。 这是一种美丽的编程技术。 假设我们要显示每个刻度的日期。 这可以使用标准的 Python 时间和日期函数来完成:

import numpy as np
import datetime
import matplotlib.pyplot as plt
import matplotlib.ticker as ticker
start_date = datetime.datetime(1998, 1, 1)

def make_label(value, pos):
  time = start_date + datetime.timedelta(days = 365 * value)
  return time.strftime('%b %y')

ax = plt.axes()
ax.xaxis.set_major_formatter(ticker.FuncFormatter(make_label))

X = np.linspace(0, 1, 256)
plt.plot(X, np.exp(-10 * X), c = 'k')
plt.plot(X, np.exp(-5 * X), c = 'k', ls = '--')

labels = ax.get_xticklabels()
plt.setp(labels, rotation = 30.)
plt.show()

前面的代码提供以下输出:

Advanced label generation

现在,每个刻度显示为以人类可读的格式设置的日期。 该方法与我们之前使用的方法相同:我们使用FuncFormatter。 在标签生成函数中,借助datetime标准模块,我们将刻度的位置转换为日期。 在这里,我们将[0, 1]范围内的值映射到 1998 年。datetime模块还提供了强大的格式化函数strftime,我们可以使用它来生成标签本身。

四、处理图形

在本章中,我们将介绍:

  • 合并多个图形
  • 均等缩放两个轴
  • 设定轴范围
  • 设定长宽比
  • 插入子图
  • 使用对数刻度
  • 使用极坐标

简介

设计科学的绘图包是一项艰巨的任务-涉及的需求极为多样化。 一方面,理想情况下,只需很少的编码和摆弄就可以创建任何一种图形。 另一方面,我们希望能够自定义图形的任何方面。 这两个目标是截然相反的。 matplotlib 在这两个目标之间提供了罕见的平衡。 在本章中,我们将探讨修改库存数据基本方面的方法,例如更改所使用的坐标系。

合并多个图形

在检查某些数据时,我们可能希望同时查看它的多个方面。 例如,利用来自一个国家的人口统计数据,我们希望看到男性/女性年龄金字塔,财富分配和每年的人口规模是三个不同的图形。 matplotlib 提供了将多个图形组合在一起的可能性。 从 1.2 版开始,用于此的 API 确实非常方便。 在本秘籍中,我们将了解如何将多个图形组合在一起。

操作步骤

将使用和pyplot.subplot2grid()函数,如下所示:

import numpy as np
from matplotlib import pyplot as plt

T = np.linspace(-np.pi, np.pi, 1024)

grid_size = (4, 2)

plt.subplot2grid(grid_size, (0, 0), rowspan = 3, colspan = 1)
plt.plot(np.sin(2 * T), np.cos(0.5 * T), c = 'k')

plt.subplot2grid(grid_size, (0, 1), rowspan = 3, colspan = 1)
plt.plot(np.cos(3 * T), np.sin(T), c = 'k')

plt.subplot2grid(grid_size, (3, 0), rowspan=1, colspan=3)

plt.plot(np.cos(5 * T), np.sin(7 * T), c= 'k')

plt.tight_layout()
plt.show()

绘制了三个图形,将图形分为三个区域,如下所示:

How to do it...

工作原理

pyplot.subplot2grid()之后的想法是定义 R 行和 C 列的网格。 然后,我们可以将图形渲染到该网格的矩形补丁。

pyplot.subplot2grid()函数具有四个参数:

  • 第一个参数是作为元组传递的网格的行数和列数。 如果我们想要一个由R行和C列组成的网格,我们将传递(R, C)
  • 第二个参数是网格中的行和列坐标,也作为元组传递。
  • 可选参数rowspan定义图形将跨越多少行。
  • 可选参数colspan定义图形将跨越多少列。

一旦调用pyplot.subplot2grid(),对pyplot的进一步调用将在指定的矩形区域内定义图形。 为了在另一个区域渲染另一个图形,我们再次调用pyplot.subplot2grid()

在示例脚本中,我们定义了2 x 4的网格。 前两个数字跨越 1 列和 3 行,因此每个填充几乎一整列。 第三个数字跨越 2 列,但只有 1 行,填充了底行。 描述完所有附图后,我们将其称为pyplot.tight_layout()。 此命令要求 matplotlib 打包所有图形,以使它们彼此不重叠。

更多

我们已经看到pyplot.title()在图形上添加了标题。 在下面的示例中,我们使用pyplot.title()为每个子图赋予标题:

import numpy as np
from matplotlib import pyplot as plt

def get_radius(T, params):
  m, n_1, n_2, n_3 = params
  U = (m * T) / 4

  return (np.fabs(np.cos(U)) ** n_2 + np.fabs(np.sin(U)) ** n_3) ** (-1\. / n_1)

grid_size = (3, 4)
T = np.linspace(0, 2 * np.pi, 1024)

for i in range(grid_size[0]):
  for j in range(grid_size[1]):
    params = np.random.random_integers(1, 20, size = 4)
    R = get_radius(T, params)

    axes = plt.subplot2grid(grid_size, (i, j), rowspan=1, colspan=1)
    axes.get_xaxis().set_visible(False)
    axes.get_yaxis().set_visible(False)

    plt.plot(R * np.cos(T), R * np.sin(T), c = 'k')
    plt.title('%d, %d, %d, %d' % tuple(params), fontsize = 'small')

plt.tight_layout()
plt.show()

下图包含 12 个图形,每个图形都有自己的标题:

There's more...

pyplot.title()函数为一个子图提供标题。 如果整个图形需要一个标题,则应使用pyplot.suptitle(),其中suptitle代表超级标题。

合并图形的另一种方法

这里介绍的子图机制相当笼统。 它使我们可以创建复杂的布局。 如果我们只需要在一行或一列中包含几个数字,则可以使用更简单的代码,如下所示:

import numpy as np
from matplotlib import pyplot as plt

T = np.linspace(-np.pi, np.pi, 1024)

fig, (ax0, ax1) = plt.subplots(ncols =2)
ax0.plot(np.sin(2 * T), np.cos(0.5 * T), c = 'k')
ax1.plot(np.cos(3 * T), np.sin(T), c = 'k')

plt.show()

只需调用一次pyplot.subplots(),我们就创建了两个彼此相邻的子图,如下图所示:

An alternative way to composite figures

pyplot.subplot()函数具有两个可选参数ncolsnrows,并将返回带有ncols * nrows实例的AxesFigure对象。 Axes实例通过nrows行布置在ncols列的网格中。 这使得网格布局非常容易创建。

均等缩放两个轴

默认情况下,matplotlib 将对图形的两个轴使用不同的比例。 在本秘籍中,我们将了解如何对图形的两个轴使用相同的比例。

操作步骤

要完成,我们需要使用pyplot API 和Axes对象,如以下代码所示:

import numpy as np
import matplotlib.pyplot as plt
T = np.linspace(0, 2 * np.pi, 1024)

plt.plot(2\. * np.cos(T), np.sin(T), c = 'k', lw = 3.)
plt.axes().set_aspect('equal')

plt.show()

前面的脚本用其实际的长宽比绘制了一个椭圆,如下所示:

How to do it...

工作原理

在此示例中,我们显示一个椭圆,其中长轴是短轴长度的两倍。 实际上,渲染的椭圆遵循这些比例。

pyplot.axes()函数返回Axes对象的实例,该对象负责轴域。 Axes实例具有set_aspect方法,我们将其设置为'equal'。 现在,两个轴使用相同的比例。 如果我们未设置相同的方面,则该图看起来会有所不同。

How it works...

上图仍是椭圆形,但长宽比变形。

设置轴范围

默认情况下,matplotlib 将在两个轴上查找数据的最小值和最大值,并将其用作绘制数据的范围。 但是,有时最好手动设置此范围,以更好地了解数据的极值。 在本秘籍中,我们将了解如何设置轴范围。

操作步骤

pyplot API 提供了直接设置一个轴范围的函数,如下所示:

import numpy as np
import matplotlib.pyplot as plt
X = np.linspace(-6, 6, 1024)

plt.ylim(-.5, 1.5)
plt.plot(X, np.sinc(X), c = 'k')
plt.show()

前面的脚本绘制了曲线。 与默认设置相反,该图形不能完美地拟合曲线。 我们在曲线的上部有一些空间,如下图所示:

How to do it...

工作原理

pyplot.xlim()pyplot.ylim()参数允许我们分别控制 x 轴和 y 轴的范围。 这些参数是最大值和最小值。

设置长宽比

在为期刊出版物或网站准备图形时,可能需要一个具有特定长宽比的图形。 在本秘籍中,我们将看到如何控制图形的长宽比。

操作步骤

pyplot API 提供了一种设置自定义长宽比的简单方法,如下所示:

import numpy as np
import matplotlib.pyplot as plt

X = np.linspace(-6, 6, 1024)
Y1, Y2 = np.sinc(X), np.cos(X)

plt.figure(figsize=(10.24, 2.56))
plt.plot(X, Y1, c='k', lw = 3.)
plt.plot(X, Y2, c='.75', lw = 3.)

plt.show()

下图的长宽比与我们默认情况下的长宽比有很大不同:

How to do it...

工作原理

我们使用pyplot.figure()函数,该函数创建一个新的Figure实例。 Figure对象整体上代表图形。 通常,该对象是在幕后隐式创建的。 但是,通过显式创建对象,我们可以控制图形的各个方面,包括其长宽比。 figsize参数允许我们指定其大小。 在此示例中,我们将水平尺寸设置为垂直尺寸的四倍,使其长宽比为 4:1。

插入子图

插入一个小的嵌入式图形可以帮助显示图形的细节,或更一般而言,可以强调图形的特定部分。 在本秘籍中,我们将看到如何在图形中插入子图形。

操作步骤

matplotlib 允许我们在图形的任何部分中创建子区域,并将图形分配给该子区域。 在以下示例中,创建了一个子区域以显示曲线的详细信息:

import numpy as np
from matplotlib import pyplot as plt

X = np.linspace(-6, 6, 1024)
Y = np.sinc(X)

X_detail = np.linspace(-3, 3, 1024)
Y_detail = np.sinc(X_detail)

plt.plot(X, Y, c = 'k')

sub_axes = plt.axes([.6, .6, .25, .25])
sub_axes.plot(X_detail, Y_detail, c = 'k')
plt.setp(sub_axes)

plt.show()

该子区域显示在该图的右上方。

How to do it...

工作原理

我们首先从在图上创建一个子区域,如下所示:

sub_axes = plt.axes([.6, .6, .25, .25])

该区域以图形坐标表示; 也就是说,(0, 0)是整个图形的左下角,(1, 1)是整个图形的右上角。 该子区域由四个值定义:区域左下角的坐标及其尺寸。

定义子区域后,我们将在其中创建图形的Axes实例。 然后,我们需要在上调用实例上的pyplot.setp(),如下所示:

plt.setp(sub_axes)

请注意,可以创建多少个子区域没有限制。

使用对数刻度

当可视化很大范围内变化的数据时,对数刻度使我们能够可视化否则很难看到的变化。 在本秘籍中,我们将向您展示如何操纵图形的缩放系统。

操作步骤

有几种方法可以将设置为对数刻度。 此处完成的方式适用于任何类型的图形,而不仅适用于线形图。 在下面的示例中,我们设置了对数刻度,该刻度将应用于所有绘图元素:

import numpy as np
import matplotlib.pyplot as plt

X = np.linspace(1, 10, 1024)

plt.yscale('log')
plt.plot(X, X, c = 'k', lw = 2., label = r'$f(x)=x$')
plt.plot(X, 10 ** X, c = '.75', ls = '--', lw = 2., label = r'$f(x)=e^x$')
plt.plot(X, np.log(X), c = '.75', lw = 2., label = r'$f(x)=\log(x)$')

plt.legend()
plt.show()

下图显示了几条曲线,垂直轴使用对数刻度:

How to do it...

工作原理

在此示例中,显示三个函数, y 轴遵循对数刻度。 所有工作都由pyplot.yscale()完成,我们在其中传递'log'来指定我们想要的比例尺类型。 同样,我们将使用plot.xscale()至在 x 轴上获得相同的结果。 可以很简单地创建对数-对数图,如下所示:

plt.xscale('log')
plt.yscale('log')

对数底数默认为 10,但可以使用可选参数basexbasey进行更改。

更多

使用对数刻度在放大很大范围数据上的一个小范围上也很有用,如以下示例所示:

import numpy as np
import matplotlib.pyplot as plt

X = np.linspace(-100, 100, 4096)

plt.xscale('symlog', linthreshx=6.)
plt.plot(X, np.sinc(X), c = 'k')

plt.show()

曲线的中心部分([-6, 6]范围)以线性标尺显示,而其他部分以对数标尺显示在下图中:

There's more...

在这里,我们将'symlog'作为参数pyplot.xscale()的参数,该参数是以 0 为中心的对称对数刻度。通过设置'linthreshx=6.',我们指定在[-6, 6]范围内,我们希望线性和对数刻度在这个范围。 这样,我们就可以在一个范围内获得详细的视图,同时仍然可以查看很大范围的剩余数据。

使用极坐标

一些现象具有角性质。 一个例子就是扬声器的功率,具体取决于我们从中测量扬声器的角度。 极坐标是表示此类数据的自然选择。 此外,可以方便地在极坐标中绘制诸如年度或每日统计数据之类的循环数据。 在本秘籍中,我们将了解如何使用极坐标。

操作步骤

让我们绘制一个简单的极坐标曲线,如下所示:

import numpy as np
import matplotlib.pyplot as plt

T = np.linspace(0 , 2 * np.pi, 1024)
plt.axes(polar = True)
plt.plot(T, 1\. + .25 * np.sin(16 * T), c= 'k')

plt.show()

下图显示了极坐标图的特殊布局:

How to do it...

工作原理

正如我们之前看到的,pyplot.axes() 显式创建一个Axes实例,该实例允许进行一些自定义设置。 只需使用可选的polar参数即可设置极坐标投影。 请注意图例如何适应投影。

更多

绘制曲线可能是极投影的最常见用法。 但是,我们可以使用其他任何类型的图,例如条形图和显示形状。 例如,使用极坐标投影和多边形,您可以绘制雷达图。 使用以下代码执行此操作:

import numpy as np
import matplotlib.patches as patches
import matplotlib.pyplot as plt
ax = plt.axes(polar = True)

theta = np.linspace(0, 2 * np.pi, 8, endpoint = False)
radius = .25 + .75 * np.random.random(size = len(theta))
points = np.vstack((theta, radius)).transpose()

plt.gca().add_patch(patches.Polygon(points, color = '.75'))
plt.show()

下图显示了我们用极坐标定义的多边形:

There's more...

注意,多边形的坐标是到原点的角度和距离。 我们不需要执行从极坐标到直角坐标的显式转换。

五、文件输出

在本章中,我们将介绍:

  • 生成 PNG 图片文件
  • 处理透明度
  • 控制输出分辨率
  • 生成 PDF 或 SVG 文档
  • 处理多页 PDF 文档

简介

像其他类型的技术图表一样,科学图表很少是独立的文档,它们应成为文档的一部分。 matplotlib 可以将任何图形渲染为各种常见的文件格式,例如 PNG,EPS,SVG 和 PDF。 默认情况下,显示的图形具有简约的用户界面,可让您将图形保存到文件中。 但是,如果必须生成大量图形,则此方法不方便。 此外,您可能希望每次更新某些数据时都能生成一个新图形。 在本章中,我们将探讨 matplotlib 的文件输出功能。 除了以编程方式生成文件输出之外,我们还将学习如何控制重要因素,例如输出的分辨率和大小以及透明度。

生成 PNG 图片文件

默认情况下,matplotlib 在带有基本用户界面的窗口中显示图形。 该界面允许您将图形保存到文件中。 尽管这是一种合理的原型制作方法,但在几种常见的使用情况下并不方便。 例如,您可能想生成一堆图片,这些图片将包含在自动生成的报告中。 您可能想为每个输入文件生成一张图片作为批处理器。 matplotlib 允许您非常灵活地将图形直接保存到图片文件中。

首先,我们将了解如何将图形输出到 PNG 文件。 PNG 文件是位图输出的理想选择,它也是位图图片的实际标准。 这是一个得到良好支持的标准; 它依靠无损压缩算法(从而避免了难看的压缩伪像),并处理透明性。

操作步骤

当要求 matplotlib 渲染图形时,我们将使用pyplot.savefig() 调用而不是通常的pyplot.show() 调用:

import numpy as np
from matplotlib import pyplot as plt

X = np.linspace(-10, 10, 1024)
Y = np.sinc(X)

plt.plot(X, Y)
plt.savefig('sinc.png', c = 'k')

该脚本不会在带有用户界面的窗口中显示图形,而只会创建一个名为sinc.png的文件。 它的分辨率为800 x 600像素,采用 8 位颜色(每像素 24 位)。 该文件表示以下图形:

How to do it...

工作原理

函数pyplot.savefig()的工作方式与pyplot.show()完全相同,它解释了发给pyplot的所有命令并生成图形。 唯一的区别是在处理结束时执行的操作。 pyplot.show()函数将图片数据发送到它可以使用的任何用户界面库,而pyplot.savefig()函数将该数据写入文件。 因此,无论最终输出的性质如何,所有命令的工作方式都完全相同。

pyplot.savefig()函数提供了多种可选参数,我们将在以下各节中进行探讨。

处理透明度

创建图形时,很少将它们单独使用。 例如,数字可以是网站或演示文稿的一部分。 在这种情况下,这些图形必须与其他图形集成在一起。 透明度对于这种整合非常重要-数字将以美观和一致的方式与背景融为一体。 在本秘籍中,我们将看到如何输出透明的图形。

操作步骤

为了演示的透明度,我们将创建一个图形并将其嵌入到网页中。 该图将与网页背景融合在一起。 在此秘籍中创建的所有文件应位于同一目录中。 我们将在本节中执行以下操作:

  • 将图形渲染为具有透明背景的 PNG 文件
  • 制作一个包含图形的 HTML 页面

将图形渲染为具有透明背景的 PNG 文件

要将图形渲染为 PNG 文件,我们将再次使用pyplot.savefig()。 但是,可选参数transparent设置为True,如以下脚本所示:

import numpy as np
import matplotlib.pyplot as plt

X = np.linspace(-10, 10, 1024)
Y = np.sinc(X)

plt.plot(X, Y, c = 'k')
plt.savefig('sinc.png', transparent = True)

制作一个包含图形的 HTML 页面

让我们在具有背景的网页上使用 PNG 文件。 最小的 HTML 代码显示sinc.png,并在背景中平铺background.png图片,如下所示:

<html>
  <head>
    <style>
      body {
        background: white url(background.png);
      }
    </style>
  </head>
  <body>
    <img src='sinc.png' width='800 height='600'></img>
  </body>
</html>

使用浏览器查看网页时,该图会与平铺的背景融合在一起,如下图所示。 在其他情况下(例如在演示中使用图形时)也会发生相同的情况。

Making a HTML page that includes the figure

工作原理

默认情况下,的pyplot.savefig()将不在输出中包含透明度信息。 例如,当我们输出 PNG 图片时,默认情况下,PNG 文件每个像素使用 24 位,仅在 8 位上存储像素的红色,绿色和蓝色分量。 但是,启用transparent输出时,pyplot.savefig()每个像素将使用 32 位-另一个通道 alpha 通道存储透明度信息。

更多

到目前为止,唯一涉及图形背景的透明度信息是图形的元素是背景(完全透明)或前景(完全不透明)。 但是,我们可以控制使用 matplotlib 生成的任何图形的透明度。

matplotlib 允许您将图形的透明级别定义为可选参数alpha。 如果alpha等于1,则图形将完全不透明,这是默认设置。 如果alpha等于0,则该图形将完全不可见。 中间值alpha将提供部分透明度。 可选参数 alpha可用于大多数图形函数。

以下脚本演示了这一点:

import numpy as np
import matplotlib.pyplot as plt

name_list = ('Omar', 'Serguey', 'Max', 'Zhou', 'Abidin')
value_list = np.random.randint(99, size=len(name_list))
pos_list = np.arange(len(name_list))

plt.bar(pos_list, value_list, alpha = .75, color = '.75', align = 'center')
plt.xticks(pos_list, name_list)

plt.savefig('bar.png', transparent = True)

前面的脚本将创建一个条形图,并将该图保存到 PNG 文件中。 在网页中使用此 PNG 文件时,我们不仅可以看到图形的背景融合在一起,而且图形的内容也可以融合进去,如以下屏幕截图所示:

There's more...

控制输出分辨率

默认情况下,当将输出用于位图图片时,matplotlib 为我们选择输出的大小和分辨率。 根据位图图片的用途,我们可能要自己选择分辨率。 例如,如果图片要成为大海报的一部分,我们可能更喜欢高分辨率,或者,如果我们想生成缩略图,则分辨率会非常低。 在本秘籍中,我们将学习如何控制输出分辨率。

操作步骤

pyplot.savefig()函数提供了一个可选参数来控制输出分辨率,如以下脚本所示:

import numpy as np
from matplotlib import pyplot as plt

X = np.linspace(-10, 10, 1024)
Y = np.sinc(X)

plt.plot(X, Y)
plt.savefig('sinc.png', dpi = 300)

前面的脚本绘制一条曲线并将结果输出到文件中。 而不是通常的800 x 600像素输出,而是2400 x 1800像素。

工作原理

pyplot.savefig()函数具有一个称为dpi的可选参数。 此参数控制以 DPI每英寸点数)表示的图片的分辨率。 对于更熟悉公制单位的人,1 英寸等于 2.54 厘米。 该单位表示在实际文档的 1 英寸中可以找到多少个点。 好的喷墨打印机将以 300 dpi 的分辨率打印文档。 高质量的激光打印机可以轻松以 600 dpi 的速度打印。

默认情况下,matplotlib 将输出8 x 6空间单位(长宽比为 4/3)的图形。 在 matplotlib 中,1 个空间单位等于 100 个像素。 因此,默认情况下,matplotlib 将提供800 x 600像素的图片文件。 如果我们使用dpi = 300,则图片大小将为8 * 300 x 6 * 300,即2400 x 1800像素。

更多

在第 4 章“处理图形”,我们看到了如何控制长宽比。 如果我们将宽高比和 DPI 结合起来,则可以完全控制图片的一般比例。 假设我们要在512 x 512像素的图片中显示一个六边形。 我们将执行以下操作:

import numpy as np
import matplotlib.pyplot as plt

theta = np.linspace(0, 2 * np.pi, 8)
points = np.vstack((np.cos(theta), np.sin(theta))).transpose()

plt.figure(figsize=(4., 4.))
plt.gca().add_patch(plt.Polygon(points, color = '.75'))

plt.grid(True)
plt.axis('scaled')

plt.savefig('polygon.png', dpi = 128)

上一个脚本的结果将是以下图形:

There's more...

我们将图形显示在4 x 4单位区域上,并以 128 dpi 的分辨率输出-输出将为512 x 512像素。 我们还可以显示 512 像素,单位面积为8 x 8,并以 64 dpi 的分辨率输出。 这将为我们带来以下结果:

There's more...

标注更小,网格线更细。 标注和线条的粗细具有它们自己的默认值,以空间单位表示。 因此,将输出分辨率除以 2 将使标注变小两倍。 如果您开始操纵空间分辨率和每个单独的元素大小,它会很快变得混乱。 通常,最好只相对彼此更改单个元素的大小(注解和厚度)。 如果要使所有标注一致地变大,则可以使用分辨率设置。

生成 PDF 或 SVG 文档

到位图图片的输出并不总是理想的。 位图图片将图片表示为一个给定比例的像素数组。 放大,您将得到一些众所周知的伪影(锯齿,阶梯,模糊等),具体取决于所采用的采样算法。 向量图片是尺度不变的。 无论您以多大的比例观察它们,都不会丢失任何细节或工件。 这样,在编写较大的文档(例如日记帐文章)时,向量图片是理想的。 调整图形比例时,我们不需要生成新图片。 matplotlib 可以输出向量图片,例如 PDF 和 SVG 图片。

操作步骤

PDF 文档的输出很简单,如以下脚本所示:

import numpy as np
from matplotlib import pyplot as plt

X = np.linspace(-10, 10, 1024)
Y = np.sinc(X)

plt.plot(X, Y)
plt.savefig('sinc.pdf')

前面的脚本将绘制一个图形并将其保存到名为sinc.pdf的文件中。

工作原理

我们已经讨论了pyplot.savefig()函数,该函数将图形渲染到文件中。 文件名足以指定文件是 PNG,PDF 还是 SVG。 matplotlib 将查看文件名的文件扩展名并推断文件的类型。

更多

在某些情况下,您可能想以给定格式保存文件,例如 SVG,但是您不希望文件名具有.svg扩展名。 pyplot.savefig参数(作为可选参数)使您可以执行此操作。 设置format = 'svg', pyplot.savefig不会从传递给函数的文件名推断出输出文件类型,而是会使用传递给格式的名称。

处理多页 PDF 文档

在第 4 章“处理图形”中,看到看到了如何在一张 matplotlib 图形中构成多个图形。 这使您可以创建非常复杂的图。 使用 PDF 输出时,我们必须记住该图形必须适合一页。 但是,通过一些额外的工作,我们可以输出多页的 PDF 文档。 请注意,matplotlib 是科学的绘图包,而不是文档合成系统,例如 LaTeX 或 ReportLab。 因此,对多个页面的支持非常少。 在本秘籍中,我们将了解如何生成多页 PDF 文档。

操作步骤

为了演示使用 matplotlib 的多页 PDF 输出,让我们生成 15 个条形图,每页五个图。 以下脚本将输出名为barcharts.pdf的三页文档:

import numpy as np
from matplotlib import pyplot as plt
from matplotlib.backends.backend_pdf import PdfPages

## Generate the data
data = np.random.randn(15, 1024)

## The PDF document
pdf_pages = PdfPages('barcharts.pdf')

## Generate the pages
plots_count = data.shape[0]
plots_per_page = 5
pages_count = int(np.ceil(plots_count / float(plots_per_page)))
grid_size = (plots_per_page, 1)

for i, samples in enumerate(data):
  # Create a figure instance (ie. a new page) if needed
  if i % plots_per_page == 0:
    fig = plt.figure(figsize=(8.27, 11.69), dpi=100)

  # Plot one bar chart
  plt.subplot2grid(grid_size, (i % plots_per_page, 0))
  plt.hist(samples, 32, normed=1, facecolor='.5', alpha=0.75)

  # Close the page if needed
  if (i + 1) % plots_per_page == 0 or (i + 1) == plots_count:
    plt.tight_layout()
    pdf_pages.savefig(fig)

## Write the PDF document to the disk
pdf_pages.close()

条形图整齐地布置在三个页面上,如以下屏幕截图所示:

How to do it...

工作原理

通常,使用 matplotlib 的脚本不依赖于输出的类型。 我们始终将pyplot.savefig()用于各种输出。 但是,在这里,我们必须处理 PDF 输出的细节。 因此,此脚本执行以下操作:

  • 导入可处理 PDF 输出的 matplotlib 包matplotlib.backends.backend_pdf。 从这个包中,我们只需要PdfPages对象。 该对象代表 PDF 文档。
  • 创建名为pdf_pages的 PDF 文档实例。 这是通过使用pdf_pages = PdfPages('histograms.pdf')函数完成的。
  • 要生成每个页面,它会执行以下操作:
    • 创建一个具有 A4 页面尺寸的新图形实例。 这是通过使用fig = plot.figure(figsize=(8.27, 11.69), dpi=100)函数完成的。
    • 用图填充图。 在此示例中,我们使用子图在一个图形中放置多个图。
    • 在 PDF 文档中创建一个新页面,其中将包含我们的图形。 这是通过使用pdf_pages.savefig(fig)函数完成的。
  • 生成所需的所有图形后,即可使用pdf_pages.close()函数输出文档。

请注意,页面在此处是一个相当笼统的术语。 页面不必具有特定大小。 不同的页面可以具有不同的大小。 编写脚本是为了从总的图形数和每页的图形数自动计算出页面数。

更多

由于 matplotlib 并不是完整的文档组成系统,因此如果没有可怕的技巧,就很难实现页码或页眉之类的东西。 如果确实需要这些功能,明智的做法是将每个图形生成为单个 PDF 文档。 然后,这些数字将由文档撰写系统用来自动生成 PDF 文档。 例如,DocBook 是一个使用 XML 描述来生成 PDF 或其他常见格式的文档的系统。 当然,这是一个完全不同的努力规模。

六、处理地图

在本章中,我们将介绍以下主题:

  • 可视化 2D 数组的内容
  • 向图中添加有色图例
  • 可视化非均匀 2D 数据
  • 可视化 2D 标量场
  • 可视化等高线
  • 可视化 2D 向量场
  • 可视化 2D 向量场的流线

简介

到现在为止,我们已经涵盖了基本一维字符数据的绘图基元。 通过绘制某种类型的地图,您可以可视化两个变量对第三个变量的影响。 想象一下,您的气象站遍布全国。 地图可视化可以一目了然显示全国的降雨和风向分布。 matplotlib 提供了由简单 API 驱动的强大原语来创建地图。

可视化 2D 数组的内容

让我们从最基本方案开始。 我们有一个 2D 数组,我们想可视化它的内容。 例如,我们将可视化曼德勃罗集。 曼德勃罗集(一种著名的分形形状)将多次迭代与平面上的每个点相关联。

操作步骤

我们将首先用值填充 2D 正方形数组,然后调用pyplot.imshow()到使其可视化,如以下代码所示:

import numpy as np
import matplotlib.cm as cm
from matplotlib import pyplot as plt 

def iter_count(C, max_iter): 
  X = C 
  for n in range(max_iter): 
    if abs(X) > 2.: 
      return n 
    X = X ** 2 + C 
  return max_iter 

N = 512 
max_iter = 64 
xmin, xmax, ymin, ymax = -2.2, .8, -1.5, 1.5 
X = np.linspace(xmin, xmax, N) 
Y = np.linspace(ymin, ymax, N) 
Z = np.empty((N, N)) 

for i, y in enumerate(Y): 
  for j, x in enumerate(X): 
    Z[i, j] = iter_count(complex(x, y), max_iter)

plt.imshow(Z, cmap = cm.gray)
plt.show()

根据您的计算机,此脚本可能需要几秒钟到几分钟才能产生输出。 减少N,即我们正在填充的正方形数组的大小,将减少计算量。 结果将是曼德勃罗集的所有分形荣耀的视图:

How to do it...

注意,在轴域上显示的坐标是 2D 数组索引。

工作原理

pyplot.imshow()函数非常简单; 给它一个 2D 数组,它将渲染一张图片,其中每个像素代表一个从 2D 数组获取的值。 像素的颜色是从颜色表中选取的-数组的每个值都在[0, 1]间隔内线性归一化。 pyplot.imshow()函数可渲染图表,但不会显示它。 与往常一样,我们应该调用pyplot.show()来查看该图。 但是,具有两个具有类似名称的函数可能会引起混淆。

脚本的其余部分生成我们的示例数据。 创建 2D 数组Z,然后用双循环填充。 此循环对[-2.2, 0.8]*[-1.5, 1.5]正方形进行采样。 对于每个样本,iter_count函数都会计算曼德勃罗集的迭代次数。 Z数组中的数据可能来自文件或任何其他来源。

更多

我们从pyplot.imshow()得到的结果有点原始。 轴域上显示的坐标是 2D 数组索引。 我们可能更喜欢不同的坐标; 在这种情况下,我们采样的正方形的坐标。 此处使用的颜色表不理想。 可以使用pyplot.imshow()的可选参数来解决。 让我们将调用更改为pyplot.imshow()

plt.imshow(Z, cmap = cm.binary, extent=(xmin, xmax, ymin, ymax))

由于我们使用了颜色表,因此我们应该导入 matplotlib 的颜色表模块。 在脚本的开头,添加以下行:

import matplotlib.cm as cm

虽然数据严格相同,但是输出将更改:

There's more…

extent 可选参数指定 2D 数组中存储的数据的坐标系。 坐标系是四个值的元组。 最小和最大范围在水平轴上,然后在垂直轴上。 现在,轴域显示我们采样以计算曼德勃罗集的正方形的坐标。 参数cmap指定颜色表。

现在,让我们在脚本中将样本数据的大小从512减小为32。 输出将类似于以下屏幕截图:

There's more…

我们使用32*32样本而不是512*512样本来表示曼德勃罗集。 但是的结果并不小。 实际上,pyplot.imshow()所做的不只是为 2D 数组着色像素。 如果输入数据小于或大于数字,pyplot.imshow()函数将产生给定任意大小的图片并执行插值。 在此示例中,我们可以看到默认插值是线性的。 这并不总是理想的。 pyplot.imshow()函数有一个可选参数interpolation,它允许我们指定要使用的插值类型。 matplotlib 提供了令人印象深刻的插值方案列表。 让我们看一下最简单的插值方案:最近邻插值:

plt.imshow(Z, cmap = cm.binary, interpolation = 'nearest', extent=(xmin, xmax, ymin, ymax))

原始数据现在更加明显:

There's more…

我们可能想使用比细线线性插值更复杂的插值方案。 后者计算起来很便宜,但会产生难看的伪像。 让我们使用双三次插值,使用interpolation = 'bicubic'。 我们会得到更好的结果:

There's more…

注意

matplotlib 具有更复杂的插值方案,例如 sinclanzcos

向图中添加颜色表

颜色表是产生可读和视觉悦目的图形的关键要素。 但是,我们在这里进行科学,而审美只是一个副目标。 使用颜色表时,我们想知道哪个值对应于给定的颜色。 在本秘籍中,我们将探讨一种将此类信息添加到图形的简单方法。

操作步骤

我们将使用相同的示例曼德勃罗集。 我们只需将调用添加到pyplot.colorbar()

import numpy as np
from matplotlib import pyplot as plt 
import matplotlib.cm as cm 

def iter_count(C, max_iter): 
  X = C 
  for n in range(max_iter): 
    if abs(X) > 2.: 
      return n 
    X = X ** 2 + C 
  return max_iter 

N = 512 
max_iter = 64 
xmin, xmax, ymin, ymax = -2.2, .8, -1.5, 1.5 
X = np.linspace(xmin, xmax, N) 
Y = np.linspace(ymin, ymax, N) 
Z = np.empty((N, N)) 

for i, y in enumerate(Y): 
  for j, x in enumerate(X): 
    Z[i, j] = iter_count(complex(x, y), max_iter) 

plt.imshow(Z, 
            cmap = cm.binary, 
            interpolation = 'bicubic', 
            extent=(xmin, xmax, ymin, ymax)) 
cb = plt.colorbar(orientation='horizontal', shrink=.75) 
cb.set_label('iteration count') 

plt.show()

代码前面的将产生以下输出:

How to do it...

整洁的颜色条可让您将颜色表中的颜色与感兴趣的值相关联。 这是曼德勃罗迭代计数。

工作原理

大部分脚本与上一秘籍中介绍的脚本严格相同。 脚本的相关内容如下:

cb = plt.colorbar(orientation='horizontal', shrink=.75) 
cb.set_label('iteration count')

pyplot.colorbar()函数向 matplotlib 发出信号,要求我们将颜色条显示为。 为了演示,我们在此处使用一些可选参数。 orientation参数用于选择颜色条是垂直还是水平。 默认情况下为垂直。 shrink参数用于将颜色栏缩小为其默认大小。 默认情况下,颜色栏没有图例。 可以设置图例,但这样做有点尴尬。 调用pyplot.colorbar()函数将产生一个Colorbar实例。 然后,我们调用该Colorbar实例的set_label()方法。

可视化非均匀 2D 数据

到目前为止,我们假设已对 2D 数据进行了统一采样; 我们的数据以网格模式进行采样。 但是,非均匀采样的数据非常普遍。 例如,我们可能希望可视化气象站的测量结果。 尽可能地建立气象站; 它们被布置成一个完美的网格。 在进行采样时,我们可能会使用不产生网格布局的复杂采样过程(自适应采样,准随机采样等)。 在这里,我们展示了一种处理此类 2D 数据的简单方法。

操作步骤

该脚本绘制了从与先前秘籍相同的正方形采样的曼德勃罗集。 但是,我们不使用常规的网格采样,而是对曼德勃罗集进行随机采样,如下例所示:

import numpy as np
from numpy.random import uniform, seed 

from matplotlib import pyplot as plt 
from matplotlib.mlab import griddata 
import matplotlib.cm as cm 

def iter_count(C, max_iter): 
  X = C 
  for n in range(max_iter): 
    if abs(X) > 2.: 
      return n 
    X = X ** 2 + C 
  return max_iter 

max_iter = 64 
xmin, xmax, ymin, ymax = -2.2, .8, -1.5, 1.5 

sample_count = 2 ** 12 
A = uniform(xmin, xmax, sample_count) 
B = uniform(ymin, ymax, sample_count) 
C = [iter_count(complex(a, b), max_iter) for a, b in zip(A, B)] 
N = 512 
X = np.linspace(xmin, xmax, N) 
Y = np.linspace(ymin, ymax, N) 
Z = griddata(A, B, C, X, Y, interp = 'linear') 

plt.scatter(A, B, color = (0., 0., 0., .5), s = .5) 
plt.imshow(Z, 
            cmap = cm.binary, 
            interpolation = 'bicubic', 
            extent=(xmin, xmax, ymin, ymax)) 
plt.show()

该脚本将显示随机采样的曼德勃罗集。 样本点显示为黑色小点:

How to do it...

显然,由于的随机采样过程,结果比常规采样的结果更加混乱。 但是,我们仅使用了 4,096 个样本,而不是先前示例中使用的 262,144 个样本,因此我们得到的结果是令人满意的。 通过 matplotlib 的非均匀采样功能,使用自适应采样方法将使您能够以比常规网格采样低得多的计算成本获得曼德勃罗集的高分辨率视图。

工作原理

首先,该脚本对曼德勃罗集进行随机采样,这由脚本的以下部分完成:

sample_count = 2 ** 12 
A = uniform(xmin, xmax, sample_count) 
B = uniform(ymin, ymax, sample_count) 
C = [iter_count(complex(a, b), max_iter) for a, b in zip(A, B)]

数组AB保存样本的坐标,而列表C包含这些样本中每个样本的值。

然后,脚本将从不均匀的样本中生成一个二维数据数组,这由以下部分完成:

N = 512 
X = np.linspace(xmin, xmax, N) 
Y = np.linspace(ymin, ymax, N) 
Z = griddata(A, B, C, X, Y, interp = 'linear') 

数组XY定义了规则的网格。 数组Z是通过内插非均匀样本而构建的 2D 数组。 该插值是通过matplotlib.mlab包中的griddata()函数完成的。 由于现在有了 2D 数组,因此可以使用pyplot.imshow()函数对其进行可视化。 对pyplot.scatter()的附加调用用于显示原始采样点。

为了演示,我们对pyplot.griddata()使用线性插值,并使用可选参数interp。 默认情况下,此参数设置为'nn',代表自然邻居插值。 后一种方案在大多数情况下是首选,因为它非常健壮。

可视化 2D 标量场

matplotlib 和 NumPy 提供一些有趣的机制,使二维标量场的可视化变得方便。 在本秘籍中,我们展示了一种非常简单的方法来可视化 2D 标量场。

操作步骤

numpy.meshgrid()函数从显式 2D 函数生成样本。 然后,pyplot.pcolormesh()是用于显示的函数,如以下代码所示:

import numpy as np
from matplotlib import pyplot as plt 
import matplotlib.cm as cm 
n = 256 
x = np.linspace(-3., 3., n) 
y = np.linspace(-3., 3., n) 
X, Y = np.meshgrid(x, y) 

Z = X * np.sinc(X ** 2 + Y ** 2) 

plt.pcolormesh(X, Y, Z, cmap = cm.gray) 
plt.show()

前面的脚本将产生以下输出:

How to do it...

注意明智地选择颜色表会有所帮助; 在此,负值显示为黑色,正值显示为白色。 这样,我们就可以一目了然地看到符号和大小信息。 使用从红色到蓝色,中间为白色的颜色表,效果更好。

工作原理

numpy.meshgrid()函数取两个坐标xy,并建立两个坐标为XY的网格。 因为XY是 NumPy 2D 数组,所以我们可以像处理单个变量一样操作它们。 我们不必编写循环来生成矩阵Z。 这使得计算标量字段简洁明了:

Z = X * numpy.sinc(X ** 2 + Y ** 2) 

然后,调用函数pyplot.pcolormesh()渲染样本。 我们可以从pyplot.imshow()获得相同的结果。 但是,我们只需要在此处传递XYZ即可获得正确的坐标系,而不是使用可选参数。 这样做使脚本更易于阅读。 同样,对于大量数据,pyplot.pcolormesh()可能会快得多。

可视化等高线

到目前为止,我们已经通过给每个数据点着色来可视化数据,并在顶部插入了一些插值。 matplotlib 能够为 2D 数据提供更复杂的表示形式。 等高线以相同的值链接所有点,从而帮助您捕获否则可能不容易看到的要素。 在本秘籍中,我们将看到如何显示这种等高线。

操作步骤

函数pyplot.contour()允许您生成轮廓标注。 为了演示,让我们重用先前秘籍中的代码,以研究曼德勃罗集的放大部分:

import numpy as np
from matplotlib import pyplot as plt 
import matplotlib.cm as cm 

def iter_count(C, max_iter): 
  X = C 
  for n in range(max_iter): 
    if abs(X) > 2.: 
      return n 
    X = X ** 2 + C 
  return max_iter 

N = 512 
max_iter = 64 
xmin, xmax, ymin, ymax = -0.32, -0.22, 0.8, 0.9 
X = np.linspace(xmin, xmax, N) 
Y = np.linspace(ymin, ymax, N) 
Z = np.empty((N, N)) 

for i, y in enumerate(Y): 
  for j, x in enumerate(X): 
    Z[i, j] = iter_count(complex(x, y), max_iter) 

plt.imshow(Z, 
            cmap = cm.binary, 
            interpolation = 'bicubic', 
            origin = 'lower', 
            extent=(xmin, xmax, ymin, ymax)) 

levels = [8, 12, 16, 20] 
ct = plt.contour(X, Y, Z, levels, cmap = cm.gray) 
plt.clabel(ct, fmt='%d') 

plt.show()

前面的脚本将显示带有复杂轮廓标注的曼德勃罗集的详细信息:

How to do it...

工作原理

我们认识到先前在曼德勃罗集中展示的秘籍中使用的代码。 唯一的区别是,我们通过更改xminxmaxyminymax的值来放大曼德勃罗集的特定细节。 和以前一样,我们使用pyplot.imshow()渲染每个样本的迭代计数。

仅添加了一个:调用pyplot.contour()。 此函数获取样本网格的坐标XY以及存储在矩阵Z中的样本。 然后,该函数将渲染与levels列表中指定的值相对应的轮廓。 可以使用可选参数cmap使用颜色表对级别进行着色。 我们可以使用可选参数color为所有轮廓指定一种唯一的颜色。

每个轮廓的水平可以用色条显示或直接在图形上显示。 pyplot.contour()函数返回Contour实例。 pyplot.clabel()函数采用Contour实例和一个可选的格式字符串来渲染每个轮廓的标签。

更多

在此,轮廓仅显示为线条。 但是,我们可以显示填充轮廓。 让我们在之前使用的曼德勃罗集的相同细节上进行演示:

import numpy as np
from matplotlib import pyplot as plt 
import matplotlib.cm as cm 

def iter_count(C, max_iter): 
  X = C 
  for n in range(max_iter): 
    if abs(X) > 2.: 
      return n 
    X = X ** 2 + C 
  return max_iter 

N = 512 
max_iter = 64 
xmin, xmax, ymin, ymax = -0.32, -0.22, 0.8, 0.9 
X = np.linspace(xmin, xmax, N) 
Y = np.linspace(ymin, ymax, N) 
Z = np.empty((N, N)) 

for i, y in enumerate(Y): 
  for j, x in enumerate(X): 
    Z[i, j] = iter_count(complex(x, y), max_iter) 
levels = [0, 8, 12, 16, 20, 24, 32] 
plt.contourf(X, Y, Z, levels, cmap = cm.gray, antialiased = True) 
plt.show()

上面的脚本将产生以下输出:

There's more...

在这里,我们简单地用pyplot.contourf()将替换为pyplot.contour(),并为轮廓使用了附加等级。 默认情况下,不填充轮廓。 我们使用antialiased可选参数来获得更悦目的结果。

可视化 2D 向量场

到目前为止,我们一直在使用 2D 标量字段:将值与 2D 平面的每个点相关联的函数。 向量场将 2D 向量关联到 2D 平面的每个点。 向量场在物理学中很常见,因为它们提供了微分方程的解。 matplotlib 提供了可视化向量场的函数。

准备

对于此示例,我们将需要 SymPy 包; 用于符号计算的包。 该包仅用于使示例简短,并且对于使用向量字段不是必需的。

操作步骤

为了说明向量场的可视化,让我们可视化圆柱周围不可压缩流体的速度流。 我们无需费心如何计算这样的向量场,而仅需担心如何显示它。 我们需要pyplot.quiver()函数; 请参考以下代码:

import numpy as np
import sympy 
from sympy.abc import x, y 
from matplotlib import pyplot as plt 
import matplotlib.patches as patches 

def cylinder_stream_function(U = 1, R = 1): 
  r = sympy.sqrt(x ** 2 + y ** 2) 
  theta = sympy.atan2(y, x) 
  return U * (r - R ** 2 / r) * sympy.sin(theta) 

def velocity_field(psi): 
  u = sympy.lambdify((x, y), psi.diff(y), 'numpy') 
  v = sympy.lambdify((x, y), -psi.diff(x), 'numpy') 
  return u, v 

U_func, V_func = velocity_field(cylinder_stream_function() ) 

xmin, xmax, ymin, ymax = -2.5, 2.5, -2.5, 2.5 
Y, X = np.ogrid[ymin:ymax:16j, xmin:xmax:16j] 
U, V = U_func(X, Y), V_func(X, Y) 

M = (X ** 2 + Y ** 2) < 1\. 
U = np.ma.masked_array(U, mask = M) 
V = np.ma.masked_array(V, mask = M) 

shape = patches.Circle((0, 0), radius = 1., lw = 2., fc = 'w', ec = 'k', zorder = 0) 
plt.gca().add_patch(shape) 

plt.quiver(X, Y, U, V, zorder = 1) 

plt.axes().set_aspect('equal') 
plt.show()

上面的脚本将产生以下输出:

How to do it...

工作原理

尽管脚本长,但纯图形部分很简单。 向量字段存储在矩阵UV中:我们从向量字段中采样的每个向量的坐标。 矩阵XY包含样本位置。 矩阵XYUV被传递到 pyplot.quiver(),从而渲染向量场。 请注意,pyplot.quiver()只能使用UV作为参数,但图例将显示样本的索引而不是其坐标。

由于我们在此处用作说明的向量场是圆柱体周围的流体流动,圆柱体本身显示如下:

shape = patches.Circle((0, 0), radius = 1., lw = 2., fc = 'w', ec = 'k', zorder = 0) 
plt.gca().add_patch(shape)

圆柱体内的向量场不会出现; 我们使用蒙版数组。 我们首先创建一个定义应显示哪些样本的蒙版。 然后,将此掩码应用于UV,如以下脚本所示:

M = (X ** 2 + Y ** 2) < 1\. 
U = np.ma.masked_array(U, mask = M) 
V = np.ma.masked_array(V, mask = M) 

这使您可以隐藏解决方案中的奇点。

可视化 2D 向量场的流线

使用箭头指向表示向量场效果很好。 但是 matplotlib 可以做得更好,它可以显示向量场的流线。 一条流线显示了向量场的流动方式。 在本秘籍中,我们将向您展示如何创建流线型。

操作步骤

让我们使用上一个秘籍的流体流动示例。 我们将简单地用流线替换箭头,如以下代码所示:

import numpy as np
import sympy 
from sympy.abc import x, y 
from matplotlib import pyplot as plt 
import matplotlib.patches as patches 

def cylinder_stream_function(U = 1, R = 1): 
  r = sympy.sqrt(x ** 2 + y ** 2) 
  theta = sympy.atan2(y, x) 
  return U * (r - R ** 2 / r) * sympy.sin(theta) 

def velocity_field(psi): 
  u = sympy.lambdify((x, y), psi.diff(y), 'numpy') 
  v = sympy.lambdify((x, y), -psi.diff(x), 'numpy') 
  return u, v 

psi = cylinder_stream_function() 
U_func, V_func = velocity_field(psi) 

xmin, xmax, ymin, ymax = -3, 3, -3, 3 
Y, X = np.ogrid[ymin:ymax:128j, xmin:xmax:128j] 
U, V = U_func(X, Y), V_func(X, Y) 

M = (X ** 2 + Y ** 2) < 1\. 
U = np.ma.masked_array(U, mask = M) 
V = np.ma.masked_array(V, mask = M) 
shape = patches.Circle((0, 0), radius = 1., lw = 2., fc = 'w', ec = 'k', zorder = 0) 
plt.gca().add_patch(shape) 

plt.streamplot(X, Y, U, V, color = 'k') 

plt.axes().set_aspect('equal') 
plt.show()

前面的脚本将显示圆柱周围的流,如以下屏幕快照中的所示:

How to do it...

工作原理

生成样本向量坐标的代码与先前的秘籍相同。 在这里,我们使用更多样本(用128*128代替32*32)来获得准确的流线型。 除此之外,唯一的区别是我们使用pyplot.streamlines()代替了pyplot.quiver()的。 四个必需参数相同:样本的坐标XY,以及向量的坐标UV。 可选参数color用于设置流线的颜色。

更多

我们可以使用带有可选参数colorcmap的颜色表为流线着色:

plt.streamplot(X, Y, U, V, color = U ** 2 + V ** 2, cmap = cm.binary)

color参数采用 2D 数组,该数组用于为流线着色。 在此示例中,颜色反映了流的速度,如以下输出所示:

There's more...

七、处理 3D 图形

在本章中,我们将介绍以下主题:

  • 创建 3D 散点图
  • 创建 3D 线形图
  • 在 3D 中绘制标量场
  • 绘制参数化 3D 曲面
  • 将 2D 图形嵌入 3D 图形
  • 创建 3D 条形图

简介

matplotlib 对三维图的支持不断增加。 从 1.2 版开始,制作 3D 图形的 API 与 2D API 非常相似。 在地块上再增加一个维度可以一目了然地可视化更多信息。 此外,3D 图在演示文稿或课程期间非常引人注目。 在本章中,我们将探讨 matplotlib 在第三维中可以做什么。

创建 3D 散点图

散点图是非常简单的图。 对于数据集的每个点,图中显示一个点。 一个点的坐标就是相应数据的坐标。 我们已经在第 1 章“第一步”中探索了二维散点图。 在本秘籍中,我们将看到三个维度的散点图以相同的方式工作,但变化很小。

为了使本示例中的数据有趣,我们将使用洛伦兹奇异吸引子。 这是一个 3D 结构,代表了来自气象学的简单动力系统的解决方案。 这个动力学系统是一个著名的教科书例子,一个混沌系统。

操作步骤

在下面的代码中,我们将从Axes实例调用图形渲染方法,而不是从pyplot调用方法:

import numpy as np
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt

## Dataset generation
a, b, c = 10., 28., 8\. / 3.
def lorenz_map(X, dt = 1e-2):
  X_dt = np.array([a * (X[1] - X[0]),
                             X[0] * (b - X[2]) - X[1],
                             X[0] * X[1] - c * X[2]])
  return X + dt * X_dt

points = np.zeros((2000, 3))
X = np.array([.1, .0, .0])
for i in range(points.shape[0]):
  points[i], X = X, lorenz_map(X)

## Plotting
fig = plt.figure()
ax = fig.gca(projection = '3d')

ax.set_xlabel('X axis')
ax.set_ylabel('Y axis')
ax.set_zlabel('Z axis')
ax.set_title('Lorenz Attractor a=%0.2f b=%0.2f c=%0.2f' % (a, b, c))

ax.scatter(points[:, 0], points[:, 1],  points[:, 2], zdir = 'y', c = 'k')
plt.show()

上面的代码将显示现在熟悉的用户界面,如下图:

How to do it...

在我们眼前,洛伦兹吸引子! 如果将鼠标拖到图形中(按住鼠标左键移动鼠标),则 3D 形状将旋转,就像您正在操纵轨迹球一样。 您可以旋转图形并以所有可能的角度检查洛伦兹吸引子。

请注意,尽管所有点都以蓝色显示,但有些点趋向于白色阴影。 matplotlib 应用这种类似于雾的效果来增强散点图的深度感知。 距我们较远的点将抖动为白色,这是文艺复兴时期画家已经知道的古老技巧。

工作原理

对于本示例,我们不会在数据生成上花太多时间; 这不是重点。 我们只需要知道数据集存储在具有三列的矩阵点中,每个维度一列。

在使用 matplotlib 进行三维操作之前,我们首先需要为 matplotlib 导入 3D 扩展名:这是以下import指令的目的:

from mpl_toolkits.mplot3d import Axes3D

到目前为止,在的大部分时间内,我们已经通过调用pyplot中的方法提交了所有渲染指令。 但是,对于三维图,涉及的内容更多,如以下代码所示:

fig = plt.figure()
ax = fig.gca(projection = '3d')

我们创建一个Figure实例,并将一个Axes3D实例附加到该实例。 当Axes实例负责通常的 2D 渲染时,Axes3D将负责 3D 渲染。 然后,3D 散点图的工作方式与 2D 散点图完全相同,如以下代码所示:

ax.scatter(points[:, 0], points[:, 1],  points[:, 2])

我们给出要表示的点的XYZ坐标。 在这里,我们仅给出点矩阵的三列。 我们可以使用普通的 Python 列表,但是为了方便起见,我们使用 NumPy 数组。 再次注意,我们调用了Axes3D实例的scatter()方法,而不是pyplot中的scatter方法。 Axes3D中只有scatter()方法会解释 3D 数据。

最后,尽管在Axes3D实例中调用了它们,但是我们在第 3 章,“处理标注”中探讨的功能也可用。 用set_title()设置标题,并用set_xlabel()set_ylabel()set_zlabel()标注轴。

更多

正如我们已经看到的,3D 中的散点图的工作方式与 2D 中的散点图相同。 实际上,除了用于创建Axes3D实例的设置代码之外,所有内容似乎都像在 2D 模式下一样工作。 这不仅仅是印象。 例如,自定义散点图的工作方式完全相同。 让我们通过替换对Axes3D.scatter()的调用来更改标记的形状和颜色,如下所示:

ax.scatter(points[:, 0], points[:, 1],  points[:, 2],
                 marker = 's',
                 edgecolor = '.5',
           facecolor = '.5')

现在,输出将如下图所示:

There's more...

实际上,第 2 章“自定义颜色和样式”中的所有技巧都可以在 3D 中实现。

创建 3D 线形图

正如先前的秘籍所展示的那样,在创建三维图形时,我们在前几章中学到的内容是正确的。 让我们通过绘制 3D 参数曲线来确认这一点。 在此秘籍中,我们保留与上一个秘籍相同的数据集; 就是洛伦兹吸引子。

操作步骤

在 2D 中,我们通过调用pyplot.plot()绘制曲线。 正如前面的秘籍所暗示的,我们要做的就是设置一个Axes3D实例并调用其plot()方法,如以下代码所示:

import numpy as np
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt

a, b, c = 10., 28., 8\. / 3.
def lorenz_map(X, dt = 1e-2):
  X_dt = np.array([a * (X[1] - X[0]),
                            X[0] * (b - X[2]) - X[1],
                            X[0] * X[1] - c * X[2]])
  return X + dt * X_dt

points = np.zeros((10000, 3))
X = np.array([.1, .0, .0])
for i in range(points.shape[0]):
  points[i], X = X, lorenz_map(X)

fig = plt.figure()
ax = fig.gca(projection = '3d')
ax.plot(points[:, 0], points[:, 1],  points[:, 2], c = 'k')
plt.show()

前面的代码将显示熟悉的洛伦兹吸引子,但是这些点通过曲线链接,而不是简单地显示每个数据点,如下图所示:

How to do it…

当我们使用用户界面旋转视图时,洛伦兹吸引子的特殊缠绕螺旋结构非常明显。

工作原理

对于任何三维图形,我们首先设置一个Axes3D实例。 然后,对plot()的调用类似于其 2D 对应项:我们为每个维度提供一个列表,并为每个维度提供点的坐标。

绘制 3D 标量场

到目前为止,我们已经看到 3D 图基本上模仿了 2D 对应图。 但是,还有 matplotlib 的三维绘图函数。 许多特定于三维的图形也是可能的。 让我们从一个简单的用例开始:将 2D 标量场绘制为 3D 表面。

操作步骤

和往常一样,我们将要生成一些测试数据,设置一个Axes3D实例,然后将我们的数据传递给它:

import numpy as np
from matplotlib import cm
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt

x = np.linspace(-3, 3, 256)
y = np.linspace(-3, 3, 256)
X, Y = np.meshgrid(x, y)
Z = np.sinc(np.sqrt(X ** 2 + Y ** 2))

fig = plt.figure()
ax = fig.gca(projection = '3d')
ax.plot_surface(X, Y, Z, cmap=cm.gray)
plt.show()

上面的代码将显示下图:

How to do it...

工作原理

数据生成的工作方式与第 6 章和“处理地图”中演示的完全相同。 将创建两个矩阵XY,其中包含规则网格的坐标。 我们计算矩阵Z以及XY的标量场函数。

从这里开始,事情变得非常琐碎。 我们调用plot_surface()方法,该方法使用XYZ将标量场显示为 3D 曲面。 颜色来自颜色表(cmap可选参数)和矩阵Z

更多

您可能不希望看到 3D 表面上显示的黑色曲线。 可以使用plot_surface()的一些其他可选参数来完成,如以下代码所示:

ax.plot_surface(X, Y, Z,
    cmap=cm.gray,
    linewidth=0,
    antialiased=False)

黑色曲线现已消失,从而使图形更简单:

There's more...

另一方面,我们可能想要保留黑色曲线并摆脱花哨的颜色。 也可以使用plot_surface()的可选参数来完成,如以下代码所示:

ax.plot_surface(X, Y, Z, color = 'w')

并且仅保留黑色曲线,从而形成了极简的表面图,如下图所示:

There's more...

最后,我们可能希望摆脱隐藏面的去除,并希望表面由线框制成。 现在,这不是plot_surface()可以实现的。 但是,plot_wireframe()正是为此而制作的,如以下代码所示:

ax.plot_wireframe(X, Y, Z, cstride=8, rstride=8, color = 'k')

现在,以线框样式渲染相同的曲面,如下图所示:

There's more...

plot_wireframe()参数采用与输入相同的XYZ坐标作为plot_surface()参数。 我们使用两个可选参数rstridecstride来告诉 matplotlib 跳过XY轴上的每八个坐标。 如果没有这个,曲线之间的间隔将太小,我们将只能看到一个大的黑色轮廓。

绘制参数化 3D 曲面

在先前的秘籍中,我们使用plot_surface()绘制了标量场:即f(x, y) = z形式的函数。 但是,matplotlib 能够绘制通用的参数化 3D 曲面。 让我们通过绘制圆环来演示这一点,圆环是一个相当简单的参数化曲面。

操作步骤

我们将使用以下代码再次使用plot_surface()显示圆环:

import numpy as np
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt

## Generate torus mesh
angle = np.linspace(0, 2 * np.pi, 32)
theta, phi = np.meshgrid(angle, angle)
r, R = .25, 1.
X = (R + r * np.cos(phi)) * np.cos(theta)
Y = (R + r * np.cos(phi)) * np.sin(theta)
Z = r * np.sin(phi)

## Display the mesh
fig = plt.figure()
ax = fig.gca(projection = '3d')
ax.set_xlim3d(-1, 1)
ax.set_ylim3d(-1, 1)
ax.set_zlim3d(-1, 1)
ax.plot_surface(X, Y, Z, color = 'w', rstride = 1, cstride = 1)
plt.show()

前面的代码将显示我们的圆环,如下所示:

How to do it...

工作原理

圆环是可以用两个参数thetaphi进行参数化的曲面,其范围从02 * pi,如下代码所示:

angle = np.linspace(0, 2 * np.pi, 32)
theta, phi = np.meshgrid(angle, angle)

thetaphi变量描述了规则的网格布局。 圆环网格的 3D 坐标根据thetaphi编写,如以下代码所示:

r, R = .25, 1.
X = (R + r * np.cos(phi)) * np.cos(theta)
Y = (R + r * np.cos(phi)) * np.sin(theta)
Z = r * np.sin(phi)

然后,我们只需将XYZ传递给plot_surface()方法。 plot_surface()方法假定XYZ是网格数据。 我们需要设置可选参数rstridecstride,以明确XYZ是网格数据。

我们明确地将的轴限制设置为[-1, 1]范围。 默认情况下,在创建 3D 图时,matplotlib 将自动缩放每个轴。 我们的圆环在XY轴的[-1, 1]范围内延伸,但仅在Z轴的[-.25, .25]范围内延伸。 如果让 matplotlib 缩放轴,则圆环将显示在Z轴上拉伸,如下图所示:

How it works...

因此,在绘制 3D 曲面时,我们必须手动设置每个轴范围以获得正确缩放的视图。

更多

如前面的秘籍所示,我们可以使用以下代码将对plot_surface()的调用替换为对plot_wireframe()的调用,以获取圆环的线框视图:

ax.plot_wireframe(X, Y, Z, color = 'k', rstride = 1, cstride = 1)

这个简单的更改足以获得线框视图,如下图所示:

There's more...

将 2D 图形嵌入 3D 图形

我们已经在第 3 章“处理标注”来了解如何对图形进行标注。 标注三维图形的一种有效方法是简单地使用二维图形。 此秘籍是说明这种可能性的简单示例。

操作步骤

为了说明这个想法,我们将仅使用我们之前已经看到的图元绘制一个简单的 3D 表面和两条曲线,如以下代码所示:

import numpy as np
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
x = np.linspace(-3, 3, 256)
y = np.linspace(-3, 3, 256)
X, Y = np.meshgrid(x, y)
Z = np.exp(-(X ** 2 + Y ** 2))
u = np.exp(-(x ** 2))

fig = plt.figure()
ax = fig.gca(projection = '3d')
ax.set_zlim3d(0, 3)
ax.plot(x, u, zs=3, zdir='y', lw = 2, color = '.75')
ax.plot(x, u, zs=-3, zdir='x', lw = 2., color = 'k')
ax.plot_surface(X, Y, Z, color = 'w')

plt.show()

上面的代码将产生下图:

How to do it...

黑色和灰色曲线绘制为投影在平面上的 2D 曲线。

工作原理

如先前秘籍所示,将生成 3D 表面。 Axes3D实例ax支持常用的 2D 渲染命令,例如plot(),如以下代码所示:

ax.plot(x, u, zs=3, zdir='y', lw = 2, color = '.75')

但是,对plot()的调用具有两个新的可选参数:zszdir

  • Zdir:这决定了要在哪个平面上绘制 2D 图,X,Y 或 Z
  • Zs:这确定平面的偏移

因此,要将 2D 图形嵌入 3D 图形中,我们只需要记住Axes3D即可使用所有 2D 图元。 我们只有两个可选参数zdirzs,以设置放置需要绘制图形的平面。

更多

将 2D 图形嵌入到 3D 图形中非常简单,但是使用到目前为止我们探索的简单基元创建复杂图形提供了很多可能性。 例如,我们已经知道使用以下代码制作分层条形图的一切:

import numpy as np
from matplotlib import cm
import matplotlib.colors as col
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt

## Data generation
alpha = 1\. / np.linspace(1, 8, 5)
t = np.linspace(0, 5, 16)
T, A = np.meshgrid(t, alpha)
data = np.exp(-T * A)

## Plotting
fig = plt.figure()
ax = fig.gca(projection = '3d')
cmap = cm.ScalarMappable(col.Normalize(0, len(alpha)), cm.gray)
for i, row in enumerate(data):
  ax.bar(4 * t, row, zs=i, zdir='y', alpha=0.8, color=cmap.to_rgba(i))
plt.show()

上面的代码将产生下图:

There's more...

我们可以看到前面的代码使用了前面各章中介绍的功能:

  • 条形图的创建已在第 1 章,“第一步”中进行了介绍。
  • 第 2 章“自定义颜色和样式”中已经介绍了使用颜色表对条形图进行着色。
  • 本秘籍涵盖了条形图的分层

创建 3D 条形图

在 3D 图形中使用几个 2D 图层,我们可以绘制多个条形图。 但是,我们也可以使用完整的 3D 和带有实际 3D 条形图的条形图。

操作步骤

为了演示 3D 条形图,我们将使用先前秘籍中的简单合成数据集,如以下代码所示:

import numpy as np
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt

## Data generation
alpha = np.linspace(1, 8, 5)
t = np.linspace(0, 5, 16)
T, A = np.meshgrid(t, alpha)
data = np.exp(-T * (1\. / A))

## Plotting
fig = plt.figure()
ax = fig.gca(projection = '3d')

Xi = T.flatten()
Yi = A.flatten()
Zi = np.zeros(data.size)

dx = .25 * np.ones(data.size)
dy = .25 * np.ones(data.size)
dz = data.flatten()

ax.set_xlabel('T')
ax.set_ylabel('Alpha')
ax.bar3d(Xi, Yi, Zi, dx, dy, dz, color = 'w')

plt.show()

这次,这些条显示为 3D 块,如下图所示:

How to do it...

工作原理

这些条以网格布局放置在中。 bar3d()方法采用六个必填参数作为输入。 前三个参数是每个条形图下端的XYZ坐标。 在这里,我们根据数据集构建条形的坐标,如下所示:

Xi = T.flatten()
Yi = A.flatten()
Zi = np.zeros(data.size)

每个小节将从同一级别0开始。 XY坐标是数据集的坐标。 bar3d()方法将坐标列表而不是网格坐标作为输入,这就是为什么我们在矩阵AT上调用flatten方法的原因。

bar3d()方法的接下来的三个必需参数是每个尺寸上每个条的尺寸。 在此,条形的高度取自data矩阵。 钢筋的宽度和深度设置为.25,如以下代码所示:

dx = .25 * np.ones(data.size)
dy = .25 * np.ones(data.size)
dz = data.flatten()

现在,我们可以使用以下代码调用bar3d()

ax.bar3d(Xi, Yi, Zi, dx, dy, dz, color = 'w')

八、用户界面

在本章中,我们将介绍:

  • 制作用户可控制的图
  • 将图整合到 Tkinter 用户界面中
  • 将图整合到 wxWidgets 用户界面中
  • 将图整合到 GTK 用户界面中
  • 将图整合到 Pyglet 应用中

简介

matplotlib 不仅可以绘制图表,还可以绘制您可以与之交互的图形。 交互式可视化可能是探索一些数据并发现一些有趣模式的好方法。 同样,交互式图表可以为教学目的提供很好的支持。 在本章中,我们将探讨创建 交互式绘图所必须使用的不同选项。

制作用户可控制的绘图

开箱即用的不需要任何其他包,matplotlib 提供了在图上添加控制器的原语,以便用户可以与其交互。 在本秘籍中,我们将了解如何绘制著名的参数曲线: SuperShape 曲线。 该曲线由六个参数控制:A,B,M,N1,N2 和 N3。 这些参数确定曲线的形状。 用户可以通过在图形上移动光标来交互式设置它们。

操作步骤

以下代码将使用pyplot.plot()显示一条曲线,这时应该很简单。 但是,我们现在使用用户界面元素(通常更称为小部件),即滑块。 可以通过以下步骤完成:

  1. 我们从必要的导入指令开始,如下所示:

    import numpy as np
    from matplotlib import pyplot as plt
    from matplotlib.widgets import Slider
    
  2. SuperShape 曲线由以下函数定义:

    def supershape_radius(phi, a, b, m, n1, n2, n3):
      theta = .25 * m * phi
      cos = np.fabs(np.cos(theta) / a) ** n2
      sin = np.fabs(np.sin(theta) / b) ** n3
      r = (cos + sin) ** (-1\. / n1)
      r /= np.max(r)
      return r
    
  3. 然后,我们使用以下代码定义 SuperShape 曲线的参数的初始值:

    phi = np.linspace(0, 2 * np.pi, 1024)
    m_init = 3
    n1_init = 2
    n2_init = 18
    n3_init = 18
    
  4. 我们定义图并按如下所示放置滑块:

    fig = plt.figure()
    ax = fig.add_subplot(111, polar = True)
    
    ax_m  = plt.axes([0.05, 0.05, 0.25, 0.025])
    ax_n1 = plt.axes([0.05, 0.10, 0.25, 0.025])
    ax_n2 = plt.axes([0.7, 0.05, 0.25, 0.025])
    ax_n3 = plt.axes([0.7, 0.10, 0.25, 0.025])
    
    slider_m  = Slider(ax_m,  'm',  1, 20, valinit = m_init)
    slider_n1 = Slider(ax_n1, 'n1', .1, 10, valinit = n1_init)
    slider_n2 = Slider(ax_n2, 'n2', .1, 20, valinit = n2_init)
    slider_n3 = Slider(ax_n3, 'n3', .1, 20, valinit = n3_init)
    
  5. 我们使用以下代码绘制一次曲线:

    r = supershape_radius(phi, 1, 1, m_init, n1_init, n2_init, n3_init)
    lines, = ax.plot(phi, r, lw = 3.)
    
  6. 我们指定用户更新滑块时的操作,如以下代码所示:

    def update(val):
        r = supershape_radius(phi, 1, 1, np.floor(slider_m.val), slider_n1.val, slider_n2.val, slider_n3.val)
        lines.set_ydata(r)
        fig.canvas.draw_idle()
    
    slider_n1.on_changed(update)
    slider_n2.on_changed(update)
    slider_n3.on_changed(update)
    slider_m.on_changed(update)
    
  7. 现在,我们已经完成,可以使用以下命令结束脚本:

    plt.show()
    
  8. 前面的代码将按预期显示一条曲线,并带有(基本)滑块控件,如下图所示:

    How to do it...

您可以拖动左侧或右侧的滑块,以查看曲线的变化。 请注意,在较旧的计算机上,动画感觉很慢。

工作原理

该代码比平时更长。 让我们分手吧!

SuperShape 曲线是极线。 supershape_radius函数计算[0, 2 * pi]间隔中每个角度的半径。 该函数将角度数组和 SuperShape 曲线的六个参数作为输入。

我们显式创建一个Figure实例fig和一个Axes实例ax,如以下代码所示:

fig = plt.figure()
ax = fig.add_subplot(111, polar = True)

所有小部件都在matplotlib.widgets包中定义。 我们在图中为参数 M,N1,N2 和 N3 放置了四个滑块控件。

每个滑块与与通过调用plot.axes()创建的子图相关联。 每个Slider实例都是通过调用Slider构造器来创建的。 构造器采用四个强制性参数:子图实例,标签,最小值和最大值。 我们使用可选参数valinit设置每个滑块的初始位置,如以下代码所示:

ax_m  = plt.axes([0.05, 0.05, 0.25, 0.025])
ax_n1 = plt.axes([0.05, 0.10, 0.25, 0.025])
ax_n2 = plt.axes([0.7, 0.05, 0.25, 0.025])
ax_n3 = plt.axes([0.7, 0.10, 0.25, 0.025])

slider_m  = Slider(ax_m,  'm',  1, 20, valinit = m_init)
slider_n1 = Slider(ax_n1, 'n1', .1, 10, valinit = n1_init)
slider_n2 = Slider(ax_n2, 'n2', .1, 20, valinit = n2_init)
slider_n3 = Slider(ax_n3, 'n3', .1, 20, valinit = n3_init)

我们绘制曲线本身,但是跟踪要渲染的内容:lines变量中存储的线的集合,这是使用以下代码完成的:

lines, = ax.plot(phi, r, lw = 3.)

我们定义了每个滑块在更改位置时的行为:它们将调用名为update的函数:

slider_n1.on_changed(update)
slider_n2.on_changed(update)
slider_n3.on_changed(update)
slider_m.on_changed(update)

update函数读取每个滑块的位置,并更新要显示的曲线的每个点的位置,更新线的集合,最后,将更改通知给Figure实例fig, 如以下代码所示:

def update(val):
    lines.set_ydata(supershape_radius(phi, 1, 1, np.floor(slider_m.val), slider_n1.val, slider_n2.val, slider_n3.val))
    fig.canvas.draw_idle()

最后,我们准备使用以下命令绘制所有内容:

plt.show()

更多

尽管滑块控件绝对是一种交互式调整参数的好方法,但是matplotlib.widgets包中提供了更多小部件。 按钮和复选框也可用。

将绘图集成到 Tkinter 用户界面

matplotlib 提供了基本的小部件来构建交互式图形。 但是,这些小部件非常初级,不能很好地扩展,而只需要几个控制器即可。 真正的图形用户界面库更适合于创建复杂的交互。 幸运的是,Python 附带了这样的库: Tkinter。 Tkinter 允许您创建一些小部件并为其提供 Windows 布局。 更好的是,matplotlib 提供了一个方便的钩子,可以将绘图集成到使用 Tkinter 制作的用户界面中。 在本秘籍中,我们将重现前面的示例,但将 Tkinter 用于用户界面部分。

操作步骤

方便地, matplotlib 提供了一个特殊的 Tkinter 小部件,可用于渲染图形。 如前面的秘籍中所述,更新了该特殊小部件中的图形。 这是我们需要遵循的步骤:

  1. 我们从常规导入指令开始,如下所示:

    import numpy as np
    from tkinter import *
    
    from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
    from matplotlib.figure import Figure
    

    请注意,此处 Tkinter 的import指令对 Python 3 有效。如果使用 Python 2,则应将tkinter替换为Tkinter

  2. 然后,我们使用以下代码定义 SuperShape 曲线的函数:

    def supershape_radius(phi, a, b, m, n1, n2, n3):
      theta = .25 * m * phi
      cos = np.fabs(np.cos(theta) / a) ** n2
      sin = np.fabs(np.sin(theta) / b) ** n3
      r = (cos + sin) ** (-1\. / n1)
    
      r /= np.max(r)
      return r
    
  3. 我们定义了一个工具对象,以将范围线性缩放为另一个范围,如下所示:

    class LinearScaling(object):
      def __init__(self, src_range, dst_range):
        self.src_start, src_diff = src_range[0], src_range[1] - src_range[0]
        self.dst_start, dst_diff = dst_range[0], dst_range[1] - dst_range[0]
        self.src_to_dst_coeff = dst_diff / src_diff
        self.dst_to_src_coeff = src_diff / dst_diff
    
      def src_to_dst(self, X):
        return (X - self.src_start) * self.src_to_dst_coeff + self.dst_start
    
      def dst_to_src(self, X):
        return (X - self.dst_start) * self.dst_to_src_coeff + self.src_start
    
  4. 现在进入了用户界面,该用户界面的编码如下:

    class SuperShapeFrame(Frame):
      def __init__(self, master = None):
        Frame.__init__(self, master)
        self.grid()
        self.m = 3
        self.n1 = 2
        self.n1_scaling = LinearScaling((.1, 20), (0, 200))
        self.n2 = 18
        self.n2_scaling = LinearScaling((.1, 20), (0, 200))
        self.n3 = 18
        self.n3_scaling = LinearScaling((.1, 20), (0, 200))
    
        self.fig = Figure((6, 6), dpi = 80)
        canvas = FigureCanvasTkAgg(self.fig, master = self)
        canvas.get_tk_widget().grid(row = 0, column = 0, columnspan = 4)
        label = Label(self, text = 'M')
        label.grid(row = 1, column = 1)
        self.m_slider = Scale(self, from_ = 1, to = 20, orient = HORIZONTAL, command = lambda i : self.update_m())
    
        self.m_slider.grid(row = 1, column = 2)
    
        label = Label(self, text = 'N1')
        label.grid(row = 2, column = 1)
        self.n1_slider = Scale(self, from_ = 0, to = 200, orient = HORIZONTAL, command = lambda i : self.update_n1())
        self.n1_slider.grid(row = 2, column = 2)
    
        label = Label(self, text = 'N2')
        label.grid(row = 3, column = 1)
        self.n2_slider = Scale(self, from_ = 0, to = 200, orient = HORIZONTAL, command = lambda i : self.update_n2())
        self.n2_slider.grid(row = 3, column = 2)
    
        label = Label(self, text = 'N3')
        label.grid(row = 4, column = 1)
        self.n3_slider = Scale(self, from_ = 0, to = 200, orient = HORIZONTAL, command = lambda i : self.update_n3())
        self.n3_slider.grid(row = 4, column = 2)
    
        self.draw_figure()
    
      def update_m(self):
        self.m = self.m_slider.get()
        self.refresh_figure()
    
      def update_n1(self):
        self.n1 = self.n1_scaling.dst_to_src(self.n1_slider.get())
        self.refresh_figure()
    
      def update_n2(self):
        self.n2 = self.n2_scaling.dst_to_src(self.n2_slider.get())
        self.refresh_figure()
    
      def update_n3(self):
        self.n3 = self.n3_scaling.dst_to_src(self.n3_slider.get())
        self.refresh_figure()
    
      def refresh_figure(self):
        r = supershape_radius(self.phi, 1, 1, self.m, self.n1, self.n2, self.n3)
        self.lines.set_ydata(r)
        self.fig.canvas.draw_idle()
      def draw_figure(self):
        self.phi = np.linspace(0, 2 * numpy.pi, 1024)
        r = supershape_radius(self.phi, 1, 1, self.m, self.n1, self.n2, self.n3)
        ax = self.fig.add_subplot(111, polar = True)
        self.lines, = ax.plot(self.phi, r, lw = 3.)
        self.fig.canvas.draw()
    
  5. 最后,我们设置并启动我们的用户界面,如下所示:

    app = SuperShapeFrame()
    app.master.title('SuperShape')
    app.mainloop()
    
  6. 绘制了SuperShape曲线,可以用四个滑块控件对其进行控制,如下图所示:

    How to do it...

工作原理

在此示例中,的所有工作均由SuperShapeFrame对象完成,该对象是 TKinter Frame类的子类。 Frame对象只是 Tkinter 词典中的一个窗口。

matplotlib 提供了一个FigureCanvasTKAgg对象作为matplotlib.backends.backend_tkagg模块的一部分。 FigureCanvasTKAgg对象封装了Figure实例,其行为类似于 Tkinter 对象。 因此,在此示例中,我们创建一个窗口(Frame对象),并使用小部件填充该窗口:四个滑块实例和一个FigureCanvasTKAgg实例。 画布创建如下:

self.fig = Figure((6, 6), dpi = 80)
canvas = FigureCanvasTkAgg(self.fig, master = self)

我们首先创建一个 matplotlib 图形,并将其作为参数传递给FigureCanvasTkAgg构造器。 我们不需要跟踪画布本身。 我们只需要跟踪该数字即可。 画布的大小取决于图形的大小及其分辨率。 在这里,我们的数字是 80 dpi 的六个单位的正方形:480 像素。

我们需要执行两个操作:绘制图形并刷新它。 我们只需要画一次图。 然后,当用户更改我们显示的曲线的某些参数时,我们必须刷新图形。

使用draw_figure()方法绘制该图,如下所示:

  def draw_figure(self):
    self.phi = np.linspace(0, 2 * np.pi, 1024)
    r = supershape_radius(self.phi, 1, 1, self.m, self.n1, self.n2, self.n3)
    ax = self.fig.add_subplot(111, polar = True)
    self.lines, = ax.plot(self.phi, r, lw = 3.)
    self.fig.canvas.draw()

我们将Axes实例ax附加到我们的Figure实例。 我们绘制曲线并跟踪此操作的结果:线的集合。 最后,我们告诉画布渲染图。

使用refresh_figure()方法刷新图,如下所示:

  def refresh_figure(self):
    r = supershape_radius(self.phi, 1, 1, self.m, self.n1, self.n2, self.n3)
    self.lines.set_ydata(r)
    self.fig.canvas.draw_idle()

刷新图形时,我们不会重新绘制所有图形(但是可以那样做)。 我们只需更新行集合并通知画布来更新图形。 每次用户修改滑块时,我们通过调用refresh_figure()刷新图形。

使用 Tkinter 滑块的一个怪癖是这些滑块仅返回整数值; 但是,实际上,至少在科学或工程领域,我们需要浮点值。 要解决,我们实现了LinearScaling类,将值从一个范围线性缩放到另一个范围。 滑块的范围为 0 到 200。为四个参数中的每个参数创建一个LinearScaling实例,以将滑块位置转换为参数的实际值。

将绘图集成到 wxWidgets 用户界面

使用 Tkinter,我们可以将 matplotlib 的绘图函数与功能齐全的 GUI 库相结合。 该解决方案的优点是仅依赖于标准 Python。 但是,针对 Tkinter 的经典说法是外观:用户界面具有自己的外观,而不是其运行平台的外观。

wxWidgets 用户界面是 Python 的另一个 GUI 模块,它绑定了 wx 库。 wx 库公开了用于在 Windows,OSX 和 Linux 上创建图形界面的通用 API。 用 wx 创建的图形界面将具有运行它们的平台的外观。 在本秘籍中,我们将研究如何将 wxWidgets 与 matplotlib 接口。

操作步骤

总体思路与 matplotlib/Tkinter 集成所做的非常相似。 matplotlib 提供了一个特殊的 wxWidget 小部件,该小部件嵌入了Figure对象。 创建和更新Figure对象的工作方式与以前相同,如以下步骤所示:

  1. 我们从如下的导入指令开始:

    import wx
    import numpy as np
    
    from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg
    from matplotlib.figure import Figure
    
  2. 我们添加了使用以下代码定义 SuperShape 曲线的函数:

    def supershape_radius(phi, a, b, m, n1, n2, n3):
      theta = .25 * m * phi
    
      cos = np.fabs(np.cos(theta) / a) ** n2
      sin = np.fabs(np.sin(theta) / b) ** n3
      r = (cos + sin) ** (-1\. / n1)
      r /= np.max(r)
      return r
    
  3. 我们将要需要一个工具对象,以将从一个范围线性缩放到另一个范围,如下所示:

    class LinearScaling(object):
      def __init__(self, src_range, dst_range):
        self.src_start, src_diff = src_range[0], src_range[1] - src_range[0]
        self.dst_start, dst_diff = dst_range[0], dst_range[1] - dst_range[0]
        self.src_to_dst_coeff = dst_diff / src_diff
        self.dst_to_src_coeff = src_diff / dst_diff
    
      def src_to_dst(self, X):
        return (X - self.src_start) * self.src_to_dst_coeff + self.dst_start
    
      def dst_to_src(self, X):
        return (X - self.dst_start) * self.dst_to_src_coeff + self.src_start
    
  4. 我们使用以下代码定义用户界面:

    class SuperShapeFrame(wx.Frame):
      def __init__(self, parent, id, title):
        wx.Frame.__init__(self, parent, id, title,
          style = wx.DEFAULT_FRAME_STYLE ^ wx.RESIZE_BORDER,
          size = (480, 640))
        self.m = 3
        self.n1 = 2
        self.n1_scaling = LinearScaling((.01, 20), (0, 200))
    
        self.n2 = 18
        self.n2_scaling = LinearScaling((.01, 20), (0, 200))
    
        self.n3 = 18
        self.n3_scaling = LinearScaling((.01, 20), (0, 200))
    
        self.fig = Figure((6, 6), dpi = 80)
    
        panel = wx.Panel(self, -1)
        self.m_slider = wx.Slider(panel, -1, self.m, 1, 20, size = (250, -1), style = wx.SL_AUTOTICKS | wx.SL_HORIZONTAL | wx.SL_LABELS)
    
        self.n1_slider = wx.Slider(panel, -1, self.n1_scaling.src_to_dst(self.n1), 0, 200, size = (250, -1), style = wx.SL_AUTOTICKS | wx.SL_HORIZONTAL | wx.SL_LABELS)
    
        self.n2_slider = wx.Slider(panel, -1, self.n1_scaling.src_to_dst(self.n2), 0, 200, size = (250, -1), style = wx.SL_AUTOTICKS | wx.SL_HORIZONTAL | wx.SL_LABELS)
    
        self.n3_slider = wx.Slider(panel, -1, self.n1_scaling.src_to_dst(self.n3), 0, 200, size = (250, -1), style = wx.SL_AUTOTICKS | wx.SL_HORIZONTAL | wx.SL_LABELS)
    
        self.m_slider.Bind(wx.EVT_SCROLL, self.on_m_slide)
        self.n1_slider.Bind(wx.EVT_SCROLL, self.on_n1_slide)
        self.n2_slider.Bind(wx.EVT_SCROLL, self.on_n2_slide)
        self.n3_slider.Bind(wx.EVT_SCROLL, self.on_n3_slide)
    
        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(FigureCanvasWxAgg(panel, -1, self.fig), 0, wx.TOP)
        sizer.Add(self.m_slider,  0, wx.ALIGN_CENTER)
        sizer.Add(self.n1_slider, 0, wx.ALIGN_CENTER)
        sizer.Add(self.n2_slider, 0, wx.ALIGN_CENTER)
        sizer.Add(self.n3_slider, 0, wx.ALIGN_CENTER)
        panel.SetSizer(sizer)
    
        self.draw_figure()
    
      def on_m_slide(self, event):
        self.m = self.m_slider.GetValue()
        self.refresh_figure()
    
      def on_n1_slide(self, event):
    
        self.n1 = self.n1_scaling.dst_to_src(self.n1_slider.GetValue())
        self.refresh_figure()
    
      def on_n2_slide(self, event):
        self.n2 = self.n2_scaling.dst_to_src(self.n2_slider.GetValue())
        self.refresh_figure()
      def on_n3_slide(self, event):
        self.n3 = self.n3_scaling.dst_to_src(self.n3_slider.GetValue())
        self.refresh_figure()
    
      def refresh_figure(self):
        r = supershape_radius(self.phi, 1, 1, self.m, self.n1, self.n2, self.n3)
        self.lines.set_ydata(r)
        self.fig.canvas.draw_idle()
    
      def draw_figure(self):
        self.phi = np.linspace(0, 2 * np.pi, 1024)
        r = supershape_radius(self.phi, 1, 1, self.m, self.n1, self.n2, self.n3)
        ax = self.fig.add_subplot(111, polar = True)
        self.lines, = ax.plot(self.phi, r, lw = 3.)
    
        self.fig.canvas.draw()
    
  5. 现在,我们可以按照下面的初始化和启动用户界面:

    app = wx.App(redirect = True)
    top = SuperShapeFrame(None, -1, 'SuperShape')
    top.Show()
    app.MainLoop()
    
  6. 该脚本生成一个显示SuperShape曲线的窗口。 与本章前面的方法一样,移动滑块将修改曲线的形状,如下图所示:

    How to do it...

用户界面的外观将根据在哪个平台上运行脚本而有所不同:Linux,Windows,OSX 等。

工作原理

matplotlib 在matplotlib.backends.backend_wxagg模块中提供FigureCanvasWxAgg对象。 FigureCanvasWxAgg对象是一个 wxWidget 小部件,其中包含 matplotlib 图形。 该小部件的实际大小取决于它包含的图形。 在这里,我们创建一个6 x 6单位的Figure实例,每单位 80 像素:480 x 480像素。 创建Figure实例及其小部件就像运行以下代码一样容易:

self.fig = Figure((6, 6), dpi = 80)
canvas = FigureCanvasWxAgg(canvas_container, -1, self.fig)

与 Tkinter 示例一样,使用 matplotlib 小部件需要执行两个步骤。 我们必须绘制该图并进行更新。 同样,我们创建draw_figure()refresh_figure()方法来处理这些步骤。

draw_figure()方法创建一个Axes实例,绘制曲线并跟踪结果,即一组线。 最后,该图呈现如下:

  def draw_figure(self):
    self.phi = np.linspace(0, 2 * np.pi, 1024)
    r = supershape_radius(self.phi, 1, 1, self.m, self.n1, self.n2, self.n3)
    ax = self.fig.add_subplot(111, polar = True)
    self.lines, = ax.plot(self.phi, r, lw = 3.)

    self.fig.canvas.draw()

然后,由于需要用户输入,每次需要刷新图形时,我们将其称为refresh_figure()refresh_figure()方法使用以下代码更新绘制绘图的线组:

  def refresh_figure(self):
    r = supershape_radius(self.phi, 1, 1, self.m, self.n1, self.n2, self.n3)
    self.lines.set_ydata(r)
    self.fig.canvas.draw_idle()

因此,正如我们所看到的,使用 wxWidget 或 Tkinter 不会在 matplotlib 方面引入任何明显的区别。 请注意,就像 Tkinter 一样,wxWidgets 滑块只能输出整数值的位置,并且我们必须使用先前秘籍的LinearScaling对象来获取实际值的位置。

将绘图集成到 GTK 用户界面

GTK 是一个用户界面库,在 Linux 环境中特别流行。 GTK 非常完整,其针对 Python 的PyGObject绑定使用起来特别方便。 在本秘籍中,我们演示了如何将 GTK 与 matplotlib 接口。 我们使用 SuperShape 应用进行此演示。

准备

此秘籍演示了如何将最新的 Python 绑定用于 GTK PyGObject。 因此,您将需要安装PyGObject(大多数 Linux 发行版都具有标准包),并且显然如果您还没有 GTK,则需要安装 GTK。

操作步骤

到现在为止,如果经历了 Tkinter 和 WxWidget 上的先前秘籍,则将看到 matplotlib 与用户界面集成方式的一种模式。 这里的模式是相同的:Matplolib 提供了特定于 GTK 的画布对象,该对象嵌入了Figure实例。 可以通过以下步骤将绘图集成到 GTK 用户界面:

  1. 我们从必要的导入指令开始,如下所示:

    from gi.repository import Gtk
    import numpy as np
    from matplotlib.figure import Figure
    from matplotlib.backends.backend_gtk3agg import FigureCanvasGTK3Agg
    
  2. 我们添加以下 SuperShape 曲线定义:

    def supershape_radius(phi, a, b, m, n1, n2, n3):
      theta = .25 * m * phi
      cos = np.fabs(np.cos(theta) / a) ** n2
      sin = np.fabs(np.sin(theta) / b) ** n3
      r = (cos + sin) ** (-1\. / n1)
      r /= np.max(r)
      return r
    
  3. 然后,我们使用以下代码定义用户界面:

    class SuperShapeWindow(Gtk.Window):
      def __init__(self):
        Gtk.Window.__init__(self, title = 'SuperShape')
    
        layout_box = Gtk.Box.new(Gtk.Orientation.VERTICAL, 0)
        self.add(layout_box)
        self.m = 3
        self.n1 = 2
        self.n2 = 18
        self.n3 = 18
    
        self.fig = Figure((6, 6), dpi = 80)
        w, h = self.fig.get_size_inches()
        dpi_res = self.fig.get_dpi()
        w, h = int(np.ceil(w * dpi_res)), int(np.ceil(h * dpi_res))
    
        canvas = FigureCanvasGTK3Agg(self.fig)
        canvas.set_size_request(w, h)
        layout_box.add(canvas)
    
        self.m_slider = Gtk.HScale.new(Gtk.Adjustment(self.m, 1, 20, 1., .1, 1))
        self.m_slider.connect('value-changed', self.on_m_slide)
        layout_box.add(self.m_slider)
    
        self.n1_slider = Gtk.HScale.new(Gtk.Adjustment(self.n1, .01, 20, 1., .1, 1))
        self.n1_slider.connect('value-changed', self.on_n1_slide)
        layout_box.add(self.n1_slider)
    
        self.n2_slider = Gtk.HScale.new(Gtk.Adjustment(self.n2, .01, 20, 1., .1, 1))
        self.n2_slider.connect('value-changed', self.on_n2_slide)
        layout_box.add(self.n2_slider)
    
        self.n3_slider = Gtk.HScale.new(Gtk.Adjustment(self.n3, .01, 20, 1., .1, 1))
        self.n3_slider.connect('value-changed', self.on_n3_slide)
        layout_box.add(self.n3_slider)
    
        self.draw_figure()
      def on_m_slide(self, event):
        self.m = self.m_slider.get_value()
    
        self.refresh_figure()
    
      def on_n1_slide(self, event):
        self.n1 = self.n1_slider.get_value()
        self.refresh_figure()
      def on_n2_slide(self, event):
        self.n2 = self.n2_slider.get_value()
        self.refresh_figure()
    
      def on_n3_slide(self, event):
        self.n3 = self.n3_slider.get_value()
        self.refresh_figure()
    
      def draw_figure(self):
        self.phi = np.linspace(0, 2 * np.pi, 1024)
        ax = self.fig.add_subplot(111, polar = True)
        r = supershape_radius(self.phi, 1, 1, self.m, self.n1, self.n2, self.n3)
        self.lines, = ax.plot(self.phi, r, lw = 3.)
        self.fig.canvas.draw()
    
      def refresh_figure(self):
        r = supershape_radius(self.phi, 1, 1, self.m, self.n1, self.n2, self.n3)
        self.lines.set_ydata(r)
        self.fig.canvas.draw_idle()
    
  4. 总结一下,我们设置了,并使用以下代码启动了该应用:

    win = SuperShapeWindow()
    win.connect('delete-event', Gtk.main_quit)
    win.show_all()
    Gtk.main()
    
  5. SuperShape曲线显示在一个窗口中,并且可以使用滑块调整曲线的参数,如下图所示:

    How to do it...

工作原理

matplotlib 在matplotlib.backends.backend_gtk3agg模块中提供FigureCanvasGTK3Agg对象。 FigureCanvasGtk3Agg对象是一个包含 matplotlib 图形的 GTK 小部件。 我们必须使用以下代码设置canvas对象的大小:

  self.fig = Figure((6, 6), dpi = 80)

  w, h = self.fig.get_size_inches()
  dpi_res = self.fig.get_dpi()
  w, h = int(np.ceil(w * dpi_res)), int(np.ceil(h * dpi_res))

  canvas = FigureCanvasGTK3Agg(self.fig)
  canvas.set_size_request(w, h)

从那里,我们回到了熟悉的组织。 我们有,draw_figure()方法创建图和refresh_figure()方法更新图。 这些方法与 WxWidget 秘籍的方法相同。 WxWidget 秘籍的一些细微差异来自 GTK API 规范。 例如,GTK 中的滑块小部件可与浮点单元一起使用。

将绘图集成到 Pyglet 应用

Pyglet 是一个编写良好的 Python 模块,可以在任何平台上使用 OpenGL。 使用 Pyglet(从而使用 OpenGL)可以最大程度地使用计算机的图形硬件。 例如,使用 Pyglet 在具有花哨过渡效果的三个相邻屏幕上显示图形将非常容易。 在本秘籍中,我们将了解如何将 matplotlib 与 Pyglet 进行接口。 与前面的示例一样,我们将在整个屏幕上显示 SuperShape 曲线,并且没有任何小部件。

操作步骤

Pyglet 不与 Tkinter 和 wxWidgets 小部件具有相同的功能。 该脚本将曲线渲染为内存图像。 然后,该图像将简单地显示在整个屏幕表面上。 因此,该图将以全屏模式显示。 让我们看看如何使用以下代码完成此操作:

import pyglet, StringIO
import numpy as np

from matplotlib.figure import Figure
from matplotlib.backends.backend_agg import FigureCanvasAgg

def render_figure(fig):
  w, h = fig.get_size_inches()
  dpi_res = fig.get_dpi()
  w, h = int(np.ceil(w * dpi_res)), int(np.ceil(h * dpi_res))

  canvas = FigureCanvasAgg(fig)
  pic_data = StringIO.StringIO()
  canvas.print_raw(pic_data, dpi = dpi_res)
  return pyglet.image.ImageData(w, h, 'RGBA', pic_data.getvalue(), -4 * w)
def draw_figure(fig):
  X = np.linspace(-6, 6, 1024)
  Y = np.sinc(X)

  ax = fig.add_subplot(111)
  ax.plot(X, Y, lw = 2, color = 'k')

window = pyglet.window.Window(fullscreen = True)
dpi_res = min(window.width, window.height) / 10
fig = Figure((window.width / dpi_res, window.height / dpi_res), dpi = dpi_res)

draw_figure(fig)
image = render_figure(fig)

@window.event
def on_draw():
  window.clear()
  image.blit(0, 0)

pyglet.app.run()

该脚本将以全屏模式显示曲线,并利用整个屏幕表面。 请注意,您必须按 Esc 键以关闭应用。

工作原理

matplotlib 提供了一个特殊对象FigureCanvasAgg,作为matplotlib.backends.backend_agg模块的一部分。 该对象构造器将图形作为输入,并将结果呈现到文件中。 使用print_raw方法,文件将包含原始像素数据。 标准的StringIO模块允许我们创建一个内存文件。 因此,我们仅要求FigureCanvasAgg渲染为StringIO文件,如下所示:

  canvas = FigureCanvasAgg(fig)
  pic_data = StringIO.StringIO()
  canvas.print_raw(pic_data, dpi = dpi_res)

然后,我们可以检索内存中的数据并将其用于创建 Pyglet Image对象,如下所示:

pyglet.image.ImageData(w, h, 'RGBA', pic_data.getvalue(), -4 * w)

注意,我们必须指定图片的宽度w和高度h。 可以使用以下代码从Figure实例的尺寸及其分辨率推导得出:

  w, h = fig.get_size_inches()
  dpi_res = fig.get_dpi()
  w, h = int(np.ceil(w * dpi_res)), int(np.ceil(h * dpi_res))

该秘籍更一般地向您展示如何将 matplotlib 图形渲染到内存缓冲区中。 例如,可以编写一个脚本,在内存中呈现多个图形,然后将其输入模块以创建视频。 因为所有这些都发生在内存中,所以它比仅将图片文件保存在硬盘上并随后将图片编译成视频要快。

posted @ 2025-10-09 13:24  绝不原创的飞龙  阅读(3)  评论(0)    收藏  举报