请 [注册] 或 [登录]  | 返回主站

量化交易吧 /  数理科学 帖子:3352802 新帖:2

【翻译搬运】Matplotlib - 用Python绘制2D和3D图像

SCSDV_d发表于:8 月 24 日 18:54回复(1)

Matplotlib是Python中最常用的可视化工具之一,可以非常方便地创建海量类型的2D图表和一些基本的3D图表。本文翻译自Jupyter nbviewer中的第四讲,主要介绍了绘制2D图像的相关信息,图像的位置、大小,曲线的样式、宽度,坐标轴的刻度、数值、标签,以及图例、标题参数的设置,还包括各种类型的图像的绘制,如柱状图、色图、等高线图等等。作为延伸,又介绍了3D曲线图、框线图和投影图,以及动图的制作。最后作为了解,介绍了后端以及图片视频格式的相关内容。

作者:J.R. Johansson (邮箱:jrjohansson@gmail.com)

最新版本的用法介绍见网站http://github.com/jrjohansson/scientific-python-lectures. 其他相关介绍见http://jrjohansson.github.io.

# 利用matplotlib生成的图像嵌入notebook中,而不用每次生成图像时打开一个新的窗口,
# 后面会再次提到它的用法。如果你在使用旧版本的Python,请运行 ‘%pylab inline’,如新版本,则输入
%matplotlib inline

简介¶

Matplotlib是一个绘制2D和3D科学图像的库,它包含了以下的优点:

  1. 容易学习和掌握
  2. 兼容LaTeX格式的标题和文档
  3. 可以控制图像中的每个元素,包括图像大小和扫描精度。
  4. 对于很多格式都可以高质量的输出图像,包括PNG,PDF,SVG,EPS和PGF.
  5. 可以生成图形用户界面(GUI),做到交互式的获取图像以及无脑生成图像文件(通常用于批量作业)

Matplotlib最重要的一个特点,也是它作出的图像非常适合作为科学出版物的原因,是因为图像可以完全被程序所控制。这一点对于图像重现非常重要,同时为更新数据后重新作图以及改变图像形状提供了方便。更多关于Matplotlib网页请见http://matplotlib.org/

在Python中调用Matplotlib函数包有两种方法,一种是在pylab模式中包含一个星号(简单的方法)

from pylab import *

另一种是在matplotlib.pyplot模式下使用plt(整洁的方法):

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

MATLAB样式的API¶

学习用matplotlib绘制图像最简单的方法使用matplotlib自身提供的类似MATLAB的API。它和MATLAB绘制图像的函数非常相近,所以熟悉MATLAB的用户可以非常容易的上手。采用在pylab模式中包含星号的方式可以使用matplotlib中的API:

from pylab import *

例:¶

采用一个类似MATLAB作图的API,能够做出以下简单的图像:

x = np.linspace(0, 5, 10)
y = x ** 2
figure()
plot(x, y, 'r')
xlabel('x')
ylabel('y')
title('title')
show()

MATLAB中大多数绘图相关的函数都能在pylab模式下实现。例如将多个图像绘制在一个窗口中,以及选择颜色和线条类型:

subplot(1,2,1)
plot(x, y, 'r--')
subplot(1,2,2)
plot(y, x, 'g*-');

pylab这种MATLAB格式的API有一个优点,对于MATLAB熟悉的用户能够非常容易上手,而且对于绘制简单图像而言不需要花费很多精力去学习。

然而,对于并不是特别简单的图像,并不推荐使用MATLAB类似的API,学习使用matplotlib面向对象的绘图API是一种更好更强大的方法。对于多个复杂图像绘制在一个窗口中,插入图像和加入其它成分这样的复杂操作,matplotlib的API能够很好的解决。

matplotlib面向对象的API¶

面向对象的程序的主要思路是让用户能够面向对象来使用函数和进行操作,而不是像MATLAB类似的API一样采用全局的程序状态。Matplotlib的优势在绘制多个图像或者一个图像窗口中包含多个子图像的时候能够彰显出来。

我们这次采用面向对象的API来绘制和前一个例子相似的图像,但是这次我们存储一个引用在新创建的fig变量的图像中,而并不直接创建一个全局的图像,然后我们创建一个新的坐标轴图像axes(采用Figure函数族中的add_axes方法):

fig = plt.figure()

axes = fig.add_axes([0.1, 0.1, 0.8, 0.8]) # 左侧间距,底部间距,宽度,高度 (从0到1)

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

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

尽管代码看起来多了一点,但是我们现在能够完全掌控图像的坐标轴位置,并且能够在图像上轻易增加多个坐标轴:

fig = plt.figure()

axes1 = fig.add_axes([0.1, 0.1, 0.8, 0.8]) # 主坐标轴
axes2 = fig.add_axes([0.2, 0.5, 0.4, 0.3]) # 插入的坐标轴

# 主要图像
axes1.plot(x, y, 'r')
axes1.set_xlabel('x')
axes1.set_ylabel('y')
axes1.set_title('title')

