Kolmogorov-Smirnov test

公開日: 2023-07-02
更新日: 2023-08-13

  Table of Contents

KS testのスコープ

\[X_1, X_2, \cdots, X_n \sim_{i.i.d} \mathbb P \tag{1}\]

とデータが与えられたとき, 以下のような形で帰無仮説を設定する場合を考えます

\[\begin{align*} &H_0: \mathbb P = \mathbb P_0\\ &H_1: \mathbb P \neq \mathbb P_0 \end{align*}\]

$\mathbb P$が離散分布ならば「chi-squared goodness-of-fit test」で検定することができますが, $\mathbb P$が連続分布の場合, 検定手法の一つとして「Kolmogorov-Smirnov test」を用いることができます.

KS Testの考え方

(1)同様に, $X_1, X_2, \cdots, X_n \sim_{i.i.d} \mathbb P$ を考えます. このとき, empirical c.d.fは以下のように定義されます:

\[F_n(x) = \mathbb P_n(X\leq x) = \frac{1}{n}\sum_{i\in N}I(X_i\leq x) \tag{2}\]
  • $x$以下で観測されるsample pointsの割合を表している

上記(2)について, 大数の法則により, 任意の $x\in\mathbb R$について以下の式が成立します:

\[F_n(x) \to \mathbb E[I(X_i\leq x)] = \mathbb P(X|i\leq x) = F(x) \tag{3}\]

任意の $x\in\mathbb R$について成立するので, (3)より

\[\sup_{x\in\mathbb R} |F_n(x)-F(x)|\to 0 \tag{4}\]

KS testはこのsupremumが未知の分布 $\mathbb P$に依存しないことに着目した統計量という特徴があります.

なぜsupremumが未知の分布に依存しないのか?

Theorem

$F(x)$が連続ならば,

\[\sup_{x\in\mathbb R} |F_n(x)-F(x)|\]

は$F$の形状に依存しない

Proof

$F$の逆関数を以下のように定義します:

\[F^{-1}(y) = \min\{x: F(x)\geq y\}\]

このとき以下の式が成立します:

\[\mathbb P\bigg(\sup_{x\in\mathbb R} |F_n(x)-F(x)| \leq t\bigg) = \mathbb P\bigg(\sup_{y\in[0, 1]} |F_n(F^{-1}(y))-F(F^{-1}(y))|\leq t\bigg)\]

empirical c.d.fの定義より

\[\begin{align*} F_n(F^{-1}(y)) &= \frac{1}{n}\sum_{i\in N}I(X_i \leq F^{-1}(y))\\[8pt] &= \frac{1}{n}\sum_{i\in N}I(F(X_i) \leq y) \end{align*}\]

したがって,

\[\mathbb P\left(\sup_{y\in[0, 1]} |F_n(F^{-1}(y))-y|\leq t\right) = \mathbb P\left(\sup_{y\in[0, 1]} \bigg|\frac{1}{n}\sum_{i\in N}I(F(X_i) \leq y)-y\bigg|\leq t\right)\]

また,

\[\begin{align*} \mathbb P(F(X_1)\leq t) &= \mathbb P(X_1\leq F^{-1}(t))\\ &= F(F^{-1}(t))\\ &= t \end{align*}\]

上記より, c.d.f $F(X_i)$は確率分布がどんな形状であろうとも連続確率変数ならば $[0,1]$区間の一様分布になることがわかります. したがって, $U_i = F(X-i)$とすると $U_i \sim Unif(0,1)$なので

\[\mathbb P\left(\sup_{y\in[0, 1]} |F_n(F^{-1}(y))-y|\leq t\right) = \mathbb P\left(\sup_{y\in[0, 1]} \bigg|\frac{1}{n}\sum_{i\in N}I(U_i \leq y)-y\bigg|\leq t\right)\]

よって題意は示された.

(証明終了)

Kolmogorov-Smirnov test statistics

Kolmogorov-Smirnov distribution

\[\mathbb P\bigg(\sqrt{n}\sup_{x\in\mathbb R}\bigg|F_n(x) - F(x)\bigg|\leq t\bigg)\to H(t)=1 - 2\sum_{j=1}^\infty(-1)^{j-1}\exp(-2j^2t)\]

この $H(t)$ はKolmogorov-Smirnov distributionのc.d.f

KS testの統計量は以下のように表される

\[D_n = \sqrt{n}\sup_{x\in\mathbb R}|F_n(x)-F_0(x)|\]
  • 帰無仮説が正しい場合, $n$が十分大きいと$D_n$はKolmogorov-Smirnov distributionに漸近的に収束する.
  • 帰無仮説が成立しない場合, $F$はLLNより真のc.d.f関数に収束する

帰無仮説が成立しない場合, nが十分大きいと,

\[\begin{align*} &\sup_{x\in\mathbb R}|F_n(x)-F_0(x)| > \delta\\ &D_n = \sqrt{n}\sup_{x\in\mathbb R}|F_n(x)-F_0(x)| > \sqrt{n}\delta \end{align*}\]

つまり, $H_0$が成立性ない場合, $n$が十分大きいと $D_n > \sqrt{n}\delta\to\infty$となる. この性質を利用して, KS testでは次のような形で棄却レベル$\alpha$と統計量の関係を定義している

\[\begin{align*} \alpha &= Pr(\text{reject }H_0 | H_0)\\ &= \mathbb P(D_n \geq c | H_0)\\ &\approx 1 -H(c) \end{align*}\]

Pythonで実装

Pythonではscipyパッケージを用いればkstestコマンドがありますが, method='asymp'の場合は以下と同様の挙動となります(defaultはexact):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import numpy as np
from scipy import stats
import statsmodels.api as sm
from scipy.stats import kstwobign
np.random.seed(42)

## scipy.stats.kstest
x = stats.norm.rvs(size=100)
res = stats.kstest(x, stats.norm.cdf, method='asymp')
print(res)
>> KstestResult(statistic=0.10357070563896065, pvalue=0.23367246360310912, statistic_location=0.37569801834567196, statistic_sign=1)

## 再現
def calculate_kstest(data, cdf):
    data = np.sort(data)
    sqrt_n = np.sqrt(len(data))
    ecdf = sm.distributions.ECDF(data)

    gaps = np.column_stack([np.abs(np.r_[0, ecdf(data[:-1])] - stats.norm.cdf(data)), 
                            np.abs(ecdf(data) - stats.norm.cdf(data))])
    gaps = np.max(gaps, axis=1, keepdims=False)
    data_idx = np.argmax(gaps)
    max_diff= np.max(gaps)
    D_n = max_diff * sqrt_n
    pval = 1 - kstwobign.cdf(D_n)

    return max_diff, pval, data[data_idx], D_n, ecdf
    

max_diff, p_val, sup_x_loc, D_n, ecdf = calculate_kstest(x, stats.norm.cdf)
print(calculate_kstest(x, stats.norm.cdf)[:-2])
>> (0.10357070563896065, 0.23367246360310912, 0.37569801834567196)

References



Share Buttons
Share on:

Feature Tags
Leave a Comment
(注意:GitHub Accountが必要となります)