Python使用Matplotlib绘制双(多)Y轴图像详解

一:灵感问题(难点)

        在利用python中的matplotlib绘制双y轴图像(条形图+折线图)过程中,为了防止折线图被条形图遮挡,我们需要先绘制条形图,而后绘制折线图,大致效果图如下:

        (这里为了看得更清楚对一些线条做了加宽处理)

        这里我们可以发现:先画的条形图默认使用了左侧的y轴,而后画的折线图默认使用了右侧的y轴。但个人看图习惯加上强迫症想让后画的折线图使用的y轴在左侧,而先画的条形图使用的y轴在右侧,这该如何调整?

二:调整思路

        前期的一些绘制双y轴图像所需要的基础代码:导入模块-修改字体-输入统计数据-创建图像对象-添加子图(创建轴对象);

# import the module needed
from matplotlib import pyplot as plt
from matplotlib import font_manager

# adjust the font
my_font = font_manager.FontProperties(fname="C:/Windows/Fonts/msyh.ttc")
            # module.property(parameter-path)

# input the data
year = list(range(2008, 2023))  
num = [25, 32, 28, 22, 24, 26, 9, 10, 18, 16, 16, 11, 14, 16, 13]
loss = [192.24, 84.97, 65.79, 48.81, 126.29, 152.45, 135.78,
        72.18, 45.94, 55.77, 44.56, 116.38, 8.10, 24.67, 23.79]  

# 创建一个空白图像对象
fig = plt.figure(figsize=(13, 7), dpi=80)

# 添加子图(创建轴对象)
# 创建后会默认显示一个0-1的坐标轴,即使没有绘制任何内容
axis_1 = fig.add_axes((0.1, 0.1, 0.8, 0.8))
         # rect-tuple(left, bottom, width, height) 
         # 0.1-0.1-0.8-0.8为平时作图默认的轴位置
axis_2 = axis_1.twinx()  # 在axis_1基础上创建新的轴对象,共用x轴绘制不同的数据集

1.调整作图顺序及透明度

        调整作图顺序:先画折线图,后画条形图,使得折线图使用左侧y轴,条形图使用右侧y轴;

        调整透明度:绘制条形图时修改alpha参数,调整透明度,削弱条形图对折线图的遮挡效果;

# 绘制year-num数据集(折线图)
axis_1.plot(year, num, color="#6AA84F", marker="o", linewidth=2)

# 绘制year-loss数据集(条形图)
axis_2.bar(year, loss, color="#5470C6", alpha=0.8)

# 设置坐标轴颜色
plt.gca().spines["left"].set_color("#6AA84F")
plt.gca().spines["right"].set_color("#5470C6")

# 设置坐标轴轴线宽度
plt.gca().spines["left"].set_linewidth(2)
plt.gca().spines["right"].set_linewidth(2) 

        按上述方式调整的最终大致效果图如下:

        这里我们可以发现:图像基本达到我们想要的效果,但这样损失了条形图的可见度,折线图被遮挡的效果也并没有完全消除,有没有更好的办法?

2.调整轴对象对应关系  

        为了彻底消除折线图被条形图遮挡的情况,我们还是采用先画条形图,后画折线图的方式;

axis_1.bar(year, loss, color="#5470C6")
axis_2.plot(year, num, color="#6AA84F", marker="o", linewidth=2)

        但这一次我们先使用axis_2轴对象,绘制条形图,再用轴对象axis_1,绘制折线图,更改轴对应关系重新绘图,让条形图使用axis_2对应的右侧y轴,让折线图使用axis_1对应的左侧y轴:

# 更改轴对应关系
# 绘制year-loss数据集(条形图),使用axis_2
axis_2.bar(year, loss, color="#5470C6")
# 绘制year-num数据集(折线图),使用axis_1
axis_1.plot(year, num, color="#6AA84F", marker="o", linewidth=2)

        我们发现:的确,折线图如愿用到了左侧的y轴,条形图用到了右侧的y轴,但同时,非常奇怪的是,后绘制的折线图居然被先绘制的条形图所遮挡,这是为什么?

        这里我先想到的原因是图层原因:先创建的axis_1图层在底层,后创建的axis_2图层在顶层;于是拿着代码问了下AI,AI十分肯定地回答了我这只与绘图顺序有关,与轴对象创建顺序无关!

        这样子又绕了一大圈,试着加入参数zorder什么的都没有效果(我觉得应该是不适用于这种双y轴图像的绘制),又绕了回来,按照最开始的想法自己调试代码找一下原因:

        当仅创建一个轴对象时:

axis_1 = fig.add_axes((0.1, 0.1, 0.8, 0.8))
plt.gca().spines["left"].set_color("r")  # 左侧坐标轴标红
plt.title("仅创建一个轴对象axis_1", fontproperties=my_font, size=17)

        当创建两个轴对象时:

axis_1 = fig.add_axes((0.1, 0.1, 0.8, 0.8))
plt.gca().spines["left"].set_color("r")  # 左侧坐标轴标红
axis_2 = axis_1.twinx()
plt.gca().spines["right"].set_color("b")  # 右侧坐标轴标蓝
plt.title("创建两个轴对象axis_1、axis_2", fontproperties=my_font, size=17)

        我们发现:axis_1标红的左侧坐标轴不见了,是被覆盖了吗?

        越发肯定图层问题,修改代码验证:

