数据可视化 | 手撕 Matplotlib 绘图原理(一)

2021-06-24 11:05:28 浏览数 (1)

本文内容适合入门及复习阅读,绘图所需的基本知识均有涉及,内容较多,由于篇幅限制,故分成两部分。

为方面小伙伴们阅读,将本文的目录附在文首。

  • 绘图准备
    • 导入需要用到的模块
    • 中文与负号显示问题解决
    • 初步认识 matplotlib 通用函数
  • 创建画布
    • MATLAB 风格接口
    • 面向对象接口
  • 标题
  • 调整颜色
  • 设置轴标签
    • x 轴标签
  • 坐标轴刻度与标签
    • 隐藏刻度与标签
    • 增减刻度数量
    • 自定义刻度
    • 格式生成器与定位器小结
    • x 轴的刻度与标签
    • 轴的刻度范围
    • 去掉坐标轴
    • 调整日期自适应
    • 轴标签、刻度、标签的相关说明
  • 双坐标轴
  • 图例
    • 同时显示多个图例

Matplotlib 最重要的特性之一就是具有良好的操作系统兼容性和图形显示底层接口兼容性(graphics backend)。Matplotlib 支持几十种图形显示接口与输出格式,这使得用户无论在哪种操作系统上都可以输出自己想要的图形格式。这种跨平台、面面俱到的特点已经成为 Matplotlib 最强大的功能之一,Matplotlib 也因此吸引了大量用户,进而形成了一个活跃的开发者团队,晋升为 Python 科学领域不可或缺的强大武器。

绘图准备

导入需要用到的模块

代码语言:javascript复制
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
%matplotlib inline

在 Notebook 中画图时,将图形直接嵌在 Notebook 页面中,有两种展现形式:

  • %matplotlib notebook 会在 Notebook 中启动交互式图形。
  • %matplotlib inline 会在 Notebook 中启动静态图形。

在脚本中画图时,显示图形的时候必须使用 plt.show()plt.show()会启动一个事件循环(event loop),并找到所有当前可用的图形对象,然后打开一个或多个交互式窗口显示图形。

一个 Python 会话(session)中只能使用一次 plt.show() ,因此通常都把它放在脚本的最后。

中文与负号显示问题解决

代码语言:javascript复制
plt.rcParams['font.sans-serif']=['Simhei']
# 显示中文,解决图中无法显示中文的问题

plt.rcParams['axes.unicode_minus']=False
# 设置显示中文后,负号显示受影响。解决坐标轴上乱码问题

另一种设置方式

代码语言:javascript复制
import matplotlib as mpl  

# 设置全局中文字体
mpl.rcParams["font.family"] = "SimHei"

# 设置全局字体风格
mpl.rcParams["font.style"] = "italic"

# 设置全局字体大小
mpl.rcParams["font.size"] = 16

# 负号设置
mpl.rcParams["axes.unicode_minus"] = False

# 设置局部字体
font = {"family":"Kaiti",
        "style":"oblique",
        "weight":"normal",
        "color":"green",
        "size": 20
       }
plt.title("中文", fontdict=font)

字体风格设置 font.style 字体的风格 normal 常规(默认) italic 斜体 oblique 倾斜

不同的电脑可能显示出来依旧有问题, 这就需要自己查询一下自己电脑什么中文字体, 从选出即可查询 matplotlib 系统中文字体。

代码语言:javascript复制
from matplotlib.font_manager import fontManager
import os

fonts = [font.name for font in fontManager.ttflist if
         os.path.exists(font.fname) and os.stat(font.fname).st_size>1e6]
