专栏名称: SegmentFault思否
SegmentFault (www.sf.gg)开发者社区,是中国年轻开发者喜爱的极客社区,我们为开发者提供最纯粹的技术交流和分享平台。
目录
相关文章推荐
程序员小灰  ·  清华大学《DeepSeek学习手册》(全5册) ·  昨天  
程序员小灰  ·  3个令人惊艳的DeepSeek项目,诞生了! ·  13 小时前  
OSC开源社区  ·  升级到Svelte ... ·  3 天前  
程序员的那些事  ·  成人玩偶 + ... ·  3 天前  
程序员的那些事  ·  李彦宏自曝开源真相:从骂“智商税”到送出“史 ... ·  4 天前  
51好读  ›  专栏  ›  SegmentFault思否

数据可视化之 matplotlib 绘图篇

SegmentFault思否  · 公众号  · 程序员  · 2020-03-02 12:07

正文

本文转载于 SegmentFault 社区

作者:mhxin




引言


首先来看几个简单的图表, 下面 4 段不同的 matplotlib 绘图代码最终的结果是一样的,绘制的图形如下图所示。
a = np.linspace(-5, 5, 100)
b = np.sin(a)
c = np.cos(a)

# -------------------------------------- #
# ---------------First way-------------- #
fig, ax = plt.subplots()
ax.set_title('Sin-Cos')
ax.set_xlabel('X')
ax.set_ylabel('Y')
ax.plot(a, b, c='r')
ax.plot(a, c, c='g')
ax.legend(['a', 'b'])
fig.show()


# -------------------------------------- #
# ---------------Second way-------------- #
fig = plt.figure()
ax = fig.add_subplot(111)
ax.set_title('Sin-Cos')
ax.set_xlabel('X')
ax.set_ylabel('Y')
ax.plot(a, b, c='r')
ax.plot(a, c, c='g')
ax.legend(['a', 'b'])
fig.show()


# -------------------------------------- #
# ---------------Third way-------------- #
plt.figure()
plt.title('Sin-Cos')
plt.xlabel('X')
plt.ylabel('Y')
plt.plot(a, b, c='r')
plt.plot(a, c, c='g')
plt.legend(['a', 'b'])
plt.show()


# -------------------------------------- #
# ---------------Fourth way------------- #
plt.subplot(111)
plt.title('Sin-Cos')
plt.xlabel('X')
plt.ylabel('Y')
plt.plot(a, b, c='r')
plt.plot(a, c, c='g')
plt.legend(['a', 'b'])
plt.show()

在以上的 4 种实现方式中,可能大多数都会使用后面两种,即直接使用 plt.plot() 进行绘制,我刚开始也一样,它看起来简单,用起来也很方便。但当你逐渐习惯使用默认的方式来进行绘图时,慢慢会出现各种的问题,尤其是当你需要对图表使用很多自定义的设置的时候。默认接口隐藏了绘图过程中的很多细节,因为不了解其内部细节,所以在绘图过程中,对一些图表的设置方法全靠记忆。



Matplotlib 中的部件


好了,说了这么多,我们先来了解 matplotlib 绘图过程中几个主要的名称,如下图所示:

  • Figure 可以理解为画板,使用 fig = plt.figure() 会创建一个画板。
  • Axes可以理解为画板上的各种图形,一个图形就是一个 axes,利用 axes 可以对图形的各个部分进行设置。比如使用 fig.add_subplot() 会在 fig 上创建一个 axes。
  • Axis 表示一个 Axes 的坐标轴,比如 x 轴,y 轴以及 z 轴等。

接着,介绍图形(Axes)中的主要部件,了解了每个部件在图中的位置以及对应的功能之后才能按照自己的方式来对图表进行设置。


以上图中的所有部件,都可以通过 axes 来实现精确的控制,比如需要设置 x 轴主刻度标签, 即可使用 axes.xaxis.set_ticklabels(['', '']),这种面向对象的控制方式,很容易理解和实现。



实战



基于上面的介绍,我们来绘制一些图形。

时间序列图


直接上代码:

# 导入FontProperties类,该类用于定义字体,包括字体,大小等
from matplotlib.font_manager import FontProperties

