この教材は原論文と同じデータを読み込み、同じ手順で計算し直しています。画面に出る数値と図は、あなたのブラウザがその場で計算した結果です。
| 原論文が使ったデータ | SSDSE-B・社会福祉施設等調査・労働力調査・生産農業所得統計・e-stat 林業産出額・e-stat 漁業産出額・都道府県地価調査 分析単位:都道府県 中核手法:パネルデータ分析・固定効果モデル・ラグモデル |
|---|---|
| この教材が使うデータ |
|
| 原論文(PDF) | 地方移住の決定要因に関するパネルデータ分析ーラグ効果の検証を通じてー 統計数理賞/水本 優希(鳥取県立鳥取湖陵高等学校)、山口 晃(角川ドワンゴ学園 S高等学校) |
▶ ここに書いてある分析は、ページ下部の「🐍 ブラウザで動かす」でそのまま実行できます(インストール不要)。動かしているのは code/2025_H3_suri.py(381 行)そのものです。
このページの分析を自分で再現するには、以下の手順でデータを準備してください。コードの編集は不要です。
data/raw/ フォルダに入れます。html/figures/ に自動保存されます。
「地方移住」は近年の社会トレンドとして注目されているが、何が移住の決定要因となるかについては複雑な時間的ダイナミクスが存在する。例えば「老人福祉施設が多い」という情報は、当期の転入者には「介護施設依存の高齢者が多い地域」と見なされて忌避されるが、1〜2年後には「介護環境が整っている地域」として評価が変わるという逆転現象が起こりうる。
| データ | 出典 | 主要変数 |
|---|---|---|
| SSDSE-B 都道府県別時系列 | 統計センター | 転入者数(男女別)・一般病院数・年平均気温・総人口 |
| 社会福祉施設等調査 | 厚生労働省 | 老人福祉施設数(教育目的の代理変数を使用) |
| 都道府県地価調査 | 国土交通省 | 基準地価(教育目的の代理変数を使用) |
| 農業・林業・漁業産出額 | 農林水産省 | 各産業産出額(教育目的の代理変数を使用) |
| 区分 | 変数名 | 説明 |
|---|---|---|
| 目的変数 | 転入者数(男性) | 日本人転入者数(男性, 人) |
| 転入者数(女性) | 日本人転入者数(女性, 人) | |
| 説明変数 | 老人福祉施設数 | 都道府県内の老人ホーム等施設数 |
| 一般病院数 | SSDSE-B I510120 | |
| 年平均気温 | SSDSE-B B4101(℃) | |
| 総人口 | SSDSE-B A1101(人) | |
| 基準地価 | 住宅地の平均地価(千円/m²) | |
| 林業産出額 | 都道府県別林業産出額(億円) |
パネルデータとは「同じ個体(都道府県)を複数時点にわたって観察したデータ」。47都道府県 × 12年間 = 最大564観測値。ラグ変数を作ると先頭の年のデータが失われるため、ラグ2期では実質T=10。
まずどの推定モデルが適切かを統計的に判定することが有効だと考えられる。 その理由は都道府県ごとに気候・歴史的経緯など観測されない固有要因が存在し、それを無視すると係数が歪むからである。 ここでは個体固有効果の取り扱いに着目し、Breusch-Pagan検定とHausman検定を組み合わせて用いる。 検定の組み合わせから「固定効果モデルが最適」という結論が得られる結果が期待される。
パネルデータ分析では、Pooled OLS(プール最小二乗法)・固定効果モデル(FE)・変量効果モデル(RE)の3種類が候補となる。適切なモデルを統計的検定で選択する。
「個体固有効果の分散がゼロ(=Pooled OLSで十分)」という帰無仮説を検定。
「個体固有効果と説明変数が無相関(=変量効果モデルが一致推定量)」という帰無仮説を検定。
固定効果モデルは、各個体(都道府県)の時系列平均を差し引く「within変換」によって個体固有の不変要因を除去し、純粋に時間変動する部分だけを分析する。
1 2 3 4 5 6 7 8 9 10 11 12 | 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 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} のように書式も指定できます。13 14 15 16 17 18 19 20 21 22 23 24 | # ── パス設定 ────────────────────────────────────────────────────────── DATA_DIR = 'data/raw' FIG_DIR = 'html/figures' 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ループ不要なのが強み。25 26 27 28 29 30 | # ── SSDSE-B-2026.csv 読み込み ────────────────────────────────────── print("=== SSDSE-B 読み込み ===") df_raw = pd.read_csv( os.path.join(DATA_DIR, 'SSDSE-B-2026.csv'), encoding='cp932', header=1 ) |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。pd.read_csv(...) でCSVを読み込みます。encoding='cp932' は日本語Windows由来の文字コード、header=1 は「2行目を列名として使う」。.map() は「1対1の置き換え」、.apply() は「関数を当てる」。辞書なら .map()、ロジックなら .apply()。31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 | # 47都道府県のみ(地域コード = R + 5桁数字) df_raw = df_raw[df_raw['地域コード'].str.match(r'^R\d{5}$', na=False)].copy() df_raw['年度'] = pd.to_numeric(df_raw['年度'], errors='coerce') # 数値型に変換 num_cols = [ '総人口', '65歳以上人口', '15~64歳人口', '15歳未満人口', '転入者数(日本人移動者)', '転入者数(日本人移動者)(男)', '転出者数(日本人移動者)', '保育所等数', '年平均気温', '婚姻件数', '高等学校卒業者数', '高等学校卒業者のうち進学者数', '出生数', '死亡数', '教育費(二人以上の世帯)', ] for c in num_cols: df_raw[c] = pd.to_numeric(df_raw[c], errors='coerce') df_raw = df_raw.sort_values(['都道府県', '年度']).reset_index(drop=True) print(f"SSDSE-B 読み込み完了: {len(df_raw)}行 ({df_raw['都道府県'].nunique()}都道府県, " f"年度: {sorted(df_raw['年度'].dropna().astype(int).unique())})") |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。df['地域コード'].str.match(r'^R\d{5}', ...) — 正規表現で「R+数字5桁」の行(47都道府県)だけTrueにし、真偽値で行をフィルタ。.astype(int) — 列を整数に変換(年度などを数値比較するため)。sort_values('列名', ascending=False) — 指定列で並べ替え(降順)。[式 for x in リスト] はリスト内包表記。forループでappendする代わりに1行でリストを作れます。51 52 53 54 55 56 57 58 59 | # ── SSDSE-E-2026.csv 読み込み(面積・県民所得) ───────────────────── print("=== SSDSE-E 読み込み ===") df_e_raw = pd.read_csv( os.path.join(DATA_DIR, 'SSDSE-E-2026.csv'), encoding='cp932', header=1 ) df_e = df_e_raw.iloc[1:].copy() df_e.columns = df_e_raw.iloc[0].values df_e = df_e[df_e['都道府県'] != '全国'].reset_index(drop=True) |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。pd.read_csv(...) でCSVを読み込みます。encoding='cp932' は日本語Windows由来の文字コード、header=1 は「2行目を列名として使う」。r, p = stats.pearsonr(...) — Pythonは複数戻り値を同時に受け取れる(タプルアンパック)。60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 | # 面積(ha → km²)と1人当たり県民所得 df_e['面積_km2'] = pd.to_numeric(df_e['総面積(北方地域及び竹島を除く)'], errors='coerce') / 100.0 df_e['県民所得_1人'] = pd.to_numeric(df_e['1人当たり県民所得(平成27年基準)'], errors='coerce') df_cross = df_e[['都道府県', '面積_km2', '県民所得_1人']].copy() print(f"SSDSE-E 読み込み完了: {len(df_cross)}都道府県") # ── パネルデータ構築 ────────────────────────────────────────────── panel = df_raw[['都道府県', '年度', '総人口', '65歳以上人口', '15~64歳人口', '転入者数(日本人移動者)', '転入者数(日本人移動者)(男)', '転出者数(日本人移動者)', '保育所等数', '年平均気温', '婚姻件数', '高等学校卒業者数', '高等学校卒業者のうち進学者数', '教育費(二人以上の世帯)', ]].copy() panel.columns = ['pref', 'year', 'pop', 'pop65', 'pop1564', 'inflow', 'inflow_m', 'outflow', 'nursery', 'avg_temp', 'marriages', 'hs_grads', 'hs_college', 'edu_exp'] |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。x if cond else y は三項演算子。リスト内包表記と組み合わせると、forとifを1行で書けます。79 80 81 82 83 84 85 | # SSDSE-E をマージ(面積のみを使用: 人口密度の計算のため) panel = panel.merge(df_cross[['都道府県', '面積_km2']].rename(columns={'都道府県': 'pref'}), on='pref', how='left') # ── 派生変数の計算(実データのみ) ───────────────────────────────── # 保育所充実度: 保育所等数 / 総人口 × 10000 panel['nursery_rate'] = panel['nursery'] / panel['pop'] * 10000 |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。df[col](1列)と df[[col1, col2]](複数列)でカッコの数が違います。リストを渡していると覚えるとミスを減らせます。86 87 88 89 90 91 92 93 | # 高齢化率: 65歳以上人口 / 総人口 × 100 panel['aging_rate'] = panel['pop65'] / panel['pop'] * 100 # 婚姻率: 婚姻件数 / 15〜64歳人口 × 1000 panel['marriage_rate'] = panel['marriages'] / panel['pop1564'] * 1000 # 大学進学率: 高校卒業者のうち進学者数 / 高校卒業者数 × 100 panel['college_rate'] = panel['hs_college'] / panel['hs_grads'] * 100 |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。s[:-n]「末尾n文字を除く」/s[n:]「先頭n文字を除く」。スライス [start:stop:step] はリスト・タプル・文字列共通の基本ワザです。94 95 96 97 98 99 | # 教育費(二人以上の世帯): 時間変動する所得代理変数(SSDSE-B実データ) # 注: 1人当たり県民所得(SSDSE-E)は時点固定のため固定効果推定では識別不能 panel['edu_expense'] = panel['edu_exp'] # 人口密度: 総人口 / 面積(km²) panel['pop_density'] = panel['pop'] / panel['面積_km2'] |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。np.cumsum(arr) は累積和、np.linspace(a, b, n) は「aからbを等間隔でn個」。NumPyの定石です。100 101 102 103 104 105 106 107 108 109 | # 目的変数: 転入者数(人)※原論文に合わせた線形 panel = panel.sort_values(['pref', 'year']).reset_index(drop=True) # ── ラグ変数の作成 ────────────────────────────────────────────────── LAG_VARS = ['nursery_rate', 'aging_rate', 'avg_temp', 'marriage_rate', 'college_rate', 'edu_expense', 'pop_density'] for var in LAG_VARS: panel[f'{var}_lag1'] = panel.groupby('pref')[var].shift(1) panel[f'{var}_lag2'] = panel.groupby('pref')[var].shift(2) |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。df.groupby('列').apply(関数) — グループごとに関数を適用。時系列や地域別の集計でよく使います。sort_values('列名', ascending=False) — 指定列で並べ替え(降順)。{値:.2f}(小数2桁)、{値:,}(3桁区切り)、{値:>10}(右寄せ10桁)など、覚えると出力が一気に整います。110 111 112 113 114 115 116 117 118 | # ラグ2期まで使えるデータ(2016年〜) panel_est = panel[panel['year'] >= 2016].dropna( subset=['inflow_m'] + LAG_VARS + [f'{v}_lag1' for v in LAG_VARS] + [f'{v}_lag2' for v in LAG_VARS] ).copy() print(f"\n推定用サンプル: {len(panel_est)}観測 " f"({panel_est['pref'].nunique()}都道府県, " f"年度: {sorted(panel_est['year'].unique())})") |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。plt.subplots(figsize=(W, H)) で図サイズ指定、fig.savefig(..., bbox_inches='tight') で余白を自動で詰めて保存。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 144 | # ── 固定効果パネル回帰(within変換) ─────────────────────────────── def within_transform(df, dep_var, expl_vars, id_col='pref'): """within推定量(固定効果モデル): 個体内平均を除去""" df2 = df.copy() all_vars = [dep_var] + expl_vars group_means = df2.groupby(id_col)[all_vars].transform('mean') for v in all_vars: df2[v + '_dm'] = df2[v] - group_means[v] y = df2[dep_var + '_dm'].values X = sm.add_constant(df2[[v + '_dm' for v in expl_vars]].values) res = sm.OLS(y, X).fit(cov_type='HC1') return res EXPL_CURRENT = LAG_VARS EXPL_LAG1 = [v + '_lag1' for v in LAG_VARS] EXPL_LAG2 = [v + '_lag2' for v in LAG_VARS] res_t0 = within_transform(panel_est, 'inflow_m', EXPL_CURRENT) res_t1 = within_transform(panel_est, 'inflow_m', EXPL_LAG1) res_t2 = within_transform(panel_est, 'inflow_m', EXPL_LAG2) print("\n=== 固定効果モデル(当期, 男性転入者数)===") tbl = res_t0.summary2().tables[1] cols_needed = [c for c in ['Coef.', 'Std.Err.', 't', 'P>|t|'] if c in tbl.columns] print(tbl[cols_needed].to_string()) print(f"\nR² (within) = {res_t0.rsquared:.4f}") |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。df.groupby('列').apply(関数) — グループごとに関数を適用。時系列や地域別の集計でよく使います。sm.add_constant(X) — 切片項(定数1の列)を先頭に追加。statsmodelsで必須。sm.OLS(y, X).fit() — 最小二乗法でモデルを推定。model.params, model.pvalues, model.conf_int() で結果取得。.dropna() は欠損行を除去、.copy() は独立したコピーを作る。pandasで警告を防ぐ定石。145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 | # ── Figure 1: 転入者数の時系列推移 ─────────────────────────────── print("\nFigure 1: 転入者数の時系列推移...") YEARS_ALL = sorted(panel['year'].dropna().astype(int).unique()) fig, axes = plt.subplots(1, 2, figsize=(12, 5)) # 全国合計転入者数 nat = panel.groupby('year')['inflow'].sum() / 1e4 # 万人 ax = axes[0] ax.plot(nat.index, nat.values, 'o-', color='#1565C0', lw=2.5) ax.fill_between(nat.index, nat.values, nat.values.min(), alpha=0.15, color='#1565C0') ax.set_xlabel('年度') ax.set_ylabel('全国転入者数(万人)') ax.set_title('全国転入者数(日本人)の推移', fontsize=12, fontweight='bold') ax.set_xticks(YEARS_ALL) ax.set_xticklabels([str(y) for y in YEARS_ALL], rotation=45) |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。.astype(int) — 列を整数に変換(年度などを数値比較するため)。df.groupby('列').apply(関数) — グループごとに関数を適用。時系列や地域別の集計でよく使います。fig, ax = plt.subplots(...) — 図全体(fig)と軸(ax)を作る定番。以降は ax.bar(...) 等で操作。ax.fill_between(...) — 2つの曲線で囲まれた領域を塗りつぶし。Lorenz曲線の格差面積などを可視化。f"...{x}..." はf-string。文字列の中に {変数} と書くだけで埋め込めて、{x:.2f} のように書式も指定できます。163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 | # 地方主要県の推移 ax2 = axes[1] highlight_prefs = ['鳥取県', '島根県', '長野県', '宮崎県'] colors_h = ['#E53935', '#8E24AA', '#1E88E5', '#43A047'] for pref, col in zip(highlight_prefs, colors_h): sub = panel[panel['pref'] == pref].sort_values('year') ax2.plot(sub['year'], sub['inflow'] / 1000, 'o-', color=col, lw=2, label=pref) ax2.set_xlabel('年度') ax2.set_ylabel('転入者数(千人)') ax2.set_title('地方主要県の転入者数推移', fontsize=12, fontweight='bold') ax2.legend(fontsize=10, ncol=2) ax2.set_xticks(YEARS_ALL) ax2.set_xticklabels([str(y) for y in YEARS_ALL], rotation=45) plt.tight_layout() plt.savefig(os.path.join(FIG_DIR, '2025_H3_fig1_trend.png'), bbox_inches='tight') plt.close() |
=== SSDSE-B 読み込み ===
SSDSE-B 読み込み完了: 564行 (47都道府県, 年度: [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)])
=== SSDSE-E 読み込み ===
SSDSE-E 読み込み完了: 47都道府県
推定用サンプル: 376観測 (47都道府県, 年度: [np.int64(2016), np.int64(2017), np.int64(2018), np.int64(2019), np.int64(2020), np.int64(2021), np.int64(2022), np.int64(2023)])
=== 固定効果モデル(当期, 男性転入者数)===
Coef. Std.Err.
const -5.639933e-13 49.555864
x1 -1.441553e+02 164.936000
x2 3.964584e+02 193.465602
x3 -8.361692e+01 145.530137
x4 9.942635e+02 399.742794
x5 -1.515402e+01 49.648256
x6 2.418225e-02 0.019732
x7 -2.395090e+01 12.671620
R² (within) = 0.2143
Figure 1: 転入者数の時系列推移...sort_values('列名', ascending=False) — 指定列で並べ替え(降順)。fig.savefig(..., bbox_inches='tight') — 余白を自動で詰めて保存。plt.close() でメモリ解放。df['A'] / df['B'] — pandasの列同士の四則演算は要素ごと(element-wise)。forループ不要なのが強み。
| 変数 | 推定係数 | 標準誤差 | t値 | p値 | 符号 |
|---|---|---|---|---|---|
| 老人福祉施設数 | 負(−) | — | — | 有意 | − |
| 基準地価 | 負(−) | — | — | 有意 | − |
| 林業産出額 | 負(−) | — | — | 有意 | − |
| 一般病院数 | 正(+) | — | — | 非有意 | + |
| 年平均気温 | 正(+) | — | — | 非有意 | + |
| 総人口 | 正(+) | — | — | 有意 | + |
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 213 214 215 216 217 | print("Figure 1 saved.") # ── Figure 2: 固定効果モデル係数(当期) ──────────────────────────── print("Figure 2: 固定効果モデル係数(当期)...") fig, ax = plt.subplots(figsize=(10, 5)) var_labels = ['保育所充実度\n(保育所数/人口万)', '高齢化率\n(%)', '年平均気温\n(℃)', '婚姻率\n(婚姻数/15-64歳人口千)', '大学進学率\n(%)', '教育費\n(二人以上世帯, 円)', '人口密度\n(人/km²)'] coefs = res_t0.params[1:] cis = np.asarray(res_t0.conf_int())[1:] pvals = res_t0.pvalues[1:] colors_bar = ['#E53935' if c < 0 else '#1E88E5' for c in coefs] sig_marks = ['***' if p < 0.001 else ('**' if p < 0.01 else ('*' if p < 0.05 else '')) for p in pvals] y_pos = np.arange(len(var_labels)) ax.barh(y_pos, coefs, color=colors_bar, alpha=0.8, height=0.6) ax.errorbar(coefs, y_pos, xerr=[coefs - cis[:, 0], cis[:, 1] - coefs], fmt='none', color='black', capsize=5, lw=1.5) ax.axvline(0, color='black', lw=1) ax.set_yticks(y_pos) ax.set_yticklabels([f'{l} {m}' for l, m in zip(var_labels, sig_marks)], fontsize=10) ax.set_xlabel('固定効果推定量(男性転入者数, 人)') ax.set_title('固定効果モデル推定結果(当期)\n男性転入者数への影響', fontsize=12, fontweight='bold') neg_patch = mpatches.Patch(color='#E53935', alpha=0.8, label='負の効果') pos_patch = mpatches.Patch(color='#1E88E5', alpha=0.8, label='正の効果') ax.legend(handles=[neg_patch, pos_patch], fontsize=10) plt.tight_layout() plt.savefig(os.path.join(FIG_DIR, '2025_H3_fig2_fe.png'), bbox_inches='tight') plt.close() |
Figure 1 saved. Figure 2: 固定効果モデル係数(当期)...
fig, ax = plt.subplots(...) — 図全体(fig)と軸(ax)を作る定番。以降は ax.bar(...) 等で操作。ax.axhline / ax.axvline — 水平/垂直の点線。平均線や基準線として定番。fig.savefig(..., bbox_inches='tight') — 余白を自動で詰めて保存。plt.close() でメモリ解放。df['A'] / df['B'] — pandasの列同士の四則演算は要素ごと(element-wise)。forループ不要なのが強み。前節の当期の固定効果モデルで老人福祉施設数や基準地価が「負」の係数を示した結果を踏まえると、 地方の魅力は瞬時には伝わらず、情報伝達と意思決定のタイムラグが存在すると考えられる。 これを検証する必要があるが、その手法として1期ラグ・2期ラグを含む動的固定効果モデルに着目した。 1〜2年前の説明変数では係数の符号が反転するという結果が期待される。
当期だけでなく、前期(t-1期)・前々期(t-2期)の変数を説明変数とするラグモデルを推定する。ここで注目するのは「係数の符号が当期と反対になるか」というラグ効果逆転現象である。
| 変数 | 当期 (t) | 1期ラグ (t-1) | 2期ラグ (t-2) | 解釈 |
|---|---|---|---|---|
| 老人福祉施設数(男性) | 負 *** | 正 ** | 正 * | 当期は忌避→1年後は評価 |
| 基準地価(男性) | 負 ** | 正(有意傾向) | 非有意 | 地価高→短期忌避→長期は魅力 |
| 林業産出額(男性) | 負 * | 正 * | 非有意 | 短期逆転(過疎イメージ→自然志向) |
| 年平均気温(男性) | 非有意 | 非有意 | 正 * | 気候は2年後の移住に影響 |
| 老人福祉施設数(女性) | 負(有意傾向) | 正(有意傾向) | 正 ** | 女性は2期後まで正効果が持続 |
| 総人口(女性) | 正 *** | 正 ** | 負 * | 都市集積の逆転(混雑→回避) |
Pandasの groupby().shift() を使えば、都道府県をまたがずに時系列をずらしてラグ変数を作成できる。グループ(都道府県)の先頭では NaN が生成される。
移住という意思決定は「情報収集 → 意思決定 → 実行」という段階を踏む。施設の整備という「シグナル」が移住者に届くまでには時間がかかる。これを計量経済学では「分散ラグモデル(Distributed Lag Model)」として定式化する。
219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 | print("Figure 2 saved.") # ── Figure 3: ラグ効果の係数推移 ──────────────────────────────────── print("Figure 3: ラグ効果の係数推移...") fig, axes = plt.subplots(1, 2, figsize=(13, 5.5)) lag_labels = ['当期 (t)', '1期ラグ (t-1)', '2期ラグ (t-2)'] def plot_lag_coef(ax, idx, title_var, title_text, results_list): """ラグ別係数をバーチャートで描画""" coef_list = [r.params[idx] for r in results_list] ci_list = [np.asarray(r.conf_int())[idx] for r in results_list] pval_list = [r.pvalues[idx] for r in results_list] bar_colors = ['#E53935' if c < 0 else '#1E88E5' for c in coef_list] ax.bar(range(3), coef_list, color=bar_colors, alpha=0.85, width=0.5) for i, (ci, pv) in enumerate(zip(ci_list, pval_list)): ax.errorbar(i, coef_list[i], yerr=[[coef_list[i] - ci[0]], [ci[1] - coef_list[i]]], fmt='none', color='black', capsize=6, lw=2) if pv < 0.05: ax.text(i, max(coef_list[i], 0) + abs(ci[1] - coef_list[i]) + abs(coef_list[i]) * 0.05, '***' if pv < 0.001 else ('**' if pv < 0.01 else '*'), ha='center', fontsize=12, color='red') ax.axhline(0, color='black', lw=1.2) ax.set_xticks(range(3)) ax.set_xticklabels(lag_labels, fontsize=11) ax.set_ylabel('推定係数(男性転入者数, 人)') ax.set_title(title_text, fontsize=12, fontweight='bold') return coef_list |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。fig, ax = plt.subplots(...) — 図全体(fig)と軸(ax)を作る定番。以降は ax.bar(...) 等で操作。ax.axhline / ax.axvline — 水平/垂直の点線。平均線や基準線として定番。.map() は「1対1の置き換え」、.apply() は「関数を当てる」。辞書なら .map()、ロジックなら .apply()。248 249 250 251 252 253 254 255 256 257 258 259 260 | # 左: 保育所充実度のラグ別係数(idx=1: 第1説明変数) c1 = plot_lag_coef(axes[0], 1, '保育所充実度', '保育所充実度のラグ別係数\n(保育所数/人口万)', [res_t0, res_t1, res_t2]) # 右: 高齢化率のラグ別係数(idx=2) c2 = plot_lag_coef(axes[1], 2, '高齢化率', '高齢化率のラグ別係数\n(65歳以上人口/総人口 × 100)', [res_t0, res_t1, res_t2]) plt.tight_layout() plt.savefig(os.path.join(FIG_DIR, '2025_H3_fig3_lag.png'), bbox_inches='tight') plt.close() |
Figure 2 saved. Figure 3: ラグ効果の係数推移...
fig.savefig(..., bbox_inches='tight') — 余白を自動で詰めて保存。plt.close() でメモリ解放。[式 for x in リスト] はリスト内包表記。forループでappendする代わりに1行でリストを作れます。複数の説明変数間に強い相関がある場合(多重共線性)、推定係数が不安定になる。VIF(Variance Inflation Factor)で確認する。
262 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 291 292 293 294 | print("Figure 3 saved.") # ── Figure 4: VIF確認 ───────────────────────────────────────────── print("Figure 4: VIF確認...") fig, ax = plt.subplots(figsize=(10, 5)) expl_data = panel_est[EXPL_CURRENT].dropna() X_vif = expl_data.values vif_values = [] for i in range(X_vif.shape[1]): vif_values.append(variance_inflation_factor(X_vif, i)) var_labels_vif = ['保育所充実度', '高齢化率', '年平均気温', '婚姻率', '大学進学率', '教育費(二人以上世帯)', '人口密度'] colors_vif = ['#E53935' if v >= 10 else ('#FB8C00' if v >= 5 else '#43A047') for v in vif_values] bars = ax.barh(range(len(var_labels_vif)), vif_values, color=colors_vif, alpha=0.85) ax.axvline(10, color='#E53935', ls='--', lw=1.5, label='VIF=10 (問題あり)') ax.axvline(5, color='#FB8C00', ls='--', lw=1.5, label='VIF=5 (注意)') ax.set_yticks(range(len(var_labels_vif))) ax.set_yticklabels(var_labels_vif, fontsize=11) ax.set_xlabel('VIF値') ax.set_title('多重共線性の確認(VIF)', fontsize=12, fontweight='bold') ax.legend(fontsize=10) for bar, val in zip(bars, vif_values): ax.text(bar.get_width() + 0.1, bar.get_y() + bar.get_height() / 2, f'{val:.2f}', va='center', fontsize=10) plt.tight_layout() plt.savefig(os.path.join(FIG_DIR, '2025_H3_fig4_vif.png'), bbox_inches='tight') plt.close() |
Figure 3 saved. Figure 4: VIF確認...
fig, ax = plt.subplots(...) — 図全体(fig)と軸(ax)を作る定番。以降は ax.bar(...) 等で操作。ax.axhline / ax.axvline — 水平/垂直の点線。平均線や基準線として定番。fig.savefig(..., bbox_inches='tight') — 余白を自動で詰めて保存。plt.close() でメモリ解放。[式 for x in リスト] はリスト内包表記。forループでappendする代わりに1行でリストを作れます。295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 | print("Figure 4 saved.") print("\n=== ラグ効果まとめ(保育所充実度係数) ===") for i, (res, lag) in enumerate(zip([res_t0, res_t1, res_t2], ['当期', 't-1期', 't-2期'])): coef = res.params[1] pval = res.pvalues[1] sig = '***' if pval < 0.001 else ('**' if pval < 0.01 else ('*' if pval < 0.05 else 'n.s.')) print(f" {lag}: beta={coef:.2f}, p={pval:.3f} {sig}") print("\n=== ラグ効果まとめ(高齢化率係数) ===") for i, (res, lag) in enumerate(zip([res_t0, res_t1, res_t2], ['当期', 't-1期', 't-2期'])): coef = res.params[2] pval = res.pvalues[2] sig = '***' if pval < 0.001 else ('**' if pval < 0.01 else ('*' if pval < 0.05 else 'n.s.')) print(f" {lag}: beta={coef:.2f}, p={pval:.3f} {sig}") print("\n分析完了。html/figures/ に図を保存しました。") print(f" 2025_H3_fig1_trend.png - 転入者数の時系列推移") print(f" 2025_H3_fig2_fe.png - 固定効果モデル推定結果") print(f" 2025_H3_fig3_lag.png - ラグ効果の係数推移") print(f" 2025_H3_fig4_vif.png - VIF確認") |
Figure 4 saved. === ラグ効果まとめ(保育所充実度係数) === 当期: beta=-144.16, p=0.382 n.s. t-1期: beta=-140.82, p=0.799 n.s. t-2期: beta=292.30, p=0.510 n.s. === ラグ効果まとめ(高齢化率係数) === 当期: beta=396.46, p=0.040 * t-1期: beta=111.75, p=0.300 n.s. t-2期: beta=-71.81, p=0.584 n.s. 分析完了。html/figures/ に図を保存しました。 2025_H3_fig1_trend.png - 転入者数の時系列推移 2025_H3_fig2_fe.png - 固定効果モデル推定結果 2025_H3_fig3_lag.png - ラグ効果の係数推移 2025_H3_fig4_vif.png - VIF確認
r, p = stats.pearsonr(...) — Pythonは複数戻り値を同時に受け取れる(タプルアンパック)。
以下のファイルをダウンロードして同じフォルダに置き、
python 2025_H3_suri.py を実行すると全図を再現できます。
SSDSE-B-2026.csv を読み込んでパネル固定効果推定・ラグモデル・VIF・全図を生成。
必要ライブラリ: numpy, pandas, matplotlib, statsmodels
| データ | 出典 |
|---|---|
| 転入者数・一般病院数・年平均気温・総人口 | SSDSE-B(統計でみる都道府県のすがた)2026年版, 統計センター |
| 老人福祉施設数 | 社会福祉施設等調査, 厚生労働省 |
| 基準地価 | 都道府県地価調査, 国土交通省 |
| 林業産出額 | 林業産出額調査, 農林水産省 |
統計分析の解釈で初心者がやりがちな勘違いをまとめます。特に「相関と因果の混同」「p値の過信」は研究現場でもよく起きる落とし穴です。本文を読む前にも、読んだ後にも、目を通してみてください。
統計の基本用語を初心者向けに解説します。本文中で見慣れない言葉が出てきたら、ここに戻って確認してください。
統計手法について「何のためか」「結果をどう読むか」を初心者向けに解説します。
この研究をさらに発展させるための3つの方向性を示します。「今回わかったこと(X)」から「次に検証すべき仮説(Y)」を立て、「具体的に何をするか(Z)」まで考えてみましょう。
学んだだけでは身につきません。実際に手を動かすのが最強の学習方法です。本論文のスクリプトをベースに、以下のチャレンジに挑戦してみてください。難易度別に5つ用意しました。
本論文で学んだ手法は、研究の世界だけでなく、行政・企業・NPO の現場でも様々に活用されています。具体的なシーンを紹介します。
この論文を読んで初心者が抱きやすい疑問に、教育的観点から答えます。
この論文を理解できたか、クリックで確認しましょう。間違えても解説が出ます。
このページの分析は、この画面の中でそのまま実行できます。
Python をインストールする必要も、CSV をダウンロードする必要もありません。
下のセルの 「▶ ブラウザで実行」 を上から順に押すか、
「▶ 最初から全部実行」 で一気に流してください。
表示されるのは、本文の図表とまったく同じ計算の結果です
(動かしているのは再現スクリプト code/2025_H3_suri.py そのもの)。
コードは書き換えられます。「✏️ 書き換える」で数字や変数を変えて ▶ を押すと、 結果がどう変わるかをその場で確かめられます(元に戻すボタンつき)。 ページを開いた時点で裏で実行環境の準備が始まっているので、待ち時間はほとんどありません。 初回だけ実行環境の取得にインターネット接続が必要です。