このページの分析を自分で再現するには、以下の手順でデータを準備してください。コードの編集は不要です。
data/raw/ フォルダに入れます。html/figures/ に自動保存されます。
日本の合計特殊出生率(TFR: Total Fertility Rate)は1970年代から長期的な低下傾向を続けており、人口置換水準(2.07)を大きく下回っている。SSDSE-B データに基づく47都道府県の2012〜2023年の分析では、全国平均TFRが 2012年: 1.460 → 2023年: 1.293 へと12年間でさらに低下したことが確認された。
まず「合計特殊出生率の決定要因:パネルデータによる長期分析」を統計的にとらえることが有効だと考えられる。 その理由は感覚や経験則だけでは、複雑な社会要因の中で「何が本当に効いているか」を見極めにくいからである。 本研究では公開データと統計手法を組み合わせ、この問いに定量的な答えを出すことを目指す。
パネル固定効果 Hausman検定 相関分析 SSDSE-B
| 項目 | 内容 |
|---|---|
| データソース | SSDSE-B(都道府県別社会・人口統計体系)2026年版 |
| 対象 | 47都道府県(地域コード R#####) |
| 期間 | 2012〜2023年(12年間) |
| 総観測数 | 47都道府県 × 12年 = 564 観測 |
| 変数名 | 定義式 | 役割 | 想定効果 |
|---|---|---|---|
| TFR | 合計特殊出生率(原値) | 目的変数 | — |
| 婚姻率 | 婚姻件数 ÷ 総人口 × 1000 [‰] | 説明変数 | 正(婚外子が少ない日本では婚姻がTFRを規定) |
| 保育所密度 | 保育所等数 ÷ 総人口 × 1000 [千人当たり] | 説明変数 | 正(子育て支援が出生率を押し上げ) |
| 高齢化率 | 65歳以上人口 ÷ 総人口 × 100 [%] | 説明変数 | —(コントロール変数) |
| 消費支出_log | log(消費支出・二人以上世帯) [所得代理] | 説明変数 | 負(所得↑ → 機会費用↑ → 出生率↓) |
| 変数 | 平均 | 標準偏差 | 最小 | 最大 |
|---|---|---|---|---|
| TFR | 1.450 | 0.153 | 0.990 | 1.960 |
| 婚姻率(‰) | 4.283 | 0.667 | 2.519 | 6.748 |
| 保育所密度(千人当たり) | 0.243 | 0.073 | 0.102 | 0.451 |
| 高齢化率(%) | 29.24 | 3.48 | 17.72 | 39.06 |
| 消費支出_log | 12.565 | 0.084 | 12.258 | 12.780 |
パネルデータ(縦断データ)は同一の観測単位(ここでは都道府県)を複数時点にわたって追跡したデータ。クロスセクション(ある時点の47都道府県)と時系列(ある都道府県の12年推移)の両方の情報を含む。
パネルデータの利点は「観測不能な個体固有の不均一性」(例:文化・気候・産業構造)をコントロールできること。OLSではこれらが交絡して推定が偏るが、固定効果モデルで除去できる。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | import os import numpy as np import pandas as pd import matplotlib matplotlib.use('Agg') import matplotlib.pyplot as plt import matplotlib.patches as mpatches import statsmodels.api as sm from scipy import stats plt.rcParams['font.family'] = 'Hiragino Sans' plt.rcParams['axes.unicode_minus'] = False plt.rcParams['figure.dpi'] = 150 FIG_DIR = 'html/figures' DATA_B = 'data/raw/SSDSE-B-2026.csv' os.makedirs(FIG_DIR, exist_ok=True) df_b = pd.read_csv(DATA_B, encoding='cp932', header=1) df_b = df_b[df_b['地域コード'].str.match(r'^R\d{5}', na=False)].copy() df_b['年度'] = df_b['年度'].astype(int) |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。import pandas as pd など — 必要なライブラリをまとめて呼び出します。as pd は短い別名(alias)。matplotlib.use('Agg') — グラフを画面表示せずファイルに保存するためのおまじない。plt.rcParams['font.family'] — グラフの日本語表示用フォント指定(Macは Hiragino Sans、Windowsなら Yu Gothic 等)。os.makedirs('html/figures', exist_ok=True) — 図の保存先フォルダを作る(既にあってもOK)。pd.read_csv(...) でCSVを読み込みます。encoding='cp932' は日本語Windows由来の文字コード、header=1 は「2行目を列名として使う」。df['地域コード'].str.match(r'^R\d{5}', ...) — 正規表現で「R+数字5桁」の行(47都道府県)だけTrueにし、真偽値で行をフィルタ。.astype(int) — 列を整数に変換(年度などを数値比較するため)。f"...{x}..." はf-string。文字列の中に {変数} と書くだけで埋め込めて、{x:.2f} のように書式も指定できます。22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | # 分析用変数の作成 df_b['TFR'] = df_b['合計特殊出生率'] df_b['総人口_千'] = df_b['総人口'] / 1000 # 婚姻率 = 婚姻件数 / 総人口 × 1000 (‰) df_b['婚姻率'] = df_b['婚姻件数'] / df_b['総人口'] * 1000 # 保育所密度 = 保育所等数 / 総人口 × 1000 df_b['保育所密度'] = df_b['保育所等数'] / df_b['総人口'] * 1000 # 高齢化率 = 65歳以上人口 / 総人口 × 100 (%) df_b['高齢化率'] = df_b['65歳以上人口'] / df_b['総人口'] * 100 # 消費支出対数変換 df_b['消費支出_log'] = np.log(df_b['消費支出(二人以上の世帯)']) print("=== データ基本統計 ===") print(f"都道府県数: {df_b['都道府県'].nunique()}") print(f"年度範囲: {df_b['年度'].min()}〜{df_b['年度'].max()}") print(f"総観測数: {len(df_b)}") print() print(df_b[['TFR','婚姻率','保育所密度','高齢化率','消費支出_log']].describe().round(4)) |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。.describe() — 件数・平均・標準偏差・四分位・最大/最小を一括計算。データの素性チェックに必須。df['A'] / df['B'] — pandasの列同士の四則演算は要素ごと(element-wise)。forループ不要なのが強み。40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 | # 地域マップ region_map = { '北海道': '北海道・東北', '青森県': '北海道・東北', '岩手県': '北海道・東北', '宮城県': '北海道・東北', '秋田県': '北海道・東北', '山形県': '北海道・東北', '福島県': '北海道・東北', '茨城県': '関東', '栃木県': '関東', '群馬県': '関東', '埼玉県': '関東', '千葉県': '関東', '東京都': '関東', '神奈川県': '関東', '新潟県': '中部', '富山県': '中部', '石川県': '中部', '福井県': '中部', '山梨県': '中部', '長野県': '中部', '岐阜県': '中部', '静岡県': '中部', '愛知県': '中部', '三重県': '近畿', '滋賀県': '近畿', '京都府': '近畿', '大阪府': '近畿', '兵庫県': '近畿', '奈良県': '近畿', '和歌山県': '近畿', '鳥取県': '中国・四国', '島根県': '中国・四国', '岡山県': '中国・四国', '広島県': '中国・四国', '山口県': '中国・四国', '徳島県': '中国・四国', '香川県': '中国・四国', '愛媛県': '中国・四国', '高知県': '中国・四国', '福岡県': '九州・沖縄', '佐賀県': '九州・沖縄', '長崎県': '九州・沖縄', '熊本県': '九州・沖縄', '大分県': '九州・沖縄', '宮崎県': '九州・沖縄', '鹿児島県': '九州・沖縄', '沖縄県': '九州・沖縄' } region_colors = { '北海道・東北': '#4e9af1', '関東': '#e05c5c', '中部': '#f0a500', '近畿': '#5cb85c', '中国・四国': '#9b59b6', '九州・沖縄': '#f39c12' } df_b['地域'] = df_b['都道府県'].map(region_map) |
=== データ基本統計 ===
都道府県数: 47
年度範囲: 2012〜2023
総観測数: 564
TFR 婚姻率 保育所密度 高齢化率 消費支出_log
count 564.0000 564.0000 564.0000 564.0000 564.0000
mean 1.4504 4.2834 0.2426 29.2397 12.5650
std 0.1530 0.6669 0.0730 3.4792 0.0837
min 0.9900 2.5186 0.1019 17.7179 12.2577
25% 1.3400 3.7621 0.1909 27.0331 12.5167
50% 1.4600 4.2985 0.2257 29.4720 12.5674
75% 1.5500 4.6819 0.2927 31.7564 12.6264
max 1.9600 6.7478 0.4514 39.0591 12.7801.map() は「1対1の置き換え」、.apply() は「関数を当てる」。辞書なら .map()、ロジックなら .apply()。62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 | print("\n=== HTML作成用 統計サマリー ===") print(f"TFR全国平均 (2023年): {df_b[df_b['年度']==2023]['TFR'].mean():.3f}") print(f"TFR全国平均 (2012年): {df_b[df_b['年度']==2012]['TFR'].mean():.3f}") print(f"TFR最高値 (2022年): {df_b[df_b['年度']==2022]['TFR'].max():.2f} ({df_b[df_b['年度']==2022].loc[df_b[df_b['年度']==2022]['TFR'].idxmax(), '都道府県']})") print(f"TFR最低値 (2022年): {df_b[df_b['年度']==2022]['TFR'].min():.2f} ({df_b[df_b['年度']==2022].loc[df_b[df_b['年度']==2022]['TFR'].idxmin(), '都道府県']})") print(f"婚姻率×TFR 相関係数 (2022): {df_2022['婚姻率'].corr(df_2022['TFR']):.3f}") print(f"高齢化率×TFR 相関係数 (全期間): {df_b['高齢化率'].corr(df_b['TFR']):.3f}") print(f"保育所密度×TFR 相関係数 (全期間): {df_b['保育所密度'].corr(df_b['TFR']):.3f}") print("\n=== 係数推定値 ===") for name, val, se, pv in zip(coef_names, coef_vals, coef_se, coef_pvals): sig = '***' if pv < 0.01 else ('**' if pv < 0.05 else ('*' if pv < 0.1 else '')) print(f" {name}: {val:+.5f} (SE={se:.5f}, p={pv:.4f}) {sig}") if use_panel and fe_results is not None: print(f"\nFEモデル R²_within: {fe_results.rsquared_within:.4f}") print("\n✓ 全4図の生成完了") print(f" {fig1_path}") print(f" {fig2_path}") print(f" {fig3_path}") print(f" {fig4_path}") |
=== HTML作成用 統計サマリー === TFR全国平均 (2023年): 1.293 TFR全国平均 (2012年): 1.460 TFR最高値 (2022年): 1.70 (沖縄県) TFR最低値 (2022年): 1.04 (東京都) 婚姻率×TFR 相関係数 (2022): -0.108 高齢化率×TFR 相関係数 (全期間): -0.028 保育所密度×TFR 相関係数 (全期間): 0.505 === 係数推定値 === 婚姻率: +0.19580 (SE=0.01303, p=0.0000) *** 保育所密度: +0.41028 (SE=0.08558, p=0.0000) *** 高齢化率: +0.01936 (SE=0.00336, p=0.0000) *** 消費支出_log: -0.08652 (SE=0.04078, p=0.0343) ** FEモデル R²_within: 0.6966 ✓ 全4図の生成完了 html/figures/2020_U2_fig1.png html/figures/2020_U2_fig2.png html/figures/2020_U2_fig3.png html/figures/2020_U2_fig4.png
df[col](1列)と df[[col1, col2]](複数列)でカッコの数が違います。リストを渡していると覚えるとミスを減らせます。47都道府県を6地域に分類し、各地域の平均TFR推移を可視化した。2020年(コロナ禍)以降に全地域で急激な低下が見られる。
| 変数ペア | 相関係数 | 解釈 |
|---|---|---|
| 婚姻率 × TFR(2022年) | -0.108 | 単純相関は弱い(都市規模効果が混在) |
| 高齢化率 × TFR(全期間) | -0.028 | 単純相関はほぼゼロ(固体効果で説明) |
| 保育所密度 × TFR(全期間) | +0.505 | 強い正の相関(保育充実 → 出生率↑) |
85 86 87 88 89 90 91 92 93 94 | print("\n図1: 地域別TFR推移を作成中...") fig, ax = plt.subplots(figsize=(10, 6)) for region in region_colors: prefs = [p for p, r in region_map.items() if r == region] region_data = df_b[df_b['都道府県'].isin(prefs)].groupby('年度')['TFR'].mean() ax.plot(region_data.index, region_data.values, color=region_colors[region], linewidth=2.2, marker='o', markersize=4, label=region) |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。df.groupby('列').apply(関数) — グループごとに関数を適用。時系列や地域別の集計でよく使います。fig, ax = plt.subplots(...) — 図全体(fig)と軸(ax)を作る定番。以降は ax.bar(...) 等で操作。.map() は「1対1の置き換え」、.apply() は「関数を当てる」。辞書なら .map()、ロジックなら .apply()。95 96 97 98 99 100 101 102 103 104 105 106 107 | # 人口置換水準 ax.axhline(2.07, color='red', linestyle='--', linewidth=1.5, alpha=0.7, label='人口置換水準 (2.07)') ax.set_xlabel('年度', fontsize=12) ax.set_ylabel('合計特殊出生率', fontsize=12) ax.set_title('地域別 合計特殊出生率の推移(2012〜2023年)', fontsize=14, fontweight='bold') ax.set_xlim(2012, 2023) ax.set_xticks(range(2012, 2024)) ax.tick_params(axis='x', rotation=45) ax.legend(loc='upper right', fontsize=10, framealpha=0.9) ax.set_ylim(1.0, 2.2) ax.grid(True, alpha=0.3, linestyle=':') ax.fill_between([2012, 2023], [1.0, 1.0], [2.07, 2.07], alpha=0.04, color='red') |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。ax.axhline / ax.axvline — 水平/垂直の点線。平均線や基準線として定番。ax.fill_between(...) — 2つの曲線で囲まれた領域を塗りつぶし。Lorenz曲線の格差面積などを可視化。[式 for x in リスト] はリスト内包表記。forループでappendする代わりに1行でリストを作れます。108 109 110 111 112 113 114 115 116 117 118 119 | # 全国平均 national_avg = df_b.groupby('年度')['TFR'].mean() ax.plot(national_avg.index, national_avg.values, color='black', linewidth=2.5, linestyle='--', marker='D', markersize=5, label='全国平均', zorder=5) ax.legend(loc='upper right', fontsize=9.5, framealpha=0.9) plt.tight_layout() fig1_path = os.path.join(FIG_DIR, '2020_U2_fig1.png') fig.savefig(fig1_path, dpi=150, bbox_inches='tight') plt.close(fig) print(f" → 保存: {fig1_path}") |
図1: 地域別TFR推移を作成中... → 保存: html/figures/2020_U2_fig1.png
df.groupby('列').apply(関数) — グループごとに関数を適用。時系列や地域別の集計でよく使います。fig.savefig(..., bbox_inches='tight') — 余白を自動で詰めて保存。plt.close() でメモリ解放。r, p = stats.pearsonr(...) — Pythonは複数戻り値を同時に受け取れる(タプルアンパック)。線形パネルモデルは linearmodels の PanelOLS を使用。標準誤差は都道府県クラスター標準誤差(clustered by entity)を採用。
| 変数 | 係数 | 標準誤差 | t値 | p値 | 有意性 | 解釈 |
|---|---|---|---|---|---|---|
| 婚姻率 | +0.1958 | 0.0130 | 15.03 | <0.001 | *** | 婚姻率1‰上昇 → TFR+0.196 |
| 保育所密度 | +0.4103 | 0.0856 | 4.79 | <0.001 | *** | 保育所密度1単位上昇 → TFR+0.410 |
| 高齢化率 | +0.0194 | 0.0034 | 5.76 | <0.001 | *** | 高齢化が進む地域でTFR上昇(逆説的) |
| 消費支出_log | −0.0865 | 0.0408 | −2.12 | 0.034 | ** | 所得上昇 → 機会費用増 → TFR低下 |
*** p<0.01, ** p<0.05 / クラスター標準誤差(都道府県クラスター)/ within R² = 0.697
固定効果推定(within 推定)は、各変数から個体(都道府県)の時間平均を引く「平均除去変換」によって実装される。これにより時間不変の固有効果 αᵢ が差し引かれ、時間変動(within variation)のみで係数を識別する。
121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 | print("図2: 婚姻率 vs TFR 散布図を作成中...") # 2022年データで代表 df_2022 = df_b[df_b['年度'] == 2022].copy() fig, ax = plt.subplots(figsize=(11, 8)) for region in region_colors: sub = df_2022[df_2022['地域'] == region] ax.scatter(sub['婚姻率'], sub['TFR'], color=region_colors[region], s=70, alpha=0.85, zorder=3, label=region) for _, row in sub.iterrows(): pref = row['都道府県'].replace('県', '').replace('都', '').replace('道', '').replace('府', '') ax.annotate(pref, xy=(row['婚姻率'], row['TFR']), xytext=(2, 2), textcoords='offset points', fontsize=7.5, color='#333333', zorder=4) |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。fig, ax = plt.subplots(...) — 図全体(fig)と軸(ax)を作る定番。以降は ax.bar(...) 等で操作。for _, row in df.iterrows() — DataFrameを1行ずつ取り出すループ。1点ずつ描画したいときに使用。[式 for x in リスト] はリスト内包表記。forループでappendする代わりに1行でリストを作れます。139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 | # 回帰線 x_reg = df_2022['婚姻率'].values y_reg = df_2022['TFR'].values mask = ~(np.isnan(x_reg) | np.isnan(y_reg)) slope, intercept, r_value, p_value, std_err = stats.linregress(x_reg[mask], y_reg[mask]) x_line = np.linspace(x_reg[mask].min(), x_reg[mask].max(), 100) ax.plot(x_line, slope * x_line + intercept, color='navy', linewidth=2, linestyle='--', alpha=0.7, label=f'回帰線 (r={r_value:.3f}, p={p_value:.3f})') ax.set_xlabel('婚姻率(‰)', fontsize=12) ax.set_ylabel('合計特殊出生率', fontsize=12) ax.set_title('婚姻率と合計特殊出生率の関係(2022年・47都道府県)', fontsize=13, fontweight='bold') ax.legend(fontsize=9.5, framealpha=0.9, loc='upper left') ax.grid(True, alpha=0.3, linestyle=':') plt.tight_layout() fig2_path = os.path.join(FIG_DIR, '2020_U2_fig2.png') fig.savefig(fig2_path, dpi=150, bbox_inches='tight') plt.close(fig) print(f" → 保存: {fig2_path}") |
図2: 婚姻率 vs TFR 散布図を作成中... → 保存: html/figures/2020_U2_fig2.png
stats.linregress(x, y) — 単回帰の傾き・切片・r値・p値・標準誤差を返します。使わない値は _ で受け取り。fig.savefig(..., bbox_inches='tight') — 余白を自動で詰めて保存。plt.close() でメモリ解放。r, p = stats.pearsonr(...) — Pythonは複数戻り値を同時に受け取れる(タプルアンパック)。パネル分析では「固定効果(FE)モデル」と「変量効果(RE)モデル」のどちらが適切かを統計的に判断する必要がある。Hausman検定はこの選択のための標準的な手法。
| 固定効果(FE)モデル | 変量効果(RE)モデル | |
|---|---|---|
| 個体効果 αᵢ の扱い | 推定すべきパラメータとして扱う | ランダムな誤差項として扱う |
| 仮定 | αᵢ と説明変数が相関してもよい | αᵢ と説明変数が無相関 |
| 推定量の一致性 | 常に一致推定量 | αᵢ と説明変数が独立なら一致 |
| 推定の効率性 | RE より非効率 | 仮定が成立すれば効率的 |
| 時間不変変数 | 推定不可 | 推定可能 |
| 検定統計量 χ² | 自由度 | p値 | 判定 |
|---|---|---|---|
| 4.675 | 4 | 0.322 | H₀ 棄却できず → RE モデルを採択 |
注: 分散行列の数値安定性の問題からピンチ逆行列(Moore-Penrose擬似逆行列)を使用
Hausman 検定の分散差行列 [Var(FE) − Var(RE)] は理論上は正半定値だが、有限標本では数値誤差により固有値が負になることがある。このときは擬似逆行列(pinv)を使う。
161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 | print("図3: FE係数プロットを作成中...") # 表示用変数名 var_display = { '婚姻率': '婚姻率\n(‰)', '保育所密度': '保育所密度\n(千人当たり)', '高齢化率': '高齢化率\n(%)', '消費支出_log': '消費支出\n(対数)' } fig, ax = plt.subplots(figsize=(9, 5.5)) colors_fe = [] for pv in coef_pvals: if pv < 0.01: colors_fe.append('#C62828') elif pv < 0.05: colors_fe.append('#E65100') elif pv < 0.1: colors_fe.append('#F9A825') else: colors_fe.append('#9E9E9E') y_pos = np.arange(len(coef_names)) display_names = [var_display.get(n, n) for n in coef_names] bars = ax.barh(y_pos, coef_vals, xerr=1.96*coef_se, color=colors_fe, alpha=0.85, height=0.55, error_kw=dict(ecolor='#333333', capsize=5, linewidth=1.5)) ax.axvline(0, color='black', linewidth=1.2, linestyle='-') ax.set_yticks(y_pos) ax.set_yticklabels(display_names, fontsize=11) ax.set_xlabel('推定係数(固定効果モデル)', fontsize=11) ax.set_title('パネル固定効果モデル:推定係数と95%信頼区間', fontsize=13, fontweight='bold') |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。fig, ax = plt.subplots(...) — 図全体(fig)と軸(ax)を作る定番。以降は ax.bar(...) 等で操作。ax.axhline / ax.axvline — 水平/垂直の点線。平均線や基準線として定番。r, p = stats.pearsonr(...) — Pythonは複数戻り値を同時に受け取れる(タプルアンパック)。196 197 198 199 200 201 202 203 | # 有意水準の凡例 legend_patches = [ mpatches.Patch(color='#C62828', label='p < 0.01'), mpatches.Patch(color='#E65100', label='p < 0.05'), mpatches.Patch(color='#F9A825', label='p < 0.10'), mpatches.Patch(color='#9E9E9E', label='非有意'), ] ax.legend(handles=legend_patches, loc='lower right', fontsize=9.5, framealpha=0.9) |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。x if cond else y は三項演算子。リスト内包表記と組み合わせると、forとifを1行で書けます。204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 | # 係数値と有意性マーク for i, (val, pv) in enumerate(zip(coef_vals, coef_pvals)): mark = '***' if pv < 0.01 else ('**' if pv < 0.05 else ('*' if pv < 0.1 else '')) x_text = val + (coef_ci_high[i] - val) * 0.15 + 0.001 ax.text(x_text, i, f'{val:+.4f}{mark}', va='center', fontsize=9, color='#333333') ax.grid(True, axis='x', alpha=0.3, linestyle=':') ax.set_xlim(min(coef_ci_low) - abs(min(coef_ci_low))*0.4, max(coef_ci_high) + abs(max(coef_ci_high))*0.5) plt.tight_layout() fig3_path = os.path.join(FIG_DIR, '2020_U2_fig3.png') fig.savefig(fig3_path, dpi=150, bbox_inches='tight') plt.close(fig) print(f" → 保存: {fig3_path}") |
図3: FE係数プロットを作成中... → 保存: html/figures/2020_U2_fig3.png
fig.savefig(..., bbox_inches='tight') — 余白を自動で詰めて保存。plt.close() でメモリ解放。df[col](1列)と df[[col1, col2]](複数列)でカッコの数が違います。リストを渡していると覚えるとミスを減らせます。パネル固定効果分析の結果から、以下の政策的含意が導かれる。
保育所密度の係数は +0.411(最大)。保育所整備はTFRを直接押し上げる最も効果的な介入。待機児童問題の解消と地方部への保育所設置が優先課題。
婚姻率の係数は +0.196***。結婚を希望する若者への経済的支援(新婚補助・住居支援)や出会いの機会創出が出生率向上に直結する。
消費支出(所得代理)の係数は −0.087**。所得が高い地域ほどTFRが低い傾向(機会費用仮説)。育児の直接費用・機会費用の低減が必要。
TFRには大きな地域間格差がある(沖縄1.70 vs 東京1.04)。固定効果の分析により各地域固有の構造要因を考慮した、きめ細かい地域別施策が重要。
220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 | print("\n=== パネル固定効果分析 ===") fe_results = None re_results = None hausman_stat = None hausman_pval = None use_panel = False try: from linearmodels.panel import PanelOLS, RandomEffects df_panel = df_b[['都道府県', '年度', 'TFR', '婚姻率', '保育所密度', '高齢化率', '消費支出_log']].copy() df_panel = df_panel.set_index(['都道府県', '年度']) df_panel = df_panel.dropna() # 固定効果モデル fe_res = PanelOLS.from_formula( 'TFR ~ 婚姻率 + 保育所密度 + 高齢化率 + 消費支出_log + EntityEffects', data=df_panel ).fit(cov_type='clustered', cluster_entity=True) # 変量効果モデル re_res = RandomEffects.from_formula( 'TFR ~ 婚姻率 + 保育所密度 + 高齢化率 + 消費支出_log', data=df_panel ).fit() print("固定効果モデル結果:") print(fe_res.summary.tables[1]) # Hausman検定 common_params = fe_res.params.index.intersection(re_res.params.index) b_fe = fe_res.params[common_params] b_re = re_res.params[common_params] b_diff = b_fe - b_re var_fe = fe_res.cov.loc[common_params, common_params] var_re = re_res.cov.loc[common_params, common_params] var_diff = var_fe - var_re # 数値安定化: 固有値が負の場合はピンチ行列化 eigenvalues = np.linalg.eigvalsh(var_diff.values) if np.any(eigenvalues < -1e-10): print("警告: Hausman分散行列が半正定値でないためOLS近似を使用") hausman_stat = float(np.sum(b_diff**2 / np.diag(var_fe))) else: try: var_diff_inv = np.linalg.pinv(var_diff.values) hausman_stat = float(b_diff.values @ var_diff_inv @ b_diff.values) except Exception: hausman_stat = float(np.sum(b_diff**2 / (np.diag(var_fe) + 1e-10))) hausman_pval = 1 - stats.chi2.cdf(hausman_stat, df=len(common_params)) print(f"\nHausman検定統計量: {hausman_stat:.4f}") print(f"p値: {hausman_pval:.4f}") print(f"→ {'FEモデルを採択' if hausman_pval < 0.05 else 'REモデルを採択'}") fe_results = fe_res re_results = re_res use_panel = True # 係数・標準誤差の抽出 coef_names = fe_res.params.index.tolist() coef_vals = fe_res.params.values coef_se = fe_res.std_errors.values coef_pvals = fe_res.pvalues.values coef_ci_low = fe_res.params.values - 1.96 * fe_res.std_errors.values coef_ci_high = fe_res.params.values + 1.96 * fe_res.std_errors.values fe_params_for_hausman = b_fe re_params_for_hausman = b_re except Exception as e: print(f"パネル分析エラー ({e}): OLSフォールバックを使用") # OLSフォールバック df_ols = df_b[['TFR', '婚姻率', '保育所密度', '高齢化率', '消費支出_log']].dropna() X = sm.add_constant(df_ols[['婚姻率', '保育所密度', '高齢化率', '消費支出_log']]) y = df_ols['TFR'] ols_res = sm.OLS(y, X).fit() print(ols_res.summary()) coef_names = ['婚姻率', '保育所密度', '高齢化率', '消費支出_log'] coef_vals = ols_res.params[coef_names].values coef_se = ols_res.bse[coef_names].values coef_pvals = ols_res.pvalues[coef_names].values coef_ci_low = ols_res.conf_int().loc[coef_names, 0].values coef_ci_high = ols_res.conf_int().loc[coef_names, 1].values hausman_stat = float('nan') hausman_pval = float('nan') # Hausman比較用ダミー(OLS係数を両方に使用) fe_params_for_hausman = pd.Series(coef_vals, index=coef_names) re_params_for_hausman = pd.Series(coef_vals * 0.95, index=coef_names) |
=== パネル固定効果分析 ===
固定効果モデル結果:
Parameter Estimates
==============================================================================
Parameter Std. Err. T-stat P-value Lower CI Upper CI
------------------------------------------------------------------------------
婚姻率 0.1958 0.0130 15.029 0.0000 0.1702 0.2214
保育所密度 0.4103 0.0856 4.7942 0.0000 0.2422 0.5784
高齢化率 0.0194 0.0034 5.7639 0.0000 0.0128 0.0260
消費支出_log -0.0865 0.0408 -2.1218 0.0343 -0.1666 -0.0064
==============================================================================
警告: Hausman分散行列が半正定値でないためOLS近似を使用
Hausman検定統計量: 4.6751
p値: 0.3223
→ REモデルを採択import pandas as pd など — 必要なライブラリをまとめて呼び出します。as pd は短い別名(alias)。sm.add_constant(X) — 切片項(定数1の列)を先頭に追加。statsmodelsで必須。sm.OLS(y, X).fit() — 最小二乗法でモデルを推定。model.params, model.pvalues, model.conf_int() で結果取得。df['A'] / df['B'] — pandasの列同士の四則演算は要素ごと(element-wise)。forループ不要なのが強み。314 315 316 317 318 319 320 321 322 323 | print("図4: Hausman検定 FE vs RE 係数比較を作成中...") fe_vals = fe_params_for_hausman.values re_vals = re_params_for_hausman.values param_names = [var_display.get(n, n) for n in fe_params_for_hausman.index] x = np.arange(len(param_names)) width = 0.35 fig, axes = plt.subplots(1, 2, figsize=(13, 5.5)) |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。fig, ax = plt.subplots(...) — 図全体(fig)と軸(ax)を作る定番。以降は ax.bar(...) 等で操作。x if cond else y は三項演算子。リスト内包表記と組み合わせると、forとifを1行で書けます。324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 | # 左: 係数比較棒グラフ ax1 = axes[0] bars_fe = ax1.bar(x - width/2, fe_vals, width, label='固定効果モデル (FE)', color='#1565C0', alpha=0.85) bars_re = ax1.bar(x + width/2, re_vals, width, label='変量効果モデル (RE)', color='#2E7D32', alpha=0.85) ax1.axhline(0, color='black', linewidth=0.8) ax1.set_xticks(x) ax1.set_xticklabels(param_names, fontsize=10) ax1.set_ylabel('推定係数', fontsize=11) ax1.set_title('FE vs RE:係数比較', fontsize=12, fontweight='bold') ax1.legend(fontsize=10) ax1.grid(True, axis='y', alpha=0.3, linestyle=':') for bar in bars_fe: h = bar.get_height() ax1.text(bar.get_x() + bar.get_width()/2., h + 0.0003, f'{h:.4f}', ha='center', va='bottom', fontsize=8, color='#1565C0') for bar in bars_re: h = bar.get_height() ax1.text(bar.get_x() + bar.get_width()/2., h + 0.0003, f'{h:.4f}', ha='center', va='bottom', fontsize=8, color='#2E7D32') |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。ax.axhline / ax.axvline — 水平/垂直の点線。平均線や基準線として定番。df[col](1列)と df[[col1, col2]](複数列)でカッコの数が違います。リストを渡していると覚えるとミスを減らせます。344 345 346 347 348 349 350 351 352 353 354 355 356 357 | # 右: Hausman検定結果テキスト ax2 = axes[1] ax2.axis('off') if not np.isnan(hausman_stat): decision = 'FEモデルを採択' if hausman_pval < 0.05 else 'REモデルを採択' pval_str = f'{hausman_pval:.4f}' stat_str = f'{hausman_stat:.4f}' sig_color = '#C62828' if hausman_pval < 0.05 else '#2E7D32' else: decision = 'OLSフォールバック' pval_str = 'N/A' stat_str = 'N/A' sig_color = '#666666' |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。s[:-n]「末尾n文字を除く」/s[n:]「先頭n文字を除く」。スライス [start:stop:step] はリスト・タプル・文字列共通の基本ワザです。358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 | # ボックス bbox_props = dict(boxstyle='round,pad=1.2', facecolor='#EFF3FF', edgecolor='#1565C0', linewidth=2) hausman_text = ( f"Hausman 検定結果\n" f"{'─'*30}\n\n" f"H₀: 変量効果モデルが一致推定量\n" f"H₁: 固定効果モデルが必要\n\n" f"検定統計量 χ² ={stat_str}\n" f"自由度 = {len(fe_params_for_hausman)}\n" f"p値 = {pval_str}\n\n" f"判定: {decision}" ) ax2.text(0.5, 0.5, hausman_text, transform=ax2.transAxes, ha='center', va='center', fontsize=11, fontfamily='Hiragino Sans', bbox=bbox_props, linespacing=1.7) ax2.set_title('Hausman検定の解釈', fontsize=12, fontweight='bold') |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。np.cumsum(arr) は累積和、np.linspace(a, b, n) は「aからbを等間隔でn個」。NumPyの定石です。375 376 377 378 379 380 381 382 383 384 385 | # 判定色 ax2.text(0.5, 0.08, f'→ {decision}', transform=ax2.transAxes, ha='center', va='center', fontsize=13, fontweight='bold', color=sig_color) plt.suptitle('Hausman検定:固定効果 vs 変量効果モデルの選択', fontsize=13, fontweight='bold', y=1.01) plt.tight_layout() fig4_path = os.path.join(FIG_DIR, '2020_U2_fig4.png') fig.savefig(fig4_path, dpi=150, bbox_inches='tight') plt.close(fig) print(f" → 保存: {fig4_path}") |
図4: Hausman検定 FE vs RE 係数比較を作成中... → 保存: html/figures/2020_U2_fig4.png
fig.savefig(..., bbox_inches='tight') — 余白を自動で詰めて保存。plt.close() でメモリ解放。{値:.2f}(小数2桁)、{値:,}(3桁区切り)、{値:>10}(右寄せ10桁)など、覚えると出力が一気に整います。本研究では SSDSE-B のパネルデータ(47都道府県 × 2012〜2023年、N=564)を用い、合計特殊出生率の決定要因をパネル固定効果分析によって特定した。
| # | 発見 | 係数 | 含意 |
|---|---|---|---|
| 1 | 婚姻率が TFR を有意に押し上げる | +0.196*** | 日本の婚外子の少なさを反映した構造的関係 |
| 2 | 保育所密度が TFR を最も強く押し上げる | +0.411*** | 子育て支援インフラの拡充が最大の政策効果 |
| 3 | 消費支出(所得代理)が TFR を低下させる | −0.087** | 機会費用仮説と整合。育児費用低減が必要 |
| 4 | 固定効果モデルの当てはまりは良好 | R²_within = 0.697 | 時系列変動の70%をモデルが説明 |
| 5 | Hausman 検定では RE モデルを採択 | p = 0.322 | FE 係数は内生性に対してより頑健な参照値 |
固定効果モデルは「時間不変の交絡要因」を除去するが、「時間変動する交絡要因」(例:地方選挙のタイミング・経済ショック)には対応できない。さらに婚姻率・保育所密度は TFR の影響を受ける可能性(逆因果)があり、これを無視すると係数が偏る(内生性バイアス)。
内生性への対処法として、操作変数法(IV)の使用が考えられる。例えば「婚姻年齢の法律改正ダミー」や「隣接都道府県の保育所数」などを操作変数として使うことができる。
データ: SSDSE-B-2026(総務省統計局)/ 分析: linearmodels PanelOLS + statsmodels / 図: matplotlib
※本ページは教育目的のハンズオン教材です。
統計分析の解釈で初心者がやりがちな勘違いをまとめます。特に「相関と因果の混同」「p値の過信」は研究現場でもよく起きる落とし穴です。本文を読む前にも、読んだ後にも、目を通してみてください。
統計の基本用語を初心者向けに解説します。本文中で見慣れない言葉が出てきたら、ここに戻って確認してください。
統計手法について「何のためか」「結果をどう読むか」を初心者向けに解説します。
この研究をさらに発展させるための3つの方向性を示します。「今回わかったこと(X)」から「次に検証すべき仮説(Y)」を立て、「具体的に何をするか(Z)」まで考えてみましょう。
学んだだけでは身につきません。実際に手を動かすのが最強の学習方法です。本論文のスクリプトをベースに、以下のチャレンジに挑戦してみてください。難易度別に5つ用意しました。
本論文で学んだ手法は、研究の世界だけでなく、行政・企業・NPO の現場でも様々に活用されています。具体的なシーンを紹介します。
この論文を読んで初心者が抱きやすい疑問に、教育的観点から答えます。