0
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

相場師のために測度論:符号検定

0
Last updated at Posted at 2025-08-14

問題

3人の学生に対して,補習を行ったところ,点数の差($d=$ 補習後 − 補習前)が次のようになった。
$D:\ -15,\ 6,\ 11$

符号検定(sign test)を説明する。本検定の帰無仮説は「分布 $D$ の中央値 $=0$」である。本例では補習の有効性を示したいので,対立仮説が「分布 $D$ の中央値 $>0$」の片側検定を考える。$D$ の分布の対称性を仮定する必要はない。0 の値をとらないサンプルサイズ $n$ に対して,差 $d$ の値が正になった個数 $T_+$ を検定統計量とする。帰無仮説が正しいとき,$T_+$ は二項分布 $ \mathrm{Bin}(n, 0.5) $ に従うと考えることができる。

符号検定は $d_i$ が独立で、かつ $P(d_i=0)=0$(連続)を仮定する。$0$ が混じるときは除外し $n^*=$#${d_i\neq 0}$ に置き換える。

 本例では$n=3$ である。つまり,$T_+$ は二項分布 $ \mathrm{Bin}(n, 0.5) $ に従うと考える。$t_{+}=2$ より,符号検定片側 $P$-値は,$P(T_{+}\ge 2)=(_3C_2+_3C_3)\times(0.5)^3=4\times(0.5)^3\approx0.5$ となる。

n=3の符号検定を測度論で考える。

標本空間は$\Omega=\lbrace-1,+1 \rbrace^{n}={(\omega_1,\ldots,\omega_{n})}$という順序付きベクトルの集合だ。成分 $i$ は「$i$ 番目の観測(0 を除外し固定した順)」に対応する。

$n=3$ のとき $\Omega$ は $2^3=8$ 個。$T_+=2$ を満たすのは $(+,+,-),(+,-,+),(-,+,+)$ の 3 通りで、$\binom{3}{2}=3$。順序の違いは異なる $\omega$ だが、$T_+$ の値としては同一に束ねられる。

観測の符号を $\omega=(\omega_1,\omega_2,\omega_3)$、各成分 $\omega_i\in{-1,+1}$ とする。
順序付きなので標本空間は

$$
\begin{align*}
\Omega={-1,+1}^3
={(-1,-1,-1),,(-1,-1,+1),,(-1,+1,-1),,(-1,+1,+1),\
(+1,-1,-1),,(+1,-1,+1),,(+1,+1,-1),,(+1,+1,+1)}.
\end{align*}
$$

要素数は $|\Omega|=2^3=8$。

σ加法族の列挙の仕方

有限集合ではボレル族=冪集合なので

$$
\mathcal F=2^{\Omega}={A:\ A\subseteq\Omega},\qquad |\mathcal F|=2^{|\Omega|}=2^8=256.
$$

すべての部分集合を含む(空集合と $\Omega$ 自身を含み、補集合・可算和に閉)。
完全列挙は $256$ 個になりますが、サイズ別には

#$ {A\in\mathcal F:\ |A|=k}=\binom{8}{k}\quad(k=0,\dots,8).$

$\mathcal F=2^\Omega$ は 256 個の部分集合から成り、そのうち 符号検定で使う $\sigma(T_+)$ は $T_+\in{0,1,2,3}$ の冪集合に縮約して 16 個ある。

大きさ0:$\ \emptyset$
大きさ1:${0},{1},{2},{3}$
大きさ2:${0,1},{0,2},{0,3},{1,2},{1,3},{2,3}$
大きさ3:${0,1,2},{0,1,3},{0,2,3},{1,2,3}$
大きさ4:${0,1,2,3}$

対応する事象はすべて ${T_+\in A}$($A$ は上のいずれか)である。
$\Omega$ 上での原子(大きさ1)は

$$
\begin{aligned}
A_0&=T_+^{-1}({0})={(-,-,-)},\
A_1&=T_+^{-1}({1})={(+,-,-),(-,+,-),(-,-,+)},\
A_2&=T_+^{-1}({2})={(+,+,-),(+,-,+),(-,+,+)},\
A_3&=T_+^{-1}({3})={(+,+,+)},
\end{aligned}
$$

で、これを組み合わせればよい。たとえば、大きさ2の${0,1}={(-,-,-),(+,-,-),(-,+,-),(-,-,+)}$となる。

