0. 摘要

本文重点不在于具体画图操作,更在意 matplotlib 的基本概念,或者说画图的组件结构。在系统地学习了【莫烦Python】Matplotlib Python 画图教程 后,进行一些思考和总结,整个 matplotlib 包就清楚了,希望以后再画图的时候不再挠头。

1. 基本概念

最基本的就是我们看到的,一张图中可以含有多个坐标系,可以想象为:在一张白纸上,用笔画坐标系。所以有两个最基本的概念:figure(白纸),axes(坐标系)。

1.1 figure


一个 figure 就是输出图的窗口(框),相当于一个容器,其中包含图象。如上图,一个框包含了 5 张图象。有点像 word 中的画布。如果不声明 plt.figure(),系统会有一个默认的 figure,所以你直接 plt.plot(x, y) 时,也可以画出图象。

1.2 axes

直译的话,axes 是坐标轴的意思,但是目前来看,它是一个坐标系,更确切地说,它是一张图象。上图中的 5 个图象就是 5 个 axes 。ps:就当是在画布上画了 5 个坐标系,坐标系中可以画线。我们画出来的图象实例,如散点或者曲线(上图中的蓝色直线),其实是由 axes 对象操作的,画在当前的 axes 对象对应的坐标系中。

刚开始对程序中“时而 plt.plot(x, y) 时而 ax.plot(x, y)”感到迷惑,图象到底是怎么画出来的?

由 figure 和 axes 的基本概念以及两者之间的关系来看,图象是由 axes 画出来的。那为什么 plt.plot(x, y) 也可以?

还是“默认”的问题,既然“不声明 plt.figure(),系统会有一个默认的 figure”,那么 figure 中自然也可以有默认的 axes。调用函数 axes = figure.get_axes() 就可以拿到当前 figure 中所有 axes 对象的列表。

from matplotlib import pyplot as plt
import numpy as npx = np.linspace(-1, 1, 50)
y = 2 * x + 1figure = plt.figure()
plt.plot(x, y)
plt.show()axess = figure.get_axes()
print(len(axess))  # 1
print(axess[0])  # AxesSubplot(0.125,0.11;0.775x0.77)

可以看到,即使没有明确地声明 axes 坐标系,figure 中依然存在一个 AxesSubplot。所以我猜测 plt.plot(x, y) 其实是 figure.get_axes()[0].plot(x, y),即:自动调用默认的 axes 进行画图的。

但进一步的试验打脸了,如果把 plt.plot(x, y) 一句注释掉,axess 就是空的,也就是说,不画图像,就没有 axes 哦!(也不会出现框框,啥反应没有)。这至少说明 plt.plot(x, y) 画图的时候发现没有 axes,暗地里执行了 plt.axes(),创建了一个 axes。

更常看到的是 ax = plt.gca() 获取当前 axes,# gca = ‘get current axes’1,如果没有,也会自动创建一个。所以 plt.plot(x, y) 也可能是 plt.gca().plot(x, y)。反正是偷偷建了一个 axes。

2. 组件结构

明白了基本的两个概念之后,我们看一看 matplotlib 包是如何组织这些组件的,画出来的形形色色的图象是怎样的结构。

2.1 plt.xxx() vs. ax.set_xxx() && 设置坐标

由以上对 plt.plot(x, y)ax.plot(x, y) 的分析可知,两者联系紧密。再由下一节的坐标轴设置可知,plt.xxx() 对坐标图象的设置很可能也是由类似 plt.gca().plot(x, y)plt.gca().set_xxx() 操作完成的。

具体看下面两段代码,结果是一样的:

