回帰分析における仮説検定(t検定・F検定)を解説

回帰分析は、変数間の関係をモデル化する最も基本的な統計手法のひとつです。しかし、推定された回帰係数が「本当に意味のある影響を持つのか」を判断するためには、仮説検定が不可欠です。

回帰分析における仮説検定は、大きく2つに分類されます。ひとつは個々の回帰係数が有意かどうかを判定する 回帰係数のt検定、もうひとつはモデル全体が有意な説明力を持つかを判定する F検定 です。この2つは独立な概念ではなく、平方和の分解と自由度の関係を通じて深く結びついています。

本記事の内容

  • 回帰モデルの仮定と推定の復習
  • 回帰係数のt検定($H_0: \beta_j = 0$ の検定統計量導出)
  • モデル全体のF検定(平方和の分解と検定統計量導出)
  • 決定係数 $R^2$ と F 統計量の関係式の導出
  • 残差分析(正規性・等分散性の確認)
  • Pythonでの回帰分析と検定の実装

前提知識

この記事を読む前に、以下の記事を読んでおくと理解が深まります。

回帰モデルの仮定と推定

線形回帰モデル

$n$ 個のデータ $(x_{i1}, x_{i2}, \dots, x_{ip}, y_i)$, $i = 1, \dots, n$ に対する重回帰モデルは、

$$ y_i = \beta_0 + \beta_1 x_{i1} + \beta_2 x_{i2} + \cdots + \beta_p x_{ip} + \varepsilon_i $$

行列形式で書くと、

$$ \bm{y} = \bm{X}\bm{\beta} + \bm{\varepsilon} $$

ここで、

  • $\bm{y} = (y_1, \dots, y_n)^\top$: $n \times 1$ の応答変数ベクトル
  • $\bm{X}$: $n \times (p+1)$ の計画行列(第1列は切片に対応する1のベクトル)
  • $\bm{\beta} = (\beta_0, \beta_1, \dots, \beta_p)^\top$: $(p+1) \times 1$ の回帰係数ベクトル
  • $\bm{\varepsilon} = (\varepsilon_1, \dots, \varepsilon_n)^\top$: $n \times 1$ の誤差ベクトル

誤差の仮定(ガウス・マルコフの仮定 + 正規性)

  1. 線形性: $E[\bm{y}] = \bm{X}\bm{\beta}$
  2. 等分散性: $\text{Var}(\varepsilon_i) = \sigma^2$(一定)
  3. 無相関: $\text{Cov}(\varepsilon_i, \varepsilon_j) = 0$($i \neq j$)
  4. 正規性: $\varepsilon_i \sim N(0, \sigma^2)$

まとめると、$\bm{\varepsilon} \sim N(\bm{0}, \sigma^2 \bm{I}_n)$ です。

最小二乗推定量(OLS)

回帰係数の最小二乗推定量は、残差平方和 $\|\bm{y} – \bm{X}\bm{\beta}\|^2$ を最小化して得られます。

$$ \hat{\bm{\beta}} = (\bm{X}^\top\bm{X})^{-1}\bm{X}^\top\bm{y} $$

$\hat{\bm{\beta}}$ の分布

$\bm{y} \sim N(\bm{X}\bm{\beta}, \sigma^2\bm{I})$ なので、

$$ \hat{\bm{\beta}} = (\bm{X}^\top\bm{X})^{-1}\bm{X}^\top\bm{y} \sim N\left(\bm{\beta}, \sigma^2(\bm{X}^\top\bm{X})^{-1}\right) $$

各成分について、

$$ \hat{\beta}_j \sim N\left(\beta_j, \sigma^2 [(\bm{X}^\top\bm{X})^{-1}]_{jj}\right) $$

ここで $[(\bm{X}^\top\bm{X})^{-1}]_{jj}$ は行列 $(\bm{X}^\top\bm{X})^{-1}$ の $(j, j)$ 成分です。

誤差分散の推定

$$ \hat{\sigma}^2 = \frac{\sum_{i=1}^{n}(y_i – \hat{y}_i)^2}{n – p – 1} = \frac{RSS}{n – p – 1} = \frac{\bm{e}^\top\bm{e}}{n – p – 1} $$

ここで $\bm{e} = \bm{y} – \hat{\bm{y}} = \bm{y} – \bm{X}\hat{\bm{\beta}}$ は残差ベクトル、$RSS = \sum e_i^2$ は残差平方和です。

