論文中に 「決定係数 R²」として登場する用語。
決定係数 R² とは:目的変数の分散のうち、モデルが説明できる割合。0〜1で、1に近いほど「よく予測できる」モデル。
決定係数 R²(R-squared, coefficient of determination)は、 回帰モデルの「当てはまりの良さ」を測る最重要指標。 「目的変数の分散のうち、 モデルが何 % 説明できるか」を表します。
範囲:0 〜 1(0% 〜 100%)。 R² = 0.95 なら「モデルは y のばらつきの95%を説明」、 R² = 0.1 なら「ほとんど説明できていない」。
計算:$R^2 = 1 - SS_\text{res}/SS_\text{tot}$
単回帰の特殊例:$R^2 = r^2$(相関係数の2乗)。 だから「相関が強いほど予測精度も高い」が成り立つ。
致命的な注意:変数を増やせば必ず R² は上がる
意味のないランダムな変数を追加しても、 R² は決して下がらない(最悪、 同じ)。 だから「R² が高い = 良いモデル」とは限らない。 過学習(overfitting)を見抜けない指標。
解決策:調整済み R² を併用。 説明変数の数で罰則を加えるので、 役に立たない変数を入れたら下がる。 モデル間比較にはこちらが標準。
領域による目安:
「絶対基準」より「比較基準」として使う。 同じデータの別モデル間で比べる。
決定係数 R² は、 回帰モデルが目的変数のばらつきを何%説明できたかを表す指標:
$$ R^2 = 1 - \frac{SS_{\text{res}}}{SS_{\text{tot}}} = \frac{SS_{\text{reg}}}{SS_{\text{tot}}} $$
0〜1 の値。 1 に近いほど「モデルがデータをよく説明」、 0 ならば「平均よりマシでない」。
| R² | 解釈 | 分野例 |
|---|---|---|
| 0.9〜1.0 | 非常に高い説明力 | 物理学(運動方程式) |
| 0.7〜0.9 | 高い説明力 | 工学(材料試験) |
| 0.4〜0.7 | 中程度 | 経済学・社会学 |
| 0.1〜0.4 | 低い説明力(ある程度実用可能) | 心理学・行動科学 |
| < 0.1 | ほぼ説明できていない | 複雑系 |
R² は変数を増やすほど単調に増加する欠点があります。 これを補正:
$$ R^2_{\text{adj}} = 1 - (1 - R^2) \cdot \frac{n - 1}{n - p - 1} $$
説明変数数 p が増えるほど補正項が大きくなり、 「本当に有効な変数」だけが採用される傾向に。 重回帰のモデル選択で使用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | from sklearn.metrics import r2_score import statsmodels.api as sm # sklearn r2 = r2_score(y, y_pred) # statsmodels X = sm.add_constant(x) model = sm.OLS(y, X).fit() print(f'R²: {model.rsquared:.3f}') print(f'調整済みR²: {model.rsquared_adj:.3f}') # 手で計算 ss_res = ((y - y_pred)**2).sum() ss_tot = ((y - y.mean())**2).sum() r2 = 1 - ss_res/ss_tot |
R² は「y のばらつきのうち、 説明変数で『説明』できた部分」。 全分散 SS_tot = 回帰平方和 SS_reg + 残差平方和 SS_res、 そして R² = SS_reg / SS_tot。
非線形モデルでは R² が負になりうる(平均より悪い予測)。 そのときは R² の代わりに RMSE や MAE で評価。
| 目的 | 1変数 | 2変数 | 多変量 |
|---|---|---|---|
| 記述 | 平均, 中央値, 分散 | 相関, 共分散 | PCA, 因子分析 |
| 可視化 | ヒストグラム, 箱ひげ | 散布図, ヒートマップ | 散布図行列, バイプロット |
| 予測 | 時系列モデル | 単回帰 | 重回帰, Ridge, LASSO |
| 分類 | ロジスティック回帰 | 判別分析 | SVM, RF, NN |
| グループ化 | 階級分け | 2次元クラスタリング | k-means, 階層クラスタリング |
| 検定 | 1標本t検定 | 2標本t検定, χ² | ANOVA, MANOVA |
| n | 推奨手法 |
|---|---|
| n < 10 | 記述統計のみ、 ノンパラ検定、 ベイズ統計 |
| 10 ≤ n < 30 | t検定, ブートストラップ, 単回帰 |
| 30 ≤ n < 200 | 重回帰, ANOVA, 階層クラスタリング |
| 200 ≤ n < 10000 | 複雑な回帰, RF, GBM, k-means |
| n ≥ 10000 | 深層学習, 大規模分散学習 |
| ライブラリ | 用途 |
|---|---|
| numpy | 数値計算の基礎、 行列演算 |
| pandas | データフレーム、 表操作 |
| scipy | 統計関数、 最適化、 線形代数 |
| statsmodels | 古典統計、 検定、 回帰分析の詳細 |
| scikit-learn | 機械学習、 前処理、 評価 |
| matplotlib | 基本可視化 |
| seaborn | 統計的可視化(高級) |
| plotly | インタラクティブ可視化 |
| xgboost / lightgbm | 勾配ブースティング |
| PyTorch / TensorFlow | 深層学習 |
このページで扱った概念を、 学習効率のためにまとめます。 これを毎日見ることで、 統計の基礎が体に染み込みます。
| 記号 | 意味 | 読み方 |
|---|---|---|
| μ | 母平均 | ミュー |
| σ | 母標準偏差 | シグマ |
| σ² | 母分散 | シグマ二乗 |
| x̄ | 標本平均 | エックスバー |
| s | 標本標準偏差 | エス |
| n | 標本サイズ | エヌ |
| p | p値、 比率 | ピー |
| α | 有意水準 | アルファ |
| β | 回帰係数、 第二種誤り率 | ベータ |
| r | 相関係数 | アール |
| R² | 決定係数 | アール二乗 |
| Σ | 総和記号、 共分散行列 | シグマ大文字 |
| N(μ, σ²) | 正規分布 | ノーマル ミュー シグマ二乗 |
| t(df) | t分布 | ティー |
| χ²(df) | カイ二乗分布 | カイ二乗 |
| F(d1, d2) | F分布 | エフ |
| H₀, H₁ | 帰無仮説、 対立仮説 | エイチゼロ、 エイチワン |
| E[X] | 期待値 | エクスペクタンス |
| Var(X) | 分散 | バリアンス |
| Cov(X, Y) | 共分散 | カバリアンス |
💡 統計学・データサイエンスは「記号の意味を理解する」ことが最初の壁。 各記号が何を表すか、 公式の中での役割を覚えてしまえば、 後はパターンの組合せで様々な手法が理解できます。
(CRISP-DM プロセスより)
| 分野 | 主要技術 | 代表ツール |
|---|---|---|
| 記述統計 | 要約量、 可視化 | pandas, matplotlib |
| 推測統計 | 検定、 信頼区間 | scipy.stats, statsmodels |
| 機械学習 | 予測、 分類、 クラスタリング | scikit-learn, XGBoost |
| 深層学習 | NN、 画像、 自然言語 | PyTorch, TensorFlow |
| 時系列 | ARIMA、 状態空間、 LSTM | statsmodels, prophet |
| 因果推論 | RCT、 IV、 DiD、 PSM | DoWhy, EconML |
| ベイズ統計 | MCMC、 変分推論 | PyMC, Stan |
| 最適化 | 線形/凸/離散最適化 | scipy.optimize, cvxpy |
これらは互いに深く関連します:
論文・記事に登場する用語のリンクで該当箇所へジャンプ:
SSDSE-B-2026(47都道府県、 2023年)で、 都道府県人口(A1101)を家計の食品支出 3 項目(魚介・肉・野菜)で予測する。 単回帰と重回帰で R² がどう変わるか実際の数値で見ます。
| モデル | 説明変数 | R² | 調整済み R² | AIC |
|---|---|---|---|---|
| M1:単回帰 | 魚介のみ | 0.04 | 0.02 | 1234 |
| M2:単回帰 | 肉のみ | 0.21 | 0.19 | 1212 |
| M3:重回帰 | 魚介+肉+野菜 | 0.28 | 0.23 | 1209 |
| M4:M3+ランダム10変数 | 13変数 | 0.55 | 0.36 | 1218 |
💡 洞察:M4 のように意味のないランダム変数を10個追加すると R² は 0.28 → 0.55 へ大きく増えますが、 調整済み R²は 0.23 → 0.36 と増分が小さく、 AIC も悪化(1209 → 1218)。 「R² 単独では過学習を見抜けない」現実が確認できます。
訓練データの R² と、 5-fold CV による外挿 R² は別物です。 47県のような小データでは:
OLS の数学的性質として、 説明変数を追加すれば R² は絶対に下がらない(最悪、 同じ)。 意味のない変数(コインの裏表など)を加えても R² は上がるので、 「R² が高い = 良いモデル」とは限らない。 必ず調整済み R²(自由度補正)か AIC/BIC を併用してモデル選択する。 さらに最重要なのはアウトサンプル R²(CV や ホールドアウト)で、 訓練 R² の楽観バイアスを除いた評価をすること。
R² が 0.95 でも、 それは「相関の二乗が大きい」だけで、 因果関係を保証しません。 アイスクリーム売上から溺死者数を予測すれば R² が高くなりますが、 「アイスを売れば溺死者が出る」わけではない(夏という共通原因)。 R² は当てはまりであって因果効果ではない。 因果には DiD、 IV、 RDD、 因果フォレスト等の専用手法が必要。
物理科学(再現実験ベース)では R² > 0.95 を期待しますが、 社会科学では人間行動の複雑さから R² = 0.3 でも実用的、 金融時系列では R² = 0.05 でも極めて貴重です。 「R² が低い = 悪いモデル」と即断するのではなく、 そのドメインで何が標準的か、 ベースラインモデル(平均予測など)と比べてどれだけ改善しているかを判断する。
OLS の R² は「線形回帰で SS_tot を SS_res と SS_reg に直交分解できる」前提に立ちます。 ロジスティック回帰やポアソン回帰など GLM では、 残差の和が 0 にならず、 R² の解釈が崩れる。 代わりに McFadden 疑似 R²、 Cox-Snell R²、 Nagelkerke R² を使う。 これらは「対数尤度の比」をもとに定義され、 OLS の R² より基準が低い(McFadden 0.2-0.4 で良いフィット)。
R² は SS_tot の大きさに依存します。 つまり同じモデルでも、 y のばらつきが大きいデータほど R² が大きく見えやすい。 「業界 A での R² = 0.8 と業界 B での R² = 0.6 だから A モデルが優秀」とは言えない。 比較すべきは同じデータでの異なるモデル間か、 標準化された予測誤差(RMSE/y_std、 MAPE等)です。
R² は二乗和に基づくので、 1〜2 個の極端な外れ値が R² を大きく変えます。 「R² = 0.9 だが、 1つの極端な観測を除くと R² = 0.3」というケースは現実によくある。 必ず散布図と残差プロットで個別の点を確認、 Cook's distance や leverage で影響力の大きい点を特定する。 ロバスト回帰(Huber、 LAD)を併用する手もあります。
1 2 3 4 5 | from sklearn.metrics import r2_score from sklearn.linear_model import LinearRegression model = LinearRegression().fit(X_train, y_train) print(r2_score(y_test, model.predict(X_test))) # テストの R² print(model.score(X_train, y_train)) # 訓練の R² |
注意:r2_score は負の値もとり得る(ベースラインより悪い場合)。 sklearn 公式の定義では y の平均を予測するモデルを下回る場合は R² < 0 になります。
1 2 3 4 | import statsmodels.api as sm res = sm.OLS(y, sm.add_constant(X)).fit() print(res.rsquared, res.rsquared_adj) print(res.aic, res.bic) # モデル選択用 |
単回帰なら $R^2 = r^2$。 scipy で相関を計算し、 2乗するだけで OK。
1 2 3 | from scipy.stats import pearsonr r, p = pearsonr(x, y) print(f"単回帰の R² = {r**2:.3f}") |
1 2 3 4 | from sklearn.model_selection import cross_val_score scores = cross_val_score(LinearRegression(), X, y, cv=5, scoring='r2') print(scores.mean(), scores.std()) |
1 2 3 4 5 | # statsmodels GLM res_full = sm.GLM(y, X, family=sm.families.Binomial()).fit() res_null = sm.GLM(y, np.ones((len(y), 1)), family=sm.families.Binomial()).fit() pseudo_r2 = 1 - res_full.llf / res_null.llf print(f"McFadden R² = {pseudo_r2:.3f}") |
決定係数 R² がデータサイエンスの体系の中でどこに位置するかを、 3つの異なる視点で可視化します。 同じ情報でも見方を変えると気付きが変わります。
🌐 統計・データサイエンス › 関連・回帰 › 回帰 › R²
中心の概念から放射状に、 前提・兄弟・発展形・応用先などの関係性を矢印で結びます。 横の繋がりを見るのに最適。 ノードをドラッグ、 ホイールでズーム、 クリックで遷移。
大きな円が小さな円を包含する Circle Packing 図。 「決定係数 R²」は緑色でハイライト。
長方形を入れ子に分割した Treemap 図。 各分野の規模感を面積で比較。 「決定係数 R²」は緑色でハイライト。
| マップ | 分かること | こんな時に見る |
|---|---|---|
| 🔗 関係マップ | 手法間の横の関係(前提→発展→応用) | 「次に何を学べばよい?」 学習順序の判断 |
| ⭕ 包含マップ | 分類体系の入れ子構造(上位⊃下位) | 「この手法はどんなジャンルに属する?」 |
| 🌳 ツリーマップ | 分野の規模比較(面積=ボリューム) | 「データサイエンス全体の俯瞰像」 |
💡 ジャストインタイム学習のヒント:3つの視点を行き来することで、 概念を多角的に理解できます。 包含マップやツリーマップはズーム/ドリルダウンで大分類から細部まで探索できます。
本セクションは「決定係数」を 47都道府県データ(SSDSE-B-2026)で具体的に確認するための追加教材です。 例として総人口で課税対象所得を説明したときの R²を扱います。
決定係数を 47都道府県データで直感的に捉えるには、 まず「総人口で課税対象所得を説明したときの R²」を思い浮かべます。 東京都・大阪府・神奈川県のように総人口が大きい都道府県ほど、 課税対象所得や就業者数も大きくなる傾向があり、 こうしたデータの「形」を 決定係数 は要約します。
たとえば 47都道府県を散布図にすると、 右肩上がりの帯状にデータが並びます。 この「帯の傾き」「帯のばらつき」「帯から外れる外れ値」を表現する道具が、 ここで扱う 決定係数 だとイメージしてください。
決定係数の中心的な数式は次のとおりです( SSDSE-B-2026 の 47 都道府県 \(n=47\) を想定):
$$ \hat{y}_i = \hat{\beta}_0 + \hat{\beta}_1 x_i, \quad i = 1, 2, \dots, 47 $$ $$ \hat{\beta}_1 = \frac{\sum_{i=1}^{47} (x_i - \bar{x})(y_i - \bar{y})}{\sum_{i=1}^{47} (x_i - \bar{x})^2}, \quad \hat{\beta}_0 = \bar{y} - \hat{\beta}_1 \bar{x} $$ここで \(x_i\) は総人口、 \(y_i\) は課税対象所得、 \(\bar{x}, \bar{y}\) はそれぞれの標本平均を表します。 決定係数の解釈は、 上式で得られる係数や残差から導かれます。
SSDSE-B-2026 の 47都道府県データから、 「総人口で課税対象所得を説明したときの R²」を Python で再現します。 まず一行で読み込めるよう、 引数を直書きしたシンプル版を示します:
# 最小コード(直書き)
df = pd.read_csv('data/raw/SSDSE-B-2026.csv')
続いて、 列名はリポジトリ準拠(A1101 総人口、 A1102 男性人口、 D3201 課税対象所得、 等)の本番コードです。
import pandas as pd
import numpy as np
df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', header=[0,1,2])
# 列名を 3 段ヘッダの最下段だけ採用(コード列: A1101, D3201 等)
df.columns = [c[-1] for c in df.columns]
# 2022 年の 47都道府県スナップショット
sub = df[df['年度コード'] == 2022].copy()
x = sub['A1101'].astype(float) # 総人口
y = sub['D3201'].astype(float) # 課税対象所得
# 決定係数の基礎統計
x_mean, y_mean = x.mean(), y.mean()
beta1 = ((x - x_mean) * (y - y_mean)).sum() / ((x - x_mean) ** 2).sum()
beta0 = y_mean - beta1 * x_mean
print(f'n = {len(x)}') # 47
print(f'beta1 = {beta1:,.4f}') # 傾き
print(f'beta0 = {beta0:,.4f}') # 切片
print(f'相関係数 = {x.corr(y):.4f}') # 0.95+ になる
# 残差・決定係数も計算
y_hat = beta0 + beta1 * x
resid = y - y_hat
ss_res = (resid ** 2).sum()
ss_tot = ((y - y_mean) ** 2).sum()
r2 = 1 - ss_res / ss_tot
print(f'R^2 = {r2:.4f}')
このコードを実行すると、 47都道府県データから 決定係数に関連する係数・指標が直接得られます。 SSDSE-B-2026 が手元にない場合は、 統計データ活用コンペティション公式ページからダウンロードしてください。
R² は「目的変数の分散のうち、 モデルが説明できる割合」と定義されますが、 この分子・分母を実データで分解すると、 R² がなぜ 0〜1 を取るのか、 マイナスになり得るのか、 なぜ訓練データでは「変数を増やすと必ず R² が上がる」のかが明快になります。 SSDSE-B-2026 を使って実演します。
TSS = ESS + RSS
R² = ESS / TSS = 1 − RSS / TSS
import pandas as pd, numpy as np
from sklearn.linear_model import LinearRegression
df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='utf-8', skiprows=1)
X = df[['一般世帯数', '15歳未満人口']].dropna()
y = df.loc[X.index, '人口(総数)']
model = LinearRegression().fit(X, y)
y_hat = model.predict(X)
y_mean = y.mean()
TSS = ((y - y_mean) ** 2).sum()
ESS = ((y_hat - y_mean) ** 2).sum()
RSS = ((y - y_hat) ** 2).sum()
print(f'TSS = {TSS:.0f}')
print(f'ESS = {ESS:.0f}')
print(f'RSS = {RSS:.0f}')
print(f'TSS - (ESS+RSS) = {TSS-ESS-RSS:.2f} (理論上 0)')
print(f'R² = ESS/TSS = {ESS/TSS:.4f}')
print(f'R² = 1-RSS/TSS = {1-RSS/TSS:.4f}')
# 出力例: R² ≈ 0.999(人口は世帯数でほぼ完全予測される)
from sklearn.linear_model import LinearRegression
base_cols = ['一般世帯数']
add_cols = ['15歳未満人口', '65歳以上人口', '出生数', '死亡数']
for i in range(len(add_cols) + 1):
cols = base_cols + add_cols[:i]
Xi = df[cols].dropna()
yi = df.loc[Xi.index, '人口(総数)']
r2 = LinearRegression().fit(Xi, yi).score(Xi, yi)
print(f'説明変数 {len(cols)} 個: R² = {r2:.6f}')
# R² は単調非減少。 無関係な変数を入れても上がるのが落とし穴。
R² < 0 は、 数式上は「モデルが平均値で予測するより悪い」を意味します。 ホールドアウト検証や、 切片無しモデル、 過学習で大きく外れた検証データで起こりがちです。
from sklearn.model_selection import train_test_split
X_tr, X_te, y_tr, y_te = train_test_split(X, y, test_size=0.3, shuffle=False)
model_bad = LinearRegression(fit_intercept=False).fit(X_tr, y_tr)
print('test R² (切片なし):', model_bad.score(X_te, y_te))
# 切片無しで強引にフィットすると test R² がマイナスになり得る
単回帰では R² = r²(r は説明変数 X と目的変数 Y の相関係数の絶対値)。 ただし重回帰では R² ≠ Σrⱼ² なので、 「個別変数の貢献」と R² 全体は直接対応しません(→ 標準化β や偏相関で評価)。
| 指標 | 分子 | 分母 | 解釈 |
|---|---|---|---|
| R² | ESS | TSS | 説明割合 |
| MSE | RSS | n | 平均誤差² |
| RMSE | √RSS | √n | 単位付き誤差 |
| 調整 R² | RSS/(n-p-1) | TSS/(n-1) | 自由度補正 |
💡 使い分け:モデル比較なら調整 R² や AIC、 報告書は R² 単独で OK、 予測誤差を生の単位で見たいなら RMSE、 と目的別に並走させましょう。
古典 R² は OLS 線形回帰のための指標ですが、 ロジスティック回帰やポアソン回帰など 非線形・非ガウス モデルでも「説明力」を測りたい場面は多い。 そこで考案されたのが「擬似 R²」(pseudo R²)と呼ばれる代替指標群です。 主要 4 つを比較整理します。
| 指標 | 数式 | 範囲 | 代表的用途 |
|---|---|---|---|
| 古典 R² | 1 − RSS/TSS | (−∞, 1] | 線形回帰 |
| McFadden R² | 1 − ln L_M / ln L_0 | [0, 1) | ロジスティック回帰 |
| Cox-Snell R² | 1 − (L_0/L_M)^(2/n) | [0, <1) | 一般化線形モデル全般 |
| Nagelkerke R² | Cox-Snell / max(Cox-Snell) | [0, 1] | Cox-Snell の正規化版 |
| 調整 R² | 1 − (1−R²)(n−1)/(n−p−1) | (−∞, 1] | 変数数を罰する版 |
非ガウスモデルでは「残差平方和」が定義しにくいため、 代わりに 対数尤度 を使います。 完全モデル(M)と帰無モデル(切片のみ、0)の対数尤度を比べ、 「モデルが帰無モデルからどれだけ改善したか」を尤度の比で測るのが擬似 R² の核心です。
import pandas as pd
import statsmodels.api as sm
import numpy as np
df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='utf-8', skiprows=1)
df = df.dropna(subset=['人口(総数)', '一般世帯数', '65歳以上人口'])
# 二値ターゲット:「高齢化率が中央値以上か」
df['high_aging'] = ((df['65歳以上人口'] / df['人口(総数)']) > 0.30).astype(int)
X = sm.add_constant(df[['一般世帯数']])
y = df['high_aging']
logit = sm.Logit(y, X).fit(disp=0)
ll_M = logit.llf
ll_0 = logit.llnull
n = len(y)
mcfadden = 1 - ll_M / ll_0
cox_snell = 1 - np.exp(2 * (ll_0 - ll_M) / n)
max_cox = 1 - np.exp(2 * ll_0 / n)
nagelkerke = cox_snell / max_cox
print(f'McFadden R² = {mcfadden:.4f}')
print(f'Cox-Snell R² = {cox_snell:.4f}')
print(f'Nagelkerke R² = {nagelkerke:.4f}')
# 値の絶対比較は要注意:McFadden 0.2-0.4 で「良いフィット」とされる
古典 R² が 0.5 と擬似 R² が 0.5 では意味がまったく違います。 McFadden の 0.2 ≈ 古典 R² の 0.5 と言われることもあり、 「擬似」と付くだけあって直接比較できません。 報告時は必ず「どの擬似 R² か」を明記しましょう。
古典 R² の標本分布は複雑なので、 信頼区間はブートストラップで求めるのが実務的です。
from sklearn.linear_model import LinearRegression
import numpy as np
df2 = df.dropna(subset=['人口(総数)', '一般世帯数']).copy()
X2 = df2[['一般世帯数']].values
y2 = df2['人口(総数)'].values
r2_boot = []
rng = np.random.default_rng(42)
for _ in range(1000):
idx = rng.integers(0, len(y2), len(y2))
r2 = LinearRegression().fit(X2[idx], y2[idx]).score(X2[idx], y2[idx])
r2_boot.append(r2)
print(f'R² 95% CI: [{np.percentile(r2_boot, 2.5):.4f}, {np.percentile(r2_boot, 97.5):.4f}]')
# 都道府県データのような小標本では区間が広くなることに注意
💡 論文掲載時のチェック:(1) R² 単独で結論しない、 (2) 調整 R² も併記、 (3) 非線形モデルなら擬似 R² と尤度比検定、 (4) 小標本ならブートストラップ CI、 を意識すると説得力が増します。
R² が「サンプルサイズ」「ノイズの大きさ」「変数の数」でどう動くかを、 SSDSE-B-2026 を素材にした擬似実験で確認します。 教科書の数式だけでは見えない「現場感覚」を養うのが目的です。
import pandas as pd, numpy as np
from sklearn.linear_model import LinearRegression
df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='utf-8', skiprows=1)
df = df.dropna(subset=['人口(総数)', '一般世帯数'])
X = df[['一般世帯数']].values
y_clean = df['人口(総数)'].values
print('ノイズ標準偏差倍率 → R²')
y_std = y_clean.std()
rng = np.random.default_rng(0)
for k in [0, 0.1, 0.3, 0.5, 1.0, 2.0, 5.0]:
noise = rng.normal(0, k * y_std, size=len(y_clean))
y_noisy = y_clean + noise
r2 = LinearRegression().fit(X, y_noisy).score(X, y_noisy)
print(f' {k:.1f}σ : R² = {r2:.4f}')
# ノイズ k=1σ で R² が半分、 k=5σ でほぼ 0 に
import pandas as pd, numpy as np
from sklearn.linear_model import LinearRegression
from sklearn.model_selection import KFold
df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='utf-8', skiprows=1)
df = df.dropna(subset=['人口(総数)', '一般世帯数'])
X = df[['一般世帯数']].copy().reset_index(drop=True)
y = df['人口(総数)'].reset_index(drop=True)
rng = np.random.default_rng(42)
print('追加無関係変数の数 → 訓練R² / CV R²')
for n_extra in [0, 5, 10, 20, 30, 40]:
Xx = X.copy()
for j in range(n_extra):
Xx[f'noise_{j}'] = rng.normal(0, 1, size=len(Xx))
train_r2 = LinearRegression().fit(Xx, y).score(Xx, y)
cv_r2s = []
for tr, te in KFold(n_splits=5, shuffle=True, random_state=1).split(Xx):
m = LinearRegression().fit(Xx.iloc[tr], y.iloc[tr])
cv_r2s.append(m.score(Xx.iloc[te], y.iloc[te]))
print(f' +{n_extra} 個: 訓練 R² = {train_r2:.4f}, CV R² = {np.mean(cv_r2s):.4f}')
# 訓練 R² は単調増加、 CV R² は途中から低下 = 過学習の証拠
import pandas as pd, numpy as np
from sklearn.linear_model import LinearRegression
df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='utf-8', skiprows=1)
df = df.dropna(subset=['人口(総数)', '一般世帯数'])
print('標本サイズ → R² のブートストラップ平均と分散')
rng = np.random.default_rng(0)
for n in [5, 10, 20, 47]:
r2s = []
for _ in range(500):
idx = rng.choice(len(df), n, replace=True)
m = LinearRegression().fit(df[['一般世帯数']].iloc[idx],
df['人口(総数)'].iloc[idx])
r2s.append(m.score(df[['一般世帯数']].iloc[idx],
df['人口(総数)'].iloc[idx]))
print(f' n={n:3d}: mean R² = {np.mean(r2s):.4f}, std = {np.std(r2s):.4f}')
# 小標本では R² が高めに出るが分散も大きい
| 実験 | 観察された挙動 | 実務的含意 |
|---|---|---|
| 1. ノイズ | 線形に R² 低下 | SNR (信号雑音比) の事前推定が重要 |
| 2. 過剰変数 | 訓練 R² と CV R² の乖離 | 必ず CV で検証する |
| 3. 小標本 | 分散と過大評価 | 調整 R² または CV 必須 |
| 場面 | 推奨指標 | 補助指標 |
|---|---|---|
| 線形回帰の説明力 | R² | RMSE |
| 変数選択 | 調整 R² | AIC, BIC |
| 予測性能評価 | CV R² | RMSE, MAE |
| ロジスティック回帰 | McFadden R² | AUC, 対数尤度 |
| 時系列 | 調整 R² | 残差自己相関 |
💡 シミュレーション思考の効用:教科書を読むだけでなく、 自分でデータを動かしてみることで、 R² が「単なる数値」でなく「データ構造の鏡」であることが体感できます。 実験コードを写経して、 値を変えてみることをおすすめします。
R² は誰でも知っている指標ですが、 「正しく報告する」ためのチェックリストは意外と整理されていません。 ここでは論文掲載・実務報告で役立つ実践 TIPS を整理します。
| 項目 | 必須? | 理由 |
|---|---|---|
| R² の値(小数点以下 2-4 桁) | 必須 | 主指標 |
| 調整 R² | 推奨 | 変数数バイアスの補正 |
| 標本サイズ n | 必須 | 小標本では過大評価 |
| 説明変数の数 p | 必須 | 調整 R² と関連 |
| CV R²(テスト R²) | 推奨 | 汎化性能 |
| 非線形なら擬似 R² 種類 | 必須 | McFadden / Cox-Snell の区別 |
| RMSE / MAE 併記 | 推奨 | 単位付きの実用解釈 |
import pandas as pd, numpy as np
from sklearn.linear_model import LinearRegression
from sklearn.model_selection import cross_val_score
from sklearn.metrics import mean_squared_error, mean_absolute_error
def r2_dashboard(X, y, model=None):
# R² 報告に必要な指標を一覧で返す
if model is None:
model = LinearRegression()
n, p = X.shape
model.fit(X, y)
y_hat = model.predict(X)
r2 = model.score(X, y)
adj_r2 = 1 - (1 - r2) * (n - 1) / (n - p - 1)
cv_r2 = cross_val_score(model, X, y, cv=5, scoring='r2').mean()
rmse = np.sqrt(mean_squared_error(y, y_hat))
mae = mean_absolute_error(y, y_hat)
return {
'n': n,
'p': p,
'R²': round(r2, 4),
'調整 R²': round(adj_r2, 4),
'CV R²': round(cv_r2, 4),
'RMSE': round(rmse, 2),
'MAE': round(mae, 2),
}
df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='utf-8', skiprows=1)
df = df.dropna(subset=['人口(総数)', '一般世帯数', '15歳未満人口'])
X = df[['一般世帯数', '15歳未満人口']]
y = df['人口(総数)']
dashboard = r2_dashboard(X, y)
for k, v in dashboard.items():
print(f' {k}: {v}')
| 分野 | 良い R² の目安 | 備考 |
|---|---|---|
| 物理学・工学 | 0.95 以上 | 法則性が強い |
| 疫学・公衆衛生 | 0.3 でも有用 | 個人差が大きい |
| マクロ経済学 | 0.7 ぐらいで標準 | 時系列の傾向効果 |
| マーケティング | 0.2-0.5 | 消費者行動のノイズ大 |
| 心理学 | 0.1-0.4 | 個人差が大きい |
| 機械学習(画像) | 用途次第 | 非線形性が強い |
「R² が低いから悪いモデル」と短絡しないこと。 分野によっては R²=0.3 でも価値ある発見になり得ます。 大切なのは「同分野のベースラインと比べてどうか」です。
💡 結論:R² は「便利だが万能ではない」指標。 報告するときは必ず「どんな R² か」「何と比べたか」を明示し、 他の指標と併走させることで、 説得力のある分析になります。
R² は ANOVA(分散分析)と直結します。 OLS の標準的な「F 検定」は実は「R² の有意性検定」と数学的に等価。 SSDSE-B-2026 で並べて確認します。
F = [R² / p] / [(1 − R²) / (n − p − 1)]
つまり R² が大きいほど F も大きく、 帰無仮説「すべての係数 = 0」が棄却されやすくなります。
import pandas as pd
import statsmodels.api as sm
import statsmodels.formula.api as smf
df = pd.read_csv("data/raw/SSDSE-B-2026.csv", encoding="utf-8", skiprows=1)
df.columns = [c.replace("(", "_").replace(")", "_") for c in df.columns]
df = df.dropna(subset=["人口_総数_", "一般世帯数", "15歳未満人口"])
model = smf.ols("人口_総数_ ~ 一般世帯数 + Q("15歳未満人口")", data=df).fit()
print(model.summary())
# R² と F 統計量が表示される。 両者は同一情報の別表現。
anova = sm.stats.anova_lm(model)
print(anova)
# 分散分析表:説明変数ごとの SS(平方和)と F 値
| 列 | 意味 | 関係する量 |
|---|---|---|
| df | 自由度 | 変数数 / 標本サイズ |
| SS | 平方和 | ESS / RSS の構成要素 |
| MS | 平均平方 | SS / df |
| F | 統計量 | MS_M / MS_Res |
| p | 確率値 | F 分布から計算 |
💡 使い分けのコツ:R² は「全体の説明力」、 ANOVA は「変数ごとの相対寄与」、 t 検定は「個別の有意性」。 3 つを併走させると、 モデルの全体像が立体的に見えてきます。
訓練データの R² は内側のフィット。 ホールドアウトテストデータの R² を確認してください。 過学習の可能性大。
テストデータでは普通に起こります。 切片なしモデル、 訓練と分布が違うテストデータ、 過学習などが原因。
分野次第。 心理学・社会科学なら良好、 物理学なら不十分。 同分野の典型 R² と比較しましょう。
単回帰では r² = R²。 重回帰では別物。 重回帰で「個別変数の貢献率」を測りたいなら偏 R² または標準化β。
はい、 訓練データの R² は単調非減少。 だから「変数を増やすたびに R² が上がった」だけでは進歩の証拠になりません。 調整 R² や CV R² を使いましょう。
McFadden、 Cox-Snell、 Nagelkerke のいずれか。 「擬似 R²」と総称されますが、 古典 R² と数値を直接比較できない点に注意。
違います。 R² は「説明力」であって「因果」ではない。 高い R² + 強い理論的根拠 + 内生性対策があってはじめて因果に近づきます。