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 并固定了支架的长度和宽度,是否可以以某种方式优雅地完成它?
实现此目的的一种方法是使用以混合变换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)
这可能需要进行更多调整以使值在视觉上更加令人愉悦,并且您可以扩展签名以将样式传递到括号和文本。
如果您想要线路,您也可以将ax.text呼叫替换为ax.annotate,但我不确定这是否是 OP 的关键功能。