Python - Matplot 绘制多图 直方图和折线图并存 共用 X 轴

引言

前面一篇文章 Python 绘制直方图 Matplotlib Pyplot figure bar legend gca text 有介绍如何用 Python 绘制直方图,但现实应用中需要在直方图的基础上再绘制折线图。比如测试报告中,就可以用图形反映测试用例的运行情况,直方图可以反映测试用例 pass 了多少,失败了多少,如果想反映 pass rate 趋势,就可以用折线图,本文介绍如何绘制多个图,以及最后应用在同一图上绘制直方图和折线图并存。

内容提要:

  1. subplot 和 subplots 函数
  2. twinx 函数
  3. 直方图和折线图并存例子

subplot 和 subplots 函数

plt.figure 的作用是定义一个大的图纸,可以设置图纸的大小、分辨率等。
例如:
初始化一张画布
fig = plt.figure(figsize=(7,10),dpi=100)

直接在当前活跃的的 axes 上面作图,注意是当前活跃的
plt.plot()plt.bar()

那么现在来看 subplotsubplots ,两者的区别在于 suplots 绘制多少图已经指定了,所以 ax 提前已经准备好了,而 subplot 函数调用一次就绘制一次,没有指定。

subplot 函数

subplot 函数是添加一个指定位置的子图到当前画板中。

matplotlib.pyplot.subplot(*args, **kwargs)

函数签名:

subplot(nrows, ncols, index, **kwargs) 括号里的数值依次表示行数、列数、第几个
subplot(pos, **kwargs)
subplot(ax) 这个我没用应用成功

如果需要自定义画板大小,使用 subplot 这个函数时需要先定义一个自定义大小的画板,因为 subplot 函数无法更改画板的大小和分辨率等信息;所以必须通过 fig = plt.figure(figsize=(12, 4), dpi=200) 来定义画板相关设置;不然就按默认画板的大小; 同时,后续对于这个函数便捷的操作就是直接用 plt,获取当前活跃的图层。

例如:添加 4 个子图
没有自定义画板,所以是在默认大小的画板上添加子图的。
plt.subplot(221) 中的 221 表示子图区域划分为 2 行 2 列,取其第一个子图区域。子区编号从左上角为 1 开始,序号依次向右递增。

import matplotlib.pyplot as plt

    # equivalent but more general than plt.subplot(221)
    ax1 = plt.subplot(2, 2, 1)

    # add a subplot with no frame
    ax2 = plt.subplot(222, frameon=False)

    # add a polar subplot
    plt.subplot(223, projection='polar')

    # add a red subplot that shares the x-axis with ax1
    plt.subplot(224, sharex=ax1, facecolor='red')

    plt.show()

效果:
在这里插入图片描述

subplots 函数

subplots 函数主要是创建一个画板和一系列子图。

matplotlib.pyplot.subplots(nrows=1, ncols=1, sharex=False, sharey=False, squeeze=True, subplot_kw=None, gridspec_kw=None, **fig_kw)

该函数返回两个变量,一个是 Figure 实例,另一个 AxesSubplot 实例 。Figure 代表整个图像也就是画板整个容器,AxesSubplot 代表坐标轴和画的子图,通过下标获取需要的子区域。

例 1:一个画板中只有 1 个子图
plt.subplots() 默认 1 行 1 列

	import numpy as np
    import matplotlib.pyplot as plt
    # First create some toy data:
    x = np.linspace(0, 2*np.pi, 400)
    y = np.sin(x**2)
    # Creates just a figure and only one subplot
    fig, ax = plt.subplots()
    ax.plot(x, y)
    ax.set_title('Simple plot')
    plt.show()

在这里插入图片描述
例 2:一个画板中只有 2 个子图
plt.subplots(1, 2)1 行 2 列

	import numpy as np
    import matplotlib.pyplot as plt

    # First create some toy data:
    x = np.linspace(0, 2*np.pi, 400)
    y = np.sin(x**2)

    # Creates two subplots and unpacks the output array immediately
    f, (ax1, ax2) = plt.subplots(1, 2, sharey=True)
    ax1.plot(x, y)
    ax1.set_title('Sharing Y axis')
    ax2.scatter(x, y)
    plt.show()

在这里插入图片描述
例 3:一个画板中只有 4 个子图
plt.subplots(2, 2)2 行 2 列
通过索引来访问子图:
axes[0, 0] 第一个图位置
axes[0, 1] 第二个图位置
axes[1, 0] 第三个图位置
axes[1, 1] 第四个图位置

	import numpy as np
    import matplotlib.pyplot as plt

    # First create some toy data:
    x = np.linspace(0, 2*np.pi, 400)
    y = np.sin(x**2)

    # Creates four polar axes, and accesses them through the returned array
    fig, axes = plt.subplots(2, 2, subplot_kw=dict(polar=True))
    axes[0, 0].plot(x, y)
    axes[1, 1].scatter(x, y)
    plt.show()

