このページの分析を自分で再現するには、以下の手順でデータを準備してください。コードの編集は不要です。
data/raw/ フォルダに入れます。html/figures/ に自動保存されます。
2020年春、COVID-19の国内感染拡大(「第一波」)は日本経済に深刻な打撃を与えた。政府は4月7日に緊急事態宣言を発令し、不要不急の外出自粛・飲食店の時短営業・観光業の事実上の停止が各地で実施された。しかしその影響は、産業構造や観光依存度によって地域間で大きく異なった。
まず「コロナ禍の地域経済第一波による雇用・消費への影響分析」を統計的にとらえることが有効だと考えられる。 その理由は感覚や経験則だけでは、複雑な社会要因の中で「何が本当に効いているか」を見極めにくいからである。 本研究では公開データと統計手法を組み合わせ、この問いに定量的な答えを出すことを目指す。
本研究は、SSDSE-B(都道府県統計データ)2012〜2023年のパネルデータを用い、固定効果パネル推定によってCOVID-19第一波が消費支出・雇用(求人倍率)に与えた影響を定量的に分析する。さらに観光依存度(旅館密度)との交互作用を通じて、被害の地域格差メカニズムを解明する。
パネルデータ 固定効果推定 COVID-19 地域格差分析 SSDSE-B
総務省統計局が公開する「社会・人口統計体系データ(SSDSE-B-2026)」の47都道府県データを使用する。分析期間は2012〜2023年度(12年分)で、観測数は47×12 = 564である。
| データソース | 内容 | 分析期間 | 観測数 |
|---|---|---|---|
| SSDSE-B-2026 | 都道府県別統計(消費・雇用・人口等) | 2012〜2023年度 | 564(47×12) |
| 変数名 | 定義・計算方法 | 役割 |
|---|---|---|
| 消費支出 | 消費支出(二人以上の世帯)[円/月] SSDSE-B 列:消費支出(二人以上の世帯) |
目的変数(Y) |
| COVID期ダミー | 2020年度・2021年度 = 1、それ以外 = 0 | 主要説明変数(処置変数) |
| 求人倍率 | 月間有効求人数(一般)÷ 月間有効求職者数(一般) | 雇用市場の代理変数 |
| 旅館密度 | 旅館営業施設数(ホテル含む)÷ 総人口 × 10,000 | 観光依存度の代理変数 |
| COVID×旅館密度 | COVID期ダミー × 旅館密度(交互作用項) | 観光依存度×COVID打撃の異質性 |
| 高齢化率 | 65歳以上人口 ÷ 総人口 × 100 [%] | コントロール変数 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | import os 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 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) # 分析期間: 2012〜2023 df_b = df_b[df_b['年度'].between(2012, 2023)].copy() |
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)。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} のように書式も指定できます。24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 | df_b['COVID期'] = df_b['年度'].isin([2020, 2021]).astype(int) # 消費支出(目的変数) df_b['消費支出'] = pd.to_numeric(df_b['消費支出(二人以上の世帯)'], errors='coerce') # 求人倍率(月間有効求人数 / 月間有効求職者数) df_b['求人倍率'] = ( pd.to_numeric(df_b['月間有効求人数(一般)'], errors='coerce') / pd.to_numeric(df_b['月間有効求職者数(一般)'], errors='coerce') ) # 旅館密度(旅館営業施設数 / 総人口 × 10,000) df_b['旅館密度'] = ( pd.to_numeric(df_b['旅館営業施設数(ホテルを含む)'], errors='coerce') / pd.to_numeric(df_b['総人口'], errors='coerce') * 10000 ) # 高齢化率(65歳以上人口 / 総人口 × 100) df_b['高齢化率'] = ( pd.to_numeric(df_b['65歳以上人口'], errors='coerce') / pd.to_numeric(df_b['総人口'], errors='coerce') * 100 ) |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。.astype(int) — 列を整数に変換(年度などを数値比較するため)。df['A'] / df['B'] — pandasの列同士の四則演算は要素ごと(element-wise)。forループ不要なのが強み。46 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 | region_map = { '北海道': '北海道・東北', '青森県': '北海道・東北', '岩手県': '北海道・東北', '宮城県': '北海道・東北', '秋田県': '北海道・東北', '山形県': '北海道・東北', '福島県': '北海道・東北', '茨城県': '関東', '栃木県': '関東', '群馬県': '関東', '埼玉県': '関東', '千葉県': '関東', '東京都': '関東', '神奈川県': '関東', '新潟県': '中部', '富山県': '中部', '石川県': '中部', '福井県': '中部', '山梨県': '中部', '長野県': '中部', '岐阜県': '中部', '静岡県': '中部', '愛知県': '中部', '三重県': '近畿', '滋賀県': '近畿', '京都府': '近畿', '大阪府': '近畿', '兵庫県': '近畿', '奈良県': '近畿', '和歌山県': '近畿', '鳥取県': '中国・四国', '島根県': '中国・四国', '岡山県': '中国・四国', '広島県': '中国・四国', '山口県': '中国・四国', '徳島県': '中国・四国', '香川県': '中国・四国', '愛媛県': '中国・四国', '高知県': '中国・四国', '福岡県': '九州・沖縄', '佐賀県': '九州・沖縄', '長崎県': '九州・沖縄', '熊本県': '九州・沖縄', '大分県': '九州・沖縄', '宮崎県': '九州・沖縄', '鹿児島県': '九州・沖縄', '沖縄県': '九州・沖縄' } region_colors = { '北海道・東北': '#4e9af1', '関東': '#e05c5c', '中部': '#f0a500', '近畿': '#5cb85c', '中国・四国': '#9b59b6', '九州・沖縄': '#f39c12' } df_b['地域'] = df_b['都道府県'].map(region_map) |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。.map() は「1対1の置き換え」、.apply() は「関数を当てる」。辞書なら .map()、ロジックなら .apply()。72 73 74 75 76 77 78 79 80 81 82 83 84 | df_2019_density = df_b[df_b['年度'] == 2019][['都道府県', '旅館密度']].dropna() top10_prefs = df_2019_density.nlargest(10, '旅館密度')['都道府県'].tolist() bot10_prefs = df_2019_density.nsmallest(10, '旅館密度')['都道府県'].tolist() df_b['観光グループ'] = df_b['都道府県'].apply( lambda x: '高観光依存(上位10県)' if x in top10_prefs else ('低観光依存(下位10県)' if x in bot10_prefs else None) ) print("\n=== 旅館密度 上位10県 ===") print(df_2019_density.nlargest(10, '旅館密度')[['都道府県', '旅館密度']].to_string(index=False)) print("\n=== 旅館密度 下位10県 ===") print(df_2019_density.nsmallest(10, '旅館密度')[['都道府県', '旅館密度']].to_string(index=False)) |
=== 旅館密度 上位10県 === 都道府県 旅館密度 山梨県 16.331288 沖縄県 14.746922 長野県 12.712276 福井県 12.308690 鳥取県 10.843806 大分県 9.523810 新潟県 9.271583 福島県 8.212743 静岡県 7.777169 山形県 7.472222 === 旅館密度 下位10県 === 都道府県 旅館密度 埼玉県 0.938436 神奈川県 1.453816 愛知県 1.590578 大阪府 1.670437 千葉県 2.051568 東京都 2.361676 広島県 2.492001 福岡県 2.520452 兵庫県 2.715015 奈良県 3.128282
r, p = stats.pearsonr(...) — Pythonは複数戻り値を同時に受け取れる(タプルアンパック)。85 86 87 88 89 90 91 92 93 94 | df_2019c = df_b[df_b['年度'] == 2019][['都道府県', '消費支出', '地域']].rename(columns={'消費支出': '消費2019'}) df_2020c = df_b[df_b['年度'] == 2020][['都道府県', '消費支出']].rename(columns={'消費支出': '消費2020'}) df_change = df_2019c.merge(df_2020c, on='都道府県').dropna() df_change['変化率'] = (df_change['消費2020'] - df_change['消費2019']) / df_change['消費2019'] * 100 df_change = df_change.sort_values('変化率') print("\n=== 2019→2020年 消費支出変化率(下位5県) ===") print(df_change.head(5)[['都道府県', '変化率']].to_string(index=False)) print("\n=== 2019→2020年 消費支出変化率(上位5県) ===") print(df_change.tail(5)[['都道府県', '変化率']].to_string(index=False)) |
=== 2019→2020年 消費支出変化率(下位5県) === 都道府県 変化率 石川県 -15.684452 岡山県 -14.380299 長野県 -14.157331 福井県 -11.485298 香川県 -10.705295 === 2019→2020年 消費支出変化率(上位5県) === 都道府県 変化率 岐阜県 5.920707 徳島県 6.026788 島根県 6.666498 群馬県 8.040385 大分県 8.892135
sort_values('列名', ascending=False) — 指定列で並べ替え(降順)。x if cond else y は三項演算子。リスト内包表記と組み合わせると、forとifを1行で書けます。95 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 130 131 | print("\n" + "="*60) print("=== HTML転記用 統計サマリ ===") print("="*60) print(f"分析対象: 47都道府県 × 12年度 = {len(df_panel_all)}観測値(欠損除外後)") print(f"モデル: {model_type}") print(f"R² = {r2:.4f}") print(f"N = {n_obs}") print() for var, (coef, se, pval) in coef_results.items(): sig = '***' if pval < 0.001 else ('**' if pval < 0.01 else ('*' if pval < 0.05 else 'n.s.')) ci_lo, ci_hi = coef - 1.96*se, coef + 1.96*se print(f"{var}: β={coef:+.1f} (95%CI: {ci_lo:+.1f}〜{ci_hi:+.1f}), SE={se:.1f}, p={pval:.4f} {sig}") print() # 消費支出2020変化 covid_mean = df_change['変化率'].mean() covid_worst = df_change.iloc[0] covid_best = df_change.iloc[-1] print(f"2019→2020消費変化(全国平均): {covid_mean:+.2f}%") print(f"最大減少: {covid_worst['都道府県']} {covid_worst['変化率']:+.2f}%") print(f"最大増加: {covid_best['都道府県']} {covid_best['変化率']:+.2f}%") print() # 求人倍率2020 job_2019 = df_b[df_b['年度']==2019]['求人倍率'].mean() job_2020 = df_b[df_b['年度']==2020]['求人倍率'].mean() print(f"求人倍率(全国平均): 2019年={job_2019:.2f} → 2020年={job_2020:.2f} (変化: {job_2020-job_2019:+.2f})") print() # 観光依存グループ比較 print("=== 観光依存グループ別 2019→2020消費変化率 ===") df_change_group = df_change.copy() df_change_group['観光グループ'] = df_change_group['都道府県'].apply( lambda x: '高観光依存(上位10)' if x in top10_prefs else ('低観光依存(下位10)' if x in bot10_prefs else None) ) for grp in ['高観光依存(上位10)', '低観光依存(下位10)']: sub = df_change_group[df_change_group['観光グループ']==grp]['変化率'] print(f"{grp}: 平均 {sub.mean():+.2f}% (N={len(sub)})") print("="*60) |
============================================================ === HTML転記用 統計サマリ === ============================================================ 分析対象: 47都道府県 × 12年度 = 564観測値(欠損除外後) モデル: Entity FE パネル推定(PanelOLS) R² = 0.0581 N = 564 COVID期: β=-6712.8 (95%CI: -12187.6〜-1238.0), SE=2793.3, p=0.0166 * 求人倍率: β=+1586.4 (95%CI: -3077.7〜+6250.4), SE=2379.6, p=0.5053 n.s. COVID×旅館密度: β=-297.5 (95%CI: -1022.3〜+427.2), SE=369.8, p=0.4214 n.s. 2019→2020消費変化(全国平均): -2.79% 最大減少: 石川県 -15.68% 最大増加: 大分県 +8.89% 求人倍率(全国平均): 2019年=1.46 → 2020年=1.11 (変化: -0.35) === 観光依存グループ別 2019→2020消費変化率 === 高観光依存(上位10): 平均 -2.49% (N=10) 低観光依存(下位10): 平均 -2.81% (N=10) ============================================================
plt.subplots(figsize=(W, H)) で図サイズ指定、fig.savefig(..., bbox_inches='tight') で余白を自動で詰めて保存。まず地域別の時系列推移を可視化し、COVID-19第一波(2020年度)前後の変化パターンを把握する。
政策変数(COVID期ダミー)の効果を視覚化する際、axvspanでシェーディングを加えると前後の変化が一目でわかる。折れ線グラフに複数系列を重ねる場合は色と線種を統一することが重要。
133 134 135 136 137 138 139 140 141 | fig, ax = plt.subplots(figsize=(10, 5.5)) region_yearly = df_b.groupby(['年度', '地域'])['消費支出'].mean().reset_index() for region, color in region_colors.items(): sub = region_yearly[region_yearly['地域'] == region].sort_values('年度') if len(sub) > 0: ax.plot(sub['年度'], sub['消費支出'] / 1000, marker='o', markersize=4, label=region, color=color, linewidth=1.8) |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。df.groupby('列').apply(関数) — グループごとに関数を適用。時系列や地域別の集計でよく使います。fig, ax = plt.subplots(...) — 図全体(fig)と軸(ax)を作る定番。以降は ax.bar(...) 等で操作。sort_values('列名', ascending=False) — 指定列で並べ替え(降順)。df[col](1列)と df[[col1, col2]](複数列)でカッコの数が違います。リストを渡していると覚えるとミスを減らせます。142 143 144 145 146 147 148 149 150 151 152 153 154 | # COVID期シェーディング ax.axvspan(2019.5, 2021.5, alpha=0.15, color='#e74c3c', zorder=0) ax.axvline(x=2020, color='#e74c3c', linestyle='--', linewidth=1.2, alpha=0.7) ax.text(2020.05, ax.get_ylim()[0] if ax.get_ylim()[0] != 0 else 250, 'COVID-19\n第一波', fontsize=9, color='#c0392b', va='bottom') ax.set_xlabel('年度', fontsize=11) ax.set_ylabel('消費支出(千円/月)', fontsize=11) ax.set_title('地域別 消費支出の推移(2012〜2023年)', fontsize=13, fontweight='bold') ax.legend(loc='upper left', fontsize=9, ncol=2, framealpha=0.8) ax.set_xticks(range(2012, 2024)) ax.tick_params(axis='x', rotation=45) ax.grid(axis='y', alpha=0.3, linestyle='--') |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。ax.axhline / ax.axvline — 水平/垂直の点線。平均線や基準線として定番。s[:-n]「末尾n文字を除く」/s[n:]「先頭n文字を除く」。スライス [start:stop:step] はリスト・タプル・文字列共通の基本ワザです。155 156 157 158 159 160 161 162 163 164 | # y軸範囲を確定してからテキスト位置を修正 ymin, ymax = ax.get_ylim() for txt in ax.texts: txt.set_y(ymin + (ymax - ymin) * 0.02) plt.tight_layout() fig1_path = os.path.join(FIG_DIR, '2020_U1_fig1.png') plt.savefig(fig1_path, bbox_inches='tight') plt.close() print(f"\n図1保存: {fig1_path}") |
図1保存: html/figures/2020_U1_fig1.png
fig.savefig(..., bbox_inches='tight') — 余白を自動で詰めて保存。plt.close() でメモリ解放。np.cumsum(arr) は累積和、np.linspace(a, b, n) は「aからbを等間隔でn個」。NumPyの定石です。パネルデータを用い、都道府県固定効果(Entity Effects)によって各地域の不変な特性(気候・歴史的産業構造等)を制御した上で、COVID期ダミーが消費支出に与える純効果を推定する。
| 変数 | 係数 (β) | 標準誤差 | 95%信頼区間 | p値 | 有意性 |
|---|---|---|---|---|---|
| COVID期ダミー | -6,713 円/月 | 2,793 | -12,188 〜 -1,238 | 0.017 | * (p<0.05) |
| 求人倍率 | +1,586 円/月 | 2,380 | -3,089 〜 +6,261 | 0.505 | n.s. |
| COVID期 × 旅館密度 | -298 円/月 | 370 | -1,024 〜 +429 | 0.421 | n.s. |
| N=564(47都道府県×12年度)、Within R²=0.058、クラスターロバスト標準誤差(都道府県) | |||||
Pythonのlinearmodelsパッケージを使うと、固定効果パネルモデルをRのplmパッケージ相当の精度で推定できる。set_index(['エンティティ', '時間'])が必須。
固定効果モデルでは「Within R²」(地域内の時間変動を説明する割合)と「Overall R²」(全変動)が大きく異なる。Within R²が低くても、固定効果自体が変動の大部分を説明している場合が多い。
166 167 168 169 170 171 172 173 174 | fig, ax = plt.subplots(figsize=(10, 5.5)) region_job = df_b.groupby(['年度', '地域'])['求人倍率'].mean().reset_index() for region, color in region_colors.items(): sub = region_job[region_job['地域'] == region].sort_values('年度') if len(sub) > 0: ax.plot(sub['年度'], sub['求人倍率'], marker='o', markersize=4, label=region, color=color, linewidth=1.8) |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。df.groupby('列').apply(関数) — グループごとに関数を適用。時系列や地域別の集計でよく使います。fig, ax = plt.subplots(...) — 図全体(fig)と軸(ax)を作る定番。以降は ax.bar(...) 等で操作。sort_values('列名', ascending=False) — 指定列で並べ替え(降順)。s[:-n]「末尾n文字を除く」/s[n:]「先頭n文字を除く」。スライス [start:stop:step] はリスト・タプル・文字列共通の基本ワザです。175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 | # COVID期シェーディング ax.axvspan(2019.5, 2021.5, alpha=0.15, color='#e74c3c', zorder=0) ax.axvline(x=2020, color='#e74c3c', linestyle='--', linewidth=1.2, alpha=0.7) ax.axhline(y=1.0, color='gray', linestyle=':', linewidth=1.2, alpha=0.8, label='倍率=1.0') ymin2, ymax2 = ax.get_ylim() ax.text(2020.05, ymin2 + (ymax2 - ymin2) * 0.02, 'COVID-19\n第一波', fontsize=9, color='#c0392b', va='bottom') ax.set_xlabel('年度', fontsize=11) ax.set_ylabel('求人倍率(求人数/求職者数)', fontsize=11) ax.set_title('地域別 求人倍率の推移(2012〜2023年)', fontsize=13, fontweight='bold') ax.legend(loc='upper left', fontsize=9, ncol=2, framealpha=0.8) ax.set_xticks(range(2012, 2024)) ax.tick_params(axis='x', rotation=45) ax.grid(axis='y', alpha=0.3, linestyle='--') plt.tight_layout() fig2_path = os.path.join(FIG_DIR, '2020_U1_fig2.png') plt.savefig(fig2_path, bbox_inches='tight') plt.close() print(f"図2保存: {fig2_path}") |
図2保存: html/figures/2020_U1_fig2.png
ax.axhline / ax.axvline — 水平/垂直の点線。平均線や基準線として定番。fig.savefig(..., bbox_inches='tight') — 余白を自動で詰めて保存。plt.close() でメモリ解放。np.cumsum(arr) は累積和、np.linspace(a, b, n) は「aからbを等間隔でn個」。NumPyの定石です。2019年度から2020年度にかけての消費支出変化率を都道府県別に算出し、地域ごとのCOVID打撃の大きさを可視化する。
| 消費減少が大きかった上位5県 | ||
|---|---|---|
| 順位 | 都道府県 | 変化率 |
| 1 | 石川県 | -15.7% |
| 2 | 岡山県 | -14.4% |
| 3 | 長野県 | -14.2% |
| 4 | 福井県 | -11.5% |
| 5 | 香川県 | -10.7% |
| 消費減少が小さかった上位5県 | ||
|---|---|---|
| 順位 | 都道府県 | 変化率 |
| 1 | 大分県 | +8.9% |
| 2 | 群馬県 | +8.0% |
| 3 | 島根県 | +6.7% |
| 4 | 徳島県 | +6.0% |
| 5 | 岐阜県 | +5.9% |
| グループ | 旅館密度基準 | 代表県 | 2019→2020 消費変化率(平均) |
|---|---|---|---|
| 高観光依存(上位10県) | 7.5以上(人口1万対) | 山梨・沖縄・長野・福井・鳥取 | -2.5% |
| 低観光依存(下位10県) | 2.5以下(人口1万対) | 埼玉・神奈川・愛知・大阪・千葉 | -2.8% |
| 全国平均 | - | - | -2.8% |
47都道府県を一つのグラフにランキング表示する際、地域区分別の色分けにより空間的パターンが視覚的に把握しやすくなる。
198 199 200 201 202 203 204 205 206 207 208 209 210 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 | var_labels = { 'COVID期': 'COVID期ダミー\n(2020・2021年)', '求人倍率': '求人倍率', 'COVID×旅館密度': 'COVID×旅館密度\n(観光依存×コロナ)', '高齢化率': '高齢化率(%)', } fig, ax = plt.subplots(figsize=(8, 5)) vars_order = [v for v in ['COVID期', '求人倍率', 'COVID×旅館密度', '高齢化率'] if v in coef_results] y_pos = list(range(len(vars_order))) coefs = [coef_results[v][0] for v in vars_order] ses = [coef_results[v][1] for v in vars_order] pvals = [coef_results[v][2] for v in vars_order] ci95 = [1.96 * se for se in ses] colors_bar = [] for p in pvals: if p < 0.01: colors_bar.append('#c0392b') elif p < 0.05: colors_bar.append('#e67e22') else: colors_bar.append('#7f8c8d') bars = ax.barh(y_pos, coefs, xerr=ci95, color=colors_bar, alpha=0.85, capsize=5, error_kw={'linewidth': 1.5}, height=0.55) ax.axvline(x=0, color='black', linewidth=1.2, linestyle='-') for i, (coef, se, pval) in enumerate(zip(coefs, ses, pvals)): sig_str = '***' if pval < 0.001 else ('**' if pval < 0.01 else ('*' if pval < 0.05 else 'n.s.')) x_text = coef + 1.96 * se + abs(max(coefs + [ci95[j] for j in range(len(ci95))])) * 0.03 ax.text(coef, i, f' {coef:+.0f} {sig_str}', va='center', ha='left' if coef >= 0 else 'right', fontsize=10, fontweight='bold', color='#2c3e50') ax.set_yticks(y_pos) ax.set_yticklabels([var_labels[v] for v in vars_order], fontsize=10) ax.set_xlabel('推定係数(消費支出への効果:円/月)', fontsize=10) ax.set_title(f'固定効果パネル推定:主要係数と95%信頼区間\n({model_type})', fontsize=12, fontweight='bold') ax.grid(axis='x', alpha=0.3, linestyle='--') |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。fig, ax = plt.subplots(...) — 図全体(fig)と軸(ax)を作る定番。以降は ax.bar(...) 等で操作。ax.axhline / ax.axvline — 水平/垂直の点線。平均線や基準線として定番。np.cumsum(arr) は累積和、np.linspace(a, b, n) は「aからbを等間隔でn個」。NumPyの定石です。241 242 243 244 245 246 247 248 249 250 251 252 253 254 | # 凡例 from matplotlib.patches import Patch legend_elements = [ Patch(facecolor='#c0392b', alpha=0.85, label='p < 0.01 (***)'), Patch(facecolor='#e67e22', alpha=0.85, label='p < 0.05 (*)'), Patch(facecolor='#7f8c8d', alpha=0.85, label='n.s.'), ] ax.legend(handles=legend_elements, loc='lower right', fontsize=9) plt.tight_layout() fig3_path = os.path.join(FIG_DIR, '2020_U1_fig3.png') plt.savefig(fig3_path, bbox_inches='tight') plt.close() print(f"図3保存: {fig3_path}") |
図3保存: html/figures/2020_U1_fig3.png
import pandas as pd など — 必要なライブラリをまとめて呼び出します。as pd は短い別名(alias)。fig.savefig(..., bbox_inches='tight') — 余白を自動で詰めて保存。plt.close() でメモリ解放。{値:.2f}(小数2桁)、{値:,}(3桁区切り)、{値:>10}(右寄せ10桁)など、覚えると出力が一気に整います。本分析の結果から、以下の政策的インプリケーションが示唆される。
256 257 258 259 260 261 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 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 | vars_needed = ['都道府県', '年度', '消費支出', 'COVID期', '求人倍率', '旅館密度', '高齢化率'] df_panel_all = df_b[vars_needed].dropna().copy() coef_results = {} res_summary = None model_type = 'OLS(フォールバック)' try: from linearmodels.panel import PanelOLS # COVID期ダミーは時間固定効果に完全吸収されるため、 # 「COVID期 × 観光依存度」の交互作用項を用いてEntity FEのみで推定 df_panel_fe = df_panel_all.copy() df_panel_fe['COVID_旅館'] = df_panel_fe['COVID期'] * df_panel_fe['旅館密度'] df_panel_fe = df_panel_fe.set_index(['都道府県', '年度']) mod = PanelOLS.from_formula( '消費支出 ~ COVID期 + 求人倍率 + COVID_旅館 + EntityEffects', data=df_panel_fe ) res = mod.fit(cov_type='clustered', cluster_entity=True) print("=== PanelOLS(Entity FE)推定結果 ===") print(res.summary) coef_results = { 'COVID期': (res.params['COVID期'], res.std_errors['COVID期'], res.pvalues['COVID期']), '求人倍率': (res.params['求人倍率'], res.std_errors['求人倍率'], res.pvalues['求人倍率']), 'COVID×旅館密度': (res.params['COVID_旅館'], res.std_errors['COVID_旅館'], res.pvalues['COVID_旅館']), } model_type = 'Entity FE パネル推定(PanelOLS)' r2 = res.rsquared n_obs = res.nobs print(f"\nR²={r2:.4f}, N={n_obs}") except Exception as e: print(f'PanelOLS failed: {e}, falling back to OLS') # エンティティダミー + 年度ダミーをOLSで実装(2Way FE相当) df_ols = df_panel_all.copy() df_ols['COVID_旅館'] = df_ols['COVID期'] * df_ols['旅館密度'] df_ols = pd.get_dummies(df_ols, columns=['都道府県', '年度'], drop_first=True) bool_cols = df_ols.select_dtypes(include='bool').columns df_ols[bool_cols] = df_ols[bool_cols].astype(int) y_ols = df_ols['消費支出'] X_cols = ['COVID期', '求人倍率', 'COVID_旅館', '高齢化率'] + \ [c for c in df_ols.columns if c.startswith('都道府県_') or c.startswith('年度_')] X_ols = sm.add_constant(df_ols[X_cols]) res_ols = sm.OLS(y_ols, X_ols).fit(cov_type='HC1') print("=== OLS(FEフォールバック)推定結果 ===") print(res_ols.summary()) coef_results = { 'COVID期': (res_ols.params['COVID期'], res_ols.bse['COVID期'], res_ols.pvalues['COVID期']), '求人倍率': (res_ols.params['求人倍率'], res_ols.bse['求人倍率'], res_ols.pvalues['求人倍率']), 'COVID×旅館密度': (res_ols.params['COVID_旅館'], res_ols.bse['COVID_旅館'], res_ols.pvalues['COVID_旅館']), '高齢化率': (res_ols.params['高齢化率'], res_ols.bse['高齢化率'], res_ols.pvalues['高齢化率']), } r2 = res_ols.rsquared n_obs = int(res_ols.nobs) print(f"\nR²={r2:.4f}, N={n_obs}") |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。import pandas as pd など — 必要なライブラリをまとめて呼び出します。as pd は短い別名(alias)。.astype(int) — 列を整数に変換(年度などを数値比較するため)。sm.add_constant(X) — 切片項(定数1の列)を先頭に追加。statsmodelsで必須。sm.OLS(y, X).fit() — 最小二乗法でモデルを推定。model.params, model.pvalues, model.conf_int() で結果取得。[式 for x in リスト] はリスト内包表記。forループでappendする代わりに1行でリストを作れます。311 312 313 314 315 | # 推定係数サマリ表示 print("\n=== 主要係数サマリ ===") for var, (coef, se, pval) in coef_results.items(): sig = '***' if pval < 0.001 else ('**' if pval < 0.01 else ('*' if pval < 0.05 else 'n.s.')) print(f"{var}: coef={coef:+.1f}, SE={se:.1f}, p={pval:.4f} {sig}") |
=== PanelOLS(Entity FE)推定結果 ===
PanelOLS Estimation Summary
================================================================================
Dep. Variable: 消費支出 R-squared: 0.0581
Estimator: PanelOLS R-squared (Between): 0.0034
No. Observations: 564 R-squared (Within): 0.0581
Date: Mon, May 18 2026 R-squared (Overall): 0.0035
Time: 11:23:40 Log-likelihood -6133.8
Cov. Estimator: Clustered
F-statistic: 10.566
Entities: 47 P-value 0.0000
Avg Obs: 12.000 Distribution: F(3,514)
Min Obs: 12.000
Max Obs: 12.000 F-statistic (robust): 10.802
P-value 0.0000
Time periods: 12 Distribution: F(3,514)
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
------------------------------------------------------------------------------
COVID期 -6712.8 2793.3 -2.4032 0.0166 -1.22e+04 -1225.2
求人倍率 1586.4 2379.6 0.6667 0.5053 -3088.6 6261.3
COVID_旅館 -297.52 369.77 -0.8046 0.4214 -1024.0 428.91
==============================================================================
F-test for Poolability: 24.826
P-value: 0.0000
Distribution: F(46,514)
Included effects: Entity
R²=0.0581, N=564
=== 主要係数サマリ ===
COVID期: coef=-6712.8, SE=2793.3, p=0.0166 *
求人倍率: coef=+1586.4, SE=2379.6, p=0.5053 n.s.
COVID×旅館密度: coef=-297.5, SE=369.8, p=0.4214 n.s.r, p = stats.pearsonr(...) — Pythonは複数戻り値を同時に受け取れる(タプルアンパック)。316 317 318 319 320 321 322 323 324 325 326 327 328 329 | fig, ax = plt.subplots(figsize=(12, 7)) bar_colors = [region_colors.get(r, '#aaaaaa') for r in df_change['地域']] bars = ax.bar(range(len(df_change)), df_change['変化率'], color=bar_colors, alpha=0.85, edgecolor='white', linewidth=0.5) ax.axhline(y=0, color='black', linewidth=1.2) ax.axhline(y=df_change['変化率'].mean(), color='#2c3e50', linewidth=1.5, linestyle='--', alpha=0.8, label=f"全国平均: {df_change['変化率'].mean():+.1f}%") ax.set_xticks(range(len(df_change))) ax.set_xticklabels(df_change['都道府県'].tolist(), rotation=90, fontsize=8) ax.set_ylabel('消費支出変化率(%)', fontsize=11) ax.set_title('2019年→2020年 消費支出変化率(都道府県別)\nCOVID-19 第一波の地域格差', fontsize=13, fontweight='bold') ax.grid(axis='y', alpha=0.3, linestyle='--') |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。fig, ax = plt.subplots(...) — 図全体(fig)と軸(ax)を作る定番。以降は ax.bar(...) 等で操作。ax.axhline / ax.axvline — 水平/垂直の点線。平均線や基準線として定番。{値:.2f}(小数2桁)、{値:,}(3桁区切り)、{値:>10}(右寄せ10桁)など、覚えると出力が一気に整います。330 331 332 333 334 335 336 337 338 339 340 341 342 | # 地域凡例 from matplotlib.patches import Patch legend_elements = [Patch(facecolor=c, alpha=0.85, label=r) for r, c in region_colors.items()] ax.legend(handles=legend_elements, loc='upper left', fontsize=9, ncol=2, framealpha=0.85) ax.legend(handles=legend_elements + [ plt.Line2D([0], [0], color='#2c3e50', linestyle='--', linewidth=1.5, label=f"全国平均: {df_change['変化率'].mean():+.1f}%") ], loc='upper left', fontsize=9, ncol=2, framealpha=0.85) plt.tight_layout() fig4_path = os.path.join(FIG_DIR, '2020_U1_fig4.png') plt.savefig(fig4_path, bbox_inches='tight') plt.close() print(f"図4保存: {fig4_path}") |
図4保存: html/figures/2020_U1_fig4.png
import pandas as pd など — 必要なライブラリをまとめて呼び出します。as pd は短い別名(alias)。fig.savefig(..., bbox_inches='tight') — 余白を自動で詰めて保存。plt.close() でメモリ解放。plt.subplots(figsize=(W, H)) で図サイズ指定、fig.savefig(..., bbox_inches='tight') で余白を自動で詰めて保存。本研究では、SSDSE-B 47都道府県の2012〜2023年パネルデータを用い、COVID-19第一波(2020〜2021年度)が消費支出・雇用(求人倍率)に与えた影響を固定効果パネル推定で分析した。
| 分析テーマ | 主な発見 |
|---|---|
| COVID期の消費抑制効果 | 都道府県固定効果制御後も月平均−6,713円の有意な消費抑制(p=0.017) |
| 求人倍率の変化 | 全国平均 1.46 → 1.11(−0.35ポイント):2013年以降の回復トレンドが一気に反転 |
| 地域格差 | 2019→2020年の消費変化率は最大−15.7%(石川)〜+8.9%(大分)と大きな地域差 |
| 観光依存度の効果 | 交互作用項は有意でなく、観光依存度による系統的差異は本サンプルでは検出困難 |
| Entity FE 検定 | Poolability F検定 F=24.8(p<0.001):固定効果モデルの採用が統計的に支持 |
SSDSE-B-2026 Entity FE PanelOLS COVID-19 第一波 消費支出分析 地域格差 総務大臣賞 2020
統計分析の解釈で初心者がやりがちな勘違いをまとめます。特に「相関と因果の混同」「p値の過信」は研究現場でもよく起きる落とし穴です。本文を読む前にも、読んだ後にも、目を通してみてください。
統計の基本用語を初心者向けに解説します。本文中で見慣れない言葉が出てきたら、ここに戻って確認してください。
統計手法について「何のためか」「結果をどう読むか」を初心者向けに解説します。
この研究をさらに発展させるための3つの方向性を示します。「今回わかったこと(X)」から「次に検証すべき仮説(Y)」を立て、「具体的に何をするか(Z)」まで考えてみましょう。
学んだだけでは身につきません。実際に手を動かすのが最強の学習方法です。本論文のスクリプトをベースに、以下のチャレンジに挑戦してみてください。難易度別に5つ用意しました。
本論文で学んだ手法は、研究の世界だけでなく、行政・企業・NPO の現場でも様々に活用されています。具体的なシーンを紹介します。
この論文を読んで初心者が抱きやすい疑問に、教育的観点から答えます。