优雅设置分组堆叠条形图自定义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
相关产品推荐
相关产品推荐

