Matplotlib快速入门

577 阅读14分钟

matplotlib

是一个专门用于可视化的Python库。官网地址为:matplotlib.org/

image.png

下面翻译官网的Quick-Start

一个小例子

先引入依赖库

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

Matplotlib会将你的数据绘制在Figure上(例如,窗口、Jupyter小部件等),每个Figure可以包含一个或多个,这是一个可以根据x-y坐标(或极坐标图中的theta-r,3D图中的x-y-z等)指定点的区域。创建一个带有Figure的最简单方法是使用pyplot.subplots方法。然后我们可以使用Axes.plot上绘制一些数据:

fig, ax = plt.subplots()  # 创建只有一个轴的图
ax.plot([1, 2, 3, 4], [1, 4, 2, 3])  # 绘制数据

image.png

请注意,为了显示这个Figure,你可能需要调用plt.show(),这取决于你使用的后端。

Figure的组成

image.png

Figure

整个Figure。Figure会跟踪所有子图,一组“特殊”的艺术家(标题、图例、色条等),甚至是嵌套的子图。

创建一个新的Figure最简单的方法是使用pyplot:

fig = plt.figure()  # 空的figure
fig, ax = plt.subplots()  # 只有一个子图的figure
fig, axs = plt.subplots(2, 2)  # 有2x2网格的figure
# 左边有一个子图,右边有两个子图的figure
fig, axs = plt.subplot_mosaic([['left', 'right_top'],
                               ['left', 'right_bottom']])

通常情况下,一起创建Axes和Figure会更加方便,但你也可以随后手动添加Axes。

子图

子图是附加到Figure的一个艺术家,它包含一个用于绘制数据的区域,并且通常包括两个(或三个在3D情况下)Axis对象(注意Axes和Axis之间的区别),这些对象提供刻度和刻度标签以提供数据在子图中的比例。每个Axes还有一个标题(通过set_title()设置)、一个x标签(通过set_xlabel()设置)和一个y标签(通过set_ylabel()设置)。

Axes类及其成员函数是与面向对象编程接口进行工作的主要入口点,并且其中定义了大多数绘图方法(例如,上面使用了plot方法)。

这些对象设置刻度和限制,并生成刻度线(Axis上的标记)和刻度标签(标记刻度的字符串)。刻度位置由Locator对象确定,刻度标签字符串由Formatter格式化。正确使用Locator和Formatter的组合可以非常精细地控制刻度位置和标签。

艺术家

基本上,Figure上的所有可见内容都是一个艺术家(包括Figure、Axes和Axis对象)。这包括文本对象、Line2D对象、集合对象、Patch对象等。当Figure被渲染时,所有的艺术家都会被绘制到画布上。大多数艺术家都与一个Axes相关联;这样的艺术家不能被多个Axes共享,也不能从一个Axes移动到另一个Axes。

绘图函数接受的输入类型

绘图函数期望以numpy.array或numpy.ma.masked_array作为输入,或者是可以传递给numpy.asarray的对象。类似于数组(‘array-like’)的数据对象,如pandas数据对象和numpy矩阵,可能无法按预期工作。常见的约定是在绘制之前将其转换为numpy.array对象。例如,要将一个numpy.matrix对象转换为numpy.array对象,可以使用以下代码:

import numpy as np

matrix = np.matrix([[1, 2], [3, 4]])
array = np.array(matrix)

然后你可以使用这个array来进行绘图操作。

大多数方法还可以解析可寻址的对象,例如字典、numpy.recarray或pandas.DataFrame。Matplotlib允许您提供data关键字参数,并通过传递对应于x和y变量的字符串生成绘图。这样可以方便地使用列名来指定绘图所需的数据。

np.random.seed(19680801)  # seed the random number generator.
data = {'a': np.arange(50),
        'c': np.random.randint(0, 50, 50),
        'd': np.random.randn(50)}
data['b'] = data['a'] + 10 * np.random.randn(50)
data['d'] = np.abs(data['d']) * 100

