このページの分析を自分で再現するには、以下の手順でデータを準備してください。コードの編集は不要です。
data/raw/ フォルダに入れます。html/figures/ に自動保存されます。
COVID-19パンデミックは日本全国の人流を激変させたが、その影響は地域によって大きく異なる。 観光地・都市部・農村部で落ち込み幅が異なるのはなぜか。 本研究は 延べ宿泊者数 を人流の代理変数として、コロナ前後の変化率を都道府県・地域別に定量化し、 地域の特性(気候・高齢化率・観光施設数等)が変化率に与える影響を重回帰分析で解明する。
まず「パンデミックは人流をどう変えたか―地域の特性別に―」を統計的にとらえることが有効だと考えられる。 その理由は感覚や経験則だけでは、複雑な社会要因の中で「何が本当に効いているか」を見極めにくいからである。 本研究では公開データと統計手法を組み合わせ、この問いに定量的な答えを出すことを目指す。
SSDSE-B(都道府県) 時系列分析 地域別比較 重回帰分析(標準化係数) 代理変数(プロキシ)
| 項目 | 内容 |
|---|---|
| データセット | SSDSE-B-2026(教育用標準データセット 都道府県版) |
| 出典 | 情報・システム研究機構(統計数理研究所) |
| 対象 | 47都道府県 × 2012〜2023年(12年間) |
| 人流の代理変数 | 延べ宿泊者数(変数コード: G7101) |
分析では3期間を定義し、コロナ前との変化率で影響を定量化する。
| 期間 | 対象年度 | 意味 |
|---|---|---|
| コロナ前 | 2018〜2019年(平均) | パンデミック直前のベースライン |
| コロナ禍 | 2020〜2021年(平均) | 緊急事態宣言・移動制限の影響期 |
| 回復期 | 2022〜2023年(平均) | 規制緩和後の回復・正常化期 |
| 変数 | 想定される効果 | 選択理由 |
|---|---|---|
| 年平均気温 | 正(観光地・南方ほど落ち込み大) | 気候依存型観光の代理 |
| 合計特殊出生率 | ? | 地域活力・人口動態の指標 |
| 住宅地標準価格 | 正(都市ほど回復力高い可能性) | 経済活力の代理 |
| 消費支出(世帯) | 正(消費余力が多いほど回復) | 地域の購買力 |
| 高齢化率 | 負(高齢化が進む地域ほど人流回復遅い) | 人口構造の影響 |
| 有効求人倍率 | ?(雇用環境と人流の関係) | 経済活動水準 |
| 旅館・ホテル施設数 | 負(観光依存ほど落ち込み大) | 観光依存度の代理 |
直接測定できない概念を、それと強く相関する別の変数で代替することを「代理変数(proxy variable)」の活用という。本研究では「人流」を直接測定する手段がないため、延べ宿泊者数をその代理変数として使用する。
代理変数を使う際の注意点:代理変数が真の概念を完全に捉えているわけではない(測定誤差の存在)。延べ宿泊者数は「旅行者の泊まりがけ移動」を捉えるが、日帰り・通勤等の人流は含まれない。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | print("=" * 60) print("【Step 2】期間別 延べ宿泊者数の変化率を計算") print(" コロナ前平均 (2018-2019) → コロナ禍 (2020-2021) → 回復期 (2022-2023)") print("=" * 60) def period_mean(df, years, col): return df[df['年度'].isin(years)].groupby('都道府県')[col].mean() pre_mean = period_mean(df_b, [2018, 2019], '延べ宿泊者数') covid_mean = period_mean(df_b, [2020, 2021], '延べ宿泊者数') rec_mean = period_mean(df_b, [2022, 2023], '延べ宿泊者数') # COVID期の変化率(対前期比%) covid_chg = ((covid_mean - pre_mean) / pre_mean * 100).rename('covid_chg') rec_chg = ((rec_mean - pre_mean) / pre_mean * 100).rename('rec_chg') print(f" 全国平均 コロナ禍変化率: {covid_chg.mean():.1f}%") print(f" 全国平均 回復期変化率: {rec_chg.mean():.1f}%") print() |
============================================================ 【Step 2】期間別 延べ宿泊者数の変化率を計算 コロナ前平均 (2018-2019) → コロナ禍 (2020-2021) → 回復期 (2022-2023) ============================================================ 全国平均 コロナ禍変化率: -38.7% 全国平均 回復期変化率: -9.8%
df.groupby('列').apply(関数) — グループごとに関数を適用。時系列や地域別の集計でよく使います。df['A'] / df['B'] — pandasの列同士の四則演算は要素ごと(element-wise)。forループ不要なのが強み。20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | REGION_MAP = { '北海道': '北海道・東北', '青森県': '北海道・東北', '岩手県': '北海道・東北', '宮城県': '北海道・東北', '秋田県': '北海道・東北', '山形県': '北海道・東北', '福島県': '北海道・東北', '茨城県': '関東', '栃木県': '関東', '群馬県': '関東', '埼玉県': '関東', '千葉県': '関東', '東京都': '関東', '神奈川県': '関東', '新潟県': '中部', '富山県': '中部', '石川県': '中部', '福井県': '中部', '山梨県': '中部', '長野県': '中部', '岐阜県': '中部', '静岡県': '中部', '愛知県': '中部', '三重県': '近畿', '滋賀県': '近畿', '京都府': '近畿', '大阪府': '近畿', '兵庫県': '近畿', '奈良県': '近畿', '和歌山県': '近畿', '鳥取県': '中国・四国', '島根県': '中国・四国', '岡山県': '中国・四国', '広島県': '中国・四国', '山口県': '中国・四国', '徳島県': '中国・四国', '香川県': '中国・四国', '愛媛県': '中国・四国', '高知県': '中国・四国', '福岡県': '九州・沖縄', '佐賀県': '九州・沖縄', '長崎県': '九州・沖縄', '熊本県': '九州・沖縄', '大分県': '九州・沖縄', '宮崎県': '九州・沖縄', '鹿児島県': '九州・沖縄', '沖縄県': '九州・沖縄', } REGION_ORDER = ['北海道・東北', '関東', '中部', '近畿', '中国・四国', '九州・沖縄'] REGION_COLORS = { '北海道・東北': '#1565C0', '関東': '#E53935', '中部': '#F57F17', '近畿': '#2E7D32', '中国・四国': '#6A1B9A', '九州・沖縄': '#00838F', } df_b['地域区分'] = df_b['都道府県'].map(REGION_MAP) |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。.map() は「1対1の置き換え」、.apply() は「関数を当てる」。辞書なら .map()、ロジックなら .apply()。49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 | print() print("=" * 60) print("【Step 4】記述統計サマリー") print("=" * 60) desc_cols = ['covid_chg', 'rec_chg'] desc_df = pd.DataFrame({'covid_chg': covid_chg, 'rec_chg': rec_chg}) print(desc_df.describe().round(2)) print() # 地域別サマリー region_summary = chg_df.groupby('地域区分')[['covid_chg', 'rec_chg']].mean().round(1) region_summary.columns = ['コロナ禍変化率(%)', '回復期変化率(%)'] print(" ─ 地域別平均変化率 ─") for r in REGION_ORDER: if r in region_summary.index: row = region_summary.loc[r] print(f" {r:<12} コロナ禍: {row.iloc[0]:>7.1f}% 回復期: {row.iloc[1]:>7.1f}%") print() |
============================================================
【Step 4】記述統計サマリー
============================================================
covid_chg rec_chg
count 47.00 47.00
mean -38.67 -9.76
std 8.25 7.99
min -60.23 -25.06
25% -42.84 -14.28
50% -36.73 -8.77
75% -32.85 -5.63
max -24.57 15.12
─ 地域別平均変化率 ─
北海道・東北 コロナ禍: -34.6% 回復期: -11.6%
関東 コロナ禍: -38.1% 回復期: 0.2%
中部 コロナ禍: -39.5% 回復期: -13.0%
近畿 コロナ禍: -43.4% 回復期: -8.2%
中国・四国 コロナ禍: -33.5% 回復期: -8.5%
九州・沖縄 コロナ禍: -43.5% 回復期: -16.1%.describe() — 件数・平均・標準偏差・四分位・最大/最小を一括計算。データの素性チェックに必須。df.groupby('列').apply(関数) — グループごとに関数を適用。時系列や地域別の集計でよく使います。s[:-n]「末尾n文字を除く」/s[n:]「先頭n文字を除く」。スライス [start:stop:step] はリスト・タプル・文字列共通の基本ワザです。まず延べ宿泊者数の全国平均値を2012年から2023年にかけて追い、COVID-19の影響がいつ・どの程度現れたかを視覚的に確認する。 背景色は3期間(コロナ前・コロナ禍・回復期)を示し、細線は各都道府県の推移、太線が全国平均を表す。
時系列データを分析する際は、まず折れ線グラフで「構造変化点(structural break)」を探す。COVID-19のような外生ショックは明確な変化点として現れる。
背景色による期間区分は、読み手が変化の「文脈」を理解しやすくする有効なビジュアライゼーション技法である。分析目的に応じて前後比較の「ベースライン期間」を明示することが重要。
69 70 71 72 73 74 75 76 | print("=" * 60) print("【Step 3】図の生成") print("=" * 60) print(" 図1:全国平均 延べ宿泊者数の時系列(2012〜2023年)") national_ts = df_b.groupby('年度')['延べ宿泊者数'].mean() / 1e6 # 百万人単位 fig, ax = plt.subplots(figsize=(11, 5.5)) |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。df.groupby('列').apply(関数) — グループごとに関数を適用。時系列や地域別の集計でよく使います。fig, ax = plt.subplots(...) — 図全体(fig)と軸(ax)を作る定番。以降は ax.bar(...) 等で操作。[式 for x in リスト] はリスト内包表記。forループでappendする代わりに1行でリストを作れます。77 78 79 80 81 82 83 84 85 86 87 88 | # 期間背景色 ax.axvspan(2017.5, 2019.5, color='#E8F5E9', alpha=0.7, label='コロナ前 (2018-2019)') ax.axvspan(2019.5, 2021.5, color='#FFEBEE', alpha=0.7, label='コロナ禍 (2020-2021)') ax.axvspan(2021.5, 2023.5, color='#E3F2FD', alpha=0.7, label='回復期 (2022-2023)') # 都道府県別の薄い折れ線 for pref in df_b['都道府県'].unique(): pref_ts = df_b[df_b['都道府県'] == pref].set_index('年度')['延べ宿泊者数'] / 1e6 pref_ts = pref_ts.sort_index() region = REGION_MAP.get(pref, '関東') ax.plot(pref_ts.index, pref_ts.values, color=REGION_COLORS.get(region, '#999'), alpha=0.15, linewidth=0.8) |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。r, p = stats.pearsonr(...) — Pythonは複数戻り値を同時に受け取れる(タプルアンパック)。89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 | # 全国平均(太線) ax.plot(national_ts.index, national_ts.values, color='black', linewidth=2.5, marker='o', markersize=5, label='全国平均', zorder=5) # 注釈 ax.annotate('COVID-19\n感染拡大', xy=(2020, national_ts[2020]), xytext=(2020.3, national_ts[2020] * 1.25), fontsize=9, color='#C62828', arrowprops=dict(arrowstyle='->', color='#C62828', lw=1.2)) ax.set_xlabel('年度', fontsize=12) ax.set_ylabel('延べ宿泊者数(百万人)', fontsize=12) ax.set_title('図1:全国平均 延べ宿泊者数の時系列推移(2012〜2023年)\n' '出典: SSDSE-B-2026(実データ) 各細線=都道府県別、太線=全国平均', fontsize=11) ax.legend(loc='upper left', fontsize=9) ax.set_xlim(2011.5, 2023.8) ax.grid(True, alpha=0.3) plt.tight_layout() save_fig('fig1_timeseries') |
============================================================ 【Step 3】図の生成 ============================================================ 図1:全国平均 延べ宿泊者数の時系列(2012〜2023年) → 保存: html/figures/2022_U1_fig1_timeseries.png
x if cond else y は三項演算子。リスト内包表記と組み合わせると、forとifを1行で書けます。47都道府県を6つの地域ブロックに分類し、コロナ禍(2020〜2021年)・回復期(2022〜2023年)それぞれの変化率を比較する。 地域ブロック:北海道・東北 / 関東 / 中部 / 近畿 / 中国・四国 / 九州・沖縄
| 地域区分 | 含まれる都道府県(例) | 特徴 |
|---|---|---|
| 北海道・東北 | 北海道、青森、岩手、宮城… | 雪観光、農業、長距離移動 |
| 関東 | 東京、神奈川、埼玉、千葉… | 都市・ビジネス移動中心 |
| 中部 | 愛知、長野、新潟、岐阜… | 製造業・山岳・温泉観光 |
| 近畿 | 大阪、京都、兵庫… | インバウンド・歴史観光 |
| 中国・四国 | 広島、岡山、愛媛… | 地方中枢都市・瀬戸内 |
| 九州・沖縄 | 福岡、鹿児島、沖縄… | リゾート・国際観光 |
地域特性を分析に組み込む方法は主に2つある。
①集計アプローチ(本図):都道府県を地域ブロックにグループ化し、ブロック平均で比較する。直感的でわかりやすいが、ブロック内の分散が隠れる。
②ダミー変数アプローチ:回帰分析に地域ダミー変数を加え、地域固定効果をコントロールする。個体差を制御した上で他の要因の効果を推定できる。
110 111 112 113 114 115 116 117 118 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 145 146 147 148 149 150 151 152 153 154 | print(" 図2:地域別(6区分)宿泊者数の変化率比較") # COVID変化率のDataFrame chg_df = pd.DataFrame({'covid_chg': covid_chg, 'rec_chg': rec_chg}).reset_index() chg_df['地域区分'] = chg_df['都道府県'].map(REGION_MAP) chg_df = chg_df.dropna(subset=['地域区分', 'covid_chg', 'rec_chg']) region_covid = chg_df.groupby('地域区分')['covid_chg'].mean() region_rec = chg_df.groupby('地域区分')['rec_chg'].mean() fig, axes = plt.subplots(1, 2, figsize=(13, 5.5), sharey=True) for ax, (col_data, col_name, period_label, bg_color) in zip( axes, [(region_covid, 'covid_chg', 'コロナ禍 (2020-2021) vs 前期', '#FFEBEE'), (region_rec, 'rec_chg', '回復期 (2022-2023) vs 前期', '#E3F2FD')]): y_pos = np.arange(len(REGION_ORDER)) vals = [col_data.get(r, np.nan) for r in REGION_ORDER] colors = [REGION_COLORS[r] for r in REGION_ORDER] bars = ax.barh(y_pos, vals, color=colors, alpha=0.75, height=0.6) # 値ラベル for i, v in enumerate(vals): if not np.isnan(v): ax.text(v + (1 if v >= 0 else -1), i, f'{v:.1f}%', va='center', fontsize=9, ha='left' if v >= 0 else 'right') ax.axvline(0, color='black', linewidth=0.9, linestyle='--') ax.set_yticks(y_pos) ax.set_yticklabels(REGION_ORDER, fontsize=10) ax.set_xlabel('変化率(%)', fontsize=10) ax.set_title(period_label, fontsize=10) ax.set_facecolor(bg_color) ax.grid(True, axis='x', alpha=0.3) ax.invert_yaxis() axes[0].set_title('コロナ禍 (2020-2021)\nvs コロナ前 (2018-2019)', fontsize=10) axes[1].set_title('回復期 (2022-2023)\nvs コロナ前 (2018-2019)', fontsize=10) plt.suptitle('図2:地域別(6区分)延べ宿泊者数の変化率\n' '出典: SSDSE-B-2026(実データ) 各区分は都道府県平均', fontsize=11, y=1.01) plt.tight_layout() save_fig('fig2_region') |
図2:地域別(6区分)宿泊者数の変化率比較 → 保存: html/figures/2022_U1_fig2_region.png
df.groupby('列').apply(関数) — グループごとに関数を適用。時系列や地域別の集計でよく使います。fig, ax = plt.subplots(...) — 図全体(fig)と軸(ax)を作る定番。以降は ax.bar(...) 等で操作。ax.axhline / ax.axvline — 水平/垂直の点線。平均線や基準線として定番。r, p = stats.pearsonr(...) — Pythonは複数戻り値を同時に受け取れる(タプルアンパック)。コロナ禍における延べ宿泊者数の変化率(%)を目的変数とし、コロナ前(2018〜2019年平均)の都道府県特性を説明変数とする重回帰分析を実施する。 説明変数は標準化(平均0・標準偏差1)してから回帰するため、係数の大きさで効果の強さを直接比較できる。
156 157 158 159 160 161 162 163 164 165 166 167 168 169 | print(" 図3:重回帰 標準化係数プロット(コロナ禍変化率の規定要因)") # 説明変数:コロナ前(2018-2019)平均 def pre_mean_var(df, years, col): return df[df['年度'].isin(years)].groupby('都道府県')[col].mean() x_vars_raw = { '年平均気温': pre_mean_var(df_b, [2018, 2019], '年平均気温'), '合計特殊出生率': pre_mean_var(df_b, [2018, 2019], '合計特殊出生率'), '住宅地価': pre_mean_var(df_b, [2018, 2019], '標準価格(平均価格)(住宅地)'), '消費支出': pre_mean_var(df_b, [2018, 2019], '消費支出(二人以上の世帯)'), '高齢化率': pre_mean_var(df_b, [2018, 2019], '高齢化率'), '有効求人倍率': pre_mean_var(df_b, [2018, 2019], '有効求人倍率'), } |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。df.groupby('列').apply(関数) — グループごとに関数を適用。時系列や地域別の集計でよく使います。x if cond else y は三項演算子。リスト内包表記と組み合わせると、forとifを1行で書けます。170 171 172 173 174 175 176 177 178 179 180 181 | # 旅館施設数(観光地ダミーの代理変数) x_vars_raw['旅館施設数'] = pre_mean_var(df_b, [2018, 2019], '旅館営業施設数(ホテルを含む)') \ if '旅館営業施設数(ホテルを含む)' in df_b.columns else \ pre_mean_var(df_b, [2018, 2019], '旅館営業施設客室数(ホテルを含む)') x_df = pd.DataFrame(x_vars_raw) reg_df = x_df.join(covid_chg).dropna() print(f" 回帰サンプル数: N={len(reg_df)}") X_raw = reg_df[list(x_vars_raw.keys())] y_reg = reg_df['covid_chg'] |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。df[col](1列)と df[[col1, col2]](複数列)でカッコの数が違います。リストを渡していると覚えるとミスを減らせます。182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 | # 標準化 scaler = StandardScaler() X_std = scaler.fit_transform(X_raw) X_std_df = pd.DataFrame(X_std, columns=X_raw.columns, index=X_raw.index) # OLS(statsmodels) X_sm = sm.add_constant(X_std_df) ols = sm.OLS(y_reg, X_sm).fit() print() print(f" R² = {ols.rsquared:.3f} adj-R² = {ols.rsquared_adj:.3f}") print(f" {'変数名':<15} {'係数':>8} {'p値':>8} {'有意'}") print(" " + "─" * 42) for var in X_raw.columns: b = ols.params[var]; p = ols.pvalues[var] sig = '***' if p < 0.001 else '**' if p < 0.01 else '*' if p < 0.05 else '' print(f" {var:<15} {b:>8.4f} {p:>8.4f} {sig}") print() |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。StandardScaler().fit_transform(X) — 各列を「平均0・分散1」に標準化。単位が違う変数のβを比較可能に。sm.add_constant(X) — 切片項(定数1の列)を先頭に追加。statsmodelsで必須。sm.OLS(y, X).fit() — 最小二乗法でモデルを推定。model.params, model.pvalues, model.conf_int() で結果取得。s[:-n]「末尾n文字を除く」/s[n:]「先頭n文字を除く」。スライス [start:stop:step] はリスト・タプル・文字列共通の基本ワザです。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 | # 係数プロット var_labels = { '年平均気温': '年平均気温(℃)', '合計特殊出生率': '合計特殊出生率', '住宅地価': '住宅地標準価格', '消費支出': '消費支出(世帯)', '高齢化率': '高齢化率(%)', '有効求人倍率': '有効求人倍率', '旅館施設数': '旅館・ホテル施設数', } coefs = [ols.params[v] for v in X_raw.columns] pvals = [ols.pvalues[v] for v in X_raw.columns] ci_lo = [ols.conf_int().loc[v, 0] for v in X_raw.columns] ci_hi = [ols.conf_int().loc[v, 1] for v in X_raw.columns] labs = [var_labels.get(v, v) for v in X_raw.columns] fig, ax = plt.subplots(figsize=(10, 6)) y_pos = np.arange(len(coefs)) bar_colors = ['#C62828' if b < 0 else '#1565C0' for b, p in zip(coefs, pvals)] bar_alpha = [0.85 if p < 0.05 else 0.35 for p in pvals] for i, (b, cl, ch, bc, ba) in enumerate(zip(coefs, ci_lo, ci_hi, bar_colors, bar_alpha)): ax.barh(i, b, color=bc, alpha=ba, height=0.6) ax.plot([cl, ch], [i, i], color='black', linewidth=1.5) ax.plot([cl, cl], [i - 0.15, i + 0.15], color='black', linewidth=1.5) ax.plot([ch, ch], [i - 0.15, i + 0.15], color='black', linewidth=1.5) ax.axvline(0, color='black', linewidth=1.0, linestyle='--') ax.set_yticks(y_pos) ax.set_yticklabels(labs, fontsize=10) ax.set_xlabel('標準化偏回帰係数(95% CI)\n※ 濃色: p<0.05 有意、薄色: 非有意', fontsize=10) ax.set_title(f'図3:コロナ禍 延べ宿泊者数変化率の規定要因(重回帰 標準化係数)\n' f'N=47都道府県, R²={ols.rsquared:.3f} 出典: SSDSE-B-2026(実データ)', fontsize=11) ax.grid(True, axis='x', alpha=0.3) |
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の定石です。235 236 237 238 239 240 241 242 243 244 245 246 247 248 | # 有意性マーク for i, (b, ch, p) in enumerate(zip(coefs, ci_hi, pvals)): sig = '***' if p < 0.001 else '**' if p < 0.01 else '*' if p < 0.05 else '' if sig: ax.text(ch + 0.02, i, sig, va='center', fontsize=10, color='#C62828', fontweight='bold') sig_patch = mpatches.Patch(color='#1565C0', alpha=0.85, label='正の効果(p<0.05)') nosig_patch = mpatches.Patch(color='#1565C0', alpha=0.35, label='p≥0.05(非有意)') neg_patch = mpatches.Patch(color='#C62828', alpha=0.85, label='負の効果(p<0.05)') ax.legend(handles=[sig_patch, neg_patch, nosig_patch], fontsize=9, loc='lower right') plt.tight_layout() save_fig('fig3_coef') |
図3:重回帰 標準化係数プロット(コロナ禍変化率の規定要因) 回帰サンプル数: N=47 R² = 0.417 adj-R² = 0.312 変数名 係数 p値 有意 ────────────────────────────────────────── 年平均気温 -3.1930 0.0537 合計特殊出生率 1.6754 0.3340 住宅地価 -0.0574 0.9738 消費支出 0.3167 0.7838 高齢化率 2.2484 0.1319 有効求人倍率 0.1401 0.9007 旅館施設数 -1.8915 0.1158 → 保存: html/figures/2022_U1_fig3_coef.png
{値:.2f}(小数2桁)、{値:,}(3桁区切り)、{値:>10}(右寄せ10桁)など、覚えると出力が一気に整います。重回帰で統計的に有意だった主要4変数について、コロナ禍変化率との散布図を確認する。 各点は1都道府県を表し、色は地域区分に対応する。回帰直線と相関係数rも合わせて示す。
本研究の変化率 = (コロナ禍平均 − コロナ前平均) / コロナ前平均 × 100 は、シンプルな差分分析(差の差法の片側)の考え方に基づく。
利点:個体(都道府県)ごとの固定的な差異(例:もともと観光地かどうか)が差分を取ることでキャンセルされ、COVID-19の「純粋な影響」に近い推計ができる。
注意点:コロナ前の期間選択(2018〜2019年)次第で結果が変わる可能性がある。また、コロナ以外の要因(台風・インバウンド政策変化など)との混同に注意が必要。
250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 | import numpy as np import pandas as pd import matplotlib matplotlib.use('Agg') import matplotlib.pyplot as plt import matplotlib.patches as mpatches from matplotlib.gridspec import GridSpec import statsmodels.api as sm from scipy import stats from sklearn.linear_model import LinearRegression from sklearn.preprocessing import StandardScaler import warnings import os warnings.filterwarnings('ignore') |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。import pandas as pd など — 必要なライブラリをまとめて呼び出します。as pd は短い別名(alias)。matplotlib.use('Agg') — グラフを画面表示せずファイルに保存するためのおまじない。StandardScaler().fit_transform(X) — 各列を「平均0・分散1」に標準化。単位が違う変数のβを比較可能に。f"...{x}..." はf-string。文字列の中に {変数} と書くだけで埋め込めて、{x:.2f} のように書式も指定できます。265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 | # 日本語フォント設定(macOS) plt.rcParams['font.family'] = 'Hiragino Sans' plt.rcParams['axes.unicode_minus'] = False plt.rcParams['font.size'] = 10 # 出力先ディレクトリ OUT_DIR = 'html/figures' DATA_B = 'data/raw/SSDSE-B-2026.csv' os.makedirs(OUT_DIR, exist_ok=True) def save_fig(name): path = os.path.join(OUT_DIR, f'2022_U1_{name}.png') plt.savefig(path, dpi=150, bbox_inches='tight', facecolor='white') plt.close() print(f' → 保存: {path}') print("=" * 60) print("【Step 1】SSDSE-B-2026 読み込み・前処理") print(" 出典: SSDSE-B-2026.csv(実データ)") print("=" * 60) 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() |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。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にし、真偽値で行をフィルタ。fig.savefig(..., bbox_inches='tight') — 余白を自動で詰めて保存。plt.close() でメモリ解放。df['A'] / df['B'] — pandasの列同士の四則演算は要素ごと(element-wise)。forループ不要なのが強み。288 289 290 291 292 293 294 295 296 | # 数値変換(エラーは NaN) numeric_cols = [ '延べ宿泊者数', '総人口', '年平均気温', '合計特殊出生率', '標準価格(平均価格)(住宅地)', '消費支出(二人以上の世帯)', '65歳以上人口', '月間有効求人数(一般)', '月間有効求職者数(一般)', ] for col in numeric_cols: if col in df_b.columns: df_b[col] = pd.to_numeric(df_b[col], errors='coerce') |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。.map() は「1対1の置き換え」、.apply() は「関数を当てる」。辞書なら .map()、ロジックなら .apply()。297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 | # 65歳以上人口 → 高齢化率 df_b['高齢化率'] = df_b['65歳以上人口'] / df_b['総人口'] * 100 # 有効求人倍率 df_b['有効求人倍率'] = df_b['月間有効求人数(一般)'] / df_b['月間有効求職者数(一般)'] # 宿泊者数の人口あたり(千人あたり) df_b['宿泊者数_千人対'] = df_b['延べ宿泊者数'] / df_b['総人口'] * 1000 df_b['年度'] = pd.to_numeric(df_b['年度'], errors='coerce').astype(int) print(f" データ形状: {df_b.shape} ( {df_b['年度'].min()}〜{df_b['年度'].max()}年 × 47都道府県 )") print(f" 延べ宿泊者数: 非欠損={df_b['延べ宿泊者数'].notna().sum()}, " f"最小={df_b['延べ宿泊者数'].min():.0f}, 最大={df_b['延べ宿泊者数'].max():.0f}") print() |
============================================================ 【Step 1】SSDSE-B-2026 読み込み・前処理 出典: SSDSE-B-2026.csv(実データ) ============================================================ データ形状: (564, 115) ( 2012〜2023年 × 47都道府県 ) 延べ宿泊者数: 非欠損=564, 最小=1046680, 最大=80273650
.astype(int) — 列を整数に変換(年度などを数値比較するため)。[式 for x in リスト] はリスト内包表記。forループでappendする代わりに1行でリストを作れます。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 343 344 345 346 347 348 349 350 351 352 353 354 355 | print(" 図4:COVID変化率 vs 主要説明変数 散布図") # p値の低い順で上位4変数を選択 pval_sorted = sorted(zip(pvals, X_raw.columns), key=lambda x: x[0]) top4 = [v for _, v in pval_sorted[:4]] fig, axes = plt.subplots(2, 2, figsize=(12, 10)) axes = axes.flatten() scatter_colors = [REGION_COLORS.get(REGION_MAP.get(p, '関東'), '#666') for p in reg_df.index] for ax, var in zip(axes, top4): x_vals = reg_df[var].values y_vals = reg_df['covid_chg'].values N_pts = len(x_vals) # jitter: np.linspaceベースの擬似ジッター(重なり軽減) jitter = np.linspace(-0.15, 0.15, N_pts) ax.scatter(x_vals + jitter * (x_vals.max() - x_vals.min()) * 0.03, y_vals, c=scatter_colors, alpha=0.75, s=60, edgecolors='white', linewidths=0.5) # 回帰直線 slope, intercept, r_val, p_val, _ = stats.linregress(x_vals, y_vals) x_line = np.linspace(x_vals.min(), x_vals.max(), 100) ax.plot(x_line, intercept + slope * x_line, color='black', linewidth=1.5, linestyle='--', label=f'r={r_val:.3f}, p={p_val:.3f}') # 都道府県名ラベル(上位・下位3点) sort_idx = np.argsort(y_vals) label_idx = np.concatenate([sort_idx[:3], sort_idx[-3:]]) for idx in label_idx: pref_name = reg_df.index[idx] ax.annotate(pref_name, (x_vals[idx], y_vals[idx]), xytext=(4, 2), textcoords='offset points', fontsize=7, alpha=0.8) ax.axhline(0, color='gray', linewidth=0.8, linestyle=':') ax.set_xlabel(var_labels.get(var, var), fontsize=10) ax.set_ylabel('コロナ禍変化率(%)', fontsize=9) ax.set_title(f'{var_labels.get(var, var)} vs 変化率', fontsize=10) ax.legend(fontsize=9) ax.grid(True, alpha=0.25) |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。fig, ax = plt.subplots(...) — 図全体(fig)と軸(ax)を作る定番。以降は ax.bar(...) 等で操作。ax.axhline / ax.axvline — 水平/垂直の点線。平均線や基準線として定番。stats.linregress(x, y) — 単回帰の傾き・切片・r値・p値・標準誤差を返します。使わない値は _ で受け取り。df[col](1列)と df[[col1, col2]](複数列)でカッコの数が違います。リストを渡していると覚えるとミスを減らせます。356 357 358 359 360 361 362 363 364 365 | # 地域カラー凡例 legend_patches = [mpatches.Patch(color=c, label=r, alpha=0.75) for r, c in REGION_COLORS.items()] fig.legend(handles=legend_patches, loc='lower center', ncol=3, fontsize=9, bbox_to_anchor=(0.5, -0.02)) plt.suptitle('図4:コロナ禍 延べ宿泊者数変化率 vs 主要説明変数(上位4変数)\n' '出典: SSDSE-B-2026(実データ) 色=地域区分', fontsize=11, y=1.01) plt.tight_layout() save_fig('fig4_scatter') |
図4:COVID変化率 vs 主要説明変数 散布図 → 保存: html/figures/2022_U1_fig4_scatter.png
s[:-n]「末尾n文字を除く」/s[n:]「先頭n文字を除く」。スライス [start:stop:step] はリスト・タプル・文字列共通の基本ワザです。366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 | print("=" * 60) print("【学習ポイント】この論文から学べるデータサイエンスの技術") print("=" * 60) print(""" 1. 時系列分析の基本 ・単純な集計(年度別平均)から変化のパターンを読み取る ・「外れ値」(コロナ禍の急落)の視覚的把握が重要 2. 期間比較と変化率 ・「コロナ前 → コロナ禍 → 回復期」を定量化 ・変化率 = (後期 - 前期) / 前期 × 100 3. 地域分類による集計 ・47都道府県を6区分に分類し、地域差を浮き彫りにする ・観光地(旅館施設数が多い地域)ほど落ち込みが大きい傾向 4. 重回帰分析による規定要因の特定 ・目的変数: COVID変化率(宿泊者数の落ち込み度) ・説明変数: 地域の人口・経済・観光・気候特性 ・標準化係数で変数間の効果の大きさを比較 5. 散布図による個別確認 ・各都道府県のポジションを確認(外れ値・傾向の確認) ・回帰直線と相関係数rでの変数関係の要約 6. 実データの活用 ・SSDSE-B:47都道府県×12年の多変数データ(全て実測値) ・宿泊者数は人流(mobility)の代理変数として活用 """) print("=" * 60) print("すべての図を保存しました。") print(" 2022_U1_fig1_timeseries.png : 全国平均の時系列(2012-2023)") print(" 2022_U1_fig2_region.png : 地域別6区分の変化率比較") print(" 2022_U1_fig3_coef.png : 重回帰 標準化係数プロット") print(" 2022_U1_fig4_scatter.png : 変化率 vs 主要説明変数の散布図") print("=" * 60) |
============================================================ 【学習ポイント】この論文から学べるデータサイエンスの技術 ============================================================ 1. 時系列分析の基本 ・単純な集計(年度別平均)から変化のパターンを読み取る ・「外れ値」(コロナ禍の急落)の視覚的把握が重要 2. 期間比較と変化率 ・「コロナ前 → コロナ禍 → 回復期」を定量化 ・変化率 = (後期 - 前期) / 前期 × 100 3. 地域分類による集計 ・47都道府県を6区分に分類し、地域差を浮き彫りにする ・観光地(旅館施設数が多い地域)ほど落ち込みが大きい傾向 4. 重回帰分析による規定要因の特定 ・目的変数: COVID変化率(宿泊者数の落ち込み度) ・説明変数: 地域の人口・経済・観光・気候特性 ・標準化係数で変数間の効果の大きさを比較 5. 散布図による個別確認 ・各都道府県のポジションを確認(外れ値・傾向の確認) ・回帰直線と相関係数rでの変数関係の要約 6. 実データの活用 ・SSDSE-B:47都道府県×12年の多変数データ(全て実測値) ・宿泊者数は人流(mobility)の代理変数として活用 ============================================================ すべての図を保存しました。 2022_U1_fig1_timeseries.png : 全国平均の時系列(2012-2023) 2022_U1_fig2_region.png : 地域別6区分の変化率比較 2022_U1_fig3_coef.png : 重回帰 標準化係数プロット 2022_U1_fig4_scatter.png : 変化率 vs 主要説明変数の散布図 ============================================================
np.cumsum(arr) は累積和、np.linspace(a, b, n) は「aからbを等間隔でn個」。NumPyの定石です。SSDSE-B(47都道府県×2012〜2023年)の延べ宿泊者数を人流の代理変数として用いた時系列・地域別・重回帰分析の結果:
| データ | 変数 | 出典 |
|---|---|---|
| SSDSE-B-2026 | 延べ宿泊者数、総人口、年平均気温、合計特殊出生率、住宅地価、消費支出、高齢化率関連、有効求人数・求職者数、旅館施設数 | 情報・システム研究機構「教育用標準データセット SSDSE-B」(都道府県統計) |
本スクリプトは SSDSE-B の実データ(data/raw/SSDSE-B-2026.csv)を使用しています。 合成データ・乱数シードは使用していません。 データファイルは SSDSE 公式サイト から取得してください。
統計分析の解釈で初心者がやりがちな勘違いをまとめます。特に「相関と因果の混同」「p値の過信」は研究現場でもよく起きる落とし穴です。本文を読む前にも、読んだ後にも、目を通してみてください。
統計の基本用語を初心者向けに解説します。本文中で見慣れない言葉が出てきたら、ここに戻って確認してください。
統計手法について「何のためか」「結果をどう読むか」を初心者向けに解説します。
この研究をさらに発展させるための3つの方向性を示します。「今回わかったこと(X)」から「次に検証すべき仮説(Y)」を立て、「具体的に何をするか(Z)」まで考えてみましょう。
学んだだけでは身につきません。実際に手を動かすのが最強の学習方法です。本論文のスクリプトをベースに、以下のチャレンジに挑戦してみてください。難易度別に5つ用意しました。
本論文で学んだ手法は、研究の世界だけでなく、行政・企業・NPO の現場でも様々に活用されています。具体的なシーンを紹介します。
この論文を読んで初心者が抱きやすい疑問に、教育的観点から答えます。