このページの分析を自分で再現するには、以下の手順でデータを準備してください。コードの編集は不要です。
data/raw/ フォルダに入れます。html/figures/ に自動保存されます。
日本では、都市部と地方の間に根強い賃金・雇用格差が存在する。東京都と沖縄県の消費支出差は月額10万円を超え、これは単なる「物価差」や「一時的な景気変動」では説明できない構造的格差の存在を示唆する。
まず「地域間賃金・雇用格差の構造分析パネルデータによる長期的要因の解明」を統計的にとらえることが有効だと考えられる。 その理由は感覚や経験則だけでは、複雑な社会要因の中で「何が本当に効いているか」を見極めにくいからである。 本研究では公開データと統計手法を組み合わせ、この問いに定量的な答えを出すことを目指す。
しかし、多くの先行研究は横断面(クロスセクション)データに基づく分析に留まり、地域固有の不観測要因(地理的条件・歴史的経緯・産業集積等)が十分に制御されていなかった。本研究はこの問題を、固定効果パネルモデル(FE) によって解決する。
固定効果パネル Hausman検定 クラスター標準誤差 SSDSE-B
社会・人口統計体系(SSDSE)のBシリーズは都道府県別の統計データを収録する。本研究では2012〜2023年度の47都道府県データを用い、564観測(47×12)のバランスドパネルを構築した。
| 変数名 | 定義 | 出典(SSDSE-B項目) | 役割 |
|---|---|---|---|
| 消費支出 | 二人以上世帯の月額消費支出(円) | 消費支出(二人以上の世帯) | 目的変数(賃金・所得の代理) |
| 求人倍率 | 月間有効求人数 ÷ 月間有効求職者数 | 月間有効求人数・求職者数(一般) | 雇用環境(需給バランス) |
| 高齢化率 | 65歳以上人口 ÷ 総人口 × 100(%) | 65歳以上人口・総人口 | 人口構造(扶養負担) |
| サービス業密度 | 旅館営業施設数 ÷ 総人口 × 1000 | 旅館営業施設数(ホテルを含む) | 第三次産業の代理変数 |
| 大学進学率 | 高校卒業者のうち進学者数 ÷ 高校卒業者数 × 100(%) | 高等学校卒業者数・進学者数 | 人的資本・教育水準 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | import os, numpy as np, pandas as pd import matplotlib; matplotlib.use('Agg') import matplotlib.pyplot as plt from linearmodels.panel import PanelOLS, RandomEffects import statsmodels.api as sm from scipy import stats plt.rcParams['font.family'] = 'Hiragino Sans' plt.rcParams['axes.unicode_minus'] = False plt.rcParams['figure.dpi'] = 150 FIG_DIR = 'html/figures' DATA_B = 'data/raw/SSDSE-B-2026.csv' os.makedirs(FIG_DIR, exist_ok=True) 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() df_b['年度'] = df_b['年度'].astype(int) print(f"読み込み完了: {df_b.shape[0]}行, {df_b.shape[1]}列") print(f"年度範囲: {df_b['年度'].min()}〜{df_b['年度'].max()}") print(f"都道府県数: {df_b['都道府県'].nunique()}") |
読み込み完了: 564行, 112列 年度範囲: 2012〜2023 都道府県数: 47
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)。pd.read_csv(...) でCSVを読み込みます。encoding='cp932' は日本語Windows由来の文字コード、header=1 は「2行目を列名として使う」。df['地域コード'].str.match(r'^R\d{5}', ...) — 正規表現で「R+数字5桁」の行(47都道府県)だけTrueにし、真偽値で行をフィルタ。.astype(int) — 列を整数に変換(年度などを数値比較するため)。f"...{x}..." はf-string。文字列の中に {変数} と書くだけで埋め込めて、{x:.2f} のように書式も指定できます。23 24 25 26 27 28 29 | df_b['消費支出'] = df_b['消費支出(二人以上の世帯)'] # 求人倍率: 月間有効求人数 / 月間有効求職者数 df_b['求人倍率'] = df_b['月間有効求人数(一般)'] / df_b['月間有効求職者数(一般)'] # 高齢化率: 65歳以上人口 / 総人口 × 100 df_b['高齢化率'] = df_b['65歳以上人口'] / df_b['総人口'] * 100 |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。df['A'] / df['B'] — pandasの列同士の四則演算は要素ごと(element-wise)。forループ不要なのが強み。30 31 32 33 34 35 | # 第三次産業割合の代理: 転入率(移動活発=都市型サービス産業) # ただし旅館営業施設数/総人口を用いてサービス業密度を代理 df_b['旅館密度'] = df_b['旅館営業施設数(ホテルを含む)'] / df_b['総人口'] * 1000 # 人口密度の代理: 総人口の対数(都道府県面積が含まれないため) df_b['log_人口'] = np.log(df_b['総人口']) |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。.map() は「1対1の置き換え」、.apply() は「関数を当てる」。辞書なら .map()、ロジックなら .apply()。36 37 38 39 40 41 42 43 44 45 46 | # 大学進学率: 高校卒業者のうち進学者数 / 高校卒業者数 × 100 df_b['大学進学率'] = df_b['高等学校卒業者のうち進学者数'] / df_b['高等学校卒業者数'] * 100 # 有効な行のみ使用(欠損値除去) vars_needed = ['年度', '都道府県', '消費支出', '求人倍率', '高齢化率', '旅館密度', 'log_人口', '大学進学率'] df_p = df_b[vars_needed].dropna().copy() print(f"\nパネルデータ: {df_p.shape[0]}行 ({df_p['都道府県'].nunique()}都道府県 × {df_p['年度'].nunique()}年度)") print(f"変数の基本統計:") print(df_p[['消費支出','求人倍率','高齢化率','旅館密度','大学進学率']].describe().round(2)) |
パネルデータ: 564行 (47都道府県 × 12年度)
変数の基本統計:
消費支出 求人倍率 高齢化率 旅館密度 大学進学率
count 564.00 564.00 564.00 564.00 564.00
mean 287335.79 1.19 29.24 0.57 52.59
std 23591.18 0.33 3.48 0.34 7.09
min 210593.00 0.36 17.72 0.09 37.67
25% 272849.75 0.96 27.03 0.34 46.69
50% 287054.00 1.19 29.47 0.55 52.02
75% 304501.75 1.43 31.76 0.73 57.28
max 355065.00 2.00 39.06 2.27 74.12.describe() — 件数・平均・標準偏差・四分位・最大/最小を一括計算。データの素性チェックに必須。[式 for x in リスト] はリスト内包表記。forループでappendする代わりに1行でリストを作れます。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 72 | region_map = { '北海道': '北海道・東北', '青森県': '北海道・東北', '岩手県': '北海道・東北', '宮城県': '北海道・東北', '秋田県': '北海道・東北', '山形県': '北海道・東北', '福島県': '北海道・東北', '茨城県': '関東', '栃木県': '関東', '群馬県': '関東', '埼玉県': '関東', '千葉県': '関東', '東京都': '関東', '神奈川県': '関東', '新潟県': '中部', '富山県': '中部', '石川県': '中部', '福井県': '中部', '山梨県': '中部', '長野県': '中部', '岐阜県': '中部', '静岡県': '中部', '愛知県': '中部', '三重県': '近畿', '滋賀県': '近畿', '京都府': '近畿', '大阪府': '近畿', '兵庫県': '近畿', '奈良県': '近畿', '和歌山県': '近畿', '鳥取県': '中国・四国', '島根県': '中国・四国', '岡山県': '中国・四国', '広島県': '中国・四国', '山口県': '中国・四国', '徳島県': '中国・四国', '香川県': '中国・四国', '愛媛県': '中国・四国', '高知県': '中国・四国', '福岡県': '九州・沖縄', '佐賀県': '九州・沖縄', '長崎県': '九州・沖縄', '熊本県': '九州・沖縄', '大分県': '九州・沖縄', '宮崎県': '九州・沖縄', '鹿児島県': '九州・沖縄', '沖縄県': '九州・沖縄' } region_colors = { '北海道・東北': '#4e9af1', '関東': '#e05c5c', '中部': '#f0a500', '近畿': '#5cb85c', '中国・四国': '#9b59b6', '九州・沖縄': '#f39c12' } df_p['地域'] = df_p['都道府県'].map(region_map) |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。.map() は「1対1の置き換え」、.apply() は「関数を当てる」。辞書なら .map()、ロジックなら .apply()。まず地域ブロック別の消費支出推移を俯瞰し、格差の構造と時系列変動パターンを把握する。
| 地域 | 平均消費支出(千円/月) | 最高年度 | 最低年度 |
|---|---|---|---|
| 関東 | 約310〜330 | 2022 | 2013 |
| 近畿 | 約285〜305 | 2022 | 2013 |
| 中部 | 約280〜305 | 2019 | 2013 |
| 中国・四国 | 約270〜295 | 2022 | 2012 |
| 北海道・東北 | 約260〜285 | 2022 | 2012 |
| 九州・沖縄 | 約255〜280 | 2022 | 2012 |
横断面(クロスセクション)分析は「ある1時点の47都道府県の差」を見る。パネルデータは「同じ個体(都道府県)を複数時点で追跡」する。この違いにより、地域固有の不観測要因(気候・歴史的産業構造など)を「固定効果」として除去できる。
74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 | print("\n=== 図1: 地域別消費支出推移 ===") fig1, ax1 = plt.subplots(figsize=(12, 7)) region_groups = df_p.groupby(['地域', '年度'])['消費支出'].mean().reset_index() for region, color in region_colors.items(): rg = region_groups[region_groups['地域'] == region].sort_values('年度') if len(rg) > 0: ax1.plot(rg['年度'], rg['消費支出'] / 1000, color=color, linewidth=2.5, marker='o', markersize=5, label=region) ax1.set_xlabel('年度', fontsize=13) ax1.set_ylabel('消費支出(千円/月)', fontsize=13) ax1.set_title('地域別消費支出の推移(2012〜2023年度)\n47都道府県・地域ブロック別平均', fontsize=14, fontweight='bold') ax1.legend(loc='upper left', fontsize=11, framealpha=0.9) ax1.grid(axis='y', alpha=0.4, linestyle='--') ax1.set_xticks(sorted(df_p['年度'].unique())) ax1.tick_params(axis='x', rotation=45) |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。df.groupby('列').apply(関数) — グループごとに関数を適用。時系列や地域別の集計でよく使います。fig, ax = plt.subplots(...) — 図全体(fig)と軸(ax)を作る定番。以降は ax.bar(...) 等で操作。sort_values('列名', ascending=False) — 指定列で並べ替え(降順)。r, p = stats.pearsonr(...) — Pythonは複数戻り値を同時に受け取れる(タプルアンパック)。92 93 94 95 96 97 98 99 100 101 | # COVID-19注記 ax1.axvspan(2020, 2021, alpha=0.12, color='gray', label='COVID-19') ax1.text(2020.15, ax1.get_ylim()[0] + (ax1.get_ylim()[1]-ax1.get_ylim()[0])*0.03, 'COVID-19', fontsize=10, color='gray') plt.tight_layout() fig1_path = os.path.join(FIG_DIR, '2019_U1_fig1.png') fig1.savefig(fig1_path, dpi=150, bbox_inches='tight') plt.close(fig1) print(f"保存: {fig1_path}") |
=== 図1: 地域別消費支出推移 === 保存: html/figures/2019_U1_fig1.png
x if cond else y は三項演算子。リスト内包表記と組み合わせると、forとifを1行で書けます。固定効果モデルは「都道府県ごとに固有の切片 αᵢ を推定することで、地域の不観測特性(気候・地理・歴史的産業基盤等)を除去」する。以下のモデルを推定する。
| 変数 | 推定係数 β | 標準誤差 | t値 | p値 | 解釈 |
|---|---|---|---|---|---|
| 求人倍率 | +11,592円 | 4,251 | 2.73 | 0.007** | 倍率が1上昇 → 消費支出+1.2万円 |
| 高齢化率 | −2,478円 | 887 | −2.80 | 0.005** | 高齢化率1%上昇 → 消費支出−2,478円 |
| サービス業密度 | +5,521円 | 2,973 | 1.86 | 0.064 n.s. | 旅館密度1単位増加 → +5,521円(非有意) |
| 大学進学率 | +1,140円 | 426 | 2.68 | 0.008** | 進学率1%上昇 → 消費支出+1,140円 |
** p < 0.01。クラスター標準誤差(entity)使用。αᵢ(47都道府県の固定効果)は除く。
パネルデータでは、同じ都道府県の複数年度の観測値が互いに相関している(系列相関)。通常のOLS標準誤差はこの相関を無視するため過小推計になりやすい。エンティティクラスター標準誤差は、各都道府県内の相関を明示的に考慮することで、信頼性の高い推定を実現する。
103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 | print("\n=== 図2: 散布図(求人倍率 vs 消費支出)===") # 2019年度のスナップショット df_2019 = df_p[df_p['年度'] == 2019].copy() fig2, ax2 = plt.subplots(figsize=(12, 9)) for region, color in region_colors.items(): sub = df_2019[df_2019['地域'] == region] ax2.scatter(sub['求人倍率'], sub['消費支出'] / 1000, color=color, s=80, alpha=0.85, label=region, zorder=3) for _, row in sub.iterrows(): ax2.annotate(row['都道府県'].replace('県','').replace('都','').replace('府','').replace('道',''), xy=(row['求人倍率'], row['消費支出'] / 1000), xytext=(3, 3), textcoords='offset points', fontsize=7.5, color='#333333') |
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行で書けます。118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 | # 回帰直線 x_all = df_2019['求人倍率'].values y_all = df_2019['消費支出'].values / 1000 slope, intercept, r_val, p_val, se = stats.linregress(x_all, y_all) x_line = np.linspace(x_all.min(), x_all.max(), 100) ax2.plot(x_line, slope * x_line + intercept, 'k--', linewidth=1.8, label=f'回帰直線 (r={r_val:.3f}, p={p_val:.3f})') ax2.set_xlabel('求人倍率(月間有効求人数/求職者数)', fontsize=13) ax2.set_ylabel('消費支出(千円/月)', fontsize=13) ax2.set_title('求人倍率と消費支出の関係(2019年度・47都道府県)', fontsize=14, fontweight='bold') ax2.legend(loc='upper left', fontsize=10, framealpha=0.9) ax2.grid(alpha=0.3, linestyle='--') plt.tight_layout() fig2_path = os.path.join(FIG_DIR, '2019_U1_fig2.png') fig2.savefig(fig2_path, dpi=150, bbox_inches='tight') plt.close(fig2) print(f"保存: {fig2_path}") print(f"相関係数: r={r_val:.4f}, p={p_val:.4f}") |
=== 図2: 散布図(求人倍率 vs 消費支出)=== 保存: html/figures/2019_U1_fig2.png 相関係数: r=0.2203, p=0.1368
stats.linregress(x, y) — 単回帰の傾き・切片・r値・p値・標準誤差を返します。使わない値は _ で受け取り。df[col](1列)と df[[col1, col2]](複数列)でカッコの数が違います。リストを渡していると覚えるとミスを減らせます。固定効果(FE)と変量効果(RE)のどちらが適切かを統計的に判断するのが Hausman検定 である。帰無仮説は「個体効果(αᵢ)と説明変数が無相関」(RE の一致性条件)。
| 検定統計量 | χ² = 220.34 | 自由度 k = 4 |
| p値 | p < 0.0001 | χ²(4, 5%) の棄却域 ≒ 9.49 |
地域固有効果 αᵢ と説明変数の相関が統計的に有意(p < 0.001)。RE推定量は不一致であり、FEが理論的に正当化される。
FEとREの係数差のノルム(共分散行列の差の逆行列で重み付けしたユークリッド距離)がカイ二乗分布に従う。p値が小さければFEが正しく、REは一致性を失っているという。
139 140 141 142 143 144 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 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 | print("\n=== 図3: FE係数プロット ===") fig3, ax3 = plt.subplots(figsize=(9, 6)) if use_panel and fe_result is not None: params = fe_result.params.drop('Intercept', errors='ignore') std_errors = fe_result.std_errors.drop('Intercept', errors='ignore') pvalues = fe_result.pvalues.drop('Intercept', errors='ignore') # 95% CI ci_lower = params - 1.96 * std_errors ci_upper = params + 1.96 * std_errors var_labels = { '求人倍率': '求人倍率', '高齢化率': '高齢化率 (%)', '旅館密度': 'サービス業密度\n(旅館/千人)', '大学進学率': '大学進学率 (%)' } ys = np.arange(len(params)) colors_bar = ['#e05c5c' if p < 0.05 else '#aaaaaa' for p in pvalues] bars = ax3.barh(ys, params.values, xerr=1.96*std_errors.values, color=colors_bar, alpha=0.85, capsize=5, height=0.5, error_kw={'linewidth': 2, 'ecolor': '#333'}) ax3.set_yticks(ys) ax3.set_yticklabels([var_labels.get(v, v) for v in params.index], fontsize=12) ax3.axvline(0, color='black', linewidth=1.0, linestyle='-') ax3.set_xlabel('推定係数(95% CI)', fontsize=12) ax3.set_title('固定効果モデル(FE):推定係数と95%信頼区間\n目的変数:消費支出(円/月)', fontsize=13, fontweight='bold') # 凡例 from matplotlib.patches import Patch legend_elements = [ Patch(facecolor='#e05c5c', alpha=0.85, label='有意(p < 0.05)'), Patch(facecolor='#aaaaaa', alpha=0.85, label='非有意(p >= 0.05)') ] ax3.legend(handles=legend_elements, loc='lower right', fontsize=11) ax3.grid(axis='x', alpha=0.3, linestyle='--') else: # OLS fallback params = ols_result.params.drop('const', errors='ignore') std_errors = ols_result.bse.drop('const', errors='ignore') pvalues = ols_result.pvalues.drop('const', errors='ignore') ci_lower = params - 1.96 * std_errors ci_upper = params + 1.96 * std_errors ys = np.arange(len(params)) colors_bar = ['#e05c5c' if p < 0.05 else '#aaaaaa' for p in pvalues] ax3.barh(ys, params.values, xerr=1.96*std_errors.values, color=colors_bar, alpha=0.85, capsize=5, height=0.5, error_kw={'linewidth': 2, 'ecolor': '#333'}) ax3.set_yticks(ys) ax3.set_yticklabels(params.index, fontsize=12) ax3.axvline(0, color='black', linewidth=1.0) ax3.set_xlabel('推定係数(95% CI)', fontsize=12) ax3.set_title('OLSモデル:推定係数と95%信頼区間\n目的変数:消費支出(円/月)', fontsize=13, fontweight='bold') ax3.grid(axis='x', alpha=0.3, linestyle='--') plt.tight_layout() fig3_path = os.path.join(FIG_DIR, '2019_U1_fig3.png') fig3.savefig(fig3_path, dpi=150, bbox_inches='tight') plt.close(fig3) print(f"保存: {fig3_path}") |
=== 図3: FE係数プロット === 保存: html/figures/2019_U1_fig3.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単位改善) | 優先度 |
|---|---|---|---|
| 地方への企業誘致・雇用拡大 | 求人倍率 | +11,592円/月 | 高 |
| 若年層定住・出生率向上 | 高齢化率(抑制) | +2,478円/月(1%抑制) | 高 |
| 奨学金拡充・地方大学振興 | 大学進学率 | +1,140円/月 | 中 |
固定効果モデルは「地域固有の不観測要因」によるバイアスを除去するが、それでも因果関係の特定には不十分な場合がある。例えば「求人倍率が上昇したから消費が増えた」のか「地域が豊かになったから求人が増えた」のか(逆因果)は、FEだけでは識別できない。より厳密な因果推論には操作変数法(IV)や差の差分析(DiD)が必要となる。
211 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 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 | print("\n=== パネル分析 ===") fe_result = None re_result = None ols_result = None use_panel = False try: df_panel = df_p.set_index(['都道府県', '年度']) fe_res = PanelOLS.from_formula( '消費支出 ~ 求人倍率 + 高齢化率 + 旅館密度 + 大学進学率 + EntityEffects', data=df_panel ).fit(cov_type='clustered', cluster_entity=True) re_res = RandomEffects.from_formula( '消費支出 ~ 求人倍率 + 高齢化率 + 旅館密度 + 大学進学率', data=df_panel ).fit() fe_result = fe_res re_result = re_res use_panel = True print("固定効果モデル (FE):") print(fe_res.summary.tables[1]) print("\nランダム効果モデル (RE):") print(re_res.summary.tables[1]) # Hausman検定 (手動実装) # 共通変数の係数差を利用 common_vars = ['求人倍率', '高齢化率', '旅館密度', '大学進学率'] b_fe = fe_res.params[common_vars].values b_re = re_res.params[common_vars].values diff = b_fe - b_re # 共分散行列の差 cov_fe = fe_res.cov.loc[common_vars, common_vars].values cov_re = re_res.cov.loc[common_vars, common_vars].values cov_diff = cov_fe - cov_re try: hausman_stat = float(diff @ np.linalg.inv(cov_diff) @ diff) hausman_df = len(common_vars) hausman_pval = 1 - stats.chi2.cdf(hausman_stat, df=hausman_df) print(f"\nHausman検定: χ²={hausman_stat:.4f}, df={hausman_df}, p={hausman_pval:.4f}") print("→ FE採択" if hausman_pval < 0.05 else "→ RE採択") except np.linalg.LinAlgError: # 擬似逆行列で代替 hausman_stat = float(diff @ np.linalg.pinv(cov_diff) @ diff) hausman_df = len(common_vars) hausman_pval = 1 - stats.chi2.cdf(hausman_stat, df=hausman_df) print(f"\nHausman検定 (擬似逆行列): χ²={hausman_stat:.4f}, df={hausman_df}, p={hausman_pval:.4f}") except Exception as e: print(f"パネル分析エラー: {e}") print("OLSフォールバックを使用") X_vars = ['求人倍率', '高齢化率', '旅館密度', '大学進学率'] X_ols = sm.add_constant(df_p[X_vars]) ols_result = sm.OLS(df_p['消費支出'], X_ols).fit() print(ols_result.summary()) use_panel = False hausman_stat = None hausman_pval = None |
=== パネル分析 ===
固定効果モデル (FE):
Parameter Estimates
==============================================================================
Parameter Std. Err. T-stat P-value Lower CI Upper CI
------------------------------------------------------------------------------
求人倍率 1.159e+04 4251.1 2.7268 0.0066 3240.0 1.994e+04
高齢化率 -2478.2 886.66 -2.7950 0.0054 -4220.1 -736.29
旅館密度 5521.1 2972.5 1.8574 0.0638 -318.53 1.136e+04
大学進学率 1140.4 425.91 2.6775 0.0077 303.64 1977.1
==============================================================================
ランダム効果モデル (RE):
Parameter Estimates
==============================================================================
Parameter Std. Err. T-stat P-value Lower CI Upper CI
------------------------------------------------------------------------------
求人倍率 -2.112e+04 4447.8 -4.7489 0.0000 -2.986e+04 -1.239e+04
高齢化率 3473.4 677.50 5.1268 0.0000 2142.6 4804.2
旅館密度 3.182e+04 6586.1 4.8309 0.0000 1.888e+04 4.475e+04
大学進学率 3358.3 306.06 10.973 0.0000 2757.1 3959.4
==============================================================================
Hausman検定: χ²=220.3381, df=4, p=0.0000
→ FE採択sm.add_constant(X) — 切片項(定数1の列)を先頭に追加。statsmodelsで必須。sm.OLS(y, X).fit() — 最小二乗法でモデルを推定。model.params, model.pvalues, model.conf_int() で結果取得。[式 for x in リスト] はリスト内包表記。forループでappendする代わりに1行でリストを作れます。276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 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 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 | print("\n=== 図4: FE vs RE 係数比較 ===") fig4, ax4 = plt.subplots(figsize=(10, 6)) if use_panel and fe_result is not None and re_result is not None: common_vars = ['求人倍率', '高齢化率', '旅館密度', '大学進学率'] var_labels = { '求人倍率': '求人倍率', '高齢化率': '高齢化率', '旅館密度': 'サービス業密度', '大学進学率': '大学進学率' } fe_coefs = fe_result.params[common_vars].values re_coefs = re_result.params[common_vars].values fe_se = fe_result.std_errors[common_vars].values re_se = re_result.std_errors[common_vars].values x_pos = np.arange(len(common_vars)) width = 0.35 bars_fe = ax4.bar(x_pos - width/2, fe_coefs, width, yerr=1.96*fe_se, capsize=6, label='固定効果 (FE)', color='#1565C0', alpha=0.85, error_kw={'linewidth': 2}) bars_re = ax4.bar(x_pos + width/2, re_coefs, width, yerr=1.96*re_se, capsize=6, label='変量効果 (RE)', color='#E65100', alpha=0.85, error_kw={'linewidth': 2}) ax4.set_xticks(x_pos) ax4.set_xticklabels([var_labels.get(v, v) for v in common_vars], fontsize=12) ax4.axhline(0, color='black', linewidth=1.0) ax4.set_ylabel('推定係数', fontsize=12) try: hausman_stat_v = hausman_stat hausman_pval_v = hausman_pval title_str = (f'Hausman検定によるFE・RE係数比較\n' f'Hausman χ²={hausman_stat_v:.3f}, p={hausman_pval_v:.4f} ' f'→ {"FE採択" if hausman_pval_v < 0.05 else "RE採択"}') except NameError: title_str = 'Hausman検定によるFE・RE係数比較' ax4.set_title(title_str, fontsize=13, fontweight='bold') ax4.legend(fontsize=12, loc='upper right') ax4.grid(axis='y', alpha=0.3, linestyle='--') else: # OLS only: show coefficient bar chart with CI if ols_result is not None: params = ols_result.params.drop('const', errors='ignore') std_errors = ols_result.bse.drop('const', errors='ignore') pvalues = ols_result.pvalues.drop('const', errors='ignore') ys = np.arange(len(params)) ax4.bar(ys, params.values, color='#1565C0', alpha=0.8) ax4.set_xticks(ys) ax4.set_xticklabels(params.index, fontsize=11) ax4.axhline(0, color='black', linewidth=1.0) ax4.set_title('OLS推定係数(パネル分析フォールバック)', fontsize=13, fontweight='bold') ax4.grid(axis='y', alpha=0.3, linestyle='--') plt.tight_layout() fig4_path = os.path.join(FIG_DIR, '2019_U1_fig4.png') fig4.savefig(fig4_path, dpi=150, bbox_inches='tight') plt.close(fig4) print(f"保存: {fig4_path}") |
=== 図4: FE vs RE 係数比較 === 保存: html/figures/2019_U1_fig4.png
fig, ax = plt.subplots(...) — 図全体(fig)と軸(ax)を作る定番。以降は ax.bar(...) 等で操作。ax.axhline / ax.axvline — 水平/垂直の点線。平均線や基準線として定番。s[:-n]「末尾n文字を除く」/s[n:]「先頭n文字を除く」。スライス [start:stop:step] はリスト・タプル・文字列共通の基本ワザです。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 | print("\n" + "="*60) print("統計値サマリー(HTML用)") print("="*60) print(f"\n■ データ規模") print(f" 観測数: {df_p.shape[0]} ({df_p['都道府県'].nunique()}都道府県 × {df_p['年度'].nunique()}年)") print(f" 期間: {df_p['年度'].min()}〜{df_p['年度'].max()}年度") print(f"\n■ 消費支出の記述統計") cs = df_p['消費支出'] print(f" 平均: {cs.mean():,.0f}円/月") print(f" 標準偏差: {cs.std():,.0f}円/月") print(f" 最大: {cs.max():,.0f}円/月 ({df_p.loc[cs.idxmax(),'都道府県']})") print(f" 最小: {cs.min():,.0f}円/月 ({df_p.loc[cs.idxmin(),'都道府県']})") print(f"\n■ 求人倍率の記述統計") kj = df_p['求人倍率'] print(f" 平均: {kj.mean():.3f}") print(f" 最大: {kj.max():.3f}") print(f" 最小: {kj.min():.3f}") print(f"\n■ 相関分析(2019年度スナップショット)") print(f" 求人倍率 vs 消費支出: r={r_val:.4f}, p={p_val:.4f}") if use_panel and fe_result is not None: print(f"\n■ 固定効果モデル(FE)結果") for var in fe_result.params.index: if var == 'Intercept': continue coef = fe_result.params[var] se_v = fe_result.std_errors[var] pv = fe_result.pvalues[var] sig = '***' if pv < 0.001 else '**' if pv < 0.01 else '*' if pv < 0.05 else 'n.s.' print(f" {var}: β={coef:+.2f}, SE={se_v:.2f}, p={pv:.4f} {sig}") try: print(f"\n■ Hausman検定") print(f" χ²={hausman_stat:.4f}, df={len(common_vars)}, p={hausman_pval:.4f}") print(f" 判断: {'固定効果モデルを採択' if hausman_pval < 0.05 else '変量効果モデルを採択'}") except NameError: pass print("\n=== 完了 ===") print(f"図1: {fig1_path}") print(f"図2: {fig2_path}") print(f"図3: {fig3_path}") print(f"図4: {fig4_path}") |
============================================================ 統計値サマリー(HTML用) ============================================================ ■ データ規模 観測数: 564 (47都道府県 × 12年) 期間: 2012〜2023年度 ■ 消費支出の記述統計 平均: 287,336円/月 標準偏差: 23,591円/月 最大: 355,065円/月 (石川県) 最小: 210,593円/月 (沖縄県) ■ 求人倍率の記述統計 平均: 1.188 最大: 2.000 最小: 0.362 ■ 相関分析(2019年度スナップショット) 求人倍率 vs 消費支出: r=0.2203, p=0.1368 ■ 固定効果モデル(FE)結果 求人倍率: β=+11591.73, SE=4251.09, p=0.0066 ** 高齢化率: β=-2478.22, SE=886.66, p=0.0054 ** 旅館密度: β=+5521.15, SE=2972.45, p=0.0638 n.s. 大学進学率: β=+1140.39, SE=425.91, p=0.0077 ** ■ Hausman検定 χ²=220.3381, df=4, p=0.0000 判断: 固定効果モデルを採択 === 完了 === 図1: html/figures/2019_U1_fig1.png 図2: html/figures/2019_U1_fig2.png 図3: html/figures/2019_U1_fig3.png 図4: html/figures/2019_U1_fig4.png
np.cumsum(arr) は累積和、np.linspace(a, b, n) は「aからbを等間隔でn個」。NumPyの定石です。本研究は SSDSE-B(2012〜2023年度、47都道府県)を用いたパネルデータ分析により、地域間消費支出格差の構造的要因を解明した。
| 限界 | 今後の課題 |
|---|---|
| 消費支出は賃金・所得の代理変数に過ぎない(実際の賃金データが望ましい) | 賃金センサス等との統合分析 |
| 第三次産業割合の直接データが未使用(代理変数として旅館密度を使用) | 経済センサス等からの産業構造データの統合 |
| 逆因果の可能性が残る(求人倍率 ↔ 消費支出) | 操作変数法(IV)や差の差分析(DiD)の適用 |
| COVID-19(2020〜2022年度)の構造変化への対応が不十分 | 構造変化検定(Chow検定)の実施 |
使用データ:総務省 SSDSE(社会・人口統計体系データセット)B-2026(2012〜2023年度、47都道府県)
分析手法:固定効果パネルモデル(linearmodels)、Hausman検定(scipy.stats.chi2)、クラスター標準誤差(entity)
対象論文:2019年度(令和元年度)統計データ分析コンペティション 総務大臣賞(大学生・一般の部)
統計分析の解釈で初心者がやりがちな勘違いをまとめます。特に「相関と因果の混同」「p値の過信」は研究現場でもよく起きる落とし穴です。本文を読む前にも、読んだ後にも、目を通してみてください。
統計の基本用語を初心者向けに解説します。本文中で見慣れない言葉が出てきたら、ここに戻って確認してください。
統計手法について「何のためか」「結果をどう読むか」を初心者向けに解説します。
この研究をさらに発展させるための3つの方向性を示します。「今回わかったこと(X)」から「次に検証すべき仮説(Y)」を立て、「具体的に何をするか(Z)」まで考えてみましょう。
学んだだけでは身につきません。実際に手を動かすのが最強の学習方法です。本論文のスクリプトをベースに、以下のチャレンジに挑戦してみてください。難易度別に5つ用意しました。
本論文で学んだ手法は、研究の世界だけでなく、行政・企業・NPO の現場でも様々に活用されています。具体的なシーンを紹介します。
この論文を読んで初心者が抱きやすい疑問に、教育的観点から答えます。