fig, ax = plt.subplots(figsize=(5, 2.7), layout='constrained')
ax.scatter('a', 'b', c='c', s='d', data=data)
ax.set_xlabel('entry a')
ax.set_ylabel('entry b')

image.png

编程风格

显式接口和隐式接口
如前所述,使用Matplotlib有两种基本方式:

显式地创建Figure和Axes,并在它们上调用方法(“面向对象(OO)风格”)。

依赖pyplot来隐式创建和管理Figure和Axes,并使用pyplot的函数进行绘图。

有关隐式接口和显式接口之间权衡的说明,请参阅Matplotlib应用程序接口(API)。

因此,可以使用面向对象的风格

x = np.linspace(0, 2, 100)  # Sample data.

# Note that even in the OO-style, we use `.pyplot.figure` to create the Figure.
fig, ax = plt.subplots(figsize=(5, 2.7), layout='constrained')
ax.plot(x, x, label='linear')  # Plot some data on the axes.
ax.plot(x, x**2, label='quadratic')  # Plot more data on the axes...
ax.plot(x, x**3, label='cubic')  # ... and some more.
ax.set_xlabel('x label')  # Add an x-label to the axes.
ax.set_ylabel('y label')  # Add a y-label to the axes.
ax.set_title("Simple Plot")  # Add a title to the axes.
ax.legend()  # Add a legend.

image.png

或者pyplot风格

x = np.linspace(0, 2, 100)  # Sample data.

plt.figure(figsize=(5, 2.7), layout='constrained')
plt.plot(x, x, label='linear')  # Plot some data on the (implicit) axes.
plt.plot(x, x**2, label='quadratic')  # etc.
plt.plot(x, x**3, label='cubic')
plt.xlabel('x label')
plt.ylabel('y label')
plt.title("Simple Plot")
plt.legend()

(此外,还有一种第三种方法,用于将Matplotlib嵌入GUI应用程序的情况,它完全放弃了pyplot,即使是用于创建图形。请参阅图库中相应部分获取更多信息:在图形用户界面中嵌入Matplotlib。)

Matplotlib的文档和示例中同时使用了OO风格和pyplot风格。总体而言,我们建议在复杂的绘图、以及作为较大项目的一部分进行重复使用的函数和脚本中使用OO风格。然而,对于快速交互式工作,pyplot风格非常方便。

注意

您可能会发现一些使用pylab接口的旧示例,通过from pylab import *导入。这种方法已经被强烈弃用。

创建辅助函数

在使用Matplotlib进行绘图时,如果您需要重复使用不同数据集创建相同的图形,或者想要轻松封装Matplotlib方法,可以使用下面推荐的函数签名。

def my_plotter(ax, data1, data2, param_dict):
    """
    A helper function to make a graph.
    """
    out = ax.plot(data1, data2, **param_dict)
    return out

然后,您可以使用这个辅助函数两次来填充两个子图:

data1, data2, data3, data4 = np.random.randn(4, 100)  # make 4 random data sets
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(5, 2.7))
my_plotter(ax1, data1, data2, {'marker': 'x'})
my_plotter(ax2, data3, data4, {'marker': 'o'})

image.png

请注意,如果您想将这些函数作为Python包进行安装,或者进行其他自定义操作,您可以使用网络上的许多模板之一;Matplotlib在mpl-cookiecutter中提供了一个模板。

艺术家的样式

大多数绘图方法都对艺术家(Artists)具有样式选项,可以在调用绘图方法时或通过艺术家的“setter”进行访问和设置。在下面的示例中,我们手动设置了plot创建的艺术家的颜色、线宽和线型,并使用set_linestyle方法在之后设置了第二条线的线型。

fig, ax = plt.subplots(figsize=(5, 2.7))
x = np.arange(len(data1))
ax.plot(x, np.cumsum(data1), color='blue', linewidth=3, linestyle='--')
l, = ax.plot(x, np.cumsum(data2), color='orange', linewidth=2)
l.set_linestyle(':')

image.png

颜色

