如何修正plt.bar+plt.plot组合图表的标注位置?
问题描述
我用Matplotlib绘制了堆叠柱状图+折线图的组合图表,当前标注位置不符合预期,需要将柱状图的标注放在柱子内部,折线图的标注靠近折线,请问如何修改代码?
月度会话数据
| month_dt | ses_canceled | ses_finished | all_ses | canceled_ptc | |
|---|---|---|---|---|---|
| 0 | 2021-02-01 | 0 | 3 | 3 | 0 |
| 1 | 2021-03-01 | 0 | 3 | 3 | 0 |
| 2 | 2021-04-01 | 2 | 5 | 7 | 28.57 |
| 3 | 2021-05-01 | 4 | 15 | 19 | 21.05 |
| 4 | 2021-06-01 | 4 | 23 | 27 | 14.81 |
| 5 | 2021-07-01 | 7 | 30 | 37 | 18.92 |
| 6 | 2021-08-01 | 6 | 53 | 59 | 10.17 |
| 7 | 2021-09-01 | 9 | 62 | 71 | 12.68 |
| 8 | 2021-10-01 | 11 | 90 | 101 | 10.89 |
| 9 | 2021-11-01 | 24 | 95 | 119 | 20.17 |
| 10 | 2021-12-01 | 30 | 131 | 161 | 18.63 |
| 11 | 2022-01-01 | 33 | 159 | 192 | 17.19 |
| 12 | 2022-02-01 | 34 | 189 | 223 | 15.25 |
| 13 | 2022-03-01 | 60 | 275 | 335 | 17.91 |
| 14 | 2022-04-01 | 75 | 391 | 466 | 16.09 |
| 15 | 2022-05-01 | 108 | 485 | 593 | 18.21 |
| 16 | 2022-06-01 | 90 | 585 | 675 | 13.33 |
| 17 | 2022-07-01 | 160 | 775 | 935 | 17.11 |
| 18 | 2022-08-01 | 216 | 1140 | 1356 | 15.93 |
| 19 | 2022-09-01 | 187 | 955 | 1142 | 16.37 |
原绘图代码
import matplotlib.pyplot as plt fig, ax = plt.subplots(figsize=(10, 6)) ax1 = plt.bar( x=df7_2['month_dt'], height=df7_2['ses_finished'], label='Finished sessions', edgecolor='black', linewidth=0, width=20, color='#3049BF' ) ax2 = plt.bar( x=df7_2['month_dt'], height=df7_2['ses_canceled'], bottom=df7_2['ses_finished'], label='Canceled sessions', edgecolor='black', linewidth=0, width=20, color='#BF9530' ) secax = ax.twinx() secax.set_ylim(min(df7_2['canceled_ptc'])-10, max(df7_2['canceled_ptc'])*1.5) ax3 = plt.plot( df7_2['month_dt'], df7_2['canceled_ptc'], color='#E97800', label='Canceled sessions, ptc' ) columns = ['ses_finished', 'ses_canceled', 'canceled_ptc'] colors = ['b', 'y', 'black'] for col, c in zip(columns, colors): for x, y in zip(df7_2['month_dt'], df7_2[col]): # list with quantiles for data lst = [0, 0.25, 0.5, 0.75, 1] describe_nearest = [] [describe_nearest.append(df7_2[col].quantile(el, interpolation='nearest')) for el in lst] describe_nearest.append(df7_2[col].values[-1::][0]) # add annotation if value in quantiles list if y in describe_nearest: label = '{:.0f}'.format(y) plt.annotate( label, (x, y), textcoords='offset points', xytext=(0, 0), ha='center', color=c ) ax.grid(False) secax.grid(False) h1, l1 = ax.get_legend_handles_labels() h2, l2 = secax.get_legend_handles_labels() handles = h1 + h2 labels = l1 + l2 ax.legend(handles, labels) plt.show()
修正方案
要实现目标效果,需要拆分柱状图和折线图的标注逻辑,针对性调整位置参数,并明确标注对应的坐标轴:
关键修改点
- 柱状图标注:
ses_finished:标注放在柱子垂直居中位置(y/2),用白色文字保证可读性ses_canceled:标注位置基于下层柱子高度加上自身高度的一半(finished_height + y/2),确保在上层柱子内部居中
- 折线图标注:设置固定偏移(如
(5,5)),让文字偏离折线点避免重叠 - 坐标轴绑定:用
ax.annotate和secax.annotate分别对应主、次坐标轴,避免双轴坐标混淆
修改后的完整代码
import matplotlib.pyplot as plt fig, ax = plt.subplots(figsize=(10, 6)) # 绘制堆叠柱状图 ax1 = ax.bar( x=df7_2['month_dt'], height=df7_2['ses_finished'], label='已完成会话', edgecolor='black', linewidth=0, width=20, color='#3049BF' ) ax2 = ax.bar( x=df7_2['month_dt'], height=df7_2['ses_canceled'], bottom=df7_2['ses_finished'], label='已取消会话', edgecolor='black', linewidth=0, width=20, color='#BF9530' ) # 绘制折线图(次坐标轴) secax = ax.twinx() secax.set_ylim(min(df7_2['canceled_ptc'])-10, max(df7_2['canceled_ptc'])*1.5) ax3 = secax.plot( df7_2['month_dt'], df7_2['canceled_ptc'], color='#E97800', label='取消会话占比' ) # 处理已完成会话标注 lst_finished = [0, 0.25, 0.5, 0.75, 1] describe_finished = [df7_2['ses_finished'].quantile(el, interpolation='nearest') for el in lst_finished] describe_finished.append(df7_2['ses_finished'].values[-1]) for x, y in zip(df7_2['month_dt'], df7_2['ses_finished']): if y in describe_finished: label = '{:.0f}'.format(y) ax.annotate( label, (x, y/2), textcoords='offset points', xytext=(0, 0), ha='center', va='center', color='white' ) # 处理已取消会话标注 lst_canceled = [0, 0.25, 0.5, 0.75, 1] describe_canceled = [df7_2['ses_canceled'].quantile(el, interpolation='nearest') for el in lst_canceled] describe_canceled.append(df7_2['ses_canceled'].values[-1]) for idx, (x, y) in enumerate(zip(df7_2['month_dt'], df7_2['ses_canceled'])): if y in describe_canceled and y != 0: finished_height = df7_2['ses_finished'].iloc[idx] label = '{:.0f}'.format(y) ax.annotate( label, (x, finished_height + y/2), textcoords='offset points', xytext=(0, 0), ha='center', va='center', color='white' ) # 处理取消占比折线标注 lst_ptc = [0, 0.25, 0.5, 0.75, 1] describe_ptc = [df7_2['canceled_ptc'].quantile(el, interpolation='nearest') for el in lst_ptc] describe_ptc.append(df7_2['canceled_ptc'].values[-1]) for x, y in zip(df7_2['month_dt'], df7_2['canceled_ptc']): if y in describe_ptc: label = '{:.1f}'.format(y) secax.annotate( label, (x, y), textcoords='offset points', xytext=(5, 5), ha='left', va='bottom', color='#E97800' ) # 样式设置 ax.grid(False) secax.grid(False) # 合并图例 h1, l1 = ax.get_legend_handles_labels() h2, l2 = secax.get_legend_handles_labels() handles = h1 + h2 labels = l1 + l2 ax.legend(handles, labels) plt.show()
内容的提问来源于stack exchange,提问作者John Doe
相关产品推荐
相关产品推荐

