このページの分析を自分で再現するには、以下の手順でデータを準備してください。コードの編集は不要です。
data/raw/ フォルダに入れます。html/figures/ に自動保存されます。
日本の合計特殊出生率(TFR)は長期にわたって低下を続けており、2022年に初めて1.26を記録した。少子化の要因として、女性の就業拡大と出生率の関係は古くから議論されてきた。かつて「女性就業率↑ → 出生率↓」という負の相関が通説であったが、1990年代以降の北欧諸国のデータは逆の関係(就業率が高い国ほどTFRも高い)を示し、「Nordic paradox」として注目される。
まず「女性就労と少子化女性就業率・育児環境と合計特殊出生率の関係」を統計的にとらえることが有効だと考えられる。 その理由は感覚や経験則だけでは、複雑な社会要因の中で「何が本当に効いているか」を見極めにくいからである。 本研究では公開データと統計手法を組み合わせ、この問いに定量的な答えを出すことを目指す。
SSDSE-B 相関分析 OLS回帰 時系列分析 散布図
統計数理研究所が公表する SSDSE-B は47都道府県の各種統計を年度ごとに収録したパネルデータである。本分析では2012〜2023年(12年分、564観測)を使用し、主な分析は2022年断面(N=47)で行う。
| 変数名 | 定義式 | SSDSE-Bコード | 単位 |
|---|---|---|---|
| TFR(目的変数) | 合計特殊出生率 | A4103 | – |
| 女性就業率_代理 | 15〜64歳人口(女)÷ 15〜64歳人口 × 100 | A130202 / A1302 | % |
| 保育所密度 | 保育所等数 ÷ 総人口 × 10,000 | J2503 / A1101 | 所/万人 |
| 高齢化率 | 65歳以上人口 ÷ 総人口 × 100 | A1303 / A1101 | % |
| 婚姻率 | 婚姻件数 ÷ 総人口 × 1,000 | A9101 / A1101 | 件/千人 |
| 消費支出_log | log(消費支出(二人以上の世帯)) | log(L3221) | – |
| 変数 | 平均 | 標準偏差 | 最小 | 最大 |
|---|---|---|---|---|
| TFR | 1.358 | 0.149 | 1.040 | 1.700 |
| 女性就業率代理(%) | 49.46 | 0.98 | 47.80 | 51.57 |
| 保育所密度(所/万人) | 2.758 | 0.719 | 1.708 | 4.514 |
| 高齢化率(%) | 31.35 | 3.27 | 22.81 | 38.60 |
| 婚姻率(件/千人) | 3.676 | 0.465 | 2.631 | 5.355 |
出典:SSDSE-B-2026(社会・人口統計体系,統計センター公表実データ)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | df_raw = pd.read_csv(DATA_B, encoding='cp932', header=0) # 行0はコード行(英字)、行1はラベル行(日本語)、行2以降がデータ data = df_raw.iloc[1:].copy() data.columns = df_raw.columns # 列名マッピング(コード → 意味) # SSDSE-B-2026 = 年度 # Code = 地域コード # Prefecture = 都道府県 # A4103 = 合計特殊出生率 # A1302 = 15〜64歳人口(総数) # A130202 = 15〜64歳人口(女) # A1101 = 総人口 # A1303 = 65歳以上人口 # A9101 = 婚姻件数 # J2503 = 保育所等数 # L3221 = 消費支出(二人以上の世帯) num_cols = ['A4103', 'A1302', 'A130202', 'A1101', 'A1303', 'A9101', 'J2503', 'L3221'] for c in num_cols: data[c] = pd.to_numeric(data[c], errors='coerce') data['SSDSE-B-2026'] = pd.to_numeric(data['SSDSE-B-2026'], errors='coerce') |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。pd.read_csv(...) でCSVを読み込みます。encoding='cp932' は日本語Windows由来の文字コード、header=1 は「2行目を列名として使う」。df['A'] / df['B'] — pandasの列同士の四則演算は要素ごと(element-wise)。forループ不要なのが強み。23 24 25 26 27 28 29 30 | # 年度・都道府県・国全体コードを整理 data = data[data['Code'] != 'Z00000'].copy() # 全国計除外 data = data.dropna(subset=num_cols + ['SSDSE-B-2026']).copy() data = data[(data['SSDSE-B-2026'] >= 2012) & (data['SSDSE-B-2026'] <= 2023)].copy() print("=" * 60) print(f"■ 読み込み完了: {len(data)}件({data['SSDSE-B-2026'].nunique()}年 × {data['Prefecture'].nunique()}都道府県)") print("=" * 60) |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。.map() は「1対1の置き換え」、.apply() は「関数を当てる」。辞書なら .map()、ロジックなら .apply()。31 32 33 34 35 36 37 38 39 40 41 42 | # ── 特徴量エンジニアリング ──────────────────────────────────────── data['TFR'] = data['A4103'].astype(float) data['女性就業率_代理'] = data['A130202'] / data['A1302'] * 100 # 15-64歳女性/15-64歳総人口 data['保育所密度'] = data['J2503'] / data['A1101'] * 10000 # 保育所等数/総人口×10000 data['高齢化率'] = data['A1303'] / data['A1101'] * 100 # 65歳以上/総人口×100 data['婚姻率'] = data['A9101'] / data['A1101'] * 1000 # 婚姻件数/総人口×1000 data['消費支出_log'] = np.log(data['L3221'].clip(lower=1)) # log(消費支出) data['年度'] = data['SSDSE-B-2026'].astype(int) data['都道府県'] = data['Prefecture'] vars_stat = ['TFR', '女性就業率_代理', '保育所密度', '高齢化率', '婚姻率', '消費支出_log'] |
============================================================ ■ 読み込み完了: 564件(12年 × 47都道府県) ============================================================
.astype(int) — 列を整数に変換(年度などを数値比較するため)。[式 for x in リスト] はリスト内包表記。forループでappendする代わりに1行でリストを作れます。43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 | region_map = { '北海道': '北海道・東北', '青森県': '北海道・東北', '岩手県': '北海道・東北', '宮城県': '北海道・東北', '秋田県': '北海道・東北', '山形県': '北海道・東北', '福島県': '北海道・東北', '茨城県': '関東甲信越', '栃木県': '関東甲信越', '群馬県': '関東甲信越', '埼玉県': '関東甲信越', '千葉県': '関東甲信越', '東京都': '関東甲信越', '神奈川県': '関東甲信越', '新潟県': '関東甲信越', '山梨県': '関東甲信越', '長野県': '関東甲信越', '富山県': '東海・北陸', '石川県': '東海・北陸', '福井県': '東海・北陸', '静岡県': '東海・北陸', '愛知県': '東海・北陸', '三重県': '東海・北陸', '岐阜県': '東海・北陸', '滋賀県': '近畿', '京都府': '近畿', '大阪府': '近畿', '兵庫県': '近畿', '奈良県': '近畿', '和歌山県': '近畿', '鳥取県': '中国・四国', '島根県': '中国・四国', '岡山県': '中国・四国', '広島県': '中国・四国', '山口県': '中国・四国', '徳島県': '中国・四国', '香川県': '中国・四国', '愛媛県': '中国・四国', '高知県': '中国・四国', '福岡県': '九州・沖縄', '佐賀県': '九州・沖縄', '長崎県': '九州・沖縄', '熊本県': '九州・沖縄', '大分県': '九州・沖縄', '宮崎県': '九州・沖縄', '鹿児島県': '九州・沖縄', '沖縄県': '九州・沖縄', } data['地域'] = data['都道府県'].map(region_map).fillna('その他') region_order = ['北海道・東北', '関東甲信越', '東海・北陸', '近畿', '中国・四国', '九州・沖縄'] region_colors = ['#1565C0', '#C62828', '#2E7D32', '#E65100', '#6A1B9A', '#00695C'] |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。.map() は「1対1の置き換え」、.apply() は「関数を当てる」。辞書なら .map()、ロジックなら .apply()。67 68 69 70 71 | # 2022年断面データ(地域列を含む) df22 = data[data['年度'] == 2022].copy() print("\n【変数の記述統計(2022年断面)】") print(df22[vars_stat].describe().round(3)) |
【変数の記述統計(2022年断面)】
TFR 女性就業率_代理 保育所密度 高齢化率 婚姻率 消費支出_log
count 47.000 47.000 47.000 47.000 47.000 47.000
mean 1.358 49.458 2.758 31.350 3.676 12.574
std 0.149 0.980 0.719 3.269 0.465 0.067
min 1.040 47.795 1.708 22.810 2.631 12.409
25% 1.245 48.678 2.161 29.847 3.433 12.531
50% 1.360 49.380 2.449 31.421 3.642 12.570
75% 1.455 50.258 3.297 33.720 3.814 12.619
max 1.700 51.565 4.514 38.602 5.355 12.691.describe() — 件数・平均・標準偏差・四分位・最大/最小を一括計算。データの素性チェックに必須。[式 for x in リスト] はリスト内包表記。forループでappendする代わりに1行でリストを作れます。まず合計特殊出生率が2012〜2023年にかけてどのように変化したかを地域別に確認する。SSDSE-Bの47都道府県を6地域に区分し、各地域の平均TFRを折れ線グラフで示す。
「女性が働くと子どもを産まなくなる」というかつての通説は、1990年代以降のデータで覆されつつある。北欧(デンマーク・スウェーデン等)では就業率が高くTFRも高い。これを「Nordic paradox(北欧逆説)」と呼ぶ。
逆U字仮説の論理:就業率が低い段階では就業↑→TFR↓(機会費用の増大)だが、保育・育児支援が整備されると就業↑→TFR↑(経済的安定の効果が上回る)に転じるという考え方。
73 74 75 76 77 78 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 | print("\n図1を生成中 ...") fig1, ax1 = plt.subplots(figsize=(10, 5.5)) tfr_region = ( data[data['地域'].isin(region_order)] .groupby(['年度', '地域'])['TFR'] .mean() .reset_index() ) for reg, col in zip(region_order, region_colors): subset = tfr_region[tfr_region['地域'] == reg].sort_values('年度') ax1.plot(subset['年度'], subset['TFR'], marker='o', markersize=4, linewidth=1.8, color=col, label=reg, alpha=0.9) ax1.set_xlabel('年度', fontsize=12) ax1.set_ylabel('合計特殊出生率(TFR)', fontsize=12) ax1.set_title('合計特殊出生率の時系列推移(地域別平均)\nSSDSE-B 2012〜2023年 実データ', fontsize=13, fontweight='bold') ax1.legend(fontsize=9, loc='upper right', ncol=2) ax1.set_xlim(2011.5, 2023.5) ax1.set_xticks(range(2012, 2024)) ax1.xaxis.set_tick_params(rotation=45) ax1.grid(True, alpha=0.3) ax1.axvline(2020, color='gray', linestyle=':', linewidth=1.2, alpha=0.6) ax1.text(2020.1, ax1.get_ylim()[0] + 0.03, 'COVID-19', fontsize=8, color='gray') plt.tight_layout() fig1_path = os.path.join(FIG_DIR, '2022_H5_10_fig1_timeseries.png') fig1.savefig(fig1_path, bbox_inches='tight', dpi=150) plt.close(fig1) print(f"図1保存: {fig1_path}") |
図1を生成中 ... 図1保存: html/figures/2022_H5_10_fig1_timeseries.png
df.groupby('列').apply(関数) — グループごとに関数を適用。時系列や地域別の集計でよく使います。fig, ax = plt.subplots(...) — 図全体(fig)と軸(ax)を作る定番。以降は ax.bar(...) 等で操作。sort_values('列名', ascending=False) — 指定列で並べ替え(降順)。ax.axhline / ax.axvline — 水平/垂直の点線。平均線や基準線として定番。r, p = stats.pearsonr(...) — Pythonは複数戻り値を同時に受け取れる(タプルアンパック)。2022年の47都道府県データを用いて、女性就業率代理変数とTFRの散布図を作成する。各点は1都道府県を表し、地域ごとに色分けした。
本研究で用いる「女性就業率代理」は人口構成比(15〜64歳女性の占める割合)であり、就業の有無は直接捉えていない。より正確な女性就業率は就業者数/15〜64歳女性人口であるが、SSDSE-Bには都道府県別就業者数の直接データが限られるため代理変数を使用している。この点を念頭に置いて結果を解釈する必要がある。
相関係数 r は −1 から +1 の範囲をとる。r > 0 は正の相関(一方が増えると他方も増える傾向)、r < 0 は負の相関を意味する。絶対値の大きさが関係の強さを示す。
Cohen(1988)の慣習的基準:|r| < 0.1(ほぼなし)、|r| ≈ 0.3(小)、|r| ≈ 0.5(中)、|r| ≈ 0.7(大)。
107 108 109 110 111 112 113 114 115 | print("図2を生成中 ...") fig2, ax2 = plt.subplots(figsize=(11, 7)) scatter_colors = [region_colors[region_order.index(reg)] if reg in region_order else '#888888' for reg in df22_clean['地域']] ax2.scatter(df22_clean['女性就業率_代理'], df22_clean['TFR'], c=scatter_colors, s=60, alpha=0.8, edgecolors='white', linewidths=0.5, zorder=3) |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。fig, ax = plt.subplots(...) — 図全体(fig)と軸(ax)を作る定番。以降は ax.bar(...) 等で操作。x if cond else y は三項演算子。リスト内包表記と組み合わせると、forとifを1行で書けます。116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 | # 都道府県ラベル(略称) label_map = { '北海道': '北海', '青森県': '青森', '岩手県': '岩手', '宮城県': '宮城', '秋田県': '秋田', '山形県': '山形', '福島県': '福島', '茨城県': '茨城', '栃木県': '栃木', '群馬県': '群馬', '埼玉県': '埼玉', '千葉県': '千葉', '東京都': '東京', '神奈川県': '神奈', '新潟県': '新潟', '富山県': '富山', '石川県': '石川', '福井県': '福井', '山梨県': '山梨', '長野県': '長野', '岐阜県': '岐阜', '静岡県': '静岡', '愛知県': '愛知', '三重県': '三重', '滋賀県': '滋賀', '京都府': '京都', '大阪府': '大阪', '兵庫県': '兵庫', '奈良県': '奈良', '和歌山県': '和歌', '鳥取県': '鳥取', '島根県': '島根', '岡山県': '岡山', '広島県': '広島', '山口県': '山口', '徳島県': '徳島', '香川県': '香川', '愛媛県': '愛媛', '高知県': '高知', '福岡県': '福岡', '佐賀県': '佐賀', '長崎県': '長崎', '熊本県': '熊本', '大分県': '大分', '宮崎県': '宮崎', '鹿児島県': '鹿児', '沖縄県': '沖縄', } for _, row in df22_clean.iterrows(): short = label_map.get(row['都道府県'], row['都道府県'][:2]) ax2.annotate(short, xy=(row['女性就業率_代理'], row['TFR']), xytext=(3, 3), textcoords='offset points', fontsize=6.5, alpha=0.85) |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。for _, row in df.iterrows() — DataFrameを1行ずつ取り出すループ。1点ずつ描画したいときに使用。df[col](1列)と df[[col1, col2]](複数列)でカッコの数が違います。リストを渡していると覚えるとミスを減らせます。135 136 137 138 139 140 141 142 | # 回帰直線 x_emp = df22_clean['女性就業率_代理'].astype(float).values y_tfr = df22_clean['TFR'].astype(float).values r2, p2 = stats.pearsonr(x_emp, y_tfr) z2 = np.polyfit(x_emp, y_tfr, 1) xs2 = np.linspace(x_emp.min(), x_emp.max(), 100) ax2.plot(xs2, np.poly1d(z2)(xs2), color='#C62828', linewidth=1.5, linestyle='--', alpha=0.8, label=f'回帰直線 r={r2:.3f} (p={p2:.3f})') |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。stats.pearsonr(x, y) — Pearson相関係数 r と p値を同時に返します。s[:-n]「末尾n文字を除く」/s[n:]「先頭n文字を除く」。スライス [start:stop:step] はリスト・タプル・文字列共通の基本ワザです。143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 | # 凡例(地域別) from matplotlib.lines import Line2D handles = [Line2D([0], [0], marker='o', color='w', markerfacecolor=col, markersize=8, label=reg) for reg, col in zip(region_order, region_colors)] handles.append(Line2D([0], [0], color='#C62828', linestyle='--', linewidth=1.5, label='回帰直線')) ax2.legend(handles=handles, fontsize=8, loc='upper left') ax2.set_xlabel('女性就業率代理(15〜64歳女性/15〜64歳総人口 ×100, %)', fontsize=11) ax2.set_ylabel('合計特殊出生率(TFR)', fontsize=11) ax2.set_title('女性就業率代理 vs 合計特殊出生率(2022年, 47都道府県)\nSSDSE-B 実データ', fontsize=12, fontweight='bold') ax2.grid(True, alpha=0.3) plt.tight_layout() fig2_path = os.path.join(FIG_DIR, '2022_H5_10_fig2_scatter.png') fig2.savefig(fig2_path, bbox_inches='tight', dpi=150) plt.close(fig2) print(f"図2保存: {fig2_path}") |
図2を生成中 ... 図2保存: html/figures/2022_H5_10_fig2_scatter.png
import pandas as pd など — 必要なライブラリをまとめて呼び出します。as pd は短い別名(alias)。np.cumsum(arr) は累積和、np.linspace(a, b, n) は「aからbを等間隔でn個」。NumPyの定石です。TFRを目的変数、5つの説明変数(女性就業率代理・保育所密度・高齢化率・婚姻率・消費支出_log)を投入してOLS重回帰分析を行う。2022年の47都道府県断面データを使用する。
| 変数 | 回帰係数 | 標準誤差 | t値 | p値 | 有意性 |
|---|---|---|---|---|---|
| 女性就業率代理 | 0.0209 | 0.0213 | 0.98 | 0.333 | n.s. |
| 保育所密度 | 0.1278 | 0.0287 | 4.46 | < 0.001 | *** |
| 高齢化率 | 0.0097 | 0.0134 | 0.73 | 0.471 | n.s. |
| 婚姻率 | 0.0764 | 0.0903 | 0.85 | 0.403 | n.s. |
| 消費支出_log | 0.0653 | 0.3305 | 0.20 | 0.844 | n.s. |
R² = 0.432, 調整済R² = 0.363, F(5,41) = 6.25, p < 0.001。*** p < 0.001, ** p < 0.01, * p < 0.05。
交絡因子(confounder)とは、説明変数と目的変数の両方に影響を与える第三の変数のこと。女性就業率とTFRの関係を分析する際、婚姻率・高齢化率を制御しないと見かけの相関が生じる可能性がある。
例:若い人口が多い都道府県は(1)婚姻率が高く、(2)TFRも高く、(3)女性就業率も高い傾向がある。婚姻率・年齢構成を制御しないと、就業率→TFRの見かけの正の相関が生じうる。
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 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 | print("図3を生成中 ...") coef_names = ['女性就業率_代理', '保育所密度', '高齢化率', '婚姻率', '消費支出_log'] coef_labels = [ '女性就業率代理\n(15-64歳女性比率)', '保育所密度\n(保育所数/万人)', '高齢化率\n(65歳以上/総人口)', '婚姻率\n(婚姻件数/千人)', '消費支出(log)\n(二人以上世帯)', ] coefs3 = np.asarray(ols_model.params[1:]) # 定数項を除く ses3 = np.asarray(ols_model.bse[1:]) pvals3 = np.asarray(ols_model.pvalues[1:]) bar_colors3 = [] for p in pvals3: if p < 0.01: bar_colors3.append('#C62828') # 赤: p<0.01 elif p < 0.05: bar_colors3.append('#FF8F00') # オレンジ: p<0.05 elif p < 0.1: bar_colors3.append('#1565C0') # 青: p<0.10 else: bar_colors3.append('#9E9E9E') # 灰: n.s. fig3, ax3 = plt.subplots(figsize=(9, 5)) y_pos3 = np.arange(len(coef_names)) bars3 = ax3.barh(y_pos3, coefs3, color=bar_colors3, alpha=0.80, edgecolor='white', height=0.6) ax3.errorbar(coefs3, y_pos3, xerr=1.96 * ses3, fmt='none', color='#333333', capsize=4, linewidth=1.2, zorder=5) ax3.axvline(0, color='gray', linestyle='--', linewidth=1.0) ax3.set_yticks(y_pos3) ax3.set_yticklabels(coef_labels, fontsize=10) ax3.set_xlabel('標準化なし回帰係数(±1.96 SE)', fontsize=11) ax3.set_title(f'OLS回帰係数プロット(目的変数: TFR)\nR²={ols_model.rsquared:.3f}, N=47都道府県(2022年)', fontsize=12, fontweight='bold') ax3.invert_yaxis() ax3.grid(axis='x', alpha=0.3) from matplotlib.patches import Patch legend_elements = [ Patch(facecolor='#C62828', alpha=0.80, label='p < 0.01'), Patch(facecolor='#FF8F00', alpha=0.80, label='p < 0.05'), Patch(facecolor='#1565C0', alpha=0.80, label='p < 0.10'), Patch(facecolor='#9E9E9E', alpha=0.80, label='n.s.'), ] ax3.legend(handles=legend_elements, fontsize=9, loc='lower right') |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。import pandas as pd など — 必要なライブラリをまとめて呼び出します。as pd は短い別名(alias)。fig, ax = plt.subplots(...) — 図全体(fig)と軸(ax)を作る定番。以降は ax.bar(...) 等で操作。ax.axhline / ax.axvline — 水平/垂直の点線。平均線や基準線として定番。df[col](1列)と df[[col1, col2]](複数列)でカッコの数が違います。リストを渡していると覚えるとミスを減らせます。212 213 214 215 216 217 218 219 220 221 222 223 | # p値注記 for i, (coef, se, pval) in enumerate(zip(coefs3, ses3, pvals3)): sig = '***' if pval < 0.001 else '**' if pval < 0.01 else '*' if pval < 0.05 else '†' if pval < 0.10 else '' offset = 0.003 if coef >= 0 else -0.003 ax3.text(coef + 1.96 * se + offset, i, f' {sig}', va='center', ha='left', fontsize=10, color='black') plt.tight_layout() fig3_path = os.path.join(FIG_DIR, '2022_H5_10_fig3_coef.png') fig3.savefig(fig3_path, bbox_inches='tight', dpi=150) plt.close(fig3) print(f"図3保存: {fig3_path}") |
図3を生成中 ... 図3保存: html/figures/2022_H5_10_fig3_coef.png
s[:-n]「末尾n文字を除く」/s[n:]「先頭n文字を除く」。スライス [start:stop:step] はリスト・タプル・文字列共通の基本ワザです。TFR・女性就業率代理・保育所密度・婚姻率・高齢化率の5変数間の相関行列をヒートマップで可視化する。各セルの値はピアソン相関係数、アスタリスクは有意性を示す。
| 変数ペア | 相関係数 r | 有意性 | 解釈 |
|---|---|---|---|
| TFR ↔ 保育所密度 | +0.628 | *** | 保育インフラ充実 → 出生率高 |
| TFR ↔ 女性就業率代理 | +0.242 | n.s. | 弱い正の相関(非有意) |
| TFR ↔ 高齢化率 | +0.227 | n.s. | 高齢化と出生率は独立に変動 |
| TFR ↔ 婚姻率 | −0.108 | n.s. | 弱い負相関(非有意) |
| 婚姻率 ↔ 高齢化率 | −0.890 | *** | 強い負相関:多重共線性の懸念 |
| 保育所密度 ↔ 高齢化率 | +0.334 | * | 高齢化地域で保育所が多い(農村型) |
相関・回帰分析は「関連性」を示すが、「因果関係」の証明には「もし保育所を整備しなかったら(カウンターファクチュアル)」を考える必要がある。観察データのみでは逆の因果性(TFRが高い地域だから保育所が増えた)や交絡も考えられる。
政策効果の因果推論には、差の差(DID)分析・自然実験・操作変数法などの手法が有効。例えば、ある年に保育所整備が急拡大した都道府県と近隣の未整備都道府県のTFR変化を比較する。
225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 | 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 import warnings warnings.filterwarnings('ignore') plt.rcParams['font.family'] = 'Hiragino Sans' plt.rcParams['axes.unicode_minus'] = False plt.rcParams['figure.dpi'] = 150 import os FIG_DIR = 'html/figures' DATA_B = 'data/raw/SSDSE-B-2026.csv' os.makedirs(FIG_DIR, exist_ok=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)。f"...{x}..." はf-string。文字列の中に {変数} と書くだけで埋め込めて、{x:.2f} のように書式も指定できます。243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 | print("\n" + "=" * 60) print("■ OLS回帰(2022年断面, N=47都道府県)") print("=" * 60) df22_clean = df22.dropna(subset=vars_stat).copy() X_vars = ['女性就業率_代理', '保育所密度', '高齢化率', '婚姻率', '消費支出_log'] X_ols = sm.add_constant(df22_clean[X_vars].astype(float)) y_ols = df22_clean['TFR'].astype(float) ols_model = sm.OLS(y_ols, X_ols).fit() print(ols_model.summary2()) # 相関行列(2022年) print("\n【相関行列(2022年, N=47)】") corr_vars = ['TFR', '女性就業率_代理', '保育所密度', '婚姻率', '高齢化率'] corr_df = df22_clean[corr_vars].astype(float).corr() print(corr_df.round(3)) |
============================================================
■ OLS回帰(2022年断面, N=47都道府県)
============================================================
Results: Ordinary least squares
=================================================================
Model: OLS Adj. R-squared: 0.363
Dependent Variable: TFR AIC: -60.9772
Date: 2026-05-18 11:24 BIC: -49.8763
No. Observations: 47 Log-Likelihood: 36.489
Df Model: 5 F-statistic: 6.247
Df Residuals: 41 Prob (F-statistic): 0.000215
R-squared: 0.432 Scale: 0.014207
------------------------------------------------------------------
Coef. Std.Err. t P>|t| [0.025 0.975]
------------------------------------------------------------------
const -1.4355 4.6697 -0.3074 0.7601 -10.8662 7.9952
女性就業率_代理 0.0209 0.0213 0.9807 0.3325 -0.0221 0.0639
保育所密度 0.1278 0.0287 4.4573 0.0001 0.0699 0.1857
高齢化率 0.0097 0.0134 0.7281 0.4707 -0.0173 0.0368
婚姻率 0.0764 0.0903 0.8454 0.4028 -0.1061 0.2588
消費支出_log 0.0653 0.3305 0.1977 0.8443 -0.6021 0.7327
-----------------------------------------------------------------
Omnibus: 8.818 Durbin-Watson: 0.855
Prob(Omnibus): 0.012 Jarque-Bera (JB): 8.119
Skew: -0.978 Prob(JB): 0.017
Kurtosis: 3.563 Condition No.: 16183
=================================================================
Notes:
[1] Standard Errors assume that the covariance matrix of the
errors is correctly specified.
[2] The condition number is large, 1.62e+04. This might indicate
that there are strong multicollinearity or other numerical
problems.
【相関行列(2022年, N=47)】
TFR 女性就業率_代理 保育所密度 婚姻率 高齢化率
TFR 1.000 0.242 0.628 -0.108 0.227
女性就業率_代理 0.242 1.000 0.124 -0.022 0.215
保育所密度 0.628 0.124 1.000 -0.260 0.334
婚姻率 -0.108 -0.022 -0.260 1.000 -0.890
高齢化率 0.227 0.215 0.334 -0.890 1.000sm.add_constant(X) — 切片項(定数1の列)を先頭に追加。statsmodelsで必須。sm.OLS(y, X).fit() — 最小二乗法でモデルを推定。model.params, model.pvalues, model.conf_int() で結果取得。[式 for x in リスト] はリスト内包表記。forループでappendする代わりに1行でリストを作れます。260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 | print("図4を生成中 ...") heat_vars = ['TFR', '女性就業率_代理', '保育所密度', '婚姻率', '高齢化率'] heat_labels = [ 'TFR\n(合計特殊出生率)', '女性就業率代理\n(15-64歳)', '保育所密度\n(保育所/万人)', '婚姻率\n(件/千人)', '高齢化率\n(65歳以上%)', ] corr_mat = df22_clean[heat_vars].astype(float).corr() fig4, ax4 = plt.subplots(figsize=(8, 6.5)) im = ax4.imshow(corr_mat.values, cmap='RdBu_r', vmin=-1, vmax=1, aspect='auto') cbar = plt.colorbar(im, ax=ax4, fraction=0.046, pad=0.04) cbar.set_label('相関係数', fontsize=10) n_vars4 = len(heat_vars) ax4.set_xticks(range(n_vars4)) ax4.set_yticks(range(n_vars4)) ax4.set_xticklabels(heat_labels, fontsize=8.5, rotation=0, ha='center') ax4.set_yticklabels(heat_labels, fontsize=8.5) |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。fig, ax = plt.subplots(...) — 図全体(fig)と軸(ax)を作る定番。以降は ax.bar(...) 等で操作。s[:-n]「末尾n文字を除く」/s[n:]「先頭n文字を除く」。スライス [start:stop:step] はリスト・タプル・文字列共通の基本ワザです。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 | # 相関係数と有意星を表示 for i in range(n_vars4): for j in range(n_vars4): val = corr_mat.iloc[i, j] if i == j: ax4.text(j, i, '1.00', ha='center', va='center', fontsize=10, fontweight='bold', color='black') else: # p値計算 x_c = df22_clean[heat_vars[j]].astype(float).values y_c = df22_clean[heat_vars[i]].astype(float).values mask_c = np.isfinite(x_c) & np.isfinite(y_c) _, pval_c = stats.pearsonr(x_c[mask_c], y_c[mask_c]) sig_c = '***' if pval_c < 0.001 else '**' if pval_c < 0.01 else '*' if pval_c < 0.05 else '' text_color = 'white' if abs(val) > 0.6 else 'black' ax4.text(j, i, f'{val:.2f}{sig_c}', ha='center', va='center', fontsize=9, color=text_color, fontweight='bold' if abs(val) > 0.3 else 'normal') ax4.set_title('相関ヒートマップ(2022年, N=47都道府県)\n* p<0.05, ** p<0.01, *** p<0.001', fontsize=12, fontweight='bold', pad=14) plt.tight_layout() fig4_path = os.path.join(FIG_DIR, '2022_H5_10_fig4_heatmap.png') fig4.savefig(fig4_path, bbox_inches='tight', dpi=150) plt.close(fig4) print(f"図4保存: {fig4_path}") |
図4を生成中 ... 図4保存: html/figures/2022_H5_10_fig4_heatmap.png
stats.pearsonr(x, y) — Pearson相関係数 r と p値を同時に返します。np.cumsum(arr) は累積和、np.linspace(a, b, n) は「aからbを等間隔でn個」。NumPyの定石です。311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 | print("\n" + "=" * 60) print("■ 分析結果サマリー") print("=" * 60) print(f" 使用データ: SSDSE-B-2026(実データ)") print(f" 対象期間: 2012〜2023年, 47都道府県パネル") print() print(" OLS回帰結果(2022年断面, N=47):") print(f" {'変数':<20} {'係数':>8} {'SE':>8} {'p値':>10} {'有意'}") print(" " + "-" * 55) for name, coef, se, pval in zip(X_vars, coefs3, ses3, pvals3): sig = '***' if pval < 0.001 else '**' if pval < 0.01 else '*' if pval < 0.05 else '†' if pval < 0.10 else 'n.s.' print(f" {name:<20} {coef:>8.4f} {se:>8.4f} {pval:>10.4f} {sig}") print(f" R² = {ols_model.rsquared:.4f}, N=47") print() print(" 相関分析(2022年, TFRとの相関):") for var in heat_vars[1:]: x_v = df22_clean[var].astype(float).values y_v = df22_clean['TFR'].astype(float).values mask_v = np.isfinite(x_v) & np.isfinite(y_v) r_v, p_v = stats.pearsonr(x_v[mask_v], y_v[mask_v]) sig_v = '***' if p_v < 0.001 else '**' if p_v < 0.01 else '*' if p_v < 0.05 else 'n.s.' print(f" TFR ↔ {var:<18}: r={r_v:+.3f} ({sig_v})") print("\n全図の生成完了(4枚)") print(f" fig1: 2022_H5_10_fig1_timeseries.png (TFR時系列推移)") print(f" fig2: 2022_H5_10_fig2_scatter.png (女性就業率 vs TFR散布図)") print(f" fig3: 2022_H5_10_fig3_coef.png (OLS回帰係数プロット)") print(f" fig4: 2022_H5_10_fig4_heatmap.png (相関ヒートマップ)") |
============================================================ ■ 分析結果サマリー ============================================================ 使用データ: SSDSE-B-2026(実データ) 対象期間: 2012〜2023年, 47都道府県パネル OLS回帰結果(2022年断面, N=47): 変数 係数 SE p値 有意 ------------------------------------------------------- 女性就業率_代理 0.0209 0.0213 0.3325 n.s. 保育所密度 0.1278 0.0287 0.0001 *** 高齢化率 0.0097 0.0134 0.4707 n.s. 婚姻率 0.0764 0.0903 0.4028 n.s. 消費支出_log 0.0653 0.3305 0.8443 n.s. R² = 0.4324, N=47 相関分析(2022年, TFRとの相関): TFR ↔ 女性就業率_代理 : r=+0.242 (n.s.) TFR ↔ 保育所密度 : r=+0.628 (***) TFR ↔ 婚姻率 : r=-0.108 (n.s.) TFR ↔ 高齢化率 : r=+0.227 (n.s.) 全図の生成完了(4枚) fig1: 2022_H5_10_fig1_timeseries.png (TFR時系列推移) fig2: 2022_H5_10_fig2_scatter.png (女性就業率 vs TFR散布図) fig3: 2022_H5_10_fig3_coef.png (OLS回帰係数プロット) fig4: 2022_H5_10_fig4_heatmap.png (相関ヒートマップ)
stats.pearsonr(x, y) — Pearson相関係数 r と p値を同時に返します。np.cumsum(arr) は累積和、np.linspace(a, b, n) は「aからbを等間隔でn個」。NumPyの定石です。SSDSE-B の47都道府県パネルデータ(2012〜2023年)を用いた分析の結果:
| データ | 出典・説明 |
|---|---|
| SSDSE-B-2026.csv | 社会・人口統計体系(都道府県データ), 統計センター公表実データ, 2012〜2023年度 |
| 合計特殊出生率(A4103) | 各都道府県の合計特殊出生率(厚生労働省 人口動態統計より) |
| 保育所等数(J2503) | 保育所・認定こども園等の施設数(厚生労働省 保育所等関連状況) |
| 婚姻件数(A9101) | 各都道府県の年間婚姻件数(厚生労働省 人口動態統計) |
本教育用コードはSSDSE-B-2026の実データのみを使用(合成データなし)。分析コードはPython 3 / pandas / statsmodels / matplotlib で実装。
統計分析の解釈で初心者がやりがちな勘違いをまとめます。特に「相関と因果の混同」「p値の過信」は研究現場でもよく起きる落とし穴です。本文を読む前にも、読んだ後にも、目を通してみてください。
統計の基本用語を初心者向けに解説します。本文中で見慣れない言葉が出てきたら、ここに戻って確認してください。
統計手法について「何のためか」「結果をどう読むか」を初心者向けに解説します。
この研究をさらに発展させるための3つの方向性を示します。「今回わかったこと(X)」から「次に検証すべき仮説(Y)」を立て、「具体的に何をするか(Z)」まで考えてみましょう。
学んだだけでは身につきません。実際に手を動かすのが最強の学習方法です。本論文のスクリプトをベースに、以下のチャレンジに挑戦してみてください。難易度別に5つ用意しました。
本論文で学んだ手法は、研究の世界だけでなく、行政・企業・NPO の現場でも様々に活用されています。具体的なシーンを紹介します。
この論文を読んで初心者が抱きやすい疑問に、教育的観点から答えます。