KStwo 分布#

这是从 \(n\) 个样本或观察值计算的经验分布函数与假定为连续的比较(或目标)累积分布函数之间的最大绝对差值的分布。(名称中的“two”是因为这是双边差。 ksone 是正差 \(D_n^+\) 的分布,因此它涉及单边差。 kstwobign归一化最大绝对差值 \(\sqrt{n} D_n\) 的极限分布。)

写成 \(D_n = \sup_t \left|F_{empirical,n}(t)-F_{target}(t)\right|\)kstwo\(D_n\) 值的分布。

kstwo 也可以用于两个经验分布函数之间的差异,分别对应具有 \(m\)\(n\) 个样本的观察集。写成 \(D_{m,n} = \sup_t \left|F_{1,m}(t)-F_{2,n}(t)\right|\),其中 \(F_{1,m}\)\(F_{2,n}\) 是两个经验分布函数,则在适当条件下,\(Pr(D_{m,n} \le x) \approx Pr(D_N \le x)\),其中 \(N = \sqrt{\left(\frac{mn}{m+n}\right)}\).

有一个形状参数 \(n\),一个正整数,并且支持为 \(x\in\left[0,1\right]\).

该实现遵循 Simard & L’Ecuyer,它将 Durbin 和 Pomeranz 的精确算法与 Li-Chien、Pelz 和 Good 的渐近估计相结合,以计算具有 5-15 位有效数字的 CDF。

示例#

>>> import numpy as np
>>> from scipy.stats import kstwo

显示大小为 5 的样本中至少与 0、0.5 和 1.0 一样大的间隙的概率

>>> kstwo.sf([0, 0.5, 1.0], 5)
array([1.   , 0.112, 0.   ])

将大小为 5 的样本(从源 N(0.5, 1) 分布中抽取)与目标 N(0, 1) CDF 进行比较。

>>> from scipy.stats import norm
>>> n = 5
>>> gendist = norm(0.5, 1)       # Normal distribution, mean 0.5, stddev 1
>>> x = np.sort(gendist.rvs(size=n, random_state=np.random.default_rng()))
>>> x
array([-1.59113056, -0.66335147,  0.54791569,  0.78009321,  1.27641365])  # may vary
>>> target = norm(0, 1)
>>> cdfs = target.cdf(x)
>>> cdfs
array([0.0557901 , 0.25355274, 0.7081251 , 0.78233199, 0.89909533])   # may vary
>>> # Construct the Empirical CDF and the K-S statistics (Dn+, Dn-, Dn)
>>> ecdfs = np.arange(n+1, dtype=float)/n
>>> cols = np.column_stack([x, ecdfs[1:], cdfs, cdfs - ecdfs[:n], ecdfs[1:] - cdfs])
>>> np.set_printoptions(precision=3)
>>> cols
array([[-1.591,  0.2  ,  0.056,  0.056,  0.144],     # may vary
       [-0.663,  0.4  ,  0.254,  0.054,  0.146],
       [ 0.548,  0.6  ,  0.708,  0.308, -0.108],
       [ 0.78 ,  0.8  ,  0.782,  0.182,  0.018],
       [ 1.276,  1.   ,  0.899,  0.099,  0.101]])
>>> gaps = cols[:, -2:]
>>> Dnpm = np.max(gaps, axis=0)
>>> Dn = np.max(Dnpm)
>>> iminus, iplus = np.argmax(gaps, axis=0)
>>> print('Dn- = %f (at x=%.2f)' % (Dnpm[0], x[iminus]))
Dn- = 0.246201 (at x=-0.14)
>>> print('Dn+ = %f (at x=%.2f)' % (Dnpm[1], x[iplus]))
Dn+ = 0.224726 (at x=0.19)
>>> print('Dn  = %f' % (Dn))
Dn  = 0.246201
>>> probs = kstwo.sf(Dn, n)
>>> print(chr(10).join(['For a sample of size %d drawn from a N(0, 1) distribution:' % n,
...      ' Kolmogorov-Smirnov 2-sided n=%d: Prob(Dn >= %f) = %.4f' % (n, Dn, probs)]))
For a sample of size 5 drawn from a N(0, 1) distribution:
 Kolmogorov-Smirnov 2-sided n=5: Prob(Dn >= 0.246201) = 0.8562

绘制经验 CDF 与目标 N(0, 1) CDF

>>> import matplotlib.pyplot as plt
>>> plt.step(np.concatenate([[-3], x]), ecdfs, where='post', label='Empirical CDF')
>>> x3 = np.linspace(-3, 3, 100)
>>> plt.plot(x3, target.cdf(x3), label='CDF for N(0, 1)')
>>> plt.ylim([0, 1]); plt.grid(True); plt.legend();
>>> plt.vlines([x[iminus]], ecdfs[iminus], cdfs[iminus], color='r', linestyle='solid', lw=4)
>>> plt.vlines([x[iplus]], cdfs[iplus], ecdfs[iplus+1], color='m', linestyle='solid', lw=4)
>>> plt.annotate('Dn-', xy=(x[iminus], (ecdfs[iminus]+ cdfs[iminus])/2),
...              xytext=(x[iminus]+1, (ecdfs[iminus]+ cdfs[iminus])/2 - 0.02),
...              arrowprops=dict(facecolor='white', edgecolor='r', shrink=0.05), size=15, color='r');
>>> plt.annotate('Dn+', xy=(x[iplus], (ecdfs[iplus+1]+ cdfs[iplus])/2),
...             xytext=(x[iplus]-2, (ecdfs[iplus+1]+ cdfs[iplus])/2 - 0.02),
...             arrowprops=dict(facecolor='white', edgecolor='m', shrink=0.05), size=15, color='m');
>>> plt.show()

参考资料#

  • “Kolmogorov-Smirnov 检验”,维基百科 https://en.wikipedia.org/wiki/Kolmogorov-Smirnov_test

  • Durbin J. “样本分布函数位于两条平行直线之间的概率。” Ann. Math. Statist.,39 (1968) 39, 398-411。

  • Pomeranz J. “小样本 Kolmogorov-Smirnov 统计量的精确累积分布(算法 487)。” ACM 通讯,17(12),(1974) 703-704。

  • Li-Chien, C. “关于 A. N. Kolmogorov 统计量的精确分布及其渐近展开。” 中国数学学报,6,(1956) 55-81。

  • Pelz W, Good IJ. “近似 Kolmogorov-Smirnov 单样本统计量的下尾部区域。” 皇家统计学会杂志,系列 B,(1976) 38(2), 152-156。

  • Simard, R., L’Ecuyer, P. “计算双边 Kolmogorov-Smirnov 分布”,统计软件杂志,第 39 卷,(2011) 11。

实现:scipy.stats.kstwo