使用频率、区间、CDF进行卡方检验,Python

2 投票
2 回答
3833 浏览
提问于 2025-04-16 06:01

我正在尝试自己从头写一个卡方拟合优度检验,用于Beta分布,不想使用任何外部函数。下面的代码报告说拟合结果是'1',但实际上使用scipy.stats中的kstest函数时返回的是零。我的数据是正态分布的,所以我的函数也应该返回零。

import numpy as np
from scipy.stats import chi2
from scipy.stats import beta
from scipy.stats import kstest
from scipy.stats import norm

preds = norm.rvs(5,2,size=200)
preds.sort()

bin_size = 30
bins = np.linspace(0,10,bin_size)
counts = np.digitize(preds, bins)
mean = 5
var = 2

sum = 0
for i in range(len(bins)-1):
    p = beta.cdf(bins[i+1], mean, var) - beta.cdf(bins[i], mean, var)  
    freq = len(counts[counts==i]) / float(len(counts))    
    sum = sum + ((freq - p)**2)/p

dof = len(counts)-2
pval = 1 - chi2.cdf(sum, dof)
print pval

在代码中,我创建了数据区间,基于这些区间测量频率,使用Beta分布的累积分布函数(CDF)计算预期频率,然后把这些结果加起来,得到了X^2检验统计量。

kstest的调用是

print kstest(preds, 'beta', [mean, var])

我在这里做错了什么呢?

谢谢,

2 个回答

3

我觉得你自己对问题的回答不太正确,代码里有一系列问题。

首先,根据你的实现,使用 len(counts)-2 计算的自由度(dof)和 len(preds)-2 是一样的。所以改变这个其实没有什么区别。

其次,要对参数拟合进行卡方(Chi^2)检验,你需要构建一些互不重叠的区间,这叫做MECE,意思是区间之间不能有重叠,并且这些区间要覆盖所有可能的 X 值。然而,你用 bins = np.linspace(0,10,bin_size) 设置的区间,强制把最右边的区间限制在 10。而高斯分布的范围是从负无穷到正无穷。所以你生成的随机数有可能会超过 10

但这可能比另一个问题要小:每个区间的计数通常要求至少为5。然而,使用你的方法来统计落入区间的数字(这里你设置了30个区间),可能会出现数字少于5,甚至是0的情况。任何区间的计数为0会导致后续的 sum 计算变成无穷大,这样无论拟合好坏都会导致拒绝结果。我觉得这就是你在把自由度改为 len(preds)-2 后得到0的原因,因为你恰好在某个区间的计数中有至少一个0。

另一个问题是卡方的计算。我觉得你没有使用频率,而是用了每个区间的实际计数:

p = beta.cdf(bins[i+1], mean, var) - beta.cdf(bins[i], mean, var)  
p = p*200
freq = len(counts[counts==i])    
sum = sum + ((freq - p)**2)/p

所以 pfreq 都是每个类别的计数,而不是相对频率。不过我对此不是特别确定。

最后,自由度的定义是区间数减去拟合的参数个数(这里是2)再减1。所以如果你有10个区间,dof = 10 - 2 - 1 = 7。在你的代码中是 `200 - 2 = 198`。这样的自由度的卡方分布非常扁平,这意味着你需要非常大的卡方值才能拒绝拟合。这就是你在代码中得到1的原因。

0

问题出在自由度的定义上:

dof = len(preds)-2

这是正确的选择。此外,我还需要把箱子的大小减小到15,才能得到一致的'0'结果。大家都知道,卡方检验对箱子的大小很敏感。

撰写回答