Matplotlib提供了一系列非常灵活的颜色选项,可以用于大多数艺术家(Artists)。您可以在颜色教程中查看可接受的颜色规范的列表。有些艺术家将接受多个颜色参数。例如,在散点图中,标记的边缘颜色可以与内部颜色不同:

fig, ax = plt.subplots(figsize=(5, 2.7))
ax.scatter(data1, data2, s=50, facecolor='C0', edgecolor='k')

image.png

Linewidth, linestyle, 以及 markersize

线宽通常以排版点(1 pt = 1/72英寸)表示,并适用于具有描边线条的艺术家。同样,描边线条可以具有线型。请参阅关于线型的示例来了解更多信息。

标记大小取决于使用的方法。在plot方法中,markersize以点为单位指定,通常是标记的直径或宽度。在scatter方法中,markersize的大小大致与标记的可视面积成正比。有一系列可用的标记样式作为字符串代码(参见标记样式),用户还可以定义自己的MarkerStyle(参见Marker参考文档):

fig, ax = plt.subplots(figsize=(5, 2.7))
ax.plot(data1, 'o', label='data1')
ax.plot(data2, 'd', label='data2')
ax.plot(data3, 'v', label='data3')
ax.plot(data4, 's', label='data4')
ax.legend()

image.png

标记图表

轴标签和文本

set_xlabel、set_ylabel和set_title用于在指定位置添加文本(有关更多讨论,请参阅Matplotlib绘图中的文本)。也可以使用text直接将文本添加到绘图中:

mu, sigma = 115, 15
x = mu + sigma * np.random.randn(10000)
fig, ax = plt.subplots(figsize=(5, 2.7), layout='constrained')
# the histogram of the data
n, bins, patches = ax.hist(x, 50, density=True, facecolor='C0', alpha=0.75)

ax.set_xlabel('Length [cm]')
ax.set_ylabel('Probability')
ax.set_title('Aardvark lengths\n (not really)')
ax.text(75, .025, r'$\mu=115,\ \sigma=15$')
ax.axis([55, 175, 0, 0.03])
ax.grid(True)

image.png

所有的文本函数都会返回一个matplotlib.text.Text实例。就像上面的线条一样,您可以通过将关键字参数传递给文本函数来自定义属性。

t = ax.set_xlabel('my data', fontsize=14, color='red')

在文本中使用数学表达式

Matplotlib接受TeX方程式表达式作为任何文本表达式的一部分。例如,要在标题中写出数学表达式,可以使用美元符号将TeX表达式括起来:

ax.set_title(r'$\sigma_i=15$')

确实,r前缀用于表示字符串是原始字符串(raw string),不将反斜杠视为Python转义字符。Matplotlib内置了一个TeX表达式解析器和布局引擎,并且附带了自己的数学字体。有关详细信息,请参阅《编写数学表达式》。您还可以直接使用LaTeX格式化文本,并将输出直接合并到显示图形或保存为PostScript文件中,详见《使用LaTeX进行文本渲染》。

标注

我们还可以通过在xy和xytext之间连接一个箭头,将一个文本标注添加到绘图中的某个点上:

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

t = np.arange(0.0, 5.0, 0.01)
s = np.cos(2 * np.pi * t)
line, = ax.plot(t, s, lw=2)

ax.annotate('local max', xy=(2, 1), xytext=(3, 1.5),
            arrowprops=dict(facecolor='black', shrink=0.05))

ax.set_ylim(-2, 2)

image.png

在这个基本示例中,xy和xytext都是使用数据坐标系。Matplotlib还提供了其他多种坐标系供选择,详细信息请参阅《基本注释》和《高级注释》。在《注释绘图》中还可以找到更多的示例。

图例

通常我们希望使用Axes.legend方法来为线条或标记添加图例:

fig, ax = plt.subplots(figsize=(5, 2.7))
ax.plot(np.arange(len(data1)), data1, label='data1')
ax.plot(np.arange(len(data2)), data2, label='data2')
ax.plot(np.arange(len(data3)), data3, 'd', label='data3')
ax.legend()

image.png

Matplotlib中的图例在布局、位置和所代表的元素方面非常灵活。这些内容在《图例指南》中有详细讨论。

