このページの分析を自分で再現するには、以下の手順でデータを準備してください。コードの編集は不要です。
data/raw/ フォルダに入れます。html/figures/ に自動保存されます。
2020年初頭から拡大したCOVID-19パンデミックは、日本の地域経済に甚大かつ不均等な打撃を与えた。緊急事態宣言・まん延防止等重点措置・Go Toトラベルの停止など政策介入が相次ぐ中、消費支出の急落と雇用の悪化は都道府県によって大きく異なった。
まず「COVID-19が地域経済に与えた影響消費支出・雇用指標のパネル分析」を統計的にとらえることが有効だと考えられる。 その理由は感覚や経験則だけでは、複雑な社会要因の中で「何が本当に効いているか」を見極めにくいからである。 本研究では公開データと統計手法を組み合わせ、この問いに定量的な答えを出すことを目指す。
本研究は、SSDSE-B(都道府県別パネルデータ)を用いて、COVID-19期(2020〜2021年)が消費支出と有効求人倍率に与えた影響を双方向固定効果パネルOLSで定量的に推定する。さらに差の差(DiD)の枠組みを応用し、観光依存度が高い地域と低い地域の消費変動格差を検証する。
SSDSE-B パネルOLS 双方向固定効果 差の差(DiD) COVID-19 総務大臣賞
統計数理研究所が提供する SSDSE-B は、都道府県を単位とした多分野の統計指標を収録している。本分析では2012〜2023年度の12年間、47都道府県の計564観測値を使用する。
| 変数名 | 説明 | 役割 | 変換 |
|---|---|---|---|
| 消費支出(二人以上の世帯) | 月額消費支出(円) | 被説明変数 | log変換(ln) |
| COVID期ダミー | 年度 ∈ {2020, 2021} → 1, それ以外 → 0 | 主要説明変数 | 0/1ダミー |
| 有効求人倍率 | 月間有効求人数 / 月間有効求職者数(一般) | 雇用指標 | 比率 |
| 宿泊密度 | 延べ宿泊者数 / 総人口 | 観光依存度の代理変数 | 一人あたり |
| 高齢化率 | 65歳以上人口 / 総人口 × 100 | 人口構造コントロール | % |
宿泊密度(延べ宿泊者数 / 総人口)は、都道府県の観光業への依存度を測る代理変数として設計した。宿泊密度が高い地域はCOVID期に需要蒸発リスクが大きい一方、Go Toトラベル停止の直撃を受けやすい。
差の差(DiD)推定では、COVID-19のような外生的ショックを「自然実験」として利用する。処置群(COVID期)と対照群(非COVID期)が、ショック前に同じトレンドを持つ(並行トレンド仮定)ことを確認するのが第一歩。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | import numpy as np import pandas as pd import matplotlib matplotlib.use('Agg') import matplotlib.pyplot as plt import warnings warnings.filterwarnings('ignore') from linearmodels.panel import PanelOLS 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 import os 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) df_b = df_b.sort_values(['都道府県', '年度']).reset_index(drop=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)。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) — 指定列で並べ替え(降順)。f"...{x}..." はf-string。文字列の中に {変数} と書くだけで埋め込めて、{x:.2f} のように書式も指定できます。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 65 66 67 68 69 70 71 | # 変数生成 df_b['消費支出_log'] = np.log(df_b['消費支出(二人以上の世帯)'].clip(lower=1)) df_b['消費支出_万円'] = df_b['消費支出(二人以上の世帯)'] / 10000 df_b['求人倍率'] = df_b['月間有効求人数(一般)'] / df_b['月間有効求職者数(一般)'].replace(0, np.nan) df_b['宿泊密度'] = df_b['延べ宿泊者数'] / df_b['総人口'].replace(0, np.nan) df_b['高齢化率'] = df_b['65歳以上人口'] / df_b['総人口'] * 100 df_b['COVID期'] = df_b['年度'].isin([2020, 2021]).astype(int) region_map = { '北海道': '北海道・東北', '青森県': '北海道・東北', '岩手県': '北海道・東北', '宮城県': '北海道・東北', '秋田県': '北海道・東北', '山形県': '北海道・東北', '福島県': '北海道・東北', '茨城県': '関東', '栃木県': '関東', '群馬県': '関東', '埼玉県': '関東', '千葉県': '関東', '東京都': '関東', '神奈川県': '関東', '新潟県': '中部', '富山県': '中部', '石川県': '中部', '福井県': '中部', '山梨県': '中部', '長野県': '中部', '岐阜県': '中部', '静岡県': '中部', '愛知県': '中部', '三重県': '近畿', '滋賀県': '近畿', '京都府': '近畿', '大阪府': '近畿', '兵庫県': '近畿', '奈良県': '近畿', '和歌山県': '近畿', '鳥取県': '中国・四国', '島根県': '中国・四国', '岡山県': '中国・四国', '広島県': '中国・四国', '山口県': '中国・四国', '徳島県': '中国・四国', '香川県': '中国・四国', '愛媛県': '中国・四国', '高知県': '中国・四国', '福岡県': '九州・沖縄', '佐賀県': '九州・沖縄', '長崎県': '九州・沖縄', '熊本県': '九州・沖縄', '大分県': '九州・沖縄', '宮崎県': '九州・沖縄', '鹿児島県': '九州・沖縄', '沖縄県': '九州・沖縄', } df_b['地域'] = df_b['都道府県'].map(region_map) region_colors = { '北海道・東北': '#4e9af1', '関東': '#e05c5c', '中部': '#f0a500', '近畿': '#5cb85c', '中国・四国': '#9b59b6', '九州・沖縄': '#f39c12', } fig, ax = plt.subplots(figsize=(10, 5)) yearly = df_b.groupby(['年度', '地域'])['消費支出_万円'].mean().reset_index() for reg, grp in yearly.groupby('地域'): ax.plot(grp['年度'], grp['消費支出_万円'], marker='o', markersize=4, label=reg, color=region_colors.get(reg, 'gray')) ax.axvspan(2019.5, 2021.5, alpha=0.1, color='red', label='COVID期(2020-2021年)') ax.set_xlabel('年度', fontsize=12) ax.set_ylabel('消費支出(万円/月)', fontsize=12) ax.set_title('地域別 消費支出の推移(2012〜2023年)\nCOVID-19の影響(2020-2021年の急落)', fontsize=13, fontweight='bold') ax.legend(fontsize=8, ncol=2) ax.grid(alpha=0.3) plt.tight_layout() plt.savefig(os.path.join(FIG_DIR, '2021_U1_fig1_ts.png'), dpi=150, bbox_inches='tight') plt.close() print("Fig1 saved") |
Fig1 saved
.astype(int) — 列を整数に変換(年度などを数値比較するため)。df.groupby('列').apply(関数) — グループごとに関数を適用。時系列や地域別の集計でよく使います。fig, ax = plt.subplots(...) — 図全体(fig)と軸(ax)を作る定番。以降は ax.bar(...) 等で操作。fig.savefig(..., bbox_inches='tight') — 余白を自動で詰めて保存。plt.close() でメモリ解放。df['A'] / df['B'] — pandasの列同士の四則演算は要素ごと(element-wise)。forループ不要なのが強み。個体固定効果(都道府県の時不変特性)と時間固定効果(全国共通の年次変動)を同時に制御する双方向固定効果(Two-Way Fixed Effects)モデルを推定する。
| 変数 | 係数 | 標準誤差(クラスター) | p値 | 有意性 |
|---|---|---|---|---|
| COVID期ダミー(2020・2021年) | (結果図参照) | — | — | — |
| 有効求人倍率 | (結果図参照) | — | — | — |
| 宿泊密度 | (結果図参照) | — | — | — |
| 高齢化率 | (結果図参照) | — | — | — |
| 注:クラスター標準誤差(都道府県クラスター)。個体効果・時間効果をともに含む双方向FE。 N=564(47都道府県 × 12年)。 | ||||
linearmodels の PanelOLS は entity_effects=True(個体固定効果)と time_effects=True(時間固定効果)を同時に指定できる。クラスター標準誤差(cluster_entity=True)は都道府県内の系列相関を修正し、過小推定を防ぐ。
DiD推定の核心は「処置群のショック前後の変化」から「対照群のショック前後の変化」を引くこと。2×2の単純な比較から始め、複数時点・複数個体に拡張するのがパネルOLDSの差の差推定。
73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 | fig, ax = plt.subplots(figsize=(10, 5)) yearly_k = df_b.groupby(['年度', '地域'])['求人倍率'].mean().reset_index() for reg, grp in yearly_k.groupby('地域'): ax.plot(grp['年度'], grp['求人倍率'], marker='o', markersize=4, label=reg, color=region_colors.get(reg, 'gray')) ax.axvspan(2019.5, 2021.5, alpha=0.1, color='red', label='COVID期') ax.axhline(1.0, color='black', linestyle='--', linewidth=0.8, label='倍率=1.0') ax.set_xlabel('年度', fontsize=12) ax.set_ylabel('有効求人倍率', fontsize=12) ax.set_title('地域別 有効求人倍率の推移(2012〜2023年)\nCOVID-19の雇用への影響', fontsize=13, fontweight='bold') ax.legend(fontsize=8, ncol=2) ax.grid(alpha=0.3) plt.tight_layout() plt.savefig(os.path.join(FIG_DIR, '2021_U1_fig2_koyou_ts.png'), dpi=150, bbox_inches='tight') plt.close() print("Fig2 saved") |
Fig2 saved
df.groupby('列').apply(関数) — グループごとに関数を適用。時系列や地域別の集計でよく使います。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ループ不要なのが強み。
| 要因 | 高影響都道府県の特徴 | 低影響都道府県の特徴 |
|---|---|---|
| 産業構造 | 観光業・飲食業・宿泊業の比率高 | 製造業・農業の比率高 |
| 人口密度 | 大都市・人口集中地域 | 地方圏・人口分散地域 |
| 外国人依存度 | インバウンド比率が高い | 国内需要中心 |
| 緊急事態宣言 | 発令対象地域に含まれた | 発令対象外(一部) |
90 91 92 93 94 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 | panel_vars = ['COVID期', '求人倍率', '宿泊密度', '高齢化率'] df_panel = df_b[['年度', '都道府県', '消費支出_log'] + panel_vars].dropna() df_panel = df_panel.set_index(['都道府県', '年度']) y = df_panel['消費支出_log'] X = sm.add_constant(df_panel[panel_vars]) try: mod = PanelOLS(y, X, entity_effects=True, time_effects=True, drop_absorbed=True) res = mod.fit(cov_type='clustered', cluster_entity=True) coefs = res.params.drop('const', errors='ignore') ses = res.std_errors.drop('const', errors='ignore') pvals = res.pvalues.drop('const', errors='ignore') print(res.summary) except Exception as e: print(f"2-way FE error: {e}") mod = PanelOLS(y, X, entity_effects=True, drop_absorbed=True) res = mod.fit(cov_type='clustered', cluster_entity=True) coefs = res.params.drop('const', errors='ignore') ses = res.std_errors.drop('const', errors='ignore') pvals = res.pvalues.drop('const', errors='ignore') fig, ax = plt.subplots(figsize=(8, 5)) colors_c = ['#e05c5c' if p < 0.05 else '#888888' for p in pvals] ax.barh(range(len(coefs)), coefs, xerr=1.96 * ses, color=colors_c, alpha=0.8, error_kw={'elinewidth': 1.5, 'capsize': 4}) ax.set_yticks(range(len(coefs))) ax.set_yticklabels(coefs.index, fontsize=10) ax.axvline(0, color='black', linewidth=0.8) ax.set_xlabel('双方向固定効果推定係数', fontsize=12) ax.set_title('消費支出への影響 — 双方向FEパネルOLS\n(赤=p<0.05, 灰=非有意)', fontsize=12, fontweight='bold') ax.grid(axis='x', alpha=0.3) plt.tight_layout() plt.savefig(os.path.join(FIG_DIR, '2021_U1_fig3_fe_coef.png'), dpi=150, bbox_inches='tight') plt.close() print("Fig3 saved") |
PanelOLS Estimation Summary
================================================================================
Dep. Variable: 消費支出_log R-squared: 0.0009
Estimator: PanelOLS R-squared (Between): 0.0068
No. Observations: 564 R-squared (Within): -0.0183
Date: Mon, May 18 2026 R-squared (Overall): -0.0008
Time: 11:23:54 Log-likelihood 961.46
Cov. Estimator: Clustered
F-statistic: 0.1568
Entities: 47 P-value 0.9253
Avg Obs: 12.000 Distribution: F(3,503)
Min Obs: 12.000
Max Obs: 12.000 F-statistic (robust): 0.1524
P-value 0.9282
Time periods: 12 Distribution: F(3,503)
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
------------------------------------------------------------------------------
const 12.608 0.1182 106.66 0.0000 12.376 12.840
求人倍率 -0.0116 0.0242 -0.4778 0.6330 -0.0591 0.0360
宿泊密度 -0.0009 0.0030 -0.3078 0.7583 -0.0067 0.0049
高齢化率 -0.0009 0.0041 -0.2173 0.8281 -0.0089 0.0071
==============================================================================
F-test for Poolability: 19.072
P-value: 0.0000
Distribution: F(57,503)
Included effects: Entity, Time
Fig3 savedfig, ax = plt.subplots(...) — 図全体(fig)と軸(ax)を作る定番。以降は ax.bar(...) 等で操作。ax.axhline / ax.axvline — 水平/垂直の点線。平均線や基準線として定番。sm.add_constant(X) — 切片項(定数1の列)を先頭に追加。statsmodelsで必須。fig.savefig(..., bbox_inches='tight') — 余白を自動で詰めて保存。plt.close() でメモリ解放。.map() は「1対1の置き換え」、.apply() は「関数を当てる」。辞書なら .map()、ロジックなら .apply()。124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 | df_2019 = df_b[df_b['年度'] == 2019][['都道府県', '消費支出_万円', '地域']].copy() df_2020 = df_b[df_b['年度'] == 2020][['都道府県', '消費支出_万円']].copy() df_covid = df_2019.merge(df_2020, on='都道府県', suffixes=('_2019', '_2020')) df_covid['変化率'] = (df_covid['消費支出_万円_2020'] - df_covid['消費支出_万円_2019']) / df_covid['消費支出_万円_2019'] * 100 df_covid = df_covid.sort_values('変化率', ascending=True) fig, ax = plt.subplots(figsize=(8, 12)) bar_colors = [region_colors.get(r, 'gray') for r in df_covid['地域']] ax.barh(df_covid['都道府県'], df_covid['変化率'], color=bar_colors, alpha=0.8) ax.axvline(0, color='black', linewidth=0.8) ax.set_xlabel('消費支出変化率(2019→2020年, %)', fontsize=12) ax.set_title('COVID-19による消費支出変化率ランキング\n(2019→2020年、都道府県別)', fontsize=13, fontweight='bold') ax.grid(axis='x', alpha=0.3) plt.tight_layout() plt.savefig(os.path.join(FIG_DIR, '2021_U1_fig4_covid_rank.png'), dpi=150, bbox_inches='tight') plt.close() print("Fig4 saved") print("All done!") |
Fig4 saved All done!
fig, ax = plt.subplots(...) — 図全体(fig)と軸(ax)を作る定番。以降は ax.bar(...) 等で操作。sort_values('列名', ascending=False) — 指定列で並べ替え(降順)。ax.axhline / ax.axvline — 水平/垂直の点線。平均線や基準線として定番。fig.savefig(..., bbox_inches='tight') — 余白を自動で詰めて保存。plt.close() でメモリ解放。[式 for x in リスト] はリスト内包表記。forループでappendする代わりに1行でリストを作れます。| ファイル | 説明 | 出典 |
|---|---|---|
| SSDSE-B-2026.csv | 都道府県別パネルデータ(2012〜2023年度) | 統計数理研究所 SSDSE(社会・人口統計体系) |
| 2021_U1_fig1_ts.png | 地域別消費支出の時系列推移 | 本コード生成(SSDSE-B実データ) |
| 2021_U1_fig2_koyou_ts.png | 地域別有効求人倍率の時系列推移 | 本コード生成(SSDSE-B実データ) |
| 2021_U1_fig3_fe_coef.png | 双方向FEパネルOLS係数プロット | 本コード生成(SSDSE-B実データ) |
| 2021_U1_fig4_covid_rank.png | 都道府県別COVID消費変化率ランキング | 本コード生成(SSDSE-B実データ) |
本教育用コードは SSDSE-B-2026.csv(実公的データ)のみを使用しています。合成データ(np.random.seed等)は一切使用していません。
統計分析の解釈で初心者がやりがちな勘違いをまとめます。特に「相関と因果の混同」「p値の過信」は研究現場でもよく起きる落とし穴です。本文を読む前にも、読んだ後にも、目を通してみてください。
統計の基本用語を初心者向けに解説します。本文中で見慣れない言葉が出てきたら、ここに戻って確認してください。
統計手法について「何のためか」「結果をどう読むか」を初心者向けに解説します。
この研究をさらに発展させるための3つの方向性を示します。「今回わかったこと(X)」から「次に検証すべき仮説(Y)」を立て、「具体的に何をするか(Z)」まで考えてみましょう。
学んだだけでは身につきません。実際に手を動かすのが最強の学習方法です。本論文のスクリプトをベースに、以下のチャレンジに挑戦してみてください。難易度別に5つ用意しました。
本論文で学んだ手法は、研究の世界だけでなく、行政・企業・NPO の現場でも様々に活用されています。具体的なシーンを紹介します。
この論文を読んで初心者が抱きやすい疑問に、教育的観点から答えます。