Python获取Nemenyi检验临界值杂记

最近学到机器学习中的Friedman检验及Nemenyi后续检验[1],注意到Nemenyi检验的临界值域

$$CD=q_\alpha\sqrt{\dfrac{k(k+1)}{6N}}$$

其中的$q_\alpha$是由显著性水平$\alpha$及算法个数$k$查表得到的。

在[1]中,表2.7已给出Nemenyi检验中常用的临界值,表旁边注提示此值与“Tukey分布的临界值”相关,亦给出此值在R语言中的计算方法:

1
nemenyi_quantile <- qtukey(1 - alpha, k, Inf) / sqrt(2)

前文提及过的二项检验、$t$检验、$\chi^2$检验、$F$检验,[1]中除给出部分临界值表之外,边注亦同时给出了临界值在R和MATLAB中的计算方法。对于这几种检验,用Python也同样容易实现,只需利用scipy.stats模块中的相应模块的ppf()方法即可。以$\chi^2$检验为例,得到显著性水平为alpha、自由度为k - 1的$\chi^2$检验临界值的Python代码如下:

1
2
3
from scipy.stats import chi2

chi2_quantile = chi2.ppf(1 - alpha, k - 1)

scipy.stats中虽有binomtchi2f等模块,但似乎并没有提供Nemenyi检验或Tukey检验相关的模块。发现其中虽然存在名称相近的tukey_hsd()方法,但也不能用来计算Nemenyi检验的临界值。

查找Tukey检验相关内容,发现其基于学生化极差分布(studentized range distribution)。再次在scipy.stats模块中查看,发现了studentized_range模块,其中提供了熟悉的ppf()方法。参考[1]中的R代码,可以使用:

1
2
3
4
from scipy.stats import studentized_range
import math

nemenyi_quantile = studentized_range.ppf(1 - alpha, k, math.inf) / math.sqrt(2)

另外在查找过程中偶然发现statsmodels库的stats.libqsturng模块中提供的qsturng()方法也可用于计算学生化极差分布的临界值。statsmodels库并未就此方法提供文档,查阅此方法源代码[2]中的注释,确认此函数与R中的qtukey()函数等价,也不难给出:

1
2
3
4
from statsmodels.stats.libqsturng import qsturng
import math

nemenyi_quantile = qsturng(1 - alpha, k, math.inf) / math.sqrt(2)

不过,在确认这两个方法计算的临界值是否正确(毕竟不像$t$、$\chi^2$那样同名,同名的肯定是同一个分布就不用确认了)时,意外发现部分结果在四舍五入至千分位后,与[1]中表2.7所给临界值表相差0.001:

$\alpha$值 $k$值 [1]中所给临界值 scipy库计算的临界值 statsmodels库计算的临界值
0.05 7 2.949 2.948 2.948
0.10 5 2.459 2.460 2.459
0.10 6 2.589 2.589 2.588
0.10 7 2.693 2.693 2.692
0.10 9 2.855 2.855 2.854

不禁好奇为何会出现0.001的偏差。考虑到[1]中给出了R代码,尝试利用R计算上述几组$\alpha$、$k$值对应的临界值,结果更加出人意料:

$\alpha$值 $k$值 [1]中所给临界值 R计算的临界值 scipy库计算的临界值 statsmodels库计算的临界值
0.05 7 2.949 2.94832 2.948320017529675 2.9482857187201383
0.10 5 2.459 2.459516 2.4595157642714183 2.459359694834468
0.10 6 2.589 2.588521 2.588520601922348 2.5882688946620926
0.10 7 2.693 2.692732 2.6927321009677594 2.692460382234158
0.10 9 2.855 2.854606 2.854606431198026 2.854379183347442

除了$\alpha=0.05,k=7$和$\alpha=0.10,k=5$这两组之外,[1]中所给临界值都与R的计算结果四舍五入至千分位后相等。R与scipy库的结果基本一致,但是statsmodels库的计算结果居然在万分位就开始出现了巨大的偏差!而$\alpha=0.10,k=5$这一组,[1]中的临界值却和statsmodels库计算的临界值相近。

考虑到可能三者的实现方法不同。阅读qtukey()函数的文档[3]studentized_range模块的文档[4]qsturng()方法的源代码[2],发现确实如此:R中qtukey()函数的实现基于Copenhaver和Holland(1988)[5]的方法,scipy库中studentized_range.ppf()方法的实现基于Lund和Lund(1983)[6]的方法,而statsmodels库中qsturng()方法的实现基于Gleason(1999)[7]的方法。[7]认为自己提出的计算方法比[6]更简单、更准确。关于三者具体有何不同、何者更优,已偏离“机器学习”的主题甚远,本文不再深入讨论。

至于[1]中为何给出了异于以上任何一种实现算得的临界值,在该章末尾的参考文献[8]中可以找到答案。[8]中表5(a)与[1]中表2.7所给数据完全相同,[8]也是博主目前能找到的最早给出$\alpha=0.05,k=7$时临界值$q_\alpha=2.949$的文献,可能也是最早提出将Nemenyi方法用于模型比较的。[8]中只提及$q_\alpha$的计算方法是学生化极差的数据除以$\sqrt2$,并未提及具体如何计算学生化极差的临界值,或许是采用的数据来源有舍入而损失了精度吧。

参考资料

  1. 周志华.机器学习[M].北京:清华大学出版社.2016:42-44.
  2. statsmodels.qsturng_.py[CP/OL].[2024-02-02].https://github.com/statsmodels/statsmodels/blob/main/statsmodels/stats/libqsturng/qsturng_.py.
  3. DataCamp.Tukey function[EB/OL]//RDocumentation[2024-02-03].https://www.rdocumentation.org/packages/stats/versions/3.6.2/topics/Tukey.
  4. The SciPy community.scipy.stats.studentized_range[EB/OL]//SciPy v1.12.0 Manual[2024-02-03].https://docs.scipy.org/doc/scipy/reference/generated/scipy.stats.studentized_range.html.
  5. Copenhaver M D,Holland B.Computation of the distribution of the maximum studentized range statistic with application to multiple significance testing of simple effects[J].Journal of Statistical Computation and Simulation,1988,30(1):1-15.
  6. Lund R E,Lund J R.Algorithm AS 190:Probabilities and Upper Quantiles for the Studentized Range[J].Journal of the Royal Statistical Society,1983,32(2),204-210.
  7. Gleason J R.An accurate,non-iterative approximation for studentized range quantiles[J].Computational Statistics&Data Analysis,1999,31(2):147-158.
  8. Demšar J.Statistical comparisons of classifiers over multiple data sets[J].Journal of Machine learning research,2006,7:1-30.