# 插入的图像
axes2.plot(y, x, 'g')
axes2.set_xlabel('y')
axes2.set_ylabel('x')
axes2.set_title('insert title');

如果我们并不关心坐标轴的位置是否要明确处于画图窗口的哪个位置,我们可以采用matplotlib布局工具中的一个,例如subplots,用法如下:

fig, axes = plt.subplots()

axes.plot(x, y, 'r')
axes.set_xlabel('x')
axes.set_ylabel('y')
axes.set_title('title');
fig, axes = plt.subplots(nrows=1, ncols=2)

for ax in axes:
    ax.plot(x, y, 'r')
    ax.set_xlabel('x')
    ax.set_ylabel('y')
    ax.set_title('title')

这样的代码很简单,但是如果坐标轴或者标签重合在一起,就显得不太美观了。

我们可以采用fig.tight_layout方法来解决这个问题,它能够更自动调整坐标轴在图像窗口的位置,从而避免重合的发生:

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

for ax in axes:
    ax.plot(x, y, 'r')
    ax.set_xlabel('x')
    ax.set_ylabel('y')
    ax.set_title('title')
    
fig.tight_layout()

图像大小,纵横比和图像精度¶

Matplotlib在绘制Figure对象时,允许用户确定图像纵横比、图像精度和大小,采用figsize和dpi关键字参数。figsize是关于图像宽度和高度(单位:英寸)的元组型变量,dpi是每英寸点数(像素)。为创建一个800×400像素,每英寸点数为100的图像,代码如下:

fig = plt.figure(figsize=(8,4), dpi=100)
<matplotlib.figure.Figure at 0x7f7f385be950>

同样的操作可以在布局工具中运行,例如subplots函数:

fig, axes = plt.subplots(figsize=(12,3))

axes.plot(x, y, 'r')
axes.set_xlabel('x')
axes.set_ylabel('y')
axes.set_title('title');

保存图像¶

我们可以采用Figure函数族中的savefig方法来存储图像:

fig.savefig("filename.png")

这里我们还可以确定图像精度,以及选择不同的输出格式:

fig.savefig("filename.png", dpi=200)

可以存储什么样的格式?为获取最高质量图像,我们应该选择哪种格式?¶

Matplotlib可以产生各种高质量的输出格式,包括PNG,JPG,EPS,SVG,PGF和PDF。在学术论文中,如果可以的话推荐使用PDF格式(LaTeX文件编译pdflatex可以采用includegraphics来编译PDF)。在一些情况下,PGF格式也是一种好的选择。

图例、标签和标题¶

既然我们已经介绍了绘图和添加坐标轴的基本方法,我们现在来介绍如何添加图例、标签和标题。

标题¶

标题可以加在每个图像上,可以采用set_title方法来设置标题:

ax.set_title("title");

坐标轴标签¶

同样的,用set_xlabel和set_ylabel可以设置X和Y轴的标签:

ax.set_xlabel("x")
ax.set_ylabel("y");

图例¶

图像中曲线的图例可以用两种方式添加,一种是用坐标轴对象的legend指令,对于之前定义的曲线添加列表或元组形式的文本:

ax.legend(["curve1", "curve2", "curve3"]);

上面这种方法其实是MATLAB的API,如果图像上的曲线被添加或者删除时可能会报错(导致错误的添加图例)。

一种更好的方法是在绘图或添加其他元素的时候利用label="label text" 关键字参数,然后用无参数的legend指令把图例添加到图像上:

ax.plot(x, x**2, label="curve1")
ax.plot(x, x**3, label="curve2")
ax.legend();

这种方法的优点是,如果在图像上添加或者删除曲线,图例会随之自动更新。

legend函数有一个可供选择的关键字参数loc,用来确定图例添加的位置,loc参数的允许值是数值型代码,详见http://matplotlib.org/users/legend_guide.html#legend-location. 下面列举了一些常见的loc值:

ax.legend(loc=0) # 由matplotlib确定最优位置
ax.legend(loc=1) # 右上角
ax.legend(loc=2) # 左上角
ax.legend(loc=3) # 左下角
ax.legend(loc=4) # 右下角
# .. 还有一些其他的选择,不一一列举
<matplotlib.legend.Legend at 0x7f7f441f33d0>

运用上面介绍的关于标题,坐标轴标签和图例的内容,我们可以作出如下图像:

fig, ax = plt.subplots()

ax.plot(x, x**2, label="y = x**2")
ax.plot(x, x**3, label="y = x**3")
ax.legend(loc=2); # 左上角
ax.set_xlabel('x')
ax.set_ylabel('y')
ax.set_title('title');

文本格式: LaTeX,字体大小,字体样式¶

上面的绘制图像的方法都非常实用,但是还不能满足发表论文所需的标准。第一也是最重要的,我们需要采用LaTeX文本格式;第二,我们需要能够调整字体大小以适应出版社所需的要求。

Matplotlib与LaTeX非常兼容,我们只需采用美元符号来封装LaTeX的文本(图例,题目,标签等等),例如:"$y=x^3$"。

