基于多级索引分组的Python堆叠柱状图绘制及图例问题求助
问题1:解决多级索引下的堆叠柱状图生成问题
Pandas原生方法
分组后的数据存在多级索引(ID和Cat),直接plot会报错,我们可以用unstack()将多级索引转为宽表格式,再绘制堆叠柱状图:
import pandas as pd import matplotlib.pyplot as plt df = pd.DataFrame({"Cat":["A", "B","A","B","A","B","A","B","C"], "Time":[0,0,1,1,0,0,1,1,1], "ID":[0,0,0,0,1,1,1,1,1]}) # 分组计数并转换为宽表,缺失类别用0填充 df_grouped = df.groupby(["ID", "Cat"])["Time"].count().unstack(fill_value=0) # 绘制堆叠柱状图 df_grouped.plot(kind="bar", stacked=True, figsize=(8, 5)) plt.xlabel("ID") plt.ylabel("Time计数") plt.title("按ID分组的Cat堆叠计数") plt.legend(title="Cat") plt.show()
unstack(fill_value=0)会把Cat的不同类别转为列,确保每个ID对应的所有Cat类别都有数值,Pandas就能正确识别X轴为ID,自动生成堆叠效果。
Seaborn方法
Seaborn没有原生的堆叠柱状图API,我们可以结合Matplotlib的bar函数手动实现堆叠,同时使用Seaborn的配色方案:
import pandas as pd import seaborn as sns import matplotlib.pyplot as plt import numpy as np df = pd.DataFrame({"Cat":["A", "B","A","B","A","B","A","B","C"], "Time":[0,0,1,1,0,0,1,1,1], "ID":[0,0,0,0,1,1,1,1,1]}) # 先计算每个ID和Cat的计数 counts = df.groupby(["ID", "Cat"])["Time"].count().reset_index(name="Count") ids = counts["ID"].unique() cats = counts["Cat"].unique() # 使用Seaborn的配色方案 colors = sns.color_palette("husl", len(cats)) fig, ax = plt.subplots(figsize=(8, 5)) bottom = np.zeros(len(ids)) # 初始堆叠底部位置为0 for cat, color in zip(cats, colors): # 筛选当前类别的数据 cat_data = counts[counts["Cat"] == cat] # 绘制柱子,bottom参数控制堆叠的起始位置 ax.bar(cat_data["ID"], cat_data["Count"], bottom=bottom, label=cat, color=color) # 更新下一个类别的底部位置 bottom += cat_data["Count"].values ax.set_xlabel("ID") ax.set_ylabel("Time计数") ax.set_title("按ID分组的Cat堆叠计数") ax.legend(title="Cat") sns.despine() # 去除多余边框 plt.show()
问题2:解决图例超出绘图窗口的问题
针对你使用seaborn.objects(so)的代码,图例超出窗口通常是因为绘图宽度不足或图例位置不合理,可通过以下几种方式解决:
方法1:将图例移至绘图区域外
通过bbox_to_anchor参数把图例放在图的右侧,同时用tight_layout()调整布局避免内容裁剪:
import pandas as pd import seaborn.objects as so import matplotlib.pyplot as plt import datetime as dt # 假设df和变量c已定义 p = so.Plot(df.astype({"Time": "category"}), x='Time', color='Cat').add(so.Bar(), so.Count(), so.Stack()) fig, ax = plt.subplots(figsize=(2560/120, 1335/120)) today = dt.datetime.today().strftime("%Y%m%d") plt.grid() # 绘制图形 p.on(ax).plot() # 调整图例位置到图外 ax.legend(bbox_to_anchor=(1.05, 1), loc='upper left', title="Cat") # 手动调整布局范围,给图例留出空间 plt.tight_layout(rect=[0, 0, 0.85, 1]) # 保存时加上bbox_inches='tight'确保图例被完整保存 plt.savefig(f"{today}_barchart_sources_{c}.png", bbox_inches='tight') plt.close()
方法2:增大绘图宽度
直接调整figsize的宽度参数,给图例留出足够空间:
# 增大宽度,比如从2560/120改为3000/120 fig, ax = plt.subplots(figsize=(3000/120, 1335/120))
方法3:将图例拆分为多列
如果Cat类别较多,可把图例分成多列,减少横向占用的宽度:
ax.legend(ncol=2, title="Cat") plt.tight_layout()
注意:代码中color='Category'应为color='Cat'(匹配你的测试数据列名),否则会出现列名不存在的错误。
内容的提问来源于stack exchange,提问作者swissy
相关产品推荐
相关产品推荐

