代码之家  ›  专栏  ›  技术社区  ›  gozzilli

matplotlib子批中的行和列标题

  •  125
  • gozzilli  · 技术社区  · 10 年前

    在循环中生成的子图网格中添加行和列标题的最佳做法是什么 matplotlib ? 我能想到一对,但不是特别整洁:

    1. 对于列,可以使用循环计数器 set_title() 仅用于第一行。对于行,这不起作用。你必须画画 text 在地块之外。
    2. 您可以在顶部添加额外的一行子情节,在左侧添加额外的一栏子情节,并在该子情节的中间绘制文本。

    你能提出更好的选择吗?

    enter image description here

    3 回复  |  直到 10 年前
        1
  •  178
  •   Daniel Chin    2 年前

    有几种方法可以做到这一点。简单的方法是利用图的y标签和标题,然后使用 fig.tight_layout() 为标签腾出空间。或者,您可以使用 annotate 然后半手动为其腾出空间。


    如果轴上没有y标签,很容易利用第一行和第一列轴的标题和y标签。

    import matplotlib.pyplot as plt
    
    cols = ['Column {}'.format(col) for col in range(1, 4)]
    rows = ['Row {}'.format(row) for row in ['A', 'B', 'C', 'D']]
    
    fig, axes = plt.subplots(nrows=4, ncols=3, figsize=(12, 8))
    
    for ax, col in zip(axes[0], cols):
        ax.set_title(col)
    
    for ax, row in zip(axes[:,0], rows):
        ax.set_ylabel(row, rotation=0, size='large')
    
    fig.tight_layout()
    plt.show()
    

    enter image description here


    如果您确实有y标签,或者如果您更喜欢一点灵活性,可以使用 注释 放置标签。这更复杂,但允许您除了行和列标签之外,还可以使用单独的绘图标题、ylabels等。

    import matplotlib.pyplot as plt
    from matplotlib.transforms import offset_copy
    
    
    cols = ['Column {}'.format(col) for col in range(1, 4)]
    rows = ['Row {}'.format(row) for row in ['A', 'B', 'C', 'D']]
    
    fig, axes = plt.subplots(nrows=4, ncols=3, figsize=(12, 8))
    plt.setp(axes.flat, xlabel='X-label', ylabel='Y-label')
    
    pad = 5 # in points
    
    for ax, col in zip(axes[0], cols):
        ax.annotate(col, xy=(0.5, 1), xytext=(0, pad),
                    xycoords='axes fraction', textcoords='offset points',
                    size='large', ha='center', va='baseline')
    
    for ax, row in zip(axes[:,0], rows):
        ax.annotate(row, xy=(0, 0.5), xytext=(-ax.yaxis.labelpad - pad, 0),
                    xycoords=ax.yaxis.label, textcoords='offset points',
                    size='large', ha='right', va='center')
    
    fig.tight_layout()
    # tight_layout doesn't take these labels into account. We'll need 
    # to make some room. These numbers are are manually tweaked. 
    # You could automatically calculate them, but it's a pain.
    fig.subplots_adjust(left=0.15, top=0.95)
    
    plt.show()
    

    enter image description here

        2
  •  3
  •   Alan Shteyman    8 年前

    以上答案有效。只是在答案的第二个版本中,你有:

    for ax, row in zip(axes[:,0], rows):
        ax.annotate(col, xy=(0, 0.5), xytext=(-ax.yaxis.labelpad-pad,0),
                    xycoords=ax.yaxis.label, textcoords='offset points',
                    size='large', ha='right', va='center')
    

    而不是:

    for ax, row in zip(axes[:,0], rows):
        ax.annotate(row,xy=(0, 0.5), xytext=(-ax.yaxis.labelpad-pad,0),                    
                    xycoords=ax.yaxis.label, textcoords='offset points',
                    size='large', ha='right', va='center')
    
        3
  •  3
  •   paime    2 年前

    基于Joe Kington's answer ,我提出了一个可以在代码库中重用的函数:

    它接受以下参数:

    • fig :包含要处理的轴的图形
    • row_headers , col_headers :要作为标头的字符串序列
    • row_pad , col_pad : int 调整填充的值
    • rotate_row_headers :是否将行标题旋转90°
    • **text_kwargs :转发至 ax.annotate(...)

    此处为函数,示例如下:

    import numpy as np
    
    def add_headers(
        fig,
        *,
        row_headers=None,
        col_headers=None,
        row_pad=1,
        col_pad=5,
        rotate_row_headers=True,
        **text_kwargs
    ):
        # Based on https://stackoverflow.com/a/25814386
    
        axes = fig.get_axes()
    
        for ax in axes:
            sbs = ax.get_subplotspec()
    
            # Putting headers on cols
            if (col_headers is not None) and sbs.is_first_row():
                ax.annotate(
                    col_headers[sbs.colspan.start],
                    xy=(0.5, 1),
                    xytext=(0, col_pad),
                    xycoords="axes fraction",
                    textcoords="offset points",
                    ha="center",
                    va="baseline",
                    **text_kwargs,
                )
    
            # Putting headers on rows
            if (row_headers is not None) and sbs.is_first_col():
                ax.annotate(
                    row_headers[sbs.rowspan.start],
                    xy=(0, 0.5),
                    xytext=(-ax.yaxis.labelpad - row_pad, 0),
                    xycoords=ax.yaxis.label,
                    textcoords="offset points",
                    ha="right",
                    va="center",
                    rotation=rotate_row_headers * 90,
                    **text_kwargs,
                )
    

    以下是在标准网格上使用的示例(没有轴跨越多行/列):

    import random
    import matplotlib.pyplot as plt
    
    mosaic = [
        ["A0", "A1", "A2"],
        ["B0", "B1", "B2"],
    ]
    row_headers = ["Row A", "Row B"]
    col_headers = ["Col 0", "Col 1", "Col 2"]
    
    subplots_kwargs = dict(sharex=True, sharey=True, figsize=(10, 6))
    fig, axes = plt.subplot_mosaic(mosaic, **subplots_kwargs)
    
    font_kwargs = dict(fontfamily="monospace", fontweight="bold", fontsize="large")
    add_headers(fig, col_headers=col_headers, row_headers=row_headers, **font_kwargs)
    
    plt.show()
    

    result: regular grid

    如果某些轴跨越多行/列,那么正确分配行/列标题就不那么简单了。 我没有设法从函数内部进行排序,但要小心给定的 行标头(_H) 冷水龙头 参数足以使其轻松工作:

    mosaic = [
        ["A0", "A1", "A1", "A2"],
        ["A0", "A1", "A1", "A2"],
        ["B0", "B1", "B1", "B2"],
    ]
    
    row_headers = ["A", "A", "B"]  # or
    row_headers = ["A", None, "B"]  # or
    row_headers = {0: "A", 2: "B"}
    
    col_headers = ["0", "1", "1", "2"]  # or
    col_headers = ["0", "1", None, "2"]  # or
    col_headers = {0: "0", 1: "1", 3: "2"}
    
    fig, axes = plt.subplot_mosaic(mosaic, **subplots_kwargs)
    add_headers(fig, col_headers=col_headers, row_headers=row_headers, **font_kwargs)
    plt.show()
    

    result: non-regular grid