但是这里我们可能在转换LaTeX代码和Python字符串的过程中出现一点问题。在LaTeX中,我们经常用反斜杠符号,例如用\alpha来产生符号α. 但是反斜杠在Python中已经有别的含义(转义码字符)。为了避免Python和LaTeX代码混淆,我们采用“原始”字符串。原始字符串带有前缀“r”,例如r"\alpha" 或者 r'\alpha' 而不是 "\alpha" or '\alpha':

fig, ax = plt.subplots()

ax.plot(x, x**2, label=r"$y = \alpha^2$")
ax.plot(x, x**3, label=r"$y = \alpha^3$")
ax.legend(loc=2) # 左上角
ax.set_xlabel(r'$\alpha$', fontsize=18)
ax.set_ylabel(r'$y$', fontsize=18)
ax.set_title('title');

我们也可以改变全局的字体大小和字体样式,使得图像中的所有文本元素都适用(刻度标记、坐标轴标签,标题和图例等等):

# 更新matplotlib的布局参数:
matplotlib.rcParams.update({'font.size': 18, 'font.family': 'serif'})
fig, ax = plt.subplots()

ax.plot(x, x**2, label=r"$y = \alpha^2$")
ax.plot(x, x**3, label=r"$y = \alpha^3$")
ax.legend(loc=2) # 左上角
ax.set_xlabel(r'$\alpha$')
ax.set_ylabel(r'$y$')
ax.set_title('title');

全局字体选择STIX字体样式是一个好的选择:

# 更新matplotlib的布局参数:
matplotlib.rcParams.update({'font.size': 18, 'font.family': 'STIXGeneral', 'mathtext.fontset': 'stix'})
fig, ax = plt.subplots()

ax.plot(x, x**2, label=r"$y = \alpha^2$")
ax.plot(x, x**3, label=r"$y = \alpha^3$")
ax.legend(loc=2) # 左上角
ax.set_xlabel(r'$\alpha$')
ax.set_ylabel(r'$y$')
ax.set_title('title');

或者,我们可以要求matplotlib在图像中采用LaTeX文本元素:

matplotlib.rcParams.update({'font.size': 18, 'text.usetex': True})
fig, ax = plt.subplots()

ax.plot(x, x**2, label=r"$y = \alpha^2$")
ax.plot(x, x**3, label=r"$y = \alpha^3$")
ax.legend(loc=2) # 左上角
ax.set_xlabel(r'$\alpha$')
ax.set_ylabel(r'$y$')
ax.set_title('title');
# 存储
matplotlib.rcParams.update({'font.size': 12, 'font.family': 'sans', 'text.usetex': False})

设置颜色,线条宽度和线条类型¶

颜色¶

用matplotlib,我们可以运用各种方法定义线条颜色和其他图像元素。首先,我们可以运用MATLAB的语法,定义'b'代表蓝色,'g'代表绿色,等等。同样,matplotlib也支持用MATLAB的API设置线条类型,例如:'b.-'代表蓝色虚点线:

# MATLAB样式的线条颜色和类型
ax.plot(x, x**2, 'b.-') # 蓝色虚点线
ax.plot(x, x**3, 'g--') # 绿色短划线
[<matplotlib.lines.Line2D at 0x7f7f2892a790>]

我们也可以定义用颜色的英文名称定义,或者RGB十六进制码,或者用color和alpha关键字参数来选择性提供α值:

fig, ax = plt.subplots()

ax.plot(x, x+1, color="red", alpha=0.5) # 半透明红色
ax.plot(x, x+2, color="#1155dd")        # 浅蓝色的RGB十六进制码
ax.plot(x, x+3, color="#15cc55")        # 浅绿色的RGB十六进制码
[<matplotlib.lines.Line2D at 0x7f7f382a0050>]

线条和标记样式¶

我们可以用linewidth或者lw关键字参数来调整线宽度,线条样式则可以在linestyle或者ls关键字参数中选择:

fig, ax = plt.subplots(figsize=(12,6))

ax.plot(x, x+1, color="blue", linewidth=0.25)
ax.plot(x, x+2, color="blue", linewidth=0.50)
ax.plot(x, x+3, color="blue", linewidth=1.00)
ax.plot(x, x+4, color="blue", linewidth=2.00)

# 线条样式选择
ax.plot(x, x+5, color="red", lw=2, linestyle='-')
ax.plot(x, x+6, color="red", lw=2, ls='-.')
ax.plot(x, x+7, color="red", lw=2, ls=':')

# 自定义设置
line, = ax.plot(x, x+8, color="black", lw=1.50)
line.set_dashes([5, 10, 15, 10]) # 格式:线长, 间距, ...

# 标记符号
ax.plot(x, x+ 9, color="green", lw=2, ls='--', marker='+')
ax.plot(x, x+10, color="green", lw=2, ls='--', marker='o')
ax.plot(x, x+11, color="green", lw=2, ls='--', marker='s')
ax.plot(x, x+12, color="green", lw=2, ls='--', marker='1')