在这里插入图片描述
例 4:子图共享 X 轴

sharex=‘col’

   	import numpy as np
    import matplotlib.pyplot as plt

    # Share a X axis with each column of subplots
    plt.subplots(2, 2, sharex='col')
    plt.show()

在这里插入图片描述
例 5:子图共享 Y 轴

sharey=‘row’

   	import numpy as np
    import matplotlib.pyplot as plt

    # Share a X axis with each column of subplots
    plt.subplots(2, 2, sharey='row')
    plt.show()

在这里插入图片描述
例 6:子图共享 X 和 Y 轴
sharex=‘all’, sharey=‘all’

   	import numpy as np
    import matplotlib.pyplot as plt

    # Share a X axis with each column of subplots
    plt.subplots(2, 2, sharex='all', sharey='all')
    # Note that this is the same as
    # plt.subplots(2, 2, sharex=True, sharey=True)
    plt.show()

在这里插入图片描述
例 7:创建制定编号的画图
编号为 10 的画板,如果该编号已经存在,则删除编号。

   	import numpy as np
    import matplotlib.pyplot as plt

	# Creates figure number 10 with a single subplot
    # and clears it if it already exists.
    fig, ax=plt.subplots(num=10, clear=True)
    plt.show()

在这里插入图片描述

twinx 函数

了解了 subplots 函数,对 twinx 函数的理解就容易多了。

twinx 返回一个新的子图并共用当前子图的 X 轴。新的子图会覆盖 X 轴(如果新子图 X 轴是 None 就会用当前子图的 X 轴数据),其 Y 轴会在右侧。同理 twiny 函数是共享 Y 轴的。

例:我们先画了一个红色线子图,再画一个蓝色线子图,蓝色线子图是共用红色线子图的 X 轴的,那么就可以实样实现:

fig, ax1 = plt.subplots() 生成一个画板 fig,并默认第一个子图 ax1.
ax1.plot(t, data1, color=color) ax1 第一子图区域画折线图
ax2 = ax1.twinx() 生成一个新的子图 ax2 ,并共享 ax1 第一子图的 X 轴
ax2.plot(t, data2, color=color) 在新的子图 ax2 区域画折线图

效果图:

在这里插入图片描述

代码:

	import numpy as np
    import matplotlib.pyplot as plt

    # Create some mock data
    t = np.arange(0.01, 10.0, 0.01)
    data1 = np.exp(t)
    data2 = np.sin(2 * np.pi * t)

    fig, ax1 = plt.subplots()

    color = 'tab:red'
    ax1.set_xlabel('time (s)')
    ax1.set_ylabel('exp', color=color)
    ax1.plot(t, data1, color=color)
    ax1.tick_params(axis='y', labelcolor=color)

    ax2 = ax1.twinx()  # instantiate a second axes that shares the same x-axis

    color = 'tab:blue'
    ax2.set_ylabel('sin', color=color)  # we already handled the x-label with ax1
    ax2.plot(t, data2, color=color)
    ax2.tick_params(axis='y', labelcolor=color)

    fig.tight_layout()  # otherwise the right y-label is slightly clipped
    plt.show()

应用:直方图和折线图并存

有了前面的基础,我们来画一个直方图和折线图并存。基于前面一篇文章 Python 绘制直方图 Matplotlib Pyplot figure bar legend gca text 直方图的例子基础上再画折线图。

应用场景是测试用例运行结果图,直方图描述 Smoke 和 Regressin 用例pass 和 fail 数量,折线图描述总的用例 Pass Rate。把 Mock 的数字换一下就可以直接应用了,具体代码细节就不详细介绍了,可以参考 文章 Python 绘制直方图 Matplotlib Pyplot figure bar legend gca text

效果图:
在这里插入图片描述

代码:

import matplotlib.pyplot as plt
import matplotlib.font_manager as font_manager
import numpy as np
from matplotlib.ticker import FuncFormatter

def to_percent(num, position):
    return str(num) + '%'
    