# 定义标签字体,字体为Time New Roman
# 也可以通过指定fname(字体文件的路径)来定义字体
label_font = FontProperties(family='Times New Roman', size=18, weight='bold', style='normal')

# 定义标题字体
title_font = FontProperties(family='Times New Roman', size=20, weight='bold', style='normal')

# 定义坐标轴刻度字体
ticks_font = FontProperties(family='Times New Roman', size=12, weight='bold', style='normal')

# 定义legend字体
legend_font = FontProperties(family='Times New Roman', size=18, weight='bold', style='normal')

year = np.arange(1985, 2017, 1)
ndvi = np.random.rand(len(year))

# 创建fig和axes
fig, ax = plt.subplots(figsize=(10, 5))


# 取消x方向的格网
# axis.xaxis.grid(False)
# 取消y方向的格网
# axis.yaxis.grid(False)

# 取消所有格网
ax.grid(False)


# 定义标题,字体和颜色
# axis.yaxis.label.set_fontproperties(font)
# axis.xaxis.label.set_fontproperties(font)

ax.set_xlabel('Year', fontproperties=label_font, color='black')
ax.set_ylabel('NDVI', fontproperties=label_font, color='black')


# 定义坐标轴显示范围
ax.set_xlim(1985, 2017)
ax.set_ylim(0, 1)

# 取消图表的上边界和右边界
ax.spines['top'].set_visible(False)
ax.spines['right'].set_visible(False)
# 将图表的左边界和下边界的颜色设为黑色(默认为黑色)
# 颜色可以参考:
# https://matplotlib.org/3.1.3/gallery/color/color_demo.html#sphx-glr-gallery-color-color-demo-py
ax.spines['left'].set_color('black')
ax.spines['bottom'].set_color('black')

# 将图表的主刻度字体改为指定字体,并设定刻度不旋转,遍历所有主刻度
for tick in ax.xaxis.get_majorticklabels():
tick.set_fontproperties(ticks_font)
tick.set_rotation(0)

for tick in ax.yaxis.get_majorticklabels():
tick.set_fontproperties(ticks_font)
tick.set_rotation(0)

# 设置图表的主刻度线的位置,x轴在下边,y轴在左边
ax.yaxis.set_ticks_position('left')
ax.xaxis.set_ticks_position('bottom')

# 设置显示的x主刻度
ax.set_xticks(np.arange(1985, 2017, 3))

# 绘制时间序列
ax.plot(year, ndvi)

# 设置第一条line(这里只有一条)的属性
ax.lines[0].set_linewidth(2)
ax.lines[0].set_linestyle('-')
ax.lines[0].set_color('black')
ax.lines[0].set_marker('*')
ax.lines[0].set_markeredgecolor('red')

# 设置图例
ax.legend(['TS1', ], prop=legend_font, facecolor='white')

#设置标题
ax.set_title('Time Series', fontproperties=title_font, color='black')

plt.show()
绘制结果如下所示:


绘制图表的代码虽然比较长,但结构比较清楚,结合前面的部件介绍,了解每一步的含义,相信很容易就能看懂。

带直方图的散点图


代码如下:
# 定义坐标轴刻度字体
ticks_font = FontProperties(family='Times New Roman', size=14, weight='normal', style='normal')

# 定义画板大小
fig_width = 5

#
space = fig_width / 100


left, bottom, width, height = fig_width / 10, fig_width / 10, fig_width / 2, fig_width / 2
scatter_region = [left, bottom, width, height]
topbar_region = [left, bottom + height + space, width, height/2]
rightbar_region = [left + width + space, bottom, width/2, height]

# 定义画板
plt.figure(figsize=(fig_width, fig_width))

# 定义Axes用于绘制散点图
# plt.axes([left, bottom, width, height])
ax_scatter = plt.axes(scatter_region)

# 定义Axes用于绘制x的直方图
ax_topbar = plt.axes(topbar_region)

# 定义Axes用于绘制y的直方图
ax_rightbar = plt.axes(rightbar_region)

# ax_rightbar.invert_xaxis()
# ax_topbar.xaxis.set_ticks([])
# ax_rightbar.xaxis.set_ticks_position('bottom')
# ax_rightbar.yaxis.set_ticks([])

