系列文章目录
matplotlib可视化:基础绘图函数使用【函数功能+案例代码】
matplotlib可视化:基础图像绘制
matplotlib可视化:局部放大折线图+阴影区间绘制【原理+代码】
文章目录
一、效果展示
首先放效果图。图像由主图和子图组成,包括两条折线,没一条折线均被上下误差线组成的区间包围,并通过颜色填充。
二、图像绘制
2.1 准备工作
2.1.1 依赖库导入
采用2022年美赛C题数据集,通过pandas读取数据,matplotlib绘制图像。
import matplotlib.pyplot as plt
import pandas as pd
from mpl_toolkits.axes_grid1.inset_locator import mark_inset
2.1.2 字体设置和乱码消除
#字体设置和乱码消除
plt.rcParams['font.sans-serif'] = ['SongNTR']
plt.rcParams["axes.unicode_minus"] = False
采用的SongNTR字体(中文:宋体;英文:Times New Roman),具体本机的字体配置参考博文:字体设置
2.1.3 数据准备
分析图像,上面共存在2条折线,以及他们的上下区间折线。这里采用黄金比特币的价格作为主要的2条折线;价格±5天的标准差*n作为上下区间折线。
#数据
mkpruData=pd.read_csv(r"D:\data\BCHAIN-MKPRU.csv")
goldData=pd.read_csv(r"D:\data\LBMA-GOLD.csv")
gold_price=goldData["USD (PM)"]
mkpru_price=mkpruData["Value"]
#标准差计算
goldStd=gold_price.rolling(5).std()
mkpruStd=mkpru_price.rolling(5).std()
2.2 主图绘制
主图绘制过程中,主要遇到了两个问题:1、阴影区间如何填充。2、双轴图的图例如何绘制。
2.2.1 阴影区间绘制
主要采用函数如下:
matplotlib.pyplot.fill_between(x, y1, y2=0, where=None, interpolate=False, step=None, *, data=None, **kwargs)
其中主要的参数为x,y1,y2。x表示的为x轴数据,y1,y2表示上界,下界,其中y2默认为0。
在图像中,我们采用如下语法绘制:
plt.fill_between(range(len(goldStd)),
gold_price-5*goldStd,
gold_price+5*goldStd,
color='#1111FF',
alpha=0.2)
plt.fill_between(range(len(mkpruStd)),
mkpru_price-8*mkpruStd,
mkpru_price+8*mkpruStd,
color='#FF1010',
alpha=0.2)
注:alpha为透明度设置。
2.2.2 双轴图的图例绘制
双轴图的相关介绍在matplotlib可视化:基础图像绘制的第二章。这里不再赘述。
对于单轴的图像来说,直接通过ax.legend()便可以设置图例的相关属性;但是双轴图存在两个axes对象,如何让两个axes对象生成的图例位于一个方框中。
通过查阅博客发现,plot函数返回值是一个存放Line2D类型的元素的列表,legend方法中可以通过传递图像列表以及标签列表,然后绘制不同axes中的图例。
line1,=ax1.plot(gold_price,color='#1111FF',linewidth=1.3)
line2,=ax1_2.plot(mkpru_price,color='#FF1010',linewidth=1.3)
ax1.legend([line1,line2],["黄金","比特币"],loc='upper left',ncol=2,prop={"size":9})
由此,可以得到[line1,line2]图像对应的图例名称为[“黄金”,“比特币”],将其传入legend中即可。其余的legend参数可以参考博客:matplotlib可视化:基础绘图函数使用第2.5小节坐标轴设置。
2.2.3 主图绘制代码
主图绘制的所有代码如下:
#绘制折线图
fig=plt.figure(dpi=300)
ax1=fig.add_axes([0.1,0.1,0.95,0.5])
#黄金折线
line1,=ax1.plot(gold_price,color='#1111FF',linewidth=1.3)
ax1.set_title("黄金比特币价格折线图",fontsize=14)
ax1.set_xlabel("时间",fontsize=12)
ax1.set_ylabel("黄金价格",fontsize=12,color="#1111FF")
ax1.set_xlim(0,1200)
plt.fill_between(range(len(goldStd)),
gold_price-5*goldStd,
gold_price+5*goldStd,
color='#1111FF',
alpha=0.2)
plt.tick_params('y',labelcolor="#1111FF")
#比特币折线
ax1_2=ax1.twinx()
line2,=ax1_2.plot(mkpru_price,color='#FF1010',linewidth=1.3)
ax1_2.set_ylabel("比特币价格",fontsize=12,color="#FF1010")
plt.fill_between(range(len(mkpruStd)),
mkpru_price-8*mkpruStd,
mkpru_price+8*mkpruStd,
color='#FF1010',
alpha=0.2)
plt.tick_params('y',labelcolor="#FF1010")
ax1.legend([line1,line2],["黄金","比特币"],loc='upper left',ncol=2,prop={"size":9})
2.3 子图绘制
2.3.1 子图坐标轴建立
子图坐标轴的建立,其核心思想主要是通过主图的axes对象创建一个新的axes对象作为子图,通过主图的axes对象和子图的axes对象相互关联。主要通过两种方式实现
【方法一】
mpl_toolkits.axes_grid1.inset_locator.inset_axes(parent_axes, width, height, loc='upper right', bbox_to_anchor=None, bbox_transform=None, axes_class=None, axes_kwargs=None, borderpad=0.5)
主要参数说明:
参数 | 含义 |
---|---|
parent_axes | 主坐标系的axes对象 |
width,height | 创建的子图轴的大小。若为浮点数(1.3),则以英寸为单位;若为字符串(‘40%’),则以主图的相对大小为单位。若没有指定bbox_to_anchor和bbox_transform,就是相对主图的大小,否则为相对于bbox_to_anchor的大小。 |
loc | 子图边界框的位置,和图例的loc相同 |
bbox_to_anchor | 边界框,四元数组(x0, y0, width, height) |
bbox_transform | 一般为parent_axes.transAxes |
通过上述函数,构建如下的子图:
childax=inset_axes(ax1, width='60%', height='60%', bbox_to_anchor=(0,0.3,0.55,0.55),bbox_transform=ax1.transAxes)
【方法二】
通过ax.inset_axes(x0,y0,width,height)函数实现。
其中x0,y0为子图左下方坐标原点,width和height为子图的长度和宽度。
例如:
axins = ax.inset_axes((0.2, 0.2, 0.4, 0.3))
上述代码的含义是:以父坐标系中的x0=0.2x,y0=0.2y为左下角起点,嵌入一个宽度为0.4x,高度为0.3y的子坐标系,其中x和y分别为父坐标系的坐标轴范围。效果如下图所示:
由此,我们基于主图建立子图坐标轴:
#创建子坐标系,并设置区间
childAx1=ax1.inset_axes((0.16, 0.5, 0.4, 0.3))
2.3.2 子图折线图绘制
子图于主图的折线绘制并没有什么区别,主要是对于x轴和y轴的坐标轴区间范围显示发生了变化。传入的数据与主图相同。
childAx1=ax1.inset_axes((0.16, 0.5, 0.4, 0.3))
#黄金子折线图
line3,=childAx1.plot(gold_price,color='#1111FF',linewidth=1.3)
childAx1.fill_between(range(len(goldStd)),
gold_price-5*goldStd,
gold_price+5*goldStd,
color='#1111FF',
alpha=0.2)
childAx1.set_xlim(400,650)
childAx1.set_ylim(1100,1400)
childAx1.tick_params('y',labelcolor="#1111FF")
#比特币子折线图
childAx1_2=childAx1.twinx()
line2,=childAx1_2.plot(range(len(mkpru_price)),mkpru_price,color='#FF1010',linewidth=1.3)
childAx1_2.fill_between(range(len(mkpruStd)),
mkpru_price-8*mkpruStd,
mkpru_price+8*mkpruStd,
color='#FF1010',
alpha=0.2)
childAx1_2.tick_params('y',labelcolor="#FF1010")
childAx1_2.set_ylim(0,21000)
在这里,我选择了x轴范围在400-650之间的数据绘制子图;然后分别设置y轴的范围为1100-1400,0-21000。采用的命令:
childAx1.set_xlim(400,650)
childAx1.set_ylim(1100,1400)
childAx1_2.set_ylim(0,21000)
2.3.3 主图和子图之间边界框连线绘制
采用mark_inset函数实现
mpl_toolkits.axes_grid1.inset_locator.mark_inset(parent_axes, inset_axes, loc1, loc2, **kwargs)
参数含义如下:
参数 | 含义 |
---|---|
parent_axes | 主坐标系的axes对象 |
inset_axes | 子坐标系的axes对象 |
loc1 | 其中一个连接线的位置 ,坐标系的四个角( 1 (右上) 2 (左上) 3(左下) 4(右下)) |
loc2 | 另外一个连接线的位置 |
由此,我们绘制子图边界框:
mark_inset(ax1, childAx1, loc1=3, loc2=4, fc="none", ec='k', lw=1)
三、完整代码
完整代码如下:
import matplotlib.pyplot as plt
import pandas as pd
from mpl_toolkits.axes_grid1.inset_locator import mark_inset
#字体设置和乱码消除
plt.rcParams['font.sans-serif'] = ['SongNTR']
plt.rcParams["axes.unicode_minus"] = False
#数据
mkpruData=pd.read_csv(r"D:\paper\图像复刻\折线图\data\BCHAIN-MKPRU.csv")
goldData=pd.read_csv(r"D:\paper\图像复刻\折线图\data\LBMA-GOLD.csv")
gold_price=goldData["USD (PM)"]
mkpru_price=mkpruData["Value"]
#标准差计算
goldStd=gold_price.rolling(5).std()
mkpruStd=mkpru_price.rolling(5).std()
plt.style.use("default")
#绘制折线图
fig=plt.figure(dpi=300)
ax1=fig.add_axes([0.1,0.1,0.95,0.5])
#黄金折线
line1,=ax1.plot(gold_price,color='#1111FF',linewidth=1.3)
ax1.set_title("黄金比特币价格折线图",fontsize=14)
ax1.set_xlabel("时间",fontsize=12)
ax1.set_ylabel("黄金价格",fontsize=12,color="#1111FF")
ax1.set_xlim(0,1200)
plt.fill_between(range(len(goldStd)),
gold_price-5*goldStd,
gold_price+5*goldStd,
color='#1111FF',
alpha=0.2)
plt.tick_params('y',labelcolor="#1111FF")
#比特币折线
ax1_2=ax1.twinx()
line2,=ax1_2.plot(mkpru_price,color='#FF1010',linewidth=1.3)
ax1_2.set_ylabel("比特币价格",fontsize=12,color="#FF1010")
plt.fill_between(range(len(mkpruStd)),
mkpru_price-8*mkpruStd,
mkpru_price+8*mkpruStd,
color='#FF1010',
alpha=0.2)
plt.tick_params('y',labelcolor="#FF1010")
ax1.legend([line1,line2],["黄金","比特币"],loc='upper left',ncol=2,prop={"size":9})
#创建子坐标系,并设置区间
# childax=inset_axes(ax1, width='60%', height='60%', bbox_to_anchor=(0,0.3,0.55,0.55),bbox_transform=ax1.transAxes)
childAx1=ax1.inset_axes((0.16, 0.5, 0.4, 0.3))
#黄金子折线图
line3,=childAx1.plot(gold_price,color='#1111FF',linewidth=1.3)
childAx1.fill_between(range(len(goldStd)),
gold_price-5*goldStd,
gold_price+5*goldStd,
color='#1111FF',
alpha=0.2)
childAx1.set_xlim(400,650)
childAx1.set_ylim(1100,1400)
childAx1.tick_params('y',labelcolor="#1111FF")
#比特币子折线图
childAx1_2=childAx1.twinx()
line2,=childAx1_2.plot(range(len(mkpru_price)),mkpru_price,color='#FF1010',linewidth=1.3)
childAx1_2.fill_between(range(len(mkpruStd)),
mkpru_price-8*mkpruStd,
mkpru_price+8*mkpruStd,
color='#FF1010',
alpha=0.2)
childAx1_2.tick_params('y',labelcolor="#FF1010")
childAx1_2.set_ylim(0,21000)
# loc1 loc2: 坐标系的四个角
# 1 (右上) 2 (左上) 3(左下) 4(右下)
mark_inset(ax1, childAx1, loc1=3, loc2=4, fc="none", ec='k', lw=1)
# plt.show()
plt.savefig("test.png",dpi=300,transparent=False,bbox_inches='tight')