def draw_trend_image_test(reg_total_count_pass, reg_total_count_fail, smoke_total_count_pass, smoke_total_count_fail, date_list, image_file_name = 'test_result_trend.png'):
    
    # attributes
    bar_width = 0.3
    regression_pass_color = '#0ac00a'
    smoke_pass_color = '#068606'
    font_name = 'Calibri'
    label_size = 10
    text_size = 8
    title_size = 14

    # set the color for failure
    smoke_fail_color = []
    for item in smoke_total_count_fail:
       if item > 0:
           smoke_fail_color.append("red")
       else:
           smoke_fail_color.append(smoke_pass_color)   

    reg_fail_color = []
    for item in reg_total_count_fail:
       if item > 0:
           reg_fail_color.append("red")
       else:
           reg_fail_color.append(regression_pass_color)   

    total_pass_rate = [round((s_p+r_p)/(s_p+r_p+s_f+r_f), 2)*100 for s_p, r_p, s_f, r_f in zip(
            smoke_total_count_pass, reg_total_count_pass, smoke_total_count_fail, reg_total_count_fail)]       

    if len(date_list) > 5:
        fig, ax1 = plt.subplots(figsize=(10.8, 4.8))
    else:
        fig, ax1 = plt.subplots()
         
    # draw bar
    x = np.arange(len(date_list))
    ax1.bar(x - bar_width/2, smoke_total_count_pass, color=smoke_pass_color, edgecolor=smoke_pass_color, width= bar_width, label="Smoke Passed")
    ax1.bar(x - bar_width/2, smoke_total_count_fail, color="red", edgecolor=smoke_fail_color, width= bar_width, bottom=smoke_total_count_pass)
    ax1.bar(x + bar_width/2, reg_total_count_pass, color=regression_pass_color, edgecolor=regression_pass_color, width= bar_width, label="Regression Passed")
    ax1.bar(x + bar_width/2, reg_total_count_fail, color="red", edgecolor=reg_fail_color, width= bar_width, label="Failed", bottom=reg_total_count_pass)
  
    # set title, labels
    ax1.set_title("Test Result Trend", fontsize=title_size, fontname=font_name)
    ax1.set_xticks(x, date_list,fontsize=label_size, fontname=font_name)
    ax1.set_ylabel("Count",fontsize=label_size, fontname=font_name)

    # set bar text
    for i in x:
        if smoke_total_count_fail[i] > 0:
            ax1.text(i-bar_width/2, smoke_total_count_fail[i] + smoke_total_count_pass[i], smoke_total_count_fail[i],horizontalalignment = 'center', verticalalignment='bottom',fontsize=text_size,family=font_name,color='red',weight='bold')   
          
        ax1.text(i-bar_width, smoke_total_count_pass[i], smoke_total_count_pass[i],horizontalalignment = 'right', verticalalignment='top',fontsize=text_size,family=font_name,color=smoke_pass_color,weight='bold')
        ax1.text(i, reg_total_count_pass[i], reg_total_count_pass[i], horizontalalignment = 'right', verticalalignment='top',fontsize=text_size,family=font_name,color=regression_pass_color,weight='bold')
 
        if reg_total_count_fail[i] > 0:
            ax1.text(i+ bar_width/2, reg_total_count_fail[i] + reg_total_count_pass[i], reg_total_count_fail[i],horizontalalignment = 'center', verticalalignment='bottom',fontsize=text_size,family=font_name,color='red',weight='bold')   

    # draw plot
    ax2 = ax1.twinx()
    ax2.plot(x, total_pass_rate, label='Total Pass Rate',
    linewidth=2, color='#FFB90F')
    ax2.yaxis.set_major_formatter(FuncFormatter(to_percent))
    ax2.set_ylim(0, 100)
    ax2.set_ylabel("Percent",fontsize=label_size, fontname=font_name)
    # plt.show() # should comment it if save the pic as a file, or the saved pic is blank
    # dpi: image resolution, better resolution, bigger image size
    legend_font = font_manager.FontProperties(family=font_name, weight='normal',style='normal', size=label_size)    
    fig.legend(loc="lower center", ncol=4, frameon=False, prop=legend_font)
    fig.savefig(image_file_name,dpi = 100)
    
if  __name__ == '__main__':
    reg_total_count_pass = [916,916,916,906,916,716,916,916,916,916]
    reg_total_count_fail = [73,73,73,83,73,273,73,73,73,73]
    smoke_total_count_pass = [420, 420, 420,420, 420, 400,420, 420, 420,420] 
    smoke_total_count_fail = [5,5,5,5,5,25,5,5,5,5]
    date_list = ['2022/1/1', '2022/1/2', '2022/1/3','2022/1/4','2022/1/5', '2022/1/6', '2022/1/7','2022/1/8', '2022/1/9', '2022/1/10']
    draw_trend_image_test(reg_total_count_pass, reg_total_count_fail, smoke_total_count_pass, smoke_total_count_fail, date_list)
  • 8
    点赞
  • 67
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值