決定係数(R二乗値)とは?回帰モデルの評価指標をわかりやすく解説

決定係数(coefficient of determination, $R^2$)は、回帰モデルの当てはまりの良さを評価する最も基本的な指標です。「モデルがデータの変動をどの程度説明できているか」を0から1の値で表し、1に近いほどモデルの説明力が高いことを意味します。

本記事では、決定係数の定義を全変動の分解から丁寧に導出し、Pythonでの計算方法と注意点を解説します。

本記事の内容

  • 全変動・回帰変動・残差変動の定義と関係
  • 決定係数 $R^2$ の導出
  • 自由度調整済み決定係数 $\bar{R}^2$
  • Pythonでの計算と可視化

前提知識

この記事を読む前に、重回帰分析の基礎を押さえておくと理解が深まります。

変動の分解

回帰分析では、目的変数 $y$ の変動を「モデルで説明できる部分」と「説明できない部分」に分解します。

$n$ 個のデータ $(x_i, y_i)$ に対し、モデルの予測値を $\hat{y}_i$、目的変数の平均を $\bar{y} = \frac{1}{n}\sum_{i=1}^{n} y_i$ とすると、以下の3つの量を定義します。

全変動(Total Sum of Squares, SST):

$$ \text{SST} = \sum_{i=1}^{n} (y_i – \bar{y})^2 $$

回帰変動(Regression Sum of Squares, SSR):

$$ \text{SSR} = \sum_{i=1}^{n} (\hat{y}_i – \bar{y})^2 $$

残差変動(Residual Sum of Squares, SSE):

$$ \text{SSE} = \sum_{i=1}^{n} (y_i – \hat{y}_i)^2 $$

最小二乗法を用いた回帰では、以下の分解が成り立ちます。

$$ \text{SST} = \text{SSR} + \text{SSE} $$

イメージとしては、データのばらつき(SST)は「モデルが捉えたばらつき(SSR)」と「モデルが捉えきれなかったばらつき(SSE)」の和になっています。

決定係数の定義

決定係数 $R^2$ は、全変動のうちモデルで説明できる割合として定義されます。

$$ R^2 = \frac{\text{SSR}}{\text{SST}} = 1 – \frac{\text{SSE}}{\text{SST}} $$

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

$R^2$ の解釈:

  • $R^2 = 1$: 全てのデータ点をモデルが完全に予測($\hat{y}_i = y_i$ for all $i$)
  • $R^2 = 0$: モデルの予測力が平均値の予測と同等
  • $R^2 < 0$: モデルの予測が平均値の予測よりも悪い(過学習等で発生)

自由度調整済み決定係数

$R^2$ には、説明変数を増やすと必ず値が大きくなる(または不変)という性質があります。これではモデルの複雑さを考慮した適切な評価ができません。

自由度調整済み決定係数 $\bar{R}^2$ は、説明変数の数 $p$ とサンプル数 $n$ で補正します。

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

$$ = 1 – \frac{\text{SSE} / (n – p – 1)}{\text{SST} / (n – 1)} $$

$\bar{R}^2$ は不要な説明変数を加えるとペナルティが働き、値が下がる場合があります。

Pythonでの実装

import numpy as np
import matplotlib.pyplot as plt
from sklearn.linear_model import LinearRegression
from sklearn.preprocessing import PolynomialFeatures
from sklearn.metrics import r2_score

np.random.seed(42)

# --- データ生成 ---
n = 50
X = np.random.uniform(0, 10, n).reshape(-1, 1)
y_true = 2 * X.ravel() + 0.5 * X.ravel()**2 - 10
y = y_true + np.random.randn(n) * 5

# --- スクラッチでR^2を計算 ---
def r_squared(y_true, y_pred):
    """決定係数の計算"""
    ss_res = np.sum((y_true - y_pred) ** 2)
    ss_tot = np.sum((y_true - np.mean(y_true)) ** 2)
    return 1 - ss_res / ss_tot

def adjusted_r_squared(y_true, y_pred, n_features):
    """自由度調整済み決定係数"""
    n = len(y_true)
    r2 = r_squared(y_true, y_pred)
    return 1 - (n - 1) / (n - n_features - 1) * (1 - r2)

# --- 多項式次数ごとのR^2を比較 ---
degrees = range(1, 10)
r2_train_list = []
r2_adj_list = []

X_plot = np.linspace(0, 10, 200).reshape(-1, 1)

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

for i, deg in enumerate([1, 2, 5, 9]):
    ax = axes[i // 2, i % 2]
    poly = PolynomialFeatures(degree=deg)
    X_poly = poly.fit_transform(X)
    X_plot_poly = poly.transform(X_plot)

    model = LinearRegression()
    model.fit(X_poly, y)
    y_pred = model.predict(X_poly)
    y_plot = model.predict(X_plot_poly)

    r2 = r_squared(y, y_pred)
    r2_adj = adjusted_r_squared(y, y_pred, deg)

    ax.scatter(X, y, c='red', s=20, alpha=0.7, label='Data')
    ax.plot(X_plot, y_plot, 'b-', label=f'degree={deg}')
    ax.set_title(f'Degree {deg}: $R^2$={r2:.4f}, $\\bar{{R}}^2$={r2_adj:.4f}')
    ax.set_xlabel('x')
    ax.set_ylabel('y')
    ax.legend(fontsize=8)
    ax.grid(True, alpha=0.3)

plt.tight_layout()
plt.show()

# --- R^2とadjusted R^2の次数依存性 ---
r2_list = []
r2_adj_list = []
for deg in degrees:
    poly = PolynomialFeatures(degree=deg)
    X_poly = poly.fit_transform(X)
    model = LinearRegression()
    model.fit(X_poly, y)
    y_pred = model.predict(X_poly)
    r2_list.append(r_squared(y, y_pred))
    r2_adj_list.append(adjusted_r_squared(y, y_pred, deg))

fig, ax = plt.subplots(figsize=(8, 5))
ax.plot(list(degrees), r2_list, 'bo-', label='$R^2$')
ax.plot(list(degrees), r2_adj_list, 'rs--', label='Adjusted $R^2$')
ax.set_xlabel('Polynomial Degree')
ax.set_ylabel('Score')
ax.set_title('$R^2$ vs Adjusted $R^2$')
ax.legend()
ax.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()

# scikit-learnとの比較
poly = PolynomialFeatures(degree=2)
X_poly = poly.fit_transform(X)
model = LinearRegression()
model.fit(X_poly, y)
y_pred = model.predict(X_poly)
print(f"スクラッチ R^2: {r_squared(y, y_pred):.6f}")
print(f"sklearn R^2:    {r2_score(y, y_pred):.6f}")

多項式の次数を上げるとR^2は単調に増加しますが、自由度調整済みR^2はある次数で頭打ちになり、不要な変数を追加するとむしろ低下することが確認できます。

まとめ

本記事では、決定係数 $R^2$ について解説しました。

  • $R^2$ はデータの全変動のうち、モデルで説明できる割合を表す指標
  • $R^2 = 1 – \text{SSE}/\text{SST}$ で定義され、1に近いほどモデルの当てはまりが良い
  • 自由度調整済み $\bar{R}^2$ はモデルの複雑さにペナルティを課し、過学習を抑制する
  • 回帰モデルの評価では $R^2$ だけでなく、残差プロットや他の指標も合わせて確認することが重要

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