axis_1 = fig.add_axes((0.1, 0.1, 0.8, 0.8))
plt.gca().spines["left"].set_color("r")  # 左侧坐标轴标红
axis_2 = axis_1.twinx()
plt.gca().spines["right"].set_color("b")  # 右侧坐标轴标蓝
plt.gca().spines["left"].set_visible(False)  # 隐藏左侧坐标轴
plt.title("创建两个轴对象axis_1、axis_2并隐藏axis_2左侧坐标轴",
          fontproperties=my_font, size=17)

        我们发现:在axis_1中标红的左侧坐标轴重新显现,因此,最初的猜测是正确的:在图像对象fig下创建轴对象axis相当于在画布上创建图层,且先创建的轴对象图层在下,后创建的轴对象图层在上

        同时,在这次轴对象创建的验证猜想过程中,也发现对于函数plt.gca()的一些误读:plt.gca()函数的作用是获取当前的轴对象,因此不仅在上述创建两个轴对象并分别对两个轴对象的坐标轴进行标色过程中需要注意plt.gca()函数的调用位置,同时也意识到之前的画图中如调整思路1中对坐标轴进行标色时,连续调用两次plt.gca()函数的错误操作,以至于后续的一系列误导。

        整思路1中对坐标轴进行标色时,最初的认识是:创建的两个轴对象,axis_1只有左侧纵坐标轴,axis_2只有右侧纵坐标轴,(但实际上都有,只是另一侧没有刻度),因此在创建完两个轴对象后,连续调用两次plt.gca()函数对左右纵坐标轴标色,即axis_1的左侧纵坐标轴,axis_2的右侧纵坐标轴,(但实际上是对当前轴对象也就是axis_2的左右两侧纵坐标轴进行了标色),却也误打误撞呈现出了想要的纵坐标轴标色效果。

        因此,将plt.gca().spines直接替换为指定轴对象如axis_1.spines会更加清晰明确。

        最后,认识到了先后创建轴对象所带来的的图层上下关系,那么更改作图时轴对象对应关系必然会带来图像遮挡,无法达到要求

3.调整坐标轴位置

        在上次的思路中认识到了可以指定轴对象进行坐标轴标色、坐标轴隐藏等效果后,既然无法让图像对应到想要位置的坐标轴,是否可以通过移动坐标轴到达想要的位置?

        最终思路3也是完美的达到了想要的效果,这里直接借思路3的回顾过程直接完整的描述整个解决过程和代码:

        前期的基础工作代码还是一样,在这之后对所需轴对象的坐标轴进行位置移动,并进行坐标轴颜色和轴线宽度的设置以便让观看效果更佳:

# 调整坐标轴位置
axis_1.spines["left"].set_position(("axes", 1))
axis_2.spines["right"].set_position(("axes", 0))

# 调整坐标轴颜色
axis_1.spines["left"].set_color("#5470C6")
axis_2.spines["right"].set_color("#6AA84F")

# 设置坐标轴轴线宽度
axis_1.spines["left"].set_linewidth(2)  # 设置左边坐标轴线宽度为2
axis_2.spines["right"].set_linewidth(2)  # 设置右边坐标轴线宽度为2

         完成上述工作后简单画图发现:坐标轴的颜色显示比较怪异,存在遮挡效果,且坐标轴刻度看着十分别扭,对此进行进一步完善:

# 由于图层关系,隐藏轴对象的另一侧坐标轴
axis_1.spines["right"].set_visible(False)  # 非必要,因为axis_1图层在下,不隐藏也会被覆盖
axis_2.spines["left"].set_visible(False)  # 必要,axis_2图层在上,不隐藏会遮挡axis_1坐标轴颜色

# 调整坐标轴刻度位置
axis_1.yaxis.set_ticks_position("right")
axis_2.yaxis.set_ticks_position("left")

        最后,画出符合自我要求的图像:

# 绘制year-loss数据集(条形图),使用axis_2
axis_1.bar(year, loss, color="#5470C6")

# 绘制year-num数据集(折线图),使用axis_1
axis_2.plot(year, num, color="#6AA84F", marker="o", linewidth=2)

# 展示图像
plt.show()

        当然,还可以进行调整坐标轴标签,添加图例,调整坐标轴刻度范围等等一系列操作进一步完善图像,让图像表现的信息更加多样全面。

        由此进一步添加描述信息对我国2008至2022年风暴潮危害发生次数及经济损失进行简单的数据统计和可视化操作:

# 绘制year-loss数据集(条形图),使用axis_2
axis_1.bar(year, loss, color="#5470C6", label="损失(亿元)")

# 进一步完善axis_1图像信息
axis_1.set_xlabel("年份", fontproperties=my_font, size=17)  
                                                 # 加入size参数调节坐标轴信息字体大小
axis_1.set_ylabel("损失(亿元)", fontproperties=my_font, size=17, color="#5470C6")  
                  # 设置轴标签
axis_1.yaxis.set_label_position("right")  # 调整y轴标签位置
axis_1.legend(prop=my_font, loc="upper center", bbox_to_anchor=(0.7, 0.77))
                                                # 加入参数调整legend位置,(1,1)为右上角

# 绘制year-num数据集(折线图),使用axis_1
axis_2.plot(year, num, color="#6AA84F", label="发生次数", marker="o", linewidth=2)

# 进一步完善axis_2图像信息
axis_2.set_ylabel("发生次数", fontproperties=my_font, size=17, color="#6AA84F")
axis_2.yaxis.set_label_position("left")
axis_2.legend(prop=my_font, loc="right")  # 要在绘图函数中加入label参数

# 两个轴对象共用描述信息
plt.xticks(year)  # 调整坐标轴刻度
plt.title("2008-2022风暴潮危害发生次数及经济损失统计", 
          fontproperties=my_font, size=20, color="m")

三:整体回顾

        以上整个灵感问题来自天马行空,整个调整和解决思路也都是基于个人低水平的角度,感觉整个回顾下来还是有些没有表述清楚,逻辑不太明确,写的也有些冗余,一起交流,共同进步。

物联沃分享整理
物联沃-IOTWORD物联网 » Python使用Matplotlib绘制双(多)Y轴图像详解

发表评论