如何用Pandas实现分组柱状图+占比折线图?支持透视表/GroupBy
问题描述
给定数据集:
import pandas as pd df = pd.DataFrame({ 'Vintage': ['2016Q1','2016Q1', '2016Q2','2016Q3','2016Q4','2016Q1', '2016Q2','2016Q2','2016Q2','2016Q3','2016Q4'], 'Model': ['A','A','A','A','A','B','B','B','B','B','B'], 'Count': [1,1,1,1,1,1,1,1,1,1,1], 'Case':[0,1,1,0,1,1,0,0,1,1,0], })
对应的数据表:
| Vintage | Model | Count | Case | |
|---|---|---|---|---|
| 0 | 2016Q1 | A | 1 | 0 |
| 1 | 2016Q1 | A | 1 | 1 |
| 2 | 2016Q2 | A | 1 | 1 |
| 3 | 2016Q3 | A | 1 | 0 |
| 4 | 2016Q4 | A | 1 | 1 |
| 5 | 2016Q1 | B | 1 | 1 |
| 6 | 2016Q2 | B | 1 | 0 |
| 7 | 2016Q2 | B | 1 | 0 |
| 8 | 2016Q2 | B | 1 | 1 |
| 9 | 2016Q3 | B | 1 | 1 |
| 10 | 2016Q4 | B | 1 | 0 |
需要完成两个任务:
- 绘制分组柱状图,以
Vintage为分组维度,Model作为颜色区分(hue); - 在同一图表中添加两条折线图,展示每个
Model和Vintage下Case占Count的百分比(即Case/Count的比值)。
已实现第一个任务的代码:
dfp = df.pivot_table(index='Vintage', columns='Model', values='Count', aggfunc='sum') dfp.plot(kind='bar', figsize=(8, 4), rot=45, ylabel='Frequency', title="Vintages")
但无法在该透视表中完成占比计算并绘制折线图,尝试列间除法后格式不支持绘图。需解决:
- 无需创建额外表格完成占比计算与折线图绘制;
- 能否用
groupby替代透视表完成全部任务?
解决方案
方法一:基于透视表扩展,无需额外表格
直接在绘图流程中完成计数柱状图绘制、占比计算与折线图叠加,通过双轴适配计数和百分比的数值范围:
import matplotlib.pyplot as plt # 绘制计数柱状图 dfp_count = df.pivot_table(index='Vintage', columns='Model', values='Count', aggfunc='sum') ax1 = dfp_count.plot(kind='bar', figsize=(8, 4), rot=45, ylabel='Frequency', title="Vintages", color=['#1f77b4', '#ff7f0e']) # 计算Case/Count占比 dfp_ratio = df.pivot_table(index='Vintage', columns='Model', values='Case', aggfunc='sum') / dfp_count * 100 # 叠加折线图,使用右侧Y轴 ax2 = ax1.twinx() dfp_ratio.plot(kind='line', marker='o', ax=ax2, ylabel='Case/Count Ratio (%)', color=['#1f77b4', '#ff7f0e']) # 合并图例,避免重复 handles1, labels1 = ax1.get_legend_handles_labels() handles2, labels2 = ax2.get_legend_handles_labels() ax1.legend(handles1 + handles2, labels1 + [f"{l} (%)" for l in labels2], loc='upper left') plt.tight_layout() plt.show()
方法二:用groupby替代透视表完成全部任务
通过groupby按Vintage和Model聚合,直接计算计数、占比后绘图:
import matplotlib.pyplot as plt # 分组聚合计算总Count、总Case grouped = df.groupby(['Vintage', 'Model']).agg( total_count=('Count', 'sum'), total_case=('Case', 'sum') ).reset_index() # 计算占比百分比 grouped['case_ratio'] = grouped['total_case'] / grouped['total_count'] * 100 # 绘制柱状图 fig, ax1 = plt.subplots(figsize=(8, 4)) for model in grouped['Model'].unique(): subset = grouped[grouped['Model'] == model] ax1.bar(subset['Vintage'], subset['total_count'], width=0.4, label=model, alpha=0.7) # 叠加折线图 ax2 = ax1.twinx() for model in grouped['Model'].unique(): subset = grouped[grouped['Model'] == model] ax2.plot(subset['Vintage'], subset['case_ratio'], marker='o', label=f"{model} (%)", linewidth=2) # 调整图表样式 ax1.set_ylabel('Frequency') ax2.set_ylabel('Case/Count Ratio (%)') ax1.set_title('Vintages') ax1.tick_params(axis='x', rotation=45) # 合并图例 handles1, labels1 = ax1.get_legend_handles_labels() handles2, labels2 = ax2.get_legend_handles_labels() ax1.legend(handles1 + handles2, labels1 + labels2, loc='upper left') plt.tight_layout() plt.show()
说明
- 两种方法均无需额外存储冗余表格,计算与绘图流程连贯;
- 双轴设置解决了计数与百分比数值范围差异的问题,提升图表可读性;
- groupby方法更灵活,便于后续扩展更多聚合逻辑。
内容的提问来源于stack exchange,提问作者amestrian
相关产品推荐
相关产品推荐

