如何平滑 Python 中的概率分布图?

How to smooth a probability distribution plot in Python?

我做了一个概率DataFrame df,按value:

排序
    value   prob
0   -31     0.002597
1   -23     0.005195
2   -22     0.005195
3   -21     0.002597
4   -20     0.002597
5   -18     0.005195
6   -15     0.002597
...
39  19      0.007792
40  21      0.002597
41  22      0.005195
42  23      0.002597
43  25      0.002597
44  28      0.002597
45  29      0.005195
46  37      0.002597

(如您所见,value 的值并未涵盖 df[0]df[46] 之间的所有整数)

我通过简单地执行绘制了一个概率分布图:

import matplotlib as plt

plt.plot(df['value'], df['prob'])

它在其中返回

现在,我想平滑概率曲线,所以我尝试了两种方法。首先,我尝试了 np.polyfit:

import numpy as np

x = df['value']
y = df['prob']
n = 10

poly = np.polyfit(x,y,n)
poly_y = np.poly1d(poly)(x)
plt.plot(x,poly_y, color='red')
plt.plot(x,y, color='blue')

结果图显示为

没有成功舍入概率(操纵n值没有解决欠舍入问题)。

其次,我尝试了 scipy.interpolate:

from scipy import interpolate

xnew = np.linspace(x.min(), x.max(), 10) 
bspline = interpolate.make_interp_spline(x, y)
y_smoothed = bspline(xnew)
plt.plot(xnew, y_smoothed, color='red')
plt.plot(x,y, color='blue')

和这个returns

它遇到了同样的问题,即在 value = 0 处的概率表示不足(并且也没有真正平滑它)。

关于如何成功平滑概率分布图而不显着低估或高估概率的任何建议?

要人为地做到这一点,应该使用插值法。

尝试使用 interpolate.make_interp_spline 函数的 k 输入参数。

尝试 k=3 及以上。

从观察样本生成的概率分布通常用 histogram 表示。我对为什么这是标准做法的理解是直方图(即连续的条而不是线)呈现了基础数据的更真实的画面。

在您给出的示例中,数据被合并为整数。由于我缺乏关于测量内容的信息,我们首先假设您的数据是真正离散的并且只能取整数值(例如,足球队在一年中每场比赛结束时的净得分)。然后在这种情况下,用一条线绘制概率分布有点欺骗性,因为它给人的印象是变量是连续的,而实际上它不是(足球队不能以净 +1.5 分结束比赛)。

如果事实上,您的数据是连续的,则意味着您提供的数据样本已分装到整数范围的分箱中。在这种情况下,即使您确实拥有真正连续的数据,但由于以下原因,用连续线显示概率密度仍然具有欺骗性。例如,假设您使用 .5 作为中点四舍五入了所有测量值。那么您可能在 -0.4 处进行了 10 次测量,在 +0.3 处进行了 5 次测量,并且 none 介于两者之间。然而,您的图表给人的印象是,实际上根本 none 根本没有。

使用条形图而不是直线可以解决这个问题,因为它可以更清楚地表明数据点可以位于条形图宽度所覆盖的值范围内的任何位置,您只需要说明是否保留了条形图或右包含 x 轴上的标签。

关于平滑曲线的问题。据我所知,最常用的方法是使用核密度估计。你可以阅读它 here and see how it is implemented in Python here and here. More perspective on histograms versus kernel density estimates (KDE) and how to choose an optimal bandwidth can be found here and here.

这里是如何使用 pandas 绘制 KDE 的示例。我首先创建一个类似于您的示例的随机变量,并以相同的方式绘制它以进行比较。

import numpy as np                 # v 1.19.2
import pandas as pd                # v 1.1.3
import matplotlib.pyplot as plt    # v 3.3.2

# Create an integer-valued random variable using numpy
rng = np.random.default_rng(123)
variable = rng.laplace(loc=0, scale=5, size=1000).round()

# Create dataframe with values and probabilities
var_range = variable.max()-variable.min()
probabilities, values = np.histogram(variable, bins=int(var_range), density=True)

# Plot probability distribution like in your example
df = pd.DataFrame(dict(value=values[:-1], prob=probabilities))
df.plot.line(x='value', y='prob')

现在不管我提到为什么不推荐这样绘制分布的原因,与直接绘制原始变量相比,尝试根据计算的概率密度在此图上绘制 KDE 将是一件令人头疼的事情。事实上,Python 中的绘图包被构建为使用原始数据测量而不是计算的概率来处理变量的概率分布。以下示例说明了这一点。

# Better way to plot the probability distribution of measured data,
# with a kernel density estimate

s = pd.Series(variable)

# Plot pandas histogram
s.plot.hist(bins=20, density=True, edgecolor='w', linewidth=0.5)

# Save default x-axis limits for final formatting because the pandas kde
# plot uses much wider limits which decreases readability
ax = plt.gca()
xlim = ax.get_xlim()

# Plot pandas KDE
s.plot.density(color='black', alpha=0.5) # identical to s.plot.kde(...)

# Reset hist x-axis limits and add legend
ax.set_xlim(xlim)
ax.legend(labels=['KDE'], frameon=False)

您可以使用以下代码获得与 seaborn 相同的绘图。

import seaborn as sns    # v 0.11.0
sns.histplot(data=variable, bins=20, stat='density', alpha= 1, kde=True,
             edgecolor='white', linewidth=0.5,
             line_kws=dict(color='black', alpha=0.5,
                           linewidth=1.5, label='KDE'))
plt.gca().get_lines()[0].set_color('black') # manually edit line color due to bug in sns v 0.11.0
plt.legend(frameon=False)

文档:pandas, seaborn