绘制最大似然估计的置信区间

时间:2014-01-30 10:09:48

标签: python numpy statistics scipy statsmodels

我正在尝试编写代码,以便为库中不同书籍的数量生成置信区间(以及生成信息图)。

我堂兄在小学,每周都会给老师讲一本书。然后他读取并及时返回,以便在下周获得另一个。过了一会儿,我们开始注意到他以前读过的书,随着时间的推移逐渐变得越来越普遍。

假设图书馆中真实的图书数量为N,教师会随机选择一个(有替换),每周都会给你。如果在第t周,您收到的图书的读取次数是x,那么我可以根据https://math.stackexchange.com/questions/615464/how-many-books-are-in-a-library生成图书馆中图书数量的最大似然估计值。


示例:考虑一个包含五本书A,B,C,D和E的图书馆。如果您收到七本书[A,B,A,C,B,B,D]连续几周,然后x的值(重复的数量)将在每个星期之后为[0,0,1,1,2,3,3],这意味着在七周之后,你已经收到了一本书已经有了读了三次。


为了可视化函数的可视化(假设我已经理解了什么是正确的)我写了下面的代码,我相信它绘制了似然函数。最大值约为135,这实际上是根据上述MSE链接的最大似然估计。

from __future__ import division
import random
import matplotlib.pyplot as plt
import numpy as np

#N is the true number of books. t is the number of weeks.unk is the true number of repeats found 
t = 30
unk = 3
def numberrepeats(N, t):
    return t - len(set([random.randint(0,N) for i in xrange(t)]))

iters = 1000
ydata = []
for N in xrange(10,500):
    sampledunk = [numberrepeats(N,t) for i in xrange(iters)].count(unk)
    ydata.append(sampledunk/iters)

print "MLE is", np.argmax(ydata)
xdata = range(10, 500)
print len(xdata), len(ydata)
plt.plot(xdata,ydata)
plt.show()

输出看起来像

enter image description here

我的问题是这些:

  • 是否有一种简单的方法可以获得95%的置信区间并将其绘制在图表上?
  • 如何在曲线上叠加平滑的曲线?
  • 我的代码应该写得更好吗?它不是很优雅,也很慢。

找到95%置信区间意味着找到x轴的范围,这样95%的时间我们通过采样获得的经验最大似然估计(在本例中理论上应该是135)将落在其中。 @mbatchkarov给出的答案目前没有正确执行此操作。


https://math.stackexchange.com/questions/656101/how-to-find-a-confidence-interval-for-a-maximum-likelihood-estimate现在有一个数学答案。

3 个答案:

答案 0 :(得分:8)

看起来你在第一部分没问题,所以我将解决你的第二和第三点。

有很多方法可以使用scipy.interpolate和样条曲线或scipy.optimize.curve_fit来拟合平滑曲线。就个人而言,我更喜欢curve_fit,因为你可以提供自己的功能并让它适合你的参数。

或者,如果您不想学习参数函数,可以使用numpy.convolve进行简单的滚动窗口平滑。

至于代码质量:你没有利用numpy的速度,因为你在纯python中做事。我会写这样的(现有)代码:

from __future__ import division
import numpy as np
import matplotlib.pyplot as plt

# N is the true number of books.
# t is the number of weeks.
# unk is the true number of repeats found 
t = 30
unk = 3
def numberrepeats(N, t, iters):
    rand = np.random.randint(0, N, size=(t, iters))
    return t - np.array([len(set(r)) for r in rand])

iters = 1000
ydata = np.empty(500-10)
for N in xrange(10,500):
    sampledunk = np.count_nonzero(numberrepeats(N,t,iters) == unk)
    ydata[N-10] = sampledunk/iters

print "MLE is", np.argmax(ydata)
xdata = range(10, 500)
print len(xdata), len(ydata)
plt.plot(xdata,ydata)
plt.show()

可能更有可能对此进行优化,但这种变化会使我的代码的运行时间在我的机器上从大约30秒到大约2秒。

