このページの分析を自分で再現するには、以下の手順でデータを準備してください。コードの編集は不要です。
data/raw/ フォルダに入れます。html/figures/ に自動保存されます。
2020年初頭から始まった新型コロナウイルス感染症(COVID-19)のパンデミックは、日本経済に甚大な打撃を与えた。中でも観光業・宿泊業・飲食業は、政府による移動制限や緊急事態宣言の影響を直接的に受け、急激な売上減少を経験した。
まず「コロナ禍における観光業への打撃と地域経済への影響」を統計的にとらえることが有効だと考えられる。 その理由は感覚や経験則だけでは、複雑な社会要因の中で「何が本当に効いているか」を見極めにくいからである。 本研究では公開データと統計手法を組み合わせ、この問いに定量的な答えを出すことを目指す。
本研究は、SSDSE-B(都道府県パネルデータ)を用いて、コロナ禍が観光業を通じて地域経済(消費支出)に与えた影響を定量化することを目的とする。特に、観光依存度の高い県と低い県で、コロナ禍の消費落ち込みに差があったかを差の差分析(DID)で検証する。
差の差分析(DID) 時系列分析 地域比較 SSDSE-B パネルデータ
統計数理研究所が提供する SSDSE-B(社会・人口統計体系 都道府県データ)2026年版を使用。 地域コードが「R」で始まる47都道府県のデータを抽出し、2018〜2023年の6年間(282観測)を分析対象とした。
| 変数 | SSDSE-B列名 | 役割 | 単位 |
|---|---|---|---|
| 消費支出 | 消費支出(二人以上の世帯) | 目的変数(地域経済指標) | 円/月 |
| 旅館密度 | 旅館営業施設数(ホテルを含む)/ 総人口 | 観光依存度プロキシ | 施設/万人 |
| コロナダミー | 年度 ∈ {2020, 2021} → 1 | 介入(post)変数 | 0/1 |
| 処置ダミー | 旅館密度 ≥ 中央値 → 1 | 観光高依存グループ(treat) | 0/1 |
| 交差項 | treat × post | DID推定量(主要関心変数) | — |
| 順位 | 都道府県 | 旅館密度(施設/万人) | 主な観光地 |
|---|---|---|---|
| 1 | 山梨県 | 16.33 | 富士山、富士五湖、温泉地 |
| 2 | 沖縄県 | 14.75 | リゾート、マリンスポーツ |
| 3 | 長野県 | 12.71 | 軽井沢、上高地、スキーリゾート |
| 4 | 福井県 | 12.31 | 東尋坊、芦原温泉 |
| 5 | 鳥取県 | 10.84 | 鳥取砂丘、皆生温泉 |
| 6 | 大分県 | 9.52 | 別府・湯布院(全国有数の温泉地) |
| 7 | 新潟県 | 9.27 | 越後湯沢、佐渡 |
| 8 | 福島県 | 8.21 | 会津、磐梯山、温泉地多数 |
| 9 | 静岡県 | 7.78 | 熱海、伊豆半島 |
| 10 | 山形県 | 7.47 | 蔵王、銀山温泉 |
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 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | 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) region_map = { '北海道': '北海道・東北', '青森県': '北海道・東北', '岩手県': '北海道・東北', '宮城県': '北海道・東北', '秋田県': '北海道・東北', '山形県': '北海道・東北', '福島県': '北海道・東北', '茨城県': '関東', '栃木県': '関東', '群馬県': '関東', '埼玉県': '関東', '千葉県': '関東', '東京都': '関東', '神奈川県': '関東', '新潟県': '中部', '富山県': '中部', '石川県': '中部', '福井県': '中部', '山梨県': '中部', '長野県': '中部', '岐阜県': '中部', '静岡県': '中部', '愛知県': '中部', '三重県': '近畿', '滋賀県': '近畿', '京都府': '近畿', '大阪府': '近畿', '兵庫県': '近畿', '奈良県': '近畿', '和歌山県': '近畿', '鳥取県': '中国・四国', '島根県': '中国・四国', '岡山県': '中国・四国', '広島県': '中国・四国', '山口県': '中国・四国', '徳島県': '中国・四国', '香川県': '中国・四国', '愛媛県': '中国・四国', '高知県': '中国・四国', '福岡県': '九州・沖縄', '佐賀県': '九州・沖縄', '長崎県': '九州・沖縄', '熊本県': '九州・沖縄', '大分県': '九州・沖縄', '宮崎県': '九州・沖縄', '鹿児島県': '九州・沖縄', '沖縄県': '九州・沖縄' } region_colors = { '北海道・東北': '#4e9af1', '関東': '#e05c5c', '中部': '#f0a500', '近畿': '#5cb85c', '中国・四国': '#9b59b6', '九州・沖縄': '#f39c12' } |
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} のように書式も指定できます。42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 | df = df_b[df_b['年度'].between(2018, 2023)].copy() df['地方区分'] = df['都道府県'].map(region_map) # 旅館密度(施設数 per 万人) df['旅館密度'] = df['旅館営業施設数(ホテルを含む)'] / (df['総人口'] / 10000) # コロナダミー: 2020,2021年=1, それ以外=0 df['コロナダミー'] = df['年度'].isin([2020, 2021]).astype(int) # 消費支出(月額・円) df['消費支出'] = df['消費支出(二人以上の世帯)'] print("=== データ確認 ===") print(f"観測数: {len(df)} 件(47都道府県×6年)") print(f"年度: {sorted(df['年度'].unique())}") print(f"消費支出 記述統計:\n{df['消費支出'].describe().round(0)}") |
=== データ確認 === 観測数: 282 件(47都道府県×6年) 年度: [np.int64(2018), np.int64(2019), np.int64(2020), np.int64(2021), np.int64(2022), np.int64(2023)] 消費支出 記述統計: count 282.0 mean 287144.0 std 23955.0 min 210593.0 25% 272665.0 50% 286885.0 75% 304002.0 max 355065.0 Name: 消費支出, dtype: float64
.astype(int) — 列を整数に変換(年度などを数値比較するため)。.describe() — 件数・平均・標準偏差・四分位・最大/最小を一括計算。データの素性チェックに必須。df['A'] / df['B'] — pandasの列同士の四則演算は要素ごと(element-wise)。forループ不要なのが強み。まず全国47都道府県の平均消費支出(二人以上世帯)の時系列推移を確認し、コロナ禍(2020〜2021年)における落ち込みの全体像を把握する。
全国平均では約−2.8%の落ち込みが確認された。ただしこれは47都道府県の単純平均であり、観光依存度の高い県では、より大きな落ち込みが生じた可能性がある。次節の差の差分析でこの「異質性」を検証する。
時系列データで「前後比較」をする際は、水準値(実額)と変化率のどちらを使うかが重要。水準値はそのままの値だが都道府県間の経済規模の差を反映してしまう。変化率は各地域の「ショック」の大きさを比較するのに適している。
59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 | ts = df.groupby('年度')['消費支出'].mean() years = ts.index.tolist() fig1, ax1 = plt.subplots(figsize=(9, 5)) ax1.axvspan(2019.5, 2021.5, alpha=0.15, color='red', label='COVID-19 影響期(2020-2021年)') ax1.plot(years, ts.values / 10000, 'o-', color='#1565C0', linewidth=2.5, markersize=7, label='全国平均消費支出(二人以上世帯)') # 変化率注記 for i, yr in enumerate(years): val = ts[yr] / 10000 ax1.annotate(f'{val:.1f}万円', (yr, val), textcoords='offset points', xytext=(0, 10), ha='center', fontsize=9.5, color='#1565C0') ax1.axvline(2019.5, color='red', linestyle='--', alpha=0.5, linewidth=1) ax1.axvline(2021.5, color='red', linestyle='--', alpha=0.5, linewidth=1) ax1.set_xlabel('年度', fontsize=12) ax1.set_ylabel('消費支出(万円/月)', fontsize=12) ax1.set_title('図1:全国平均消費支出の時系列変化(2018–2023年)\nコロナ前後の消費動向', fontsize=13, fontweight='bold') ax1.legend(fontsize=10, loc='lower right') ax1.set_xticks(years) ax1.set_xticklabels([f'{y}年' for y in years]) ax1.grid(axis='y', alpha=0.3) ax1.set_ylim(20, 35) |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。df.groupby('列').apply(関数) — グループごとに関数を適用。時系列や地域別の集計でよく使います。fig, ax = plt.subplots(...) — 図全体(fig)と軸(ax)を作る定番。以降は ax.bar(...) 等で操作。ax.axhline / ax.axvline — 水平/垂直の点線。平均線や基準線として定番。.map() は「1対1の置き換え」、.apply() は「関数を当てる」。辞書なら .map()、ロジックなら .apply()。82 83 84 85 86 87 88 89 90 91 92 93 94 | # コロナ前後の差 pre_avg = ts[[2018, 2019]].mean() / 10000 post_avg = ts[[2020, 2021]].mean() / 10000 chg_pct = (post_avg - pre_avg) / pre_avg * 100 print(f"\n=== 図1: 消費支出時系列 ===") print(f"コロナ前平均: {pre_avg:.2f}万円") print(f"コロナ期平均: {post_avg:.2f}万円") print(f"変化率: {chg_pct:.1f}%") fig1.tight_layout() fig1.savefig(os.path.join(FIG_DIR, '2021_U5_3_fig1.png'), dpi=150, bbox_inches='tight') plt.close(fig1) print("fig1 保存完了") |
=== 図1: 消費支出時系列 === コロナ前平均: 28.83万円 コロナ期平均: 28.03万円 変化率: -2.8% fig1 保存完了
[式 for x in リスト] はリスト内包表記。forループでappendする代わりに1行でリストを作れます。差の差(Difference-in-Differences, DID)分析は、政策・外部ショックの因果効果を推定するために広く使われる準実験的手法である。ここでは「観光高依存県(処置群)」と「観光低依存県(比較群)」のコロナ前後の消費支出変化の差を推定する。
| 変数 | 定義 | 想定符号 |
|---|---|---|
| treati | 観光高依存県 = 1(旅館密度が全国中央値以上) | ?(水準差) |
| postt | コロナ期 = 1(年度 ∈ {2020, 2021}) | −(全体落ち込み) |
| treat × post | DID交差項(主要関心変数) | −(追加ダメージ) |
| 係数 | 推定値(円/月) | 95%CI | p値 | 解釈 |
|---|---|---|---|---|
| Intercept(β₀) | 293,000 | [288,000, 298,000] | 0.000 *** | 低依存・コロナ前の基準水準 |
| treat(β₁) | −4,762 | [−11,500, +1,989] | 0.166 n.s. | 高依存県は元々消費が低い傾向(有意でない) |
| post(β₂) | −11,640 | [−20,000, −3,289] | 0.006 ** | コロナ期全体で消費が月約1.2万円減少 |
| treat×post(β₃) | +2,832 | [−8,860, +14,524] | 0.634 n.s. | 観光高依存による追加ダメージ(有意でない) |
DIDが因果効果を推定するには「並行トレンド仮定(Parallel Trends Assumption)」が必要。これは「処置がなければ、処置群と比較群は同じトレンドで推移していた」という仮定である。コロナ禍のようなパンデミックは全国同時に起きるため、この仮定の検証が難しい。
96 97 98 99 100 101 102 103 104 105 | df_2019 = df[df['年度'] == 2019][['都道府県', '消費支出', '旅館密度', '地方区分']].copy() df_2019.columns = ['都道府県', '消費支出_2019', '旅館密度_2019', '地方区分'] df_2020 = df[df['年度'] == 2020][['都道府県', '消費支出']].copy() df_2020.columns = ['都道府県', '消費支出_2020'] scatter_df = pd.merge(df_2019, df_2020, on='都道府県') scatter_df['変化率2020'] = (scatter_df['消費支出_2020'] - scatter_df['消費支出_2019']) / scatter_df['消費支出_2019'] * 100 print(f"\n=== 図2: 旅館密度 vs 消費支出変化率 ===") print(f"変化率2020 記述統計:\n{scatter_df['変化率2020'].describe().round(2)}") |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。.describe() — 件数・平均・標準偏差・四分位・最大/最小を一括計算。データの素性チェックに必須。[式 for x in リスト] はリスト内包表記。forループでappendする代わりに1行でリストを作れます。106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 | # 相関係数 r_val, p_val = stats.pearsonr(scatter_df['旅館密度_2019'], scatter_df['変化率2020']) print(f"相関係数: r={r_val:.3f}, p={p_val:.4f}") fig2, ax2 = plt.subplots(figsize=(11, 7)) for region, grp in scatter_df.groupby('地方区分'): color = region_colors[region] ax2.scatter(grp['旅館密度_2019'], grp['変化率2020'], color=color, label=region, s=60, alpha=0.85, zorder=3) for _, row in grp.iterrows(): pref = row['都道府県'] short = pref.replace('県', '').replace('府', '').replace('都', '').replace('道', '') ax2.annotate(short, (row['旅館密度_2019'], row['変化率2020']), textcoords='offset points', xytext=(4, 3), fontsize=7.5, color=color, alpha=0.9) |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。df.groupby('列').apply(関数) — グループごとに関数を適用。時系列や地域別の集計でよく使います。fig, ax = plt.subplots(...) — 図全体(fig)と軸(ax)を作る定番。以降は ax.bar(...) 等で操作。stats.pearsonr(x, y) — Pearson相関係数 r と p値を同時に返します。for _, row in df.iterrows() — DataFrameを1行ずつ取り出すループ。1点ずつ描画したいときに使用。r, p = stats.pearsonr(...) — Pythonは複数戻り値を同時に受け取れる(タプルアンパック)。121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 | # 回帰直線 x_fit = np.linspace(scatter_df['旅館密度_2019'].min(), scatter_df['旅館密度_2019'].max(), 100) slope, intercept = np.polyfit(scatter_df['旅館密度_2019'], scatter_df['変化率2020'], 1) ax2.plot(x_fit, slope * x_fit + intercept, '--', color='#555', linewidth=1.5, label=f'回帰直線 (r={r_val:.2f}, p={p_val:.3f})') ax2.axhline(0, color='gray', linewidth=0.8, linestyle='-', alpha=0.5) ax2.set_xlabel('旅館密度(施設数 / 万人)', fontsize=12) ax2.set_ylabel('消費支出変化率(2019→2020年, %)', fontsize=12) ax2.set_title('図2:旅館密度と2020年消費支出変化率の関係\n観光依存度が高い県ほど消費が落ち込んだか?', fontsize=13, fontweight='bold') ax2.legend(fontsize=9, loc='upper right', ncol=2) ax2.grid(alpha=0.3) fig2.tight_layout() fig2.savefig(os.path.join(FIG_DIR, '2021_U5_3_fig2.png'), dpi=150, bbox_inches='tight') plt.close(fig2) print("fig2 保存完了") |
=== 図2: 旅館密度 vs 消費支出変化率 === 変化率2020 記述統計: count 47.00 mean -2.79 std 5.99 min -15.68 25% -6.73 50% -2.78 75% 0.96 max 8.89 Name: 変化率2020, dtype: float64 相関係数: r=0.031, p=0.8374 fig2 保存完了
ax.axhline / ax.axvline — 水平/垂直の点線。平均線や基準線として定番。x if cond else y は三項演算子。リスト内包表記と組み合わせると、forとifを1行で書けます。DID回帰に加え、旅館密度(観光依存度プロキシ)と消費支出変化率の直接的な関係を散布図・箱ひげ図で視覚化する。
| グループ | 対象県(N=10) | 平均変化率 | 中央値 |
|---|---|---|---|
| 観光高依存(旅館密度上位10) | 山梨・沖縄・長野・福井・鳥取・大分・新潟・福島・静岡・山形 | −0.96% | +0.97% |
| 観光低依存(旅館密度下位10) | 埼玉・神奈川・愛知・大阪・千葉・東京・広島・福岡・兵庫・奈良 | −3.43% | −4.02% |
t検定の結果:t = 1.013, p = 0.324(統計的に有意でない)
DID分析では「処置群」の定義が推定結果を大きく左右する。本研究では旅館密度を使ったが、 より直接的な指標(観光業就業者割合、観光消費額等)を使うと結果が変わる可能性がある。 これを「測定誤差(measurement error)」の問題と呼ぶ。
139 140 141 142 143 144 145 | prefs_2019 = df[df['年度'] == 2019][['都道府県', '旅館密度']].copy() median_density = prefs_2019['旅館密度'].median() high_tourism = set(prefs_2019[prefs_2019['旅館密度'] >= median_density]['都道府県']) df['treat'] = df['都道府県'].isin(high_tourism).astype(int) df['post'] = df['コロナダミー'] df['treat_post'] = df['treat'] * df['post'] |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。.astype(int) — 列を整数に変換(年度などを数値比較するため)。r, p = stats.pearsonr(...) — Pythonは複数戻り値を同時に受け取れる(タプルアンパック)。146 147 148 149 150 151 152 153 154 | # DID回帰: 消費支出 ~ treat + post + treat*post + 固定効果(簡易) # 都道府県固定効果のためのダミー変数 X_vars = ['treat', 'post', 'treat_post'] X = sm.add_constant(df[X_vars]) y_did = df['消費支出'] model_did = sm.OLS(y_did, X).fit() print(f"\n=== 図3: DID回帰結果 ===") print(model_did.summary()) |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。sm.add_constant(X) — 切片項(定数1の列)を先頭に追加。statsmodelsで必須。sm.OLS(y, X).fit() — 最小二乗法でモデルを推定。model.params, model.pvalues, model.conf_int() で結果取得。x if cond else y は三項演算子。リスト内包表記と組み合わせると、forとifを1行で書けます。155 156 157 158 159 160 | # 係数と95%CI coef_names = ['定数項(Intercept)', '観光高依存(treat)', 'コロナ期(post)', 'DID交差項(treat×post)'] coefs = model_did.params.values ci_low = model_did.conf_int()[0].values ci_high = model_did.conf_int()[1].values pvals_did = model_did.pvalues.values |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。df[col](1列)と df[[col1, col2]](複数列)でカッコの数が違います。リストを渡していると覚えるとミスを減らせます。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 | # DID交差項のみハイライト(index=3) did_coef = coefs[3] did_ci_lo = ci_low[3] did_ci_hi = ci_high[3] did_pval = pvals_did[3] print(f"DID交差項係数: {did_coef:.1f}円, 95%CI=[{did_ci_lo:.1f}, {did_ci_hi:.1f}], p={did_pval:.4f}") fig3, ax3 = plt.subplots(figsize=(9, 5)) colors_coef = ['#888888', '#4e9af1', '#e05c5c', '#e65c00'] y_pos = np.arange(len(coef_names)) for i, (c, lo, hi, pv, nm) in enumerate(zip(coefs, ci_low, ci_high, pvals_did, coef_names)): color = colors_coef[i] lw = 3 if i == 3 else 1.5 ax3.plot([lo, hi], [y_pos[i], y_pos[i]], '-', color=color, linewidth=lw, zorder=2) mk = '*' if pv < 0.05 else 'o' ms = 10 if i == 3 else 8 ax3.plot(c, y_pos[i], mk, color=color, markersize=ms, zorder=3) sig_str = f"p={pv:.3f}" + (' *' if pv < 0.05 else ' n.s.') ax3.annotate(f'{c:+,.0f}円 ({sig_str})', (c, y_pos[i]), textcoords='offset points', xytext=(8, 0), fontsize=9.5, color=color, va='center') ax3.axvline(0, color='black', linewidth=1, linestyle='--', alpha=0.6) ax3.set_yticks(y_pos) ax3.set_yticklabels(coef_names, fontsize=11) ax3.set_xlabel('回帰係数(円/月)', fontsize=12) ax3.set_title('図3:差の差(DID)分析の回帰係数と95%信頼区間\n観光高依存×コロナ期の交差項で打撃の差を推定', fontsize=13, fontweight='bold') ax3.grid(axis='x', alpha=0.3) |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。fig, ax = plt.subplots(...) — 図全体(fig)と軸(ax)を作る定番。以降は ax.bar(...) 等で操作。ax.axhline / ax.axvline — 水平/垂直の点線。平均線や基準線として定番。s[:-n]「末尾n文字を除く」/s[n:]「先頭n文字を除く」。スライス [start:stop:step] はリスト・タプル・文字列共通の基本ワザです。189 190 191 192 193 194 195 | # DID交差項を囲む矩形 ax3.axhspan(2.6, 3.4, alpha=0.08, color='#e65c00') fig3.tight_layout() fig3.savefig(os.path.join(FIG_DIR, '2021_U5_3_fig3.png'), dpi=150, bbox_inches='tight') plt.close(fig3) print("fig3 保存完了") |
=== 図3: DID回帰結果 ===
OLS Regression Results
==============================================================================
Dep. Variable: 消費支出 R-squared: 0.048
Model: OLS Adj. R-squared: 0.037
Method: Least Squares F-statistic: 4.628
Date: Mon, 18 May 2026 Prob (F-statistic): 0.00355
Time: 11:23:59 Log-Likelihood: -3236.4
No. Observations: 282 AIC: 6481.
Df Residuals: 278 BIC: 6495.
Df Model: 3
Covariance Type: nonrobust
==============================================================================
coef std err t P>|t| [0.025 0.975]
------------------------------------------------------------------------------
const 2.93e+05 2450.431 119.561 0.000 2.88e+05 2.98e+05
treat -4761.6993 3429.144 -1.389 0.166 -1.15e+04 1988.687
post -1.164e+04 4244.270 -2.744 0.006 -2e+04 -3289.216
treat_post 2832.1440 5939.452 0.477 0.634 -8859.868 1.45e+04
==============================================================================
Omnibus: 7.346 Durbin-Watson: 0.799
Prob(Omnibus): 0.025 Jarque-Bera (JB): 7.156
Skew: -0.354 Prob(JB): 0.0279
Kurtosis: 3.327 Cond. No. 6.38
==============================================================================
Notes:
[1] Standard Errors assume that the covariance matrix of the errors is correctly specified.
DID交差項係数: 2832.1円, 95%CI=[-8859.9, 14524.2], p=0.6339
fig3 保存完了np.cumsum(arr) は累積和、np.linspace(a, b, n) は「aからbを等間隔でn個」。NumPyの定石です。本研究の結果は、SSDSE-Bの消費支出データを用いたDID分析では、観光高依存県の統計的に有意な「追加ダメージ」を検出できなかった。しかし、これは政策的示唆がないことを意味しない。
| 限界点 | 改善のための方向性 |
|---|---|
| 消費支出が観光ダメージの間接指標 | 宿泊者数・観光業売上高等の直接指標を追加 |
| 都道府県固定効果の不完全な制御 | 固定効果モデル(within推定)の適用 |
| 旅館密度が観光依存度の不完全なプロキシ | 観光業就業割合等の複合指標を構築 |
| N=47で統計的検出力が低い | 市区町村レベルへの分析単位の細分化 |
| 並行トレンド仮定の未検証 | コロナ前の期間でのプラセボ検定(偽のpost) |
統計分析では、仮説通りの結果が出ないことも多い。このような「ナル結果(null result)」を隠したり、有意になるまで分析を繰り返す(p-hacking)ことは科学的に不誠実。ナル結果も重要な知見であり、「この指標・このデータ・この方法では検出できなかった」という情報を正直に報告することが学術的誠実さの基本。
197 198 199 200 201 202 203 204 | top10_prefs = prefs_2019.nlargest(10, '旅館密度')['都道府県'].tolist() bottom10_prefs = prefs_2019.nsmallest(10, '旅館密度')['都道府県'].tolist() print(f"\n=== 図4: 観光高依存TOP10 ===") for p in top10_prefs: d = prefs_2019[prefs_2019['都道府県'] == p]['旅館密度'].values[0] print(f" {p}: {d:.2f}") print(f"観光低依存BOTTOM10: {bottom10_prefs}") |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。x if cond else y は三項演算子。リスト内包表記と組み合わせると、forとifを1行で書けます。205 206 207 208 209 210 211 212 213 214 215 216 | # コロナ前後の消費変化(2019→2020,2021の変化率) df_pre = df[df['年度'] == 2019][['都道府県', '消費支出']].rename(columns={'消費支出': '消費支出_pre'}) df_post2 = df[df['年度'].isin([2020, 2021])].groupby('都道府県')['消費支出'].mean().reset_index() df_post2.columns = ['都道府県', '消費支出_post'] chg_df = pd.merge(df_pre, df_post2, on='都道府県') chg_df['消費変化率'] = (chg_df['消費支出_post'] - chg_df['消費支出_pre']) / chg_df['消費支出_pre'] * 100 top10_chg = chg_df[chg_df['都道府県'].isin(top10_prefs)]['消費変化率'] bottom10_chg = chg_df[chg_df['都道府県'].isin(bottom10_prefs)]['消費変化率'] print(f"\n高依存TOP10 消費変化率: mean={top10_chg.mean():.2f}%, median={top10_chg.median():.2f}%") print(f"低依存BOTTOM10 消費変化率: mean={bottom10_chg.mean():.2f}%, median={bottom10_chg.median():.2f}%") |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。df.groupby('列').apply(関数) — グループごとに関数を適用。時系列や地域別の集計でよく使います。df[col](1列)と df[[col1, col2]](複数列)でカッコの数が違います。リストを渡していると覚えるとミスを減らせます。217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 | # t検定 t_stat, t_pval = stats.ttest_ind(top10_chg, bottom10_chg) print(f"t検定: t={t_stat:.3f}, p={t_pval:.4f}") fig4, ax4 = plt.subplots(figsize=(8, 6)) bp = ax4.boxplot( [bottom10_chg.values, top10_chg.values], labels=['観光低依存\n(旅館密度 下位10県)', '観光高依存\n(旅館密度 上位10県)'], patch_artist=True, medianprops=dict(color='black', linewidth=2) ) bp['boxes'][0].set_facecolor('#AED6F1') bp['boxes'][1].set_facecolor('#F1948A') bp['boxes'][0].set_alpha(0.8) bp['boxes'][1].set_alpha(0.8) |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。fig, ax = plt.subplots(...) — 図全体(fig)と軸(ax)を作る定番。以降は ax.bar(...) 等で操作。s[:-n]「末尾n文字を除く」/s[n:]「先頭n文字を除く」。スライス [start:stop:step] はリスト・タプル・文字列共通の基本ワザです。232 233 234 235 236 237 238 239 | # 個別データ点 ax4.scatter([1]*len(bottom10_chg), bottom10_chg.values, color='#1565C0', s=50, zorder=5, alpha=0.7) ax4.scatter([2]*len(top10_chg), top10_chg.values, color='#C0392B', s=50, zorder=5, alpha=0.7) ax4.axhline(0, color='gray', linewidth=1, linestyle='--', alpha=0.6) ax4.set_ylabel('消費支出変化率(2019→2020-21年平均, %)', fontsize=11) ax4.set_title(f'図4:観光依存度別のコロナ禍における消費支出変化\nt検定: t={t_stat:.2f}, p={t_pval:.3f}', fontsize=13, fontweight='bold') ax4.grid(axis='y', alpha=0.3) |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。ax.axhline / ax.axvline — 水平/垂直の点線。平均線や基準線として定番。np.cumsum(arr) は累積和、np.linspace(a, b, n) は「aからbを等間隔でn個」。NumPyの定石です。240 241 242 243 244 245 246 247 248 | # 平均値マーク ax4.plot(1, bottom10_chg.mean(), 'D', color='#1565C0', markersize=10, zorder=6, label='平均値') ax4.plot(2, top10_chg.mean(), 'D', color='#C0392B', markersize=10, zorder=6) ax4.legend(fontsize=10) fig4.tight_layout() fig4.savefig(os.path.join(FIG_DIR, '2021_U5_3_fig4.png'), dpi=150, bbox_inches='tight') plt.close(fig4) print("fig4 保存完了") |
=== 図4: 観光高依存TOP10 === 山梨県: 16.33 沖縄県: 14.75 長野県: 12.71 福井県: 12.31 鳥取県: 10.84 大分県: 9.52 新潟県: 9.27 福島県: 8.21 静岡県: 7.78 山形県: 7.47 観光低依存BOTTOM10: ['埼玉県', '神奈川県', '愛知県', '大阪府', '千葉県', '東京都', '広島県', '福岡県', '兵庫県', '奈良県'] 高依存TOP10 消費変化率: mean=-0.96%, median=0.97% 低依存BOTTOM10 消費変化率: mean=-3.43%, median=-4.02% t検定: t=1.013, p=0.3244 fig4 保存完了
{値:.2f}(小数2桁)、{値:,}(3桁区切り)、{値:>10}(右寄せ10桁)など、覚えると出力が一気に整います。249 250 251 252 253 254 255 256 257 258 259 260 261 262 | print("\n" + "="*60) print("【HTML作成用 統計値サマリー】") print("="*60) print(f"[時系列] コロナ前(2018-19)全国平均消費支出: {pre_avg:.2f}万円/月") print(f"[時系列] コロナ期(2020-21)全国平均消費支出: {post_avg:.2f}万円/月") print(f"[時系列] コロナ前後変化率: {chg_pct:+.1f}%") print(f"[散布図] 旅館密度×消費変化率 相関係数: r={r_val:.3f}, p={p_val:.4f}") print(f"[DID] DID交差項係数: {did_coef:+,.0f}円/月") print(f"[DID] 95%CI: [{did_ci_lo:,.0f}, {did_ci_hi:,.0f}]") print(f"[DID] p値: {did_pval:.4f}") print(f"[箱ひげ] 高依存TOP10 平均変化率: {top10_chg.mean():+.2f}%") print(f"[箱ひげ] 低依存BTM10 平均変化率: {bottom10_chg.mean():+.2f}%") print(f"[箱ひげ] t検定: t={t_stat:.3f}, p={t_pval:.4f}") print(f"[観光TOP10] {', '.join(top10_prefs)}") |
============================================================ 【HTML作成用 統計値サマリー】 ============================================================ [時系列] コロナ前(2018-19)全国平均消費支出: 28.83万円/月 [時系列] コロナ期(2020-21)全国平均消費支出: 28.03万円/月 [時系列] コロナ前後変化率: -2.8% [散布図] 旅館密度×消費変化率 相関係数: r=0.031, p=0.8374 [DID] DID交差項係数: +2,832円/月 [DID] 95%CI: [-8,860, 14,524] [DID] p値: 0.6339 [箱ひげ] 高依存TOP10 平均変化率: -0.96% [箱ひげ] 低依存BTM10 平均変化率: -3.43% [箱ひげ] t検定: t=1.013, p=0.3244 [観光TOP10] 山梨県, 沖縄県, 長野県, 福井県, 鳥取県, 大分県, 新潟県, 福島県, 静岡県, 山形県
df[col](1列)と df[[col1, col2]](複数列)でカッコの数が違います。リストを渡していると覚えるとミスを減らせます。SSDSE-B(47都道府県×2018〜2023年、N=282)を用いた時系列・差の差分析の結果:
| データ | 出典 | URL |
|---|---|---|
| SSDSE-B-2026(都道府県統計) | 統計数理研究所 SSDSE | https://www.ism.ac.jp/opendata/ssdse/ |
| 旅館営業施設数、消費支出等 | 上記SSDSE-B内(47都道府県・2012〜2023年) | — |
本教育用コードはSSDSE-B実データのみを使用しています(合成データ・np.random.seed等は一切使用しません)。
| ライブラリ | 用途 |
|---|---|
| pandas | データ読み込み・前処理・集計 |
| numpy | 数値計算 |
| statsmodels | OLS回帰(DID分析) |
| scipy.stats | t検定・相関係数検定 |
| matplotlib | 図の作成(折れ線・散布図・箱ひげ図・係数プロット) |
統計分析の解釈で初心者がやりがちな勘違いをまとめます。特に「相関と因果の混同」「p値の過信」は研究現場でもよく起きる落とし穴です。本文を読む前にも、読んだ後にも、目を通してみてください。
統計の基本用語を初心者向けに解説します。本文中で見慣れない言葉が出てきたら、ここに戻って確認してください。
統計手法について「何のためか」「結果をどう読むか」を初心者向けに解説します。
この研究をさらに発展させるための3つの方向性を示します。「今回わかったこと(X)」から「次に検証すべき仮説(Y)」を立て、「具体的に何をするか(Z)」まで考えてみましょう。
学んだだけでは身につきません。実際に手を動かすのが最強の学習方法です。本論文のスクリプトをベースに、以下のチャレンジに挑戦してみてください。難易度別に5つ用意しました。
本論文で学んだ手法は、研究の世界だけでなく、行政・企業・NPO の現場でも様々に活用されています。具体的なシーンを紹介します。
この論文を読んで初心者が抱きやすい疑問に、教育的観点から答えます。