from matplotlib import pyplot as plt
import numpy as npx = np.linspace(-3, 3, 50)
y1 = 2 * x + 1
y2 = x ** 2plt.figure()plt.plot(x, y2)
plt.plot(x, y1, color='red', linewidth=1.0, linestyle='--')
# 标题
plt.title('title')
# 坐标轴范围
plt.xlim([-1, 2])
plt.ylim([-2, 3])
# 坐标轴标签
plt.xlabel('I am X')
plt.ylabel('I am Y')
# ticks 是坐标轴的数字记号
new_ticks = np.linspace(-1, 2, 5)
print(new_ticks)
plt.xticks(new_ticks)  # 设置 xticks,没有提供符号,就直接显示当前位置数值
plt.yticks(  # set yticks[-1, 0, 1, 2, 3],  # 位置,下面提供符号,显示符号[r'$v\ b$', r'$b\ \alpha$', r'$m$', r'$g$', r'$vg$']  # 显示的符号,tex 公式
)plt.show()
from matplotlib import pyplot as plt
import numpy as npx = np.linspace(-3, 3, 50)
y1 = 2 * x + 1
y2 = x ** 2plt.figure()
ax = plt.gca()ax.plot(x, y2)
ax.plot(x, y1, color='red', linewidth=1.0, linestyle='--')
# 标题
ax.set_title('title')
# 坐标轴范围
ax.set_xlim([-1, 2])
ax.set_ylim([-2, 3])
# 坐标轴标签
ax.set_xlabel('I am X')
ax.set_ylabel('I am Y')
# ticks 是坐标轴的数字记号
new_ticks = np.linspace(-1, 2, 5)
print(new_ticks)
ax.set_xticks(new_ticks)  # 设置 xticks,没有提供符号,就直接显示当前位置数值
ax.set_yticks([-1, 0, 1, 2, 3])
ax.set_yticklabels([r'$v\ b$', r'$b\ \alpha$', r'$m$', r'$g$', r'$vg$'])
# 警告说新版本不支持放在一起了,就用set_yticklabels
plt.show()

2.2 继续设置坐标轴(spines)

ax.spines 就是坐标轴的线(ax 对象的属性),默认是 x 轴和 y 轴各两条,默认 left 和 bottom 带刻度(主轴),right 和 top 不带刻度,当然,这可以改变。可以设置这些线的位置、颜色和可见性等各种属性。

ax = plt.gca()
# spine: 脊梁,就是上下左右四周的四个线
ax.spines['right'].set_color('none')  # 颜色 none,消失
ax.spines['top'].set_color('none')  # 这个的作用主要还是设置颜色吧
ax.spines['right'].set_visible(False)  # 这样意思更明确吧
ax.spines['top'].set_visible(False)
# 设置 x, y 轴是 spine 的哪根线
ax.xaxis.set_ticks_position('bottom')
ax.yaxis.set_ticks_position('left')
# 再设置 spines 的位置,就设置了原点位置
ax.spines['bottom'].set_position(['data', 0])  # data, 定位方式为:数据值
ax.spines['left'].set_position(['data', 0])  # 还有 axes, 定位方式:坐标轴比例

这样一来,xticklabels 的概念也就清晰了:

for label in ax.get_xticklabels() + ax.get_yticklabels():  # 再看 ax.set_yticklabels() 是多么自然label.set_fontsize(12)  # 设置 ticks 的字体label.set_bbox(dict(facecolor='white', edgecolor='None', alpha=0.3))  # ticks 的显示块

2.3 annotate, text, legend,… 成了和 plot 和 scatter 一样的概念

我们常看到 plt.plot(x, y) 以及 plt.scatter(x, y),知道它们是画线和散点。经过上面对 axes 的理解,我们又知道了画线和散点其实就是在 axes 坐标系中根据坐标点 (x, y) 画图,由 axes 对象操作。但其他像 annotate, text, legend 的东西也是经常用到的,它们是什么?

