如何解决matplotlib多子图全局总标题不显示、统一设置图例的问题
问题解决方案
问题1:总标题不显示
原因是plt.suptitle()的调用位置错误,你将它放在了plt.subplots()之前,而plt.subplots()会创建新的画布,导致之前设置的总标题被覆盖。将plt.suptitle()移到plt.subplots()之后即可,同时可以调整y参数控制总标题的垂直位置,避免和子图标题重叠。
问题2:多子图共用统一图例
你需要先在每个子图的plot方法中添加legend=False参数关闭单张子图的图例,所有子图绘制完成后,提取任意一个子图的图例句柄和标签,在全局画布上添加统一图例即可,同时可以调整画布边距给图例留出空间。
修改后的完整代码
import pandas as pd import matplotlib.pyplot as plt # 虚拟数据集生成 d = { 'SeniorCitizen': [0,1,0,0,0,0,0,1,0,1,1,0,0,0,0,0,0,1,0,0,0,0,0,0,0] , 'CollegeDegree': [0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,1,1,1,1] , 'Married': [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1] , 'FulltimeJob': [1,1,1,1,1,0,0,0,1,1,1,1,1,1,1,1,1,0,0,1,1,0,0,0,1] , 'DistancefromBranch': [7,9,14,20,21,12,22,25,9,9,9,12,13,14,16,25,27,4,14,14,20,19,15,23,2] , 'ReversedPayment': [0,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0,1,0,1,0,0,1,0,1,0] } CarWash = pd.DataFrame(data = d) categoricals = ['SeniorCitizen','CollegeDegree','Married','FulltimeJob','ReversedPayment'] numerical = ['DistancefromBranch'] CarWash[categoricals] = CarWash[categoricals].astype('category') # 绘制频数子图 nrow = 1 ncol = len(categoricals[:-1]) figure1, axes1 = plt.subplots(nrow, ncol, figsize = (40,10)) # 总标题移到画布创建之后,调整y参数避免重叠 figure1.suptitle("Distribution of target variable across the categorical variables - frequencies #", fontsize=35, y=1.05) for i,ax in zip(categoricals[:-1], axes1.flatten()): # 绘制频数,关闭单图legend CarWash.groupby([i,'ReversedPayment']).size().reset_index().pivot(index = i,columns = 'ReversedPayment').plot(kind = 'bar', stacked = True, ax = ax, sharey=True, legend=False) ax.tick_params(axis='both', labelsize = 25, labelrotation = 0) ax.set_title(i, fontsize = 30) ax.set_xlabel("") # 标记数据 for p in ax.patches: x_adjust = 0.25 value = p.get_height() X = p.get_x() + x_adjust Y = p.get_y() + p.get_height()/2 XY = (X,Y) if value != 0: ax.annotate(int(value), XY, fontsize = 25) # 提取图例句柄和标签,添加统一图例 handles, labels = axes1[0].get_legend_handles_labels() # 调整标签名称为更易读的形式 labels = ['ReversedPayment=0', 'ReversedPayment=1'] figure1.legend(handles, labels, loc='upper right', bbox_to_anchor=(1.08, 0.95), fontsize=25) # 调整画布边距,给图例留出空间 figure1.tight_layout() # 绘制占比子图 figure2, axes2 = plt.subplots(nrow, ncol, figsize = (40,10)) figure2.suptitle("Distribution of target variable across the categorical variables - proportions %", fontsize=35, y=1.05) for i,ax in zip(categoricals[:-1], axes2.flatten()): # 绘制占比,关闭单图legend CarWash.groupby([i,'ReversedPayment']).size().reset_index().pivot(index = i, columns = 'ReversedPayment').apply(lambda x: x/x.sum(),axis=1).plot(kind = 'bar', stacked = True, ax = ax, legend=False) ax.tick_params(axis='both', labelsize = 25, labelrotation = 0) ax.set_title(i, fontsize = 30) ax.set_xlabel("") # 标记数据 for p in ax.patches: x_adjust = 0.25/3 value = p.get_height() X = p.get_x() + x_adjust Y = p.get_y() + p.get_height()/2 XY = (X,Y) if value != 0: ax.annotate(str(round(value*100,1)) + "%", XY, fontsize = 25) # 占比图添加统一图例 figure2.legend(handles, labels, loc='upper right', bbox_to_anchor=(1.08, 0.95), fontsize=25) figure2.tight_layout() plt.show()
补充说明
- 如果需要频数、占比两组子图共用同一个总标题,可以把两个子图放在同一个画布的上下两行,调整
plt.subplots的nrow=2即可,逻辑和上述代码一致。 - 图例的位置可以通过调整
bbox_to_anchor的参数自定义,也可以放在画布底部,避免占用横向展示空间。
内容的提问来源于stack exchange,提问作者Piyush
相关产品推荐
相关产品推荐