# 标记大小和颜色
ax.plot(x, x+13, color="purple", lw=1, ls='-', marker='o', markersize=2)
ax.plot(x, x+14, color="purple", lw=1, ls='-', marker='o', markersize=4)
ax.plot(x, x+15, color="purple", lw=1, ls='-', marker='o', markersize=8, markerfacecolor="red")
ax.plot(x, x+16, color="purple", lw=1, ls='-', marker='s', markersize=8, 
        markerfacecolor="yellow", markeredgewidth=2, markeredgecolor="blue");

控制坐标轴外观¶

坐标轴外观是一个图像重要的方面,特别是我们经常需要更改它以满足出版刊物对于图像的要求。我们需要控制刻度和标签的位置,更改字体大小和坐标轴标签。这一节中,我们将会学习如何控制matplotlib图像的这些参数。

绘图范围¶

首先我们想要设置坐标轴的范围,可以运用坐标轴对象中的set_ylim和set_xlim,或者axis('tight')来自动设置“紧密结合”的坐标范围:

fig, axes = plt.subplots(1, 3, figsize=(12, 4))

axes[0].plot(x, x**2, x, x**3)
axes[0].set_title("default axes ranges")

axes[1].plot(x, x**2, x, x**3)
axes[1].axis('tight')
axes[1].set_title("tight axes")

axes[2].plot(x, x**2, x, x**3)
axes[2].set_ylim([0, 60])
axes[2].set_xlim([2, 5])
axes[2].set_title("custom axes range");

对数标度¶

对于单个或者两个坐标轴都可以设置对数标度。这个功能其实仅仅是Matplotlib全部变换系统的一个应用。每个坐标标度可以分别用set_xscale和set_yscale来设置(值填入“log”即可):

fig, axes = plt.subplots(1, 2, figsize=(10,4))
      
axes[0].plot(x, x**2, x, np.exp(x))
axes[0].set_title("Normal scale")

axes[1].plot(x, x**2, x, np.exp(x))
axes[1].set_yscale("log")
axes[1].set_title("Logarithmic scale (y)");

刻度的放置以及用户定义的刻度标签¶

我们可以用set_xticks和set_yticks来明确确定坐标轴的刻度位置,二者都需要提供一个列表型数值。对于每个刻度位置,我们可以用set_xticklabels和set_yticklabels来提供一个用户定义的文本标签:

fig, ax = plt.subplots(figsize=(10, 4))

ax.plot(x, x**2, x, x**3, lw=2)

ax.set_xticks([1, 2, 3, 4, 5])
ax.set_xticklabels([r'$\alpha$', r'$\beta$', r'$\gamma$', r'$\delta$', r'$\epsilon$'], fontsize=18)

yticks = [0, 50, 100, 150]
ax.set_yticks(yticks)
ax.set_yticklabels(["$%.1f$" % y for y in yticks], fontsize=18); # 采用LaTeX格式标签

Matplotlib图像还有很多更为高级的方法来控制主刻度和副刻度的位置,比如在不同环境下自动确定其位置,详见http://matplotlib.org/api/ticker_api.html.

科学计数法¶

对于坐标轴上出现的较大的数字,通常运用科学计数法:

fig, ax = plt.subplots(1, 1)
      
ax.plot(x, x**2, x, np.exp(x))
ax.set_title("scientific notation")

ax.set_yticks([0, 50, 100, 150])

from matplotlib import ticker
formatter = ticker.ScalarFormatter(useMathText=True)
formatter.set_scientific(True) 
formatter.set_powerlimits((-1,1)) 
ax.yaxis.set_major_formatter(formatter)

坐标数字以及坐标标签的位置¶

# x和y轴的距离和坐标轴上的数字
matplotlib.rcParams['xtick.major.pad'] = 5
matplotlib.rcParams['ytick.major.pad'] = 5

fig, ax = plt.subplots(1, 1)
      
ax.plot(x, x**2, x, np.exp(x))
ax.set_yticks([0, 50, 100, 150])

ax.set_title("label and axis spacing")

# 坐标轴标签和坐标轴数字的距离
ax.xaxis.labelpad = 5
ax.yaxis.labelpad = 5

ax.set_xlabel("x")
ax.set_ylabel("y");
# 存储默认值
matplotlib.rcParams['xtick.major.pad'] = 3
matplotlib.rcParams['ytick.major.pad'] = 3

坐标轴位置调整¶

不幸的是,当保存图像时候,标签有时会被缩短,因此需要微调坐标轴的位置,这可以由subplots_adjust来实现:

fig, ax = plt.subplots(1, 1)
      
ax.plot(x, x**2, x, np.exp(x))
ax.set_yticks([0, 50, 100, 150])

ax.set_title("title")
ax.set_xlabel("x")
ax.set_ylabel("y")

fig.subplots_adjust(left=0.15, right=.9, bottom=0.1, top=0.9);

坐标轴网格¶

用坐标轴对象中的grid可以使用和取消网格线。我们也可以用plot函数中同样的关键字参数来定制网格样式:

fig, axes = plt.subplots(1, 2, figsize=(10,3))

# 默认网格外观
axes[0].plot(x, x**2, x, x**3, lw=2)
axes[0].grid(True)

