このページの分析を自分で再現するには、以下の手順でデータを準備してください。コードの編集は不要です。
data/raw/ フォルダに入れます。html/figures/ に自動保存されます。
日本の合計特殊出生率(TFR)は2005年に過去最低の1.26を記録した後、わずかに回復したものの長期的な少子化傾向が続いている。政府は「待機児童ゼロ」を掲げた保育所整備と、「女性活躍推進法」による女性就業支援を少子化対策の柱として位置づけてきた。
まず「保育所整備と女性就業率が合計特殊出生率に与える影響のパネルデータ分析」を統計的にとらえることが有効だと考えられる。 その理由は感覚や経験則だけでは、複雑な社会要因の中で「何が本当に効いているか」を見極めにくいからである。 本研究では公開データと統計手法を組み合わせ、この問いに定量的な答えを出すことを目指す。
本研究は都道府県パネルデータを用いた固定効果モデルによって、この問いに対して実証的な回答を試みる。都道府県固有の不観測要因(文化・慣習・地理的条件など)をコントロールしたうえで、保育所整備・女性就業率・婚姻率などが出生率に与える純粋な影響を推定することが目的である。
パネルデータ分析 固定効果モデル Hausman検定 少子化政策 女性活躍
SSDSE(社会・人口統計体系データセット)-B を使用。47都道府県 × 11年度(2012〜2022年)の517観測値をパネルデータとして構築した。
| 変数の種類 | 変数名 | 定義・計算方法 | 出典(SSDSE-B列名) |
|---|---|---|---|
| 目的変数 | 合計特殊出生率(TFR) | 15〜49歳女性の年齢別出生率の合計 | 合計特殊出生率 |
| 説明変数 | 保育所数(千人当たり) | 保育所等数 ÷ 総人口 × 1000 | 保育所等数、総人口 |
| 女性就業活動率 | 就職件数(一般)÷ 15〜64歳女性人口 × 1000 (女性労働市場活動の代理指標) |
就職件数(一般)、15〜64歳人口(女) | |
| 婚姻率 | 婚姻件数 ÷ 総人口 × 1000 | 婚姻件数、総人口 | |
| 消費支出(対数) | log(消費支出:二人以上世帯) (所得水準の代理変数) |
消費支出(二人以上の世帯) |
| 項目 | 値 |
|---|---|
| 観測単位(エンティティ) | 47都道府県 |
| 時間次元 | 2012〜2022年度(11年間) |
| 総観測数 | 517(欠損除去後) |
| パネルの種類 | バランスパネル(各都道府県11年分) |
| 推定手法 | 固定効果モデル(クラスター標準誤差) |
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 os import numpy as np import pandas as pd import matplotlib matplotlib.use('Agg') import matplotlib.pyplot as plt 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) # 2012〜2022年度に絞る df_b = df_b[df_b['年度'].between(2012, 2022)].copy() df_b = df_b.reset_index(drop=True) |
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} のように書式も指定できます。25 26 27 28 29 30 31 32 | df_b['TFR'] = df_b['合計特殊出生率'].astype(float) # 保育所数(人口千人当たり) df_b['保育所千人当たり'] = df_b['保育所等数'] / df_b['総人口'] * 1000 # 女性就業率の代理変数:就職件数(一般)/ 15〜64歳女性人口 × 1000 # (労働市場での女性活動強度の代理指標) df_b['女性就業活動率'] = df_b['就職件数(一般)'] / df_b['15~64歳人口(女)'] * 1000 |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。df['A'] / df['B'] — pandasの列同士の四則演算は要素ごと(element-wise)。forループ不要なのが強み。33 34 35 36 37 38 39 40 | # 婚姻率(人口千人当たり) df_b['婚姻率'] = df_b['婚姻件数'] / df_b['総人口'] * 1000 # 消費支出(所得水準の代理変数):対数変換 df_b['log消費支出'] = np.log(df_b['消費支出(二人以上の世帯)'].astype(float)) # 高齢化率 df_b['高齢化率'] = df_b['65歳以上人口'] / df_b['総人口'] * 100 |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。.map() は「1対1の置き換え」、.apply() は「関数を当てる」。辞書なら .map()、ロジックなら .apply()。41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 | # 保育所等定員充足率(待機児童割合の逆) df_b['保育所定員率'] = df_b['保育所等定員数'] / (df_b['保育所等在所児数'] + 1) * 100 # 地域区分(6地域) region_map = { '北海道': '北海道東北', '青森': '北海道東北', '岩手': '北海道東北', '宮城': '北海道東北', '秋田': '北海道東北', '山形': '北海道東北', '福島': '北海道東北', '茨城': '関東', '栃木': '関東', '群馬': '関東', '埼玉': '関東', '千葉': '関東', '東京': '関東', '神奈川': '関東', '新潟': '中部', '富山': '中部', '石川': '中部', '福井': '中部', '山梨': '中部', '長野': '中部', '岐阜': '中部', '静岡': '中部', '愛知': '中部', '三重': '近畿', '滋賀': '近畿', '京都': '近畿', '大阪': '近畿', '兵庫': '近畿', '奈良': '近畿', '和歌山': '近畿', '鳥取': '中国四国', '島根': '中国四国', '岡山': '中国四国', '広島': '中国四国', '山口': '中国四国', '徳島': '中国四国', '香川': '中国四国', '愛媛': '中国四国', '高知': '中国四国', '福岡': '九州沖縄', '佐賀': '九州沖縄', '長崎': '九州沖縄', '熊本': '九州沖縄', '大分': '九州沖縄', '宮崎': '九州沖縄', '鹿児島': '九州沖縄', '沖縄': '九州沖縄', } df_b['地域'] = df_b['都道府県'].map(region_map).fillna('その他') region_colors = { '北海道東北': '#4e9af1', '関東': '#e05c5c', '中部': '#f0a500', '近畿': '#5cb85c', '中国四国': '#9b59b6', '九州沖縄': '#f39c12', } |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。[式 for x in リスト] はリスト内包表記。forループでappendする代わりに1行でリストを作れます。70 71 72 73 74 75 | # 分析対象カラムの欠損除去 analysis_cols = ['TFR', '保育所千人当たり', '女性就業活動率', '婚姻率', 'log消費支出'] df_b = df_b.dropna(subset=analysis_cols).copy() print(f"分析データ:{df_b['都道府県'].nunique()}都道府県 × {df_b['年度'].nunique()}年度 = {len(df_b)}観測値") print(f"年度範囲:{df_b['年度'].min()}〜{df_b['年度'].max()}") |
分析データ:47都道府県 × 11年度 = 517観測値 年度範囲:2012〜2022
r, p = stats.pearsonr(...) — Pythonは複数戻り値を同時に受け取れる(タプルアンパック)。
| 説明変数 | FE係数 | FE標準誤差 | FE p値 | OLS係数 | OLS p値 | 解釈 |
|---|---|---|---|---|---|---|
| 保育所数(千人当たり) | +0.151 | 0.169 | 0.372 | +1.015 | <0.001 | FEで効果が大幅に縮小・非有意 |
| 女性就業活動率 | +0.0009 | 0.0007 | 0.214 | +0.0028 | <0.001 | FEで効果が縮小・非有意 |
| 婚姻率 | +0.087 | 0.011 | <0.001 | +0.093 | <0.001 | 両モデルで有意・強い正効果 |
| 消費支出(対数) | −0.029 | 0.045 | 0.517 | −0.242 | <0.001 | FEで効果が大幅に縮小・非有意 |
77 78 79 80 81 82 83 84 85 86 | fig, ax = plt.subplots(figsize=(10, 6)) years = sorted(df_b['年度'].unique()) # 地域別平均 for region, color in region_colors.items(): region_data = df_b[df_b['地域'] == region].groupby('年度')['TFR'].mean() ax.plot(region_data.index, region_data.values, color=color, linewidth=2, marker='o', markersize=4, label=region, alpha=0.85) |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。df.groupby('列').apply(関数) — グループごとに関数を適用。時系列や地域別の集計でよく使います。fig, ax = plt.subplots(...) — 図全体(fig)と軸(ax)を作る定番。以降は ax.bar(...) 等で操作。.map() は「1対1の置き換え」、.apply() は「関数を当てる」。辞書なら .map()、ロジックなら .apply()。87 88 89 90 91 92 93 94 95 96 97 98 99 100 | # 全国平均 national_avg = df_b.groupby('年度')['TFR'].mean() ax.plot(national_avg.index, national_avg.values, color='#1a1a2e', linewidth=3, marker='D', markersize=6, label='全国平均', linestyle='--', zorder=5) ax.set_xlabel('年度', fontsize=12) ax.set_ylabel('合計特殊出生率', fontsize=12) ax.set_title('都道府県別・合計特殊出生率の時系列変化(2012〜2022年)', fontsize=13, fontweight='bold') ax.legend(loc='upper right', fontsize=10, framealpha=0.9) ax.set_xticks(years) ax.tick_params(axis='x', rotation=45) ax.grid(axis='y', alpha=0.3, linestyle='--') ax.set_ylim(1.0, 2.2) |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。df.groupby('列').apply(関数) — グループごとに関数を適用。時系列や地域別の集計でよく使います。[式 for x in リスト] はリスト内包表記。forループでappendする代わりに1行でリストを作れます。101 102 103 104 105 106 107 108 | # 注釈:2020年コロナ ax.axvline(x=2020, color='gray', linestyle=':', alpha=0.5, linewidth=1.5) ax.text(2020.1, 1.05, 'COVID-19', fontsize=9, color='gray') plt.tight_layout() fig.savefig(os.path.join(FIG_DIR, '2019_U4_fig1.png'), dpi=150, bbox_inches='tight') plt.close(fig) print("fig1 saved") |
fig1 saved
ax.axhline / ax.axvline — 水平/垂直の点線。平均線や基準線として定番。fig.savefig(..., bbox_inches='tight') — 余白を自動で詰めて保存。plt.close() でメモリ解放。r, p = stats.pearsonr(...) — Pythonは複数戻り値を同時に受け取れる(タプルアンパック)。パネルデータ(longitudinal data)は同一の観測単位(ここでは都道府県)を複数時点にわたって追跡したデータである。横断面データ(断面データ)と時系列データの特性を兼ね備えており、観測できない個体固有の特性をコントロールできる点が最大の利点。
固定効果モデルは「各都道府県の平均からの偏差」だけを使って回帰する(within推定)。都道府県ごとに時間が経つにつれて変数がどう変化したかを見るため、変化しない都道府県固有の特性(不観測ヘテロジェナイティ)が自動的に除去される。
パネルデータ分析では「固定効果モデル」と「変量効果モデル」のどちらを使うかを決める必要がある。Hausman検定はその選択基準を提供する。
| モデル | 仮定 | 利点 | 欠点 |
|---|---|---|---|
| 固定効果(FE) | αiと説明変数は相関してもよい | 内生性に頑健・一致推定量 | 時間不変変数の効果が推定不可・自由度損失 |
| 変量効果(RE) | αiは説明変数と無相関(強仮定) | 効率的・時間不変変数も推定可能 | 仮定が成立しないと非一致(バイアス) |
Hausman検定の帰無仮説は「変量効果モデルが一致推定量を与える」(=個体効果と説明変数が無相関)。帰無仮説が棄却される(p<0.05)場合は固定効果モデルを使う。
パネルデータでは同一都道府県の観測値間に系列相関(serial correlation)が存在することが多い。通常のOLS標準誤差はこれを無視するため過小推定となり、t検定の第一種の過誤が膨らむ。都道府県単位のクラスター標準誤差によってこの問題を修正する。
110 111 112 113 114 115 116 117 | df_2022 = df_b[df_b['年度'] == 2022].copy() fig, ax = plt.subplots(figsize=(9, 7)) for region, color in region_colors.items(): sub = df_2022[df_2022['地域'] == region] ax.scatter(sub['女性就業活動率'], sub['TFR'], color=color, s=70, alpha=0.85, label=region, zorder=3, edgecolors='white', linewidths=0.5) |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。fig, ax = plt.subplots(...) — 図全体(fig)と軸(ax)を作る定番。以降は ax.bar(...) 等で操作。[式 for x in リスト] はリスト内包表記。forループでappendする代わりに1行でリストを作れます。118 119 120 121 122 123 124 125 126 127 | # 回帰直線 x_arr = df_2022['女性就業活動率'].values y_arr = df_2022['TFR'].values mask = np.isfinite(x_arr) & np.isfinite(y_arr) x_arr, y_arr = x_arr[mask], y_arr[mask] slope, intercept, r_value, p_value, std_err = stats.linregress(x_arr, y_arr) x_line = np.linspace(x_arr.min(), x_arr.max(), 100) y_line = slope * x_line + intercept ax.plot(x_line, y_line, color='#333333', linewidth=2, linestyle='--', zorder=4) |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。stats.linregress(x, y) — 単回帰の傾き・切片・r値・p値・標準誤差を返します。使わない値は _ で受け取り。r, p = stats.pearsonr(...) — Pythonは複数戻り値を同時に受け取れる(タプルアンパック)。128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 | # 相関係数・p値 ax.text(0.05, 0.95, f'r = {r_value:.3f}\np = {p_value:.4f}', transform=ax.transAxes, fontsize=11, verticalalignment='top', bbox=dict(boxstyle='round,pad=0.5', facecolor='white', edgecolor='#cccccc', alpha=0.9)) # 都道府県ラベル(上位・下位) label_prefs = ['沖縄', '東京', '秋田', '北海道', '宮崎', '島根', '愛知', '大阪'] for _, row in df_2022[df_2022['都道府県'].isin(label_prefs)].iterrows(): if np.isfinite(row['女性就業活動率']) and np.isfinite(row['TFR']): ax.annotate(row['都道府県'], (row['女性就業活動率'], row['TFR']), xytext=(4, 4), textcoords='offset points', fontsize=8.5, color='#333333') ax.set_xlabel('女性就業活動率(就職件数/15〜64歳女性人口×1000)', fontsize=11) ax.set_ylabel('合計特殊出生率(2022年)', fontsize=11) ax.set_title('女性就業活動率と合計特殊出生率の関係(2022年・都道府県別)', fontsize=12, fontweight='bold') ax.legend(loc='lower right', fontsize=9, framealpha=0.9) ax.grid(alpha=0.3, linestyle='--') plt.tight_layout() fig.savefig(os.path.join(FIG_DIR, '2019_U4_fig2.png'), dpi=150, bbox_inches='tight') plt.close(fig) print("fig2 saved") |
fig2 saved
for _, row in df.iterrows() — DataFrameを1行ずつ取り出すループ。1点ずつ描画したいときに使用。fig.savefig(..., bbox_inches='tight') — 余白を自動で詰めて保存。plt.close() でメモリ解放。x if cond else y は三項演算子。リスト内包表記と組み合わせると、forとifを1行で書けます。固定効果モデルでも解決できない問題がある。保育所整備と出生率の間には逆因果(出生率が高い→保育需要増加→保育所整備)と見かけ上の相関(第3の変数が両方に影響)が残る可能性がある。より厳密な因果推定には操作変数法(IV法)や差分の差分法(DID法)が有効である。
DS LEARNING POINT 3保育所整備政策の因果効果を推定するには、「政策実施前後の変化」を「政策の影響を受けなかった地域」と比較するDID推定が有効。例えば保育所の急増した都道府県を処置群、そうでない都道府県を対照群とする。
都道府県固定効果に加えて年度固定効果を追加する「双方向固定効果モデル(Two-way FE)」も重要な拡張である。景気後退・少子化対策の全国一律変化・COVID-19のような時間的ショックをコントロールできる。
DS LEARNING POINT 4都道府県固定効果(αi)と年度固定効果(γt)の両方を含めることで、観測できない都道府県特性と年度特有のショックを同時にコントロールできる。
本分析は全国47都道府県を一括して推定しているが、保育所整備の効果は地域によって異なる可能性がある(空間的異質性)。例えば、都市部では保育所整備が就業促進につながりやすいが、農村部では元々就業率が高く異なるメカニズムが働く可能性がある。サブグループ分析や交差項の導入が発展的な分析手法として考えられる。
153 154 155 156 157 158 | fig, ax = plt.subplots(figsize=(9, 7)) for region, color in region_colors.items(): sub = df_2022[df_2022['地域'] == region] ax.scatter(sub['保育所千人当たり'], sub['TFR'], color=color, s=70, alpha=0.85, label=region, zorder=3, edgecolors='white', linewidths=0.5) |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。fig, ax = plt.subplots(...) — 図全体(fig)と軸(ax)を作る定番。以降は ax.bar(...) 等で操作。r, p = stats.pearsonr(...) — Pythonは複数戻り値を同時に受け取れる(タプルアンパック)。159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 | # 回帰直線 x_arr2 = df_2022['保育所千人当たり'].values y_arr2 = df_2022['TFR'].values mask2 = np.isfinite(x_arr2) & np.isfinite(y_arr2) x2, y2 = x_arr2[mask2], y_arr2[mask2] slope2, intercept2, r2, p2, se2 = stats.linregress(x2, y2) x_line2 = np.linspace(x2.min(), x2.max(), 100) y_line2 = slope2 * x_line2 + intercept2 ax.plot(x_line2, y_line2, color='#333333', linewidth=2, linestyle='--', zorder=4) ax.text(0.05, 0.95, f'r = {r2:.3f}\np = {p2:.4f}', transform=ax.transAxes, fontsize=11, verticalalignment='top', bbox=dict(boxstyle='round,pad=0.5', facecolor='white', edgecolor='#cccccc', alpha=0.9)) |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。stats.linregress(x, y) — 単回帰の傾き・切片・r値・p値・標準誤差を返します。使わない値は _ で受け取り。x if cond else y は三項演算子。リスト内包表記と組み合わせると、forとifを1行で書けます。174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 | # 都道府県ラベル label_prefs2 = ['沖縄', '東京', '秋田', '福井', '長崎', '鳥取', '大阪', '神奈川'] for _, row in df_2022[df_2022['都道府県'].isin(label_prefs2)].iterrows(): if np.isfinite(row['保育所千人当たり']) and np.isfinite(row['TFR']): ax.annotate(row['都道府県'], (row['保育所千人当たり'], row['TFR']), xytext=(4, 4), textcoords='offset points', fontsize=8.5, color='#333333') ax.set_xlabel('保育所等数(人口千人当たり)', fontsize=11) ax.set_ylabel('合計特殊出生率(2022年)', fontsize=11) ax.set_title('保育所数(人口千人当たり)と合計特殊出生率の関係(2022年)', fontsize=12, fontweight='bold') ax.legend(loc='lower right', fontsize=9, framealpha=0.9) ax.grid(alpha=0.3, linestyle='--') plt.tight_layout() fig.savefig(os.path.join(FIG_DIR, '2019_U4_fig3.png'), dpi=150, bbox_inches='tight') plt.close(fig) print("fig3 saved") |
fig3 saved
for _, row in df.iterrows() — DataFrameを1行ずつ取り出すループ。1点ずつ描画したいときに使用。fig.savefig(..., bbox_inches='tight') — 余白を自動で詰めて保存。plt.close() でメモリ解放。df[col](1列)と df[[col1, col2]](複数列)でカッコの数が違います。リストを渡していると覚えるとミスを減らせます。47都道府県 × 2012〜2022年のパネルデータを用いた固定効果モデルの推定から、以下の知見が得られた:
| 著者・年 | タイトル | 掲載誌・出版社 |
|---|---|---|
| Hausman (1978) | Specification Tests in Econometrics | Econometrica, 46(6) |
| Arroyo Abad & Lindert (2017) | Fiscal redistribution and human capital since the 18th century | Historical panel data |
| 内閣府 (2019) | 少子化社会対策白書 | 内閣府 |
| 厚生労働省(各年) | 保育所等関連状況取りまとめ | 厚生労働省 |
| 山口慎太郎 (2016) | 「家族の幸せ」の経済学 | 光文社新書 |
| データ | 出典 | 対象年度 |
|---|---|---|
| SSDSE-B-2026(都道府県データ) | 統計数理研究所 SSDSE | 2012〜2022年 |
| 合計特殊出生率 | 厚生労働省 人口動態統計 | 2012〜2022年 |
| 保育所等数・定員・在所児数 | 厚生労働省 保育所等関連状況取りまとめ | 2012〜2022年 |
| 就職件数(一般) | 厚生労働省 職業安定業務統計 | 2012〜2022年 |
| 婚姻件数・人口データ | 厚生労働省 人口動態統計/総務省 住民基本台帳 | 2012〜2022年 |
本コードはSSDSE-B-2026の実データを使用した教育用再現コードです。すべての図表は実公的統計データから生成されています。
統計分析の解釈で初心者がやりがちな勘違いをまとめます。特に「相関と因果の混同」「p値の過信」は研究現場でもよく起きる落とし穴です。本文を読む前にも、読んだ後にも、目を通してみてください。
統計の基本用語を初心者向けに解説します。本文中で見慣れない言葉が出てきたら、ここに戻って確認してください。
統計手法について「何のためか」「結果をどう読むか」を初心者向けに解説します。
この研究をさらに発展させるための3つの方向性を示します。「今回わかったこと(X)」から「次に検証すべき仮説(Y)」を立て、「具体的に何をするか(Z)」まで考えてみましょう。
学んだだけでは身につきません。実際に手を動かすのが最強の学習方法です。本論文のスクリプトをベースに、以下のチャレンジに挑戦してみてください。難易度別に5つ用意しました。
本論文で学んだ手法は、研究の世界だけでなく、行政・企業・NPO の現場でも様々に活用されています。具体的なシーンを紹介します。
この論文を読んで初心者が抱きやすい疑問に、教育的観点から答えます。