轴的尺度和刻度

每个Axes对象都有两个(或三个)Axis对象,分别代表x轴和y轴。这些对象控制轴的刻度尺度、刻度定位器和刻度格式化器。可以附加其他Axes来显示更多的Axis对象。

刻度尺度

除了线性刻度尺之外,Matplotlib还提供了非线性刻度尺,例如对数刻度尺。由于对数刻度经常被使用,因此还提供了loglog、semilogx和semilogy等直接方法。还有一些其他的刻度尺(请参阅Scales以获取其他示例)。在这里,我们手动设置刻度尺度:

fig, axs = plt.subplots(1, 2, figsize=(5, 2.7), layout='constrained')
xdata = np.arange(len(data1))  # make an ordinal for this
data = 10**data1
axs[0].plot(xdata, data)

axs[1].set_yscale('log')
axs[1].plot(xdata, data)

image.png

刻度尺度设置了从数据值到沿轴的间距的映射。这在两个方向上都发生,并且被组合成一个转换(transform),这是Matplotlib将数据坐标映射到Axes、Figure或屏幕坐标的方式。详见《变换教程》。

刻度定位和格式化

每个坐标轴都有一个刻度定位器(tick locator)和刻度格式化器(tick formatter),用于选择在坐标轴上放置刻度标记的位置。其中一个简单的接口是set_xticks:

fig, axs = plt.subplots(2, 1, layout='constrained')
axs[0].plot(xdata, data1)
axs[0].set_title('Automatic ticks')

axs[1].plot(xdata, data1)
axs[1].set_xticks(np.arange(0, 100, 30), ['zero', '30', 'sixty', '90'])
axs[1].set_yticks([-1.5, 0, 1.5])  # note that we don't need to specify labels
axs[1].set_title('Manual ticks')

image.png

不同的刻度尺度可以有不同的刻度定位器和刻度格式化器,例如上面的对数刻度使用了LogLocator和LogFormatter。请参阅《刻度定位器》和《刻度格式化器》以了解其他格式化器和定位器,并获取编写自己的定位器和格式化器的相关信息。

绘制时间和字符串

不同的刻度尺度可以有不同的刻度定位器和刻度格式化器,例如上面的对数刻度使用了LogLocator和LogFormatter。请参阅《刻度定位器》和《刻度格式化器》以了解其他格式化器和定位器,并获取编写自己的定位器和格式化器的相关信息。

对于时间

fig, ax = plt.subplots(figsize=(5, 2.7), layout='constrained')
dates = np.arange(np.datetime64('2021-11-15'), np.datetime64('2021-12-25'),
                  np.timedelta64(1, 'h'))
data = np.cumsum(np.random.randn(len(dates)))
ax.plot(dates, data)
cdf = mpl.dates.ConciseDateFormatter(ax.xaxis.get_major_locator())
ax.xaxis.set_major_formatter(cdf)

image.png

对于字符串

fig, ax = plt.subplots(figsize=(5, 2.7), layout='constrained')
categories = ['turnips', 'rutabaga', 'cucumber', 'pumpkins']

ax.bar(categories, np.random.rand(len(categories)))

image.png

在对分类数据进行绘图时需要注意的一点是,某些解析文本文件的方法可能会返回一个字符串列表,即使这些字符串都表示数字或日期。如果传递了1000个字符串,Matplotlib会认为您指的是1000个类别,并会在图形上添加1000个刻度!

添加轴对象

在同一张图中绘制具有不同数量级的数据可能需要添加一个额外的y轴。可以使用twinx方法创建这样一个额外的轴,它会添加一个无形的x轴和一个位于右侧的y轴(类似地,对于纵向绘图,可以使用twiny方法)。请参阅《具有不同刻度的绘图》了解另一个示例。

类似地,您还可以添加一个具有与主轴不同刻度的secondary_xaxissecondary_yaxis来表示不同刻度或单位的数据。请参阅《辅助轴》以获取更多示例。