答案 1 :(得分:6)

获得置信区间的简单(数字)方法只是多次运行脚本,并查看估算值的变化程度。您可以使用该标准差来计算置信区间。

为了节省时间,另一种选择是在N的每个值(我使用2000)上运行一系列试验,然后使用这些试验的随机子采样来估计估计量标准差。基本上,这涉及选择试验的子集,使用该子集生成可能性曲线,然后找到该曲线的最大值以获得估算器。您可以在许多子集上执行此操作,这会为您提供一组估算器,您可以使用这些估算器在估算器中查找置信区间。我的完整脚本如下:

import numpy as np

t = 30
k = 3
def trial(N):
    return t - len(np.unique(np.random.randint(0, N, size=t)))

def trials(N, n_trials):
    return np.asarray([trial(N) for i in xrange(n_trials)])

n_trials = 2000
Ns = np.arange(1, 501)
results = np.asarray([trials(N, n_trials=n_trials) for N in Ns])

def likelihood(results):
    L = (results == 3).mean(-1)

    # boxcar filtering
    n = 10
    L = np.convolve(L, np.ones(n) / float(n), mode='same')

    return L

def max_likelihood_estimate(Ns, results):
    i = np.argmax(likelihood(results))
    return Ns[i]

def max_likelihood(Ns, results):
    # calculate mean from all trials
    mean = max_likelihood_estimate(Ns, results)

    # randomly subsample results to estimate std
    n_samples = 100
    sample_frac = 0.25
    estimates = np.zeros(n_samples)
    for i in xrange(n_samples):
        mask = np.random.uniform(size=results.shape[1]) < sample_frac
        estimates[i] = max_likelihood_estimate(Ns, results[:,mask])

    std = estimates.std()
    sterr = std * np.sqrt(sample_frac) # is this mathematically sound?
    ci = (mean - 1.96*sterr, mean + 1.96*sterr)
    return mean, std, sterr, ci

mean, std, sterr, ci = max_likelihood(Ns, results)
print "Max likelihood estimate: ", mean
print "Max likelihood 95% ci: ", ci

这种方法有两个缺点。其中之一是,由于您从同一组试验中获取了许多子样本,因此您的估算并非独立。为了限制这种影响,我只使用了每个子集的25%的结果。另一个缺点是每个子样本只是数据的一小部分,因此从这些子集派生的估计值将比运行完整脚本多次得出的估计值具有更多的方差。为了解释这一点,我计算了标准误差作为标准偏差除以4的平方根,因为我的完整数据集中的数据是其中一个子样本的四倍。但是,我对蒙特卡罗理论不太熟悉,不知道这是否在数学上是合理的。多次运行我的脚本似乎表明我的结果是合理的。

最后,我确实在似然曲线上使用了一个盒式滤波器来平滑它们。理想情况下,这应该可以改善结果,但即使使用过滤,结果仍然存在相当大的可变性。在计算整体估算器的值时,我不确定从所有结果计算一个似然曲线是否会更好并使用最大值(这是我最终做的),或者使用所有结果的均值子集估计量。使用子集估计器的平均值可能有助于抵消过滤后剩余的曲线中的一些粗糙度,但我不确定。

答案 2 :(得分:5)

以下是对您的第一个问题的回答以及pointer对第二个问题的解决方案:

plot(xdata,ydata)
#  calculate the cumulative distribution function
cdf = np.cumsum(ydata)/sum(ydata)
# get the left and right boundary of the interval that contains 95% of the probability mass 
right=argmax(cdf>0.975)
left=argmax(cdf>0.025)
# indicate confidence interval with vertical lines
vlines(xdata[left], 0, ydata[left])
vlines(xdata[right], 0, ydata[right])
# hatch confidence interval
fill_between(xdata[left:right], ydata[left:right], facecolor='blue', alpha=0.5)

这产生了下图: enter image description here

当我有更多时间时,我会尝试回答问题3:)