居中标签的堆叠条形图

31

我正在尝试“鲁棒地”将数据标签居中在堆叠条形图中。下面给出了一个简单的代码示例和结果。正如您所看到的,数据标签并没有真正居中在所有矩形中。我错过了什么?

import numpy as np
import matplotlib.pyplot as plt

A = [45, 17, 47]
B = [91, 70, 72]

fig = plt.figure(facecolor="white")

ax = fig.add_subplot(1, 1, 1)
bar_width = 0.5
bar_l = np.arange(1, 4)
tick_pos = [i + (bar_width / 2) for i in bar_l]

ax1 = ax.bar(bar_l, A, width=bar_width, label="A", color="green")
ax2 = ax.bar(bar_l, B, bottom=A, width=bar_width, label="B", color="blue")
ax.set_ylabel("Count", fontsize=18)
ax.set_xlabel("Class", fontsize=18)
ax.legend(loc="best")
plt.xticks(tick_pos, ["C1", "C2", "C3"], fontsize=16)
plt.yticks(fontsize=16)

for r1, r2 in zip(ax1, ax2):
    h1 = r1.get_height()
    h2 = r2.get_height()
    plt.text(r1.get_x() + r1.get_width() / 2., h1 / 2., "%d" % h1, ha="center", va="bottom", color="white", fontsize=16, fontweight="bold")
    plt.text(r2.get_x() + r2.get_width() / 2., h1 + h2 / 2., "%d" % h2, ha="center", va="bottom", color="white", fontsize=16, fontweight="bold")

plt.show()

2个回答

58

导入和测试数据框

import pandas as pd
import matplotlib.pyplot as plt

A = [45, 17, 47]
B = [91, 70, 72]
C = [68, 43, 13]

# pandas dataframe
df = pd.DataFrame(data={'A': A, 'B': B, 'C': C}, index=['C1', 'C2', 'C3'])

     A   B   C
C1  45  91  68
C2  17  70  43
C3  47  72  13

更新至matplotlib v3.4.2

  • 使用matplotlib.pyplot.bar_label,它会自动将值居中显示在柱状图上。
  • 有关使用.bar_label的详细信息和示例,请参阅如何在柱状图上添加值标签
  • 已测试通过pandas v1.2.4,该版本使用matplotlib作为绘图引擎。
  • 如果柱状图的某些部分为零,请参考我的答案,其中展示了如何自定义.bar_label()labels
  • ax.bar_label(c, fmt='%0.0f', label_type='center')将更改数字格式,以显示不带小数位的数值(如果需要)。
ax = df.plot(kind='bar', stacked=True, figsize=(8, 6), rot=0, xlabel='Class', ylabel='Count')
for c in ax.containers:

    # Optional: if the segment is small or 0, customize the labels
    labels = [v.get_height() if v.get_height() > 0 else '' for v in c]
    
    # remove the labels parameter if it's not needed for customized labels
    ax.bar_label(c, labels=labels, label_type='center')

enter image description here

其他用于删除小片段标签的选项可以使用fmt

  • matplotlib 3.7更新中,bar_labelfmt参数现在接受{}风格的格式字符串。
  • fmt=lambda x: f'{x:.0f}' if x > 0 else ''
  • fmt=lambda x: np.where(x > 0, f'{x:.0f}', '')np.where一起使用
ax = df.plot(kind='bar', stacked=True, figsize=(8, 6), rot=0, xlabel='Class', ylabel='Count')
for c in ax.containers:
    ax.bar_label(c, fmt=lambda x: f'{x:.0f}' if x > 0 else '', label_type='center')

Seaborn选项

Seaborn DataFrame格式

# create the data frame
df = pd.DataFrame(data={'A': A, 'B': B, 'C': C, 'cat': ['C1', 'C2', 'C3']})

    A   B   C cat
0  45  91  68  C1
1  17  70  43  C2
2  47  72  13  C3

# convert the dataframe to a long form
df = df.melt(id_vars='cat')

  cat variable  value
0  C1        A     45
1  C2        A     17
2  C3        A     47
3  C1        B     91
4  C2        B     70
5  C3        B     72
6  C1        C     68
7  C2        C     43
8  C3        C     13

坐标轴级别图

# plot
ax = sns.histplot(data=df, x='cat', hue='variable', weights='value', discrete=True, multiple='stack')

# iterate through each container
for c in ax.containers:

    # Optional: if the segment is small or 0, customize the labels
    labels = [v.get_height() if v.get_height() > 0 else '' for v in c]
    
    # remove the labels parameter if it's not needed for customized labels
    ax.bar_label(c, labels=labels, label_type='center')

enter image description here

图表层级绘图

# plot
g = sns.displot(data=df, x='cat', hue='variable', weights='value', discrete=True, multiple='stack')

# iterate through each axes
for ax in g.axes.flat:

    # iterate through each container
    for c in ax.containers:

        # Optional: if the segment is small or 0, customize the labels
        labels = [v.get_height() if v.get_height() > 0 else '' for v in c]

        # remove the labels parameter if it's not needed for customized labels
        ax.bar_label(c, labels=labels, label_type='center')

enter image description here


原始答案

  • 使用.patches方法解压缩一个matplotlib.patches.Rectangle对象列表,每个堆叠条的部分对应一个对象。
    • 每个.Rectangle都有提取矩形定义的各种值的方法。
    • 每个.Rectangle按从左到右、从底到顶的顺序排列,所以当通过.patches进行迭代时,每个级别的所有.Rectangle对象都按顺序出现。
  • 标签使用f-string创建,label_text = f'{height}',因此可以根据需要添加任何其他文本,例如label_text = f'{height}%'
    • label_text = f'{height:0.0f}'将显示没有小数位的数字。

绘图

plt.style.use('ggplot')

ax = df.plot(stacked=True, kind='bar', figsize=(12, 8), rot='horizontal')

# .patches is everything inside of the chart
for rect in ax.patches:
    # Find where everything is located
    height = rect.get_height()
    width = rect.get_width()
    x = rect.get_x()
    y = rect.get_y()
    
    # The height of the bar is the data value and can be used as the label
    label_text = f'{height}'  # f'{height:.2f}' to format decimal values
    
    # ax.text(x, y, text)
    label_x = x + width / 2
    label_y = y + height / 2

    # plot only when height is greater than specified value
    if height > 0:
        ax.text(label_x, label_y, label_text, ha='center', va='center', fontsize=8)
    
ax.legend(bbox_to_anchor=(1.05, 1), loc='upper left', borderaxespad=0.)    
ax.set_ylabel("Count", fontsize=18)
ax.set_xlabel("Class", fontsize=18)
plt.show()

enter image description here

绘制水平条形图的方法如下:
  • kind='barh'
  • label_text = f'{width}'
  • if width > 0:
归属权:jsoma/chart.py

15

为什么你写的是va="bottom"?你应该使用va="center"

在此输入图片描述

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接