# 用户定义的网格外观
axes[1].plot(x, x**2, x, x**3, lw=2)
axes[1].grid(color='b', alpha=0.5, linestyle='dashed', linewidth=0.5)

轴刻度标记线¶

我们也可以改变轴刻度标记线的参数:

fig, ax = plt.subplots(figsize=(6,2))

ax.spines['bottom'].set_color('blue')
ax.spines['top'].set_color('blue')

ax.spines['left'].set_color('red')
ax.spines['left'].set_linewidth(2)

# 取消右侧的坐标轴刻度
ax.spines['right'].set_color("none")
ax.yaxis.tick_left() # 只在左侧有刻度

双刻度¶

有时在图像中采用两个x或y轴是十分有用的,例如单位不同的多条曲线画在一个图中。Matplotlib提供了twinx和twiny函数:

fig, ax1 = plt.subplots()

ax1.plot(x, x**2, lw=2, color="blue")
ax1.set_ylabel(r"area $(m^2)$", fontsize=18, color="blue")
for label in ax1.get_yticklabels():
    label.set_color("blue")
    
ax2 = ax1.twinx()
ax2.plot(x, x**3, lw=2, color="red")
ax2.set_ylabel(r"volume $(m^3)$", fontsize=18, color="red")
for label in ax2.get_yticklabels():
    label.set_color("red")

x和y值为0的坐标轴¶

fig, ax = plt.subplots()

ax.spines['right'].set_color('none')
ax.spines['top'].set_color('none')

ax.xaxis.set_ticks_position('bottom')
ax.spines['bottom'].set_position(('data',0)) # 设置x坐标轴刻度位置于x=0

ax.yaxis.set_ticks_position('left')
ax.spines['left'].set_position(('data',0))   # 设置y坐标轴刻度位置于y=0

xx = np.linspace(-0.75, 1., 100)
ax.plot(xx, xx**3);

其他二维绘图样式¶

除了常规的plot方法,还有一些其他的函数能够实现不同样式的绘图,所有可以绘制的图像种类请见http://matplotlib.org/gallery.html. 下面展示一些有用的样式:

n = np.array([0,1,2,3,4,5])
fig, axes = plt.subplots(1, 4, figsize=(12,3))

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

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

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

axes[3].fill_between(x, x**2, x**3, color="green", alpha=0.5);
axes[3].set_title("fill_between");
/opt/conda/envs/python2/lib/python2.7/site-packages/matplotlib/collections.py:590: FutureWarning: elementwise comparison failed; returning scalar instead, but in the future will perform elementwise comparison
  if self._edgecolors == str('face'):
# 用add_axes的极坐标图和球极投影
fig = plt.figure()
ax = fig.add_axes([0.0, 0.0, .6, .6], polar=True)
t = np.linspace(0, 2 * np.pi, 100)
ax.plot(t, t, color='blue', lw=3);
# 柱状图
n = np.random.randn(100000)
fig, axes = plt.subplots(1, 2, figsize=(12,4))

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

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

文字注释¶

采用text函数可以完成matplotlib图像的文字注释功能。和文字以及标题一样,它也支持LaTeX格式:

fig, ax = plt.subplots()

ax.plot(xx, xx**2, xx, xx**3)

ax.text(0.15, 0.2, r"$y=x^2$", fontsize=20, color="blue")
ax.text(0.65, 0.1, r"$y=x^3$", fontsize=20, color="green");

多个子图像的绘制和插入¶

采用fig.add_axes可以手动将坐标轴加入matplotlib图像中,或者用子图绘制的布局管理器,如subplots,subplot2grid或者gridspec:

subplots¶

fig, ax = plt.subplots(2, 3)
fig.tight_layout()

subplot2grid¶

fig = plt.figure()
ax1 = plt.subplot2grid((3,3), (0,0), colspan=3)
ax2 = plt.subplot2grid((3,3), (1,0), colspan=2)
ax3 = plt.subplot2grid((3,3), (1,2), rowspan=2)
ax4 = plt.subplot2grid((3,3), (2,0))
ax5 = plt.subplot2grid((3,3), (2,1))
fig.tight_layout()

gridspec¶

import matplotlib.gridspec as gridspec
fig = plt.figure()

gs = gridspec.GridSpec(2, 3, height_ratios=[2,1], width_ratios=[1,2,1])
for g in gs:
    ax = fig.add_subplot(g)
    
fig.tight_layout()

add_axes¶

用add_axes手动添加坐标轴对于添加元素于图像中非常有用:

fig, ax = plt.subplots()

ax.plot(xx, xx**2, xx, xx**3)
fig.tight_layout()

# 插入
inset_ax = fig.add_axes([0.2, 0.55, 0.35, 0.35]) # X, Y, 宽度,高度

inset_ax.plot(xx, xx**2, xx, xx**3)
inset_ax.set_title('zoom near origin')

# 设置坐标轴范围
inset_ax.set_xlim(-.2, .2)
inset_ax.set_ylim(-.005, .01)

