このページの分析を自分で再現するには、以下の手順でデータを準備してください。コードの編集は不要です。
data/raw/ フォルダに入れます。html/figures/ に自動保存されます。
日本の地方圏では人口流出が続き、地方消滅・地域経済の縮小が深刻な課題となっている。しかし「なぜ人は地方を去るのか」という転出行動の決定要因は、地域差・時代差が入り混じり、単純な断面データ分析では正確に把握できない。
まず「地方からの人口流出転出行動の決定要因パネル分析」を統計的にとらえることが有効だと考えられる。 その理由は感覚や経験則だけでは、複雑な社会要因の中で「何が本当に効いているか」を見極めにくいからである。 本研究では公開データと統計手法を組み合わせ、この問いに定量的な答えを出すことを目指す。
本研究は、47都道府県 × 12年間(2012〜2023年度)のパネルデータを用いて、転出率の決定要因を固定効果モデル(PanelOLS FE)で推定する。「地域固有の見えない要因」(気候・地形・文化など)を取り除くことで、雇用・住宅・教育環境が転出行動に与える純粋な効果を識別する。
SSDSE-B(都道府県パネル) PanelOLS 固定効果 Hausman 検定 時系列分析 散布図(2021年断面)
SSDSE-B-2026(社会・人口統計体系 都道府県版)を使用。全47都道府県 × 12年度(2012〜2023年)のバランスドパネルデータで、観測数は564である。
| 変数名 | 定義・計算式 | SSDSE-B 列コード | 予想効果 |
|---|---|---|---|
| 転出率(被説明変数) | 転出者数 / 総人口 × 1000 | A5102 / A1101 × 1000 | — |
| 純移動率 | (転入者数 − 転出者数) / 総人口 × 1000 | (A5101 − A5102) / A1101 × 1000 | — |
| 求人倍率 | 月間有効求人数 / 月間有効求職者数 | F3103 / F3102 | 正(雇用機会↑→転出↑?) |
| 住宅地価_log | log(標準価格(平均価格)(住宅地)) | log(C5401) | 負(地価高→転出↑?) |
| 高齢化率 | 65歳以上人口 / 総人口 × 100 | A1303 / A1101 × 100 | 負(高齢化→若者少→転出↓) |
| 大学学生数_log | log(大学学生数) | log(E6302) | 正(学生多→若年転入→転出↑?) |
人口移動の分析では「転出率」と「純移動率」の2つを区別することが重要。転出率は人口流出の規模を示し、純移動率は人口変動の方向(増減)を示す。
1 2 3 4 5 6 7 8 9 10 11 12 13 | 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 from linearmodels.panel import PanelOLS, RandomEffects warnings.filterwarnings('ignore') |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。import pandas as pd など — 必要なライブラリをまとめて呼び出します。as pd は短い別名(alias)。matplotlib.use('Agg') — グラフを画面表示せずファイルに保存するためのおまじない。f"...{x}..." はf-string。文字列の中に {変数} と書くだけで埋め込めて、{x:.2f} のように書式も指定できます。14 15 16 17 18 19 20 21 22 23 24 25 | # ── パス設定 ────────────────────────────────────────────────────────────────── FIG_DIR = 'html/figures' DATA_B = 'data/raw/SSDSE-B-2026.csv' os.makedirs(FIG_DIR, exist_ok=True) plt.rcParams.update({ 'font.family': 'Hiragino Sans', 'axes.unicode_minus': False, 'figure.dpi': 150, 'axes.spines.top': False, 'axes.spines.right': False, }) |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。os.makedirs('html/figures', exist_ok=True) — 図の保存先フォルダを作る(既にあってもOK)。df['A'] / df['B'] — pandasの列同士の四則演算は要素ごと(element-wise)。forループ不要なのが強み。26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 | # ── 地域区分マップ ───────────────────────────────────────────────────────────── REGION_MAP = { '北海道': '北海道・東北', '青森県': '北海道・東北', '岩手県': '北海道・東北', '宮城県': '北海道・東北', '秋田県': '北海道・東北', '山形県': '北海道・東北', '福島県': '北海道・東北', '茨城県': '関東', '栃木県': '関東', '群馬県': '関東', '埼玉県': '関東', '千葉県': '関東', '東京都': '関東', '神奈川県': '関東', '新潟県': '中部', '富山県': '中部', '石川県': '中部', '福井県': '中部', '山梨県': '中部', '長野県': '中部', '岐阜県': '中部', '静岡県': '中部', '愛知県': '中部', '三重県': '近畿', '滋賀県': '近畿', '京都府': '近畿', '大阪府': '近畿', '兵庫県': '近畿', '奈良県': '近畿', '和歌山県': '近畿', '鳥取県': '中国・四国', '島根県': '中国・四国', '岡山県': '中国・四国', '広島県': '中国・四国', '山口県': '中国・四国', '徳島県': '中国・四国', '香川県': '中国・四国', '愛媛県': '中国・四国', '高知県': '中国・四国', '福岡県': '九州・沖縄', '佐賀県': '九州・沖縄', '長崎県': '九州・沖縄', '熊本県': '九州・沖縄', '大分県': '九州・沖縄', '宮崎県': '九州・沖縄', '鹿児島県': '九州・沖縄', '沖縄県': '九州・沖縄', } REGION_COLORS = { '北海道・東北': '#4e9af1', '関東': '#e05c5c', '中部': '#f0a500', '近畿': '#5cb85c', '中国・四国': '#9b59b6', '九州・沖縄': '#f39c12', } print("=" * 65) print("■ SSDSE-B-2026 実データ読み込み") print("=" * 65) df_raw = pd.read_csv(DATA_B, encoding='cp932', header=1) # 都道府県レベルのみ(地域コード = R + 5桁数字) df = df_raw[df_raw['地域コード'].str.match(r'^R\d{5}$', na=False)].copy() df['年度'] = df['年度'].astype(int) df = df.sort_values(['都道府県', '年度']).reset_index(drop=True) print(f" 読み込み完了: {df['都道府県'].nunique()} 都道府県 × {df['年度'].nunique()} 年度") print(f" 年度範囲: {df['年度'].min()} 〜 {df['年度'].max()}") |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。pd.read_csv(...) でCSVを読み込みます。encoding='cp932' は日本語Windows由来の文字コード、header=1 は「2行目を列名として使う」。df['地域コード'].str.match(r'^R\d{5}', ...) — 正規表現で「R+数字5桁」の行(47都道府県)だけTrueにし、真偽値で行をフィルタ。.astype(int) — 列を整数に変換(年度などを数値比較するため)。sort_values('列名', ascending=False) — 指定列で並べ替え(降順)。.map() は「1対1の置き換え」、.apply() は「関数を当てる」。辞書なら .map()、ロジックなら .apply()。65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 | # ── 変数計算 ───────────────────────────────────────────────────────────────── pop = pd.to_numeric(df['総人口'], errors='coerce') migout = pd.to_numeric(df['転出者数(日本人移動者)'], errors='coerce') migin = pd.to_numeric(df['転入者数(日本人移動者)'], errors='coerce') eff_req = pd.to_numeric(df['月間有効求人数(一般)'], errors='coerce') eff_seek = pd.to_numeric(df['月間有効求職者数(一般)'], errors='coerce') land_res = pd.to_numeric(df['標準価格(平均価格)(住宅地)'], errors='coerce') pop65 = pd.to_numeric(df['65歳以上人口'], errors='coerce') univ_st = pd.to_numeric(df['大学学生数'], errors='coerce') df['転出率'] = migout / pop * 1000 df['純移動率'] = (migin - migout) / pop * 1000 df['求人倍率'] = eff_req / eff_seek.replace(0, np.nan) df['住宅地価_log'] = np.log(land_res.replace(0, np.nan)) df['高齢化率'] = pop65 / pop * 100 df['大学学生数_log'] = np.log(univ_st.replace(0, np.nan)) df['地域'] = df['都道府県'].map(REGION_MAP) |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。[式 for x in リスト] はリスト内包表記。forループでappendする代わりに1行でリストを作れます。83 84 85 86 87 88 89 | # 都道府県省略名(2文字) df['都道府県2'] = df['都道府県'].str[:2] print(f"\n 変数計算後 欠損確認:") for v in ['転出率', '純移動率', '求人倍率', '住宅地価_log', '高齢化率', '大学学生数_log']: n_miss = df[v].isna().sum() print(f" {v:<16}: 欠損 {n_miss} 件") |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。r, p = stats.pearsonr(...) — Pythonは複数戻り値を同時に受け取れる(タプルアンパック)。90 91 92 93 94 | # ── 記述統計 ────────────────────────────────────────────────────────────────── print("\n 2021年断面の記述統計(転出率):") d21 = df[df['年度'] == 2021].copy() print(f" 平均={d21['転出率'].mean():.2f}, SD={d21['転出率'].std():.2f}, " f"最小={d21['転出率'].min():.2f}, 最大={d21['転出率'].max():.2f}") |
=================================================================
■ SSDSE-B-2026 実データ読み込み
=================================================================
読み込み完了: 47 都道府県 × 12 年度
年度範囲: 2012 〜 2023
変数計算後 欠損確認:
転出率 : 欠損 0 件
純移動率 : 欠損 0 件
求人倍率 : 欠損 0 件
住宅地価_log : 欠損 0 件
高齢化率 : 欠損 0 件
大学学生数_log : 欠損 0 件
2021年断面の記述統計(転出率):
平均=16.62, SD=2.75, 最小=9.84, 最大=26.94x if cond else y は三項演算子。リスト内包表記と組み合わせると、forとifを1行で書けます。2012〜2023年の12年間にわたる転出率の推移を地域別(6地域区分)の平均値で可視化する。転出率はどの地域でどの時期に高まったのか、またCOVID-19(2020〜2021年)は転出行動に影響を与えたのかを読み取ることができる。
時系列の観察から、転出率は単純に「地方が低く都市が高い」というわけではなく、都市においても転出が多い(転入も多い)という構造的な特徴がある。純移動率(転入−転出)こそが、地域の人口動向をより正確に反映する。
96 97 98 99 100 101 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("\n図1: 転出率の時系列推移を作成中...") ts = (df.groupby(['年度', '地域'])['転出率'] .mean() .reset_index() .pivot(index='年度', columns='地域', values='転出率')) fig1, ax1 = plt.subplots(figsize=(11, 5.5)) for region, color in REGION_COLORS.items(): if region in ts.columns: ax1.plot(ts.index, ts[region], marker='o', markersize=4, linewidth=2, color=color, label=region) # 最終年ラベル ax1.annotate( region, xy=(ts.index[-1], ts[region].iloc[-1]), xytext=(3, 0), textcoords='offset points', fontsize=8, color=color, va='center' ) ax1.axvline(2021, color='gray', linestyle='--', linewidth=1.0, alpha=0.7, label='2021年(論文対象)') ax1.set_xlabel('年度', fontsize=12) ax1.set_ylabel('転出率(人口千対)', fontsize=12) ax1.set_title('転出率の時系列推移(地域別平均)\n(実データ:SSDSE-B-2026, 47都道府県)', fontsize=13, fontweight='bold') ax1.set_xticks(range(df['年度'].min(), df['年度'].max() + 1, 2)) ax1.legend(fontsize=8, loc='upper left', ncol=2) ax1.grid(True, alpha=0.25) plt.tight_layout() fig1.savefig(os.path.join(FIG_DIR, '2021_U5_1_fig1_timeseries.png'), bbox_inches='tight', dpi=150) plt.close(fig1) print(" → 2021_U5_1_fig1_timeseries.png 保存完了") |
図1: 転出率の時系列推移を作成中... → 2021_U5_1_fig1_timeseries.png 保存完了
df.groupby('列').apply(関数) — グループごとに関数を適用。時系列や地域別の集計でよく使います。fig, ax = plt.subplots(...) — 図全体(fig)と軸(ax)を作る定番。以降は ax.bar(...) 等で操作。ax.axhline / ax.axvline — 水平/垂直の点線。平均線や基準線として定番。.map() は「1対1の置き換え」、.apply() は「関数を当てる」。辞書なら .map()、ロジックなら .apply()。2021年度の断面データで47都道府県の転出率を比較する。転出率が高い都道府県・低い都道府県の特徴を地域別(色分け)で視覚化する。
| 順位 | 都道府県 | 転出率(‰) | 地域区分 | 特徴 |
|---|---|---|---|---|
| 1位(最高) | 東京都 | 26.9 | 関東 | 転入も多く「回転型」の高流動地域 |
| 2位 | 神奈川県 | 20台 | 関東 | 首都圏は転出・転入ともに活発 |
| 47位(最低) | 北海道 | 9.8 | 北海道・東北 | 人口少・移動少の定着型地域 |
| 全国平均 | 47都道府県 | 16.6 | — | SD=2.75, 変動係数=16.6% |
131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 | print("図2: 転出率都道府県別ランキングを作成中...") d21_sorted = d21.sort_values('転出率', ascending=True).copy() colors_bar = [REGION_COLORS.get(REGION_MAP.get(p, ''), '#aaa') for p in d21_sorted['都道府県']] fig2, ax2 = plt.subplots(figsize=(9, 11)) bars = ax2.barh( d21_sorted['都道府県'], d21_sorted['転出率'], color=colors_bar, alpha=0.85, edgecolor='white', height=0.75 ) national_mean = d21['転出率'].mean() ax2.axvline(national_mean, color='#333', linestyle='--', linewidth=1.3, label=f'全国平均 {national_mean:.1f}‰') ax2.set_xlabel('転出率(人口千対, ‰)', fontsize=11) ax2.set_title('都道府県別 転出率ランキング(2021年)\n(実データ:SSDSE-B-2026)', fontsize=12, fontweight='bold') |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。fig, ax = plt.subplots(...) — 図全体(fig)と軸(ax)を作る定番。以降は ax.bar(...) 等で操作。sort_values('列名', ascending=False) — 指定列で並べ替え(降順)。ax.axhline / ax.axvline — 水平/垂直の点線。平均線や基準線として定番。[式 for x in リスト] はリスト内包表記。forループでappendする代わりに1行でリストを作れます。149 150 151 152 153 154 155 156 157 158 159 160 161 162 | # 凡例(地域別色) patches = [mpatches.Patch(color=c, label=r) for r, c in REGION_COLORS.items()] ax2.legend(handles=patches, fontsize=8, loc='lower right') ax2.grid(axis='x', alpha=0.25) # 値ラベル for bar, val in zip(bars, d21_sorted['転出率']): ax2.text(val + 0.05, bar.get_y() + bar.get_height() / 2, f'{val:.1f}', va='center', fontsize=7) plt.tight_layout() fig2.savefig(os.path.join(FIG_DIR, '2021_U5_1_fig2_ranking.png'), bbox_inches='tight', dpi=150) plt.close(fig2) print(" → 2021_U5_1_fig2_ranking.png 保存完了") |
図2: 転出率都道府県別ランキングを作成中... → 2021_U5_1_fig2_ranking.png 保存完了
r, p = stats.pearsonr(...) — Pythonは複数戻り値を同時に受け取れる(タプルアンパック)。「転出行動の決定要因」を识別するために、都道府県固定効果モデル(PanelOLS FE)を推定する。固定効果の導入により、都道府県ごとに異なる「観察できない要因」(気候・地形・文化的背景など)を制御した上で、経済・社会変数の影響を推定できる。
| 説明変数 | 係数 β | 標準誤差 | t値 | p値 | 有意性 |
|---|---|---|---|---|---|
| 求人倍率 | +0.5807 | 0.2235 | 2.598 | 0.0097 | *** |
| 住宅地価_log | −0.2231 | 0.8089 | −0.276 | 0.7828 | n.s. |
| 高齢化率 | −0.1114 | 0.0349 | −3.192 | 0.0015 | *** |
| 大学学生数_log | +2.7148 | 1.2781 | 2.124 | 0.0341 | ** |
観測数: 564 (47都道府県 × 12年度) | Within R² = 0.0853 | 都道府県クラスター標準誤差
*** p<0.01, ** p<0.05, * p<0.1
都道府県には観測できない固有の特性(気候・歴史・文化・地理的位置など)が転出行動に影響を与えている。固定効果モデルはこれを「都道府県ダミー」として制御し、時間変動する要因の純効果を推定できる。
パネルモデルでは「固定効果(FE)」か「ランダム効果(RE)」かの選択が重要。Hausman検定は「FEとREの係数推定値の差が有意かどうか」を検定する。p<0.05であればFEを支持、p≥0.05であればREも有力となる。
本分析の結果:H = 1.74, p = 0.784 → REも有力(FEを棄却できない状況)。ただし理論的に地域固有効果との相関が疑われる場合はFEが安全策。
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 212 | print("図3: FE 係数プロットを作成中...") coef_df = pd.DataFrame({ '変数': EXOG_VARS, '係数': [res_fe.params[v] for v in EXOG_VARS], 'SE': [res_fe.std_errors[v] for v in EXOG_VARS], 'p値': [res_fe.pvalues[v] for v in EXOG_VARS], }).sort_values('係数', ascending=True).reset_index(drop=True) coef_df['CI_lo'] = coef_df['係数'] - 1.96 * coef_df['SE'] coef_df['CI_hi'] = coef_df['係数'] + 1.96 * coef_df['SE'] coef_df['sig'] = coef_df['p値'] < 0.05 colors_fe = ['#C62828' if sig else '#90A4AE' for sig in coef_df['sig']] fig3, ax3 = plt.subplots(figsize=(9, 5)) ax3.barh(coef_df['変数'], coef_df['係数'], color=colors_fe, alpha=0.85, xerr=1.96 * coef_df['SE'], capsize=5, error_kw={'elinewidth': 1.5, 'ecolor': '#555'}, edgecolor='white', height=0.55) ax3.axvline(0, color='black', linewidth=1.2) for _, row in coef_df.iterrows(): label = f"{row['係数']:.4f}" if row['p値'] < 0.01: label += '***' elif row['p値'] < 0.05: label += '**' elif row['p値'] < 0.1: label += '*' offset = 0.0005 if row['係数'] >= 0 else -0.0005 ha = 'left' if row['係数'] >= 0 else 'right' ax3.text(row['係数'] + offset, row['変数'], label, va='center', fontsize=9, fontweight='bold', ha=ha) ax3.set_xlabel('回帰係数(転出率, 単位:人口千対)', fontsize=11) ax3.set_title('PanelOLS 固定効果モデル(FE)推定結果\n被説明変数:転出率 *** p<0.01, ** p<0.05, * p<0.1', fontsize=12, fontweight='bold') ax3.grid(axis='x', alpha=0.25) sig_patch = mpatches.Patch(color='#C62828', alpha=0.85, label='p<0.05(有意)') ns_patch = mpatches.Patch(color='#90A4AE', alpha=0.85, label='p≥0.05(非有意)') ax3.legend(handles=[sig_patch, ns_patch], fontsize=9, loc='lower right') plt.tight_layout() fig3.savefig(os.path.join(FIG_DIR, '2021_U5_1_fig3_fe_coef.png'), bbox_inches='tight', dpi=150) plt.close(fig3) print(" → 2021_U5_1_fig3_fe_coef.png 保存完了") |
図3: FE 係数プロットを作成中... → 2021_U5_1_fig3_fe_coef.png 保存完了
fig, ax = plt.subplots(...) — 図全体(fig)と軸(ax)を作る定番。以降は ax.bar(...) 等で操作。sort_values('列名', ascending=False) — 指定列で並べ替え(降順)。ax.axhline / ax.axvline — 水平/垂直の点線。平均線や基準線として定番。for _, row in df.iterrows() — DataFrameを1行ずつ取り出すループ。1点ずつ描画したいときに使用。r, p = stats.pearsonr(...) — Pythonは複数戻り値を同時に受け取れる(タプルアンパック)。2021年の断面データで「求人倍率(雇用機会)」と「純移動率(転入超過か転出超過か)」の関係を都道府県ラベル付きで確認する。雇用機会が豊富な地域ほど純移動率が高い(転入超過)という仮説を検証する。
この「求人倍率↑→純移動率↓」という関係は、地方の求人倍率が高いのは「低賃金・非正規雇用での人手不足」に起因することが多く、若年層を引き付ける「魅力的な雇用」とは質的に異なる可能性を示唆している。賃金水準や雇用の質を加味した指標の使用が今後の課題となる。
パネル分析の結果から、転出率に有意に影響する要因が特定された。政策的示唆として:
1. 高齢化率(負効果):高齢化が進むと「転出する若者そのものが減少」する人口構造的縮退が示唆される。若年定着施策の早期実施が重要。
2. 求人倍率(正効果):雇用の量だけでなく「質(賃金・キャリア)」の向上が純移動率の改善に必要。単なる求人数の拡大では転出抑制にならない可能性。
3. 大学学生数(正効果):地方大学の充実が必ずしも定住を促さない「学生の送り出し機能」に注意。卒業後の地元就職機会とセットの施策が必要。
214 215 216 217 218 219 220 221 222 223 224 225 226 227 | print("\n" + "=" * 65) print("■ PanelOLS 固定効果モデル推定") print("=" * 65) # パネルデータ用マルチインデックス設定 EXOG_VARS = ['求人倍率', '住宅地価_log', '高齢化率', '大学学生数_log'] ENDOG_VAR = '転出率' df_panel = df[['都道府県', '年度', ENDOG_VAR] + EXOG_VARS].dropna().copy() df_panel = df_panel.set_index(['都道府県', '年度']) y_fe = df_panel[ENDOG_VAR] X_fe = sm.add_constant(df_panel[EXOG_VARS]) X_fe.columns = ['const'] + EXOG_VARS |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。sm.add_constant(X) — 切片項(定数1の列)を先頭に追加。statsmodelsで必須。df['A'] / df['B'] — pandasの列同士の四則演算は要素ごと(element-wise)。forループ不要なのが強み。228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 | # PanelOLS 固定効果 model_fe = PanelOLS(y_fe, X_fe, entity_effects=True) res_fe = model_fe.fit(cov_type='clustered', cluster_entity=True) print("\n 【固定効果モデル(FE)推定結果】") print(f" 観測数: {res_fe.nobs}, 都道府県数: {df_panel.index.get_level_values(0).nunique()}") print(f" Within R²: {res_fe.rsquared_within:.4f}") print() param_names = [v for v in EXOG_VARS] print(f" {'変数':<18} {'係数':>10} {'SE':>10} {'t':>8} {'p値':>8}") print(f" {'-'*56}") for vname in EXOG_VARS: coef = res_fe.params[vname] se = res_fe.std_errors[vname] tval = res_fe.tstats[vname] pval = res_fe.pvalues[vname] sig = '***' if pval < 0.01 else ('**' if pval < 0.05 else ('*' if pval < 0.1 else '')) print(f" {vname:<18} {coef:>10.4f} {se:>10.4f} {tval:>8.3f} {pval:>8.4f} {sig}") |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。.map() は「1対1の置き換え」、.apply() は「関数を当てる」。辞書なら .map()、ロジックなら .apply()。246 247 248 249 250 251 252 | # ── Hausman 検定(FE vs RE)────────────────────────────────────────────────── print("\n" + "=" * 65) print("■ Hausman 検定(固定効果 vs ランダム効果の選択)") print("=" * 65) model_re = RandomEffects(y_fe, X_fe) res_re = model_re.fit(cov_type='robust') |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。[式 for x in リスト] はリスト内包表記。forループでappendする代わりに1行でリストを作れます。253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 | # Hausman 統計量の手計算 b_fe = res_fe.params[EXOG_VARS].values b_re = res_re.params[EXOG_VARS].values # FEの分散共分散行列のEXOG部分のみ V_fe = res_fe.cov.loc[EXOG_VARS, EXOG_VARS].values V_re = res_re.cov.loc[EXOG_VARS, EXOG_VARS].values diff = b_fe - b_re V_diff = V_fe - V_re try: # 正定値でない場合の対処: 対角要素のみで近似 V_diff_diag = np.diag(np.abs(np.diag(V_diff))) H_stat = float(diff @ np.linalg.pinv(V_diff_diag) @ diff) H_df = len(EXOG_VARS) H_pval = 1 - stats.chi2.cdf(H_stat, df=H_df) except Exception: H_stat, H_df, H_pval = np.nan, len(EXOG_VARS), np.nan print(f"\n Hausman 統計量 H = {H_stat:.4f}") print(f" 自由度 df = {H_df}") print(f" p値 = {H_pval:.4f}") if not np.isnan(H_pval): if H_pval < 0.05: print(" → p < 0.05: 固定効果モデル(FE)を支持") else: print(" → p >= 0.05: ランダム効果モデル(RE)も有力") |
================================================================= ■ PanelOLS 固定効果モデル推定 ================================================================= 【固定効果モデル(FE)推定結果】 観測数: 564, 都道府県数: 47 Within R²: 0.0853 変数 係数 SE t p値 -------------------------------------------------------- 求人倍率 0.5807 0.2235 2.598 0.0097 *** 住宅地価_log -0.2231 0.8089 -0.276 0.7828 高齢化率 -0.1114 0.0349 -3.192 0.0015 *** 大学学生数_log 2.7148 1.2781 2.124 0.0341 ** ================================================================= ■ Hausman 検定(固定効果 vs ランダム効果の選択) ================================================================= Hausman 統計量 H = 1.7380 自由度 df = 4 p値 = 0.7838 → p >= 0.05: ランダム効果モデル(RE)も有力
r, p = stats.pearsonr(...) — Pythonは複数戻り値を同時に受け取れる(タプルアンパック)。280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 | print("図4: 求人倍率 vs 純移動率 散布図を作成中...") d21_sc = d21[['都道府県', '都道府県2', '地域', '求人倍率', '純移動率']].dropna().copy() colors_sc = [REGION_COLORS.get(r, '#aaa') for r in d21_sc['地域']] fig4, ax4 = plt.subplots(figsize=(11, 7)) ax4.scatter(d21_sc['求人倍率'], d21_sc['純移動率'], c=colors_sc, s=70, alpha=0.85, zorder=3, edgecolors='white', linewidths=0.5) for _, row in d21_sc.iterrows(): ax4.annotate( row['都道府県2'], xy=(row['求人倍率'], row['純移動率']), xytext=(3, 3), textcoords='offset points', fontsize=7.5, color='#333', zorder=4 ) |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。fig, ax = plt.subplots(...) — 図全体(fig)と軸(ax)を作る定番。以降は ax.bar(...) 等で操作。for _, row in df.iterrows() — DataFrameを1行ずつ取り出すループ。1点ずつ描画したいときに使用。x if cond else y は三項演算子。リスト内包表記と組み合わせると、forとifを1行で書けます。297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 | # 回帰直線 x_vals = d21_sc['求人倍率'].values y_vals = d21_sc['純移動率'].values coef_sc = np.polyfit(x_vals, y_vals, 1) x_range = np.linspace(x_vals.min(), x_vals.max(), 200) r_val, p_val = stats.pearsonr(x_vals, y_vals) ax4.plot(x_range, np.polyval(coef_sc, x_range), color='black', linewidth=1.5, linestyle='--', label=f'回帰直線 (r={r_val:.3f}, p={p_val:.3f})', zorder=2) ax4.axhline(0, color='#aaa', linewidth=1.0, linestyle='-') ax4.axvline(d21_sc['求人倍率'].mean(), color='#aaa', linewidth=1.0, linestyle=':', label=f'求人倍率 全国平均 ({d21_sc["求人倍率"].mean():.2f})') ax4.set_xlabel('求人倍率(月間有効求人数÷月間有効求職者数)', fontsize=12) ax4.set_ylabel('純移動率(人口千対, ‰)\n(転入率 − 転出率)', fontsize=12) ax4.set_title('求人倍率 vs 純移動率(2021年, 都道府県別)\n(実データ:SSDSE-B-2026)', fontsize=13, fontweight='bold') patches = [mpatches.Patch(color=c, label=r) for r, c in REGION_COLORS.items() if r in d21_sc['地域'].values] ax4.legend(handles=patches + [ plt.Line2D([0], [0], color='black', linestyle='--', label=f'回帰直線 (r={r_val:.3f})') ], fontsize=8, loc='upper left') ax4.grid(True, alpha=0.2) plt.tight_layout() fig4.savefig(os.path.join(FIG_DIR, '2021_U5_1_fig4_scatter.png'), bbox_inches='tight', dpi=150) plt.close(fig4) print(" → 2021_U5_1_fig4_scatter.png 保存完了") |
図4: 求人倍率 vs 純移動率 散布図を作成中... → 2021_U5_1_fig4_scatter.png 保存完了
ax.axhline / ax.axvline — 水平/垂直の点線。平均線や基準線として定番。stats.pearsonr(x, y) — Pearson相関係数 r と p値を同時に返します。df[col](1列)と df[[col1, col2]](複数列)でカッコの数が違います。リストを渡していると覚えるとミスを減らせます。327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 | print("\n" + "=" * 65) print("✓ 全図の生成完了(4枚)") print("=" * 65) print("\n【最終サマリー】") print(f" データ: SSDSE-B-2026 実公的データ") print(f" 47都道府県 × {df['年度'].nunique()} 年度 = {len(df_panel)} 有効観測(パネル)") print(f"\n 転出率(2021年): 全国平均 {d21['転出率'].mean():.2f}‰") print(f" 最大: {d21.loc[d21['転出率'].idxmax(), '都道府県']} {d21['転出率'].max():.2f}‰") print(f" 最小: {d21.loc[d21['転出率'].idxmin(), '都道府県']} {d21['転出率'].min():.2f}‰") print(f"\n FE モデル Within R²: {res_fe.rsquared_within:.4f}") print(f"\n FE 推定結果(主要変数):") for v in EXOG_VARS: coef = res_fe.params[v] pval = res_fe.pvalues[v] sig = '***' if pval < 0.01 else ('**' if pval < 0.05 else ('*' if pval < 0.1 else 'ns')) print(f" {v:<20}: {coef:>+.4f} ({sig})") print(f"\n 求人倍率 vs 純移動率 相関係数: r={r_val:.3f}, p={p_val:.4f}") print(f"\n Hausman 検定: H={H_stat:.4f}, p={H_pval:.4f}") |
=================================================================
✓ 全図の生成完了(4枚)
=================================================================
【最終サマリー】
データ: SSDSE-B-2026 実公的データ
47都道府県 × 12 年度 = 564 有効観測(パネル)
転出率(2021年): 全国平均 16.62‰
最大: 東京都 26.94‰
最小: 北海道 9.84‰
FE モデル Within R²: 0.0853
FE 推定結果(主要変数):
求人倍率 : +0.5807 (***)
住宅地価_log : -0.2231 (ns)
高齢化率 : -0.1114 (***)
大学学生数_log : +2.7148 (**)
求人倍率 vs 純移動率 相関係数: r=-0.569, p=0.0000
Hausman 検定: H=1.7380, p=0.7838df[col](1列)と df[[col1, col2]](複数列)でカッコの数が違います。リストを渡していると覚えるとミスを減らせます。SSDSE-B-2026の47都道府県 × 12年度パネルデータを用いた固定効果モデル分析の結果:
| データ・ファイル | 出典・説明 |
|---|---|
| SSDSE-B-2026.csv | 統計数理研究所 SSDSE(社会・人口統計体系 都道府県版, 2012〜2023年度)
https://www.ism.ac.jp/editsection/ssdse/ |
| 転入・転出者数 | 住民基本台帳人口移動報告(総務省統計局) |
| 月間有効求人・求職者数 | 一般職業紹介状況(厚生労働省) |
| 標準地価(住宅地) | 地価公示(国土交通省 土地総合情報システム) |
| 大学学生数・65歳以上人口 | 学校基本調査(文部科学省)、住民基本台帳(総務省) |
本教育用コードはSSDSE-B-2026の実公的データのみを使用(合成データ・np.random不使用)。
PanelOLS: linearmodels v6.x | Python 3.x | matplotlib + pandas
統計分析の解釈で初心者がやりがちな勘違いをまとめます。特に「相関と因果の混同」「p値の過信」は研究現場でもよく起きる落とし穴です。本文を読む前にも、読んだ後にも、目を通してみてください。
統計の基本用語を初心者向けに解説します。本文中で見慣れない言葉が出てきたら、ここに戻って確認してください。
統計手法について「何のためか」「結果をどう読むか」を初心者向けに解説します。
この研究をさらに発展させるための3つの方向性を示します。「今回わかったこと(X)」から「次に検証すべき仮説(Y)」を立て、「具体的に何をするか(Z)」まで考えてみましょう。
学んだだけでは身につきません。実際に手を動かすのが最強の学習方法です。本論文のスクリプトをベースに、以下のチャレンジに挑戦してみてください。難易度別に5つ用意しました。
本論文で学んだ手法は、研究の世界だけでなく、行政・企業・NPO の現場でも様々に活用されています。具体的なシーンを紹介します。
この論文を読んで初心者が抱きやすい疑問に、教育的観点から答えます。