【问题标题】:Annotating subplots in matplotlib scales the figure to the largest axes在 matplotlib 中注释子图将图形缩放到最大轴
【发布时间】:2020-04-07 01:34:27
【问题描述】:

当我用 5 个子图制作图形并注释每个子图中的条时,matplotlib 似乎会缩放图形,以便从最大的 y 轴缩放到最小的 y 轴。

我不能很好地描述这个问题,但请看这张图片:

图形应该开始的位置上方有大量空白。

但是,理想情况下,该图应如下所示

当我将 4 个最小轴设置为与最大轴具有相同的 y 上限时,图形会正确缩放,但出于可视化的目的,我不希望这样做。

为什么会这样?有没有办法控制这个数字,使它不会像第一张图片那样自动缩放?或者,一种更合适的方式来绘制我希望实现的目标?

我用来生成图形的代码:

import numpy as np
from matplotlib import pyplot as plt
from matplotlib.patches import Patch
from matplotlib import rcParams
rcParams['font.family'] = 'sans-serif'
rcParams['font.sans-serif'] = ['Arial']
department = ["100", "1,000", "10,000", \
              "100,000", "1,000,000"]
quarter = ["Serial", "MPI", "CUDA", "Hybrid"]
budgets = np.array([[0.049979, 0.43584,  2.787366, 19.75062, 201.6935],\
                    [2.184624, 0.175213, 0.677837, 5.265575, 46.33678],\
                    [0.050294, 0.068537, 0.23739,  1.93778,  18.55734],\
                    [3.714284, 3.9917,   4.977599, 6.174967, 37.732232]])

budgets = np.transpose(budgets)
em = np.zeros((len(department), len(quarter)))

# set up barchart
x = np.arange(len(department)) # label locations
width = 0.8    # width of all the bars

# set up figure
fig, (ax1, ax2, ax3, ax4, ax5) = plt.subplots(1, 5)
axes = [ax1, ax2, ax3, ax4, ax5]

# generate bars
rects = []
color = ["tomato", "royalblue", "limegreen", "orange"]
n = len(quarter)
for i in range(n):
    bar_x = x - width/2.0 + i/float(n)*width + width/(n*2)

    m = len(budgets[:,i])
    for j in range(m):
        bar_x = x[j] - width/2.0 + i/float(n)*width + width/(n*2)
        e = budgets[j,i]
        #bar_x = x - width/2.0 + i/float(n)*width + width/(n*2)
        rects.append(axes[j].bar(bar_x, e, width=width/float(n), \
                label=quarter[i], color=color[i]))

# set figure properties
fig.set_size_inches(12, 2.5)
fig.tight_layout(rect=[0, 0.03, 1, 0.95])
nAx = len(axes)
for i in range(nAx):
    #axes[i].set_aspect("auto")
    axes[i].tick_params(axis='x', which='both', bottom=False, top=False, 
                        labelbottom=False)

ax1.set_ylabel("Time (ms)")
for i in range(nAx):
    axes[i].yaxis.grid(which="major", color="white", lw=0.75)
ax1.set_ylim([0, 4])

fig.suptitle("Time per iteration for differing dataset sizes")   # title

for i in range(nAx):
    axes[i].set_xlabel(department[i])

# annotate bars
for i in range(nAx):
    for rect in rects:
        j = 0;
        for bar in rect:
            y_bottom, y_top = axes[i].get_ylim() # axis limits

            height = bar.get_height() # bar's height

            va = 'bottom'
            offset = 3
            color = 'k'
            fg = 'w'

            # keep label within plot
            if (y_top < 1.1 * height):
                offset = -3
                va = 'top'
                color='w'
                fg = 'k'

            # annotate the bar
            axes[i].annotate('{:.2f}'.format(height),
                              xy=(bar.get_x() + bar.get_width()/2, height),
                              xytext=(0,offset),
                              textcoords="offset points",
                              ha='center', va=va, color=color)


# set custom legend
legend_elements = [Patch(facecolor='tomato', label='Serial'),
                   Patch(facecolor='royalblue', label='MPI'),
                   Patch(facecolor='limegreen', label='CUDA'),
                   Patch(facecolor='orange', label='Hybrid')]
plt.legend(handles=legend_elements, loc="upper center", fancybox=False, 
           edgecolor='k', ncol=4, bbox_to_anchor=(-2, -0.1))

plt.show()