# 设置坐标轴刻度位置
inset_ax.set_yticks([0, 0.005, 0.01])
inset_ax.set_xticks([-0.1,0,.1]);

色图和等高线图¶

色图和等高线图对于两个变量的绘图函数非常有用。在大多数函数中,我们采用色图编码一个维度的数据。下面列出了一些之前定义好的色图,他们对于确定定制版的色图是一种直接的方法,详见:http://www.scipy.org/Cookbook/Matplotlib/Show_colormaps.

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

def flux_qubit_potential(phi_m, phi_p):
    return 2 + alpha - 2 * np.cos(phi_p) * np.cos(phi_m) - alpha * np.cos(phi_ext - 2*phi_p)
phi_m = np.linspace(0, 2*np.pi, 100)
phi_p = np.linspace(0, 2*np.pi, 100)
X,Y = np.meshgrid(phi_p, phi_m)
Z = flux_qubit_potential(X, Y).T

pcolor函数¶

fig, ax = plt.subplots()

p = ax.pcolor(X/(2*np.pi), Y/(2*np.pi), Z, cmap=matplotlib.cm.RdBu, vmin=abs(Z).min(), vmax=abs(Z).max())
cb = fig.colorbar(p, ax=ax)

imshow函数¶

fig, ax = plt.subplots()

im = ax.imshow(Z, cmap=matplotlib.cm.RdBu, vmin=abs(Z).min(), vmax=abs(Z).max(), extent=[0, 1, 0, 1])
im.set_interpolation('bilinear')

cb = fig.colorbar(im, ax=ax)

contour函数¶

fig, ax = plt.subplots()

cnt = ax.contour(Z, cmap=matplotlib.cm.RdBu, vmin=abs(Z).min(), vmax=abs(Z).max(), extent=[0, 1, 0, 1])
/opt/conda/envs/python2/lib/python2.7/site-packages/matplotlib/collections.py:650: FutureWarning: elementwise comparison failed; returning scalar instead, but in the future will perform elementwise comparison
  if self._edgecolors_original != str('face'):

3D 图像¶

在使用matplotlib中的3D图像之前,我们首先需要创建Axes3D类。3D坐标轴和2D坐标轴创建的方法一样;或者更方便的方法是,在add_axes或者add_subplot中采用projection='3d'关键字参数。

from mpl_toolkits.mplot3d.axes3d import Axes3D

曲面图¶

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

# `ax` 是一个3D坐标轴,由于添加了projection='3d'关键字参数于add_subplot
ax = fig.add_subplot(1, 2, 1, projection='3d')

p = ax.plot_surface(X, Y, Z, rstride=4, cstride=4, linewidth=0)

# 带有颜色梯度和颜色条的曲面图
ax = fig.add_subplot(1, 2, 2, projection='3d')
p = ax.plot_surface(X, Y, Z, rstride=1, cstride=1, cmap=matplotlib.cm.coolwarm, linewidth=0, antialiased=False)
cb = fig.colorbar(p, shrink=0.5)

三维线框图¶

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

ax = fig.add_subplot(1, 1, 1, projection='3d')

p = ax.plot_wireframe(X, Y, Z, rstride=4, cstride=4)

带投影的等高线图¶

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

ax = fig.add_subplot(1,1,1, projection='3d')

ax.plot_surface(X, Y, Z, rstride=4, cstride=4, alpha=0.25)
cset = ax.contour(X, Y, Z, zdir='z', offset=-np.pi, cmap=matplotlib.cm.coolwarm)
cset = ax.contour(X, Y, Z, zdir='x', offset=-np.pi, cmap=matplotlib.cm.coolwarm)
cset = ax.contour(X, Y, Z, zdir='y', offset=3*np.pi, cmap=matplotlib.cm.coolwarm)

ax.set_xlim3d(-np.pi, 2*np.pi);
ax.set_ylim3d(0, 3*np.pi);
ax.set_zlim3d(-np.pi, 2*np.pi);

改变视角¶

采用view_init可以改变3D图像的视角,该命令有两个参数,elevation和azimuth角度(度数):

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

ax = fig.add_subplot(1,2,1, projection='3d')
ax.plot_surface(X, Y, Z, rstride=4, cstride=4, alpha=0.25)
ax.view_init(30, 45)

ax = fig.add_subplot(1,2,2, projection='3d')
ax.plot_surface(X, Y, Z, rstride=4, cstride=4, alpha=0.25)
ax.view_init(70, 30)

fig.tight_layout()

动图¶

Matplotlib也包含了一个简单的API用来产生一系列图像的动图。采用FuncAnimation函数,我们可以产生由一系列图像组成的视频文件。该函数采用了如下命令:fig图像窗口,func更新图像所用的函数,init_func组织图像的函数,frame产生的帧数,和blit指导动图函数只在帧数变动的时候更新(对于光滑动图而言):

def init():
    # setup figure

def update(frame_counter):
    # update figure for new frame

anim = animation.FuncAnimation(fig, update, init_func=init, frames=200, blit=True)

anim.save('animation.mp4', fps=30) # fps = frames per second


