简体   繁体   English

matplotlib 子图中的行和列标题

[英]Row and column headers in matplotlib's subplots

What's the best practise to add a row and a column header to a grid of subplots generated in a loop in matplotlib ?将一行和一列 header 添加到matplotlib的循环中生成的子图网格的最佳实践是什么? I can think of a couple, but not particularly neat:我能想到一对,但不是特别整洁:

  1. For columns, with a counter to your loop you can use set_title() for the first row only.对于列,使用循环计数器,您只能将set_title()用于第一行。 For rows this doesn't work.对于行,这不起作用。 You would have to draw text outside of the plots.您必须在绘图之外绘制text
  2. You add an extra row of subplots on top and an extra column of subplots on the left, and draw text in the middle of that subplot.您在顶部添加额外的一行子图,在左侧添加一列额外的子图,并在该子图的中间绘制文本。

Can you suggest a better alternative?你能推荐一个更好的选择吗?

在此处输入图像描述

There are several ways to do this. 有几种方法可以做到这一点。 The easy way is to exploit the y-labels and titles of the plot and then use fig.tight_layout() to make room for the labels. 简单的方法是利用绘图的y标签和标题,然后使用fig.tight_layout()为标签腾出空间。 Alternatively, you can place additional text in the right location with annotate and then make room for it semi-manually. 或者,您可以使用annotate在正确的位置放置其他文本,然后半手动为其腾出空间。


If you don't have y-labels on your axes, it's easy to exploit the title and y-label of the first row and column of axes. 如果您的轴上没有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()

在此输入图像描述


If you do have y-labels, or if you prefer a bit more flexibility, you can use annotate to place the labels. 如果您有y标签,或者您更喜欢灵活性,则可以使用annotate来放置标签。 This is more complicated, but allows you to have individual plot titles, ylabels, etc in addition to the row and column labels. 这更复杂,但除了行和列标签之外,还允许您拥有单独的绘图标题,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()

在此输入图像描述

Based on Joe Kington's answer , I put up a function that can be reused across a code base:根据 Joe Kington 的回答,我提出了一个 function 可以在代码库中重用:

It accepts as arguments:它接受为 arguments:

  • fig : The figure which contains the axes to work on fig : 包含要处理的轴的图
  • row_headers , col_headers : a sequence of strings to be headers row_headerscol_headers :作为标题的字符串序列
  • row_pad , col_pad : int value to adjust padding row_pad , col_pad : 调整填充的int
  • rotate_row_headers : whether to rotate by 90° the row headers rotate_row_headers : 是否将行标题旋转 90°
  • **text_kwargs : forwarded to ax.annotate(...) **text_kwargs : 转发到ax.annotate(...)

Function here, examples below: Function 在这里,示例如下:

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,
            )

Here is an example of using it using on a standard grid (no axes spans multiple rows / cols):这是在标准网格上使用它的示例(没有轴跨越多行/列):

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()

结果:规则网格

If some axes spans multiple rows / cols, it gets a bit less straightforward to assign rows / cols headers correctly.如果某些轴跨越多个行/列,则正确分配行/列标题会变得不那么简单。 I didn't managed to sort it out from inside the function, but being careful to the given row_headers and col_headers arguments is enough to make it work easily:我没有设法从 function 内部对其进行分类,但要小心给定的row_headerscol_headers arguments 足以使其轻松工作:

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()

结果:非规则网格

The above answer works. 以上答案有效。 Just not that in the second version of the answer, you have: 只是不是在答案的第二个版本中,你有:

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')

instead of: 代替:

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')

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM