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

优雅设置分组堆叠条形图自定义X轴刻度的方法

问题:分组堆叠条形图X轴刻度居中的优雅实现方式

我有一段可生成分组堆叠条形图的代码:

import pandas as pd
import matplotlib.pyplot as plt
import random

N=1000
pc_col = [random.randint(1,7) for i in range(N)]
year_col = [random.randint(2020,2027) for i in range(N)]
month_col = [random.randint(1,12) for i in range(N)]
color=['red', 'green', 'blue', 'yellow', 'black', 'orange', 'magenta']
data = {'PC': pc_col, 'Year': year_col, 'Month': month_col}
data = pd.DataFrame(data)
fig, ax = plt.subplots()
n_years = data.Year.nunique()
barwidth=0.7/n_years
for i, yr in enumerate(sorted(data.Year.unique())):
    data.groupby(['Month', 'Year', 'PC']).size().xs(yr, level=1, drop_level=True).unstack().\
    plot(ax=ax, kind='bar', stacked='True', width=barwidth, position=i)
    if i == 0:
        h,l = ax.get_legend_handles_labels()

ax.get_legend().remove()
ax.legend(h,l)

plt.show()

运行后X轴刻度未处于分组条形的中间位置,我通过数学计算修改了代码实现居中:

import pandas as pd
import matplotlib.pyplot as plt
import random

N=1000
pc_col = [random.randint(1,7) for i in range(N)]
year_col = [random.randint(2020,2027) for i in range(N)]
month_col = [random.randint(1,12) for i in range(N)]
color=['red', 'green', 'blue', 'yellow', 'black', 'orange', 'magenta']
data = {'PC': pc_col, 'Year': year_col, 'Month': month_col}
data = pd.DataFrame(data)
fig, ax = plt.subplots()
n_years = data.Year.nunique()
barwidth=0.7/n_years
for i, yr in enumerate(sorted(data.Year.unique())):
    data.groupby(['Month', 'Year', 'PC']).size().xs(yr, level=1, drop_level=True).unstack().\
    plot(ax=ax, kind='bar', stacked='True', width=barwidth, position=i)
    if i == 0:
        h,l = ax.get_legend_handles_labels()

ax.get_legend().remove()
ax.legend(h,l)
positions = []
for i, label in enumerate(ax.xaxis.get_ticklabels()):
    x0 = i-(n_years-1)*(barwidth)
    x1 = i-(n_years-1)*(barwidth) + n_years*(barwidth)
    middle = (x0+x1)/2
    positions.append(middle)
plt.xticks(positions, [label.get_text() for label in ax.xaxis.get_ticklabels()])
plt.show()

但这段代码可读性较差,他人难以理解其中的数学逻辑,请问是否存在更优雅的编程实现方式?


优雅实现方案

方案1:重构位置偏移逻辑,简化刻度对齐

核心思路是让每个年份的条形组以原始X轴刻度为中心偏移,避免手动计算刻度位置,代码逻辑更直观:

import pandas as pd
import matplotlib.pyplot as plt
import random

N=1000
pc_col = [random.randint(1,7) for i in range(N)]
year_col = [random.randint(2020,2027) for i in range(N)]
month_col = [random.randint(1,12) for i in range(N)]
color=['red', 'green', 'blue', 'yellow', 'black', 'orange', 'magenta']
data = {'PC': pc_col, 'Year': year_col, 'Month': month_col}
df = pd.DataFrame(data)

fig, ax = plt.subplots()
years = sorted(df.Year.unique())
n_years = len(years)
bar_width = 0.7 / n_years
# 计算每个年份的偏移量:让整个分组的中心对齐原始刻度位置
offsets = [i - (n_years - 1) / 2 for i in range(n_years)]

for idx, year in enumerate(years):
    # 筛选当前年份数据,按月份和PC分组统计数量
    year_data = df[df.Year == year].groupby(['Month', 'PC']).size().unstack(fill_value=0)
    # 使用偏移量设置条形位置,确保分组居中
    year_data.plot(ax=ax, kind='bar', stacked=True, width=bar_width, position=offsets[idx], color=color)

# 整理图例
handles, labels = ax.get_legend_handles_labels()
ax.legend(handles, labels, title='PC')
# 直接使用原始刻度位置,分组已自动对齐中心
ax.set_xticks(range(12))
ax.set_xticklabels(range(1,13))

plt.tight_layout()
plt.show()

方案2:使用Seaborn简化分组堆叠流程

Seaborn的API更贴合统计可视化逻辑,虽然堆叠需要手动处理,但分组位置的逻辑更清晰:

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

N=1000
pc_col = [random.randint(1,7) for i in range(N)]
year_col = [random.randint(2020,2027) for i in range(N)]
month_col = [random.randint(1,12) for i in range(N)]
color=['red', 'green', 'blue', 'yellow', 'black', 'orange', 'magenta']
data = {'PC': pc_col, 'Year': year_col.astype(str), 'Month': month_col.astype(str)}
df = pd.DataFrame(data)

# 统计每个(月份, 年份, PC)的样本数量
count_df = df.groupby(['Month', 'Year', 'PC']).size().reset_index(name='Count')

fig, ax = plt.subplots(figsize=(12,6))
months = sorted(df.Month.unique())
years = sorted(df.Year.unique())
n_years = len(years)
bar_width = 0.7 / n_years

# 初始化堆叠的底部位置
bottom = [0] * len(months)

for pc in sorted(df.PC.unique()):
    pc_data = count_df[count_df.PC == pc]
    for year_idx, year in enumerate(years):
        # 获取当前年份当前PC的月度数据,缺失补0
        year_pc_data = pc_data[pc_data.Year == year].set_index('Month')['Count'].reindex(months, fill_value=0)
        # 计算当前年份条形的位置:以月份刻度为中心偏移
        bar_positions = [int(m) + (year_idx - (n_years-1)/2)*bar_width for m in months]
        # 绘制堆叠条形
        ax.bar(bar_positions, 
               year_pc_data.values, 
               width=bar_width, 
               bottom=bottom, 
               color=color[pc-1],
               label=f'PC {pc}' if year_idx == 0 else "")
        # 更新堆叠底部位置
        bottom = [b + v for b, v in zip(bottom, year_pc_data.values)]

# 设置X轴刻度和图例
ax.set_xticks(range(1,13))
ax.set_xticklabels(months)
ax.legend(title='PC')
plt.tight_layout()
plt.show()

方案说明

  • 方案1通过offsets = [i - (n_years - 1)/2 for i in range(n_years)]直接计算每个年份的偏移量,让整个分组的中心恰好对齐原始X轴刻度,无需再手动调整刻度位置,逻辑一目了然。
  • 方案2利用Seaborn的生态,将分组逻辑与堆叠逻辑分离,代码结构更清晰,适合需要更复杂可视化定制的场景。

内容的提问来源于stack exchange,提问作者Warehouse_Worker

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.24 11:07:09