全事象は大きさ4で、単なる累積分布に必要な鎖は、
$$
{0}\subset{0,1}\subset{0,1,2}\subset{0,1,2,3}
$$
と記述できる。冪集合が16個必要なわけは、$\sigma$加法族の閉性(補集合・可算和に閉じる最小の入れ物)だ。区間型 ${0,1}$ や ${1,2}$ だけでなく、${0,2}$・${1,3}$(偶奇)など非区間事象も含めて整合的に確率を定義するため、結果として $2^{{0,1,2,3}}$ の 16 集合になる。

σ加法族(σ-代数)とは

集合 $\Omega$ 上の部分集合族 $\mathcal F\subseteq 2^\Omega$ が次を満たすとき,$\mathcal F$ をσ加法族(σ-代数)という。

  1. $\Omega\in\mathcal F$
  2. $A\in\mathcal F \Rightarrow A^{\mathrm c}\in\mathcal F$
  3. $A_1,A_2,\ldots\in\mathcal F \Rightarrow \bigcup_{k=1}^\infty A_k\in\mathcal F$
    (2,3 から可算交わりや差集合にも閉じる。)

生成された σ加法族

与えられた集合族 $\mathcal C$ を含む最小の σ加法族を
$\sigma(\mathcal C)$
と書く(生成 σ加法族)。例:実数のボレル σ加法族は
$\mathcal B(\mathbb R)=\sigma{(a,b):a<b}.$

確率測度(帰無仮説下)

帰無仮説(符号対称・独立)では各点確率は一様:

$$
\mathbb P({\omega})=\tfrac{1}{8}\quad(\omega\in\Omega).
$$

検定統計量と可測性

正の個数

$T_+(\omega)=$#$ {i: \omega_i=+1}\in{0,1,2,3}$

は $\mathcal F$–可測。各値の逆像(事象)を完全列挙すると

$$
\begin{align*}
T_+^{-1}(0)&={(-1,-1,-1)},\
T_+^{-1}(1)&={(+1,-1,-1),(-1,+1,-1),(-1,-1,+1)},\
T_+^{-1}(2)&={(+1,+1,-1),(+1,-1,+1),(-1,+1,+1)},\
T_+^{-1}(3)&={(+1,+1,+1)}.
\end{align*}
$$

よって $$\mathbb P(T_+=k)=\frac{\binom{3}{k}}{8}\ (k=0,1,2,3),\quad \mathbb P(T_+\ge2)=\frac{\binom{3}{2}+\binom{3}{3}}{8}=\tfrac12.$$

ゆえに分布は

$$
\mathbb P(T_+=k)=\frac{\binom{3}{k}}{8}\quad(k=0,1,2,3).
$$

置換不偏性

同じ $T_+$ を与える並べ替えは等確率、$p$ 値はその軌道の総和として書ける。

片側 $P$ 値

「中央値 $0$」で $t_+=2$ を観測したなら

$$
P=\mathbb P(T_+\ge2)=\frac{\binom{3}{2}+\binom{3}{3}}{8}=\frac{3+1}{8}=\tfrac12.
$$

まとめ

+ $d_i\neq0$ のみ用い $n^\ast$ 個とする。

+ 写像
$S:\mathbb R^{n^\ast}\to{-1,+1}^{n^\ast},\quad S(x)=(\operatorname{sgn}x_1,\dots,\operatorname{sgn}x_{n^\ast})$
を可測とする。

+ $H_0$(中央値 $=0$、連続)下では $P_0\circ S^{-1}$ は ${-1,+1}^{n^\ast}$ 上の一様測度で、

$T_+(\omega)=\sum_{i=1}^{n^\ast}1_{{\omega_i=+1}}\quad\Rightarrow\quad P_0(T_+=k)=\binom{n^\ast}{k}2^{-n^\ast}.$

+ したがって片側 $p$ 値は $P_0(T_+\ge t_+^{\text{obs}})$、両側は $2\min{P_0(T_+\ge t_+^{\text{obs}}),,P_0(T_+\le t_+^{\text{obs}})}$。

正規分布とコーシー分布で試してみた。

from math import comb
import numpy as np

