用seaborn/matplot绘制误差带阴影图

记录利用 seaborn、matplot 绘制带有误差带阴影的图。

1 前言

因为在强化学习中经常要用到利用误差带绘的阴影图,因此特地记录一下。实现的时候有两种方法,一种是 seaborn,另一种是matplot。接下来将分别介绍。

2 seaborn绘制误差带图

参考链接5 给出了一个官方的示例,我们先来对这个示例进行一些把玩。数据集fmri的简介及获取方式可参考Seaborn绘图——概述

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import seaborn as sns
import pandas as pd
import matplotlib.pyplot as plt

# 导入数据集
fmri = sns.load_dataset("fmri")
print(list(fmri)) # 获取所有列名
print(fmri.nunique()) # 获取每一列中有多少个不同的数据
print(fmri.shape) # 获取数据集大小
print(fmri.head(5)) # 打印前5行
fmri_time = fmri.loc[:, 'timepoint'] # 获取 timepoint 列的所有数据
print(fmri_time)

# 绘图
sns.relplot(x="timepoint", y="signal", data=fmri)
plt.show()
sns.relplot(x="timepoint", y="signal", kind="line", data=fmri)
plt.show()
sns.relplot(x="timepoint", y="signal", ci=None, kind="line", data=fmri)
plt.show()
sns.relplot(x="timepoint", y="signal", kind="line", ci="sd", data=fmri)
plt.show()
sns.relplot(x="timepoint", y="signal", estimator=None, kind="line", data=fmri)
plt.show()

绘制出来的曲线如下:
00
box-x
box-y

box-x
box-y

由于这个数据集的 timepoint 列包含0~18,共19个数,而数据集的长度是1064,因此,将timepoint作为横轴,则会有一个 timepoint 对应多个 signal 的情况,亦即横轴取值出现了重复,这点从第一幅散点图可以清晰看出。如果将relplot() 中的 kind 设置为 line,则会计算对应纵轴变量的均值(mean)和置信水平为0.95的置信区间(confidence interval)来实现一种聚合(aggregation)。对上图的简要说明如下:

  • 第二幅图是默认对各点的均值做聚合绘制的阴影带;
  • 第三幅图是仅绘制出均值,使用 ci=None 忽略掉置信区间;
  • 第四幅图是设置 ci="sd" 计算标准差作为置信区间;
  • 第五福图是设置 estimator=None 关闭了聚合效果,仅将点进行连线。

类似于前述文章的绘图方式,可以引入类别变量作为区分:

1
2
3
4
5
6
7
8
sns.relplot(x="timepoint", y="signal", hue="event", kind="line", data=fmri)
plt.show()
sns.relplot(x="timepoint", y="signal", hue="region", style="event", kind="line", data=fmri)
plt.show()
sns.relplot(x="timepoint", y="signal", hue="region", style="event", dashes=False, markers=True, kind="line", data=fmri)
plt.show()
sns.relplot(x="timepoint", y="signal", hue="event", style="event", kind="line", data=fmri)
plt.show()

效果如下:
box-x
box-y

box-x
box-y

上图中,第一幅图通过控制 hue 作为分类变量,用颜色区别开;第二幅图又引入了 style 变量,用线型区分开;第三幅图示例了如何设置标记点,还可以有其他的设置方式,可以以列表的形式传入给相应的参数。

3 构造简单数据集小试牛刀

如果觉得使用seaborn的数据集不过瘾,还可以自己来构造一个简单的数据集进行试验。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
import numpy as np

# 构造一个含有噪声的正弦波
time = np.arange(0, 2*np.pi, 0.1)
sin_waves = np.sin(time)
sin_waves = np.expand_dims(sin_waves, axis=-1)
noise = np.random.random((time.size, 10)) - 0.5
print('noise shape: ', noise.shape) # (63, 10)
data = sin_waves + noise
data_mean = np.mean(data, axis=1)
data_std = np.std(data, axis=1)
data_var = np.var(data, axis=1)
data_max = np.max(data, axis=1)
data_min = np.min(data, axis=1)
plt.figure()
plt.plot(data_mean)
plt.show()
plt.figure()
plt.plot(data_std)
plt.show()

# 将 time 扩展为 10 列一样的数据
time_array = time
for i in range(noise.shape[1] - 1):
time_array = np.column_stack((time_array, time))

# 将 time 和 signal 平铺为两列数据,且一一对应
time_array = time_array.flatten() # (630,)
data = data.flatten() # (630,)
data = np.column_stack((time_array, data)) # (630,2)
df = pd.DataFrame(data, columns=['time', 'signal'])

# 绘图
sns.relplot(x='time', y='signal', data=df)
plt.show()
sns.relplot(x='time', y='signal', data=df, kind='line')
plt.show()

在上述程序中,构造了一个正弦波 $\sin t$,其中 $t\in[0, 2\pi)$,然后再随机化一个噪声矩阵,并将其加在正弦波上,按行求其均值、标准差、方差,再将一开始的 time 数组扩展为同等维度的数组 time_array, time_array 中的每一列都一致。接着,将 time_array 和 data 分别平铺后合并为一个两列的数据并转为 DataFrame 格式。绘图的结果如下:
box-x
box-y

box-x
box-y

4 matplot 绘制阴影带

在上节的基础上,如果想绘制包含所有数据在内的阴影带,应该怎么办呢?
可以直接将数据的最大最小值取出,然后用 matplot 的 fill_between() 函数进行填充,同时也可以绘制其标准差的误差带。

1
2
3
4
5
6
7
8
9
10
11
12
sns.relplot(x='time', y='signal', data=df)
plt.plot(time, data_mean, color='deeppink', label='mean')
plt.plot(time, sin_waves, 'b-', label='ideal')
plt.fill_between(time, data_min, data_max, color='violet', alpha=0.2)
plt.legend()
plt.show()

plt.plot(time, data_mean, color='deeppink', label='mean')
plt.plot(time, sin_waves, 'b-', label='ideal')
plt.fill_between(time, data_mean - data_std, data_mean + data_std, color='violet', alpha=0.2)
plt.legend()
plt.show()

效果如下:
box-x
box-y

matplot中颜色的调节可参照下表,直接输入 color='颜色名' 即可[7]
color

参考链接

  1. Fill Between and Alpha
  2. Filling the area between lines
  3. fill_between(x, y1, y2)
  4. https://cloud.tencent.com/developer/ask/sof/534017
  5. Timeseries plot with error bands
  6. (一)seaborn教程——可视化统计关系
  7. matplot颜色表
------ 本文结束感谢您的阅读------
Donate a cup of cola?