如何使用Python科学库执行卡方拟合优度检验?

19

假设我有一些经验获得的数据:

from scipy import stats
size = 10000
x = 10 * stats.expon.rvs(size=size) + 0.2 * np.random.uniform(size=size)

它是指数分布(带有一些噪音),我想使用卡方适合度(GoF)测试来验证这一点。使用Python中的标准科学库(例如scipy或statsmodels)以最少的手动步骤和假设,最简单的方法是什么?

我可以使用以下模型进行拟合:

param = stats.expon.fit(x)
plt.hist(x, normed=True, color='white', hatch='/')
plt.plot(grid, distr.pdf(np.linspace(0, 100, 10000), *param))

分布和经验数据图

计算Kolmogorov-Smirnov检验非常简洁。

>>> stats.kstest(x, lambda x : stats.expon.cdf(x, *param))
(0.0061000000000000004, 0.85077099515985011)

然而,我找不到一个好的方法来计算卡方检验。

statsmodel 中有一个离散分布的卡方拟合函数,但是指数分布是连续的,不能使用该函数。

official scipy.stats 教程 只讲解了自定义分布的情况,要通过调整许多表达式(npoints,npointsh,nbound,normbound)来构建概率分布,因此对我来说如何为其他分布进行计算并不很清楚。 chisquare 的示例 假设已经得到了期望值和自由度。

此外,我不想像这里讨论的那样“手动”执行测试,而是想知道如何应用其中一个可用的库函数。


2
据我所知,没有包含连续分布分箱的“官方”Python库函数可用于卡方检验。如果我没记错的话,我建议使用Anderson-Darling、scipy的anderson函数,这应该具有更好的功效。 - Josef
好的,但据我所见,SciPy中的anderson实现仅支持5种分布。 - metakermit
是的,但是anderson支持您正在使用的指数分布。如果您估计分布的参数并希望它适用于任何分布,则需要回到chisquare进行分箱,或者引导另一个gof测试。 - Josef
请问您能否在回答中解释一下如何对我的示例执行分箱和卡方检验?我知道我需要使用hstack并组合箱子以获得>5个数据点,但我不知道如何获取这些箱子的概率数组。我正在尝试找到一个通用的工作流程,可以用于任意数据,并且我不想像使用anderson实现时那样仅限于少数分布。 - metakermit
你使用 Kolmogorov-Smirnov 检验的方式在统计上是错误的,因为分布的参数是从样本中估计出来的。正确的方法是使用 Lilliefors 测试:https://en.wikipedia.org/wiki/Lilliefors_test。 - Michael Baudin
3个回答

5
一种等概率分组的近似解决方案:
  • 估计分布的参数
  • 使用反函数 cdf,如果是 scipy.stats.distribution,则使用 ppf 将其转换为常规概率网格的 bin 边界,例如 distribution.ppf(np.linspace(0, 1, n_bins + 1),*args)
  • 然后,使用 np.histogram 计算每个 bin 中观测值的数量

然后在频率上使用卡方检验。

另一种方法是从已排序数据的百分位数中找到 bin 边缘,并使用 cdf 找到实际概率。

这只是近似解,因为 chisquare 检验的理论假设参数是通过对分组数据进行最大似然估计得出的。而且我不确定基于数据选择的 bin 边缘是否会影响渐进分布。

我已经有很长时间没有研究过这个问题了。如果近似解不够好,那么我建议您在 stats.stackexchange 上提出问题。


