使用sklearn的KDE绘制概率分布时y轴值大于1问题排查
核心误区说明
你混淆了概率和概率密度两个完全不同的统计概念,同时存在KDE带宽选择失当的问题:
- 概率的取值范围固定在[0,1],所有互斥事件的概率和为1。你之前写的直方图代码通过设置权重,让每个分箱的高度代表样本落在该分箱的概率,因此所有分箱高度求和为1,这是概率的展示方式。
- 核密度估计输出的是概率密度函数(PDF),它没有「取值必须≤1」的约束,唯一的归一化要求是在整个变量取值域上的积分等于1。举个最直观的例子:如果变量全部落在长度为0.1的区间内,该区间内的平均概率密度就是1/0.1=10,远大于1,这是完全合法的。
- 你当前代码里设置的
bandwidth=0.2严重不符合数据尺度:你的原始数据均匀分布在[-0.0793, 0.0793],全范围宽度仅0.1586,0.2的带宽比整个数据范围还宽,会导致分布被过度平滑,输出的钟形曲线完全偏离真实的均匀分布形态。
修正方法
sklearn的KernelDensity输出本身已经做了正确的归一化,不需要额外添加归一化步骤,你可以通过数值积分自行验证:
dx = X_plot[1, 0] - X_plot[0, 0] print(np.sum(np.exp(log_dens)) * dx)
运行后会得到非常接近1的结果,证明密度积分符合概率分布要求。
你只需要调整带宽到匹配数据尺度的范围,同时如果要和直方图对比,需要绘制密度直方图而非概率直方图,参考修正后的完整代码:
import numpy as np import matplotlib.pyplot as plt from sklearn.neighbors import KernelDensity # 生成样本数据 init_range = 0.0793 X = np.random.uniform(low=-init_range, high=init_range, size=133280)[:, np.newaxis] # 核密度估计:带宽调整为和数据尺度匹配的0.005 kde = KernelDensity(kernel="gaussian", bandwidth=0.005).fit(X) # 绘图范围略大于数据范围,覆盖高斯核的尾部 X_plot = np.linspace(-init_range*1.2, init_range*1.2, 1000)[:, np.newaxis] log_dens = kde.score_samples(X_plot) dens = np.exp(log_dens) # 同时绘制KDE和密度直方图做对比 plt.plot(X_plot[:, 0], dens, lw=2, linestyle="-", label="高斯KDE密度") # density=True时直方图高度为概率密度,所有分箱面积和为1 n_bins = 40 plt.hist(X, n_bins, density=True, histtype='step', color='red', label="密度直方图") plt.ylim([0, 7]) plt.legend() plt.show()
运行后你会看到KDE曲线和密度直方图几乎完全重合,数据区间内的密度值约为6.3(对应均匀分布理论密度1/(2*0.0793)≈6.3),虽然远大于1,但积分后为1,是完全正确的概率密度结果。
如果你需要得到和之前概率直方图一致的、取值在[0,1]区间的分箱概率,可以对KDE结果在每个分箱区间做数值积分,积分结果就是对应分箱的概率,所有分箱积分结果求和为1。
内容的提问来源于stack exchange,提问作者Blade
相关产品推荐
相关产品推荐

