您可以使用matplotlib annotations 来绘制每个箭头以及添加相应的文本。您的问题没有那么简单,因为注释是有条件的,具体取决于expected < actual 还是expected > actual。
要访问条形图的高度和宽度以便您可以告诉 matplotlib 在哪里放置注释,您可以使用 plots = df.plot(kind='bar', ax=ax) 创建一个对象来存储 pandas 条形图并遍历 plots.patches,如 @987654322 中所述@。
但是,由于您有一个带有左右条形的分组条形图,遍历列表 plot.patches 将返回左侧条形对象,然后是右侧条形对象,因此最好遍历前半部分plot.patches 列表和 plot.patches 列表的后半部分同时,选择是否注释左侧或右侧栏,我通过将 zip() 应用于 plot.patches 的前半部分和 plot 的后半部分来完成.补丁。
剩下的就是根据是expected < actual还是expected > actual来确定每个箭头注释的开始和结束x坐标和y坐标,以及箭头的颜色,以及相对于文本的位置条形图尽可能接近您想要的情节。
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
df = pd.DataFrame({"Month":['Jan','Feb','Mar','Apr'], 'Expected':[30, 25, 50, 20],'Actual':[40, 23, 51, 17]})
rate_off = np.round(100-np.abs(df['Expected']/df['Actual']*100),2)
fig, ax = plt.subplots(1,1)
## store the plot in an object
plots = df.plot(kind='bar', ax=ax)
## iterating over plots.patches will return the objects for left bars, then the objects for the right bars
## so it is best to iterate over the left and right bar objects simultaneously
middle_index = len(plots.patches)//2
for change, left_bar, right_bar in zip(rate_off, plots.patches[:middle_index], plots.patches[middle_index:]):
## for expected less than actual, the annotation starts from the left bar
if change > 0:
# print(change, left_bar.get_x())
plots.annotate(f"",
xy=(left_bar.get_x() + left_bar.get_width() / 2, right_bar.get_height()),
ha='center', va='center', size=10,
xytext=(left_bar.get_x() + left_bar.get_width() / 2, left_bar.get_height()),
arrowprops=dict(arrowstyle="simple", color='green', facecolor='green'))
## annotate the text to the left of the bar
plots.annotate(f"{change}%",
xy=(left_bar.get_x() + left_bar.get_width() / 2, (left_bar.get_height() + right_bar.get_height()) / 2),
size=10, color='red', ha='right', va='center')
## for expected greater than actual, the annotation starts from the right bar
else:
# print(change, right_bar.get_x())
plots.annotate(f"",
xy=(right_bar.get_x() + right_bar.get_width() / 2, right_bar.get_height()),
ha='center', va='center', size=10,
xytext=(right_bar.get_x() + right_bar.get_width() / 2, left_bar.get_height()),
arrowprops=dict(arrowstyle="simple", color='red', facecolor='red'))
## annotate the text to the right of the bar
plots.annotate(f"{change}%",
xy=(right_bar.get_x() + right_bar.get_width() / 2, (left_bar.get_height() + right_bar.get_height()) / 2),
size=10, color='red', ha='left', va='center')
plt.show()