如何在seaborn的stripplot图例中更改标记

Posted

技术标签:

【中文标题】如何在seaborn的stripplot图例中更改标记【英文标题】:How to change the marker in a stripplot legend in seaborn 【发布时间】:2021-11-17 06:23:20 【问题描述】:

seaborn stripplot 的图例仅显示彩色圆圈,但是,标记形状与我设置的标记不符。

复制代码:

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

letters = list('abcdefghijklmnopqrstuvwxyz')
place = ['North', 'South', 'East', 'West']

letter_set1 = set("abcdefghijklmn")
letter_set2 = set("opqrstuvwxyz")

data_size = 100

df_dict = 'letter': np.random.choice(letters, data_size), 
           'place': np.random.choice(place, data_size),
           "height": np.random.randint(low=40, high=100, size=data_size),
          "weight": np.random.randint(low=150, high=210, size=data_size),

df = pd.DataFrame(df_dict)
print(df)

fig, ax = plt.subplots(1, 1, figsize=(10, 7))

# We can ignore the violinplot
sns.violinplot(x='place', y="weight", data=df, scale="width", inner="quartile", bw=0.2, linewidth=1,
)
for violin in ax.collections:
    violin.set_alpha(0.1)

set1_df = df[df['letter'].isin(letter_set1)]
set2_df = df[df['letter'].isin(letter_set2)]

sns.stripplot(data=set1_df, x='place', y="weight", hue="letter", palette="Set1", size=10, linewidth=0.05, marker='^', ax=ax
)
sns.stripplot(data=set2_df, x='place', y="weight", hue="letter", palette="Set2", size=10, linewidth=0.05, marker='D', ax=ax
)

# Update the legend oreder
handles, labels = ax.get_legend_handles_labels()
zipped_list = zip(handles, labels)
sorted_zipped_list = sorted(zipped_list, key=lambda x: x[1])
ordered_handles, ordered_labels = [x[0] for x in sorted_zipped_list], [x[1] for x in sorted_zipped_list]

ax.legend(
    handles=ordered_handles,
    labels=ordered_labels,
    title="Letter",
    bbox_to_anchor=(1.02, 1),
    loc="upper left",
    )
plt.tight_layout()

plt.show()
plt.close()

图形输出:

目标:将图例中的彩色圆圈改为菱形和三角形。

图例句柄都是matplotlib.collections.PathCollection对象,没有明显的变化方式。我还在 GitHub 上发现了一个未解决的问题:https://github.com/mwaskom/seaborn/issues/940(供参考)。

有没有人知道如何手动设置图例中的标记或其他一些快速的方法?

【问题讨论】:

【参考方案1】:

这似乎是 seaborn 的 github 中的 open issue。一种解决方法是手动创建图例句柄:

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

letters = list('abcdefghijklmnopqrstuvwxyz')
place = ['North', 'South', 'East', 'West']

letter_set1 = set("abcdefghijklmn")
letter_set2 = set("opqrstuvwxyz")

data_size = 100

df_dict = 'letter': np.random.choice(letters, data_size),
           'place': np.random.choice(place, data_size),
           "height": np.random.randint(low=40, high=100, size=data_size),
           "weight": np.random.randint(low=150, high=210, size=data_size)

df = pd.DataFrame(df_dict)

fig, ax = plt.subplots(1, 1, figsize=(10, 7))

sns.violinplot(x='place', y="weight", data=df, scale="width", inner="quartile", bw=0.2, linewidth=1)
for violin in ax.collections:
    violin.set_alpha(0.1)

set1_df = df[df['letter'].isin(letter_set1)]
set2_df = df[df['letter'].isin(letter_set2)]

marker_set1 = '^'
marker_set2 = 'D'
marker_for_letter = **letter: marker_set1 for letter in letter_set1,
                     **letter: marker_set2 for letter in letter_set2
sns.stripplot(data=set1_df, x='place', y="weight", hue="letter",
              palette="Set1", size=10, linewidth=0.05, marker=marker_set1, ax=ax)
sns.stripplot(data=set2_df, x='place', y="weight", hue="letter",
              palette="Set2", size=10, linewidth=0.05, marker=marker_set2, ax=ax)
handles, labels = ax.get_legend_handles_labels()
handles = [Line2D([], [], color=h.get_facecolor(), linestyle='',
                  marker=marker_for_letter[l])
           for h, l in zip(handles, labels)]
labels, handles = zip(*sorted(zip(labels, handles)))

ax.legend(handles, labels, title="Letter", bbox_to_anchor=(1.01, 1.01), loc="upper left")
plt.tight_layout()
plt.show()

【讨论】:

感谢@JohanC!手动创建图例句柄的方法有很大帮助!我还有一些数据集要绘制。我可能需要额外的工作才能正确格式化所有内容。另外,我刚刚在 GitHub 上的 open issue 中使用 cmets 的提示修改了 seaborn 代码,并有一个临时解决方案。我稍后会在下面发布。 我使用字典作为标记更新了代码。 zip(*sorted(zip()))围绕句柄和标签的代码优化也很棒。 糟糕。应该是labels, handles = zip(*sorted(zip(labels, handles))),以便使用默认键进行排序。我正在更改那行代码。 很好,它更短。 :)【参考方案2】:

根据 GitHub 上未决问题 #940 中 comments 的提示,我相应地修改了代码以获得临时解决方案。

我修改了这个文件://lib/python3.8/site-packages/seaborn/categorical.py

在 L1084 附近:将kws="marker": "o" 添加到输入参数,将marker=kws['marker'], 添加到传递给ax.scatter() 的输入参数:

def add_legend_data(self, ax, kws="marker": "o"):
"""Add empty scatterplot artists with labels for the legend."""
    if self.hue_names is not None:
        for rgb, label in zip(self.colors, self.hue_names):
            ax.scatter([], [],
                       color=mpl.colors.rgb2hex(rgb),
                       label=label,
                       marker=kws['marker'],
                       s=60)

L1162左右,将kws添加到self.add_legend_data(ax)

def plot(self, ax, kws):
    """Make the plot."""
    self.draw_stripplot(ax, kws)
    self.add_legend_data(ax, kws)
    self.annotate_axes(ax)
    if self.orient == "h":
        ax.invert_yaxis()

不利的一面是,您可能必须每次都将 marker 参数传递给 stripplot() 函数,否则我们会收到 KeyError。此外,此方法不可移植。您必须在所有机器上以这种方式进行编辑。

风险自负。

【讨论】:

以上是关于如何在seaborn的stripplot图例中更改标记的主要内容,如果未能解决你的问题,请参考以下文章

如何更改 seaborn countplot 中图例的位置?

如何使用 seaborn FacetGrid 更改字体大小?

Python使用matplotlib可视化stripplot抖动数据点使用seaborn中的stripplot函数可视化stripplot防止数据重叠影像可视化效果

如何自定义 seaborn.scatterplot 图例?

Python使用matplotlib可视化stripplot数据点计数图使用seaborn中的stripplot函数可视化stripplot数据越密集区域的计数数据点越大(Counts Plot

更改 Seaborn 散点图中的图例位置和标签