🍰 まずはやさしく
データのばらつきを説明する道具です。
予測モデルがどれくらい信頼できるかを確認するために使います。
スマホの利用時間で成績を予想するような場面で使われます。
ここでは決定係数がどのような意味を持つか読みます。
論文中に 「決定係数 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² がその場で計算されます。 何もない場所をクリック / タップすると点が増えます。 下の色分けバーは、 全変動 SST が「説明できた変動 SSR(緑)」と「説明できない残差変動 SSE(赤)」に分かれる様子を示します。 点を直線に沿わせると R² は 1 に近づき、 ばらけさせると 0 に落ちます。
| SST(全変動) | — |
| SSR(回帰) | — |
| SSE(残差) | — |
| 点の数 n | — |
体感ポイント①:R² = SSR/SST = 1 − SSE/SST。 右のバーで緑(SSR)が占める割合がそのまま R² です。 点を回帰直線にぴったり沿わせると赤(SSE)が消え、 R² → 1。 完全にばらけさせると緑が消え、 R² → 0(=「平均で予測するのと同程度」)になります。
体感ポイント②:外れ値の罠。 「外れ値の罠」プリセットは、 大多数の点に無関係な 1 点を遠くに置いた例。 たった 1 点が回帰直線と R² を大きく動かすのを確認できます(→ 残差の散布図で必ず目視を)。
体感ポイント③:R² だけでは分からない。 Anscombe I〜IV は Anscombe (1973) の有名な 4 データセットで、 どれも R² ≈ 0.67 とほぼ同一なのに形は全く違います(直線・曲線・外れ値 1 点・レバレッジ点)。 「R² が同じ=関係が同じ」ではないことを、 数字ではなく絵で確かめてください。
この体験と本文のつながり
決定係数 R² は、 回帰モデルが目的変数のばらつきを何%説明できたかを表す指標:
$$ R^2 = 1 - \frac{SS_{\text{res}}}{SS_{\text{tot}}} = \frac{SS_{\text{reg}}}{SS_{\text{tot}}} $$
0〜1 の値。 1 に近いほど「モデルがデータをよく説明」、 0 ならば「平均よりマシでない」。
具体例: SSDSE-B-2026 で 2023 年の 47 都道府県について、総人口から出生数を線形回帰で予測する。 全分散 SStot ≒ 1.35×1010、 残差平方和 SSres ≒ 1.24×108。 R² = 1 - 1.24×108 / 1.35×1010 = 0.99。 「出生数の分散の 99% は総人口で説明できている」と読み、 残り 1% は出生率の地域差や測定誤差として残ります。 一方、年平均気温から消費支出を予測すると、 気温と支出の関係は単調でなく弱いため、 R² ≒ 0.05 まで落ちます。 これが「説明できる分散の比率」の実感です。
注意: R² は 「予測がどれだけ当たるか」 と 「真の因果関係を捉えたか」 は別物です。 R²=0.99 でも、 出生数は人口規模の結果 (人口が多いほど生まれる子の絶対数も多い) であり、 少子化対策は『出生率』を動かす話で、 総人口を増やせばよいという政策には直結しません。 R² は あてはまり指標 として読み、 解釈は別途因果推論で行う、 という分業を心がけてください。
| 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 が増えるほど補正項が大きくなり、 「本当に有効な変数」だけが採用される傾向に。 重回帰のモデル選択で使用。
R² (決定係数) を回帰分析で使う際に頻発する誤用を、 SSDSE-B-2026 の都道府県データで起きうる例とともに列挙する。 R² の性質を誤解した解釈が、 そのまま政策提言・モデル選択ミスにつながる。
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 | import pandas as pd import numpy as np from sklearn.metrics import r2_score import statsmodels.api as sm # SSDSE-B-2026 を読み込み、 2023 年の 47 都道府県データを抽出 df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1]) df = df[df['SSDSE-B-2026'] == 2023] x = df['A1101'].astype(float) # 総人口(説明変数) y = df['A4101'].astype(float) # 出生数(目的変数) # OLS 単回帰モデル: y = a + b*x を statsmodels で推定 X = sm.add_constant(x) model = sm.OLS(y, X).fit() y_pred = model.predict(X) # R² を 3 通りで算出(一致を確認) r2_sm = model.rsquared r2_sk = r2_score(y, y_pred) ss_res = ((y - y_pred) ** 2).sum() ss_tot = ((y - y.mean()) ** 2).sum() r2_manual = 1 - ss_res / ss_tot print(f'切片 a = {model.params["const"]:.3e}') print(f'傾き b = {model.params["A1101"]:.4f}') print(f'R² (sm) = {r2_sm:.4f}') print(f'R² (sk) = {r2_sk:.4f}') print(f'R² (手) = {r2_manual:.4f}') print(f'調整済R² = {model.rsquared_adj:.4f}') |
数式 $R^2 = 1 - SS_\text{res}/SS_\text{tot}$ を言葉で読み解くと: SS_tot は「目的変数 y の平均からの全変動」(誤差ゼロのときの基準)、 SS_res は「モデルで予測しきれずに残った変動」。 比率 SS_res/SS_tot は「説明できなかった分の割合」なので、 1 から引くと「説明できた割合」になる。 R²=0.9 は「y のばらつきの 90% をモデルが説明、 10% は誤差」と読む。
記号と読み下しを SSDSE-B-2026 の都道府県データ (総人口 A1101 → 出生数 という回帰を想定) で対応付けます:
| 記号 | 読み方 | 意味 | SSDSE-B-2026 での例 |
|---|---|---|---|
$y_i$ | ワイ・アイ | i 番目の観測値(実測) | i 県の出生数(実値) |
$\hat{y}_i$ | ワイ・ハット・アイ | i 番目のモデル予測値 | 総人口から予測した出生数 |
$\bar{y}$ | ワイ・バー | y の標本平均(「何も知らない」予測の基準) | 47 県の出生数の平均 |
$SS_{\text{tot}}$ | エス・エス・トット | $\sum(y_i-\bar{y})^2$。 全変動(モデルが説明すべき総量) | 県別総生産が平均から離れる全変動 |
$SS_{\text{res}}$ | エス・エス・レジ | $\sum(y_i-\hat{y}_i)^2$。 残差平方和(説明しきれなかった部分) | 予測と実測のズレの 2 乗和 |
$SS_{\text{reg}}$ | エス・エス・レグ | $\sum(\hat{y}_i-\bar{y})^2$。 モデルが説明した変動 | $SS_{\text{tot}} - SS_{\text{res}}$ で計算 |
$R^2$ | アール・ツー(決定係数) | $1 - SS_{\text{res}}/SS_{\text{tot}}$。 説明できた割合 (0〜1) | 人口で総生産の 95% を説明できる、 など |
1 行要約:「分母 = y の動きの総量」、 「分子 = 予測しきれなかった残りの動き」、 「1 − 分子/分母 = 説明できた割合」。 R² が 0 なら「ただ平均値を予測したのと同じ」、 1 なら「完全予測」、 負なら「平均値より悪い予測」を意味します。
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 プロセスより)
💡 この 6 工程はデータ分析全般の流れで、 この用語だけの話ではない。 決定係数は「評価」の工程。 ただし単独では過学習を見抜けないので、 交差検証と併用する。
| 分野 | 主要技術 | 代表ツール |
|---|---|---|
| 記述統計 | 要約量、 可視化 | 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 6 7 8 9 10 11 12 13 14 15 16 17 | # ── この抜粋で使うデータを用意します ── import numpy as np import pandas as pd from sklearn.model_selection import train_test_split df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1]) df = df[df['SSDSE-B-2026'] == 2023] # 2023 年の 47 都道府県 X = df[['A1101', 'A1303', 'L3221']].astype(float).values y = df['A4101'].astype(float).values # 出生数 X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.3, random_state=42) 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² |
random_state を変えるとテスト R² は大きく振れる。 汎化性能を語るなら 1 回の分割ではなく交差検証を使うこと。注意: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 6 7 8 9 10 11 | # statsmodels GLM # McFadden 疑似 R² はロジスティック回帰の指標なので、目的変数は 2 値にする。 # 上の y(出生数・連続値)のままだと対数尤度が nan になり R² も nan になる。 import numpy as np aging = df['A1303'] / df['A1101'] * 100 y_bin = (aging >= aging.median()).astype(int).values # 高齢化率が中央値以上か X_bin = sm.add_constant(np.log(df[['A1101']].astype(float))) res_full = sm.GLM(y_bin, X_bin, family=sm.families.Binomial()).fit() res_null = sm.GLM(y_bin, np.ones((len(y_bin), 1)), family=sm.families.Binomial()).fit() pseudo_r2 = 1 - res_full.llf / res_null.llf print(f"McFadden R² = {pseudo_r2:.3f}") |
y のままだと対数尤度が nan になり指標が計算できない点に注意。決定係数 R² がデータサイエンスの体系の中でどこに位置するかを、 3つの異なる視点で可視化します。 同じ情報でも見方を変えると気付きが変わります。
🌐 統計・データサイエンス › 関連・回帰 › 回帰 › R²
中心に 決定係数 R² を置き、 そこから 残差・分散・最小二乗法・重回帰・平均・標準偏差 など 計 9 個の用語へ、 前提・兄弟・発展形といった関係を矢印で結んでいます。 線がどちら向きかを見れば、 先に読むべき用語とあとから読む用語が分かります。 ノードはドラッグで動かせ、 ホイールでズーム、 クリックでその用語のページへ遷移します。
大きな円が小さな円を包含する Circle Packing 図。 「決定係数 R²」は緑色でハイライト。
長方形を入れ子に分割した Treemap 図。 各分野の規模感を面積で比較。 「決定係数 R²」は緑色でハイライト。
| マップ | 分かること | こんな時に見る |
|---|---|---|
| 🔗 関係マップ | 手法間の横の関係(前提→発展→応用) | 「次に何を学べばよい?」 学習順序の判断 |
| ⭕ 包含マップ | 分類体系の入れ子構造(上位⊃下位) | 「この手法はどんなジャンルに属する?」 |
| 🌳 ツリーマップ | 分野の規模比較(面積=ボリューム) | 「データサイエンス全体の俯瞰像」 |
💡 ジャストインタイム学習のヒント:3つの視点を行き来することで、 概念を多角的に理解できます。 包含マップやツリーマップはズーム/ドリルダウンで大分類から細部まで探索できます。
本セクションは「決定係数」を 47都道府県データ(SSDSE-B-2026)で具体的に確認するための追加教材です。 例として総人口で出生数を説明したときの R²を扱います。
🍰 まずはやさしく
データの並び方を要約するメジャーのようなものです。
予測の線にデータがどれだけ集まっているかを見るために使います。
部活の練習量と試合結果の関係をグラフにしたときなどを想像してください。
ここでは決定係数を直感的に理解するための例を読みます。
決定係数を 47都道府県データで直感的に捉えるには、 まず「総人口で出生数を説明したときの R²」を思い浮かべます。 東京都・大阪府・神奈川県のように総人口が大きい都道府県ほど、 出生数や就業者数も大きくなる傾向があり、 こうしたデータの「形」を 決定係数 は要約します。
たとえば 47都道府県を散布図にすると、 右肩上がりの帯状にデータが並びます。 この「帯の傾き」「帯のばらつき」「帯から外れる外れ値」を表現する道具が、 ここで扱う 決定係数 だとイメージしてください。
R² = 0 〜 1 の数直線でイメージ: R²=0 はモデルが y の平均 ȳ と同じ予測しかできない状態 (= 説明できる分散がゼロ)、 R²=1 は全データ点が回帰直線上に乗る理想状態。 SSDSE-B-2026 の総人口 → 出生数回帰では R² ≈ 0.99 程度になり、 「総人口で出生数の分散の 99% を説明できる」と読み替える。 残り 1% は出生率・年齢構成といった人口規模以外の要因にあたる。
R² が低いときの直感: 散布図で「右肩上がりの線が引けない」「外れ値が多数ある」「データ点が雲のように散らばっている」状態。 例えば SSDSE-B の総人口で出生数を説明すれば R² は高いが、 総人口で粗出生率 (= A4101/A1101) を説明すれば R² は 0.03 前後まで急落する。 「分母で割った率指標」は人口規模の影響が消えるため、 R² がガクッと下がるのは正常で、 「説明する変数を間違えていないか」を見直す合図になる。
🍰 まずはやさしく
予測の良さを数字で表す計算式です。
ばらつきのうち何パーセントを説明できるか出すために使います。
買い物で使った金額から商品の数を予想する計算などに似ています。
ここでは決定係数を求めるための数式について読みます。
決定係数の中心的な数式は次のとおりです( 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 で再現します。 まず一行で読み込めるよう、 引数を直書きしたシンプル版を示します:
1 2 | # 最小コード(直書き, SSDSE は cp932・2 段ヘッダ) df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1]) |
続いて、 列名はリポジトリ準拠(A1101 総人口、 A1102 日本人人口、 A4101 出生数、 等)の本番コードです。
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 | import pandas as pd import numpy as np # SSDSE は 2 段ヘッダ(1段目=コード, 2段目=日本語名)。 skiprows=[1] でコード名を採用 df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1]) # 2023 年の 47都道府県スナップショット sub = df[df['SSDSE-B-2026'] == 2023].copy() x = sub['A1101'].astype(float) # 総人口 y = sub['A4101'].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}') # 傾き ≈ 0.0061 print(f'beta0 = {beta0:,.4f}') # 切片 ≈ -676.9 print(f'相関係数 = {x.corr(y):.4f}') # ≈ 0.9954 # 残差・決定係数も計算 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² は「全変動のうち、 モデルが説明できた割合」を示す。 ここでは 3 つの概念図で「回帰の幾何」「残差の意味」「過学習との関係」を整理する。
R² は数式 1 行で書ける一方、 「変数を増やすほど上がる」「外れ値で歪む」「非線形では負になる」など細かい落とし穴が多い。 ここでは SSDSE-B-2026 都道府県データを使い、 R² の挙動を 4 つの角度から確認する。
このコードでやること: SSDSE-B-2026 の「総人口(A1101)」を説明変数、「出生数(A4101)」を目的変数として、 SS_tot / SS_res から R² を手計算し、 sklearn.metrics.r2_score と一致するかを検証する。
📥 入力: SSDSE-B-2026.csv(47都道府県、 数値カラム A1101, A4101)。 df.head() は「北海道 5092000 24430」のような行が並ぶ。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | import pandas as pd from sklearn.metrics import r2_score from sklearn.linear_model import LinearRegression df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1]) df = df[df['SSDSE-B-2026'] == 2023] df = df.rename(columns={'A1101':'population', 'A4101':'births'}) X = df[['population']].values y = df['births'].values model = LinearRegression().fit(X, y) y_hat = model.predict(X) ss_res = ((y - y_hat)**2).sum() ss_tot = ((y - y.mean())**2).sum() r2_manual = 1 - ss_res/ss_tot print(f'手計算 R² = {r2_manual:.4f}') print(f'sklearn r2_score = {r2_score(y, y_hat):.4f}') print(f'model.score = {model.score(X, y):.4f}') |
📤 実行例:
💬 結果の読み方: 3 つの計算が完全一致 → 公式の理解が正しい。 R²=0.9909 は「出生数のばらつきの 99.09% を総人口だけで説明できた」ことを意味する。 ただし、 後述の通り外れ値の影響も点検すべき。
このコードでやること: SSDSE-B-2026 の説明変数に「65歳以上人口」「年平均気温」を順に追加し、 R² が単調増加することを示す。 これが調整済み R² が必要な理由。
📥 入力: 同じ df。 説明変数を 1 → 2 → 3 個に増やしていく。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | # ── この抜粋で使うデータを用意します(英字の項目コードで読み込み)── import pandas as pd df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', header=0) df = df[df['Code'].astype(str).str.match(r'^R\d{5}$', na=False)].copy() for _c in df.columns[3:]: df[_c] = pd.to_numeric(df[_c], errors='coerce') df = df[df['SSDSE-B-2026'] == df['SSDSE-B-2026'].max()] from sklearn.linear_model import LinearRegression # A1101=総人口, A1303=65歳以上人口, B4101=年平均気温 cols_sets = [ ['A1101'], ['A1101', 'A1303'], ['A1101', 'A1303', 'B4101'] ] for cols in cols_sets: X = df[cols].astype(float).values r2 = LinearRegression().fit(X, df['A4101']).score(X, df['A4101']) n, k = len(df), len(cols) r2_adj = 1 - (1-r2)*(n-1)/(n-k-1) print(f'説明変数 {k} 個: R²={r2:.4f} Adjusted R²={r2_adj:.4f}') |
📤 実行例:
💬 結果の読み方: R² は 0.9909 → 0.9930 → 0.9930 と減ることはない(3 個目は 4 桁目まで同じで、 実質的に増えていない)。 一方 Adjusted R² は 0.9907 → 0.9927 → 0.9925 と下がる——3 個目の変数が自由度を 1 消費するだけで新しい情報を持たなかった、 ということ。 無関係な変数を足せば Adjusted R² は下がり「無駄な変数」と判定できる(→ 過学習の節)。 これがモデル比較に Adjusted R² を使う理由。
このコードでやること: 東京を含む 47 都道府県と、 東京を除いた 46 都道府県で R² を比較し、 外れ値が R² を持ち上げている事実を視覚化する。
📥 入力: 同じ df。 「都道府県」または「Prefecture」カラムでフィルタ。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | # ── この抜粋で使うデータを用意します(英字の項目コードで読み込み)── import pandas as pd df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', header=0) df = df[df['Code'].astype(str).str.match(r'^R\d{5}$', na=False)].copy() for _c in df.columns[3:]: df[_c] = pd.to_numeric(df[_c], errors='coerce') df = df[df['SSDSE-B-2026'] == df['SSDSE-B-2026'].max()] from sklearn.linear_model import LinearRegression df_no_tokyo = df[~df['Prefecture'].str.contains('東京', na=False)] for label, d in [('全47都道府県', df), ('東京除く46', df_no_tokyo)]: X = d[['A1101']].astype(float).values r2 = LinearRegression().fit(X, d['A4101']).score(X, d['A4101']) print(f'{label}: R²={r2:.4f} n={len(d)}') |
📤 実行例:
💬 結果の読み方: 東京を除くと R² は 0.9909 → 0.9855 とわずかに下がる。 東京は規模が大きいが回帰直線にほぼ乗るため、 単独で R² を大きく歪めてはいない。 とはいえ高い R² を見たら必ず外れ値の影響を点検する習慣を。
このコードでやること: SSDSE-B-2026 の「年平均気温」と「消費支出」のように線形関係が弱い組合せに線形回帰を当てはめ、 分布外予測では R² が負になりうることを確認する。
📥 入力: 同じ df。 説明変数 B4101(年平均気温)、 目的変数 L3221(消費支出)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | import numpy as np from sklearn.linear_model import LinearRegression from sklearn.metrics import r2_score # 年平均気温(B4101) から 消費支出(L3221) を線形回帰 — 関係は弱い X = df[['B4101']].astype(float).values y = df['L3221'].astype(float).values r2_in = r2_score(y, LinearRegression().fit(X, y).predict(X)) # 寒い県で学習し、暖かい県で予測(分布外)→ R² は負になりうる d = df.sort_values('B4101') tr, te = d.iloc[:23], d.iloc[23:] m = LinearRegression().fit(tr[['B4101']].astype(float), tr['L3221'].astype(float)) r2_out = m.score(te[['B4101']].astype(float), te['L3221'].astype(float)) print(f'当てはめ R² (全データ) = {r2_in:.4f}') print(f'分布外 R² (寒→暖 予測) = {r2_out:.4f}') |
📤 実行例:
💬 結果の読み方: 同じデータへの当てはめでは R²=0.05 と低いだけだが、 分布外(寒い県で学習→暖かい県で予測)では R²=-0.51 と負になる。 R² が負=「平均で予測したほうがマシ」。 R² の符号も常に確認する。
5 点の小サンプル (y, ŷ) から SSE / SST / R² を Step 1〜5 で計算し、 直後の Python コードが同じ値を返すか確認する。
1 2 3 4 5 6 7 | import numpy as np y = np.array([3, 7, 12, 9, 4]) yhat = np.array([3.8, 6.6, 12.3, 8.5, 4.8]) sst = ((y - y.mean())**2).sum() sse = ((y - yhat)**2).sum() r2 = 1 - sse/sst print(f"SST: {sst}, SSE: {sse:.2f}, R²: {r2:.3f}") |
💬 手計算 (Step 4) の R² ≈ 0.967 と Python 出力 (R² = 0.967) が完全一致。 SST=54.0 / SSE=1.78 も Step 2・3 と同じ値。
「決定係数 (R²)」は単独で完結する手法ではなく、 隣接領域と連携することで真価を発揮する。
SSDSE-B-2026 の総人口 → 出生数の単回帰で R² ≈ 0.99、 自由度調整済 R² も同等、 説明変数の追加 (65歳以上人口・年平均気温) で 0.995 まで上昇するが交差検証 R² で過学習を確認する流れが実用的。
「決定係数 R²」は回帰モデルの当てはまりを示す代表指標。 SSDSE-B-2026 (47 都道府県) で総人口 → 出生数を回帰すると R² ≈ 0.99 となり、 「人口で出生数の 99% が説明できる」という解釈に対応する。 ただし R² は説明変数を増やすと単調に上昇するため、 自由度調整済 R² や交差検証 R² と併用する必要がある。
これらは「単純な R² の罠」を回避するための補正指標群である。
R² の補足ポイント:
本ページの主目的は「SSDSE-B-2026 の県別 総人口・出生数・年齢別人口を題材に、 R² の数式・実値計算・落とし穴を一貫させる」ことである。 同じ R² でも、 単回帰と重回帰、 線形と非線形で意味が異なる点を強調する。
後段では TSS = ESS + RSS の分解を 47 都道府県の生データに適用し、 numpy と sklearn の r2_score が一致することを確認する流れになる。
R² は「モデルがデータの散らばりを何割説明できたか」を 0〜1 で示します。 SSDSE-B-2026 の都道府県の総人口で出生数を予測すると R² ≈ 0.99、 つまり 99% の変動を人口だけで説明できます。 残り 1% は出生率の地域差など他要因が担っています。
「説明する」を分解する: 縦軸 y (出生数) を全 47 県でプロットすると、 平均線 ȳ の周りにばらつきが見えます。 この総ばらつき (TSS = Σ(y - ȳ)²) を、 直線 ŷ = β₀ + β₁ x で説明できた部分 (ESS = Σ(ŷ - ȳ)²) と、 残った部分 (RSS = Σ(y - ŷ)²) に分解し、 R² = ESS/TSS = 1 - RSS/TSS と定義します。 R² = 0.99 は「平均からの上下動 100% のうち、 99% を回帰直線が再現した」を意味します。
身近な比喩: 体重 (y) を身長 (x) だけで予測しようとすると R² ≈ 0.6 程度ですが、 年齢・性別・筋肉量を加えると R² ≈ 0.85 に上昇します。 「身長だけで体重の 60% を読める」、 「他要素を加えれば 85% 読める」と直感できれば R² は理解できたも同然です。
注意: R² = 1 は完全予測ですが、 過学習の可能性も高いです。 R² = 0 はモデルが平均線と同等で予測価値ゼロ、 R² < 0 は予測が平均線より 悪い 異常事態を意味します(OOS 検証で見られる)。 また R² は単調増加性質を持つため、 説明変数を増やすと必ず上昇 — そこで 調整済み R² が登場します。
決定係数 $R^2$ は、 観測値 $y_i$ を予測値 $\hat{y}_i$ で近似したときの「説明できた変動の割合」として定義される:
$$ R^2 = 1 - \frac{\mathrm{RSS}}{\mathrm{TSS}} = 1 - \frac{\sum_{i=1}^n (y_i - \hat{y}_i)^2}{\sum_{i=1}^n (y_i - \bar{y})^2} $$
ここで TSS (Total Sum of Squares) は平均 $\bar{y}$ からの総変動、 RSS (Residual Sum of Squares) はモデル誤差の二乗和。 単回帰では同等の表現として $R^2 = \mathrm{ESS}/\mathrm{TSS}$ (ESS = Explained Sum of Squares = $\sum(\hat{y}_i - \bar{y})^2$) もよく使われ、 $\mathrm{TSS} = \mathrm{ESS} + \mathrm{RSS}$ の関係が成り立つ。 単回帰の場合は $R^2 = r_{xy}^2$ (Pearson 相関係数の二乗) でもあり、 ピアソン相関と密接に結びついている。
上式の各記号を SSDSE-B-2026 (総人口 → 出生数の単回帰) に当てはめて翻訳する。
分子の RSS は「予測モデルの誤差」、 分母の TSS は「予測しない (平均値だけ使う) ときの誤差」。 R² はこの比を 1 から引いた値で、 「予測モデルを使うことで残差をどれだけ減らせたか」のスコア。 $R^2 < 0$ なら「平均値より悪い予測」を意味し、 OOS (out-of-sample) 評価では時々発生する。
R² (決定係数) を SSDSE-B-2026 の「総人口 (A1101) → 出生数 (A4101)」の単回帰で計算する。 全 47 都道府県データを使い、 R² = 1 - SS_res/SS_tot を手計算と Python の両方で求めて一致を確認する。
使用データ: SSDSE-B-2026 の x = A1101 (総人口, 人), y = A4101 (出生数, 人)。 単回帰 y = a + b·x をフィットして R² を算出。
| Step | 操作 | 値の確認 |
|---|---|---|
| 1 | y の平均 ȳ を計算 | ȳ ≈ 15,474 人 |
| 2 | SS_tot = Σ(y_i - ȳ)² | ≈ 1.35×10¹⁰ |
| 3 | 単回帰の予測値 ŷ_i を計算 | b ≈ 0.0061, a ≈ -677 |
| 4 | SS_res = Σ(y_i - ŷ_i)² | ≈ 1.24×10⁸ |
| 5 | R² = 1 - SS_res/SS_tot | 1 - 0.0091 ≈ 0.9909 |
R² ≈ 0.9909 = 「出生数の分散のうち 99% は人口で説明できる」という強い当てはまり。 残り 1% が出生率の地域差などの説明変数で補強できる余地。
上記 Step 1-5 の R² 計算を sklearn と statsmodels で再現する。 SSDSE-B-2026 を読み込み、 単回帰モデルをフィットして R² を確認する。
🎯 このコードでやること: sklearn の LinearRegression と statsmodels の OLS で R² を計算し、 手計算値と一致することを確認する。 さらに自由度調整済 R² も併記する。
📥 入力データ: SSDSE-B-2026 の A1101 (総人口) と A4101 (出生数) 列、 47 都道府県分。
1 2 3 4 5 6 7 8 9 10 | import pandas as pd from sklearn.linear_model import LinearRegression from sklearn.metrics import r2_score df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1]) df = df[df['SSDSE-B-2026'] == 2023] X, y = df[['A1101']], df['A4101'] # 総人口 → 出生数 model = LinearRegression().fit(X, y) r2 = r2_score(y, model.predict(X)) adj_r2 = 1 - (1-r2) * (47-1) / (47-1-1) print(f'R²={r2:.4f} Adjusted R²={adj_r2:.4f} b={model.coef_[0]:.4f} a={model.intercept_:.0f}') |
📤 実行結果:
💬 結果の読み方: R² = 0.9909 は手計算の 0.9909 と一致。 「総人口が 1 万人増えると出生数が約 61 人増える」傾き (b=0.0061) で、 47 都道府県の分散の 99% を説明。 Adjusted R² との差はわずかで、 単回帰なので過適合の懸念は小さい。
決定係数 R² を中心に、 関連する概念・上位カテゴリ・応用領域を放射状に配置した。
「r squared」を中心に、 隣接する手法・概念との関係を以下に整理する。 単独の手法理解にとどまらず、 分析パイプライン全体での位置付けを把握することで、 適切な前段・後段・代替手法を選べる。
| 隣接手法 | 関係 | 接続のポイント |
|---|---|---|
| 回帰分析全般 | 上位 / 一般化 | R² は最小二乗法で当てはまった回帰モデルの当てはまり度を測る指標 |
| 自由度調整済み R² | 下位 / 特殊化 | 重回帰で変数追加に対する penalty を加えた R²。 変数選択時はこちらを参照 |
| RMSE / MAE | 並列 / 対比 | 予測誤差を絶対量で測る。 R² は無次元、 RMSE は単位付き — 併記推奨 |
| 残差プロット作成 | 前段 (検証準備) | R² だけで判断せず、 残差の独立性・等分散性・正規性を視覚確認 |
| 交差検証 R² | 後段 (汎化評価) | 訓練時 R² は楽観的。 k-fold CV で out-of-sample R² を測る |
| AIC / BIC | 評価 / 比較 | モデル選択時は R² 単独でなく情報量規準で penalty 込み比較 |
決定係数 R² は「モデルの説明力」を一発で示す便利な指標だが、 単独で使うのは危険。 SSDSE-B-2026 で R² が 0.85 でも、 残差プロット・自由度調整済 R²・Cook 距離による外れ値感度・k-fold CV による外推性能 を併せて評価して、 はじめて信頼できる説明力と判定できる。
「r squared」を含む手法選択は、 データの性質・分析目的・運用制約の 3 軸で決まる。 以下に典型シナリオと対応する手法の組合せを示す。
| シナリオ | 重視する観点 | 候補手法 |
|---|---|---|
| 変数が多い重回帰 | 説明変数増加の補正が必要 | 自由度調整済み R² (adjusted R²) を併用 |
| ロジスティック回帰 | 通常 R² が定義されない | McFadden 擬似 R² や Nagelkerke R² を使用 |
| 新規データへの汎化性能を見たい | 訓練時 R² は楽観的 | 交差検証 R² (k=5 や k=10) で評価 |
| 予測誤差の絶対量を知りたい | R² だけでは尺度感が出ない | RMSE / MAE を併記 |
| モデル比較・変数選択 | R² は単調増加のため不公平 | AIC / BIC で penalize |
| 外れ値が結果を支配する | R² が見かけ上高くなる | ロバスト回帰 + 残差プロット併用 |
R² は「目的変数の分散のうち、 モデルが説明できる割合」と定義されますが、 この分子・分母を実データで分解すると、 R² がなぜ 0〜1 を取るのか、 マイナスになり得るのか、 なぜ訓練データでは「変数を増やすと必ず R² が上がる」のかが明快になります。 SSDSE-B-2026 を使って実演します。
TSS = ESS + RSS
R² = ESS / TSS = 1 − RSS / TSS
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | import pandas as pd, numpy as np from sklearn.linear_model import LinearRegression df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1]) df = df[df['SSDSE-B-2026'] == 2023] X = df[['A1303', 'A1301']].astype(float) # 65歳以上人口, 15歳未満人口 y = df['A1101'].astype(float) # 総人口 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.995(総人口は年齢別人口でほぼ完全予測される) |
1 2 3 4 5 6 7 8 9 10 11 12 | from sklearn.linear_model import LinearRegression base_cols = ['A1301'] # 15歳未満人口 add_cols = ['A1303', 'A4101', 'A4200'] # 65歳以上人口, 出生数, 死亡数 for i in range(len(add_cols) + 1): cols = base_cols + add_cols[:i] Xi = df[cols].astype(float) yi = df['A1101'].astype(float) # 総人口 r2 = LinearRegression().fit(Xi, yi).score(Xi, yi) print(f'説明変数 {len(cols)} 個: R² = {r2:.6f}') # R² は単調非減少。 無関係な変数を入れても上がるのが落とし穴。 |
R² < 0 は、 数式上は「モデルが平均値で予測するより悪い」を意味します。 ホールドアウト検証や、 切片無しモデル、 過学習で大きく外れた検証データで起こりがちです。
1 2 3 4 5 6 | 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² の核心です。
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 | import pandas as pd import statsmodels.api as sm import numpy as np df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1]) df = df[df['SSDSE-B-2026'] == 2023] df = df.dropna(subset=['A1101', 'A1303']) # 二値ターゲット:「高齢化率(A1303/A1101)が 30% 以上か」 df['high_aging'] = ((df['A1303'] / df['A1101']) > 0.30).astype(int) X = sm.add_constant(df[['A1101']].astype(float)) 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² の標本分布は複雑なので、 信頼区間はブートストラップで求めるのが実務的です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | from sklearn.linear_model import LinearRegression import numpy as np df2 = df.dropna(subset=['A1101', 'A1303']).copy() X2 = df2[['A1303']].astype(float).values y2 = df2['A1101'].astype(float).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 を素材にした擬似実験で確認します。 教科書の数式だけでは見えない「現場感覚」を養うのが目的です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | import pandas as pd, numpy as np from sklearn.linear_model import LinearRegression df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1]) df = df[df['SSDSE-B-2026'] == 2023] df = df.dropna(subset=['A1101', 'A1303']) X = df[['A1303']].astype(float).values y_clean = df['A1101'].astype(float).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 に |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | 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='cp932', skiprows=[1]) df = df[df['SSDSE-B-2026'] == 2023] df = df.dropna(subset=['A1101', 'A1303']) X = df[['A1303']].astype(float).copy().reset_index(drop=True) y = df['A1101'].astype(float).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² は途中から低下 = 過学習の証拠 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | import pandas as pd, numpy as np from sklearn.linear_model import LinearRegression df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1]) df = df[df['SSDSE-B-2026'] == 2023] df = df.dropna(subset=['A1101', 'A1303']) 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[['A1303']].astype(float).iloc[idx], df['A1101'].astype(float).iloc[idx]) r2s.append(m.score(df[['A1303']].astype(float).iloc[idx], df['A1101'].astype(float).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 併記 | 推奨 | 単位付きの実用解釈 |
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 33 34 35 | 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='cp932', skiprows=[1]) df = df[df['SSDSE-B-2026'] == 2023] df = df.dropna(subset=['A1101', 'A1303', 'A1301']) X = df[['A1303', 'A1301']].astype(float) y = df['A1101'].astype(float) 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」が棄却されやすくなります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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="cp932", skiprows=[1]) df = df[df['SSDSE-B-2026'] == 2023] df = df.dropna(subset=["A1101", "A1303", "A1301"]) model = smf.ols("A1101 ~ A1303 + A1301", 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² + 強い理論的根拠 + 内生性対策があってはじめて因果に近づきます。
このページは既に「分散の説明割合」「変数を足すと必ず上がる」「因果ではない」「テストで負になる」を厚く扱ってきました。 ここではそれらと重ならない独自の角度として、 「R² は残差 SD が元の SD の何倍かを言い換えた量」という RMSE との橋渡しと、 「同じ関係でも X の広がりを狭めると R² は崩れる(範囲制限)」という見落とされがちな落とし穴を、 SSDSE-B-2026 の 2023 年・47 都道府県の実測値で掘り下げます。 題材は x=総人口(A1101)で y=大学学生数(E6302)を単回帰したケースです。
R² は $1-SS_\text{res}/SS_\text{tot}$ ですが、 これを標準偏差で読み替えると急に体感しやすくなります。 残差の標準偏差 ÷ y の標準偏差 = √(1−R²) という関係が常に成り立つからです(= $\text{RMSE}/\sigma_y=\sqrt{1-R^2}$)。 上の単回帰の実測値がこれです。
| 量(2023・47 都道府県) | 値 |
|---|---|
| R²(=相関 r=0.889 の2乗) | 0.789 |
| y(大学学生数)の標準偏差 σ_y | 105,940 人 |
| モデルの残差 SD(=RMSE) | 48,605 人 |
| RMSE ÷ σ_y(=√(1−R²)) | 0.459 |
$\sqrt{1-0.789}=\sqrt{0.211}=0.459$ で、 実測の RMSE÷σ_y(48,605÷105,940=0.459)とぴったり一致します。 つまり 「R²=0.79 の当てはまり」とは「予測の誤差の散らばりが、 何のモデルも無いとき(平均だけで予測)の 46% まで縮んだ」ということ。 R² を「50%の分散を説明」と割合で聞くと立派に思えても、 誤差の物差し(SD)では √ を取るので、 R²=0.5 でも残差 SD はまだ元の 71%(√0.5)しか縮みません。 R² の見かけほど誤差は減っていない、 という感覚は RMSE と併読すると崩れずに済みます。
最大の誤解は 「R² は 2 変数の関係の強さそのものを表す固有の数値だ」という思い込みです。 実は R² は 手元のサンプルにおける X の広がり(分散)に強く依存します。 同じ「総人口→大学学生数」の関係でも、 人口 100 万〜300 万人の県だけに絞ると、 傾き(1 人あたりの学生数)はほぼ変わらないのに R² だけが崩れます。
| サンプル | n | 回帰の傾き | X(人口)のSD | R² |
|---|---|---|---|---|
| 全 47 都道府県 | 47 | 0.0340 | 2,767,630 | 0.789 |
| 人口 100〜300 万人に限定 | 27 | 0.0289 | 516,227 | 0.331 |
傾きは 0.0340→0.0289 とほぼ横ばい(関係の“強さ”=1人あたり学生数は変わっていない)なのに、 R² は 0.789 から 0.331 へ半分以下に崩落しました。 犯人は X の SD が 276.8 万→51.6 万へ大幅に縮んだこと。 R²=説明された分散÷全分散なので、 X の広がりが小さいと分子(傾き²×Xの分散)が縮み、 同じ残差ばらつきに対して R² が下がるのです。 これが計量経済学で言う 範囲制限(restriction of range)。 「うちのデータでは R² が低いから関係が弱い」と結論する前に、 そもそも X を十分に広い範囲でサンプリングできているかを疑ってください。 逆に、 東京都のような巨大な外れ値を1点含めるだけで X 分散が跳ね上がり R² が水増しされることもあり、 R² は「関係の強さ」ではなく「このサンプルでどれだけ Y のばらつきを説明できたか」に過ぎません。