# 设置坐标轴属性, 这里使用set_tick_params(),可以同时设置坐标轴的多个属性,详情可以参考:
# https://matplotlib.org/3.1.1/api/_as_gen/matplotlib.axes.Axes.tick_params.html#matplotlib.axes.Axes.tick_params
ax_rightbar.yaxis.set_tick_params(bottom=False, labelbottom=False)
ax_rightbar.xaxis.set_tick_params(direction='out', color='black', left=True, labelleft=True)
ax_topbar.yaxis.set_tick_params(direction='out', color='black', left=True, labelleft=True)
ax_topbar.xaxis.set_tick_params(bottom=False, labelbottom=False)


# ax_scatter.xaxis.set_tick_params(direction='out', color='black', labelsize=12)
# ax_scatter.yaxis.set_tick_params(direction='out', color='black', labelsize=12)
# 以上两行代码等同于下面一句代码
ax_scatter.tick_params(direction='out', color='black')

# 设置坐标轴字体
for ax in [ax_topbar, ax_scatter, ax_rightbar]:
for tick in ax.yaxis.get_majorticklabels():
tick.set_fontproperties(ticks_font)
for tick in ax.xaxis.get_majorticklabels():
tick.set_fontproperties(ticks_font)

# s参数用于设置散点大小,c用于设置cmap
ax_scatter.scatter(x, y, s=np.abs(x / y) * 10, c=np.abs(x * y) * 0.02, alpha=0.6)

ax_topbar.hist(x, bins=np.linspace(np.min(x), np.max(x), 16), color='#2C5784')
ax_rightbar.hist(y, bins=np.linspace(np.min(y), np.max(y), 16), orientation='horizontal', color='#20866C')
plt.show()

绘制结果如下图所示:

带标注的函数图像


# 生成数据
x = np.linspace(-4, 4, 1000)
y1 = np.sin(x)
y2 = np.cos(x)
y3 = np.exp(x)

from matplotlib.font_manager import FontProperties
label_font = FontProperties(family='Times New Roman', size=18, weight='bold', style='normal')
title_font = FontProperties(family='Times New Roman', size=20, weight='bold', style='normal')
ticks_font = FontProperties(family='Times New Roman', size=12, weight='bold', style='normal')
legend_font = FontProperties(family='Times New Roman', size=18, weight='bold', style='normal')
fig, ax = plt.subplots(figsize=(8, 10), dpi=100, ncols=1, nrows=2)

# 绘制函数图像
ax[0].plot(x, y1, color='red')
ax[0].plot(x, y2, color='orange')
ax[1].plot(x, y3, color='blue')
# ax1和ax共享x轴,当需要在同一个图表中显示比例不同的图形时使用,比如exp(x)和sin(x)
# ax1 = ax.twinx()


# --------------------------------------- Fig 1 --------------------------------------- #
# 设置标题,坐标轴标签
ax[0].set_title(r'$y=sin(x)$', fontproperties=title_font, color='black', pad=10)
# ax[0].set_xlabel(r'$x$', fontproperties=label_font, color='black', ha='right')
# ax[0].set_ylabel(r'$y$', fontproperties=label_font, color='black')
# ax[0].xaxis.set_label_coords(5, 0)
# ax[0].xaxis.set_label_coords(0, 1.5)

# 不显示右坐标轴和上坐标轴
ax[0].spines['top'].set_visible(False)
ax[0].spines['right'].set_visible(False)

# 移动左坐标轴和底部坐标轴到图形中间位置
ax[0].spines['left'].set_position('center')
ax[0].spines['bottom'].set_position('center')


# 设置x轴刻度
# ax[0].xaxis.set_ticks_position('left')
# 设置x轴刻度坐标
ax[0].set_xticks([-np.pi, -np.pi/2, 0, np.pi/2, np.pi])
# 设置x轴刻度标签
ax[0].set_xticklabels([r'$-\pi$', r'$-\frac{\pi}{2}$', r'$0$', r'$\frac{\pi}{2}$', r'$\pi$'])

# 设置y轴刻度
ax[0].set_yticks([-1, 0, 1])

# 设置y轴刻度的位置
ax[0].yaxis.set_ticks_position('left')

# ax[0].lines[1].set_linestyle('--')

