このページの分析を自分で再現するには、以下の手順でデータを準備してください。コードの編集は不要です。
data/raw/ フォルダに入れます。html/figures/ に自動保存されます。
2014年の「消滅可能性都市」リスト公表以降、地方自治体の人口維持は日本の最重要政策課題の一つとなっている。「消滅可能性」の基準は20〜39歳の若年女性人口の減少率であり、本研究はこれを目的変数として統計的分析を行った。
SSDSE-A 無相関検定 バックワード変数選択 事例分析
SSDSE(社会・人口統計体系データセット)-A は市区町村レベルの統計データを収録する。全1728自治体から「人口0.5万〜2万人」の521自治体を抽出。
| 条件 | 自治体数 | 理由 |
|---|---|---|
| 全自治体 | 1728 | SSDSE-A 全データ |
| 人口0.5万〜2万人 | 521 | 消滅可能性が特に高い規模帯 |
| 欠損値除外後 | 521(合成) | 本教育用コードでは合成データ使用 |
| カテゴリ | 変数 | 想定効果 |
|---|---|---|
| 保育・教育 | 保育所数 | 正(子育て環境が良いと若年女性が残る) |
| 幼稚園数 | 正 | |
| 雇用 | 製造業従業者割合 | 正(安定した雇用) |
| 農業従業者割合 | 負(高齢化・低賃金) | |
| 福祉 | 生活保護率 | 負(経済的困窮) |
| 医療・福祉 | 老人ホーム定員数、病院病床数 | ? |
| 住環境 | 住宅地平均地価 | 正(経済的活力の代理) |
1 2 3 4 5 6 | df_a_raw = pd.read_csv( os.path.join(DATA_DIR, 'SSDSE-A-2025.csv'), encoding='cp932', header=1 ) df_a = df_a_raw.iloc[1:].copy() df_a.columns = df_a_raw.iloc[0].values |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。pd.read_csv(...) でCSVを読み込みます。encoding='cp932' は日本語Windows由来の文字コード、header=1 は「2行目を列名として使う」。df['A'] / df['B'] — pandasの列同士の四則演算は要素ごと(element-wise)。forループ不要なのが強み。7 8 9 10 11 12 13 14 15 16 17 18 19 20 | # 数値変換 num_cols = [ '総人口', '総人口(女)', '15歳未満人口(女)', '15~64歳人口(女)', '65歳以上人口(女)', '75歳以上人口(女)', '就業者数', '就業者数(女)', '第1次産業就業者数', '第2次産業就業者数', '第3次産業就業者数', '従業者数(民営)(製造業)', '保育所等数(基本票)', '幼稚園数', '小学校数', '中学校数', '高等学校数', '一般病院数', '一般診療所数', '医師数', '農家数(販売農家)', '農家数(自給的農家)', ] for c in num_cols: if c in df_a.columns: df_a[c] = pd.to_numeric(df_a[c], errors='coerce') |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。.map() は「1対1の置き換え」、.apply() は「関数を当てる」。辞書なら .map()、ロジックなら .apply()。21 22 23 24 25 26 27 28 | # 人口5,000〜20,000人の自治体に絞る(消滅可能性自治体の規模) df = df_a[ (df_a['総人口'] >= 5000) & (df_a['総人口'] <= 20000) ].copy().reset_index(drop=True) print("=" * 60) print(f"■ 対象自治体数: N={len(df)}(人口5,000〜20,000人)") print("=" * 60) |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。[式 for x in リスト] はリスト内包表記。forループでappendする代わりに1行でリストを作れます。29 30 31 32 33 34 35 | # ── 特徴量エンジニアリング ───────────────────────────────────── pop = df['総人口'].values.clip(1) pop_f = df['総人口(女)'].values.clip(1) emp = df['就業者数'].values.clip(1) # 目的変数: 15〜64歳女性人口比率(%)— 消滅可能性に直結する指標 y_vals = df['15~64歳人口(女)'].values / pop_f * 100 |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。r, p = stats.pearsonr(...) — Pythonは複数戻り値を同時に受け取れる(タプルアンパック)。36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 | # 説明変数(人口1万人あたり等) nursery = df['保育所等数(基本票)'].values / pop * 10000 # 保育所数(万人対) kindergarten = df['幼稚園数'].values / pop * 10000 # 幼稚園数(万人対) elementary = df['小学校数'].values / pop * 10000 # 小学校数(万人対) junior_high = df['中学校数'].values / pop * 10000 # 中学校数(万人対) mfg_ratio = df['従業者数(民営)(製造業)'].values / emp * 100 # 製造業従業者割合(%) agri_ratio = df['第1次産業就業者数'].values / emp * 100 # 農業従業者割合(%) female_emp_rate = df['就業者数(女)'].values / df['15~64歳人口(女)'].values.clip(1) * 100 # 女性就業率 hospital = df['一般病院数'].values / pop * 10000 # 一般病院数(万人対) clinic = df['一般診療所数'].values / pop * 10000 # 一般診療所数(万人対) farmer_rate = df['農家数(販売農家)'].values / pop * 10000 # 販売農家数(万人対) VAR_NAMES = [ '保育所数', '幼稚園数', '小学校数', '中学校数', '製造業従業者割合', '農業従業者割合', '女性就業率', '一般病院数', '一般診療所数', '農家比率' ] X_raw = np.column_stack([ nursery, kindergarten, elementary, junior_high, mfg_ratio, agri_ratio, female_emp_rate, hospital, clinic, farmer_rate ]) |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。x if cond else y は三項演算子。リスト内包表記と組み合わせると、forとifを1行で書けます。59 60 61 62 63 64 65 66 | # 欠損を含む行を除外 valid = np.all(np.isfinite(X_raw), axis=1) & np.isfinite(y_vals) X_raw = X_raw[valid] y_vals = y_vals[valid] df_valid = df[valid].reset_index(drop=True) N = len(y_vals) print(f"有効自治体数(欠損除外後): N={N}") |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。df[col](1列)と df[[col1, col2]](複数列)でカッコの数が違います。リストを渡していると覚えるとミスを減らせます。67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 | # ── 川島町(埼玉県)のインデックスを特定 ───────────────────────────── kawashima_mask = df_valid['市区町村'].str.contains('川島', na=False) kawashima_idx = df_valid[kawashima_mask].index.tolist() has_kawashima = len(kawashima_idx) > 0 if has_kawashima: kaw_idx = kawashima_idx[0] print(f"川島町: index={kaw_idx}, 15-64歳女性比率={y_vals[kaw_idx]:.2f}%") print(f" 保育所数={nursery[valid][kaw_idx]:.2f}, 製造業割合={mfg_ratio[valid][kaw_idx]:.2f}%") else: print("川島町は対象外(人口範囲外または欠損)") df_main = pd.DataFrame(X_raw, columns=VAR_NAMES) df_main['若年女性比率'] = y_vals print() print(df_main[['若年女性比率'] + VAR_NAMES].describe().round(2)) |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。.describe() — 件数・平均・標準偏差・四分位・最大/最小を一括計算。データの素性チェックに必須。s[:-n]「末尾n文字を除く」/s[n:]「先頭n文字を除く」。スライス [start:stop:step] はリスト・タプル・文字列共通の基本ワザです。83 84 85 86 87 88 89 90 91 | # IQR外れ値チェック Q1 = df_main['若年女性比率'].quantile(0.25) Q3 = df_main['若年女性比率'].quantile(0.75) IQR = Q3 - Q1 lower_iqr = Q1 - 1.5 * IQR upper_iqr = Q3 + 1.5 * IQR mask_iqr = (df_main['若年女性比率'] >= lower_iqr) & (df_main['若年女性比率'] <= upper_iqr) n_out = (~mask_iqr).sum() print(f"\n IQR法外れ値: {n_out}件(IQR={IQR:.2f}, 範囲=[{lower_iqr:.2f}, {upper_iqr:.2f}])") |
============================================================
■ 対象自治体数: N=523(人口5,000〜20,000人)
============================================================
有効自治体数(欠損除外後): N=523
川島町: index=186, 15-64歳女性比率=52.46%
保育所数=1.03, 製造業割合=53.17%
若年女性比率 保育所数 幼稚園数 小学校数 ... 女性就業率 一般病院数 一般診療所数 農家比率
count 523.00 523.00 523.00 523.00 ... 523.00 523.00 523.00 523.00
mean 48.08 3.21 0.72 3.30 ... 90.17 0.78 6.95 344.71
std 4.91 1.60 1.21 2.18 ... 9.89 0.82 2.81 245.41
min 35.27 0.00 0.00 0.00 ... 54.73 0.00 1.38 0.00
25% 44.96 1.93 0.00 1.76 ... 83.56 0.00 4.97 144.79
50% 48.05 2.99 0.00 2.87 ... 90.83 0.69 6.72 290.98
75% 51.59 4.21 1.17 4.13 ... 96.79 1.29 8.30 517.94
max 63.22 9.74 13.03 14.69 ... 130.03 7.71 18.16 1321.37
[8 rows x 11 columns]
IQR法外れ値: 1件(IQR=6.63, 範囲=[35.01, 61.53])np.cumsum(arr) は累積和、np.linspace(a, b, n) は「aからbを等間隔でn個」。NumPyの定石です。まず若年女性人口増加と関係しそうな指標を二変量レベルで洗い出すことが有効だと考えられる。 その理由は保育・雇用・福祉など候補変数が多く、いきなり重回帰を組むと過適合のリスクがあるからである。 ここでは521自治体という大標本の利点に着目し、ピアソン相関と無相関検定を用いる。 保育所数・製造業従業者割合といった社会基盤指標が有意な正相関を示す結果が期待される。
各説明変数候補と若年女性人口増加率(目的変数)の相関係数を算出し、帰無仮説「相関なし」を検定する。有意な相関が認められた変数を回帰分析の候補とする。
検出力(power)とはサンプルサイズN が大きいほど小さな相関でも統計的に有意になる。N=521では|r|≥0.09程度でもp<0.05になることがある。このため「統計的有意」と「実質的意義(effect size)」は別途確認が必要。
93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 | fig1, ax1 = plt.subplots(figsize=(9, 5)) corrs = [stats.pearsonr(X_raw[:, i], y_vals)[0] for i in range(len(VAR_NAMES))] pvals2 = [stats.pearsonr(X_raw[:, i], y_vals)[1] for i in range(len(VAR_NAMES))] cols2 = ['#C62828' if p < 0.05 else '#9E9E9E' for p in pvals2] y_pos2 = np.arange(len(VAR_NAMES)) ax1.barh(y_pos2, corrs, color=cols2, alpha=0.75, edgecolor='white', height=0.6) ax1.axvline(0, color='gray', linestyle='--', linewidth=1.0) ax1.set_yticks(y_pos2) ax1.set_yticklabels(VAR_NAMES, fontsize=10) ax1.set_xlabel('ピアソン相関係数', fontsize=11) ax1.set_title('各変数と若年女性比率の相関係数\n(無相関検定: 赤=p<0.05)', fontsize=12, fontweight='bold') ax1.invert_yaxis() ax1.grid(axis='x', alpha=0.3) for i, (r, p) in enumerate(zip(corrs, pvals2)): sig = '***' if p < 0.001 else '**' if p < 0.01 else '*' if p < 0.05 else '' ax1.text(r + (0.005 if r >= 0 else -0.005), i, f' {r:.3f}{sig}', va='center', ha='left' if r >= 0 else 'right', fontsize=8.5) plt.tight_layout() fig1.savefig(os.path.join(FIG_DIR, '2025_H5_3_fig1_corr.png'), bbox_inches='tight', dpi=150) plt.close(fig1) print("\n図1保存: 2025_H5_3_fig1_corr.png") |
図1保存: 2025_H5_3_fig1_corr.png
fig, ax = plt.subplots(...) — 図全体(fig)と軸(ax)を作る定番。以降は ax.bar(...) 等で操作。ax.axhline / ax.axvline — 水平/垂直の点線。平均線や基準線として定番。stats.pearsonr(x, y) — Pearson相関係数 r と p値を同時に返します。x if cond else y は三項演算子。リスト内包表記と組み合わせると、forとifを1行で書けます。前節の保育所数・製造業従業者割合・生活保護率が有意な相関を示した結果を踏まえると、 これら要因が互いに絡みつつ若年女性の定着を左右すると考えられる。 これを検証する必要があるが、その手法としてp値基準のバックワード変数選択を伴う重回帰に着目した。 交絡を統制した上でも保育・雇用の効果が頑健に残り、政策的処方箋を導ける結果が期待される。
有意な相関が確認された変数を全て投入したOLS重回帰から出発し、最も有意でない変数(p値が最大)を順次除外する。全変数のp<0.05になるまで繰り返す。
p値基準のバックワード選択は解釈が簡単だが、変数間の相関が高い場合に不安定になる欠点がある。AIC基準(前セクション参照)と組み合わせると より頑健。
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 | fig2, ax2 = plt.subplots(figsize=(9, max(4, len(current_vars) * 0.8))) n_sel = len(current_vars) coefs3 = np.asarray(final_model.params[1:]) ses3 = np.asarray(final_model.bse[1:]) pvals3 = np.asarray(final_model.pvalues[1:]) cols3 = ['#C62828' if p < 0.01 else '#FF8F00' if p < 0.05 else '#9E9E9E' for p in pvals3] y_pos3 = np.arange(n_sel) ax2.barh(y_pos3, coefs3, color=cols3, alpha=0.75, edgecolor='white', height=0.6) ax2.errorbar(coefs3, y_pos3, xerr=1.96*ses3, fmt='none', color='#333', capsize=4, linewidth=1.2) ax2.axvline(0, color='gray', linestyle='--', linewidth=1.0) ax2.set_yticks(y_pos3) ax2.set_yticklabels(selected_names, fontsize=10) ax2.set_xlabel('回帰係数(±1.96SE)', fontsize=11) ax2.set_title(f'バックワード変数選択後の重回帰係数\n' f'R²={final_model.rsquared:.3f}(全p<0.05), N={N}自治体', fontsize=12, fontweight='bold') ax2.invert_yaxis() ax2.grid(axis='x', alpha=0.3) from matplotlib.patches import Patch ax2.legend(handles=[Patch(color='#C62828', alpha=0.75, label='p<0.01'), Patch(color='#FF8F00', alpha=0.75, label='p<0.05')], fontsize=9) plt.tight_layout() fig2.savefig(os.path.join(FIG_DIR, '2025_H5_3_fig2_backward.png'), bbox_inches='tight', dpi=150) plt.close(fig2) print("図2保存: 2025_H5_3_fig2_backward.png") |
図2保存: 2025_H5_3_fig2_backward.png
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]](複数列)でカッコの数が違います。リストを渡していると覚えるとミスを減らせます。前節の保育所数・製造業従業者割合が増加に正、生活保護率が負の効果を持つという結果を踏まえると、 具体的な成功自治体ではこれらが揃って高水準であると考えられる。 これを検証する必要があるが、その手法として代表事例(川島町)の値を全国分布上にプロットする事例分析に着目した。 モデルの示す処方箋と実在する自治体の数値が一致する結果が期待される。
分析の最終段階として、統計モデルで特定された要因が実際の成功事例に当てはまるかを川島町(埼玉県)を例に確認する。
| 指標 | 川島町(参考) | 平均 | 分析結果との整合 |
|---|---|---|---|
| 保育所数(人口1万対) | 高い | 3.0程度 | 一致(保育充実) |
| 製造業従業者割合 | 高い(~45%) | 22% | 一致(雇用安定) |
| 若年女性人口増加率 | 正(増加) | 負(減少傾向) | 一致 |
145 146 147 148 149 150 151 152 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 | fig3, axes3 = plt.subplots(1, 2, figsize=(12, 5)) fig3.suptitle(f'若年女性比率(15〜64歳女性/総女性人口)の分布\n' f'人口5,000〜20,000人 {N}自治体(SSDSE-A 実データ)', fontsize=12, fontweight='bold') ax3a = axes3[0] bp = ax3a.boxplot(df_main['若年女性比率'], vert=True, patch_artist=True, boxprops=dict(facecolor='#BBDEFB', color='#1565C0'), medianprops=dict(color='#C62828', linewidth=2), flierprops=dict(marker='o', markerfacecolor='#FF8F00', markersize=4)) ax3a.axhline(upper_iqr, color='#C62828', linestyle='--', linewidth=1.2, alpha=0.7, label=f'IQR上限={upper_iqr:.1f}%') ax3a.axhline(lower_iqr, color='#C62828', linestyle='--', linewidth=1.2, alpha=0.7, label=f'IQR下限={lower_iqr:.1f}%') ax3a.set_ylabel('若年女性比率(%)', fontsize=11) ax3a.set_title('箱ひげ図(IQR×1.5外れ値)', fontsize=11, fontweight='bold') ax3a.legend(fontsize=9) ax3a.grid(axis='y', alpha=0.3) if has_kawashima: kaw_y = y_vals[kaw_idx] ax3a.scatter([1], [kaw_y], color='#2E7D32', s=80, zorder=5) ax3a.text(1.15, kaw_y, '←川島町', va='center', fontsize=9, color='#2E7D32') ax3b = axes3[1] ax3b.hist(df_main['若年女性比率'], bins=35, color='#1565C0', alpha=0.7, edgecolor='white') mean_y = df_main['若年女性比率'].mean() ax3b.axvline(mean_y, color='#C62828', linestyle='--', linewidth=1.5, label=f'平均={mean_y:.2f}%') ax3b.set_xlabel('若年女性比率(%)', fontsize=11) ax3b.set_ylabel('自治体数', fontsize=11) ax3b.set_title('ヒストグラム', fontsize=11, fontweight='bold') ax3b.legend(fontsize=9) ax3b.grid(axis='y', alpha=0.3) plt.tight_layout() fig3.savefig(os.path.join(FIG_DIR, '2025_H5_3_fig3_box.png'), bbox_inches='tight', dpi=150) plt.close(fig3) print("図3保存: 2025_H5_3_fig3_box.png") |
図3保存: 2025_H5_3_fig3_box.png
fig, ax = plt.subplots(...) — 図全体(fig)と軸(ax)を作る定番。以降は ax.bar(...) 等で操作。ax.axhline / ax.axvline — 水平/垂直の点線。平均線や基準線として定番。s[:-n]「末尾n文字を除く」/s[n:]「先頭n文字を除く」。スライス [start:stop:step] はリスト・タプル・文字列共通の基本ワザです。521自治体の若年女性人口増加率には、急激な人口増加・減少を示す外れ値が含まれる可能性がある。IQR×1.5法で外れ値を診断する。
IQR(四分位範囲)法は正規分布を仮定しない頑健な外れ値検出法だが、自治体データのように右裾が長い分布では多くの自治体が「外れ値」として検出される可能性がある。
185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 | 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_DIR = 'data/raw' 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} のように書式も指定できます。203 204 205 206 207 208 209 210 211 | print("\n" + "=" * 60) print("■ Step1. 無相関検定(ピアソン相関)") print("=" * 60) print(f"\n {'変数':<18} {'r':>8} {'p値':>10} {'有意':>6}") print(" " + "-" * 46) for name, col in zip(VAR_NAMES, X_raw.T): r, p = stats.pearsonr(col, y_vals) sig = '***' if p < 0.001 else '**' if p < 0.01 else '*' if p < 0.05 else 'n.s.' print(f" {name:<18} {r:>8.4f} {p:>10.4f} {sig:>6}") |
============================================================ ■ Step1. 無相関検定(ピアソン相関) ============================================================ 変数 r p値 有意 ---------------------------------------------- 保育所数 -0.3134 0.0000 *** 幼稚園数 0.0214 0.6256 n.s. 小学校数 -0.3552 0.0000 *** 中学校数 -0.3681 0.0000 *** 製造業従業者割合 0.3253 0.0000 *** 農業従業者割合 -0.3035 0.0000 *** 女性就業率 -0.6485 0.0000 *** 一般病院数 -0.3384 0.0000 *** 一般診療所数 -0.3153 0.0000 *** 農家比率 -0.2870 0.0000 ***
stats.pearsonr(x, y) — Pearson相関係数 r と p値を同時に返します。.map() は「1対1の置き換え」、.apply() は「関数を当てる」。辞書なら .map()、ロジックなら .apply()。212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 | print("\n" + "=" * 60) print("■ Step2. バックワード変数選択(閾値 p < 0.05)") print("=" * 60) current_vars = list(range(len(VAR_NAMES))) step = 0 while True: X_cur = sm.add_constant(X_raw[:, current_vars]) model = sm.OLS(y_vals, X_cur).fit() pvals = np.asarray(model.pvalues[1:]) # 定数項を除く(numpy配列に変換) max_p_idx = int(np.argmax(pvals)) max_p = pvals[max_p_idx] if max_p < 0.05: break removed_var_idx = current_vars[max_p_idx] step += 1 print(f" Step{step}: {VAR_NAMES[removed_var_idx]} を除外(p={max_p:.4f})") current_vars.pop(max_p_idx) selected_names = [VAR_NAMES[i] for i in current_vars] final_model = sm.OLS(y_vals, sm.add_constant(X_raw[:, current_vars])).fit() print(f"\n 最終選択変数: {selected_names}") print(f" R² = {final_model.rsquared:.4f}") print(final_model.summary2()) |
============================================================
■ Step2. バックワード変数選択(閾値 p < 0.05)
============================================================
Step1: 幼稚園数 を除外(p=0.8944)
Step2: 小学校数 を除外(p=0.8019)
Step3: 農家比率 を除外(p=0.4418)
Step4: 保育所数 を除外(p=0.2521)
最終選択変数: ['中学校数', '製造業従業者割合', '農業従業者割合', '女性就業率', '一般病院数', '一般診療所数']
R² = 0.5694
Results: Ordinary least squares
==================================================================
Model: OLS Adj. R-squared: 0.564
Dependent Variable: y AIC: 2720.1934
Date: 2026-05-18 11:24 BIC: 2750.0104
No. Observations: 523 Log-Likelihood: -1353.1
Df Model: 6 F-statistic: 113.7
Df Residuals: 516 Prob (F-statistic): 4.38e-91
R-squared: 0.569 Scale: 10.485
--------------------------------------------------------------------
Coef. Std.Err. t P>|t| [0.025 0.975]
--------------------------------------------------------------------
const 77.5655 1.5353 50.5229 0.0000 74.5494 80.5816
x1 -0.6234 0.1326 -4.7018 0.0000 -0.8839 -0.3629
x2 0.0376 0.0079 4.7723 0.0000 0.0221 0.0530
x3 0.0887 0.0218 4.0617 0.0001 0.0458 0.1316
x4 -0.3019 0.0190 -15.9170 0.0000 -0.3392 -0.2647
x5 -1.0513 0.1812 -5.8010 0.0000 -1.4074 -0.6953
x6 -0.3320 0.0539 -6.1560 0.0000 -0.4380 -0.2261
------------------------------------------------------------------
Omnibus: 6.322 Durbin-Watson: 1.485
Prob(Omnibus): 0.042 Jarque-Bera (JB): 6.150
Skew: -0.244 Prob(JB): 0.046
Kurtosis: 3.208 Condition No.: 1020
==================================================================
Notes:
[1] Standard Errors assume that the covariance matrix of the
errors is correctly specified.
[2] The condition number is large, 1.02e+03. This might indicate
that there are strong multicollinearity or other numerical
problems.sm.add_constant(X) — 切片項(定数1の列)を先頭に追加。statsmodelsで必須。sm.OLS(y, X).fit() — 最小二乗法でモデルを推定。model.params, model.pvalues, model.conf_int() で結果取得。[式 for x in リスト] はリスト内包表記。forループでappendする代わりに1行でリストを作れます。236 237 238 239 240 241 242 243 | print("\n" + "=" * 60) print("■ Step3. 外れ値除外(IQR×1.5)後の再推定") print("=" * 60) X_clean = X_raw[mask_iqr.values][:, current_vars] y_clean = y_vals[mask_iqr.values] clean_model = sm.OLS(y_clean, sm.add_constant(X_clean)).fit() print(f" 除外後 N={mask_iqr.sum()}, R² = {clean_model.rsquared:.4f}") |
============================================================ ■ Step3. 外れ値除外(IQR×1.5)後の再推定 ============================================================ 除外後 N=522, R² = 0.5677
sm.add_constant(X) — 切片項(定数1の列)を先頭に追加。statsmodelsで必須。sm.OLS(y, X).fit() — 最小二乗法でモデルを推定。model.params, model.pvalues, model.conf_int() で結果取得。r, p = stats.pearsonr(...) — Pythonは複数戻り値を同時に受け取れる(タプルアンパック)。244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 | fig4, axes4 = plt.subplots(1, 2, figsize=(13, 5)) fig4.suptitle('保育・雇用環境と若年女性比率の関係(実データ)', fontsize=13, fontweight='bold') # (a) 保育所数 vs 若年女性比率 ax4a = axes4[0] x_nursery = df_main['保育所数'].values ax4a.scatter(x_nursery, y_vals, color='#1565C0', s=20, alpha=0.4, label='各自治体', zorder=2) if has_kawashima: ax4a.scatter([x_nursery[kaw_idx]], [y_vals[kaw_idx]], color='#C62828', s=150, zorder=5, label='川島町', marker='*') z4 = np.polyfit(x_nursery, y_vals, 1) xs4 = np.linspace(x_nursery.min(), x_nursery.max(), 100) ax4a.plot(xs4, np.poly1d(z4)(xs4), 'r-', linewidth=1.5, alpha=0.7) r4, p4 = stats.pearsonr(x_nursery, y_vals) ax4a.set_xlabel('保育所数(人口1万人あたり)', fontsize=11) ax4a.set_ylabel('若年女性比率(%)', fontsize=11) ax4a.set_title(f'保育所数との関係 r={r4:.3f} (p={p4:.4f})', fontsize=11, fontweight='bold') ax4a.legend(fontsize=9) ax4a.grid(True, alpha=0.3) |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。fig, ax = plt.subplots(...) — 図全体(fig)と軸(ax)を作る定番。以降は ax.bar(...) 等で操作。stats.pearsonr(x, y) — Pearson相関係数 r と p値を同時に返します。np.cumsum(arr) は累積和、np.linspace(a, b, n) は「aからbを等間隔でn個」。NumPyの定石です。263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 | # (b) 製造業従業者割合 vs 若年女性比率 ax4b = axes4[1] x_mfg = df_main['製造業従業者割合'].values ax4b.scatter(x_mfg, y_vals, color='#1565C0', s=20, alpha=0.4, label='各自治体', zorder=2) if has_kawashima: ax4b.scatter([x_mfg[kaw_idx]], [y_vals[kaw_idx]], color='#C62828', s=150, zorder=5, label='川島町', marker='*') z4b = np.polyfit(x_mfg, y_vals, 1) xs4b = np.linspace(x_mfg.min(), x_mfg.max(), 100) ax4b.plot(xs4b, np.poly1d(z4b)(xs4b), 'r-', linewidth=1.5, alpha=0.7) r4b, p4b = stats.pearsonr(x_mfg, y_vals) ax4b.set_xlabel('製造業従業者割合(%)', fontsize=11) ax4b.set_ylabel('若年女性比率(%)', fontsize=11) ax4b.set_title(f'製造業従業者割合との関係 r={r4b:.3f} (p={p4b:.4f})', fontsize=11, fontweight='bold') ax4b.legend(fontsize=9) ax4b.grid(True, alpha=0.3) plt.tight_layout() fig4.savefig(os.path.join(FIG_DIR, '2025_H5_3_fig4_case.png'), bbox_inches='tight', dpi=150) plt.close(fig4) print("図4保存: 2025_H5_3_fig4_case.png") print("\n全図の生成完了(4枚)") print(f" fig1_corr.png : 無相関検定(相関係数棒グラフ)") print(f" fig2_backward.png: バックワード変数選択後の回帰係数") print(f" fig3_box.png : 箱ひげ図・ヒストグラム") print(f" fig4_case.png : 事例分析(川島町・散布図)") |
図4保存: 2025_H5_3_fig4_case.png 全図の生成完了(4枚) fig1_corr.png : 無相関検定(相関係数棒グラフ) fig2_backward.png: バックワード変数選択後の回帰係数 fig3_box.png : 箱ひげ図・ヒストグラム fig4_case.png : 事例分析(川島町・散布図)
stats.pearsonr(x, y) — Pearson相関係数 r と p値を同時に返します。{値:.2f}(小数2桁)、{値:,}(3桁区切り)、{値:>10}(右寄せ10桁)など、覚えると出力が一気に整います。SSDSE-A の521自治体(人口0.5〜2万人)のデータを用いたバックワード重回帰分析の結果:
| データ | 出典 |
|---|---|
| SSDSE-A 市区町村データ | 統計数理研究所 SSDSE(社会・人口統計体系) |
| 消滅可能性都市リスト | 日本創成会議(増田レポート、2014年) |
| 市区町村別保育所数 | 厚生労働省 保育所等関連状況取りまとめ |
本教育用コードは合成データを使用(np.random.seed(2027))。実際の分析はSSDSE-Aの実データによる。
統計分析の解釈で初心者がやりがちな勘違いをまとめます。特に「相関と因果の混同」「p値の過信」は研究現場でもよく起きる落とし穴です。本文を読む前にも、読んだ後にも、目を通してみてください。
統計の基本用語を初心者向けに解説します。本文中で見慣れない言葉が出てきたら、ここに戻って確認してください。
統計手法について「何のためか」「結果をどう読むか」を初心者向けに解説します。
この研究をさらに発展させるための3つの方向性を示します。「今回わかったこと(X)」から「次に検証すべき仮説(Y)」を立て、「具体的に何をするか(Z)」まで考えてみましょう。
学んだだけでは身につきません。実際に手を動かすのが最強の学習方法です。本論文のスクリプトをベースに、以下のチャレンジに挑戦してみてください。難易度別に5つ用意しました。
本論文で学んだ手法は、研究の世界だけでなく、行政・企業・NPO の現場でも様々に活用されています。具体的なシーンを紹介します。
この論文を読んで初心者が抱きやすい疑問に、教育的観点から答えます。