def sign_test(x, alternative="two-sided"):
    """
    Exact sign test.
    Parameters
    ----------
    x : array-like
        Sample of differences.
    alternative : {"two-sided", "greater", "less"}
        H1 options: "greater" median > 0, "less" median < 0, "two-sided".
    Returns
    -------
    result : dict with keys
        n_star : number of nonzero observations used
        t_plus : count of positives
        pvalue : exact p-value
    """
    x = np.asarray(x)
    x_nz = x[x != 0]
    n_star = x_nz.size
    if n_star == 0:
        raise ValueError("All observations are zero; sign test not defined.")
    t_plus = int(np.sum(x_nz > 0))

    # exact binomial tail with p=0.5
    def tail_ge(k, n):  # P(T >= k)
        return sum(comb(n, j) for j in range(k, n + 1)) / (2 ** n)

    def tail_le(k, n):  # P(T <= k)
        return sum(comb(n, j) for j in range(0, k + 1)) / (2 ** n)

    if alternative == "greater":
        p = tail_ge(t_plus, n_star)
    elif alternative == "less":
        p = tail_le(t_plus, n_star)
    elif alternative == "two-sided":
        p = 2 * min(tail_ge(t_plus, n_star), tail_le(t_plus, n_star))
        p = min(p, 1.0)  # cap at 1 for discrete distributions
    else:
        raise ValueError("alternative must be 'two-sided', 'greater', or 'less'")

    return {"n_star": n_star, "t_plus": t_plus, "pvalue": p}

# ------------------------------
# Example run with 100 variables
# ------------------------------
rng = np.random.default_rng(12345)
mu = 0.0    # try 0.3 to simulate an improvement
sigma = 1.0
n = 100
d = rng.normal(loc=mu, scale=sigma, size=n)

res_greater = sign_test(d, alternative="greater")
res_twosided = sign_test(d, alternative="two-sided")

print("Generated n=%.d observations (Normal(mu=%.2f, sigma=%.2f))" % (n,mu, sigma))
print("Nonzeros used (n*):", res_greater["n_star"])
print("T+ (positives):    ", res_greater["t_plus"])
print("One-sided p (median > 0):", res_greater["pvalue"])
print("Two-sided p:", res_twosided["pvalue"])

Generated n=100 observations (Normal(mu=0.00, sigma=1.00))
Nonzeros used (n*): 100
T+ (positives): 50
One-sided p (median > 0): 0.5397946186935894
Two-sided p: 1.0

mu = 0.3    # try 0.3 to simulate an improvement
sigma = 1.0
n = 100
d = rng.normal(loc=mu, scale=sigma, size=n)

res_greater = sign_test(d, alternative="greater")
res_twosided = sign_test(d, alternative="two-sided")

print("Generated n=%.d observations (Normal(mu=%.2f, sigma=%.2f))" % (n,mu, sigma))
print("Nonzeros used (n*):", res_greater["n_star"])
print("T+ (positives):    ", res_greater["t_plus"])
print("One-sided p (median > 0):", res_greater["pvalue"])
print("Two-sided p:", res_twosided["pvalue"])

Generated n=100 observations (Normal(mu=0.30, sigma=1.00))
Nonzeros used (n*): 100
T+ (positives): 62
One-sided p (median > 0): 0.010489367838925859
Two-sided p: 0.020978735677851718

mu = 0.0    # try 0.3 to simulate an improvement
sigma = 1.0
n = 10000
d = rng.normal(loc=mu, scale=sigma, size=n)

res_greater = sign_test(d, alternative="greater")
res_twosided = sign_test(d, alternative="two-sided")

print("Generated n=%.d observations (Normal(mu=%.2f, sigma=%.2f))" % (n,mu, sigma))
print("Nonzeros used (n*):", res_greater["n_star"])
print("T+ (positives):    ", res_greater["t_plus"])
print("One-sided p (median > 0):", res_greater["pvalue"])
print("Two-sided p:", res_twosided["pvalue"])

Generated n=10000 observations (Normal(mu=0.00, sigma=1.00))
Nonzeros used (n*): 10000
T+ (positives): 5024
One-sided p (median > 0): 0.31917859905162393
Two-sided p: 0.6383571981032479

mu = 0.3    # try 0.3 to simulate an improvement
sigma = 1.0
n = 10000
d = rng.normal(loc=mu, scale=sigma, size=n)

res_greater = sign_test(d, alternative="greater")
res_twosided = sign_test(d, alternative="two-sided")

print("Generated n=%.d observations (Normal(mu=%.2f, sigma=%.2f))" % (n,mu, sigma))
print("Nonzeros used (n*):", res_greater["n_star"])
print("T+ (positives):    ", res_greater["t_plus"])
print("One-sided p (median > 0):", res_greater["pvalue"])
print("Two-sided p:", res_twosided["pvalue"])

