このページの分析を自分で再現するには、以下の手順でデータを準備してください。コードの編集は不要です。
data/raw/ フォルダに入れます。html/figures/ に自動保存されます。
社会保障政策が犯罪抑止に寄与するという仮説は、犯罪経済学(Becker, 1968)の文脈で長く論じられてきた。生活保護・医療保険・保育制度などのセーフティネットが充実することで、経済的困窮から生じる犯罪リスクが低下するという経路が考えられる。
まず「社会保障政策と犯罪の関係」を統計的にとらえることが有効だと考えられる。 その理由は感覚や経験則だけでは、複雑な社会要因の中で「何が本当に効いているか」を見極めにくいからである。 本研究では公開データと統計手法を組み合わせ、この問いに定量的な答えを出すことを目指す。
本研究は、47都道府県の複数年パネルデータを用い、社会保障支出と経済的脆弱性指標の関係を実証的に検証する。犯罪統計が SSDSE-B に直接含まれないため、「求職圧力指数」(月間有効求職者数/総人口)を経済的犯罪リスクの代理変数として使用する。
パネルデータ 代理変数 固定効果モデル Hausman検定
SSDSE-B-2026(教育用標準データセット B 都道府県版、出典:政府統計の総合窓口 e-Stat)を使用。47都道府県 × 2012-2023年の12年間、計約564観測を分析する。
この指数が高い都道府県・年度ほど、就業機会を求める人口比率が高く、経済的困窮リスクが大きいと解釈される。
| 変数名 | 定義・単位 | 仮説(符号) |
|---|---|---|
| 保健医療費(千円) | 二人以上世帯の月間保健医療費(千円/世帯) | 負(充実 → リスク低下) |
| 保育所等数(万対) | 保育所等数 / 総人口 × 10,000 | 負(子育て支援充実) |
| 一般病院数(万対) | 一般病院数 / 総人口 × 10,000 | 負(医療アクセス向上) |
| 高齢化率 | 65歳以上人口 / 総人口 | 正(高齢化 → 生産年齢人口減) |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | print("=" * 70) print("■ データ読み込み: SSDSE-B-2026.csv(47都道府県、2012-2023年)") print("=" * 70) DATA_B = os.path.join(DATA_DIR, 'SSDSE-B-2026.csv') 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() # 必要カラムを数値変換 num_cols = [ '総人口', '65歳以上人口', '月間有効求職者数(一般)', '月間有効求人数(一般)', '保健医療費(二人以上の世帯)', '消費支出(二人以上の世帯)', '保育所等数', '保育所等定員数', '一般病院数', ] for col in num_cols: df_b[col] = pd.to_numeric(df_b[col], errors='coerce') |
====================================================================== ■ データ読み込み: SSDSE-B-2026.csv(47都道府県、2012-2023年) ======================================================================
pd.read_csv(...) でCSVを読み込みます。encoding='cp932' は日本語Windows由来の文字コード、header=1 は「2行目を列名として使う」。df['地域コード'].str.match(r'^R\d{5}', ...) — 正規表現で「R+数字5桁」の行(47都道府県)だけTrueにし、真偽値で行をフィルタ。df['A'] / df['B'] — pandasの列同士の四則演算は要素ごと(element-wise)。forループ不要なのが強み。18 19 20 21 22 23 24 | df_b['求職圧力指数'] = df_b['月間有効求職者数(一般)'] / df_b['総人口'] # 保育所等数(人口1万人対) df_b['保育所等数_万対'] = df_b['保育所等数'] / df_b['総人口'] * 10000 # 一般病院数(人口1万人対) df_b['一般病院数_万対'] = df_b['一般病院数'] / df_b['総人口'] * 10000 |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。.map() は「1対1の置き換え」、.apply() は「関数を当てる」。辞書なら .map()、ロジックなら .apply()。25 26 27 28 29 30 31 32 | # 65歳以上人口割合 df_b['高齢化率'] = df_b['65歳以上人口'] / df_b['総人口'] # 保健医療費(万円単位に正規化、スケール調整) df_b['保健医療費_千円'] = df_b['保健医療費(二人以上の世帯)'] / 1000 # 分析対象変数リスト VARS = ['求職圧力指数', '保健医療費_千円', '保育所等数_万対', '一般病院数_万対', '高齢化率'] |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。[式 for x in リスト] はリスト内包表記。forループでappendする代わりに1行でリストを作れます。33 34 35 36 37 38 39 40 41 | # 欠損値除去 df_panel = df_b[['年度', '地域コード', '都道府県'] + VARS].dropna().copy() df_panel = df_panel.sort_values(['地域コード', '年度']).reset_index(drop=True) print(f"分析対象: {df_panel['地域コード'].nunique()}都道府県 × {df_panel['年度'].nunique()}年") print(f"年度: {sorted(df_panel['年度'].unique())}") print(f"総観測数: {len(df_panel)}") print() print(df_panel[VARS].describe().round(4)) |
分析対象: 47都道府県 × 12年
年度: [np.int64(2012), np.int64(2013), np.int64(2014), np.int64(2015), np.int64(2016), np.int64(2017), np.int64(2018), np.int64(2019), np.int64(2020), np.int64(2021), np.int64(2022), np.int64(2023)]
総観測数: 564
求職圧力指数 保健医療費_千円 保育所等数_万対 一般病院数_万対 高齢化率
count 564.0000 564.0000 564.0000 564.0000 564.0000
mean 0.1290 13.0576 2.4259 0.6934 0.2924
std 0.0278 1.8975 0.7297 0.2770 0.0348
min 0.0784 8.3140 1.0185 0.3129 0.1772
25% 0.1103 11.8107 1.9094 0.4889 0.2703
50% 0.1249 12.7885 2.2574 0.6074 0.2947
75% 0.1435 14.2540 2.9270 0.8345 0.3176
max 0.2347 21.0000 4.5137 1.6527 0.3906.describe() — 件数・平均・標準偏差・四分位・最大/最小を一括計算。データの素性チェックに必須。sort_values('列名', ascending=False) — 指定列で並べ替え(降順)。r, p = stats.pearsonr(...) — Pythonは複数戻り値を同時に受け取れる(タプルアンパック)。固定効果モデルの推定に先立ち、主要変数間のピアソン相関係数を算出し、多重共線性の有無と変数間の関係を確認する。
パネルデータは「同一の個体(都道府県・企業・個人など)を複数の時点にわたって追跡したデータ」である。クロスセクションデータ(1時点の多数個体)と時系列データ(1個体の多時点)を組み合わせた2次元構造を持つ。
パネルデータの強みは、観測できない個体固有の特性(固定効果)をコントロールできる点にある。都道府県レベルでは「地域固有の文化・歴史・地理的条件」が犯罪率に影響するが、これらは時間を通じて不変であり、固定効果として除去できる。
43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 | print("\n図1: 相関ヒートマップを作成中...") CORR_VARS = ['求職圧力指数', '保健医療費_千円', '保育所等数_万対', '一般病院数_万対', '高齢化率'] CORR_LABELS = [ '求職圧力\n指数', '保健医療費\n(千円)', '保育所等数\n(万対)', '一般病院数\n(万対)', '高齢化率', ] corr_mat = df_panel[CORR_VARS].corr() n_vars = len(CORR_VARS) fig1, ax1 = plt.subplots(figsize=(8, 6.5)) im = ax1.imshow(corr_mat.values, cmap='RdBu_r', vmin=-1, vmax=1, aspect='auto') plt.colorbar(im, ax=ax1, shrink=0.8, label='相関係数 r') ax1.set_xticks(range(n_vars)) ax1.set_yticks(range(n_vars)) ax1.set_xticklabels(CORR_LABELS, fontsize=10) ax1.set_yticklabels(CORR_LABELS, fontsize=10) ax1.tick_params(axis='x', labelrotation=0) |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。fig, ax = plt.subplots(...) — 図全体(fig)と軸(ax)を作る定番。以降は ax.bar(...) 等で操作。df[col](1列)と df[[col1, col2]](複数列)でカッコの数が違います。リストを渡していると覚えるとミスを減らせます。67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 | # 相関係数の数値をセルに表示 for i in range(n_vars): for j in range(n_vars): r = corr_mat.values[i, j] # 有意性検定 if i != j: x_arr = df_panel[CORR_VARS[j]].dropna().values y_arr = df_panel[CORR_VARS[i]].dropna().values mask = ~(np.isnan(x_arr) | np.isnan(y_arr)) _, pval = stats.pearsonr(x_arr[mask], y_arr[mask]) sig_mark = '***' if pval < 0.001 else '**' if pval < 0.01 else '*' if pval < 0.05 else '' else: sig_mark = '' text_color = 'white' if abs(r) > 0.6 else 'black' ax1.text(j, i, f'{r:.2f}{sig_mark}', ha='center', va='center', fontsize=9.5, color=text_color, fontweight='bold') ax1.set_title('主要変数の相関ヒートマップ\n(47都道府県 × 2012-2023年、プールドデータ)\n' '※ *p<0.05、**p<0.01、***p<0.001', fontsize=11, fontweight='bold', pad=14) ax1.set_xlabel('データ出典: SSDSE-B-2026(e-Stat)', fontsize=9, color='gray') |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。stats.pearsonr(x, y) — Pearson相関係数 r と p値を同時に返します。s[:-n]「末尾n文字を除く」/s[n:]「先頭n文字を除く」。スライス [start:stop:step] はリスト・タプル・文字列共通の基本ワザです。89 90 91 92 93 94 95 96 97 98 99 100 | # 枠線 for spine in ax1.spines.values(): spine.set_visible(False) ax1.set_xticks(np.arange(-0.5, n_vars, 1), minor=True) ax1.set_yticks(np.arange(-0.5, n_vars, 1), minor=True) ax1.grid(which='minor', color='white', linewidth=2) ax1.tick_params(which='minor', bottom=False, left=False) plt.tight_layout() fig1.savefig(os.path.join(FIGURE_DIR, '2022_U2_fig1_corr.png'), bbox_inches='tight', dpi=150) plt.close(fig1) print(" → 2022_U2_fig1_corr.png 保存完了") |
図1: 相関ヒートマップを作成中... → 2022_U2_fig1_corr.png 保存完了
np.cumsum(arr) は累積和、np.linspace(a, b, n) は「aからbを等間隔でn個」。NumPyの定石です。全国47都道府県の年度平均を取り、求職圧力指数と保健医療費の推移を2軸グラフで可視化する。両変数の時系列パターンを比較することで、マクロトレンドの把握と仮説の直観的検証を行う。
102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 | print("図2: 時系列推移プロットを作成中...") years_ts = annual.index.values fig2, ax2a = plt.subplots(figsize=(10, 5)) ax2b = ax2a.twinx() l1 = ax2a.plot(years_ts, annual['求職圧力指数'] * 1000, color=COLORS['primary'], linewidth=2.5, marker='o', markersize=7, markerfacecolor='white', markeredgewidth=2, label='求職圧力指数(×1000)') l2 = ax2b.plot(years_ts, annual['保健医療費_千円'], color=COLORS['secondary'], linewidth=2.5, marker='s', markersize=7, markerfacecolor='white', markeredgewidth=2, linestyle='--', label='保健医療費(千円/世帯)') ax2a.set_xlabel('年度', fontsize=12) ax2a.set_ylabel('求職圧力指数(×1000)', fontsize=12, color=COLORS['primary']) ax2b.set_ylabel('保健医療費(千円/二人以上世帯)', fontsize=12, color=COLORS['secondary']) ax2a.tick_params(axis='y', labelcolor=COLORS['primary']) ax2b.tick_params(axis='y', labelcolor=COLORS['secondary']) ax2a.set_title('全国平均 求職圧力指数と保健医療費の推移(2012-2023年)\n' 'データ出典: SSDSE-B-2026(e-Stat)', fontsize=12, fontweight='bold') ax2a.set_xlim(2011.5, 2023.5) ax2a.set_xticks(years_ts) ax2a.grid(True, alpha=0.3) |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。fig, ax = plt.subplots(...) — 図全体(fig)と軸(ax)を作る定番。以降は ax.bar(...) 等で操作。s[:-n]「末尾n文字を除く」/s[n:]「先頭n文字を除く」。スライス [start:stop:step] はリスト・タプル・文字列共通の基本ワザです。130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 | # 注釈: COVID-19 if 2020 in annual.index: ax2a.axvspan(2019.5, 2021.5, alpha=0.08, color='red', label='COVID-19期') ax2a.annotate('COVID-19\n(2020-21年)', xy=(2020, annual.loc[2020, '求職圧力指数'] * 1000), xytext=(2020.8, annual.loc[2020, '求職圧力指数'] * 1000 * 1.05), arrowprops=dict(arrowstyle='->', color='gray', lw=1.2), fontsize=8.5, color='gray') lines = l1 + l2 labels = [l.get_label() for l in lines] ax2a.legend(lines, labels, loc='upper right', fontsize=10) plt.tight_layout() fig2.savefig(os.path.join(FIGURE_DIR, '2022_U2_fig2_ts.png'), bbox_inches='tight', dpi=150) plt.close(fig2) print(" → 2022_U2_fig2_ts.png 保存完了") |
図2: 時系列推移プロットを作成中... → 2022_U2_fig2_ts.png 保存完了
np.cumsum(arr) は累積和、np.linspace(a, b, n) は「aからbを等間隔でn個」。NumPyの定石です。Hausman検定によりモデル選択を行い、適切と判断されたモデルでパネルOLS推定を実施する。固定効果モデルでは、都道府県固有の時不変な特性(地理・文化・歴史的条件)を除去した上で、社会保障指標と求職圧力指数の関係を推定する。
固定効果モデル(FE)は、各都道府県の時間平均を差し引いた「個体内変動(within variation)」のみを用いて係数を推定する。これにより「観察されない都道府県固有の要因(文化・地理・歴史)」を完全にコントロールできる。
係数 β の解釈:「同じ都道府県において、ある年に説明変数が1単位増加したとき、求職圧力指数は β だけ変化する」という within の変化量を意味する。都道府県間の比較(between variation)は除外される。
パネルデータ分析では「固定効果(FE)モデル」と「変量効果(RE)モデル」のどちらを使うべきかを検討する必要がある。Hausman検定はこの選択を統計的に行う手法である。
帰無仮説 H₀:「個体効果と説明変数は無相関(変量効果モデルが一致推定量)」
対立仮説 H₁:「個体効果と説明変数に相関あり(固定効果モデルが必要)」
p<0.05 ならば H₀ を棄却し、固定効果モデルが支持される。都道府県データでは地域固有の要因が説明変数と相関している可能性が高いため、固定効果が採用されることが多い。
148 149 150 151 152 153 154 155 156 157 | print("図3: 固定効果係数プロットを作成中...") # 表示用変数名マッピング var_label_map = { '保健医療費_千円': '保健医療費\n(千円/世帯)', '保育所等数_万対': '保育所等数\n(人口1万対)', '一般病院数_万対': '一般病院数\n(人口1万対)', '高齢化率': '高齢化率', 'const': '定数項', } |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。np.cumsum(arr) は累積和、np.linspace(a, b, n) は「aからbを等間隔でn個」。NumPyの定石です。158 159 160 161 162 163 164 165 166 167 168 | # CI(95%) ci_lo = fe_params - 1.96 * fe_stderr ci_hi = fe_params + 1.96 * fe_stderr # 定数項を除いて表示 plot_vars = X_vars # ['保健医療費_千円', '保育所等数_万対', '一般病院数_万対', '高齢化率'] plot_labels = [var_label_map[v] for v in plot_vars] plot_coefs = [fe_params[v] for v in plot_vars] plot_cilo = [ci_lo[v] for v in plot_vars] plot_cihi = [ci_hi[v] for v in plot_vars] plot_pvals = [fe_pvalues[v] for v in plot_vars] |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。{値:.2f}(小数2桁)、{値:,}(3桁区切り)、{値:>10}(右寄せ10桁)など、覚えると出力が一気に整います。169 170 171 172 173 174 175 176 177 178 179 180 181 | # p値に応じた色 bar_colors = [] for p in plot_pvals: if p < 0.01: bar_colors.append(COLORS['danger']) elif p < 0.05: bar_colors.append(COLORS['secondary']) elif p < 0.10: bar_colors.append(COLORS['success']) else: bar_colors.append(COLORS['gray']) fig3, ax3 = plt.subplots(figsize=(9, 5.5)) y_pos = np.arange(len(plot_vars)) ax3.barh(y_pos, plot_coefs, color=bar_colors, alpha=0.85, edgecolor='white', linewidth=0.8, height=0.5) |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。fig, ax = plt.subplots(...) — 図全体(fig)と軸(ax)を作る定番。以降は ax.bar(...) 等で操作。plt.subplots(figsize=(W, H)) で図サイズ指定、fig.savefig(..., bbox_inches='tight') で余白を自動で詰めて保存。182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 | # 95% CI エラーバー for i, (lo, hi, coef) in enumerate(zip(plot_cilo, plot_cihi, plot_coefs)): ax3.plot([lo, hi], [i, i], color='black', linewidth=2.0, solid_capstyle='round') ax3.plot(lo, i, '|', color='black', markersize=10, markeredgewidth=2) ax3.plot(hi, i, '|', color='black', markersize=10, markeredgewidth=2) ax3.axvline(0, color='black', linewidth=1.0, linestyle='--', alpha=0.7) ax3.set_yticks(y_pos) ax3.set_yticklabels(plot_labels, fontsize=11) ax3.set_xlabel('推定係数(β)', fontsize=12) ax3.set_title('固定効果モデル(PanelOLS)の推定係数\n' '目的変数: 求職圧力指数、エラーバー: 95%信頼区間', fontsize=12, fontweight='bold') ax3.grid(axis='x', alpha=0.3) ax3.invert_yaxis() |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。ax.axhline / ax.axvline — 水平/垂直の点線。平均線や基準線として定番。.dropna() は欠損行を除去、.copy() は独立したコピーを作る。pandasで警告を防ぐ定石。198 199 200 201 202 203 204 | # 係数値・有意水準のラベル for i, (coef, p) in enumerate(zip(plot_coefs, plot_pvals)): sig = ('***' if p < 0.01 else '**' if p < 0.05 else '*' if p < 0.10 else 'n.s.') x_text = max(plot_cihi[i], abs(coef)) * 1.05 + 0.00001 ax3.text(plot_cihi[i] + abs(max(plot_cihi) - min(plot_cilo)) * 0.03, i, f'{coef:+.5f} {sig}', va='center', fontsize=9, fontweight='bold') |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。f"...{x}..." はf-string。文字列の中に {変数} と書くだけで埋め込めて、{x:.2f} のように書式も指定できます。205 206 207 208 209 210 211 212 | # 凡例 legend_patches = [ mpatches.Patch(color=COLORS['danger'], label='p < 0.01 ***'), mpatches.Patch(color=COLORS['secondary'], label='p < 0.05 **'), mpatches.Patch(color=COLORS['success'], label='p < 0.10 *'), mpatches.Patch(color=COLORS['gray'], label='n.s.(p ≥ 0.10)'), ] ax3.legend(handles=legend_patches, loc='lower right', fontsize=9) |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。df['A'] / df['B'] — pandasの列同士の四則演算は要素ごと(element-wise)。forループ不要なのが強み。213 214 215 216 217 218 219 220 221 222 223 224 | # R² など r2_text = (f"R²(within)= {result_fe.rsquared:.3f}\n" f"観測数 N = {int(result_fe.nobs)}\n" f"クラスタード標準誤差(都道府県)") ax3.text(0.02, 0.97, r2_text, transform=ax3.transAxes, fontsize=9, va='top', color='gray', bbox=dict(boxstyle='round', facecolor='#F8F9FA', alpha=0.8)) plt.tight_layout() fig3.savefig(os.path.join(FIGURE_DIR, '2022_U2_fig3_fe_coef.png'), bbox_inches='tight', dpi=150) plt.close(fig3) print(" → 2022_U2_fig3_fe_coef.png 保存完了") |
図3: 固定効果係数プロットを作成中... → 2022_U2_fig3_fe_coef.png 保存完了
.map() は「1対1の置き換え」、.apply() は「関数を当てる」。辞書なら .map()、ロジックなら .apply()。固定効果モデルの推定結果を補完するため、複数年度の散布図で求職圧力指数と社会保障指標の関係を可視化する。2014年・2018年・2022年の3時点を比較することで、関係性の時間的安定性も確認する。
研究で本来測りたい概念(「経済的犯罪リスク」)が直接観測できない場合、理論的・実証的に関連する別の変数を「代理変数(proxy variable)」として使用する。
代理変数の妥当性を担保するためには以下を確認する必要がある:
(1) 概念的妥当性:代理変数と本来の概念の理論的つながり
(2) 収束的妥当性:代理変数が類似概念の指標と正相関するか
(3) 弁別的妥当性:代理変数が異なる概念の指標と過度に相関していないか
「求職圧力指数」は、雇用機会の不足 → 経済的困窮 → 犯罪動機増大という経路で犯罪リスクと理論的に連結され、代理変数として一定の妥当性を持つ。ただし、犯罪件数の直接データとの相関を確認する追加検証が理想的である。
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 | import os import warnings 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 warnings.filterwarnings('ignore') plt.rcParams['font.family'] = 'Hiragino Sans' plt.rcParams['axes.unicode_minus'] = False plt.rcParams['figure.dpi'] = 150 FIGURE_DIR = os.path.normpath('html/figures') DATA_DIR = os.path.normpath('data/raw') os.makedirs(FIGURE_DIR, exist_ok=True) COLORS = { 'primary': '#1565C0', 'secondary': '#E65100', 'success': '#2E7D32', 'danger': '#C62828', 'purple': '#6A1B9A', 'teal': '#00695C', 'gray': '#616161', } |
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} のように書式も指定できます。256 257 258 259 260 261 262 263 | print("\n" + "=" * 70) print("■ PanelOLS 固定効果モデル(entity_effects=True)") print("=" * 70) from linearmodels import PanelOLS # マルチインデックス設定(地域コード × 年度) df_fe = df_panel.set_index(['地域コード', '年度']) |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。import pandas as pd など — 必要なライブラリをまとめて呼び出します。as pd は短い別名(alias)。[式 for x in リスト] はリスト内包表記。forループでappendする代わりに1行でリストを作れます。264 265 266 267 268 269 270 271 272 273 274 | # 目的変数・説明変数 y_fe = df_fe['求職圧力指数'] X_vars = ['保健医療費_千円', '保育所等数_万対', '一般病院数_万対', '高齢化率'] X_fe = sm.add_constant(df_fe[X_vars]) # 固定効果モデル推定 model_fe = PanelOLS(y_fe, X_fe, entity_effects=True) result_fe = model_fe.fit(cov_type='clustered', cluster_entity=True) print("\n【固定効果モデル 推定結果】") print(result_fe.summary) |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。sm.add_constant(X) — 切片項(定数1の列)を先頭に追加。statsmodelsで必須。r, p = stats.pearsonr(...) — Pythonは複数戻り値を同時に受け取れる(タプルアンパック)。275 276 277 278 279 280 281 282 283 284 285 286 287 288 | # 係数・標準誤差・p値の抽出 fe_params = result_fe.params fe_stderr = result_fe.std_errors fe_pvalues = result_fe.pvalues fe_tstat = result_fe.tstats print("\n【係数サマリー(定数項を除く)】") for var in X_vars: sig = ('***' if fe_pvalues[var] < 0.01 else '**' if fe_pvalues[var] < 0.05 else '*' if fe_pvalues[var] < 0.10 else 'n.s.') print(f" {var:22s}: β={fe_params[var]:+.6f} SE={fe_stderr[var]:.6f}" f" t={fe_tstat[var]:+.3f} p={fe_pvalues[var]:.4f} {sig}") |
======================================================================
■ PanelOLS 固定効果モデル(entity_effects=True)
======================================================================
【固定効果モデル 推定結果】
PanelOLS Estimation Summary
================================================================================
Dep. Variable: 求職圧力指数 R-squared: 0.8036
Estimator: PanelOLS R-squared (Between): -3.2458
No. Observations: 564 R-squared (Within): 0.8036
Date: Mon, May 18 2026 R-squared (Overall): -1.2779
Time: 11:24:13 Log-likelihood 1883.6
Cov. Estimator: Clustered
F-statistic: 524.78
Entities: 47 P-value 0.0000
Avg Obs: 12.000 Distribution: F(4,513)
Min Obs: 12.000
Max Obs: 12.000 F-statistic (robust): 307.41
P-value 0.0000
Time periods: 12 Distribution: F(4,513)
Avg Obs: 47.000
Min Obs: 47.000
Max Obs: 47.000
Parameter Estimates
==============================================================================
Parameter Std. Err. T-stat P-value Lower CI Upper CI
------------------------------------------------------------------------------
const 0.3836 0.0328 11.679 0.0000 0.3191 0.4481
保健医療費_千円 0.0018 0.0003 7.0473 0.0000 0.0013 0.0023
保育所等数_万対 -0.0095 0.0013 -7.1385 0.0000 -0.0122 -0.0069
一般病院数_万対 0.0105 0.0387 0.2728 0.7851 -0.0654 0.0865
高齢化率 -0.8954 0.0341 -26.236 0.0000 -0.9624 -0.8283
==============================================================================
F-test for Poolability: 65.571
P-value: 0.0000
Distribution: F(46,513)
Included effects: Entity
【係数サマリー(定数項を除く)】
保健医療費_千円 : β=+0.001767 SE=0.000251 t=+7.047 p=0.0000 ***
保育所等数_万対 : β=-0.009532 SE=0.001335 t=-7.138 p=0.0000 ***
一般病院数_万対 : β=+0.010547 SE=0.038656 t=+0.273 p=0.7851 n.s.
高齢化率 : β=-0.895373 SE=0.034128 t=-26.236 p=0.0000 ***x if cond else y は三項演算子。リスト内包表記と組み合わせると、forとifを1行で書けます。289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 | print("\n" + "=" * 70) print("■ 変量効果モデル & Hausman 検定") print("=" * 70) from linearmodels import RandomEffects model_re = RandomEffects(y_fe, X_fe) result_re = model_re.fit(cov_type='robust') print("\n【変量効果モデル 推定結果(係数のみ)】") for var in X_vars: sig = ('***' if result_re.pvalues[var] < 0.01 else '**' if result_re.pvalues[var] < 0.05 else '*' if result_re.pvalues[var] < 0.10 else 'n.s.') print(f" {var:22s}: β={result_re.params[var]:+.6f} " f"p={result_re.pvalues[var]:.4f} {sig}") |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。import pandas as pd など — 必要なライブラリをまとめて呼び出します。as pd は短い別名(alias)。r, p = stats.pearsonr(...) — Pythonは複数戻り値を同時に受け取れる(タプルアンパック)。306 307 308 309 310 311 312 313 314 | # Hausman 検定(手動) # H₀: 変量効果モデルが一致推定量(ランダム効果と固定効果の差がゼロ) # H₁: 固定効果モデルが適切 b_fe = fe_params[X_vars].values b_re = result_re.params[X_vars].values # 分散共分散行列の差 cov_fe = result_fe.cov.loc[X_vars, X_vars].values cov_re = result_re.cov.loc[X_vars, X_vars].values cov_diff = cov_fe - cov_re |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。x if cond else y は三項演算子。リスト内包表記と組み合わせると、forとifを1行で書けます。315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 | # 固有値が負になりうるため、擬似逆行列を使用 try: cov_diff_inv = np.linalg.pinv(cov_diff) diff = b_fe - b_re hausman_stat = float(diff @ cov_diff_inv @ diff) hausman_df = len(X_vars) hausman_pval = 1 - stats.chi2.cdf(hausman_stat, df=hausman_df) except np.linalg.LinAlgError: hausman_stat = float('nan') hausman_df = len(X_vars) hausman_pval = float('nan') print(f"\n【Hausman 検定】") print(f" H₀: 変量効果モデルが適切(FE = RE)") print(f" H₁: 固定効果モデルが適切(FE ≠ RE)") print(f" χ²統計量 = {hausman_stat:.4f} df = {hausman_df}") print(f" p値 = {hausman_pval:.4f}") if hausman_pval < 0.05: print(" → 固定効果モデルが支持される(p < 0.05)") else: print(" → 変量効果モデルが支持される(p ≥ 0.05)") |
====================================================================== ■ 変量効果モデル & Hausman 検定 ====================================================================== 【変量効果モデル 推定結果(係数のみ)】 保健医療費_千円 : β=+0.001111 p=0.0002 *** 保育所等数_万対 : β=-0.006605 p=0.0000 *** 一般病院数_万対 : β=+0.086619 p=0.0000 *** 高齢化率 : β=-0.837292 p=0.0000 *** 【Hausman 検定】 H₀: 変量効果モデルが適切(FE = RE) H₁: 固定効果モデルが適切(FE ≠ RE) χ²統計量 = -2.1540 df = 4 p値 = 1.0000 → 変量効果モデルが支持される(p ≥ 0.05)
df[col](1列)と df[[col1, col2]](複数列)でカッコの数が違います。リストを渡していると覚えるとミスを減らせます。336 337 338 339 340 341 342 | annual = df_panel.groupby('年度')[['求職圧力指数', '保健医療費_千円', '保育所等数_万対']].mean() annual = annual.sort_index() print("\n" + "=" * 70) print("■ 全国年度平均(主要変数)") print("=" * 70) print(annual.round(5)) |
======================================================================
■ 全国年度平均(主要変数)
======================================================================
求職圧力指数 保健医療費_千円 保育所等数_万対
年度
2012 0.17245 12.25809 2.21462
2013 0.15827 12.13840 2.19384
2014 0.14303 12.13345 2.21477
2015 0.13411 12.26817 2.31696
2016 0.12483 12.69413 2.35114
2017 0.11726 12.49138 2.41710
2018 0.11199 12.85234 2.58850
2019 0.11090 13.51419 2.63852
2020 0.11991 13.74009 2.68258
2021 0.11926 13.78732 2.72341
2022 0.11767 14.38977 2.75816
2023 0.11876 14.42332 2.01176df.groupby('列').apply(関数) — グループごとに関数を適用。時系列や地域別の集計でよく使います。x if cond else y は三項演算子。リスト内包表記と組み合わせると、forとifを1行で書けます。343 344 345 346 347 348 349 350 351 352 353 354 355 356 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 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 | print("図4: 散布図を作成中...") # 3つの代表年度 years_sc = [2014, 2018, 2022] df_sc = df_panel[df_panel['年度'].isin(years_sc)].copy() scatter_pairs = [ ('保健医療費_千円', '保健医療費(千円/世帯)'), ('保育所等数_万対', '保育所等数(人口1万対)'), ] fig4, axes4 = plt.subplots(2, 3, figsize=(15, 9)) fig4.suptitle('求職圧力指数 vs 社会保障指標(散布図・複数年比較)\n' 'データ出典: SSDSE-B-2026(e-Stat)', fontsize=13, fontweight='bold', y=1.01) scatter_colors_yr = { 2014: COLORS['primary'], 2018: COLORS['secondary'], 2022: COLORS['success'], } for row_i, (xvar, xlabel) in enumerate(scatter_pairs): for col_i, yr in enumerate(years_sc): ax = axes4[row_i, col_i] df_yr = df_sc[df_sc['年度'] == yr].dropna(subset=['求職圧力指数', xvar]) x = df_yr[xvar].values y = df_yr['求職圧力指数'].values * 1000 # ×1000 で見やすく col = scatter_colors_yr[yr] ax.scatter(x, y, color=col, alpha=0.7, s=55, edgecolors='white', linewidth=0.7, zorder=3) # 回帰直線 if len(x) > 3: X_fit = sm.add_constant(x) fit_yr = sm.OLS(y, X_fit).fit() x_line = np.linspace(x.min(), x.max(), 100) y_line = fit_yr.params[0] + fit_yr.params[1] * x_line ax.plot(x_line, y_line, color=col, linewidth=2.0, linestyle='-', alpha=0.9, zorder=2) r2 = fit_yr.rsquared pv = fit_yr.pvalues[1] sig = '***' if pv < 0.01 else '**' if pv < 0.05 else '*' if pv < 0.10 else 'n.s.' stat_text = f'β={fit_yr.params[1]:.4f}{sig}\nR²={r2:.3f}' else: stat_text = 'データ不足' ax.set_xlabel(xlabel, fontsize=9.5) ax.set_ylabel('求職圧力指数(×1000)', fontsize=9.5) ax.set_title(f'{yr}年度\n{stat_text}', fontsize=10.5, fontweight='bold') ax.grid(True, alpha=0.3) # 上位・下位都道府県にラベル df_yr_sorted = df_yr.assign(y_plot=y).sort_values('y_plot') for _, row in df_yr_sorted.tail(3).iterrows(): ax.annotate(row['都道府県'], (row[xvar], row['求職圧力指数'] * 1000), fontsize=7, alpha=0.8, xytext=(3, 3), textcoords='offset points') for _, row in df_yr_sorted.head(2).iterrows(): ax.annotate(row['都道府県'], (row[xvar], row['求職圧力指数'] * 1000), fontsize=7, alpha=0.8, xytext=(3, -9), textcoords='offset points') plt.tight_layout() fig4.savefig(os.path.join(FIGURE_DIR, '2022_U2_fig4_scatter.png'), bbox_inches='tight', dpi=150) plt.close(fig4) print(" → 2022_U2_fig4_scatter.png 保存完了") |
図4: 散布図を作成中... → 2022_U2_fig4_scatter.png 保存完了
fig, ax = plt.subplots(...) — 図全体(fig)と軸(ax)を作る定番。以降は ax.bar(...) 等で操作。sort_values('列名', ascending=False) — 指定列で並べ替え(降順)。for _, row in df.iterrows() — DataFrameを1行ずつ取り出すループ。1点ずつ描画したいときに使用。sm.add_constant(X) — 切片項(定数1の列)を先頭に追加。statsmodelsで必須。sm.OLS(y, X).fit() — 最小二乗法でモデルを推定。model.params, model.pvalues, model.conf_int() で結果取得。{値:.2f}(小数2桁)、{値:,}(3桁区切り)、{値:>10}(右寄せ10桁)など、覚えると出力が一気に整います。414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 | print("\n" + "=" * 70) print("✓ 全図の生成完了") print("=" * 70) print("\n【主要結果サマリー】") print(f" 固定効果モデル R²(within)= {result_fe.rsquared:.4f}") print(f" 観測数: {int(result_fe.nobs)}") print() for var in X_vars: sig = ('***' if fe_pvalues[var] < 0.01 else '**' if fe_pvalues[var] < 0.05 else '*' if fe_pvalues[var] < 0.10 else 'n.s.') print(f" [{sig}] {var}: β={fe_params[var]:+.6f} p={fe_pvalues[var]:.4f}") print() print(f" Hausman検定: χ²={hausman_stat:.4f} df={hausman_df} p={hausman_pval:.4f}") if hausman_pval < 0.05: print(" → 固定効果モデル採用を支持") else: print(" → 変量効果モデルも許容される") |
====================================================================== ✓ 全図の生成完了 ====================================================================== 【主要結果サマリー】 固定効果モデル R²(within)= 0.8036 観測数: 564 [***] 保健医療費_千円: β=+0.001767 p=0.0000 [***] 保育所等数_万対: β=-0.009532 p=0.0000 [n.s.] 一般病院数_万対: β=+0.010547 p=0.7851 [***] 高齢化率: β=-0.895373 p=0.0000 Hausman検定: χ²=-2.1540 df=4 p=1.0000 → 変量効果モデルも許容される
plt.subplots(figsize=(W, H)) で図サイズ指定、fig.savefig(..., bbox_inches='tight') で余白を自動で詰めて保存。SSDSE-B の47都道府県 × 2012-2023年パネルデータを用い、社会保障指標が経済的脆弱性(求職圧力指数)に与える影響を固定効果モデルで実証した。
| データ | 出典 |
|---|---|
| SSDSE-B-2026(都道府県版) | 統計数理研究所 SSDSE(教育用標準データセット)、政府統計の総合窓口(e-Stat) |
| 月間有効求職者数(一般) | 厚生労働省 職業安定業務統計(e-Stat 収録) |
| 保健医療費(二人以上世帯) | 総務省 家計調査(SSDSE-B 収録) |
| 保育所等数・一般病院数 | 厚生労働省(SSDSE-B 収録) |
本分析は SSDSE-B-2026(実データ)を使用。合成データは使用していない。
統計分析の解釈で初心者がやりがちな勘違いをまとめます。特に「相関と因果の混同」「p値の過信」は研究現場でもよく起きる落とし穴です。本文を読む前にも、読んだ後にも、目を通してみてください。
統計の基本用語を初心者向けに解説します。本文中で見慣れない言葉が出てきたら、ここに戻って確認してください。
統計手法について「何のためか」「結果をどう読むか」を初心者向けに解説します。
この研究をさらに発展させるための3つの方向性を示します。「今回わかったこと(X)」から「次に検証すべき仮説(Y)」を立て、「具体的に何をするか(Z)」まで考えてみましょう。
学んだだけでは身につきません。実際に手を動かすのが最強の学習方法です。本論文のスクリプトをベースに、以下のチャレンジに挑戦してみてください。難易度別に5つ用意しました。
本論文で学んだ手法は、研究の世界だけでなく、行政・企業・NPO の現場でも様々に活用されています。具体的なシーンを紹介します。
この論文を読んで初心者が抱きやすい疑問に、教育的観点から答えます。