【问题标题】:How to add annotation in a plot如何在绘图中添加注释
【发布时间】:2023-03-21 14:23:01
【问题描述】:

鉴于以下数据框,我想在 expectedactual 之间添加一个错误,显示 rate_off

  Month  Expected  Actual  rate_off
0   Jan        30      40     25.00
1   Feb        25      23     -8.70
2   Mar        50      51      1.96
3   Apr        20      17    -17.65

所以在我的以下代码中,我得到了这个图表:

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]})     
#df['rate_off'] = np.round(100-np.abs(df['Expected']/df['Actual']*100),2) 
 
fig, ax = plt.subplots(1,1)
df.plot(kind='bar', ax=ax)
ax.legend()
plt.show()

我希望添加一些描述差异 (rate_off) 的注释,以帮助最终用户立即获取数据。

所以期望的结果是:

【问题讨论】:

    标签: python pandas matplotlib plot graph


    【解决方案1】:

    您可以使用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()
    

    【讨论】:

    • zip(rate_off, plots.patches[:middle_index], plots.patches[middle_index:]) 的好主意
    • 很高兴听到我的回答很有帮助!
    猜你喜欢
    • 2022-12-02
    • 1970-01-01
    • 1970-01-01
    • 2020-02-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多