【问题标题】:pyplot common axes labels for subplotspyplot子图的公共轴标签
【发布时间】:2011-08-05 21:55:47
【问题描述】:

我有以下情节:

import matplotlib.pyplot as plt

fig2 = plt.figure()
ax3 = fig2.add_subplot(2,1,1)
ax4 = fig2.add_subplot(2,1,2)
ax4.loglog(x1, y1)
ax3.loglog(x2, y2)
ax3.set_ylabel('hello')

我希望能够不仅为两个子图中的每一个创建轴标签和标题,而且还可以为跨越两个子图的通用标签创建。例如,由于两个图都有相同的轴,我只需要一组 x 轴和 y 轴标签。不过,我确实希望每个子图都有不同的标题。

我尝试了一些方法,但没有一个能正常工作

【问题讨论】:

    标签: python matplotlib label subplot axes


    【解决方案1】:

    您可以创建一个覆盖两个子图的大子图,然后设置公共标签。

    import random
    import matplotlib.pyplot as plt
    
    x = range(1, 101)
    y1 = [random.randint(1, 100) for _ in range(len(x))]
    y2 = [random.randint(1, 100) for _ in range(len(x))]
    
    fig = plt.figure()
    ax = fig.add_subplot(111)    # The big subplot
    ax1 = fig.add_subplot(211)
    ax2 = fig.add_subplot(212)
    
    # Turn off axis lines and ticks of the big subplot
    ax.spines['top'].set_color('none')
    ax.spines['bottom'].set_color('none')
    ax.spines['left'].set_color('none')
    ax.spines['right'].set_color('none')
    ax.tick_params(labelcolor='w', top=False, bottom=False, left=False, right=False)
    
    ax1.loglog(x, y1)
    ax2.loglog(x, y2)
    
    # Set common labels
    ax.set_xlabel('common xlabel')
    ax.set_ylabel('common ylabel')
    
    ax1.set_title('ax1 title')
    ax2.set_title('ax2 title')
    
    plt.savefig('common_labels.png', dpi=300)
    

    另一种方法是使用 fig.text() 直接设置常用标签的位置。

    import random
    import matplotlib.pyplot as plt
    
    x = range(1, 101)
    y1 = [random.randint(1, 100) for _ in range(len(x))]
    y2 = [random.randint(1, 100) for _ in range(len(x))]
    
    fig = plt.figure()
    ax1 = fig.add_subplot(211)
    ax2 = fig.add_subplot(212)
    
    ax1.loglog(x, y1)
    ax2.loglog(x, y2)
    
    # Set common labels
    fig.text(0.5, 0.04, 'common xlabel', ha='center', va='center')
    fig.text(0.06, 0.5, 'common ylabel', ha='center', va='center', rotation='vertical')
    
    ax1.set_title('ax1 title')
    ax2.set_title('ax2 title')
    
    plt.savefig('common_labels_text.png', dpi=300)
    

    【讨论】:

    • suptitle 函数使用 fig.text() 版本。所以这可能是“官方”的方式吗?
    • 值得强调的是,ax必须在ax1ax2之前创建,否则大情节将掩盖小情节。
    • 如果全局绘图参数包括(可见)网格,则还需要 ax.grid(False) 或 plt.grid(False)。
    • 似乎第一种方法不再适用于最新版本的 matplotplib(我使用 2.0.2):添加到封闭 ax 的标签不可见。
    • 如何将 y_labels 添加到每个单独的子图?
    【解决方案2】:

    一种使用subplots的简单方法:

    import matplotlib.pyplot as plt
    
    fig, axes = plt.subplots(3, 4, sharex=True, sharey=True)
    # add a big axes, hide frame
    fig.add_subplot(111, frameon=False)
    # hide tick and tick label of the big axes
    plt.tick_params(labelcolor='none', top=False, bottom=False, left=False, right=False)
    plt.grid(False)
    plt.xlabel("common X")
    plt.ylabel("common Y")
    

    【讨论】:

    • 如果全局绘图参数包括(可见)网格,则还需要 ax.grid(False) 或 plt.grid(False)。
    • 我正在为 (5, 1) 子图执行此操作,并且我的 ylabel 远离窗口的左边缘,而不是靠近子图。
    • 您获得了赞成票。但请始终解释代码在做什么,附上图片或展示示例,因为它确实需要一些时间来获取它。
    • 使用较新版本的 Matplotlib 将 'off' 更改为 False(我有 2.2.2)
    • 然后你如何添加情节? for ax in axes: ax.plot(x, y) 好像没什么用。
    【解决方案3】:

    plt.setp() 将完成这项工作:

    # plot something
    fig, axs = plt.subplots(3,3, figsize=(15, 8), sharex=True, sharey=True)
    for i, ax in enumerate(axs.flat):
        ax.scatter(*np.random.normal(size=(2,200)))
        ax.set_title(f'Title {i}')
    
    # set labels
    plt.setp(axs[-1, :], xlabel='x axis label')
    plt.setp(axs[:, 0], ylabel='y axis label')
    
    

    【讨论】:

    • 有没有办法用这种方法设置字体大小/粗细?
    • @pfabri ... plt.setp(axs[-1, :], xlabel='x axis label') 仅修改标签文本(显然)。如果你想自定义它的文本参数,例如,ax.set_xlabel(None, size=12, weight='demibold', color='xkcd:lime green', labelpad=0.33)for 循环内。查看matplotlib.text.Text了解更多**kwargs
    【解决方案4】:

    如果您不尝试导出矢量图形或者您已将 matplotlib 后端设置为忽略无色轴,那么 Wen-wei Liao 的回答很好;否则隐藏的轴将显示在导出的图形中。

    我在这里的回答suplabel 类似于使用fig.text 函数的fig.suptitle。因此,没有任何斧头艺术家被创造出来并变得无色。 然而,如果你尝试多次调用它,你会得到一个叠加的文本(就像fig.suptitle 一样)。 Wen-wei Liao 的回答没有,因为fig.add_subplot(111) 将返回相同的 Axes 对象,如果它已经创建。

    我的函数也可以在绘图创建后调用。

    def suplabel(axis,label,label_prop=None,
                 labelpad=5,
                 ha='center',va='center'):
        ''' Add super ylabel or xlabel to the figure
        Similar to matplotlib.suptitle
        axis       - string: "x" or "y"
        label      - string
        label_prop - keyword dictionary for Text
        labelpad   - padding from the axis (default: 5)
        ha         - horizontal alignment (default: "center")
        va         - vertical alignment (default: "center")
        '''
        fig = pylab.gcf()
        xmin = []
        ymin = []
        for ax in fig.axes:
            xmin.append(ax.get_position().xmin)
            ymin.append(ax.get_position().ymin)
        xmin,ymin = min(xmin),min(ymin)
        dpi = fig.dpi
        if axis.lower() == "y":
            rotation=90.
            x = xmin-float(labelpad)/dpi
            y = 0.5
        elif axis.lower() == 'x':
            rotation = 0.
            x = 0.5
            y = ymin - float(labelpad)/dpi
        else:
            raise Exception("Unexpected axis: x or y")
        if label_prop is None: 
            label_prop = dict()
        pylab.text(x,y,label,rotation=rotation,
                   transform=fig.transFigure,
                   ha=ha,va=va,
                   **label_prop)
    

    【讨论】:

    • 这是 imo 的最佳答案。由于 labelpad 选项,它很容易实现并且标签不会重叠。
    【解决方案5】:

    matplotlib 3.4.0 中的新功能

    现在有内置方法来设置公共轴标签:


    重现 OP 的 loglog 图(通用标签但标题不同):

    x = np.arange(0.01, 10.01, 0.01)
    y = 2 ** x
    
    fig, (ax1, ax2) = plt.subplots(2, 1, constrained_layout=True)
    ax1.loglog(y, x)
    ax2.loglog(x, y)
    
    # separate subplot titles
    ax1.set_title('ax1.title')
    ax2.set_title('ax2.title')
    
    # common axis labels
    fig.supxlabel('fig.supxlabel')
    fig.supylabel('fig.supylabel')
    

    【讨论】:

    • 对这个新功能感到非常兴奋,感谢您重新访问此答案并指出!
    【解决方案6】:

    这是一个解决方案,您可以设置其中一个图的 ylabel 并调整它的位置,使其垂直居中。这样可以避免 KYC 提到的问题。

    import numpy as np
    import matplotlib.pyplot as plt
    
    def set_shared_ylabel(a, ylabel, labelpad = 0.01):
        """Set a y label shared by multiple axes
        Parameters
        ----------
        a: list of axes
        ylabel: string
        labelpad: float
            Sets the padding between ticklabels and axis label"""
    
        f = a[0].get_figure()
        f.canvas.draw() #sets f.canvas.renderer needed below
    
        # get the center position for all plots
        top = a[0].get_position().y1
        bottom = a[-1].get_position().y0
    
        # get the coordinates of the left side of the tick labels 
        x0 = 1
        for at in a:
            at.set_ylabel('') # just to make sure we don't and up with multiple labels
            bboxes, _ = at.yaxis.get_ticklabel_extents(f.canvas.renderer)
            bboxes = bboxes.inverse_transformed(f.transFigure)
            xt = bboxes.x0
            if xt < x0:
                x0 = xt
        tick_label_left = x0
    
        # set position of label
        a[-1].set_ylabel(ylabel)
        a[-1].yaxis.set_label_coords(tick_label_left - labelpad,(bottom + top)/2, transform=f.transFigure)
    
    length = 100
    x = np.linspace(0,100, length)
    y1 = np.random.random(length) * 1000
    y2 = np.random.random(length)
    
    f,a = plt.subplots(2, sharex=True, gridspec_kw={'hspace':0})
    a[0].plot(x, y1)
    a[1].plot(x, y2)
    set_shared_ylabel(a, 'shared y label (a. u.)')
    

    【讨论】:

      【解决方案7】:
      # list loss and acc are your data
      fig = plt.figure()
      ax1 = fig.add_subplot(121)
      ax2 = fig.add_subplot(122)
      
      ax1.plot(iteration1, loss)
      ax2.plot(iteration2, acc)
      
      ax1.set_title('Training Loss')
      ax2.set_title('Training Accuracy')
      
      ax1.set_xlabel('Iteration')
      ax1.set_ylabel('Loss')
      
      ax2.set_xlabel('Iteration')
      ax2.set_ylabel('Accuracy')
      

      【讨论】:

        【解决方案8】:

        当 yticks 很大时,其他答案中的方法将无法正常工作。 ylabel 将与刻度重叠,在左侧被剪裁或在图形之外完全不可见/外部。

        我已经修改了 Hagne 的答案,因此它适用于超过 1 列的子图,对于 xlabel 和 ylabel,它会移动图以保持 ylabel 在图中可见。

        def set_shared_ylabel(a, xlabel, ylabel, labelpad = 0.01, figleftpad=0.05):
            """Set a y label shared by multiple axes
            Parameters
            ----------
            a: list of axes
            ylabel: string
            labelpad: float
                Sets the padding between ticklabels and axis label"""
        
            f = a[0,0].get_figure()
            f.canvas.draw() #sets f.canvas.renderer needed below
        
            # get the center position for all plots
            top = a[0,0].get_position().y1
            bottom = a[-1,-1].get_position().y0
        
            # get the coordinates of the left side of the tick labels
            x0 = 1
            x1 = 1
            for at_row in a:
                at = at_row[0]
                at.set_ylabel('') # just to make sure we don't and up with multiple labels
                bboxes, _ = at.yaxis.get_ticklabel_extents(f.canvas.renderer)
                bboxes = bboxes.inverse_transformed(f.transFigure)
                xt = bboxes.x0
                if xt < x0:
                    x0 = xt
                    x1 = bboxes.x1
            tick_label_left = x0
        
            # shrink plot on left to prevent ylabel clipping
            # (x1 - tick_label_left) is the x coordinate of right end of tick label,
            # basically how much padding is needed to fit tick labels in the figure
            # figleftpad is additional padding to fit the ylabel
            plt.subplots_adjust(left=(x1 - tick_label_left) + figleftpad)
        
            # set position of label, 
            # note that (figleftpad-labelpad) refers to the middle of the ylabel
            a[-1,-1].set_ylabel(ylabel)
            a[-1,-1].yaxis.set_label_coords(figleftpad-labelpad,(bottom + top)/2, transform=f.transFigure)
        
            # set xlabel
            y0 = 1
            for at in axes[-1]:
                at.set_xlabel('')  # just to make sure we don't and up with multiple labels
                bboxes, _ = at.xaxis.get_ticklabel_extents(fig.canvas.renderer)
                bboxes = bboxes.inverse_transformed(fig.transFigure)
                yt = bboxes.y0
                if yt < y0:
                    y0 = yt
            tick_label_bottom = y0
        
            axes[-1, -1].set_xlabel(xlabel)
            axes[-1, -1].xaxis.set_label_coords((left + right) / 2, tick_label_bottom - labelpad, transform=fig.transFigure)
        

        它适用于以下示例,而 Hagne 的答案不会绘制 ylabel(因为它在画布之外)并且 KYC 的 ylabel 与刻度标签重叠:

        import matplotlib.pyplot as plt
        import itertools
        
        fig, axes = plt.subplots(3, 4, sharey='row', sharex=True, squeeze=False)
        fig.subplots_adjust(hspace=.5)
        for i, a in enumerate(itertools.chain(*axes)):
            a.plot([0,4**i], [0,4**i])
            a.set_title(i)
        set_shared_ylabel(axes, 'common X', 'common Y')
        plt.show()
        

        或者,如果您对无色轴没问题,我已经修改了 Julian Chen 的解决方案,因此 ylabel 不会与刻度标签重叠。

        基本上,我们只需设置无色的 ylims,使其与子图的最大 ylims 匹配,以便无色刻度标签为 ylabel 设置正确的位置。

        同样,我们必须缩小绘图以防止剪裁。在这里,我已经硬编码了要缩小的数量,但是您可以四处寻找适合您的数字,或者像上面的方法一样计算它。

        import matplotlib.pyplot as plt
        import itertools
        
        fig, axes = plt.subplots(3, 4, sharey='row', sharex=True, squeeze=False)
        fig.subplots_adjust(hspace=.5)
        miny = maxy = 0
        for i, a in enumerate(itertools.chain(*axes)):
            a.plot([0,4**i], [0,4**i])
            a.set_title(i)
            miny = min(miny, a.get_ylim()[0])
            maxy = max(maxy, a.get_ylim()[1])
        
        # add a big axes, hide frame
        # set ylim to match the largest range of any subplot
        ax_invis = fig.add_subplot(111, frameon=False)
        ax_invis.set_ylim([miny, maxy])
        
        # hide tick and tick label of the big axis
        plt.tick_params(labelcolor='none', top=False, bottom=False, left=False, right=False)
        plt.xlabel("common X")
        plt.ylabel("common Y")
        
        # shrink plot to prevent clipping
        plt.subplots_adjust(left=0.15)
        plt.show()
        

        【讨论】:

          猜你喜欢
          • 2011-10-21
          • 1970-01-01
          • 2012-12-15
          • 2020-07-03
          • 2017-05-27
          • 2015-07-21
          • 2017-05-27
          • 2014-05-01
          • 1970-01-01
          相关资源
          最近更新 更多