You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何修正plt.bar+plt.plot组合图表的标注位置?

问题描述

我用Matplotlib绘制了堆叠柱状图+折线图的组合图表,当前标注位置不符合预期,需要将柱状图的标注放在柱子内部,折线图的标注靠近折线,请问如何修改代码?

月度会话数据

month_dtses_canceledses_finishedall_sescanceled_ptc
02021-02-010330
12021-03-010330
22021-04-0125728.57
32021-05-014151921.05
42021-06-014232714.81
52021-07-017303718.92
62021-08-016535910.17
72021-09-019627112.68
82021-10-01119010110.89
92021-11-01249511920.17
102021-12-013013116118.63
112022-01-013315919217.19
122022-02-013418922315.25
132022-03-016027533517.91
142022-04-017539146616.09
152022-05-0110848559318.21
162022-06-019058567513.33
172022-07-0116077593517.11
182022-08-012161140135615.93
192022-09-01187955114216.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()

修正方案

要实现目标效果,需要拆分柱状图和折线图的标注逻辑,针对性调整位置参数,并明确标注对应的坐标轴:

关键修改点

  1. 柱状图标注:
    • ses_finished:标注放在柱子垂直居中位置(y/2),用白色文字保证可读性
    • ses_canceled:标注位置基于下层柱子高度加上自身高度的一半(finished_height + y/2),确保在上层柱子内部居中
  2. 折线图标注:设置固定偏移(如(5,5)),让文字偏离折线点避免重叠
  3. 坐标轴绑定:用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.19 17:12:01