自由度が $n – p – 1$ になる理由は、$p + 1$ 個のパラメータ $(\beta_0, \beta_1, \dots, \beta_p)$ を推定したことにより、残差の自由度が $n – (p+1)$ に減少するためです。

$$ \frac{RSS}{\sigma^2} = \frac{(n – p – 1)\hat{\sigma}^2}{\sigma^2} \sim \chi^2_{n-p-1} $$

回帰係数のt検定

問題設定

個々の回帰係数 $\beta_j$ が0であるかどうかを検定します。

$$ H_0: \beta_j = 0 \quad \text{vs} \quad H_1: \beta_j \neq 0 $$

$\beta_j = 0$ は、他の説明変数を一定に保ったとき、変数 $x_j$ が応答変数 $y$ に対して有意な影響を持たないことを意味します。

検定統計量の導出

$\hat{\beta}_j$ は正規分布に従います。

$$ \hat{\beta}_j \sim N\left(\beta_j, \sigma^2 c_{jj}\right) $$

ここで $c_{jj} = [(\bm{X}^\top\bm{X})^{-1}]_{jj}$ です。

$H_0: \beta_j = 0$ のもとで標準化すると、

$$ \frac{\hat{\beta}_j – 0}{\sigma\sqrt{c_{jj}}} \sim N(0, 1) $$

しかし $\sigma$ は未知であるため、推定量 $\hat{\sigma}$ で置き換えます。

$$ t_j = \frac{\hat{\beta}_j}{\hat{\sigma}\sqrt{c_{jj}}} = \frac{\hat{\beta}_j}{\text{SE}(\hat{\beta}_j)} $$

ここで $\text{SE}(\hat{\beta}_j) = \hat{\sigma}\sqrt{c_{jj}}$ は $\hat{\beta}_j$ の 標準誤差 です。

t分布に従うことの証明

$t_j$ が自由度 $n – p – 1$ のt分布に従うことを示しましょう。

$Z = \frac{\hat{\beta}_j}{\sigma\sqrt{c_{jj}}} \sim N(0, 1)$($H_0$ のもと)です。

また、$\frac{RSS}{\sigma^2} \sim \chi^2_{n-p-1}$ であり、$\hat{\beta}_j$ と $RSS$ は独立です(正規分布における射影の性質による)。

t分布の定義から、

$$ t_j = \frac{Z}{\sqrt{\chi^2_{n-p-1}/(n-p-1)}} = \frac{\hat{\beta}_j / (\sigma\sqrt{c_{jj}})}{\sqrt{RSS/(\sigma^2(n-p-1))}} = \frac{\hat{\beta}_j}{\hat{\sigma}\sqrt{c_{jj}}} $$

$$ t_j \sim t_{n-p-1} \quad (H_0 \text{ のもと}) $$

p値の計算

観測された統計量 $t_{\text{obs}}$ に対する両側p値は、

$$ p = 2 \cdot P(T > |t_{\text{obs}}|), \quad T \sim t_{n-p-1} $$

$p < \alpha$ ならば $H_0: \beta_j = 0$ を棄却し、変数 $x_j$ は有意な説明力を持つと結論します。

$\hat{\beta}_j$ の信頼区間

$\beta_j$ の $100(1-\alpha)\%$ 信頼区間は

$$ \hat{\beta}_j \pm t_{\alpha/2, n-p-1} \cdot \text{SE}(\hat{\beta}_j) $$

モデル全体のF検定

問題設定

すべての回帰係数(切片を除く)が同時に0であるかどうかを検定します。

$$ H_0: \beta_1 = \beta_2 = \cdots = \beta_p = 0 \quad \text{vs} \quad H_1: \text{少なくとも1つの } \beta_j \neq 0 $$

$H_0$ が真であれば、説明変数はどれも $y$ の予測に役立たないことを意味します。

平方和の分解

回帰分析の基本的な等式は 平方和の分解 です。

$$ \underbrace{\sum_{i=1}^{n}(y_i – \bar{y})^2}_{SS_{\text{total}}} = \underbrace{\sum_{i=1}^{n}(\hat{y}_i – \bar{y})^2}_{SS_{\text{reg}}} + \underbrace{\sum_{i=1}^{n}(y_i – \hat{y}_i)^2}_{SS_{\text{res}}} $$

$$ TSS = SSR + RSS $$