# annotate #############  plt.annotate 也一样
ax.annotate(  # 这么说,annotate 是和 scatter、plot 一样的概念text=r'$2x+1=%s$' % y0,  # 注释字符串xy=(x0, y0),  # 注释点(xycoords='data')xycoords='data',  # 以数据点为标准找位置xytext=(+30, -30),  # 这个就是下一行的 offset 值,横坐标+30, 纵坐标-30textcoords='offset points',  # 注释的位置,相对点进行 offsetfontsize=16,arrowprops=dict(  # 箭头属性arrowstyle='->',  # 简头样式connectionstyle='arc3, rad=.2'  # 弧度之类的)
)# text #############  plt.text 也一样
ax.text(x=-1.0, y=1.5,s=r'$This\ is\ some\ text.\ \mu\ \sigma_i\ \alpha_t$',fontdict=dict(size=12, color='red')
)# handle:就是线的句柄,plot 的返回值,不知道为啥要带','
line1, = plt.plot(x, y2, label='up')  # 先设置 label
line2, = plt.plot(x, y1, color='red', linewidth=1.0, linestyle='--', label='down')
# plt.legend()  # 简单的默认图例
plt.gca().legend(  # 这样一看,legend 也和 plot 是一样的概念handles=[line1, ],  # handles 为空,则自动默认 plt.legend()labels=['aaa'],loc='best'
)

结构一下清晰了不少,原来 annotate, text, legend 这些奇奇怪怪的东西,就plot 一条线一样,都是往 figure 里的 axes 上敷东西,顺便带上一些属性呈现不同个性。就连直方图上标的数值都是用 ax.text() 敷上去的。

2.4 3D 图不过是换了 axes

import numpy as np
from matplotlib import pyplot as plt
from mpl_toolkits import mplot3dfig = plt.figure()
ax = mplot3d.Axes3D(fig)  # 创建 3d 的 axes,并放入 figurex = np.arange(-6, 6, 0.2)
y = np.arange(-6, 6, 0.2)
x, y = np.meshgrid(x, y)
r = np.sqrt(x ** 2 + y ** 2)
z = np.sin(r * np.pi / 0.8)ax.plot_surface(  # 这和 2d 的 ax.xxx() 一样的概念,只不过换成了 3d 坐标x, y, z,rstride=1, cstride=1,  # 颜色变化跨度cmap=plt.get_cmap('rainbow')
)
ax.contourf(  # 这和 2d 的 ax.xxx() 一样的概念,只不过换成了 3d 坐标x, y, z,zdir='z',  # 沿着 z 轴投影offset=-2,  # 投影位置cmap='rainbow'
)ax.set_zlim3d(-5, 5)  # 就连设置属性都是一样的概念plt.show()

看,还是 axes 调用 ax.xxx() 往坐标系敷东西。

2.5 多子图(多 axes)

开头就说过“在一张白纸上画多个坐标系”,那多子图就是一个 figure 里放多个 axes 对象咯!有多种方法画多子图,包括:plt.subplot2grid, gridspec.GridSpec, plt.subplot(s) 以及 figure.add_axes 等。这里重点不在于画多子图的方法,而在于理解多子图的结构不同的方法仅仅是 axes 的排列方式不同,本质是一样的。

2.5.1 AxesSubplot & Axes

老是说 axes 是一个坐标系,那么它在代码编程中到底是个啥?这里先给出答案:在 figure.add_axes 方法中,axes 是 Axes,其他方法中是 AxesSubplot,甚至是默认的 axes 也是 AxesSubplotAxesSubplot & Axes 有什么区别吗?figure.add_axes 这么特别,有什么特殊用途吗?

2.5.1.1 AxesSubplot

对于 AxesSubplot 以及其属性,就不多说了,这里也不打算深入探究,钻得太深容易陷进去。反正就是一个坐标系,可以往里画线画点。print(ax) 可以得到类似 AxesSubplot(0.125,0.53;0.352273x0.35) 的输出,数字依次是[left, bottom; width*height],不同多子图方法可能就是通过调整子图的这些参数来排版子图的。

2.5.1.2 Axes

对于 Axesprint(ax) 也可以得到类似 Axes(0.2,0.6;0.25x0.25) 的输出,参数的意义是一样的。

2.5.2 多子图示例

感觉 gridspec.GridSpec 最方便,可以通过切片索引的方式像表格一样方便地布局子图,下面是画出开头图片的代码:

plt.figure()
gs = gridspec.GridSpec(3, 3)ax1 = plt.subplot(gs[0, :])  # 切片索引方式确定行列
ax1.plot([1, 2],[1, 2]
)
ax1.set_title('ax1_title')
print(ax1)  # AxesSubplot(0.125,0.653529;0.775x0.226471)plt.subplot(gs[1, :2])
plt.subplot(gs[1:, 2])
plt.subplot(gs[2, 0])
plt.subplot(gs[2, 1])plt.show()