print(list(fonts))
代码语言:javascript复制
['Nirmala UI','Cambria', 'Malgun Gothic', 'STZhongsong', 'Nirmala UI',  
'Calibri', 'STKaiti', 'Microsoft JhengHei', 'FangSong', 'Yu Gothic',   
'STXihei', 'STHupo', 'Calibri', 'Malgun Gothic', 'STSong', 'MS Gothic',   
'Segoe UI Symbol','Yu Gothic', 'STXingkai', 'YouYuan', 'Times New Roman',   
'STLiti', 'Microsoft YaHei', 'FZShuTi', 'DengXian', 'SimHei', 'Calibri',   
'LiSu', 'Microsoft JhengHei', 'KaiTi', 'Yu Gothic', 'Calibri', 'Yu Gothic',   
'STFangsong', 'SimSun', 'Microsoft JhengHei', 'FZYaoTi', 'Segoe UI Historic',   
'STXinwei', 'Calibri', 'MingLiU-ExtB', 'DengXian', 'Arial', 'STCaiyun',   
'Segoe UI Emoji', 'SimSun-ExtB', 'Times New Roman', 'DengXian', 'Calibri',   
'Gabriola', 'Nirmala UI', 'Microsoft YaHei', 'Malgun Gothic', 'Microsoft YaHei']

初步认识 matplotlib 通用函数

代码语言:javascript复制
plt.style.use('seaborn')

配置图表样式,可以使用 plt.style.available 命令可以看到所有可用的风格。

代码语言:javascript复制
plt.style.available
代码语言:javascript复制
['Solarize_Light2', '_classic_test_patch', 'bmh', 'classic',
 'dark_background', 'fast', 'fivethirtyeight', 'ggplot',
 'grayscale', 'seaborn', 'seaborn-bright', 'seaborn-colorblind',
 'seaborn-dark', 'seaborn-dark-palette', 'seaborn-darkgrid',
 'seaborn-deep', 'seaborn-muted', 'seaborn-notebook', 'seaborn-paper',
 'seaborn-pastel', 'seaborn-poster', 'seaborn-talk', 'seaborn-ticks',
 'seaborn-white', 'seaborn-whitegrid', 'tableau-colorblind10']

也可以通过官网[1]查看每个样式的效果