ここで、 – $TSS$(Total Sum of Squares): 全変動 – $SSR$(Sum of Squares due to Regression): 回帰による変動 – $RSS$(Residual Sum of Squares): 残差変動

分解の証明

$$ \begin{align} y_i – \bar{y} &= (y_i – \hat{y}_i) + (\hat{y}_i – \bar{y}) \\ &= e_i + (\hat{y}_i – \bar{y}) \end{align} $$

両辺を二乗して $i$ について和を取ります。

$$ \sum_{i=1}^{n}(y_i – \bar{y})^2 = \sum_{i=1}^{n}e_i^2 + 2\sum_{i=1}^{n}e_i(\hat{y}_i – \bar{y}) + \sum_{i=1}^{n}(\hat{y}_i – \bar{y})^2 $$

交差項が0であることを示します。OLS推定の正規方程式 $\bm{X}^\top\bm{e} = \bm{0}$ より、

$$ \sum_{i=1}^{n} e_i = 0, \quad \sum_{i=1}^{n} e_i x_{ij} = 0 \quad (j = 1, \dots, p) $$

$\hat{y}_i = \hat{\beta}_0 + \sum_j \hat{\beta}_j x_{ij}$ は $1, x_{i1}, \dots, x_{ip}$ の線形結合なので、

$$ \sum_{i=1}^{n} e_i \hat{y}_i = 0 $$

また $\sum e_i = 0$ より $\sum e_i \bar{y} = 0$ ですから、

$$ \sum_{i=1}^{n} e_i(\hat{y}_i – \bar{y}) = \sum e_i \hat{y}_i – \bar{y}\sum e_i = 0 – 0 = 0 $$

したがって交差項は消え、

$$ TSS = SSR + RSS $$

が成り立ちます。

各平方和の分布

$H_0: \beta_1 = \cdots = \beta_p = 0$ のもとで、

$$ \frac{SSR}{\sigma^2} \sim \chi^2_p, \quad \frac{RSS}{\sigma^2} \sim \chi^2_{n-p-1} $$

であり、$SSR$ と $RSS$ は独立です。

F統計量の導出

F分布は、独立な2つの $\chi^2$ 変数をそれぞれの自由度で割った比として定義されます。

$$ F = \frac{SSR/p}{RSS/(n-p-1)} = \frac{MSR}{MSE} $$

ここで、

  • $MSR = SSR/p$: 回帰平均平方
  • $MSE = RSS/(n-p-1)$: 残差平均平方(= $\hat{\sigma}^2$)

$H_0$ のもとで、

$$ F = \frac{\chi^2_p / p}{\chi^2_{n-p-1} / (n-p-1)} \sim F_{p, n-p-1} $$

$F$ が大きいほど、回帰による変動($SSR$)が残差変動($RSS$)に比べて大きいことを意味し、モデルの説明力が高いことを示します。

p値

$$ p = P(F_{p, n-p-1} > F_{\text{obs}}) $$

決定係数 $R^2$ とF統計量の関係

決定係数の定義

$$ R^2 = \frac{SSR}{TSS} = 1 – \frac{RSS}{TSS} $$

$R^2$ はモデルが説明する全変動の割合を表し、$0 \leq R^2 \leq 1$ です。

$R^2$ と $F$ の関係式の導出

$SSR = R^2 \cdot TSS$ と $RSS = (1 – R^2) \cdot TSS$ を F 統計量の式に代入します。

$$ \begin{align} F &= \frac{SSR/p}{RSS/(n-p-1)} \\ &= \frac{R^2 \cdot TSS / p}{(1 – R^2) \cdot TSS / (n-p-1)} \\ &= \frac{R^2}{1 – R^2} \cdot \frac{n – p – 1}{p} \end{align} $$

$$ \boxed{F = \frac{R^2 / p}{(1 – R^2) / (n – p – 1)}} $$

逆に、F統計量から $R^2$ を求めるには、

$$ R^2 = \frac{F \cdot p}{F \cdot p + (n – p – 1)} $$

単回帰の場合

$p = 1$ のとき、

$$ F = \frac{R^2}{1 – R^2} \cdot (n – 2) = t^2 $$

すなわち、単回帰ではF統計量は回帰係数のt統計量の二乗に等しい です。これは単回帰ではモデル全体の検定と個々の回帰係数の検定が等価であることを意味します。

自由度調整済み決定係数

$R^2$ は説明変数を追加すると必ず増加する性質があります。これを補正したのが自由度調整済み $R^2$ です。

