この教材は原論文と同じデータを読み込み、同じ手順で計算し直しています。画面に出る数値と図は、あなたのブラウザがその場で計算した結果です。
| 原論文が使ったデータ | SSDSE-B・SSDSE-D・賃金構造基本調査・社会生活基本調査・児童手当事業年報・社会・人口統計体系・人口動態調査 分析単位:都道府県 中核手法:Pooled OLS(集計最小二乗法)・Hybrid モデル |
|---|---|
| この教材が使うデータ |
|
| 原論文(PDF) | 都道府県別のパネルデータを用いた合計特殊出生率の決定要因 ―地域差と女性の時間選択がどう影響しているか― 審査員奨励賞/出川 朋佳、近藤 七海、玉木 由梨、山本 桃子(東洋英和女学院大学国際社会学部国際社会学科) |
原論文と同じ粒度のデータは、この教材にも同梱しています。これを読み込めば、原論文と同じ細かさで分析をやり直せます(下の「🐍 ブラウザで動かす」でコードを書き換えて試せます)。ただし原論文が使った項目がすべて収録されているとは限りません。足りない項目は、上の「できないこと」に書いた出典から取ってくる必要があります。
🚫 この論文の再現コードは linearmodels を使うため、ブラウザの中では動かせません。お手元の Python で python3 code/2023_U5_4_shorei.py を実行してください(489 行、編集不要)。必要なデータは上のリンクからダウンロードできます。
このページの分析を自分で再現するには、以下の手順でデータを準備してください。コードの編集は不要です。
data/raw/ フォルダに入れます。html/figures/ に自動保存されます。
日本の合計特殊出生率(TFR)は1973年の第二次ベビーブーム以降、一貫した低下傾向にある。2023年は過去最低水準を更新し、少子化への対策立案に向けた実証分析の重要性が高まっている。
この論文が挑んだ問いは「出生率の地域差を生む社会経済要因は何か」(テーマ:人口・少子化・子育て・保育)。 著者はHausman検定・VIF/多重共線性・パネルデータ分析を軸にこの問いへ定量的に答えを出した。原論文がたどり着いた答えは「児童手当等の子育て支援は出生率に効くが地域差が大きい。2015-2020年は所得要因の効果が確認されず(世代重複モデルの含意)」。 本ページではその分析の流れを実データで再現しながら、使われた統計手法を一つずつ学んでいく。
本研究は 47都道府県 × 2012〜2023年(12年間)のパネルデータ を用い、パネル固定効果モデルにより TFR の統計的決定要因を特定する。固定効果(FE)と変量効果(RE)の選択はHausman検定に基づき、多重共線性はVIFで診断する。
SSDSE-B パネルFE回帰 Hausman検定 VIF linearmodels
SSDSE(社会・人口統計体系データセット)-B は都道府県レベルの時系列統計データ。2012〜2023年の12年間、47都道府県の計564観測を使用する。
| 変数名 | SSDSE-B 列コード | 計算方法 | 想定方向 |
|---|---|---|---|
| 合計特殊出生率(TFR) | A4103 | 原データそのまま | — (目的変数) |
| 婚姻率 | A9101 / A1101 | 婚姻件数 / 総人口 × 10000 | 正(既婚が出生を促進) |
| 保育所密度 | J2503 / A1101 | 保育所等数 / 総人口 × 10000 | 正(子育て環境) |
| 保育所充足率 | J2506 / J2505 | 在所児数 / 定員数 | 正(待機児童の少なさ) |
| 有効求人倍率 | F3103 / F3102 | 有効求人数 / 有効求職者数 | 正(雇用機会) |
| 消費支出(所得代理) | L3221 | 二人以上世帯の消費支出(円) | 負(生活費増大) |
| 住宅地価格 | C5401 | 標準価格(住宅地)(円/㎡) | 負(居住コスト) |
| 高齢化率 | A1303 / A1101 | 65歳以上人口 / 総人口 × 100 | 負(人口構造の硬直) |
1 2 3 4 5 6 7 8 9 | print("=" * 65) print("■ データ読み込み・構築(SSDSE-B-2026.csv)") print("=" * 65) # SSDSE-B-2026 読み込み # header=0 でコードベースの列名(A4103 等)を取得し、 # 先頭行(日本語ラベル行)をスキップする df_b = pd.read_csv(DATA_B, encoding='cp932', header=0) df_b = df_b.iloc[1:].reset_index(drop=True) # 日本語ラベル行を除外 |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。pd.read_csv(...) でCSVを読み込みます。encoding='cp932' は日本語Windows由来の文字コード、header=1 は「2行目を列名として使う」。df['A'] / df['B'] — pandasの列同士の四則演算は要素ごと(element-wise)。forループ不要なのが強み。10 11 12 13 14 15 | # 列名を分析で使いやすい名称にリネーム df_b = df_b.rename(columns={ 'SSDSE-B-2026': '年度', 'Code': '地域コード', 'Prefecture': '都道府県', }) |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。.map() は「1対1の置き換え」、.apply() は「関数を当てる」。辞書なら .map()、ロジックなら .apply()。16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | # 47都道府県のみ(地域コード = R + 5桁数字) df_b = df_b[df_b['地域コード'].str.match(r'^R\d{5}$', na=False)].copy() # 必要列を数値変換 needed = ['年度', '都道府県', 'A4103', 'A9101', 'J2503', 'A1101', 'J2506', 'J2505', 'F3103', 'F3102', 'L3221', 'C5401', 'A1303'] for col in needed: if col not in ('都道府県',): df_b[col] = pd.to_numeric(df_b[col], errors='coerce') df_b = df_b.dropna(subset=needed) print(f"読み込み完了: {len(df_b)} 行(47都道府県 × 12年度)") print(f"年度範囲: {int(df_b['年度'].min())}〜{int(df_b['年度'].max())}") print(f"都道府県数: {df_b['都道府県'].nunique()}") |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。df['地域コード'].str.match(r'^R\d{5}', ...) — 正規表現で「R+数字5桁」の行(47都道府県)だけTrueにし、真偽値で行をフィルタ。[式 for x in リスト] はリスト内包表記。forループでappendする代わりに1行でリストを作れます。31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 | # ── 説明変数の計算(実データのみ) ──────────────────────────────── # 婚姻率: 婚姻件数 / 総人口 × 10000 df_b['婚姻率'] = df_b['A9101'] / df_b['A1101'] * 10000 # 保育所密度: 保育所等数 / 総人口 × 10000 df_b['保育所密度'] = df_b['J2503'] / df_b['A1101'] * 10000 # 保育充足率: 在所児数 / 定員数 df_b['保育充足率'] = df_b['J2506'] / df_b['J2505'] # 有効求人倍率: 有効求人数 / 有効求職者数 df_b['有効求人倍率'] = df_b['F3103'] / df_b['F3102'] # 高齢化率: 65歳以上 / 総人口 × 100 df_b['高齢化率'] = df_b['A1303'] / df_b['A1101'] * 100 PREDICTOR_COLS = ['婚姻率', '保育所密度', '保育充足率', '有効求人倍率', 'L3221', 'C5401', '高齢化率'] PRED_LABELS = { '婚姻率': '婚姻率(件/万人)', '保育所密度': '保育所密度(施設/万人)', '保育充足率': '保育所充足率(在所/定員)', '有効求人倍率': '有効求人倍率', 'L3221': '消費支出(円)', 'C5401': '住宅地価格(円/㎡)', '高齢化率': '高齢化率(%)', } |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。r, p = stats.pearsonr(...) — Pythonは複数戻り値を同時に受け取れる(タプルアンパック)。55 56 57 58 59 60 61 62 | # ── パネルデータ構築 ────────────────────────────────────────────── df_panel = df_b.set_index(['都道府県', '年度']) all_needed = ['A4103'] + PREDICTOR_COLS df_panel = df_panel.dropna(subset=all_needed) print(f"\nパネルデータ: {len(df_panel)} 観測, " f"{df_panel.index.get_level_values(0).nunique()} 都道府県, " f"{df_panel.index.get_level_values(1).nunique()} 年度") |
================================================================= ■ データ読み込み・構築(SSDSE-B-2026.csv) ================================================================= 読み込み完了: 564 行(47都道府県 × 12年度) 年度範囲: 2012〜2023 都道府県数: 47 パネルデータ: 564 観測, 47 都道府県, 12 年度
x if cond else y は三項演算子。リスト内包表記と組み合わせると、forとifを1行で書けます。パネルデータ分析において 固定効果(FE) と 変量効果(RE) のどちらを採用すべきかは理論・実証両面から重要な問題である。Hausman(1978)検定は「変量効果が一致推定量を与える」という帰無仮説を検定し、棄却されれば FE が望ましい。
| 検定 | 統計量 | 自由度 | p値 | 判定 |
|---|---|---|---|---|
| Hausman 検定 | H = 50.59 | 7 | p < 0.001 | FE 採用 |
| Poolability F 検定 | F(46, 510) = 39.84 | 46, 510 | p < 0.001 | 個体効果あり |
FE と RE の係数ベクトルの差から χ² 統計量を計算する。分散共分散行列の差が正定値でない場合(差が負になる要素がある場合)は、正の要素のみを使用するのが一般的な実装。
64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 | print("図1: 全国平均TFRの時系列推移を作成中...") tfr_year = df_b.groupby('年度')['A4103'].mean() tfr_q25 = df_b.groupby('年度')['A4103'].quantile(0.25) tfr_q75 = df_b.groupby('年度')['A4103'].quantile(0.75) fig1, ax1 = plt.subplots(figsize=(11, 5.5)) ax1.fill_between(tfr_year.index, tfr_q25.values, tfr_q75.values, alpha=0.20, color='#1565C0', label='25〜75パーセンタイル') ax1.plot(tfr_year.index, tfr_year.values, 'o-', color='#1565C0', linewidth=2.2, markersize=7, label='全国平均TFR') ax1.axhline(2.07, color='#C62828', linestyle=':', linewidth=1.5, alpha=0.7, label='人口置換水準(2.07)') ax1.axhline(1.0, color='#999', linestyle='--', linewidth=1.0, alpha=0.5) |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。df.groupby('列').apply(関数) — グループごとに関数を適用。時系列や地域別の集計でよく使います。fig, ax = plt.subplots(...) — 図全体(fig)と軸(ax)を作る定番。以降は ax.bar(...) 等で操作。ax.axhline / ax.axvline — 水平/垂直の点線。平均線や基準線として定番。ax.fill_between(...) — 2つの曲線で囲まれた領域を塗りつぶし。Lorenz曲線の格差面積などを可視化。df[col](1列)と df[[col1, col2]](複数列)でカッコの数が違います。リストを渡していると覚えるとミスを減らせます。79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 | # 主要イベント注記 events = { 2015: ('少子化社会\n対策大綱', '#2E7D32'), 2019: ('新型コロナ\n前夜', '#E65100'), 2020: ('コロナ禍\n始まり', '#C62828'), 2023: ('過去最低\n1.20→1.09', '#6A1B9A'), } for yr, (label, color) in events.items(): if yr in tfr_year.index: val = tfr_year[yr] ax1.annotate(label, xy=(yr, val), xytext=(yr, val + 0.06), ha='center', fontsize=9, color=color, arrowprops=dict(arrowstyle='->', color=color, lw=1.2), bbox=dict(boxstyle='round,pad=0.3', fc='white', ec=color, alpha=0.85)) ax1.set_xlabel('年度', fontsize=11) ax1.set_ylabel('合計特殊出生率(TFR)', fontsize=11) ax1.set_title('全国平均 合計特殊出生率の推移(2012〜2023年)\n' '出典: SSDSE-B-2026(47都道府県)', fontsize=12, fontweight='bold') ax1.set_ylim(1.1, 1.75) ax1.legend(fontsize=9, loc='upper right') ax1.grid(True, alpha=0.3) ax1.set_xticks(sorted(tfr_year.index)) ax1.set_xticklabels([str(y) for y in sorted(tfr_year.index)], rotation=45, ha='right', fontsize=9) plt.tight_layout() out1 = os.path.join(FIG_DIR, '2023_U5_4_fig1_tfr_trend.png') fig1.savefig(out1, bbox_inches='tight', dpi=150) plt.close(fig1) print(f" -> {os.path.basename(out1)} 保存完了") |
図1: 全国平均TFRの時系列推移を作成中... -> 2023_U5_4_fig1_tfr_trend.png 保存完了
s[:-n]「末尾n文字を除く」/s[n:]「先頭n文字を除く」。スライス [start:stop:step] はリスト・タプル・文字列共通の基本ワザです。採用したパネル固定効果モデルは、都道府県固有の時不変な特性(観察不能な個体効果)を完全に除去する。標準誤差は都道府県レベルのクラスター補正(clustered SE)を用いて不均一分散・系列相関に頑健にした。
α_i:都道府県固定効果(観察不能な不変要因を吸収), i=1..47, t=2012..2023
| 変数 | 推定係数 | Cluster SE | t値 | p値 | 有意 | 方向 |
|---|---|---|---|---|---|---|
| 婚姻率(件/万人) | 0.01449 | 0.00183 | 7.93 | <0.001 | *** | 正 |
| 保育所密度(施設/万人) | 0.03167 | 0.00707 | 4.48 | <0.001 | *** | 正 |
| 保育所充足率(在所/定員) | 0.20756 | 0.07736 | 2.68 | 0.008 | ** | 正 |
| 有効求人倍率 | 0.09434 | 0.01929 | 4.89 | <0.001 | *** | 正 |
| 消費支出(円) | −4.1×10⁻⁷ | 1.4×10⁻⁷ | −2.83 | 0.005 | ** | 負 |
| 住宅地価格(円/㎡) | 1.4×10⁻⁷ | 6.1×10⁻⁷ | 0.23 | 0.819 | ns | — |
| 高齢化率(%) | 0.00376 | 0.00528 | 0.71 | 0.477 | ns | — |
| Within R² = 0.722 | N = 564 | F(7, 510) = 189.1 (p<0.001) | ***p<.001, **p<.01, *p<.05 | |||||
パネルFEは都道府県ダミーを含むことで α_i(文化・地理など不変要因)を除去する。残差の都道府県内系列相関(同じ都道府県の観測が複数年)に対処するため、entity-level clustered SE を使用する。
113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 | print("図2: 固定効果モデルの係数プロットを作成中...") fig2, ax2 = plt.subplots(figsize=(10, 6)) coefs = [fe.params[c] for c in PREDICTOR_COLS] ses = [fe.std_errors[c] for c in PREDICTOR_COLS] pvals = [fe.pvalues[c] for c in PREDICTOR_COLS] labels = [PRED_LABELS[c] for c in PREDICTOR_COLS] bar_colors = [ '#C62828' if c < 0 and p < 0.05 else '#1565C0' if c > 0 and p < 0.05 else '#9E9E9E' for c, p in zip(coefs, pvals) ] y_pos = np.arange(len(PREDICTOR_COLS)) ci_95 = [1.96 * s for s in ses] ax2.barh(y_pos, coefs, xerr=ci_95, color=bar_colors, alpha=0.82, edgecolor='white', capsize=4, error_kw={'elinewidth': 1.5, 'ecolor': '#444'}) ax2.axvline(0, color='black', linewidth=1.0) ax2.set_yticks(y_pos) ax2.set_yticklabels(labels, fontsize=10) ax2.set_xlabel('固定効果推定量(±1.96 × SE)', fontsize=11) ax2.set_title('パネル固定効果モデルの回帰係数(95% CI)\n' '(被説明変数: 合計特殊出生率, SSDSE-B実データ)', fontsize=11, fontweight='bold') ax2.grid(axis='x', alpha=0.3) ax2.invert_yaxis() for i, (c, s, p) in enumerate(zip(coefs, ses, pvals)): sig = ('***' if p < 0.001 else '**' if p < 0.01 else '*' if p < 0.05 else '') if sig: offset = np.sign(c) * (1.96 * s + abs(max(coefs, key=abs)) * 0.03) ax2.text(c + offset, i, sig, va='center', ha='left' if c > 0 else 'right', fontsize=11, color='#222', fontweight='bold') red_p = mpatches.Patch(color='#C62828', alpha=0.82, label='有意・負の効果(p<0.05)') blu_p = mpatches.Patch(color='#1565C0', alpha=0.82, label='有意・正の効果(p<0.05)') gry_p = mpatches.Patch(color='#9E9E9E', alpha=0.82, label='非有意(p≥0.05)') ax2.legend(handles=[blu_p, red_p, gry_p], fontsize=9, loc='lower right') |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。fig, ax = plt.subplots(...) — 図全体(fig)と軸(ax)を作る定番。以降は ax.bar(...) 等で操作。ax.axhline / ax.axvline — 水平/垂直の点線。平均線や基準線として定番。s[:-n]「末尾n文字を除く」/s[n:]「先頭n文字を除く」。スライス [start:stop:step] はリスト・タプル・文字列共通の基本ワザです。158 159 160 161 162 163 164 165 166 167 168 169 | # Hausman・R2情報をテキストで追記 ax2.text(0.98, 0.02, f"Within R²={fe.rsquared:.3f}\nHausman H={H_stat:.1f} (p<0.001)\n→ FE採用", transform=ax2.transAxes, ha='right', va='bottom', fontsize=9, color='#333', bbox=dict(boxstyle='round,pad=0.4', fc='#F3E5F5', ec='#6A1B9A', alpha=0.9)) plt.tight_layout() out2 = os.path.join(FIG_DIR, '2023_U5_4_fig2_fe_coef.png') fig2.savefig(out2, bbox_inches='tight', dpi=150) plt.close(fig2) print(f" -> {os.path.basename(out2)} 保存完了") |
図2: 固定効果モデルの係数プロットを作成中... -> 2023_U5_4_fig2_fe_coef.png 保存完了
np.cumsum(arr) は累積和、np.linspace(a, b, n) は「aからbを等間隔でn個」。NumPyの定石です。FEモデルで最も影響力の大きかった変数(婚姻率・保育所密度)について、横断面データによる散布図で関係を可視化する。
171 172 173 174 175 176 177 178 179 180 181 182 | print("図3: 婚姻率 vs TFR の散布図(2022年横断面)を作成中...") df_2022 = df_b[df_b['年度'] == 2022].copy() fig3, ax3 = plt.subplots(figsize=(9, 6.5)) sc3 = ax3.scatter(df_2022['婚姻率'], df_2022['A4103'], c=df_2022['高齢化率'], cmap='RdYlGn_r', s=70, alpha=0.85, edgecolors='white', linewidths=0.5, zorder=3) cbar3 = plt.colorbar(sc3, ax=ax3) cbar3.set_label('高齢化率(%)', fontsize=10) |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。fig, ax = plt.subplots(...) — 図全体(fig)と軸(ax)を作る定番。以降は ax.bar(...) 等で操作。np.cumsum(arr) は累積和、np.linspace(a, b, n) は「aからbを等間隔でn個」。NumPyの定石です。183 184 185 186 187 188 189 190 191 | # 回帰直線 x3 = df_2022['婚姻率'].values y3 = df_2022['A4103'].values mask3 = ~(np.isnan(x3) | np.isnan(y3)) if mask3.sum() > 2: slope3, intercept3, r3, p3, _ = stats.linregress(x3[mask3], y3[mask3]) xline3 = np.linspace(x3[mask3].min(), x3[mask3].max(), 100) ax3.plot(xline3, intercept3 + slope3 * xline3, '--', color='#1565C0', linewidth=1.8, label=f'OLS (r={r3:.2f})', zorder=2) |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。stats.linregress(x, y) — 単回帰の傾き・切片・r値・p値・標準誤差を返します。使わない値は _ で受け取り。{値:.2f}(小数2桁)、{値:,}(3桁区切り)、{値:>10}(右寄せ10桁)など、覚えると出力が一気に整います。192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 | # 代表的な都道府県をラベル表示 highlights3 = ['東京都', '沖縄県', '秋田県', '島根県', '愛知県'] for _, row in df_2022.iterrows(): if row['都道府県'] in highlights3: ax3.annotate(row['都道府県'], xy=(row['婚姻率'], row['A4103']), xytext=(4, 4), textcoords='offset points', fontsize=8.5, color='#333', bbox=dict(boxstyle='round,pad=0.2', fc='white', alpha=0.75)) ax3.set_xlabel('婚姻率(件/万人)', fontsize=11) ax3.set_ylabel('合計特殊出生率(TFR)', fontsize=11) ax3.set_title('婚姻率 vs 合計特殊出生率(2022年 都道府県横断面)\n' '色: 高齢化率(緑=低、赤=高)', fontsize=11, fontweight='bold') ax3.legend(fontsize=10, loc='upper left') ax3.grid(True, alpha=0.3) plt.tight_layout() out3 = os.path.join(FIG_DIR, '2023_U5_4_fig3_marriage_tfr.png') fig3.savefig(out3, bbox_inches='tight', dpi=150) plt.close(fig3) print(f" -> {os.path.basename(out3)} 保存完了") |
図3: 婚姻率 vs TFR の散布図(2022年横断面)を作成中... -> 2023_U5_4_fig3_marriage_tfr.png 保存完了
for _, row in df.iterrows() — DataFrameを1行ずつ取り出すループ。1点ずつ描画したいときに使用。plt.subplots(figsize=(W, H)) で図サイズ指定、fig.savefig(..., bbox_inches='tight') で余白を自動で詰めて保存。分散膨張係数(Variance Inflation Factor, VIF)は各説明変数を他の説明変数で回帰したときの R² を用いて多重共線性の程度を測る。
| 変数 | VIF | 判定 | 解釈 |
|---|---|---|---|
| 住宅地価格(円/㎡) | 3.1 | ○ 良好 | 他変数との相関が低く安定 |
| 保育所密度(施設/万人) | 16.7 | △ やや高 | 保育充足率と共線 |
| 有効求人倍率 | 21.5 | △ やや高 | 雇用・所得系変数と相関 |
| 婚姻率(件/万人) | 130.5 | ⚠ 高 | 人口構造変数と強く連動 |
| 消費支出(円) | 142.2 | ⚠ 高 | 所得・地価と相関 |
| 高齢化率(%) | 135.6 | ⚠ 高 | 婚姻率・人口構造と連動 |
| 保育所充足率(在所/定員) | 227.5 | ⚠ 高 | 保育所密度と強い共線 |
VIF=10 以上で多重共線性の懸念(一般的基準)。VIF が高い場合は (1) Ridge/Lasso 等の正則化、(2) 主成分回帰、(3) 変数削除などを検討する。パネルFEでは「within 変動」が小さいと実質的に多重共線性が悪化するため注意が必要。
216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 | 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 statsmodels.stats.outliers_influence import variance_inflation_factor from linearmodels import PanelOLS, RandomEffects from scipy import stats import warnings warnings.filterwarnings('ignore') plt.rcParams['font.family'] = 'Hiragino Sans' plt.rcParams['axes.unicode_minus'] = False plt.rcParams['figure.dpi'] = 150 import os DATA_DIR = 'data/raw' FIG_DIR = 'html/figures' os.makedirs(FIG_DIR, exist_ok=True) DATA_B = os.path.join(DATA_DIR, 'SSDSE-B-2026.csv') |
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)。f"...{x}..." はf-string。文字列の中に {変数} と書くだけで埋め込めて、{x:.2f} のように書式も指定できます。239 240 241 242 243 244 | print("\n" + "=" * 65) print("■ Step1. パネル固定効果・変量効果モデルの推定") print("=" * 65) dep = df_panel['A4103'] exog = sm.add_constant(df_panel[PREDICTOR_COLS]) |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。sm.add_constant(X) — 切片項(定数1の列)を先頭に追加。statsmodelsで必須。.map() は「1対1の置き換え」、.apply() は「関数を当てる」。辞書なら .map()、ロジックなら .apply()。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 | # 固定効果モデル(entity effects + clustered SE) fe = PanelOLS(dep, exog, entity_effects=True).fit( cov_type='clustered', cluster_entity=True) # 変量効果モデル re = RandomEffects(dep, exog).fit() print("\n【固定効果モデル(FE)】") print(f" Within R² = {fe.rsquared:.4f}") print(f" 観測数 = {fe.nobs}") print(f" F統計量 = {fe.f_statistic.stat:.3f} (p={fe.f_statistic.pval:.4f})") print() print(f" {'変数':<16} {'係数':>10} {'SE':>10} {'t値':>8} {'p値':>10} 有意") print(" " + "-" * 60) for col in PREDICTOR_COLS + ['const']: if col in fe.params.index: b = fe.params[col] se = fe.std_errors[col] t = fe.tstats[col] p = fe.pvalues[col] sig = ('***' if p < 0.001 else '**' if p < 0.01 else '*' if p < 0.05 else '†' if p < 0.1 else '') label = PRED_LABELS.get(col, col) print(f" {label:<16} {b:>10.5f} {se:>10.5f} {t:>8.3f} {p:>10.4f} {sig}") |
================================================================= ■ Step1. パネル固定効果・変量効果モデルの推定 ================================================================= 【固定効果モデル(FE)】 Within R² = 0.7219 観測数 = 564 F統計量 = 189.133 (p=0.0000) 変数 係数 SE t値 p値 有意 ------------------------------------------------------------ 婚姻率(件/万人) 0.01449 0.00183 7.927 0.0000 *** 保育所密度(施設/万人) 0.03167 0.00707 4.478 0.0000 *** 保育所充足率(在所/定員) 0.20756 0.07736 2.683 0.0075 ** 有効求人倍率 0.09434 0.01929 4.890 0.0000 *** 消費支出(円) -0.00000 0.00000 -2.827 0.0049 ** 住宅地価格(円/㎡) 0.00000 0.00000 0.230 0.8185 高齢化率(%) 0.00376 0.00528 0.712 0.4770 const 0.44453 0.19828 2.242 0.0254 *
[式 for x in リスト] はリスト内包表記。forループでappendする代わりに1行でリストを作れます。271 272 273 274 275 276 277 278 279 280 281 282 | print("\n" + "=" * 65) print("■ Step2. Hausman検定(固定効果 vs 変量効果)") print("=" * 65) b_fe = fe.params b_re = re.params common = [c for c in b_fe.index if c in b_re.index and c != 'const'] diff = np.array([b_fe[c] - b_re[c] for c in common]) var_fe = np.array([fe.cov.loc[c, c] for c in common]) var_re = np.array([re.cov.loc[c, c] for c in common]) var_diff = var_fe - var_re |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。[式 for x in リスト] はリスト内包表記。forループでappendする代わりに1行でリストを作れます。283 284 285 286 287 288 289 290 291 292 293 294 295 296 | # 分散差が正の変数のみ使用(標準的なHausman実装) valid = var_diff > 0 H_stat = float(diff[valid] @ np.diag(1.0 / var_diff[valid]) @ diff[valid]) H_df = int(valid.sum()) H_pval = 1 - stats.chi2.cdf(H_stat, df=H_df) print(f"\n Hausman検定統計量 H = {H_stat:.3f}") print(f" 自由度 = {H_df}") print(f" p値 = {H_pval:.4f}") if H_pval < 0.05: print(" → 帰無仮説(変量効果が一致推定量)を棄却") print(" → 固定効果モデル(FE)を採用") else: print(" → 帰無仮説を棄却できない → 変量効果モデル(RE)を採用") |
================================================================= ■ Step2. Hausman検定(固定効果 vs 変量効果) ================================================================= Hausman検定統計量 H = 50.587 自由度 = 7 p値 = 0.0000 → 帰無仮説(変量効果が一致推定量)を棄却 → 固定効果モデル(FE)を採用
r, p = stats.pearsonr(...) — Pythonは複数戻り値を同時に受け取れる(タプルアンパック)。297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 | print("\n" + "=" * 65) print("■ Step3. VIF(分散膨張係数)による多重共線性診断") print("=" * 65) X_vif = df_b[PREDICTOR_COLS].dropna().values vif_values = {} print(f"\n {'変数':<16} {'VIF':>8} 判定") print(" " + "-" * 40) for i, col in enumerate(PREDICTOR_COLS): vif = variance_inflation_factor(X_vif, i) vif_values[col] = vif flag = ('⚠ 高' if vif > 10 else '○ 良好') label = PRED_LABELS.get(col, col) print(f" {label:<16} {vif:>8.2f} {flag}") print("\n ※ VIF > 10 は多重共線性の懸念(パネルFEでは都道府県固定効果が吸収するため参考値)") |
================================================================= ■ Step3. VIF(分散膨張係数)による多重共線性診断 ================================================================= 変数 VIF 判定 ---------------------------------------- 婚姻率(件/万人) 130.51 ⚠ 高 保育所密度(施設/万人) 16.68 ⚠ 高 保育所充足率(在所/定員) 227.52 ⚠ 高 有効求人倍率 21.46 ⚠ 高 消費支出(円) 142.23 ⚠ 高 住宅地価格(円/㎡) 3.05 ○ 良好 高齢化率(%) 135.60 ⚠ 高 ※ VIF > 10 は多重共線性の懸念(パネルFEでは都道府県固定効果が吸収するため参考値)
r, p = stats.pearsonr(...) — Pythonは複数戻り値を同時に受け取れる(タプルアンパック)。313 314 315 | print("\n" + "=" * 65) print("■ 図の生成(4枚)") print("=" * 65) |
================================================================= ■ 図の生成(4枚) =================================================================
x if cond else y は三項演算子。リスト内包表記と組み合わせると、forとifを1行で書けます。316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 | print("図4: 保育所密度 vs TFR の散布図(複数年)を作成中...") TARGET_YEARS = [2016, 2019, 2022] YEAR_COLORS = {2016: '#1565C0', 2019: '#2E7D32', 2022: '#C62828'} YEAR_MARKERS = {2016: 'o', 2019: 's', 2022: '^'} fig4, ax4 = plt.subplots(figsize=(9, 6.5)) for yr in TARGET_YEARS: sub = df_b[df_b['年度'] == yr].copy() x4 = sub['保育所密度'].values y4 = sub['A4103'].values mask4 = ~(np.isnan(x4) | np.isnan(y4)) c4 = YEAR_COLORS[yr] m4 = YEAR_MARKERS[yr] ax4.scatter(x4[mask4], y4[mask4], color=c4, marker=m4, s=60, alpha=0.72, edgecolors='white', linewidths=0.4, label=f'{yr}年', zorder=3) if mask4.sum() > 2: slope4, intercept4, r4, p4, _ = stats.linregress(x4[mask4], y4[mask4]) xline4 = np.linspace(x4[mask4].min(), x4[mask4].max(), 80) ax4.plot(xline4, intercept4 + slope4 * xline4, '--', color=c4, linewidth=1.4, label=f'{yr}年 OLS (r={r4:.2f})', alpha=0.85, zorder=2) ax4.set_xlabel('保育所密度(施設/万人)', fontsize=11) ax4.set_ylabel('合計特殊出生率(TFR)', fontsize=11) ax4.set_title('保育所密度 vs 合計特殊出生率(2016・2019・2022年)\n' '47都道府県別 横断面散布図', fontsize=11, fontweight='bold') ax4.legend(fontsize=9, loc='upper right', ncol=2) ax4.grid(True, alpha=0.3) plt.tight_layout() out4 = os.path.join(FIG_DIR, '2023_U5_4_fig4_childcare_tfr.png') fig4.savefig(out4, bbox_inches='tight', dpi=150) plt.close(fig4) print(f" -> {os.path.basename(out4)} 保存完了") |
図4: 保育所密度 vs TFR の散布図(複数年)を作成中... -> 2023_U5_4_fig4_childcare_tfr.png 保存完了
fig, ax = plt.subplots(...) — 図全体(fig)と軸(ax)を作る定番。以降は ax.bar(...) 等で操作。stats.linregress(x, y) — 単回帰の傾き・切片・r値・p値・標準誤差を返します。使わない値は _ で受け取り。{値:.2f}(小数2桁)、{値:,}(3桁区切り)、{値:>10}(右寄せ10桁)など、覚えると出力が一気に整います。357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 | print("\n" + "=" * 65) print("■ 分析結果サマリ") print("=" * 65) print(f" パネルデータ: 47都道府県 × 12年度 = {fe.nobs} 観測") print(f" 固定効果モデル Within R² = {fe.rsquared:.4f}") print(f" Hausman検定: H={H_stat:.3f}, df={H_df}, p={H_pval:.4f} → FE採用") print() print(" 主要な決定要因(FE推定):") for col in PREDICTOR_COLS: b = fe.params[col] p = fe.pvalues[col] sig = ('***' if p < 0.001 else '**' if p < 0.01 else '*' if p < 0.05 else '†' if p < 0.1 else ' ns') direction = '正' if b > 0 else '負' print(f" {PRED_LABELS[col]:<20}: {b:>10.5f} ({direction}, {sig})") print() print(" VIF診断:") for col, vif in vif_values.items(): flag = '⚠ 高' if vif > 10 else '○ 良好' print(f" {PRED_LABELS[col]:<20}: VIF={vif:.1f} {flag}") print() print("生成図:") print(f" {os.path.basename(out1)}") print(f" {os.path.basename(out2)}") print(f" {os.path.basename(out3)}") print(f" {os.path.basename(out4)}") print() print("※ 使用データ: SSDSE-B-2026.csv のみ(合成データなし)") |
=================================================================
■ 分析結果サマリ
=================================================================
パネルデータ: 47都道府県 × 12年度 = 564 観測
固定効果モデル Within R² = 0.7219
Hausman検定: H=50.587, df=7, p=0.0000 → FE採用
主要な決定要因(FE推定):
婚姻率(件/万人) : 0.01449 (正, ***)
保育所密度(施設/万人) : 0.03167 (正, ***)
保育所充足率(在所/定員) : 0.20756 (正, **)
有効求人倍率 : 0.09434 (正, ***)
消費支出(円) : -0.00000 (負, **)
住宅地価格(円/㎡) : 0.00000 (正, ns)
高齢化率(%) : 0.00376 (正, ns)
VIF診断:
婚姻率(件/万人) : VIF=130.5 ⚠ 高
保育所密度(施設/万人) : VIF=16.7 ⚠ 高
保育所充足率(在所/定員) : VIF=227.5 ⚠ 高
有効求人倍率 : VIF=21.5 ⚠ 高
消費支出(円) : VIF=142.2 ⚠ 高
住宅地価格(円/㎡) : VIF=3.1 ○ 良好
高齢化率(%) : VIF=135.6 ⚠ 高
生成図:
2023_U5_4_fig1_tfr_trend.png
2023_U5_4_fig2_fe_coef.png
2023_U5_4_fig3_marriage_tfr.png
2023_U5_4_fig4_childcare_tfr.png
※ 使用データ: SSDSE-B-2026.csv のみ(合成データなし)plt.subplots(figsize=(W, H)) で図サイズ指定、fig.savefig(..., bbox_inches='tight') で余白を自動で詰めて保存。47都道府県 × 2012〜2023年のパネルデータを用いたパネル固定効果分析の結果:
| データ | 出典 |
|---|---|
| SSDSE-B 都道府県パネルデータ(2012〜2023年) | 統計センター SSDSE(社会・人口統計体系) |
| 合計特殊出生率(A4103) | 厚生労働省 人口動態統計(SSDSE収録) |
| 保育所等数・定員・在所数(J2503/J2505/J2506) | 厚生労働省 保育所等関連状況取りまとめ(SSDSE収録) |
| 有効求人数・求職者数(F3103/F3102) | 厚生労働省 職業安定業務統計(SSDSE収録) |
本教育用コードは SSDSE-B-2026.csv の実データのみを使用(合成データ・np.random 等は一切使用しない)。
統計分析の解釈で初心者がやりがちな勘違いをまとめます。特に「相関と因果の混同」「p値の過信」は研究現場でもよく起きる落とし穴です。本文を読む前にも、読んだ後にも、目を通してみてください。
統計の基本用語を初心者向けに解説します。本文中で見慣れない言葉が出てきたら、ここに戻って確認してください。
統計手法について「何のためか」「結果をどう読むか」を初心者向けに解説します。
この研究をさらに発展させるための3つの方向性を示します。「今回わかったこと(X)」から「次に検証すべき仮説(Y)」を立て、「具体的に何をするか(Z)」まで考えてみましょう。
学んだだけでは身につきません。実際に手を動かすのが最強の学習方法です。本論文のスクリプトをベースに、以下のチャレンジに挑戦してみてください。難易度別に5つ用意しました。
本論文で学んだ手法は、研究の世界だけでなく、行政・企業・NPO の現場でも様々に活用されています。具体的なシーンを紹介します。
この論文を読んで初心者が抱きやすい疑問に、教育的観点から答えます。
この論文を理解できたか、クリックで確認しましょう。間違えても解説が出ます。
このページの分析は、この画面の中でそのまま実行できます。
Python をインストールする必要も、CSV をダウンロードする必要もありません。
下のセルの 「▶ ブラウザで実行」 を上から順に押すか、
「▶ 最初から全部実行」 で一気に流してください。
表示されるのは、本文の図表とまったく同じ計算の結果です
(動かしているのは再現スクリプト code/2023_U5_4_shorei.py そのもの)。
コードは書き換えられます。「✏️ 書き換える」で数字や変数を変えて ▶ を押すと、 結果がどう変わるかをその場で確かめられます(元に戻すボタンつき)。 ページを開いた時点で裏で実行環境の準備が始まっているので、待ち時間はほとんどありません。 初回だけ実行環境の取得にインターネット接続が必要です。