matplotlib的子图中的行和列标题

idv4meu8  于 2023-08-06  发布在  其他
关注(0)|答案(4)|浏览(106)

matplotlib的循环中生成的子图网格中添加行和列标题的最佳实践是什么?我能想到几个,但不是特别整洁:
1.对于列,如果循环中有一个计数器,则只能对第一行使用set_title()。对于行,这不起作用。您必须在图的外部绘制text
1.在顶部添加额外一行子图,在左侧添加额外一列子图,并在该子图的中间绘制文本。
你能建议一个更好的选择吗?


的数据

92vpleto

92vpleto1#

有几种方法可以做到这一点。简单的方法是利用图的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()

字符串


的数据
如果您确实有y标签,或者如果您希望更灵活一点,您可以使用annotate来放置标签。这是更复杂的,但允许您有单独的情节标题,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()


pnwntuvh

pnwntuvh2#

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

  • fig:包含要处理的轴的图形
  • row_headerscol_headers:作为标头的字符串序列
  • row_padcol_padint调整填充的值
  • 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()


x1c 0d1x的数据
如果某些轴跨越多个行/列,则正确分配行/列标题就不那么简单了。我没有设法从函数内部整理出来,但是小心给定的row_headerscol_headers参数足以使它轻松工作:

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


jutyujz0

jutyujz03#

上述答案是可行的。只是在第二个版本的答案中,你没有想到:

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

ny6fqffe

ny6fqffe4#

作为Joe答案的扩展,您可以使用Y标签方法,即使是使用现有的Y标签,我发现这比使用annotate容易得多,特别是当轴不在规则网格上时。
为此,您可以twin轴并将其向左移动:

import matplotlib.pyplot as plt

fig, axes = plt.subplots(nrows=3, ncols=3,
                         sharex=True, sharey=True)

cols = [1, 2, 3]
rows = ['A', 'B', 'C']

# set existing Y label
axes[0][0].set_ylabel('Y label')

for c, ax in zip(cols, axes[0]):
    ax.set_title(c, size='large')

for r, ax in zip(rows, axes[:, 0]):
    ax2 = ax.twinx()
    # move extra axis to the left, with offset
    ax2.yaxis.set_label_position('left')
    ax2.spines['left'].set_position(('axes', -0.4))
    # hide spine and ticks, set group label
    ax2.spines['left'].set_visible(False)
    ax2.set_yticks([])
    ax2.set_ylabel(r, rotation=0, size='large',
                   ha='right', va='center')

字符串
当然,如果你也有现有的标题,你可以对X轴做同样的事情,在顶部添加额外的标签。
输出量:


的数据
非规则网格上的输出:


相关问题