$$ \bar{R}^2 = 1 – \frac{RSS/(n-p-1)}{TSS/(n-1)} = 1 – (1 – R^2)\frac{n-1}{n-p-1} $$

無意味な説明変数を追加すると、$RSS/(n-p-1)$ は減少せず、$\bar{R}^2$ は低下することがあります。

残差分析

残差の種類

通常の残差:

$$ e_i = y_i – \hat{y}_i $$

標準化残差: 残差を残差の標準偏差で割ったものです。

$$ r_i = \frac{e_i}{\hat{\sigma}\sqrt{1 – h_{ii}}} $$

ここで $h_{ii}$ は ハット行列 $\bm{H} = \bm{X}(\bm{X}^\top\bm{X})^{-1}\bm{X}^\top$ の対角成分で、各データ点の てこ比(leverage) を表します。

なぜ $\sqrt{1 – h_{ii}}$ で割るのか

$\bm{e} = (\bm{I} – \bm{H})\bm{y}$ なので、

$$ \text{Var}(\bm{e}) = (\bm{I} – \bm{H})\sigma^2\bm{I}(\bm{I} – \bm{H})^\top = \sigma^2(\bm{I} – \bm{H}) $$

$\bm{H}$ がべき等行列($\bm{H}^2 = \bm{H}$)であることを使いました。

したがって、

$$ \text{Var}(e_i) = \sigma^2(1 – h_{ii}) $$

残差の分散は一定ではなく、てこ比 $h_{ii}$ に依存します。標準化残差は各残差を自身の標準偏差で割ることにより、一様な分散を持つようにしたものです。

残差の診断

回帰モデルの仮定が満たされているかを、残差を用いて診断します。

  1. 正規性: 残差のQ-Qプロット、Shapiro-Wilk検定
  2. 等分散性: 残差 vs 予測値のプロット(パターンがないこと)
  3. 独立性: 残差の自己相関(Durbin-Watson検定)
  4. 外れ値: 標準化残差の絶対値が大きいデータ(通常 $|r_i| > 2$ )

具体例

数値例

3つの説明変数($x_1, x_2, x_3$)で応答変数 $y$ を回帰する重回帰分析を行います。

$n = 50$, $p = 3$ として、以下の ANOVA 表が得られたとします。

変動要因 平方和 自由度 平均平方 F値
回帰 $SSR = 240$ $p = 3$ $MSR = 80$ $F = 16.0$
残差 $RSS = 230$ $n-p-1 = 46$ $MSE = 5.0$
全体 $TSS = 470$ $n-1 = 49$

決定係数: $R^2 = 240/470 = 0.511$

F統計量の検証: $F = \frac{R^2 / p}{(1-R^2)/(n-p-1)} = \frac{0.511/3}{0.489/46} = \frac{0.1703}{0.01063} = 16.02$ (一致)

p値: $p = P(F_{3, 46} > 16.0) < 0.001$

モデル全体は有意水準1%で有意であり、少なくとも1つの説明変数は $y$ に有意な影響を持ちます。

Pythonでの実装

回帰分析と検定(statsmodels)

import numpy as np
import statsmodels.api as sm
from scipy import stats

np.random.seed(42)

# データ生成
n = 100
x1 = np.random.normal(0, 1, n)
x2 = np.random.normal(0, 1, n)
x3 = np.random.normal(0, 1, n)  # 影響のない変数

# 真のモデル: y = 2 + 3*x1 - 1.5*x2 + 0*x3 + ε
y = 2 + 3 * x1 - 1.5 * x2 + 0 * x3 + np.random.normal(0, 2, n)

# 計画行列(切片を追加)
X = np.column_stack([x1, x2, x3])
X_with_const = sm.add_constant(X)

# OLS回帰
model = sm.OLS(y, X_with_const).fit()
print(model.summary())

print("\n=== 回帰係数のt検定 ===")
for i, name in enumerate(['const', 'x1', 'x2', 'x3']):
    print(f"{name}: β̂ = {model.params[i]:.4f}, SE = {model.bse[i]:.4f}, "
          f"t = {model.tvalues[i]:.4f}, p = {model.pvalues[i]:.4f}")

print(f"\n=== モデル全体のF検定 ===")
print(f"F統計量 = {model.fvalue:.4f}")
print(f"p値 = {model.f_pvalue:.6f}")
print(f"R² = {model.rsquared:.4f}")
print(f"Adjusted R² = {model.rsquared_adj:.4f}")