1
回复:是否分箱会影响渐近分布,它几乎一定会。不过可能是可以忽略的。对于分箱和使用卡方检验,这将是正确的答案。+1 - gung - Reinstate Monica
@Gung 这取决于渐近性的性质。我认为,如果您以一种允许最小期望箱计数增长的方式拟合切点,则渐近分布应为卡方分布。但是,渐近分布并不重要:重要的是实际分布,并且很明显,基于数据建立切点将引入该分布的任意更改(即使只有一点点)。 - whuber
@user333700 请问您能否提供一下您所提供的解决方案的示例?我尝试了以下代码:In: np.random.seed(453), In: data_1 = stats.norm.rvs(size=10000), In: loc, scale = stats.norm.fit(data_1), In: data_2 = stats.norm(loc, scale).rvs(size=10000), In: data_1_hist = np.histogram(data_1, bins=10), In: data_2_hist = np.histogram(data_2, bins=10), In: print stats.chisquare(data_2_hist[0], data_1_hist[0]), Out: (statistic=564.43784612331842, pvalue=8.926608295951506e-116). 此外,distribution.ppf(np.linspace(0, 1, n_bins + 1), *args)应该如何使用呢? - Julia

2
为什么需要“验证”它是指数的?您确定需要进行统计测试吗?我可以几乎保证它最终不是指数,如果您有足够的数据,测试将是显著的,这使得使用测试的逻辑相当强制。阅读此CV线程可能会对您有所帮助:正态性测试是否“基本无用”?或者我的答案在这里:使用许多观察值进行异方差性检验
通常最好使用qq-图和/或pp-图(取决于您是否关心分布尾部或中间的拟合情况,参见我的答案:PP-图与QQ-图)。有关如何在Python SciPy中制作qq-图的信息可以在此SO线程中找到:使用SciPy制作分位数-分位数图

1
有方法可以量化两个分布之间的相似程度。统计学上的“检验”并不能完全给出这一点,因为p值是该距离和N的函数。您可以使用qq-或pp-图中点的相关性(但请记住r始终接近1),也可以使用类似于KL的东西(实际上不是距离)。您还可以在CV上提问,了解获取两个分布之间距离的定量测量的最佳方法。结果会变得复杂,并取决于您的需求。 - gung - Reinstate Monica
1
chisquare 给出了一个距离的度量,你也可以选择其他任何“距离度量”作为gof测试。然而,它不会告诉你其大小。这些问题并不特定于gof测试。在所有假设检验中,你都必须担心样本量过小导致功效不足,或者样本量过大导致功效过高。statsmodels有函数来计算卡方检验的效应大小和功效,例如http://statsmodels.sourceforge.net/devel/generated/statsmodels.stats.gof.chisquare_effectsize.html。 - Josef
1
如果你在看完直方图后首先假设分布是指数分布,那么任何拟合优度检验的p值都会过于乐观。无论如何,它们永远不会告诉你可以确定基础人口或过程是否为指数分布,但如果你得到一个非常低的p值,至少你有一个基础来表明指数假设是不正确的。 - whuber
1
感谢所有的建议。一如既往,我发现我还有很多需要学习的地方。如果有一本书能够解释如何在实际数据分析示例中使用一些scipy/statsmodels函数,那就太好了。目前的文档对我来说太过匮乏,无法理解所有的函数。我会在CV上发布任何与统计相关的问题。 - metakermit
@metakermit,自那时起,您是否找到了一本包含Python / Pandas实际数据示例的书籍? - denis
显示剩余3条评论

1

我尝试使用OpenTURNS解决您的问题。 开头相同:

import numpy as np
from scipy import stats
size = 10000
x = 10 * stats.expon.rvs(size=size) + 0.2 * np.random.uniform(size=size)

如果您怀疑您的样本x来自指数分布,您可以使用ot.ExponentialFactory()来拟合参数:
import openturns as ot
sample = ot.Sample([[p] for p in x])
distribution = ot.ExponentialFactory().build(sample)

作为Factory需要一个ot.Sample()作为输入,我需要格式化x并将其重塑为1维的10,000个点。
现在让我们使用卡方检验来评估这个拟合:
result = ot.FittingTest.ChiSquared(sample, distribution, 0.01)
print('Exponential?', result.getBinaryQualityMeasure(), ', P-value=', result.getPValue())
>>> Exponential? True , P-value= 0.9275212544642293

非常好!
当然,print(distribution) 将为您提供拟合参数:
>>> Exponential(lambda = 0.0982391, gamma = 0.0274607)

enter image description here


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接