为了使用matplotlib中的动图函数,我们首先调用matplotlib.animation:

from matplotlib import animation
# 解决复摆的ODE(常微分方程)问题

from scipy.integrate import odeint
from numpy import cos, sin

g = 9.82; L = 0.5; m = 0.1

def dx(x, t):
    x1, x2, x3, x4 = x[0], x[1], x[2], x[3]
    
    dx1 = 6.0/(m*L**2) * (2 * x3 - 3 * cos(x1-x2) * x4)/(16 - 9 * cos(x1-x2)**2)
    dx2 = 6.0/(m*L**2) * (8 * x4 - 3 * cos(x1-x2) * x3)/(16 - 9 * cos(x1-x2)**2)
    dx3 = -0.5 * m * L**2 * ( dx1 * dx2 * sin(x1-x2) + 3 * (g/L) * sin(x1))
    dx4 = -0.5 * m * L**2 * (-dx1 * dx2 * sin(x1-x2) + (g/L) * sin(x2))
    return [dx1, dx2, dx3, dx4]

x0 = [np.pi/2, np.pi/2, 0, 0]  # 初始状态
t = np.linspace(0, 10, 250) # 时间坐标
x = odeint(dx, x0, t)    # 求常微分方程的解

生成的动画显示了钟摆的位置作为时间的函数:

fig, ax = plt.subplots(figsize=(5,5))

ax.set_ylim([-1.5, 0.5])
ax.set_xlim([1, -1])

pendulum1, = ax.plot([], [], color="red", lw=2)
pendulum2, = ax.plot([], [], color="blue", lw=2)

def init():
    pendulum1.set_data([], [])
    pendulum2.set_data([], [])

def update(n): 
    # n = 帧计数器
    # 计算复摆的位置
    x1 = + L * sin(x[n, 0])
    y1 = - L * cos(x[n, 0])
    x2 = x1 + L * sin(x[n, 1])
    y2 = y1 - L * cos(x[n, 1])
    
    # 更新数据
    pendulum1.set_data([0 ,x1], [0 ,y1])
    pendulum2.set_data([x1,x2], [y1,y2])

anim = animation.FuncAnimation(fig, update, init_func=init, frames=len(t), blit=True)

# anim.save可以采用不同的方法运行,在不同平台、
# 不同版本的matplotlib和视频编码器上可能有的方法不能使用
#anim.save('animation.mp4', fps=20, extra_args=['-vcodec', 'libx264'], writer=animation.FFMpegWriter())
#anim.save('animation.mp4', fps=20, extra_args=['-vcodec', 'libx264'])
#anim.save('animation.mp4', fps=20, writer="ffmpeg", codec="libx264")
anim.save('animation.mp4', fps=20, writer="avconv", codec="libx264")

plt.close(fig)
/opt/conda/envs/python2/lib/python2.7/site-packages/matplotlib/animation.py:742: UserWarning: MovieWriter avconv unavailable
  warnings.warn("MovieWriter %s unavailable" % writer)
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-70-41850fe2257c> in <module>()
     30 #anim.save('animation.mp4', fps=20, extra_args=['-vcodec', 'libx264'])
     31 #anim.save('animation.mp4', fps=20, writer="ffmpeg", codec="libx264")
---> 32 anim.save('animation.mp4', fps=20, writer="avconv", codec="libx264")
     33 
     34 plt.close(fig)

/opt/conda/envs/python2/lib/python2.7/site-packages/matplotlib/animation.pyc in save(self, filename, writer, fps, dpi, codec, bitrate, extra_args, metadata, extra_anim, savefig_kwargs)
    747                                                         metadata=metadata)
    748                 except IndexError:
--> 749                     raise ValueError("Cannot save animation: no writers are "
    750                                      "available. Please install mencoder or "
    751                                      "ffmpeg to save animations.")

ValueError: Cannot save animation: no writers are available. Please install mencoder or ffmpeg to save animations.

Note: 为了产生视频文件,我们需要安装ffmpeg或者avconv. 在Ubuntu上安装的指令:

      $ sudo apt-get install ffmpeg

或者(更新的版本)

      $ sudo apt-get install libav-tools

在MacOSX中, 尝试:

      $ sudo port install ffmpeg

有兴趣的用户可以自行安装,这里不再演示视频文件。

from IPython.display import HTML
video = open("animation.mp4", "rb").read()
video_encoded = video.encode("base64")
video_tag = '<video controls alt="test" src="data:video/x-m4v;base64,{0}">'.format(video_encoded)
HTML(video_tag)
---------------------------------------------------------------------------
IOError                                   Traceback (most recent call last)
<ipython-input-71-dee50753e05a> in <module>()
      1 from IPython.display import HTML
----> 2 video = open("animation.mp4", "rb").read()
      3 video_encoded = video.encode("base64")
      4 video_tag = '<video controls alt="test" src="data:video/x-m4v;base64,{0}">'.format(video_encoded)
      5 HTML(video_tag)

IOError: [Errno 2] No such file or directory: 'animation.mp4'

后端¶

