如何在没有类别数据的情况下用 0 计数注释柱线

How to annotate bars with 0 counts when there's no data for the category

提问人:Jonathan Ng 提问时间:11/17/2023 最后编辑:Trenton McKinneyJonathan Ng 更新时间:11/20/2023 访问量:31

问:

我有使用 seaborn 的代码,将分类图绘制到 FacetGrid 上。我在函数中使用了 a,因此使用 .中的参数设置为变量,在此上下文中定义为 。 是 my 中的一列,顾名思义,它代表年龄类别。这是一个有序的 pandas 分类 dtype。catplotcountplotcatplotkind='count'colcatplotcol_catage_categoryage_categorydf

我的如下:df

ipdb> df
                         spirometryResult_category     age_category habits-smoking
_id                                                                               
63bb97708e5f58ef85f6e4ea                    Normal  20-39 years old            Yes
63bd1b228e5f58ef85f73130                    Normal  20-39 years old            Yes
6423cb1c174e67af0aa0f0fc                    Normal  20-39 years old             No
6423d85e174e67af0aa10cda               Restrictive  20-39 years old             No
6423d8bb174e67af0aa10d98               Obstructive  20-39 years old             No
...                                            ...              ...            ...
6549a0df0941d048fdfd94c4               Obstructive  20-39 years old             No
6549d0ab0941d048fdfd960d                    Normal  40-59 years old             No
6549d0ee0941d048fdfd962b                    Normal  20-39 years old             No
654b17a20941d048fdfda256                    Normal  20-39 years old             No
654d81700941d048fdfdc27d                    Normal  40-59 years old             No

[106 rows x 3 columns]

中的列如下所示:age_categorydf

ipdb> df['age_category']
_id
63bb97708e5f58ef85f6e4ea    20-39 years old
63bd1b228e5f58ef85f73130    20-39 years old
6423cb1c174e67af0aa0f0fc    20-39 years old
6423d85e174e67af0aa10cda    20-39 years old
6423d8bb174e67af0aa10d98    20-39 years old
                                 ...       
6549a0df0941d048fdfd94c4    20-39 years old
6549d0ab0941d048fdfd960d    40-59 years old
6549d0ee0941d048fdfd962b    20-39 years old
654b17a20941d048fdfda256    20-39 years old
654d81700941d048fdfdc27d    40-59 years old
Name: age_category, Length: 106, dtype: category
Categories (4, object): ['20-39 years old' < '40-59 years old' < '60-79 years old' < '>= 80 years old']

栏目中类别的分布如下:age_category

ipdb> df['age_category'].value_counts()
age_category
20-39 years old    89
40-59 years old    14
60-79 years old     3
>= 80 years old     0
Name: count, dtype: int64

“>= 80 岁”年龄类别中的受试者数量为 0,这让我在绘制条形图注释时遇到了问题。