代码语言:javascript复制
plt.figure(figsize=(8, 6), dpi=100)
# 获取当前轴,必要时创建一个
ax = plt.gca()
# 设置将X轴的刻度值放在底部X轴上
ax.xaxis.set_ticks_position('bottom')
# 设置将Y轴的刻度值放在左侧y轴上
ax.yaxis.set_ticks_position('left')
# 设置右边坐标轴线的颜色(设置为none表示不显示)
ax.spines['right'].set_color('none')
# 设置顶部坐标轴线的颜色(设置为none表示不显示)
ax.spines['top'].set_color('none')
# 设置底部坐标轴线的位置(设置在y轴为0的位置)
ax.spines['bottom'].set_position(('data', 0))
# 设置左侧坐标轴线的位置(设置在x轴为0的位置)
ax.spines['left'].set_position(('data', 0))
x = np.linspace(0, 10, 1000)
plt.plot(x, np.sin(x), color='r')
plt.plot(x, x**0.5)
plt.title('数据可视化')
plt.xlabel('X')
plt.ylabel('Y', rotation=0)
plt.xticks([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
plt.ylim(-2, 4)
plt.legend(['$y = sin(x)$', '$y = sqrt{x}$'])
plt.text(7.2, 3.5, '这是图例')
plt.scatter(3, np.sin(3), color='b')
plt.annotate('这是x=3的点', (3, np.sin(3)), (4, np.sin(2)),   
             arrowprops=dict(arrowstyle='- >', color='k'))

plt.plot(x, np.cos(x))
plt.savefig('s.jpg', dpi=500);

创建画布

MATLAB 风格接口

Matplotlib 最初作为 MATLAB 用户的 Python 替代品,许多语法都和 MATLAB 类似。MATLAB 风格的工具位于 pyplot(plt)接口中。

代码语言:javascript复制
plt.figure(figsize=(width, height), dpi=pixel)
  • figsize:画布的大小和长宽比, 接收一个元祖(宽, 高), 比如 figsize=(8, 6)
  • dpi:图像的像素, 值越大图像越清晰
代码语言:javascript复制
plt.subplot(nrows, ncols, index, **kwargs) # (行、列、子图编号)
  • index是沿着nrows由左到右,沿着ncols由上至下编号。

这种接口最重要的特性是有状态的:它会持续跟踪"当前的"图形和坐标轴,所有 plt命令都可以应用。你可以用 plt.gcf()(获取当前图形)和 plt.gca()(获取当前坐标轴)来查看具体信息。

设置figure(画布)大小共有两种方式:

  • 在调用plt.figure()显示创建figure对象时,通过figsize参数指定,单位为英寸。
  • 在创建figure对象后,可以通过figure对象set_size_inches方法设置
代码语言:javascript复制
figure = plt.figure()figure.set_size_inches(8, 6)

面向对象接口

在面向对象接口中,画图函数不再受到当前"活动"图形或坐标轴的限制,而变成了显式的 FigureAxes的方法。

代码语言:javascript复制
plt.subplots(
    nrows=1,
    ncols=1,
    *,
    sharex=False,
    sharey=False,
    squeeze=True,
    subplot_kw=None,
    gridspec_kw=None,
    **fig_kw,
)
  • nrows, ncols: int,default:1 子图网格的行/列数。
  • sharex, sharey : bool or {'none', 'all', 'row', 'col'}, default: False 控制x (sharex)或y (sharey)之间的属性共享轴: True或'all':所有子图共享 x 轴或 y 轴。 False或'none':每个子图 x 轴或 y 轴是独立的。 'row':每个子图行共享一个 x 轴或 y 轴。 'col':每个子图列共用一个 x 轴或 y 轴

示例

代码语言:javascript复制
# 先创建图形网格
# ax是一个包含两个Axes对象的数组
fig, ax = plt.subplots(2)
# 在每个对象上调用plot()方法
x = np.linspace(0, 10, 100)
ax[0].plot(x, np.sin(x))
ax[1].plot(x, np.cos(x));

标题

代码语言:javascript复制
plt.title(label, fontdict=None, loc=None, pad=None, *, y=None, **kwargs)

常用参数方式: plt.title(label=标题名, loc=位置, fontsize=字体大小, color=颜色, rotation=旋转角度, alpha=透明度)

  • label: 标题名接受一个字符串, 可以接受一个LaTex 语法的字符串, 从而显示数学公式,比如 ""
  • loc : 标题的位置, 有 'center', 'left', 'right' 可供选择
  • fontsize: 标题的字体大小
  • rotation: 控制字体旋转
  • alpha: 透明度, 0-1, 越大颜色越深
  • color: 标题的颜色 ,比如 color= "r", 即标题是红色, 颜色有选择如下: 蓝色|b、绿色|g、红色|r、青色|c、品红|m、黄色|y、黑色|k、白色|w

示例

代码语言:javascript复制
x = np.linspace(0, 10, 100)
plt.title('这是一个示例标题')
plt.plot(x,np.sin(x));

调整颜色

代码语言:javascript复制
fig, ax = plt.subplots()  
plt.style.use('seaborn-white')  
lines = []
styles = ['-', '--', '-.', ':']
colors = ['b', 'g', 'r', 'k']
x = np.linspace(0, 10, 1000)
# 画四条线
for i in range(4):
    lines  = ax.plot(x, np.sin(x - i * np.pi / 2),
                     styles[i], color=colors[i])
ax.axis('equal')
# 设置图例要显示的线条和标签
ax.legend(lines, ['line A', 'line B', 'line C', 'line D'],
          loc='upper right', frameon=False);

更多颜色名称可参见:颜色对照表

设置轴标签

在横轴和竖轴注明名称以及数量单位。使用 plt.xlabel()plt.ylabel()

x 轴标签

代码语言:javascript复制
plt.xlabel(xlabel, fontdict=None, labelpad=None, *, loc=None, **kwargs)

常用参数方式: plt.xlabel(xlabel=x轴标签, fontsize=字体大小 , color = 颜色, rotation = 旋转角度, alpha=透明度)

  • xlabel: x 轴要接收的标签的字符串, 同样可以接受一个 LaTex 语法的字符串, 显示数学公式

示例

代码语言:javascript复制
x = np.linspace(0, 10, 100)
plt.plot(x, np.sin(x))
plt.title("A Sine Curve示例")
plt.xlabel("x轴示例")
plt.ylabel("$sin(x)$");

坐标轴刻度与标签

可以将每个 Matplotlib 对象都看成是子对象的容器,例如每个 figure都会包含一个或多个 axes对象,每个 axes对象又会包含其他表示图形内容的对象。

每个 axes都有 xaxisyaxis属性,每个属性同样包含构成坐标轴的线条、刻度和标签的全部属性。


locator: 坐标轴定位器 formatter: 格式生成器


隐藏刻度与标签

plt.NullLocator()

代码语言:javascript复制
ax = plt.axes()
ax.plot(np.random.rand(50))
ax.yaxis.set_major_locator(plt.NullLocator())
ax.xaxis.set_major_formatter(plt.NullFormatter())

增减刻度数量

plt.MaxNLocator()

代码语言:javascript复制
fig, ax = plt.subplots(4, 4, sharex=True, sharey=True)
# 为每个坐标轴设置主要刻度定位器
for axi in ax.flat:
   axi.xaxis.set_major_locator(plt.MaxNLocator(3))
   axi.yaxis.set_major_locator(plt.MaxNLocator(3))
fig

自定义刻度

plt.FuncFormatter 实现用一个自定义的函数设置不同刻度标签的显示。

示例

代码语言:javascript复制
def format_func(value, tick_number):
    # 找到π/2的倍数刻度
    N = int(np.round(2 * value / np.pi))
    if N == 0:
        return "0"
    elif N == 1:
        return r"$pi/2$"
    elif N == 2:
        return r"$pi$"
    elif N % 2 > 0:
        return r"${0}pi/2$".format(N)
    else:
        return r"${0}pi$".format(N // 2)

# 画正弦曲线和余弦曲线
fig, ax = plt.subplots()  
plt.style.use('seaborn-white')  
x = np.linspace(0, 3 * np.pi, 1000)
ax.plot(x, np.sin(x), lw=3, label='Sine')
ax.plot(x, np.cos(x), lw=3, label='Cosine')
# 设置网格、图例和坐标轴上下限
ax.grid(True)
ax.legend(frameon=False)

# 通过改变轴的限制设置等量缩放  
# 设置x轴与y轴每个单位长度保持一致
ax.axis('equal')
ax.set_xlim(0, 3 * np.pi)
ax.xaxis.set_major_formatter(plt.FuncFormatter(format_func))

格式生成器与定位器小结

定位器类

描述

NullLocator

无刻度

FixedLocator

刻度位置固定

IndexLocator

用索引作为定位器(如 x = range(len(y)) )

LinearLocator

从 min 到 max 均匀分布刻度

LogLocator

从 min 到 max 按对数分布刻度

MultipleLocator

刻度和范围都是基数(base)的倍数

MaxNLocator

为最大刻度找到最优位置

AutoLocator

(默认)以 MaxNLocator 进行简单配置

AutoMinorLocator

次要刻度的定位器

格式生成器类

描述

NullFormatter

刻度上无标签

IndexFormatter

将一组标签设置为字符串

FixedFormatter

手动为刻度设置标签

FuncFormatter

用自定义函数设置标签

FormatStrFormatter

为每个刻度值设置字符串格式

ScalarFormatter

(默认)为标量值设置标签

LogFormatter

对数坐标轴的默认格式生成器

x 轴的刻度与标签

常用参数方式: plt.xticks(ticks=x轴的刻度, labels=刻度的标签)

  • ticks: 即要显示的刻度, 一般给一个列表
  • labels: 即刻度要显示的标签,也传入一个列表
  • 注意:fontsize=字体大小 , color = 颜色, rotation = 旋转角度, alpha=透明度 同样可用
  • y轴一致, plt.yticks()

轴的刻度范围

plt.xlim(最小值,最大值)

plt.ylim(最小值,最大值)

去掉坐标轴

plt.axis('off')

调整日期自适应

有时候显示日期会重叠在一起,非常不友好,调用plt.gcf().autofmt_xdate(),将自动调整角度。

代码语言:javascript复制
x=pd.date_range('2021/01/01',periods=30)
y=np.arange(0,30,1)
plt.figure(figsize=(8, 5))  
plt.style.use('seaborn-white')  
plt.plot(x,y)
plt.gcf().autofmt_xdate()
plt.show();

轴标签、刻度与标签的相关说明

当一张figure画布上,只有一个图的时候,通过如下方式设置:

  • plt.xlabel 设置x轴的标签说明。
  • plt.xticks 设置x轴的刻度标签。
  • plt.title 设置标题。

当一张figure画布上,有多个图形的时候,通过如下方式设置,除了通过plt对象外,我们还可以通过子绘图对象来设置与获取标签与刻度。

  • ax.set_xlim 设置x轴刻度范围。
  • ax.get_xlim 获取x轴刻度范围。
  • ax.set_xticks 设置x轴显示的刻度。
  • ax.get_xticks 获取x轴显示的刻度。
  • ax.set_xticklabels 设置x轴显示的刻度标签。默认显示的是就是刻度值。
  • ax.get_xticklabels 获取x轴显示的刻度标签。默认显示的是就是刻度值。

也可以设置标签说明与标题

  • ax.set_xlabel 设置x轴的标签说明。
  • ax.get_xlabel 获取x轴的标签说明。
  • ax.set_title 设置标题。
  • ax.get_title 获取标题。

如果需要设置或者获取y轴,只需要将x换成y即可。

双坐标轴

代码语言:javascript复制
fig, ax = plt.subplots(figsize=(8, 5))   
plt.style.use('seaborn-white') 
x = np.linspace(0, 10, 100)
y1 = np.sin(x - np.pi / 2)
y2=np.log(x)
ax.plot(x,y1,label='$y=sin(x-pi/2)$')
# 添加一个坐标轴,默认0到1
ax.twinx()
ax.plot(x,y2,'r',label='$y=log(x)$')
ax.axis('equal')
ax.legend()

图例

plt.legend([图例], loc=位置, fontsize=字体大小)

  • [图例]: 字符串形式,多个图用列表形式存储起来, 字符串同样接受 LaTex 语法而显示数学公式
  • loc: 图例的位置, 1-10, 每个数字代表代表一个位置
    • 该参数用于指定图例的摆放位置。默认是"best",还有"upper"、"down"、"left"和"right"。一共有四种组合形式"upper left""upper right""down left""down right"。也可以为该参数指定一个坐标"元组",坐标的值是基于当前坐标原点的比例。
  • fontszie 控制图例的大小
  • ncol:图例显示的列数,默认为1列。
  • frameon:设置是否显示图例的边框。True(默认值)显示,False不显示

同时显示多个图例

有时,我们可能需要在同一张图上显示多个图例,但若用 plt.legend()ax.legend()方法创建第二个图例,那么第一个图例就会被覆盖。

可以通过从头开始创建一个新的图例艺术家对象(legend artist),然后用底层的ax.add_artist()方法在图上添加第二个图例。

代码语言:javascript复制
fig, ax = plt.subplots()
lines = []
styles = ['-', '--', '-.', ':']
x = np.linspace(0, 10, 1000)
# 画四条线
for i in range(4):
    lines  = ax.plot(x, np.sin(x - i * np.pi / 2),
                     styles[i], color='k')
ax.axis('equal')
# 设置第一个图例要显示的线条和标签
ax.legend(lines[:2], ['line A', 'line B'],
          loc='upper right', frameon=False)

# 创建第二个图例,通过add_artist方法添加到图上
from matplotlib.legend import Legend
leg = Legend(ax, lines[2:], ['line C', 'line D'],
             loc='lower right', frameon=False)
ax.add_artist(leg);

可参见更多图例[2]

参考资料[1] 图表样式: https://matplotlib.org/3.1.1/gallery/style_sheets/style_sheets_reference.html[2] 图例: https://matplotlib.org/3.1.1/tutorials/intermediate/legend_guide.html

0 人点赞