Matplotlib有许多“后端”对产生的图像负责,不同的后端能够更产生不同样式的图和视频。非交互式的后端(如 'agg','svg','pdf'等)是用来产生图像文件(如savefig函数),与此不同,交互式的后端(如Qt4Agg,GTK,MaxOSX)能够运行GUI窗口,供用户进行交互式的使用图像。

可供选择的后端有:

print(matplotlib.rcsetup.all_backends)
[u'GTK', u'GTKAgg', u'GTKCairo', u'MacOSX', u'Qt4Agg', u'Qt5Agg', u'TkAgg', u'WX', u'WXAgg', u'CocoaAgg', u'GTK3Cairo', u'GTK3Agg', u'WebAgg', u'nbAgg', u'agg', u'cairo', u'emf', u'gdk', u'pdf', u'pgf', u'ps', u'svg', u'template']

默认后端是agg,它基于栅格图形库,非常适合生成像PNG这样的光栅格式。

通常来说,我们并不需要改变默认后端,但是有时转换到例如PDF或者GTKcariro(如果是Linux系统)时会非常有用,能够更产生高质量矢量图形而不是栅格图。

使用svg后端产生SVG¶

#
# 重启Notebook: matplotlib后端只能在pylab中选择
# (e.g. Kernel > Restart)
# 
import matplotlib
matplotlib.use('svg')
import matplotlib.pylab as plt
import numpy
from IPython.display import Image, SVG
/opt/conda/envs/python2/lib/python2.7/site-packages/matplotlib/__init__.py:1318: UserWarning:  This call to matplotlib.use() has no effect
because the backend has already been chosen;
matplotlib.use() must be called *before* pylab, matplotlib.pyplot,
or matplotlib.backends is imported for the first time.

  warnings.warn(_use_error_msg)
#
# 现在我们用svg后端产生SVG矢量图
#
fig, ax = plt.subplots()
t = numpy.linspace(0, 10, 100)
ax.plot(t, numpy.cos(t)*numpy.sin(t))
plt.savefig("test.svg")
#
# 显示产生的SVG文件. 
#
SVG(filename="test.svg")

IPython notebook 内联后端¶

当我们使用IPython notebook时,可以很方便的用matplotlib后端输出嵌入在notebook的图形文件。要激活这个后端,需要在开始的某处添加:

%matplotlib inline

采用如下格式也能够激活内联后端:

%pylab inline

不同之处在于%pylab inline调用了一系列函数包到全局地址空间(scipy,numpy),然而%matplotlib inline只在内联绘图时才调用。在IPython 1.0+的新的notebook中,建议使用%matplotlib inline,因为它更整洁,对于函数包的调用控制更多。通常,scipy和numpy分别通过如下形式调用:

import numpy as np
import scipy as sp
import matplotlib.pyplot as plt

内联后端有一系列的设置选择,可以通过IPython的命令%config来更新InlandBackend中的设置。例如,我们可以转换SVG图像或者更高分别率的图像通过:

%config InlineBackend.figure_format='svg'

或者

%config InlineBackend.figure_format='retina'

如需了解更多内容,请输入:

%config InlineBackend
%matplotlib inline
%config InlineBackend.figure_format='svg'

import matplotlib.pylab as plt
import numpy
#
# 现在我们替换notebook中的内联后端,使用SVG矢量图。
#
fig, ax = plt.subplots()
t = numpy.linspace(0, 10, 100)
ax.plot(t, numpy.cos(t)*numpy.sin(t))
plt.savefig("test.svg")

交互式后端(这使得Python脚本文件更有意义)¶

#
# 重启Notebook: matplotlib后端只能在pylab中选择
# (e.g. Kernel > Restart)
# 
import matplotlib
matplotlib.use('Qt4Agg') # or for example MacOSX
import matplotlib.pylab as plt
import numpy as np
/opt/conda/envs/python2/lib/python2.7/site-packages/matplotlib/__init__.py:1318: UserWarning:  This call to matplotlib.use() has no effect
because the backend has already been chosen;
matplotlib.use() must be called *before* pylab, matplotlib.pyplot,
or matplotlib.backends is imported for the first time.

  warnings.warn(_use_error_msg)
# 现在采用Qt4Agg打开一个交互式的绘图窗口
fig, ax = plt.subplots()
t = np.linspace(0, 10, 100)
ax.plot(t, np.cos(t) * np.sin(t))
plt.show()

注意,当我们采用交互式后端是,需要调用plt.show()命令将图像显示在屏幕上。

推荐阅读¶

http://www.matplotlib.org - 官方网页 https://github.com/matplotlib/matplotlib - matplotlib源代码 http://matplotlib.org/gallery.html - 展示各种各样matplotlib函数包能够绘制的图像,强烈推荐! http://www.loria.fr/~rougier/teaching/matplotlib - matplotlib课程 http://scipy-lectures.github.io/matplotlib/matplotlib.html - 其他参考文献

版本¶

%reload_ext version_information
%version_information numpy, scipy, matplotlib

全部回复

0/140

量化课程

    移动端课程