通常,下面的代码有效。我的目标是绘制多个子图,每个年龄组一个子图,显示 和 的每个组合的主题计数。spirometryResult_categoryhabits-smoking

    # Getting colours as specified in the config, for each hue category
    # Need to remove this hardcoding when i improve script
    colour_map =  config['seaborn_colourmaps'][hue_cat]

    # Plotting graph
    # count refers to param_category counts
    plt.subplots(figsize=figsize)
    # Not sure why setting axes.labelsize here doesnt
    # work
    sns.set_context('paper', rc={'font.size':fontsize})
    # height=4, aspect=.6,
    g = sns.catplot(
        data=df, x=param_category, hue=hue_cat, col=col_cat,
        kind='count', palette=colour_map, col_wrap=wrap_num,
        saturation=1
    )

    for ax in g.axes: 
        ax.tick_params(left=False, labelbottom=True)
        ax.set_xticklabels(ax.get_xticklabels(), size=fontsize)
        # Replacing subplot title if needed
        if col_cat in config['seaborn_alt_names']:
            new_title = config['seaborn_alt_names'][col_cat]
            ax.set_title( ax.get_title().replace(col_cat, new_title), size=fontsize)
        # Auto-label bars
        for container in ax.containers:
            container.datavalues = np.nan_to_num(container.datavalues)
            ax.bar_label(container, fmt='%.0f', padding=2)

    # In contrast to prev plotting code, despine goes here, as facetgrid
    # requires it to be done this way
    g.despine(top=True, right=True, left=True)
    # Fine adjustment of aesthetics    
    g.set(yticklabels=[], ylabel=None, xlabel=None)
    g.tick_params('x', rotation=90)
    # Checking if legend title is needed
    legend = False
    if 'legend' in plot_info:
        legend = plot_info['legend']
    if not legend:
        g.get_legend().set_title(None)
    else:
        # If an alternative legend title is specified,
        # use that, if not, use the default one
        if hue_cat in config['seaborn_alt_names']:
            new_title = config['seaborn_alt_names'][hue_cat]
            g.legend.set_title(new_title)
    # Continuing adjustment of aesthetics
    plt.subplots_adjust(hspace=1, wspace=0.3)
    g.figure.savefig(filename, bbox_inches='tight')
    plt.close()

输出图片如下所示:

spirometry subplots with age categories and smoking status

如您所见,“>= 80 岁”的类别没有主题,因此对于其相应的子图,根本没有绘制文本“0”。所有其他年龄类别都正确创建了相应的条形图和注释。对于这种情况,其中“>= 80 岁”没有主题,是一个空列表,因此我的 for 循环用于注释计数为 0 的案例不起作用。ax.containersfor container in ax.containers:

在这种情况下,我如何强制 seaborn 在正确的位置(由 seaborn 自动决定,因此我不必对任何内容进行硬编码)注释 0 计数的子图,其中类别有 0 个主题,并且是一个空列表?ax.containers

python seaborn facet-grid plot-annotations countplot

评论

0赞 Trenton McKinney 11/18/2023
创建子图是因为您已经创建了一个类别,并且为一个类别创建了一个子图,即使没有数据也是如此。更好的选择是更改您的类别,使其不包含不存在的年龄范围。
0赞 Trenton McKinney 11/18/2023
从视觉上看,不用 0 注释可能更干净。
0赞 Jonathan Ng 11/19/2023
感谢您的评论特伦顿。是的,我同意从视觉上看它的清洁器不是用 0 注释的。但是,我的公司不同意,他们希望我显示 0。
0赞 Jonathan Ng 11/19/2023
或者,他们希望我隐藏计数为 0 的类别。这与你的第一点有关,这是一个解决方案,但这里的困难在于一切都应该是自动化的。因此,如果我要这样做,我将需要自动提取那些计数为 0 的类别,并在 pdf 报告中生成文本,显示哪些类别被排除在外,我不确定该怎么做😅。不确定你是否理解我的解释?

答:

0赞 Trenton McKinney #1
import seaborn as sns

# sample data
df = sns.load_dataset('titanic')

# add categories
df['age_cat'] = pd.cut(x=df.age, bins=range(0, 91, 10), ordered=True)

# remove unused categories
df['age_cat'] = df['age_cat'].cat.remove_unused_categories()

g = sns.catplot(kind='count', data=df, x='embark_town', hue='sex', col='age_cat', col_wrap=3, height=2.5, aspect=2)

axes = g.axes.flat

for ax in axes:
    for c in ax.containers:
        ax.bar_label(c, fmt='%.0f', padding=2)

enter image description here

没有df['age_cat'].cat.remove_unused_categories()

enter image description here

评论

1赞 Jonathan Ng 11/22/2023
非常感谢!最终,我选择了另一种解决方案,因为我的同事们更喜欢我以 0 个计数显示子图,但删除 x 轴及其相关的标签和刻度,并添加一个短语,说由于没有主题,因此没有绘制柱线。但是,我认为您的解决方案很好,如果我使用的方法不起作用,则可以使用=)