如何在 Matplotlib 中为 Stack Percentage Barplot 添加注释

How to add annotations to Stack Percentage Barplot in Matplotlib

我想使用 matplotlib 向堆积条形图添加值。到目前为止,我已经能够创建堆叠条形图,但我对如何添加注释感到困惑。

已经回答了类似的问题 ,但是对于 ggplot。

我想要一个类似的输出,不是整个图形,而是中间的注释。

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


data = {'Range':['<10','>10', '>= 20', '<10','>10', '>= 20', '<10','>10', '>= 20'],
    'Price':[50,25,25,70,20,10,80,10,10]
    'Value':[100,50,50,140,40,20,160,20,20]}    

df1 = pd.DataFrame(data)

b1 = df1[(df1['Range'] == '<10']['Price']
b2 = df1[df1['Range'] == '>10']['Price']
b3 = df1[df1['Range'] == '>= 20']['Price']

totals = [i+j+k for i,j,k in zip(b1,b2,b3)]
greenBars = [i / j * 100 for i,j in zip(b1, totals)]
orangeBars = [i / j * 100 for i,j in zip(b2, totals)]
blueBars = [i / j * 100 for i,j in zip(b3, totals)]


barWidth = 0.5

names = ('low', 'medium', 'high')
r = [0,1,2]
plt.bar(r, greenBars, color='#b5ffb9', edgecolor='white', width=barWidth, label = '$<10')
plt.bar(r, orangeBars, bottom=greenBars, color='#f9bc86', edgecolor='white', width=barWidth, label = '$>10')
plt.bar(r, blueBars, bottom=[i+j for i,j in zip(greenBars, orangeBars)], color='#a3acff', edgecolor='white', width=barWidth, label = '$>=20')


plt.xticks(r, names)
plt.xlabel("group")

plt.legend(loc='upper left', bbox_to_anchor=(1,1), ncol=1)

plt.show()

在上面添加了代码以创建堆叠图。 期望的输出:

对于 Low 类别,通过从列 Value 中提取值为 100、50 和 50

的值在堆栈上添加注释

对于中值,值为 140、40 和 20。

高值为 160、20 和 20。

  • 这个答案将根据 Quang Hoang
  • 中的代码简化绘图
  • 可以通过从 ax.patches 中提取条形图位置来注释条形图。
    • 补丁数据不包含与数据帧对应的标签,因此关联不同的数据值集成为定制过程。
  • 为了用Value而不是Price进行注释,需要有一种方法来关联相应的值。
    • 字典不起作用,因为有重复值
    • Value 制作一个旋转数据框,为 Price 制作一个相应的数据框。这将确保相应的数据位于同一位置。
  • col_idxrow_idx 将与 .iloc 一起使用以在 df_value 中找到正确的值,用它来注释图。
    • col_idxrow_idx 都可以在 if i%3 == 0 中重置或更新,因为有 3 个条和 3 个线段,但是,如果条和线段的数量不同,需要不同的重置条件。
import pandas as pd
import matplotlib.pyplot as plt

# create the dataframe
data = {'Range':['<10','>10', '>= 20', '<10','>10', '>= 20', '<10','>10', '>= 20'],
        'Price':[50,25,25,70,20,10,80,10,10],
        'Value':[100,50,50,140,40,20,160,20,20]}    

df1 = pd.DataFrame(data)

# pivot the price data
df_price = df1.assign(idx=df1.groupby('Range').cumcount()).pivot(index='idx', columns='Range', values='Price')

Range  <10  >10  >= 20
idx                   
0       50   25     25
1       70   20     10
2       80   10     10

# pivot the value data
df_value = df1.assign(idx=df1.groupby('Range').cumcount()).pivot(index='idx', columns='Range', values='Value')

Range  <10  >10  >= 20
idx                   
0      100   50     50
1      140   40     20
2      160   20     20

# set colors
colors = ['#b5ffb9', '#f9bc86', '#a3acff']

# plot the price
ax = df_price.plot.bar(stacked=True, figsize=(8, 6), color=colors, ec='w')

# label the x-axis
plt.xticks(ticks=range(3), labels=['low', 'med', 'high'], rotation=0)

# x-axis title
plt.xlabel('group')

# position the legend
plt.legend(bbox_to_anchor=(1.05, 1), loc='upper left')

# annotate the bar segments
# col and row iloc indices for df_value
col_idx = 0
row_idx = 0

# iterate through each bar patch from ax
for i, p in enumerate(ax.patches, 1):

    left, bottom, width, height = p.get_bbox().bounds
    v = df_value.iloc[row_idx, col_idx]
    if width > 0:
        ax.annotate(f'{v:0.0f}', xy=(left+width/2, bottom+height/2), ha='center', va='center')

        # use this line to add commas for thousands
#        ax.annotate(f'{v:,}', xy=(left+width/2, bottom+height/2), ha='center', va='center')
    
    row_idx += 1
    if i%3 == 0:  # there are three bars, so update the indices 
        col_idx += 1
        row_idx = 0