matplotlib 上带括号的标签垂直分组

jea*_*ean 3 python plot matplotlib seaborn

我想显示垂直括号以将图左侧的 ylabel 分组,例如将前 3 个变量分组在一起,然后将后面的两个变量分组。

通过使用 fancyarrow 仔细调整注释的 xy 和 xytest 的位置,我可以设法使用 annotate 和 fancyarrows 获得某种垂直括号,但是在不破坏对齐的情况下放置它们非常麻烦,几乎无法撤消。这是因为这个过程同时非常痛苦和脆弱,因为不能将 xy 和 xytest 放置在同一水平线上,因为角度取决于文本大小和括号的长度。另外,在我的示例中,随着旋转,很难管理支架的方向,由于某些原因,支架的方向被颠倒了。

这是我非常糟糕的不成功的尝试:

import matplotlib.pyplot as plt
import seaborn as sns
import pandas as pd
import numpy as np
from matplotlib.pyplot import figure
figure(figsize=(10, 5), dpi=50)

np.random.seed(42)
df = pd.DataFrame({f"var{i}": np.random.uniform(-1., 1., 2) for i in range(10)})
df["facet"] = [0, 1]
# Vertical plot
df = df.melt('facet', var_name='Variable',  value_name='values')
ax = sns.barplot(data=df, x="values", y="Variable")

ylims = ax.get_ylim()
xlims = ax.get_xlim()
xmin = min(xlims)
xmax = max(xlims)
ymin = min(ylims)
ymax = max(ylims)

ax.annotate('Group1 vertical', xytext=(1.4 * xmin, ymax / 2), xy=(1.2 * xmin, ymax / 2 - (ymax - ymin)/7.5),
            fontsize=10, ha='center', va='bottom',
            bbox=dict(boxstyle='square', fc='white'),
            arrowprops=dict(arrowstyle=f'-[, widthB=10, lengthB={0.5 * xmin}', lw=2.0), rotation=90, clip_on=False, annotation_clip=False)

ax.annotate('Group2 vertical', xytext=(1.4 * xmin, ymax * 4 / 5), xy=(1.2 * xmin, ymax * 4 / 5 - (ymax - ymin)/7.5),
            fontsize=10, ha='center', va='bottom',
            bbox=dict(boxstyle='square', fc='white'),
            arrowprops=dict(arrowstyle=f'-[, widthB=5, lengthB={0.5 * xmin}', lw=2.0), rotation=90, clip_on=False, annotation_clip=False)

ax.yaxis.label.set_visible(False)
ax.xaxis.label.set_visible(False)
plt.tight_layout()
plt.show()
Run Code Online (Sandbox Code Playgroud)

例如,一旦放置了 xy 或 xytest 并固定了支架的长度和宽度,是否可以以某种方式优雅地完成它?

tac*_*ell 5

实现此目的的一种方法是使用以混合变换get_yaxis_transform形式返回的方法,其中 x 值位于“轴分数”中,y 值位于“数据”中。一旦我们完成了该转换,我们就可以为括号创建一个( docs ) 并使用( docs ) 添加标签。我将该逻辑封装在一个函数中,以便更轻松地使用它几次:PathPatchax.text

import matplotlib.path as mpath
import matplotlib.patches as mpatches


def add_label_band(ax, top, bottom, label, *, spine_pos=-0.05, tip_pos=-0.02):
    """
    Helper function to add bracket around y-tick labels.

    Parameters
    ----------
    ax : matplotlib.Axes
        The axes to add the bracket to

    top, bottom : floats
        The positions in *data* space to bracket on the y-axis

    label : str
        The label to add to the bracket

    spine_pos, tip_pos : float, optional
        The position in *axes fraction* of the spine and tips of the bracket.
        These will typically be negative

    Returns
    -------
    bracket : matplotlib.patches.PathPatch
        The "bracket" Aritst.  Modify this Artist to change the color etc of
        the bracket from the defaults.

    txt : matplotlib.text.Text
        The label Artist.  Modify this to change the color etc of the label
        from the defaults.

    """
    # grab the yaxis blended transform
    transform = ax.get_yaxis_transform()

    # add the bracket
    bracket = mpatches.PathPatch(
        mpath.Path(
            [
                [tip_pos, top],
                [spine_pos, top],
                [spine_pos, bottom],
                [tip_pos, bottom],
            ]
        ),
        transform=transform,
        clip_on=False,
        facecolor="none",
        edgecolor="k",
        linewidth=2,
    )
    ax.add_artist(bracket)

    # add the label
    txt = ax.text(
        spine_pos,
        (top + bottom) / 2,
        label,
        ha="right",
        va="center",
        rotation="vertical",
        clip_on=False,
        transform=transform,
    )

    return bracket, txt
Run Code Online (Sandbox Code Playgroud)

并使用它:


import matplotlib.pyplot as plt
import seaborn as sns
import pandas as pd
import numpy as np
from matplotlib.pyplot import figure

figure(figsize=(10, 5), dpi=50)

np.random.seed(42)
df = pd.DataFrame({f"var{i}": np.random.uniform(-1.0, 1.0, 2) for i in range(10)})
df["facet"] = [0, 1]
# Vertical plot
df = df.melt("facet", var_name="Variable", value_name="values")
ax = sns.barplot(data=df, x="values", y="Variable")

ylims = ax.get_ylim()
xlims = ax.get_xlim()
xmin = min(xlims)
xmax = max(xlims)
ymin = min(ylims)
ymax = max(ylims)

ax.yaxis.label.set_visible(False)
ax.xaxis.label.set_visible(False)

add_label_band(ax, 0.75, 5.25, "group a")
add_label_band(ax, 6.75, 8.25, "group b")

plt.tight_layout()
plt.show()


Run Code Online (Sandbox Code Playgroud)

在一些 yticks 周围用括号括起来的绘图

这可能需要进行更多调整以使值在视觉上更加令人愉悦,并且您可以扩展签名以将样式传递到括号和文本。

如果您想要线路,您也可以将ax.text呼叫替换为ax.annotate,但我不确定这是否是 OP 的关键功能。