可以看到子图1是 AxesSubplot(0.125,0.653529;0.775x0.226471).

2.5.3 figure.add_axes 多图实现图中图

2.5.1 中问道“figure.add_axes 这么特别,有什么特殊用途吗?”,接下来就展示一下图中图的用途,说一下它的特别之处。

from matplotlib import pyplot as pltfigure = plt.figure()
x = [1, 2, 3, 4, 5, 6, 7]
y = [1, 3, 4, 2, 5, 8, 6]left, bottom, width, height = 0.1, 0.1, 0.8, 0.8
ax1 = figure.add_axes([left, bottom, width, height])
ax1.plot(x, y, 'r')
ax1.set_xlabel('x')
ax1.set_ylabel('y')
ax1.set_title('title')
# 这不是平级的吗
left, bottom, width, height = 0.2, 0.6, 0.25, 0.25
ax2 = figure.add_axes([left, bottom, width, height])
ax2.plot(x, y, 'b')
ax2.set_xlabel('x')
ax2.set_ylabel('y')
ax2.set_title('title inside1')
print(ax2)  # Axes(0.2,0.6;0.25x0.25)

一张图,上面又贴了一张小图。看看代码才知道,这并不是什么图中带图,而是两个平级的图,先画的图在下面,后画的小图在上面覆盖一下,就成了图中带图的样子。print(ax2) 会打印出 Axes(0.2,0.6;0.25x0.25)。仔细想想,这种 Axes 应该是比 AxesSubplot 更低级的概念,后者带了 Subplot,就是子图的意思,可见这种 AxesSubplot 是专门为子图设计的,它本身就是一张图,位置也由 gridspec.GridSpec 之类的布局工具方便地确定了。而 Axes 是坐标系的概念,声明的时候就要用参数说明具体位置和大小。

既然可以自由地明确地说明坐标系的位置和大小,自然也就可以手动布局多子图了:

# 虽然不太方便
figure = plt.figure()
x = [1, 2, 3, 4, 5, 6, 7]
y = [1, 3, 4, 2, 5, 8, 6]
# 1, 1
left, bottom, width, height = 0.075, 0.625, 0.4, 0.32
ax1 = figure.add_axes([left, bottom, width, height])
ax1.plot(x, y, 'r')
ax1.set_xlabel('x1')
ax1.set_ylabel('y1')
ax1.set_title('title 1')
# 1, 2
left, bottom, width, height = 0.575, 0.625, 0.4, 0.32
ax2 = figure.add_axes([left, bottom, width, height])
ax2.plot(x, y, 'g')
ax2.set_xlabel('x2')
ax2.set_ylabel('y2')
ax2.set_title('title 2')
# 2, 1
left, bottom, width, height = 0.075, 0.125, 0.4, 0.32
ax3 = figure.add_axes([left, bottom, width, height])
ax3.plot(x, y, 'b')
ax3.set_xlabel('x3')
ax3.set_ylabel('y3')
ax3.set_title('title 3')
# 2, 2
left, bottom, width, height = 0.575, 0.125, 0.4, 0.32
ax4 = figure.add_axes([left, bottom, width, height])
ax4.plot(x, y, 'k')
ax4.set_xlabel('x4')
ax4.set_ylabel('y4')
ax4.set_title('title 4')
plt.show()

Summary

关于课程中诸如主次坐标、动画等其他的概念就不多说了,了解画图基本概念的目的以达到,其他的花里胡哨的东西只是翻翻文档的事,画图再也不用一头雾水地瞎改参数、瞎查博客,搞得乱七八糟的了。


  1. fig = plt.gcf() 可获取当前 figure. 还有一点就是,一旦调用 plt.figure() 就会生成新的 figure,后续的默认操作也是在新 figure 上,除非用 figure 句柄指定 figure 对象。plt.show() 会打印出当前所有的 figure,并清空,后续 plt.xxx() 会创建新的 figure. ↩︎