# R²からFを算出して確認
p = 3
R2 = model.rsquared
F_from_R2 = (R2 / p) / ((1 - R2) / (n - p - 1))
print(f"\nR²から算出したF = {F_from_R2:.4f}(モデルのF = {model.fvalue:.4f})")

平方和の分解の確認

import numpy as np
import statsmodels.api as sm

np.random.seed(42)

# データ生成
n = 100
x1 = np.random.normal(0, 1, n)
x2 = np.random.normal(0, 1, n)
y = 2 + 3 * x1 - 1.5 * x2 + np.random.normal(0, 2, n)

X = sm.add_constant(np.column_stack([x1, x2]))
model = sm.OLS(y, X).fit()

# 平方和の分解
y_hat = model.fittedvalues
y_bar = np.mean(y)
e = model.resid

TSS = np.sum((y - y_bar)**2)
SSR = np.sum((y_hat - y_bar)**2)
RSS = np.sum(e**2)

print("=== 平方和の分解 ===")
print(f"TSS (全変動)   = {TSS:.4f}")
print(f"SSR (回帰変動) = {SSR:.4f}")
print(f"RSS (残差変動) = {RSS:.4f}")
print(f"SSR + RSS      = {SSR + RSS:.4f}")
print(f"TSS = SSR + RSS? {np.isclose(TSS, SSR + RSS)}")

print(f"\n=== 検定統計量 ===")
p = 2  # 説明変数の数
MSR = SSR / p
MSE = RSS / (n - p - 1)
F = MSR / MSE

print(f"MSR = {MSR:.4f}")
print(f"MSE = {MSE:.4f}")
print(f"F統計量 = {F:.4f}")
print(f"R² = {SSR/TSS:.4f}")

# 交差項がゼロであることの確認
cross_term = np.sum(e * (y_hat - y_bar))
print(f"\n交差項 Σe_i(ŷ_i - ȳ) = {cross_term:.10f}(≈0)")

回帰係数のt検定のスクラッチ実装

import numpy as np
from scipy import stats

def regression_t_test(X, y):
    """
    回帰係数のt検定をスクラッチで実装

    Parameters
    ----------
    X : ndarray
        計画行列(切片列を含む)
    y : ndarray
        応答変数

    Returns
    -------
    beta_hat : ndarray
        回帰係数の推定値
    se : ndarray
        標準誤差
    t_stats : ndarray
        t統計量
    p_values : ndarray
        p値
    """
    n, k = X.shape  # k = p + 1(切片を含む)

    # OLS推定
    XtX_inv = np.linalg.inv(X.T @ X)
    beta_hat = XtX_inv @ X.T @ y

    # 残差
    y_hat = X @ beta_hat
    e = y - y_hat

    # 誤差分散の推定(自由度 n - k)
    RSS = e.T @ e
    sigma2_hat = RSS / (n - k)

    # 回帰係数の共分散行列
    cov_beta = sigma2_hat * XtX_inv

    # 標準誤差
    se = np.sqrt(np.diag(cov_beta))

    # t統計量
    t_stats = beta_hat / se

    # p値(両側検定)
    df = n - k
    p_values = 2 * (1 - stats.t.cdf(np.abs(t_stats), df=df))

    return beta_hat, se, t_stats, p_values


# --- 具体例 ---
np.random.seed(42)

n = 100
x1 = np.random.normal(0, 1, n)
x2 = np.random.normal(0, 1, n)
x3 = np.random.normal(0, 1, n)

# 真のモデル: y = 2 + 3*x1 - 1.5*x2 + 0*x3 + ε
y = 2 + 3 * x1 - 1.5 * x2 + 0 * x3 + np.random.normal(0, 2, n)

# 計画行列(切片を追加)
X = np.column_stack([np.ones(n), x1, x2, x3])
names = ['const', 'x1', 'x2', 'x3']

beta_hat, se, t_stats, p_values = regression_t_test(X, y)

print("=== 回帰係数のt検定(スクラッチ実装) ===")
print(f"{'変数':<8} {'β̂':>10} {'SE':>10} {'t値':>10} {'p値':>10} {'有意?':>6}")
print("-" * 56)
for name, b, s, t, p in zip(names, beta_hat, se, t_stats, p_values):
    sig = '***' if p < 0.001 else '**' if p < 0.01 else '*' if p < 0.05 else ''
    print(f"{name:<8} {b:>10.4f} {s:>10.4f} {t:>10.4f} {p:>10.4f} {sig:>6}")

