matplotlib绘制直方图如何设置柱高为区间概率且柱高总和为1
问题描述
- 核心需求:基于Pandas数据框存储的数据绘制直方图,要求每个分箱(bin)的y轴取值等于该区间内的事件发生概率(即分箱内样本数/总样本数)
- 问题背景:
matplotlib.pyplot.hist的density=True参数计算逻辑为分箱内计数/(总计数*分箱宽度),返回值为单位区间的概率密度;当分箱宽度不等于1时,y轴取值并非分箱对应的事件发生概率。本次需求分箱宽度为10个单位,因此出现数值不匹配问题。 - 已知该实现下直方图积分结果不等于1(本例积分值为10),为预期效果,无需调整为密度归一化逻辑。
- 测试数据集生成代码:
import pandas as pd import numpy as np import matplotlib.pyplot as plt from random import seed from random import randint data = pd.DataFrame(columns=['Col1']) i = 0 while i < 49500: data.loc[len(data.index)] = [0] i += 1 seed(1) j = 0 while j < 500: data.loc[len(data.index)] = [randint(1,500)] j += 1
- 原有绘图代码:
plt.figure(2) fig2, ax2 = plt.subplots() ax2.hist(data['Col1'], range=(0.0, 500.0), bins=50, label='50000 numbers\n in 10 unit bins', density=True) plt.title('Probability Density of Some Numbers from 0 to 500', wrap=True) plt.legend(loc='upper right') plt.yscale('log') plt.xticks() plt.minorticks_on() plt.ylabel('Probability') plt.xlabel('Number') plt.savefig('randnum.png')
- 异常表现:0-10分箱的数据占比约99%,但对应y轴概率值仅显示为0.1,不符合预期。
- 期望实现路径:
- 修改直方图的归一化基准值,直接输出分箱概率
- 支持直接将直方图的y轴取值乘以自定义数值调整
解决方案
方案1:通过权重参数修改归一化基准(推荐)
不需要启用density参数,直接用weights参数指定每个样本的权重为1/总样本量,此时hist计算分箱高度时会自动对分箱内的权重求和,结果正好是分箱内样本数/总样本数,也就是需要的分箱事件概率。
替换原有hist调用部分即可,完整代码如下:
total_samples = len(data['Col1']) plt.figure(2) fig2, ax2 = plt.subplots() ax2.hist( data['Col1'], range=(0.0, 500.0), bins=50, weights=np.ones(total_samples) / total_samples, label='50000 numbers\n in 10 unit bins' ) plt.title('Probability of Some Numbers from 0 to 500', wrap=True) plt.legend(loc='upper right') plt.yscale('log') plt.minorticks_on() plt.ylabel('Probability') plt.xlabel('Number') plt.savefig('randnum_prob.png')
运行后0-10分箱的y值会正确显示为0.99,和实际样本占比一致。
方案2:缩放density模式下的y轴取值
如果需要保留density=True的计算逻辑,可以直接对返回的分箱高度做缩放:概率密度乘以分箱宽度,结果就是对应分箱的事件概率。ax.hist会返回三个对象:分箱高度数组、分箱边界数组、分箱对应的图形patch列表,遍历patch修改高度即可,代码如下:
plt.figure(2) fig2, ax2 = plt.subplots() n, bins, patches = ax2.hist( data['Col1'], range=(0.0, 500.0), bins=50, density=True, label='50000 numbers\n in 10 unit bins' ) bin_width = bins[1] - bins[0] # 按分箱宽度缩放高度,将概率密度转换为分箱概率,也可替换为任意自定义缩放系数 for p, h in zip(patches, n): p.set_height(h * bin_width) # 刷新轴范围适配修改后的柱高 ax2.autoscale_view() plt.title('Probability of Some Numbers from 0 to 500', wrap=True) plt.legend(loc='upper right') plt.yscale('log') plt.minorticks_on() plt.ylabel('Probability') plt.xlabel('Number') plt.savefig('randnum_prob_scaled.png')
两种方案输出效果完全一致,方案2支持传入任意自定义系数缩放y值,适配更多自定义场景。
内容的提问来源于stack exchange,提问作者cameronpoe
相关产品推荐
相关产品推荐