Notes on Matplotlib相关推荐

  1. seaborn 教程_使用Seaborn进行数据可视化教程

    seaborn 教程 "Seaborn makes the exploratory data analysis phase of your data science project beau ...

  2. python matplotlib绘制等高线,plt.contour(),ax3.contour()和plt.contourf(),ax3.contour(), 同名函数

    引用文章 https://blog.csdn.net/lanchunhui/article/details/70495353 首先这是由不同对象调用的函数,ax3指3D Figure对象即<cl ...

  3. python matplotlib.figure.Figure.add_subplot()方法的使用

    官方文档 https://matplotlib.org/api/_as_gen/matplotlib.figure.Figure.html?highlight=add_subplot#matplotl ...

  4. Matplotlib课程–学习Python数据可视化

    Learn the basics of Matplotlib in this crash course tutorial. Matplotlib is an amazing data visualiz ...

  5. fastai 2019 lesson9 notes 笔记

    lesson9 How to train your model 本文markdown源文件:lesson9.md 2019年视频地址:https://course19.fast.ai/videos/? ...

  6. 数据可视化第八章使用matplotlib绘制高级图表

    8.1 绘制等高线图 import numpy as np import matplotlib.pyplot as plt def calcu_elevation(x1, y1): h = (1-x1 ...

  7. 第八章、使用matplotlib绘制高级图表

    8.1.绘制等高线图 等高线图是地形上高程相等的相邻各点所连成的闭合曲线,它会将地面上海拔高度相同的点连成环线,之后将环线垂直投影到某一水平面上,并按照一定的比例缩绘到图纸上. 在matplotlib ...

  8. 使用matplotlib绘制高级图表8

    1.绘制等高线图 使用contour()和contourf()函数绘制和填充等高线图 import numpy as np import matplotlib.pyplot as plt # 计算高度 ...

  9. python 堆叠柱状图 多列 复杂_[Python Study Notes]堆叠柱状图绘制

    ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ...

最新文章

  1. 【spring】编程式事务控制
  2. [PHP] Phalcon操作示范
  3. JAVA 读取图片储存至本地
  4. 字符常量在C和C++中的区别
  5. RMAN异机复制数据库(相同路径)
  6. Hadoop入门(二十二)Mapreduce的求平均值程序
  7. desktop docker 无法卸载_Docker容器无法停止或移除-权限被拒绝错误
  8. oracle监听管理工具,oracle监听器管理
  9. 华为交换机eth口作用_华为交换机口如何绑定端口号
  10. Python六大开源框架对比:Web2py略胜一筹(转)
  11. 暴走英雄坛怎么领服务器维护奖励,暴走英雄坛琅嬛福地奖励在哪里?福地奖励获取方法一览...
  12. 读书笔记:《重来REWORK》
  13. Java基础23 网络编程 socket套接字流 TCP传输总结
  14. 计算机运行游戏卡顿,Win7电脑玩游戏出现卡顿如何解决?
  15. mysql连接数尖刺激增_mysql最大连接数max_connections
  16. Android调试工具adb的高逼格使用方式
  17. 2021秋软工实践个人作业一
  18. Appium 自动化测试 手机操作
  19. vue之router莫名其妙的bug
  20. 三万字,七十图详解计算机网络六十二问(建议收藏)

热门文章

  1. 玩转树莓派(一):树莓派更换国内源、设置中文显示及输入法
  2. 牧场物语移植java_牧场物语整合包-新手入门
  3. 毕业生进入IT 业如何进行角色转换
  4. python 大智慧 dll_Python调用windows下DLL详解
  5. win7 android双系统,联想乐Pad平板电脑发布:Win7/Android双系统
  6. JavaSwing基础
  7. 活期储蓄账目管理系统(C++课程设计)
  8. SRPG游戏开发(四十二)第九章 战斗系统 - 四 计算战斗数据II(Calculate Combat Data II)
  9. 大物流行业未来发展格局研判
  10. WordXP巧画禁烟标志(转)