如何在 seaborn 图形级图中包装长刻度标签

How to wrap long tick labels in a seaborn figure-level plot

我正在可视化调查结果。答案很长,我想将它们完全放入图表中。因此,如果您能指出一种使用多行 xticklabels 的方法,或将 xticklabels 包含在侧面的图例中,如本例所示,我将不胜感激:

因为否则我将不得不使图表非常宽以适应整个答案。我当前的代码和结果图如下所示:

import seaborn as sns
from textwrap import wrap

sns.set(style="dark")
catp = (sns.catplot(data=results, x='1',
                    kind='count',
                    hue_order=results.sort_values('1')['1'],
                    palette='crest',
                    height=3.3,
                    aspect=17.4/7)
        .set(xlabel=None,
             ylabel='Number of Participants',
             title="\n".join(wrap("Question 1: Out of the three options, please choose the one you would prefer your fully autonomous car to choose, if you sat in it.", 90)))
)
plt.tight_layout()
catp.ax.set_yticks((0,10,20,30,40))
for p in catp.ax.patches:
    percentage = '{:.1f}%'.format(100 * p.get_height()/92)
    x = p.get_x() + p.get_width() / 2 - 0.05
    y = p.get_y() + p.get_height() + 0.3
    catp.ax.annotate(percentage, (x, y), size = 12)
plt.show()

此致!

编辑:您可以使用以下代码创建示例数据框:

import pandas as pd
import numpy as np
from itertools import chain

x = (np.repeat('Brake and crash into the bus', 37),
np.repeat('Steer into the passing car on the left', 22),
np.repeat('Steer into the right hand sidewall', 39))

results = pd.DataFrame({'1': list(chain(*x))})
  • 提取 xticklabels 并像处理 title
  • 一样用 wrap 修复它们
  • matplotlib 3.4.2 现在带有 .bar_label 以更轻松地注释条形图
    • 请参阅此 自定义条形注释标签。
  • 图中的heightaspect还需要根据wrap width进行一些调整。
  • 另一种解决方案是修复数据框中的值:
    • df['1'] = df['1'].apply(lambda row: '\n'.join(wrap(row, 30)))
    • for col in df.columns: df[col] = df[col].apply(lambda row: '\n'.join(wrap(row, 30))) 所有列。
  • labels 的列表理解使用赋值表达式 (:=),这需要 python >= 3.8。这可以重写为标准 for 循环。
    • labels = [f'{v.get_height()/len(df)*100:0.1f}%' for v in c] 在没有赋值表达式的情况下工作,但不检查条形高度是否为 0。
  • 测试于 python 3.8.11pandas 1.3.2matplotlib 3.4.2seaborn 0.11.2
import seaborn as sns
from textwrap import wrap
from itertools import chain
import pandas as pd
import numpy as np

# sample dataframe
x = (np.repeat('Brake and crash into the bus, which will result in the killing of the children on the bus, but save your life', 37),
np.repeat('Steer into the passing car on the left, pushing it into the wall, saving your life, but killing passengers in the other car', 22),
np.repeat('Steer into the right hand sidewall, killing you but saving the lives of all other passengers', 39))

df = pd.DataFrame({'1': list(chain(*x))})

# plotting
sns.set(style="dark")
catp = (sns.catplot(data=df, x='1',
                    kind='count',
                    hue_order=df.sort_values('1')['1'],
                    palette='crest',
                    height=5,
                    aspect=17.4/7)
        .set(xlabel=None,
             ylabel='Number of Participants',
             title="\n".join(wrap("Question 1: Out of the three options, please choose the one you would prefer your fully autonomous car to choose, if you sat in it.", 90)))
)
plt.tight_layout()
catp.ax.set_yticks((0,10,20,30,40))

for ax in catp.axes.ravel():

    # extract labels
    labels = ax.get_xticklabels()
    # fix the labels
    for v in labels:
        text = v.get_text()
        text = '\n'.join(wrap(text, 30))
        v.set_text(text)
    # set the new labels
    ax.set_xticklabels(labels)
    # annotate the bars
    for c in ax.containers:

        # create a custom annotation: percent of total
        labels = [f'{w/len(df)*100:0.1f}%' if (w := v.get_height()) > 0 else '' for v in c]
        
        ax.bar_label(c, labels=labels, label_type='edge')