print(f"\n真の値: β₀=2, β₁=3, β₂=-1.5, β₃=0")

残差分析の可視化

import numpy as np
import matplotlib.pyplot as plt
import statsmodels.api as sm
from scipy import stats

np.random.seed(42)

# データ生成
n = 150
x1 = np.random.normal(0, 1, n)
x2 = np.random.normal(0, 1, n)
y = 2 + 3 * x1 - 1.5 * x2 + np.random.normal(0, 2, n)

X = sm.add_constant(np.column_stack([x1, x2]))
model = sm.OLS(y, X).fit()

# 残差
residuals = model.resid
fitted = model.fittedvalues

# ハット行列の対角成分(てこ比)
H = X @ np.linalg.inv(X.T @ X) @ X.T
leverage = np.diag(H)

# 標準化残差
sigma_hat = np.sqrt(np.sum(residuals**2) / (n - 3))
standardized_resid = residuals / (sigma_hat * np.sqrt(1 - leverage))

fig, axes = plt.subplots(2, 2, figsize=(14, 10))

# 1. 残差 vs 予測値
axes[0, 0].scatter(fitted, residuals, alpha=0.6, edgecolor='k', linewidth=0.5)
axes[0, 0].axhline(y=0, color='red', linestyle='--', linewidth=1.5)
axes[0, 0].set_xlabel('Fitted Values', fontsize=12)
axes[0, 0].set_ylabel('Residuals', fontsize=12)
axes[0, 0].set_title('Residuals vs Fitted Values', fontsize=13)
axes[0, 0].grid(True, alpha=0.3)

# 2. Q-Qプロット
stats.probplot(standardized_resid, dist="norm", plot=axes[0, 1])
axes[0, 1].set_title('Normal Q-Q Plot', fontsize=13)
axes[0, 1].grid(True, alpha=0.3)

# 3. 標準化残差のヒストグラム
axes[1, 0].hist(standardized_resid, bins=25, density=True, alpha=0.7,
                color='steelblue', edgecolor='black')
x_range = np.linspace(-4, 4, 200)
axes[1, 0].plot(x_range, stats.norm.pdf(x_range), 'r-', linewidth=2,
                label='$N(0, 1)$')
axes[1, 0].set_xlabel('Standardized Residuals', fontsize=12)
axes[1, 0].set_ylabel('Density', fontsize=12)
axes[1, 0].set_title('Distribution of Standardized Residuals', fontsize=13)
axes[1, 0].legend(fontsize=11)
axes[1, 0].grid(True, alpha=0.3)

# 4. 標準化残差 vs てこ比
axes[1, 1].scatter(leverage, np.abs(standardized_resid), alpha=0.6,
                    edgecolor='k', linewidth=0.5)
axes[1, 1].axhline(y=2, color='red', linestyle='--', linewidth=1.5,
                    label='|Std. Residual| = 2')
axes[1, 1].axvline(x=2*(2+1)/n, color='orange', linestyle='--', linewidth=1.5,
                    label=f'Leverage = 2(p+1)/n = {2*3/n:.3f}')
axes[1, 1].set_xlabel('Leverage ($h_{ii}$)', fontsize=12)
axes[1, 1].set_ylabel('|Standardized Residuals|', fontsize=12)
axes[1, 1].set_title('Leverage vs |Standardized Residuals|', fontsize=13)
axes[1, 1].legend(fontsize=10)
axes[1, 1].grid(True, alpha=0.3)

plt.suptitle('Residual Diagnostics', fontsize=15, y=1.02)
plt.tight_layout()
plt.show()

# Shapiro-Wilk検定で正規性を確認
stat_sw, p_sw = stats.shapiro(residuals)
print(f"Shapiro-Wilk検定: W = {stat_sw:.4f}, p = {p_sw:.4f}")
print(f"正規性の帰無仮説を{'棄却しない' if p_sw > 0.05 else '棄却する'} (α=0.05)")

単回帰でのt検定とF検定の一致の確認

import numpy as np
import matplotlib.pyplot as plt
import statsmodels.api as sm
from scipy import stats

np.random.seed(42)

# 単回帰のシミュレーション
n_simulations = 5000
n = 50
t_squared_vals = []
f_vals = []

