如何为Matplotlib多子图逐个子图添加对应正态分布曲线
多子图单图叠加正态分布曲线实现方法
现有实现基础
- 已基于Matplotlib搭建3行2列布局的多子图框架,用于展示6门课程的绩点成绩柱状分布,基础柱状图效果已完成
- 待实现效果:为每个独立子图匹配对应课程成绩的正态分布拟合曲线,完成数据分布的叠加展示
核心实现逻辑
多子图场景下不存在特殊的适配障碍,现有逐个子图遍历逻辑zip(cols_to_plot, axs.flatten())本身就支持在单个ax对象上叠加自定义绘图内容,不需要额外查找全局适配方法,只需要在遍历循环内,针对当前子图对应的数据列单独计算正态参数、绘制曲线即可。
具体代码修改步骤
- 基础顺序修正:原代码中使用
value_counts()生成的柱子数据是按频次排序的,需要先按绩点值排序,避免横坐标顺序混乱:- 把原来的
dftemp = df[col].value_counts()修改为dftemp = df[col].value_counts().sort_index()
- 把原来的
- 逐列计算正态分布参数:在循环内部,针对当前遍历的课程列,直接计算非空绩点值的均值
mu和标准差sigma,不做全局统一计算:valid_scores = df[col].dropna() mu = valid_scores.mean() sigma = valid_scores.std() - 生成连续曲线坐标并做尺度匹配:因为原有柱状图的纵轴是成绩出现的频数,不是正态分布的概率密度,需要对计算出的pdf值做尺度缩放,保证曲线和柱子的高度量级一致,避免出现曲线错位:
- 用
np.linspace生成覆盖当前成绩取值范围的连续x值,用于绘制平滑曲线 - 计算标准正态分布的概率密度值
- 按照绩点的固定组距(相邻绩点差值为0.3,和定义的
NUMERIC_GRADES步长一致),把概率密度换算成和柱状图同量级的频数值,缩放公式为:缩放后曲线值 = 概率密度 * 总样本数 * 组距
- 用
- 在当前子图上绘制曲线:调用当前子图对象
ax的plot方法,把计算好的曲线坐标画出来即可。
修改后的循环段完整参考代码
for col, ax in zip(cols_to_plot, axs.flatten()): # 绘制绩点柱状图 dftemp = df[col].value_counts().sort_index() ax.bar(dftemp.index, list(dftemp), alpha=0.6) # 计算当前课程的正态分布参数 valid_scores = df[col].dropna() mu = valid_scores.mean() sigma = valid_scores.std() # 生成平滑曲线的坐标 x_curve = np.linspace(valid_scores.min(), valid_scores.max(), 100) # 计算正态分布概率密度 norm_pdf = (1 / (sigma * np.sqrt(2 * np.pi))) * np.exp(-(x_curve - mu) ** 2 / (2 * sigma ** 2)) # 把概率密度缩放到和柱状图同量级的频数尺度 bin_width = 0.3 scaled_pdf = norm_pdf * len(valid_scores) * bin_width # 在当前子图上叠加正态曲线 ax.plot(x_curve, scaled_pdf, color='darkred', linewidth=2) # 原有刻度、标题设置 ax.set_title(col.replace('_', ' ').replace('grade', 'Grade')) ax.tick_params(axis='x', labelrotation=30)
常见踩坑点
- 不要把多列的正态参数放在循环外面统一计算,必须在循环内逐列匹配对应数据,否则所有子图的曲线都会是同一个分布,和数据不匹配
- 不要直接把未缩放的正态概率密度画到图上,概率值的范围在0-1之间,和柱状图几十到上百的计数量级差异极大,会导致曲线完全贴在x轴上无法看见
- 柱子绘制前一定要按绩点数值排序,否则横坐标顺序混乱,和正态曲线的x轴完全错位
内容的提问来源于stack exchange,提问作者user12755836
相关产品推荐
相关产品推荐

