このページの分析を自分で再現するには、以下の手順でデータを準備してください。コードの編集は不要です。
data/raw/ フォルダに入れます。html/figures/ に自動保存されます。
日本の合計特殊出生率(TFR)は2022年に1.26と過去最低水準を更新し、少子化は国家的な最優先課題となっている。政府は「子ども・子育て支援新制度」(2015年施行)のもと保育所の量的拡充を進めてきたが、その政策効果を統計的に実証した研究はいまだ十分でない。
まず「子ども・子育て支援の充実は合計特殊出生率を高めるか?」を統計的にとらえることが有効だと考えられる。 その理由は感覚や経験則だけでは、複雑な社会要因の中で「何が本当に効いているか」を見極めにくいからである。 本研究では公開データと統計手法を組み合わせ、この問いに定量的な答えを出すことを目指す。
本研究では、47都道府県の横断面データ(2022年度)を用いたOLS重回帰分析により、保育所整備の各側面(密度・定員・充足率・待機率・保育士比)が出生率に与える効果を多面的に検証する。また、2012〜2023年のパネルデータで保育所数密度とTFRの長期トレンドを比較する。
SSDSE-B 都道府県 OLS重回帰 VIF(多重共線性) 交互作用項・パネル
SSDSE(社会・人口統計体系)-B は都道府県レベルの統計指標を2012〜2023年にわたり収録する。地域コードが R\d{5} の47都道府県を抽出し、2022年度データで横断面分析を行った。
| 変数 | 定義 | 単位 | 想定効果 |
|---|---|---|---|
| 目的変数 | |||
| 合計特殊出生率(TFR) | 合計特殊出生率 | 率 | — |
| 保育所関連指標(主要説明変数) | |||
| 保育所数密度 | 保育所等数 / 総人口 × 10,000 | 所/万人 | 正(量的充実) |
| 定員/15歳未満人口 | 保育所等定員数 / 15歳未満人口 | 比率 | 正(受皿確保) |
| 充足率 | 在所児数 / 定員数 | 比率 | 負(高い=需要超過) |
| 待機児童率 | 待機児童数 / 在所児数 × 100 | % | 負(供給不足の代理) |
| 保育士/在所児比 | 保育士数 / 在所児数 | 比率 | 正(保育の質) |
| コントロール変数 | |||
| 消費支出 | 消費支出(二人以上世帯) | 円/世帯 | (所得代理) |
| 住宅地価 | 標準価格(平均・住宅地) | 円/m² | 負(居住コスト) |
| 有効求人倍率 | 有効求人数 / 有効求職者数 | 倍 | 正(雇用環境) |
| 高齢化率 | 65歳以上人口 / 総人口 × 100 | % | 負(社会的負担) |
まず、主要な保育所指標と合計特殊出生率の二変量関係を確認する。各散布図には単純線形回帰直線とピアソン相関係数(r)を示す。
散布図(単純相関)は「関係の有無」を示すに過ぎない。例えば「保育所が多い都道府県ほどTFRが高い」という相関が見えても、それは第三の変数(地域の子育て意識、農村性など)による交絡かもしれない。多変量回帰分析(OLS)でコントロール変数を加えることで、より純粋な効果推定が可能になる。
1 2 3 4 5 6 7 8 9 10 11 | import os import warnings import numpy as np import pandas as pd import matplotlib matplotlib.use('Agg') import matplotlib.pyplot as plt import statsmodels.api as sm from statsmodels.stats.outliers_influence import variance_inflation_factor warnings.filterwarnings('ignore') |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。import pandas as pd など — 必要なライブラリをまとめて呼び出します。as pd は短い別名(alias)。matplotlib.use('Agg') — グラフを画面表示せずファイルに保存するためのおまじない。f"...{x}..." はf-string。文字列の中に {変数} と書くだけで埋め込めて、{x:.2f} のように書式も指定できます。12 13 14 15 16 17 | # ── パス設定 ───────────────────────────────────────────── _dir = os.path.dirname(os.path.abspath(__file__)) if '__file__' in dir() else os.getcwd() FIG_DIR = os.path.join(_dir, '..', 'html', 'figures') DATA_B = os.path.join(_dir, '..', 'data', 'raw', 'SSDSE-B-2026.csv') os.makedirs(FIG_DIR, exist_ok=True) |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。os.makedirs('html/figures', exist_ok=True) — 図の保存先フォルダを作る(既にあってもOK)。df['A'] / df['B'] — pandasの列同士の四則演算は要素ごと(element-wise)。forループ不要なのが強み。18 19 20 21 22 23 24 25 | # ── フォント設定 ───────────────────────────────────────── plt.rcParams.update({ 'font.family' : ['Hiragino Sans', 'Hiragino Kaku Gothic ProN', 'AppleGothic', 'sans-serif'], 'axes.unicode_minus': False, 'figure.dpi' : 150, }) DPI = 150 |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。.map() は「1対1の置き換え」、.apply() は「関数を当てる」。辞書なら .map()、ロジックなら .apply()。26 27 28 29 30 31 32 | # ── データ読み込み ────────────────────────────────────── print("データを読み込み中...") df_raw = pd.read_csv(DATA_B, header=1, encoding='cp932') # 都道府県レベル (R\d{5}) に絞る mask = df_raw['地域コード'].str.match(r'^R\d{5}$', na=False) df_raw = df_raw[mask].copy() |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。pd.read_csv(...) でCSVを読み込みます。encoding='cp932' は日本語Windows由来の文字コード、header=1 は「2行目を列名として使う」。df['地域コード'].str.match(r'^R\d{5}', ...) — 正規表現で「R+数字5桁」の行(47都道府県)だけTrueにし、真偽値で行をフィルタ。[式 for x in リスト] はリスト内包表記。forループでappendする代わりに1行でリストを作れます。33 34 35 36 37 38 39 40 | # 全数値列を数値型に変換 for col in df_raw.columns: if col not in ['年度', '地域コード', '都道府県']: df_raw[col] = pd.to_numeric(df_raw[col], errors='coerce') # 2022年度データ (47都道府県) df_2022 = df_raw[df_raw['年度'] == 2022].copy() print(f"2022年度 都道府県データ: {len(df_2022)}件") |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。r, p = stats.pearsonr(...) — Pythonは複数戻り値を同時に受け取れる(タプルアンパック)。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 70 71 | # ── 変数の構築 ───────────────────────────────────────── df = df_2022[['都道府県']].copy() # 目的変数: 合計特殊出生率 df['tfr'] = df_2022['合計特殊出生率'].values # 保育所関連説明変数 df['nursery_density'] = ( df_2022['保育所等数'].values / df_2022['総人口'].values * 10000 ) # 保育所数密度 (人口1万人あたり) df['capacity_ratio'] = ( df_2022['保育所等定員数'].values / df_2022['15歳未満人口'].replace(0, np.nan).values ) # 定員/15歳未満人口 df['fill_rate'] = ( df_2022['保育所等在所児数'].values / df_2022['保育所等定員数'].replace(0, np.nan).values ) # 保育所充足率 (高い = 需要が強い) df['waitlist_rate'] = ( df_2022['保育所等利用待機児童数'].values / df_2022['保育所等在所児数'].replace(0, np.nan).values * 100 ) # 待機児童率 (供給不足の代理) df['staff_ratio'] = ( df_2022['保育所等保育士数'].values / df_2022['保育所等在所児数'].replace(0, np.nan).values ) # 保育士/在所児比 (質指標) |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。x if cond else y は三項演算子。リスト内包表記と組み合わせると、forとifを1行で書けます。72 73 74 75 76 77 78 79 80 81 82 83 84 85 | # コントロール変数 df['consumption'] = df_2022['消費支出(二人以上の世帯)'].values # 所得代理 df['land_price'] = df_2022['標準価格(平均価格)(住宅地)'].values # 住宅コスト df['job_offer_rate'] = ( df_2022['月間有効求人数(一般)'].values / df_2022['月間有効求職者数(一般)'].replace(0, np.nan).values ) # 有効求人倍率 df['aging_rate'] = ( df_2022['65歳以上人口'].values / df_2022['総人口'].replace(0, np.nan).values * 100 ) # 高齢化率 df = df.dropna() print(f"欠損除外後: {len(df)}件 (都道府県)") |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。df[col](1列)と df[[col1, col2]](複数列)でカッコの数が違います。リストを渡していると覚えるとミスを減らせます。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 | # 変数リスト PRED_COLS = [ 'nursery_density', 'capacity_ratio', 'fill_rate', 'waitlist_rate', 'staff_ratio', 'consumption', 'land_price', 'job_offer_rate', 'aging_rate' ] PRED_LABELS = { 'nursery_density' : '保育所数密度\n(人口1万対)', 'capacity_ratio' : '定員/\n15歳未満人口', 'fill_rate' : '充足率\n(在所/定員)', 'waitlist_rate' : '待機児童率\n(%)', 'staff_ratio' : '保育士/\n在所児比', 'consumption' : '消費支出\n(円/世帯)', 'land_price' : '住宅地価\n(円/㎡)', 'job_offer_rate' : '有効求人\n倍率', 'aging_rate' : '高齢化率\n(%)', } Y_LABEL = '合計特殊出生率' X = df[PRED_COLS].astype(float) y = df['tfr'].astype(float) print(f"\n目的変数 '{Y_LABEL}' の基本統計:") print(y.describe().round(4)) print(f"\n説明変数の基本統計:") print(X.describe().round(4)) |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。.describe() — 件数・平均・標準偏差・四分位・最大/最小を一括計算。データの素性チェックに必須。s[:-n]「末尾n文字を除く」/s[n:]「先頭n文字を除く」。スライス [start:stop:step] はリスト・タプル・文字列共通の基本ワザです。112 113 114 115 116 117 118 119 120 121 122 123 124 125 | # ── VIF 計算 ─────────────────────────────────────────── X_vif = sm.add_constant(X) vif_vals = [] for i, col in enumerate(X_vif.columns): if col == 'const': continue vif_vals.append({ 'variable': col, 'label' : PRED_LABELS[col].replace('\n', ' '), 'VIF' : variance_inflation_factor(X_vif.values.astype(float), i) }) vif_df = pd.DataFrame(vif_vals) print("\nVIF:") print(vif_df[['variable', 'VIF']].round(2).to_string(index=False)) |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。sm.add_constant(X) — 切片項(定数1の列)を先頭に追加。statsmodelsで必須。np.cumsum(arr) は累積和、np.linspace(a, b, n) は「aからbを等間隔でn個」。NumPyの定石です。126 127 128 129 130 131 132 133 134 | # ── OLS 回帰 ─────────────────────────────────────────── X_ols = sm.add_constant(X) model = sm.OLS(y, X_ols).fit() print("\n" + "="*60) print("OLS 回帰サマリー") print("="*60) print(model.summary()) print(f"\nR² = {model.rsquared:.4f}, Adj.R² = {model.rsquared_adj:.4f}") print(f"F-stat = {model.fvalue:.2f}, p = {model.f_pvalue:.4f}") |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。sm.add_constant(X) — 切片項(定数1の列)を先頭に追加。statsmodelsで必須。sm.OLS(y, X).fit() — 最小二乗法でモデルを推定。model.params, model.pvalues, model.conf_int() で結果取得。{値:.2f}(小数2桁)、{値:,}(3桁区切り)、{値:>10}(右寄せ10桁)など、覚えると出力が一気に整います。135 136 137 138 139 140 141 | # 標準化係数 X_std = (X - X.mean()) / X.std() y_std = (y - y.mean()) / y.std() X_std_ols = sm.add_constant(X_std) model_std = sm.OLS(y_std, X_std_ols).fit() coef_std = model_std.params.drop('const') pvals = model.pvalues.drop('const') |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。sm.add_constant(X) — 切片項(定数1の列)を先頭に追加。statsmodelsで必須。sm.OLS(y, X).fit() — 最小二乗法でモデルを推定。model.params, model.pvalues, model.conf_int() で結果取得。plt.subplots(figsize=(W, H)) で図サイズ指定、fig.savefig(..., bbox_inches='tight') で余白を自動で詰めて保存。142 143 144 145 146 147 148 149 150 151 152 | # 交互作用項: 保育所密度 × 高齢化率 df['interact_nursery_aging'] = df['nursery_density'] * df['aging_rate'] X_ia = X.copy() X_ia['nursery×aging'] = df['interact_nursery_aging'].values X_ia_std = sm.add_constant(X_ia) model_ia = sm.OLS(y, X_ia_std).fit() print("\n--- 交互作用項 (保育所密度 × 高齢化率) 追加モデル ---") print(f"R² = {model_ia.rsquared:.4f}, Adj.R² = {model_ia.rsquared_adj:.4f}") ia_coef = model_ia.params.get('nursery×aging', np.nan) ia_p = model_ia.pvalues.get('nursery×aging', np.nan) print(f"交互作用項係数: {ia_coef:.6f}, p = {ia_p:.4f}") |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。sm.add_constant(X) — 切片項(定数1の列)を先頭に追加。statsmodelsで必須。sm.OLS(y, X).fit() — 最小二乗法でモデルを推定。model.params, model.pvalues, model.conf_int() で結果取得。.dropna() は欠損行を除去、.copy() は独立したコピーを作る。pandasで警告を防ぐ定石。153 154 155 156 157 158 159 160 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 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 | # ── パネルデータ: 2012-2023年 全国平均推移 ──────────────── years_all = sorted(df_raw['年度'].unique()) panel_list = [] for yr in years_all: dfy = df_raw[df_raw['年度'] == yr].copy() tfr_mean = dfy['合計特殊出生率'].mean() nursery_d = ( dfy['保育所等数'] / dfy['総人口'].replace(0, np.nan) * 10000 ).mean() panel_list.append({'year': yr, 'tfr': tfr_mean, 'nursery_density': nursery_d}) panel_df = pd.DataFrame(panel_list).sort_values('year') print("\n--- パネルデータ (全国平均) ---") print(panel_df.to_string(index=False)) fig1, axes = plt.subplots(1, 3, figsize=(14, 5)) fig1.suptitle('子育て支援指標と合計特殊出生率の関係\n(2022年度, 47都道府県)', fontsize=13, fontweight='bold', y=1.02) scatter_vars = [ ('nursery_density', '保育所数密度\n(人口1万人あたり)', '#1565C0'), ('fill_rate', '保育所充足率\n(在所児/定員)', '#E65100'), ('waitlist_rate', '待機児童率\n(待機/在所 ×100)', '#C62828'), ] for ax, (vcol, vlabel, color) in zip(axes, scatter_vars): x_vals = df[vcol].values y_vals = y.values ax.scatter(x_vals, y_vals, color=color, alpha=0.7, edgecolors='white', linewidths=0.5, s=60) # 回帰直線 slope, intercept, r_val, p_val, _ = \ __import__('scipy').stats.linregress(x_vals, y_vals) x_line = np.linspace(x_vals.min(), x_vals.max(), 100) ax.plot(x_line, slope * x_line + intercept, color=color, lw=2, linestyle='--') # 都道府県ラベル (外れ値のみ) threshold = 1.5 * np.std(y_vals) y_mean = y_vals.mean() for i, (xi, yi, pref) in enumerate( zip(x_vals, y_vals, df['都道府県'].values)): if abs(yi - y_mean) > threshold: ax.annotate(pref, (xi, yi), fontsize=7, ha='left', xytext=(3, 3), textcoords='offset points', color='#333') sig_str = '***' if p_val < 0.001 else '**' if p_val < 0.01 \ else '*' if p_val < 0.05 else 'n.s.' ax.set_xlabel(vlabel, fontsize=10) ax.set_ylabel(Y_LABEL if ax == axes[0] else '', fontsize=10) ax.set_title(f'r = {r_val:.3f} ({sig_str})', fontsize=11) ax.grid(True, alpha=0.3) plt.tight_layout() fig1_path = os.path.join(FIG_DIR, '2023_U5_6_fig1_scatter.png') fig1.savefig(fig1_path, dpi=DPI, bbox_inches='tight') plt.close(fig1) print(f"\n図1保存: {fig1_path}") |
データを読み込み中... # 実行時エラーで途中まで
fig, ax = plt.subplots(...) — 図全体(fig)と軸(ax)を作る定番。以降は ax.bar(...) 等で操作。sort_values('列名', ascending=False) — 指定列で並べ替え(降順)。stats.linregress(x, y) — 単回帰の傾き・切片・r値・p値・標準誤差を返します。使わない値は _ で受け取り。f"...{x}..." はf-string。文字列の中に {変数} と書くだけで埋め込めて、{x:.2f} のように書式も指定できます。保育所5指標とコントロール変数4つを同時に投入したOLS重回帰分析を行い、各変数の標準化係数(β)で効果の大きさを比較する。
| 変数 | 標準化係数 β | p値 | 有意性 | 解釈 |
|---|---|---|---|---|
| 保育所数密度 | +0.574 | 0.021 | * | 最大の正効果:保育所の量的整備が出生率を高める |
| 定員/15歳未満人口 | −0.105 | 0.685 | n.s. | 単独では非有意 |
| 充足率 | +0.033 | 0.843 | n.s. | 非有意 |
| 待機児童率 | +0.158 | 0.292 | n.s. | 非有意(交絡の可能性) |
| 保育士/在所児比 | −0.157 | 0.271 | n.s. | 非有意 |
| 消費支出 | −0.019 | 0.887 | n.s. | 非有意(多重共線性の影響か) |
| 住宅地価 | −0.387 | 0.029 | * | 住宅コスト高は出生率を引き下げる |
| 有効求人倍率 | +0.235 | 0.106 | n.s. | 正の傾向(限界的) |
| 高齢化率 | −0.245 | 0.182 | n.s. | 負の傾向(限界的) |
* p<0.05, ** p<0.01, *** p<0.001. N=47都道府県. R²=0.588, Adj.R²=0.488, F=5.87(p<0.001)
「高齢化率の高い地域では保育所整備の効果が異なるか」を交互作用項(保育所密度 × 高齢化率)で検証した。
OLS回帰の偏回帰係数(b)は各変数の元の単位で効果を示す。しかし単位が異なる変数(例:円vs倍率)を比較するには不便。標準化係数(β)は全変数をZ得点変換してから回帰するため、「どの変数の効果が最も大きいか」を直接比較できる。
212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 | fig2, ax2 = plt.subplots(figsize=(10, 6)) labels_ordered = [PRED_LABELS[c] for c in PRED_COLS] coef_vals = [coef_std[c] for c in PRED_COLS] p_vals_lst = [pvals[c] for c in PRED_COLS] colors = [] for cv, pv in zip(coef_vals, p_vals_lst): if pv < 0.05: colors.append('#1565C0' if cv > 0 else '#C62828') else: colors.append('#90A4AE') bars = ax2.barh(labels_ordered, coef_vals, color=colors, edgecolor='white', linewidth=0.5, height=0.6) for bar, cv, pv in zip(bars, coef_vals, p_vals_lst): sig = '***' if pv < 0.001 else '**' if pv < 0.01 \ else '*' if pv < 0.05 else 'n.s.' xpos = cv + (0.01 if cv >= 0 else -0.01) ha = 'left' if cv >= 0 else 'right' ax2.text(xpos, bar.get_y() + bar.get_height() / 2, f'p={pv:.3f} {sig}', va='center', ha=ha, fontsize=9, color='#333') ax2.axvline(0, color='#333', lw=1) ax2.set_xlabel('標準化回帰係数 (β)', fontsize=11) ax2.set_title(f'OLS重回帰 標準化係数\n(目的変数: 合計特殊出生率, R²={model.rsquared:.3f})', fontsize=12, fontweight='bold') ax2.grid(True, axis='x', alpha=0.3) |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。fig, ax = plt.subplots(...) — 図全体(fig)と軸(ax)を作る定番。以降は ax.bar(...) 等で操作。ax.axhline / ax.axvline — 水平/垂直の点線。平均線や基準線として定番。df['A'] / df['B'] — pandasの列同士の四則演算は要素ごと(element-wise)。forループ不要なのが強み。241 242 243 244 245 246 247 248 249 250 251 252 253 254 | # 凡例 from matplotlib.patches import Patch legend_elements = [ Patch(facecolor='#1565C0', label='正の有意効果 (p<0.05)'), Patch(facecolor='#C62828', label='負の有意効果 (p<0.05)'), Patch(facecolor='#90A4AE', label='非有意 (p≥0.05)'), ] ax2.legend(handles=legend_elements, loc='lower right', fontsize=9) plt.tight_layout() fig2_path = os.path.join(FIG_DIR, '2023_U5_6_fig2_coef.png') fig2.savefig(fig2_path, dpi=DPI, bbox_inches='tight') plt.close(fig2) print(f"図2保存: {fig2_path}") |
# 実行時エラーで途中まで
import pandas as pd など — 必要なライブラリをまとめて呼び出します。as pd は短い別名(alias)。.map() は「1対1の置き換え」、.apply() は「関数を当てる」。辞書なら .map()、ロジックなら .apply()。複数の保育所指標を同時投入すると変数間の相関(多重共線性)が推定精度を低下させる恐れがある。分散膨張係数(VIF)で診断する。
| 変数 | VIF | 判定 |
|---|---|---|
| 保育所数密度 | 5.12 | 要注意(5≤VIF<10) |
| 定員/15歳未満人口 | 5.96 | 要注意(5≤VIF<10) |
| 充足率 | 2.49 | 問題なし |
| 待機児童率 | 1.95 | 問題なし |
| 保育士/在所児比 | 1.77 | 問題なし |
| 消費支出 | 1.56 | 問題なし |
| 住宅地価 | 2.61 | 問題なし |
| 有効求人倍率 | 1.80 | 問題なし |
| 高齢化率 | 2.91 | 問題なし |
VIF(Variance Inflation Factor)は、変数iがその他の変数を使った回帰でどれだけ説明できるか(R²_i)から計算される。VIF=1/(1−R²_i)。R²_i が1に近づくほど(他変数で完全に予測可能)VIFが無限大に発散し、推定が不安定になる。
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 | fig3, ax3 = plt.subplots(figsize=(9, 5)) vif_labels = [PRED_LABELS[c].replace('\n', ' ') for c in PRED_COLS] vif_values = [vif_df[vif_df['variable'] == c]['VIF'].values[0] for c in PRED_COLS] vif_colors = ['#C62828' if v > 10 else '#F9A825' if v > 5 else '#2E7D32' for v in vif_values] bars3 = ax3.barh(vif_labels, vif_values, color=vif_colors, edgecolor='white', linewidth=0.5, height=0.6) for bar, v in zip(bars3, vif_values): ax3.text(v + 0.15, bar.get_y() + bar.get_height() / 2, f'{v:.2f}', va='center', ha='left', fontsize=9, color='#333') ax3.axvline(5, color='#F9A825', lw=1.5, linestyle='--', alpha=0.8, label='VIF=5 (警告)') ax3.axvline(10, color='#C62828', lw=1.5, linestyle='--', alpha=0.8, label='VIF=10 (問題)') ax3.set_xlabel('VIF(分散膨張係数)', fontsize=11) ax3.set_title('多重共線性の診断:VIF\n(VIF<5: 問題なし, 5≤VIF<10: 要注意, VIF≥10: 問題)', fontsize=12, fontweight='bold') ax3.legend(fontsize=9) ax3.grid(True, axis='x', alpha=0.3) ax3.set_xlim(0, max(vif_values) * 1.25 + 2) plt.tight_layout() fig3_path = os.path.join(FIG_DIR, '2023_U5_6_fig3_vif.png') fig3.savefig(fig3_path, dpi=DPI, bbox_inches='tight') plt.close(fig3) print(f"図3保存: {fig3_path}") |
# 実行時エラーで途中まで
fig, ax = plt.subplots(...) — 図全体(fig)と軸(ax)を作る定番。以降は ax.bar(...) 等で操作。ax.axhline / ax.axvline — 水平/垂直の点線。平均線や基準線として定番。.map() は「1対1の置き換え」、.apply() は「関数を当てる」。辞書なら .map()、ロジックなら .apply()。横断面分析の補完として、2012〜2023年の全国47都道府県平均を使い、保育所数密度とTFRの長期的な動向を比較する。
| 年度 | 保育所密度(人口1万対) | TFR(全国平均) | 主なイベント |
|---|---|---|---|
| 2012 | 2.21 | 1.46 | 子ども・子育て関連3法成立 |
| 2015 | 2.32 | 1.53 | 子ども・子育て支援新制度施行 |
| 2018 | 2.59 | 1.50 | 幼児教育・保育無償化準備期 |
| 2019 | 2.64 | 1.45 | 幼児教育・保育の無償化(10月) |
| 2022 | 2.76 | 1.36 | 少子化対策強化議論 |
| 2023 | 2.01 | 1.29 | 保育所等の認定区分変更(統計の不連続) |
横断面分析(ある時点の複数地域比較)は「地域差」を説明できるが、「時間の経過による変化」は捉えられない。パネル分析(複数地域×複数時点)では固定効果モデルや変量効果モデルにより、地域固有の特性を統制した上で効果を推定できる。少子化のような長期的現象では、両方の視点を組み合わせることが重要。
285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 | fig4, ax4a = plt.subplots(figsize=(11, 5)) ax4b = ax4a.twinx() years_plot = panel_df['year'].values tfr_plot = panel_df['tfr'].values density_plot = panel_df['nursery_density'].values line1, = ax4a.plot(years_plot, density_plot, color='#1565C0', lw=2.5, marker='o', markersize=6, label='保育所数密度\n(人口1万対, 左軸)') line2, = ax4b.plot(years_plot, tfr_plot, color='#E65100', lw=2.5, marker='s', markersize=6, label='合計特殊出生率\n(右軸)', linestyle='--') ax4a.set_xlabel('年度', fontsize=11) ax4a.set_ylabel('保育所数密度(人口1万人あたり)', fontsize=11, color='#1565C0') ax4b.set_ylabel('合計特殊出生率', fontsize=11, color='#E65100') ax4a.tick_params(axis='y', labelcolor='#1565C0') ax4b.tick_params(axis='y', labelcolor='#E65100') ax4a.set_title('全国平均:保育所数密度と合計特殊出生率の推移(2012–2023年)\n' '(47都道府県平均)', fontsize=12, fontweight='bold') |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。fig, ax = plt.subplots(...) — 図全体(fig)と軸(ax)を作る定番。以降は ax.bar(...) 等で操作。[式 for x in リスト] はリスト内包表記。forループでappendする代わりに1行でリストを作れます。307 308 309 310 311 312 | # 注釈: 2015年ピーク ax4b.annotate('TFR 局所最高\n(2015: 1.53)', xy=(2015, panel_df[panel_df['year'] == 2015]['tfr'].values[0]), xytext=(2016.5, 1.50), fontsize=9, color='#E65100', arrowprops=dict(arrowstyle='->', color='#E65100', lw=1.2)) |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。r, p = stats.pearsonr(...) — Pythonは複数戻り値を同時に受け取れる(タプルアンパック)。313 314 315 316 317 318 319 320 321 322 323 324 325 | # 凡例を統合 lines = [line1, line2] labels = [l.get_label() for l in lines] ax4a.legend(lines, labels, loc='upper left', fontsize=9) ax4a.grid(True, alpha=0.3) ax4a.set_xticks(years_plot) ax4a.set_xticklabels([str(y) for y in years_plot], rotation=45, ha='right', fontsize=9) plt.tight_layout() fig4_path = os.path.join(FIG_DIR, '2023_U5_6_fig4_timeseries.png') fig4.savefig(fig4_path, dpi=DPI, bbox_inches='tight') plt.close(fig4) print(f"図4保存: {fig4_path}") |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。x if cond else y は三項演算子。リスト内包表記と組み合わせると、forとifを1行で書けます。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 | # ── 結果サマリー ──────────────────────────────────────── print("\n" + "="*60) print("分析結果サマリー") print("="*60) print(f"サンプルサイズ : {len(df)} 都道府県 (2022年度)") print(f"R² : {model.rsquared:.4f}") print(f"Adj.R² : {model.rsquared_adj:.4f}") print(f"F統計量 : {model.fvalue:.2f} (p={model.f_pvalue:.4f})") print() print("標準化係数と有意性:") for col in PRED_COLS: lbl = PRED_LABELS[col].replace('\n', ' ') beta = coef_std[col] pv = pvals[col] sig = '***' if pv < 0.001 else '**' if pv < 0.01 \ else '*' if pv < 0.05 else 'n.s.' print(f" {lbl:25s}: β={beta:+.4f} p={pv:.4f} {sig}") print() print("VIF (多重共線性診断):") for col in PRED_COLS: vif_v = vif_df[vif_df['variable'] == col]['VIF'].values[0] flag = '[問題]' if vif_v > 10 else '[要注意]' if vif_v > 5 else '[OK]' print(f" {PRED_LABELS[col].replace(chr(10), ' '):25s}: VIF={vif_v:.2f} {flag}") print() print(f"交互作用項 (保育所密度 × 高齢化率): β={ia_coef:.6f}, p={ia_p:.4f}") print() print("生成図:") for path in [fig1_path, fig2_path, fig3_path, fig4_path]: print(f" {path}") |
# 実行時エラーで途中まで
df[col](1列)と df[[col1, col2]](複数列)でカッコの数が違います。リストを渡していると覚えるとミスを減らせます。47都道府県データ(2022年度)を用いたOLS重回帰分析の結果:
| データ | 出典 | 変数 |
|---|---|---|
| SSDSE-B 都道府県統計データ | 統計数理研究所 SSDSE(社会・人口統計体系)2026年版 | 合計特殊出生率、保育所等数、定員・在所・待機・保育士数, 消費支出, 住宅地価, 求人・求職数, 65歳以上人口 |
本スクリプトはSSDSE-B-2026.csvの実データのみを使用。合成データ・乱数(np.random等)は一切使用していない。
統計分析の解釈で初心者がやりがちな勘違いをまとめます。特に「相関と因果の混同」「p値の過信」は研究現場でもよく起きる落とし穴です。本文を読む前にも、読んだ後にも、目を通してみてください。
統計の基本用語を初心者向けに解説します。本文中で見慣れない言葉が出てきたら、ここに戻って確認してください。
統計手法について「何のためか」「結果をどう読むか」を初心者向けに解説します。
この研究をさらに発展させるための3つの方向性を示します。「今回わかったこと(X)」から「次に検証すべき仮説(Y)」を立て、「具体的に何をするか(Z)」まで考えてみましょう。
学んだだけでは身につきません。実際に手を動かすのが最強の学習方法です。本論文のスクリプトをベースに、以下のチャレンジに挑戦してみてください。難易度別に5つ用意しました。
本論文で学んだ手法は、研究の世界だけでなく、行政・企業・NPO の現場でも様々に活用されています。具体的なシーンを紹介します。
この論文を読んで初心者が抱きやすい疑問に、教育的観点から答えます。