fig, (ax1, ax3) = plt.subplots(1, 2, figsize=(7, 2.7), layout='constrained')
l1, = ax1.plot(t, s)
ax2 = ax1.twinx()
l2, = ax2.plot(t, range(len(t)), 'C1')
ax2.legend([l1, l2], ['Sine (left)', 'Straight (right)'])

ax3.plot(t, s)
ax3.set_xlabel('Angle [rad]')
ax4 = ax3.secondary_xaxis('top', functions=(np.rad2deg, np.deg2rad))
ax4.set_xlabel('Angle [°]')

image.png

颜色映射数据

通常我们希望在绘图中使用颜色映射来表示第三个维度。Matplotlib提供了几种可以实现这一目的的绘图类型:

X, Y = np.meshgrid(np.linspace(-3, 3, 128), np.linspace(-3, 3, 128))
Z = (1 - X/2 + X**5 + Y**3) * np.exp(-X**2 - Y**2)

fig, axs = plt.subplots(2, 2, layout='constrained')
pc = axs[0, 0].pcolormesh(X, Y, Z, vmin=-1, vmax=1, cmap='RdBu_r')
fig.colorbar(pc, ax=axs[0, 0])
axs[0, 0].set_title('pcolormesh()')

co = axs[0, 1].contourf(X, Y, Z, levels=np.linspace(-1.25, 1.25, 11))
fig.colorbar(co, ax=axs[0, 1])
axs[0, 1].set_title('contourf()')

pc = axs[1, 0].imshow(Z**2 * 100, cmap='plasma',
                          norm=mpl.colors.LogNorm(vmin=0.01, vmax=100))
fig.colorbar(pc, ax=axs[1, 0], extend='both')
axs[1, 0].set_title('imshow() with LogNorm()')

pc = axs[1, 1].scatter(data1, data2, c=data3, cmap='RdBu_r')
fig.colorbar(pc, ax=axs[1, 1], extend='both')
axs[1, 1].set_title('scatter()')

image.png

颜色映射

这些都是从ScalarMappable对象派生的艺术家(Artists)的示例。它们都可以在vmin和vmax之间设置一个线性映射,并使用指定的颜色映射cmap。Matplotlib有许多可供选择的颜色映射(参见Matplotlib中的选择颜色映射),您也可以自己创建(参见Matplotlib中的创建颜色映射)或下载第三方包。

归一化

有时候我们希望将数据与颜色映射进行非线性映射,就像上面的LogNorm示例一样。为此,我们可以使用norm参数来提供给ScalarMappable,而不是vmin和vmax。在Colormap Normalization中展示了更多的归一化方法。

颜色条

添加颜色条可以提供与底层数据的颜色关联的关键信息。颜色条是图形级别的艺术家(Artists),它们与ScalarMappable相关联(用于获取有关归一化和颜色映射的信息),并且通常会从父Axes中占用空间。颜色条的放置位置可能比较复杂:请参阅《放置颜色条》以获取详细信息。您还可以使用“extend”关键字来改变颜色条的外观,例如在两端添加箭头,并使用“shrink”和“aspect”控制其大小。最后,颜色条将具有与归一化相适应的默认定位器(locator)和格式化器(formatter)。您可以像对其他Axis对象一样更改这些定位器和格式化器。

多图和多子图

您可以使用多次调用fig = plt.figure()或者fig2, ax = plt.subplots()来打开多个图形(Figure)。通过保留对象引用,您可以将艺术家(Artists)添加到任何一个图形中。

可以使用多种方式添加多个坐标轴(Axes),但最基本的方法是使用plt.subplots(),就像上面的示例一样。如果想要实现更复杂的布局,例如让坐标轴跨越多列或多行,可以使用subplot_mosaic函数。

fig, axd = plt.subplot_mosaic([['upleft', 'right'],
                               ['lowleft', 'right']], layout='constrained')
axd['upleft'].set_title('upleft')
axd['lowleft'].set_title('lowleft')
axd['right'].set_title('right')

image.png

Matplotlib具有相当复杂的用于排列坐标轴的工具:请参阅《在图中排列多个坐标轴》和《复杂和语义化的图形组合(subplot_mosaic)》。