如何在Python中正确拟合贝塔分布?
我正在尝试找到一种正确的方法来拟合贝塔分布。这并不是一个真实世界的问题,我只是测试几种不同方法的效果,但在这个过程中有些地方让我感到困惑。
这是我正在使用的Python代码,我测试了三种不同的方法:
- 使用矩(样本均值和方差)进行拟合。
- 通过最小化负对数似然(使用scipy.optimize.fmin())进行拟合。
- 直接调用scipy.stats.beta.fit()。
from scipy.optimize import fmin
from scipy.stats import beta
from scipy.special import gamma as gammaf
import matplotlib.pyplot as plt
import numpy
def betaNLL(param,*args):
'''Negative log likelihood function for beta
<param>: list for parameters to be fitted.
<args>: 1-element array containing the sample data.
Return <nll>: negative log-likelihood to be minimized.
'''
a,b=param
data=args[0]
pdf=beta.pdf(data,a,b,loc=0,scale=1)
lg=numpy.log(pdf)
#-----Replace -inf with 0s------
lg=numpy.where(lg==-numpy.inf,0,lg)
nll=-1*numpy.sum(lg)
return nll
#-------------------Sample data-------------------
data=beta.rvs(5,2,loc=0,scale=1,size=500)
#----------------Normalize to [0,1]----------------
#data=(data-numpy.min(data))/(numpy.max(data)-numpy.min(data))
#----------------Fit using moments----------------
mean=numpy.mean(data)
var=numpy.var(data,ddof=1)
alpha1=mean**2*(1-mean)/var-mean
beta1=alpha1*(1-mean)/mean
#------------------Fit using mle------------------
result=fmin(betaNLL,[1,1],args=(data,))
alpha2,beta2=result
#----------------Fit using beta.fit----------------
alpha3,beta3,xx,yy=beta.fit(data)
print '\n# alpha,beta from moments:',alpha1,beta1
print '# alpha,beta from mle:',alpha2,beta2
print '# alpha,beta from beta.fit:',alpha3,beta3
#-----------------------Plot-----------------------
plt.hist(data,bins=30,normed=True)
fitted=lambda x,a,b:gammaf(a+b)/gammaf(a)/gammaf(b)*x**(a-1)*(1-x)**(b-1) #pdf of beta
xx=numpy.linspace(0,max(data),len(data))
plt.plot(xx,fitted(xx,alpha1,beta1),'g')
plt.plot(xx,fitted(xx,alpha2,beta2),'b')
plt.plot(xx,fitted(xx,alpha3,beta3),'r')
plt.show()
我遇到的问题是关于归一化过程(z=(x-a)/(b-a)
),其中a
和b
分别是样本的最小值和最大值。
当我不进行归一化时,一切都正常,不同拟合方法之间有些微小的差异,但都还不错。
但是当我进行了归一化后,得到的结果图如下。
只有矩方法(绿色线)看起来还可以。
而scipy.stats.beta.fit()方法(红色线)始终是均匀的,无论我用什么参数生成随机数。
至于最大似然估计(蓝色线),则失败了。
所以看起来归一化造成了这些问题。但我认为在贝塔分布中,x=0
和x=1
是合法的。如果是一个真实世界的问题,难道不应该第一步就将样本观察值归一化到[0,1]之间吗?在这种情况下,我应该如何拟合曲线呢?
3 个回答
我使用了在doi:10.1080/00949657808810232中提到的方法来拟合beta参数:
from scipy.special import psi
from scipy.special import polygamma
from scipy.optimize import root_scalar
from numpy.random import beta
import numpy as np
def ipsi(y):
if y >= -2.22:
x = np.exp(y) + 0.5
else:
x = - 1/ (y + psi(1))
for i in range(5):
x = x - (psi(x) - y)/(polygamma(1,x))
return x
#%%
# q satisface
# psi(q) - psi(ipsi(lng1 - lng2 + psi(q)) + q) -lng2 = 0
# O sea, busco raíz de
# f(q) = psi(q) - psi(ipsi(lng1 - lng2 + psi(q)) + q) -lng2
# luego:
# p = ipsi(lng1 - lng2 + psi(q))
def f(q,lng1,lng2):
return psi(q) - psi(ipsi(lng1 - lng2 + psi(q)) + q) -lng2
#%%
def ml_beta_pq(sample):
lng1 = np.log(sample).mean()
lng2 = np.log(1-sample).mean()
def g(q):
return f(q,lng1,lng2)
q=root_scalar(g,x0=1,x1=1.1).root
p = ipsi(lng1 - lng2 + psi(q))
return p, q
#%%
p = 2
q = 5
n = 1500
sample = beta(p,q,n)
ps,qs = ml_beta_pq(sample) #s de sombrero
print(f'Estimación de parámetros de una beta({p}, {q}) \na partir de una muestra de tamaño n = {n}')
print(f'\nn ={n:5d} | p | q')
print(f'---------+-------+------')
print(f'original | {p:2.3f} | {q:2.3f}')
print(f'estimado | {ps:2.3f} | {qs:2.3f}')
因为beta.fit
没有文档说明,所以找到相关信息有点困难。不过,如果你知道想要强制设置的上下限,可以使用参数floc
和fscale
。
我运行了你的代码,只使用了beta.fit
这个方法,并且试了有和没有floc
和fscale
这两个参数的情况。同时,我还用整数和浮点数来检查这些参数,以确保这不会影响你的结果。结果是没有影响(在这个测试中,我不能保证以后也不会)。
>>> from scipy.stats import beta
>>> import numpy
>>> def betaNLL(param,*args):
'''Negative log likelihood function for beta
<param>: list for parameters to be fitted.
<args>: 1-element array containing the sample data.
Return <nll>: negative log-likelihood to be minimized.
'''
a,b=param
data=args[0]
pdf=beta.pdf(data,a,b,loc=0,scale=1)
lg=numpy.log(pdf)
#-----Replace -inf with 0s------
lg=numpy.where(lg==-numpy.inf,0,lg)
nll=-1*numpy.sum(lg)
return nll
>>> data=beta.rvs(5,2,loc=0,scale=1,size=500)
>>> beta.fit(data)
(5.696963536654355, 2.0005252702837009, -0.060443307228404922, 1.0580278414086459)
>>> beta.fit(data,floc=0,fscale=1)
(5.0952451826831462, 1.9546341057106007, 0, 1)
>>> beta.fit(data,floc=0.,fscale=1.)
(5.0952451826831462, 1.9546341057106007, 0.0, 1.0)
总的来说,这似乎不会改变你的数据(通过归一化)或者丢弃数据。我觉得需要注意的是,使用这个方法时要小心。在你的情况下,你知道上下限是0和1,因为你得到的数据来自一个在0到1之间的特定分布。在其他情况下,可能知道上下限,但如果不知道,beta.fit
会自己计算。在这种情况下,如果不指定0和1的上下限,beta.fit
计算出的结果是loc=-0.06
和scale=1.058
。
问题在于,beta.pdf()
有时候会返回 0
和 inf
,尤其是在输入为 0
和 1
的时候。举个例子:
>>> from scipy.stats import beta
>>> beta.pdf(1,1.05,0.95)
/usr/lib64/python2.6/site-packages/scipy/stats/distributions.py:1165: RuntimeWarning: divide by zero encountered in power
Px = (1.0-x)**(b-1.0) * x**(a-1.0)
inf
>>> beta.pdf(0,1.05,0.95)
0.0
通过你的归一化过程,你保证在 0
和 1
这两个点上都有一个数据样本。虽然你对返回 0
的值进行了“修正”,但你并没有处理那些返回 inf
的值。为了处理这个问题,你可以简单地去掉所有不是有限值的数据:
def betaNLL(param,*args):
"""
Negative log likelihood function for beta
<param>: list for parameters to be fitted.
<args>: 1-element array containing the sample data.
Return <nll>: negative log-likelihood to be minimized.
"""
a, b = param
data = args[0]
pdf = beta.pdf(data,a,b,loc=0,scale=1)
lg = np.log(pdf)
mask = np.isfinite(lg)
nll = -lg[mask].sum()
return nll
不过,实际上你不应该这样进行归一化,因为这样做相当于把两个数据点从拟合中丢掉了。