Generated n=10000 observations (Normal(mu=0.30, sigma=1.00))
Nonzeros used (n*): 10000
T+ (positives): 6261
One-sided p (median > 0): 4.917207164489503e-142
Two-sided p: 9.834414328979006e-142

def run_cauchy(n=100, x0=0.0, gamma=1.0, seed=20250813):
    rng = np.random.default_rng(seed)
    d = x0 + gamma * rng.standard_cauchy(size=n)

    # Use the sign_test defined earlier in the session
    res_greater = sign_test(d, alternative="greater")
    res_twosided = sign_test(d, alternative="two-sided")

    num_pos = int(np.sum(d > 0))
    num_neg = int(np.sum(d < 0))
    num_zero = int(np.sum(d == 0))
    q05, q50, q95 = np.quantile(d, [0.05, 0.5, 0.95])

    print(f"Cauchy data (n={n}, x0={x0}, gamma={gamma}, seed={seed})")
    print(f"Sign summary -> +:{num_pos}  -:{num_neg}  0:{num_zero}")
    print(f"Quantiles (5%, 50%, 95%): {q05:.3f}, {q50:.3f}, {q95:.3f}")
    print(f"T+ (positives) = {res_greater['t_plus']}  |  n* = {res_greater['n_star']}")
    print("One-sided p (median > 0):", res_greater["pvalue"])
    print("Two-sided p:", res_twosided["pvalue"])
    np.set_printoptions(precision=3, suppress=True)
    print("First 10 values:", d[:10])
    print("-"*72)
# --- Example 1: under H0 (x0=0) ---
run_cauchy(n=100, x0=0.0, gamma=1.0, seed=20250813)

# --- Example 2: under H1 (median shifted to the right) ---
run_cauchy(n=100, x0=0.3, gamma=1.0, seed=20250813)

Cauchy data (n=100, x0=0.0, gamma=1.0, seed=20250813)
Sign summary -> +:50 -:50 0:0
Quantiles (5%, 50%, 95%): -7.121, -0.023, 5.242
T+ (positives) = 50 | n* = 100
One-sided p (median > 0): 0.5397946186935894
Two-sided p: 1.0
First 10 values: [-1.934 0.917 -0.809 -1.404 0.753 -3.015 1.315 0.517 0.307 -0.659]


Cauchy data (n=100, x0=0.3, gamma=1.0, seed=20250813)
Sign summary -> +:55 -:45 0:0
Quantiles (5%, 50%, 95%): -6.821, 0.277, 5.542
T+ (positives) = 55 | n* = 100
One-sided p (median > 0): 0.18410080866334813
Two-sided p: 0.36820161732669626
First 10 values: [-1.634 1.217 -0.509 -1.104 1.053 -2.715 1.615 0.817 0.607 -0.359]

# --- Example 1: under H0 (x0=0) ---
run_cauchy(n=10000, x0=0.0, gamma=1.0, seed=20250813)

# --- Example 2: under H1 (median shifted to the right) ---
run_cauchy(n=10000, x0=0.3, gamma=1.0, seed=20250813)

Cauchy data (n=100, x0=0.0, gamma=1.0, seed=20250813)
Sign summary -> +:50 -:50 0:0
Quantiles (5%, 50%, 95%): -7.121, -0.023, 5.242
T+ (positives) = 50 | n* = 100
One-sided p (median > 0): 0.5397946186935894
Two-sided p: 1.0
First 10 values: [-1.934 0.917 -0.809 -1.404 0.753 -3.015 1.315 0.517 0.307 -0.659]


Cauchy data (n=100, x0=0.3, gamma=1.0, seed=20250813)
Sign summary -> +:55 -:45 0:0
Quantiles (5%, 50%, 95%): -6.821, 0.277, 5.542
T+ (positives) = 55 | n* = 100
One-sided p (median > 0): 0.18410080866334813
Two-sided p: 0.36820161732669626
First 10 values: [-1.634 1.217 -0.509 -1.104 1.053 -2.715 1.615 0.817 0.607 -0.359]

符号検定は非対称の分布に関しても割と機能するので、相場師のために道具だ。ここでは正規分布よりもコーシー分布でいい結果が出ている。

参考

Python3ではじめるシステムトレード【第2版】環境構築と売買戦略

「画像をクリックしていただくとpanrollingのホームページから書籍を購入していただけます。

「画像をクリックしていただくとpanrollingのホームページから書籍を購入していただけます。」

0
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?