# annotate函数的详细参数解释可以参考:
# https://matplotlib.org/3.1.1/api/_as_gen/matplotlib.axes.Axes.annotate.html
"""
annotate(text, xy, xytext, xycoords, textcoords, arrowprops ,*args, **kwargs)
text(string): 需要标注的文本
xy(tuple -> (float, float)): 标注的点/位置
xytext(tuple -> (float, float)): 放置标注文本的位置
textcoords(string): 给定xytext的坐标系统,可选('offset points', 'offset pixel')
xycoords(string): 给定xy的坐标系,默认'data',表示使用待标注对象的坐标系统
fontproperties(FontProperties): 需要标注的文本
arrowprops(dict): 设置标注文本和标注点之间的连接方式。arrowstyle="->" 表示使用箭头连接,
connectionstyle="arc3,rad=.6" 表示连接类型为圆弧, 半径为0.6
"""


ax[0].annotate(r'$Sin(\frac{2\pi}{3}) = \frac{\sqrt{3}}{2}$', xy=(np.pi * 2 / 3, np.sqrt(3)/2), xycoords='data', xytext=(10, 30), \
textcoords='offset points', arrowprops=dict(arrowstyle="->", connectionstyle="arc3,rad=.6"), fontproperties=ticks_font)

# 这里坐标轴标签是使用注记来设置的
ax[0].annotate(r'$X$', xy=(4.4, 0), xycoords='data', color='black', fontproperties=label_font)

ax[0].annotate(r'$Cos(\frac{2\pi}{3}) = -\frac{1}{2}$', xy=(np.pi * 2 / 3, -1/2), xycoords='data', xytext=(-100, -30), \
textcoords='offset points', arrowprops=dict(arrowstyle="->", connectionstyle="arc3,rad=.2"), fontproperties=ticks_font)

ax[0].annotate(r'$Y$', xy=(-0.3, 1.05), xycoords='data', color='black', fontproperties=label_font)

# 标记特定位置
ax[0].scatter([2*np.pi/3, ], [-1/2,], color='orange', s=40)
ax[0].plot([2*np.pi/3, 2*np.pi/3], [0, -1/2], linestyle='--', color='orange')
ax[0].scatter([2*np.pi/3, ], [np.sqrt(3)/2,], color='red', s=40)
ax[0].plot([2*np.pi/3, 2*np.pi/3], [0, np.sqrt(3)/2], linestyle='--', color='red')

# 设置图例
ax[0].legend([r'$Sin(x)$', r'$Cos(x)$',], loc='best', prop=legend_font, shadow=False, facecolor='white')

# --------------------------------------- Fig 2 --------------------------------------- #
ax[1].spines['top'].set_visible(False)
ax[1].spines['right'].set_visible(False)

# 统一设置刻度标签字体
for sub_ax in ax:
for tick in sub_ax.xaxis.get_majorticklabels():
tick.set_fontproperties(ticks_font)

for tick in sub_ax.yaxis.get_majorticklabels():
tick.set_fontproperties(ticks_font)

ax[1].annotate(r'$X$', xy=(4.4, -0.7), xycoords='data', color='black', fontproperties=label_font)
ax[1].annotate(r'$Y$', xy=(-4.4, 56), xycoords='data', color='black', fontproperties=label_font)

# 填充图像和坐标轴之间的图形
# 如果要在x方向上的曲线之间填充,可以使用fill_betweenx
ax[1].fill_between(x, y3, 0, color='green')


# 使用tighe_layout可以自动调整子图参数,以使子图适合图形区域。
fig.tight_layout()
plt.show()

热力图(Heatmap)


# Heatmap

from mpl_toolkits.axes_grid1 import make_axes_locatable, axes_size
from matplotlib.font_manager import FontProperties

label_font = FontProperties(family='Times New Roman', size=18, weight='bold', style='normal')
title_font = FontProperties(family='Times New Roman', size=20, weight='bold', style='normal')
ticks_font = FontProperties(family='Times New Roman', size=12, weight='bold', style='normal')
legend_font = FontProperties(family='Times New Roman', size=18, weight='bold', style='normal')

vegetables = ["cucumber", "tomato", "lettuce", "asparagus",
"potato", "wheat", "barley"]
farmers = ["Farmer Joe", "Upland Bros.", "Smith Gardening",
"Agrifun", "Organiculture", "BioGoods Ltd."






请到「今天看啥」查看全文