for _ in range(n_simulations):
    x = np.random.normal(0, 1, n)
    # H1のもと: 真のβ = 1.0
    y = 1.0 + 1.0 * x + np.random.normal(0, 2, n)

    X = sm.add_constant(x)
    model = sm.OLS(y, X).fit()

    t_val = model.tvalues[1]  # x の t値
    f_val = model.fvalue       # F値

    t_squared_vals.append(t_val**2)
    f_vals.append(f_val)

t_squared_vals = np.array(t_squared_vals)
f_vals = np.array(f_vals)

fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(14, 5))

# t² vs F の散布図
ax1.scatter(t_squared_vals, f_vals, alpha=0.3, s=10, c='steelblue')
ax1.plot([0, 30], [0, 30], 'r--', linewidth=2, label='$t^2 = F$ (identity)')
ax1.set_xlabel('$t^2$ (t-statistic squared)', fontsize=12)
ax1.set_ylabel('$F$ (F-statistic)', fontsize=12)
ax1.set_title('Simple Regression: $t^2$ vs $F$', fontsize=13)
ax1.legend(fontsize=11)
ax1.grid(True, alpha=0.3)
ax1.set_xlim(0, 30)
ax1.set_ylim(0, 30)

# 差のヒストグラム
diff = t_squared_vals - f_vals
ax2.hist(diff, bins=50, density=True, alpha=0.7, color='steelblue',
         edgecolor='black')
ax2.axvline(x=0, color='red', linestyle='--', linewidth=2)
ax2.set_xlabel('$t^2 - F$', fontsize=12)
ax2.set_ylabel('Density', fontsize=12)
ax2.set_title(f'$t^2 - F$ (mean = {np.mean(diff):.2e})', fontsize=13)
ax2.grid(True, alpha=0.3)

plt.suptitle('Equivalence of t-test and F-test in Simple Regression',
             fontsize=14, y=1.02)
plt.tight_layout()
plt.show()

R² と F統計量の関係の可視化

import numpy as np
import matplotlib.pyplot as plt
from scipy import stats

# R²からF統計量を計算するデモ
n_values = [30, 50, 100, 200]
p = 3  # 説明変数の数
R2_range = np.linspace(0.01, 0.99, 100)

fig, ax = plt.subplots(figsize=(10, 6))

for n in n_values:
    F_values = (R2_range / p) / ((1 - R2_range) / (n - p - 1))
    ax.plot(R2_range, F_values, linewidth=2, label=f'n = {n}')

# 有意水準の臨界値を表示
for n in n_values:
    f_crit = stats.f.ppf(0.95, p, n - p - 1)
    R2_crit = (f_crit * p) / (f_crit * p + (n - p - 1))
    ax.plot(R2_crit, f_crit, 'rx', markersize=10, markeredgewidth=2)

ax.set_xlabel('$R^2$', fontsize=13)
ax.set_ylabel('F-statistic', fontsize=13)
ax.set_title(f'Relationship between $R^2$ and F-statistic (p = {p})', fontsize=14)
ax.legend(fontsize=11)
ax.grid(True, alpha=0.3)
ax.set_ylim(0, 50)

plt.tight_layout()
plt.show()

# 各nでの臨界R²値を表示
print("=== 有意水準5%での臨界R²値 ===")
for n in n_values:
    f_crit = stats.f.ppf(0.95, p, n - p - 1)
    R2_crit = (f_crit * p) / (f_crit * p + (n - p - 1))
    print(f"n = {n}: F_crit = {f_crit:.4f}, R²_crit = {R2_crit:.4f}")

まとめ

本記事では、回帰分析における仮説検定について解説しました。

  • 回帰係数のt検定: $t_j = \hat{\beta}_j / \text{SE}(\hat{\beta}_j) \sim t_{n-p-1}$ で、個々の回帰係数が0かどうかを検定
  • モデル全体のF検定: $F = MSR/MSE \sim F_{p, n-p-1}$ で、すべての回帰係数が同時に0かどうかを検定
  • 平方和の分解: $TSS = SSR + RSS$(交差項は正規方程式により消える)
  • $R^2$ と $F$ の関係: $F = \frac{R^2/p}{(1-R^2)/(n-p-1)}$
  • 単回帰: $F = t^2$(t検定とF検定は等価)
  • 残差分析: 正規性、等分散性、外れ値の確認が重要

次のステップとして、以下の記事も参考にしてください。