【问题讨论】:

  • 您好,我能够按照您在代码中指定的方式生成图表。您使用的是哪个版本的matplotlib? (我用3.2.1
  • @l_l_l_l_l_l_l_l,我使用的是3.1.3,但刚刚更新为3.2.1。同样的问题仍在发生。我能够生成图表,但不能没有空格。
  • hm,如果我无法重现该问题,我将无能为力,但是如果您使用 sharey=True 创建您的 subplots 怎么办?
  • 另外,删除tight_layout 电话会改变情况吗?
  • 哦,没关系,我知道sharey 对你来说是个糟糕的选择

标签: python matplotlib data-visualization


【解决方案1】:

这是部分答案。

这可能是一个错误,因为在我切换到 Debian 系统中的 Jupyter 笔记本之前我无法重现该问题(也有不同的硬件)。您的图形在我的 macOS Jupyter 笔记本中正确绘制,在 Debian 中从 .py 脚本显示时正确绘制。

问题似乎出在您的注释上。如果您在注解后调用tight_layout,您可能会收到如下警告:

<ipython-input-80-f9f592f5efc5>:88: UserWarning: Tight layout not applied. The bottom and top margins cannot be made large enough to accommodate all axes decorations. 
  fig.tight_layout(rect=[0, 0.03, 1, 0.95])

看起来annotate 函数正在为您的注释计算一些完全古怪的坐标,尽管文本最终出现在正确的位置。如果删除它们,空白区域就会消失。您可以尝试为您的注释计算 xy 坐标 a different way。这可能会让你开始:

        axes[i].annotate('{:.2f}'.format(height),
                          xy=(bar.get_x() + bar.get_width()/2, height),
                          xytext=(0,offset),
                          textcoords="offset points",
                          xycoords="axes points", # change
                          ha='center', va=va, color=color)

输出:

要正确计算点数,您可以尝试使用适当的轴transformation,但我还是无法让它工作,这可能与错误有关。

【讨论】:

    【解决方案2】:

    尝试将fig.tight_layout(rect=[0, 0.03, 1, 0.95]) 放在所有绘图命令之后,如下所示。

    import numpy as np
    from matplotlib import pyplot as plt
    from matplotlib.patches import Patch
    from matplotlib import rcParams
    rcParams['font.family'] = 'sans-serif'
    rcParams['font.sans-serif'] = ['Arial']
    department = ["100", "1,000", "10,000", \
                  "100,000", "1,000,000"]
    quarter = ["Serial", "MPI", "CUDA", "Hybrid"]
    budgets = np.array([[0.049979, 0.43584,  2.787366, 19.75062, 201.6935],\
                        [2.184624, 0.175213, 0.677837, 5.265575, 46.33678],\
                        [0.050294, 0.068537, 0.23739,  1.93778,  18.55734],\
                        [3.714284, 3.9917,   4.977599, 6.174967, 37.732232]])
    
    budgets = np.transpose(budgets)
    em = np.zeros((len(department), len(quarter)))
    
    # set up barchart
    x = np.arange(len(department)) # label locations
    width = 0.8    # width of all the bars
    
    # set up figure
    fig, (ax1, ax2, ax3, ax4, ax5) = plt.subplots(1, 5)
    axes = [ax1, ax2, ax3, ax4, ax5]
    
    # generate bars
    rects = []
    color = ["tomato", "royalblue", "limegreen", "orange"]
    n = len(quarter)
    for i in range(n):
        bar_x = x - width/2.0 + i/float(n)*width + width/(n*2)
    
        m = len(budgets[:,i])
        for j in range(m):
            bar_x = x[j] - width/2.0 + i/float(n)*width + width/(n*2)
            e = budgets[j,i]
            #bar_x = x - width/2.0 + i/float(n)*width + width/(n*2)
            rects.append(axes[j].bar(bar_x, e, width=width/float(n), \
                    label=quarter[i], color=color[i]))
    
    # set figure properties
    fig.set_size_inches(12, 2.5)
    #fig.tight_layout(rect=[0, 0.03, 1, 0.95])
    nAx = len(axes)
    for i in range(nAx):
        #axes[i].set_aspect("auto")
        axes[i].tick_params(axis='x', which='both', bottom=False, top=False, 
                            labelbottom=False)
    
    ax1.set_ylabel("Time (ms)")
    for i in range(nAx):
        axes[i].yaxis.grid(which="major", color="white", lw=0.75)
    ax1.set_ylim([0, 4])
    
    fig.suptitle("Time per iteration for differing dataset sizes")   # title
    
    for i in range(nAx):
        axes[i].set_xlabel(department[i])
    
    # annotate bars
    for i in range(nAx):
        for rect in rects:
            j = 0;
            for bar in rect:
                y_bottom, y_top = axes[i].get_ylim() # axis limits
    
                height = bar.get_height() # bar's height
    
                va = 'bottom'
                offset = 3
                color = 'k'
                fg = 'w'
    
                # keep label within plot
                if (y_top < 1.1 * height):
                    offset = -3
                    va = 'top'
                    color='w'
                    fg = 'k'
    
                # annotate the bar
                axes[i].annotate('{:.2f}'.format(height),
                                  xy=(bar.get_x() + bar.get_width()/2, height),
                                  xytext=(0,offset),
                                  textcoords="offset points",
                                  ha='center', va=va, color=color)
    
    
    # set custom legend
    legend_elements = [Patch(facecolor='tomato', label='Serial'),
                       Patch(facecolor='royalblue', label='MPI'),
                       Patch(facecolor='limegreen', label='CUDA'),
                       Patch(facecolor='orange', label='Hybrid')]
    plt.legend(handles=legend_elements, loc="upper center", fancybox=False, 
               edgecolor='k', ncol=4, bbox_to_anchor=(-2, -0.1))
    
    fig.tight_layout(rect=[0, 0.03, 1, 0.95])
    
    plt.show()
    

    【讨论】:

    • 试试这个,我收到警告Tight layout not applied. The bottom and top margins cannot be made large enough to accommodate all axes decorations
    猜你喜欢
    • 1970-01-01
    • 2012-07-17
    • 1970-01-01
    • 1970-01-01
    • 2017-12-07
    • 1970-01-01
    • 2017-12-28
    • 2020-10-23
    • 1970-01-01
    相关资源
    最近更新 更多