このページの分析を自分で再現するには、以下の手順でデータを準備してください。コードの編集は不要です。
data/raw/ フォルダに入れます。html/figures/ に自動保存されます。
スポーツ・身体活動は生活習慣病予防や精神的健康の維持に効果的であることが多くの研究で示されている。政府は「スポーツ基本計画」のもとスポーツ参加率の向上を掲げており、予防医療としての位置付けが注目されている。本研究では、都道府県別データを用いてスポーツ活動・体力と健康指標の関係を重回帰分析によって定量的に明らかにすることを目的とする。
まず「都道府県別スポーツ活動・体力と健康指標の関係重回帰による規定要因分析」を統計的にとらえることが有効だと考えられる。 その理由は感覚や経験則だけでは、複雑な社会要因の中で「何が本当に効いているか」を見極めにくいからである。 本研究では公開データと統計手法を組み合わせ、この問いに定量的な答えを出すことを目指す。
SSDSE-B Pearson相関 Kruskal-Wallis検定 OLS重回帰 標準化偏回帰係数
SSDSE-B(都道府県統計)にはスポーツ参加率や体力測定データは直接含まれない。そこで、統計的に入手可能な関連指標を代理変数(Proxy Variable)として活用する。
| 変数の役割 | 使用した変数(SSDSE-B) | 代理する概念 | 期待される効果 |
|---|---|---|---|
| 目的変数 | 保健医療費(二人以上の世帯) (円/月) |
健康意識・健康状態の代理 健康支出 = 健康への投資 |
— |
| 説明変数1 | 消費支出(万円/月) | 所得水準 スポーツ参加には費用が必要 |
正(+) |
| 説明変数2 | 教養娯楽費(千円/月) | 余暇・スポーツ活動支出 スポーツジムや用品費を含む |
正(+) |
| 説明変数3 | 高齢化率(%) | 年齢構成・体力水準 高齢化 → 医療需要増大 |
正(+)または曲線的 |
| 説明変数4 | 光熱・水道費(千円/月) | 生活様式・生活水準 エネルギー消費と活動量の関連 |
正(+) |
実際の研究では「欲しいデータがない」ことはよくある。そのとき、代理変数(Proxy)を使うことで分析が可能になる。ただし代理変数には測定誤差(Measurement Error)が伴い、真の効果を過小推定するアテニュエーションバイアスが生じることを認識する必要がある。
2023年のSSDSE-Bデータを用いて、47都道府県の保健医療費(二人以上の世帯、月次支出)をランキングで可視化した。地域ブロック別に色分けすることで、地理的パターンが視覚的に確認できる。
| 順位 | 都道府県 | 保健医療費(円/月) | 全国平均比 |
|---|---|---|---|
| 1位 | 埼玉 | 21,000 | +46% |
| 2位 | 愛知 | 18,175 | +26% |
| 3位 | 東京 | 18,166 | +26% |
| 最下位 | 愛媛 | 11,052 | −23% |
| 46位 | 新潟 | 11,123 | −23% |
| 全国平均 | — | 14,400 | — |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | 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 from scipy.stats import kruskal from matplotlib.patches import Patch 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) |
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)。f"...{x}..." はf-string。文字列の中に {変数} と書くだけで埋め込めて、{x:.2f} のように書式も指定できます。19 20 21 22 23 24 25 26 | # ── データ読み込み ──────────────────────────────────────────────── 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("=== df_b.columns ===") print(df_b.columns.tolist()) print() |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。pd.read_csv(...) でCSVを読み込みます。encoding='cp932' は日本語Windows由来の文字コード、header=1 は「2行目を列名として使う」。df['地域コード'].str.match(r'^R\d{5}', ...) — 正規表現で「R+数字5桁」の行(47都道府県)だけTrueにし、真偽値で行をフィルタ。.astype(int) — 列を整数に変換(年度などを数値比較するため)。df['A'] / df['B'] — pandasの列同士の四則演算は要素ごと(element-wise)。forループ不要なのが強み。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 | # ── 地域区分マッピング ──────────────────────────────────────────── region_map_raw = { '北海道': '北海道・東北', '青森': '北海道・東北', '岩手': '北海道・東北', '宮城': '北海道・東北', '秋田': '北海道・東北', '山形': '北海道・東北', '福島': '北海道・東北', '茨城': '関東', '栃木': '関東', '群馬': '関東', '埼玉': '関東', '千葉': '関東', '東京': '関東', '神奈川': '関東', '新潟': '中部', '富山': '中部', '石川': '中部', '福井': '中部', '山梨': '中部', '長野': '中部', '岐阜': '中部', '静岡': '中部', '愛知': '中部', '三重': '近畿', '滋賀': '近畿', '京都': '近畿', '大阪': '近畿', '兵庫': '近畿', '奈良': '近畿', '和歌山': '近畿', '鳥取': '中国・四国', '島根': '中国・四国', '岡山': '中国・四国', '広島': '中国・四国', '山口': '中国・四国', '徳島': '中国・四国', '香川': '中国・四国', '愛媛': '中国・四国', '高知': '中国・四国', '福岡': '九州・沖縄', '佐賀': '九州・沖縄', '長崎': '九州・沖縄', '熊本': '九州・沖縄', '大分': '九州・沖縄', '宮崎': '九州・沖縄', '鹿児島': '九州・沖縄', '沖縄': '九州・沖縄' } def get_short_name(full_name): for suffix in ['都', '道', '府', '県']: if full_name.endswith(suffix): return full_name[:-1] return full_name region_order = ['北海道・東北', '関東', '中部', '近畿', '中国・四国', '九州・沖縄'] region_colors = { '北海道・東北': '#4e9af1', '関東': '#e05c5c', '中部': '#f0a500', '近畿': '#5cb85c', '中国・四国': '#9b59b6', '九州・沖縄': '#f39c12' } |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。.map() は「1対1の置き換え」、.apply() は「関数を当てる」。辞書なら .map()、ロジックなら .apply()。58 59 60 61 62 63 | # ── 分析用データセット作成(2023年断面) ───────────────────────── latest_yr = 2023 df_cross = df_b[df_b['年度'] == latest_yr].copy() df_cross['short_name'] = df_cross['都道府県'].apply(get_short_name) df_cross['region'] = df_cross['short_name'].map(region_map_raw) |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。[式 for x in リスト] はリスト内包表記。forループでappendする代わりに1行でリストを作れます。64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 | # プロキシ変数の計算 df_cross['高齢化率'] = df_cross['65歳以上人口'] / df_cross['総人口'] * 100 df_cross['消費支出_万'] = df_cross['消費支出(二人以上の世帯)'] / 10000 df_cross['教養娯楽費_千'] = df_cross['教養娯楽費(二人以上の世帯)'] / 1000 df_cross['光熱水道費_千'] = df_cross['光熱・水道費(二人以上の世帯)'] / 1000 y_col = '保健医療費(二人以上の世帯)' analysis_cols = [y_col, '消費支出_万', '教養娯楽費_千', '高齢化率', '光熱水道費_千', 'region'] df_cross = df_cross.dropna(subset=analysis_cols).copy() print(f"分析サンプル数: {len(df_cross)} 都道府県({latest_yr}年)") print() print("=== 基本統計量 ===") print(df_cross[[y_col, '消費支出_万', '教養娯楽費_千', '高齢化率', '光熱水道費_千']].describe().round(2)) print() national_avg = df_cross[y_col].mean() print(f"保健医療費(全国平均): {national_avg:.0f} 円/月") print() |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。.describe() — 件数・平均・標準偏差・四分位・最大/最小を一括計算。データの素性チェックに必須。r, p = stats.pearsonr(...) — Pythonは複数戻り値を同時に受け取れる(タプルアンパック)。83 84 85 86 87 88 89 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 | # Top/Bottom 5 都道府県 df_sorted_rank = df_cross.sort_values(y_col, ascending=False) print("=== 保健医療費 上位5都道府県 ===") print(df_sorted_rank[['short_name', y_col]].head(5).to_string(index=False)) print("\n=== 保健医療費 下位5都道府県 ===") print(df_sorted_rank[['short_name', y_col]].tail(5).to_string(index=False)) print() df_sorted = df_cross.sort_values(y_col, ascending=False).copy() fig1, ax1 = plt.subplots(figsize=(14, 7)) colors_bar = [region_colors[r] for r in df_sorted['region']] ax1.bar(range(len(df_sorted)), df_sorted[y_col] / 1000, color=colors_bar, edgecolor='white', linewidth=0.5, alpha=0.88) ax1.axhline(national_avg / 1000, color='#333333', linewidth=1.8, linestyle='--', label=f'全国平均: {national_avg/1000:.1f} 千円/月') ax1.set_xticks(range(len(df_sorted))) ax1.set_xticklabels(df_sorted['short_name'], rotation=55, ha='right', fontsize=8.5) ax1.set_ylabel('保健医療費(千円/月)', fontsize=12) ax1.set_title('図1:都道府県別 保健医療費(2023年)\n' '〈健康意識・健康状態の代理指標:二人以上の世帯の月次保健医療支出〉', fontsize=13, fontweight='bold', pad=14) ax1.set_xlim(-0.8, len(df_sorted) - 0.2) ax1.grid(axis='y', alpha=0.3, linewidth=0.8) ax1.spines['top'].set_visible(False) ax1.spines['right'].set_visible(False) legend_elements = [Patch(facecolor=region_colors[r], label=r, alpha=0.88) for r in region_order] legend_elements.append( plt.Line2D([0], [0], color='#333333', linewidth=1.8, linestyle='--', label=f'全国平均: {national_avg/1000:.1f} 千円/月') ) ax1.legend(handles=legend_elements, fontsize=9, loc='upper right', ncol=2) plt.tight_layout() fig1.savefig(os.path.join(FIG_DIR, '2018_H4_fig1.png'), bbox_inches='tight') plt.close(fig1) print("fig1 saved.") |
=== df_b.columns ===
['年度', '地域コード', '都道府県', '総人口', '総人口(男)', '総人口(女)', '日本人人口', '日本人人口(男)', '日本人人口(女)', '15歳未満人口', '15歳未満人口(男)', '15歳未満人口(女)', '15~64歳人口', '15~64歳人口(男)', '15~64歳人口(女)', '65歳以上人口', '65歳以上人口(男)', '65歳以上人口(女)', '出生数', '出生数(男)', '出生数(女)', '合計特殊出生率', '死亡数', '死亡数(男)', '死亡数(女)', '転入者数(日本人移動者)', '転入者数(日本人移動者)(男)', '転入者数(日本人移動者)(女)', '転出者数(日本人移動者)', '転出者数(日本人移動者)(男)', '転出者数(日本人移動者)(女)', '婚姻件数', '離婚件数', '年平均気温', '最高気温(日最高気温の月平均の最高値)', '最低気温(日最低気温の月平均の最低値)', '降水日数(年間)', '降水量(年間)', '着工建築物数', '着工建築物床面積', '旅館営業施設数(ホテルを含む)', '旅館営業施設客室数(ホテルを含む)', '標準価格(平均価格)(住宅地)', '標準価格(平均価格)(商業地)', '幼稚園数', '幼稚園教員数', '幼稚園在園者数', '小学校数', '小学校教員数', '小学校児童数', '中学校数', '中学校教員数', '中学校生徒数', '中学校卒業者数', '中学校卒業者のうち進学者数', '高等学校数', '高等学校教員数', '高等学校生徒数', '高等学校卒業者数', '高等学校卒業者のうち進学者数', '短期大学数', '大学数', '短期大学教員数', '大学教員数', '短期大学学生数', '大学学生数', '短期大学卒業者数', '短期大学卒業者のうち進学者数', '大学卒業者数', '大学卒業者のうち進学者数', '専修学校数', '各種学校数', '専修学校生徒数', '各種学校生徒数', '新規求職申込件数(一般)', '月間有効求職者数(一般)', '月間有効求人数(一般)', '充足数(一般)', '就職件数(一般)', '一般旅券発行件数', '延べ宿泊者数', '外国人延べ宿泊者数', '着工新設住宅戸数', '着工新設持家数', '着工新設貸家数', '着工新設分譲住宅数', '着工新設住宅床面積', '着工新設持家床面積', '着工新設分譲住宅床面積', '着工新設貸家床面積', 'ごみ総排出量(総量)', '1人1日当たりの排出量', 'ごみのリサイクル率', '一般病院数', '一般診療所数', '歯科診療所数', '保育所等数', '保育所等定員数', '保育所等利用待機児童数', '保育所等在所児数', '保育所等保育士数', '消費支出(二人以上の世帯)', '食料費(二人以上の世帯)', '住居費(二人以上の世帯)', '光熱・水道費(二人以上の世帯)', '家具・家事用品費(二人以上の世帯)', '被服及び履物費(二人以上の世帯)', '保健医療費(二人以上の世帯)', '交通・通信費(二人以上の世帯)', '教育費(二人以上の世帯)', '教養娯楽費(二人以上の世帯)', 'その他の消費支出(二人以上の世帯)']
分析サンプル数: 46 都道府県(2023年)
=== 基本統計量 ===
保健医療費(二人以上の世帯) 消費支出_万 教養娯楽費_千 高齢化率 光熱水道費_千
count 46.00 46.00 46.00 46.00 46.00
mean 14400.11 29.58 27.45 31.55 24.06
std 2160.94 2.44 4.54 3.37 3.09
min 11052.00 22.34 18.37 22.75 18.66
25% 12596.00 27.93 25.71 30.00 21.80
50% 14420.00 30.08 27.24 31.74 23.50
75% 15478.25 30.77 30.11 34.09 25.30
max 21000.00 34.41 40.39 39.06 31.95
保健医療費(全国平均): 14400 円/月
=== 保健医療費 上位5都道府県 ===
short_name 保健医療費(二人以上の世帯)
埼玉 21000
愛知 18175
東京 18166
山梨 17817
香川 17197
=== 保健医療費 下位5都道府県 ===
short_name 保健医療費(二人以上の世帯)
宮崎 11807
沖縄 11686
群馬 11601
新潟 11123
愛媛 11052
fig1 saved.fig, ax = plt.subplots(...) — 図全体(fig)と軸(ax)を作る定番。以降は ax.bar(...) 等で操作。sort_values('列名', ascending=False) — 指定列で並べ替え(降順)。ax.axhline / ax.axvline — 水平/垂直の点線。平均線や基準線として定番。x if cond else y は三項演算子。リスト内包表記と組み合わせると、forとifを1行で書けます。所得水準の代理指標として「消費支出(万円/月)」を用い、保健医療費との散布図を作成した。47都道府県全て(欠損除く46)にラベルを付け、地域ブロック別色分けで地理的パターンも確認できる。
消費支出と保健医療費の正の相関(r=0.605)は、「健康は正常財(Normal Good)である」ことを示唆する。所得が増えると健康への支出も増える。経済学では保健医療需要の所得弾力性が正であることは古典的な発見だが、地域間でそのパターンを可視化することで、格差の存在が明らかになる。
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 155 156 157 158 159 160 161 162 163 | x2 = df_cross['消費支出_万'].values y2 = df_cross[y_col].values / 1000 # 千円 r2, p2 = stats.pearsonr(x2, y2) fig2, ax2 = plt.subplots(figsize=(10, 7)) for _, row in df_cross.iterrows(): c = region_colors[row['region']] ax2.scatter(row['消費支出_万'], row[y_col] / 1000, color=c, s=72, alpha=0.85, zorder=3, edgecolors='white', linewidths=0.8) ax2.annotate(row['short_name'], (row['消費支出_万'], row[y_col] / 1000), xytext=(3, 3), textcoords='offset points', fontsize=6.5, color='#333', zorder=4) m, b_int = np.polyfit(x2, y2, 1) xr = np.linspace(x2.min() - 0.5, x2.max() + 0.5, 200) ax2.plot(xr, m * xr + b_int, 'k-', linewidth=1.8, alpha=0.7) p2_str = f'{p2:.4f}' if p2 >= 0.0001 else f'{p2:.2e}' ax2.set_xlabel('消費支出(万円/月)', fontsize=12) ax2.set_ylabel('保健医療費(千円/月)', fontsize=12) ax2.set_title(f'図2:消費支出 vs 保健医療費(2023年、47都道府県)\n' f'Pearson r = {r2:.3f}, p = {p2_str}', fontsize=13, fontweight='bold', pad=12) legend_elem2 = [Patch(facecolor=region_colors[r], label=r, alpha=0.85) for r in region_order] ax2.legend(handles=legend_elem2, fontsize=9, loc='upper left', framealpha=0.85, ncol=2) ax2.grid(alpha=0.25, linewidth=0.8) ax2.spines['top'].set_visible(False) ax2.spines['right'].set_visible(False) plt.tight_layout() fig2.savefig(os.path.join(FIG_DIR, '2018_H4_fig2.png'), bbox_inches='tight') plt.close(fig2) print(f"fig2 saved. r={r2:.3f}, p={p2_str}") |
fig2 saved. r=0.605, p=8.57e-06
fig, ax = plt.subplots(...) — 図全体(fig)と軸(ax)を作る定番。以降は ax.bar(...) 等で操作。stats.pearsonr(x, y) — Pearson相関係数 r と p値を同時に返します。for _, row in df.iterrows() — DataFrameを1行ずつ取り出すループ。1点ずつ描画したいときに使用。df['A'] / df['B'] — pandasの列同士の四則演算は要素ごと(element-wise)。forループ不要なのが強み。6地域ブロック間で保健医療費に有意な差があるかどうかを、ノンパラメトリック検定であるKruskal-Wallis検定で確認した。箱ひげ図で分布の形状と外れ値を同時に可視化する。
一元配置分散分析(ANOVA)は正規分布を仮定するが、都道府県データはしばしば非正規分布となる。KW検定は「各群の中央値が等しい」という帰無仮説を検定するノンパラメトリック版ANOVA。n=47を6地域に分けると各群7〜9と小さくなるため、検出力が低くなることに注意。
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 | groups = [df_cross[df_cross['region'] == r][y_col].dropna().values for r in region_order] groups_nonempty = [g for g in groups if len(g) > 0] kw_stat, kw_p = kruskal(*groups_nonempty) kw_p_str = f'{kw_p:.4f}' if kw_p >= 0.0001 else f'{kw_p:.2e}' print(f"\nKruskal-Wallis: H={kw_stat:.3f}, p={kw_p_str}") fig3, ax3 = plt.subplots(figsize=(10, 6)) bp_data = [g / 1000 for g in groups] bp = ax3.boxplot(bp_data, patch_artist=True, notch=False, medianprops=dict(color='black', linewidth=2), whiskerprops=dict(linewidth=1.4), capprops=dict(linewidth=1.4), flierprops=dict(marker='o', markersize=5, alpha=0.6)) for patch, region in zip(bp['boxes'], region_order): patch.set_facecolor(region_colors[region]) patch.set_alpha(0.82) ax3.set_xticks(range(1, len(region_order) + 1)) ax3.set_xticklabels(region_order, rotation=20, ha='right', fontsize=10) ax3.set_ylabel('保健医療費(千円/月)', fontsize=12) ax3.set_title(f'図3:地域ブロック別 保健医療費の分布(2023年)\n' f'Kruskal-Wallis検定: H = {kw_stat:.2f}, p = {kw_p_str}', fontsize=13, fontweight='bold', pad=12) ax3.grid(axis='y', alpha=0.3, linewidth=0.8) ax3.spines['top'].set_visible(False) ax3.spines['right'].set_visible(False) |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。fig, ax = plt.subplots(...) — 図全体(fig)と軸(ax)を作る定番。以降は ax.bar(...) 等で操作。.map() は「1対1の置き換え」、.apply() は「関数を当てる」。辞書なら .map()、ロジックなら .apply()。194 195 196 197 198 199 200 201 202 203 | # n数表示(ylimを先に取得) y3_lim = ax3.get_ylim() for i, (region, grp) in enumerate(zip(region_order, groups)): ax3.text(i + 1, y3_lim[0] + (y3_lim[1] - y3_lim[0]) * 0.01, f'n={len(grp)}', ha='center', va='bottom', fontsize=9, color='#555') plt.tight_layout() fig3.savefig(os.path.join(FIG_DIR, '2018_H4_fig3.png'), bbox_inches='tight') plt.close(fig3) print("fig3 saved.") |
Kruskal-Wallis: H=5.055, p=0.4092 fig3 saved.
[式 for x in リスト] はリスト内包表記。forループでappendする代わりに1行でリストを作れます。4つの説明変数を投入したOLS重回帰分析を実施した。変数間の単位が異なるため全変数をZスコア(平均0・標準偏差1)に標準化し、標準化偏回帰係数(β)で各変数の相対的重要度を比較できるようにした。
| 説明変数 | 標準化β | 95%CI 下限 | 95%CI 上限 | p値 | 有意性 |
|---|---|---|---|---|---|
| 消費支出(万円/月) | +0.298 | −0.110 | +0.707 | 0.148 | n.s. |
| 教養娯楽費(千円/月) | +0.318 | −0.104 | +0.740 | 0.135 | n.s. |
| 高齢化率(%) | −0.223 | −0.508 | +0.063 | 0.123 | n.s. |
| 光熱・水道費(千円/月) | −0.061 | −0.327 | +0.205 | 0.646 | n.s. |
| モデル全体(F検定) | F = 10.49, p < 0.001 | R² = 0.506 | |||
通常の偏回帰係数は変数の単位に依存するため、「消費支出1万円増加」と「高齢化率1%増加」の効果を直接比較できない。標準化偏回帰係数(β)は全変数をZスコアに変換してから推定するため、単位が統一され、直接比較が可能になる。
205 206 207 208 209 210 211 212 213 | X_vars = ['消費支出_万', '教養娯楽費_千', '高齢化率', '光熱水道費_千'] var_labels = { '消費支出_万': '消費支出(万円/月)\n〈所得水準の代理〉', '教養娯楽費_千': '教養娯楽費(千円/月)\n〈余暇・スポーツ支出の代理〉', '高齢化率': '高齢化率(%)\n〈年齢構成・体力水準の代理〉', '光熱水道費_千': '光熱・水道費(千円/月)\n〈生活様式の指標〉', } df_reg = df_cross[X_vars + [y_col]].dropna().copy() |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。[式 for x in リスト] はリスト内包表記。forループでappendする代わりに1行でリストを作れます。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 276 | # 標準化 for col in X_vars + [y_col]: df_reg[col + '_z'] = (df_reg[col] - df_reg[col].mean()) / df_reg[col].std() X_std = sm.add_constant(df_reg[[v + '_z' for v in X_vars]]) y_std = df_reg[y_col + '_z'] ols_result = sm.OLS(y_std, X_std).fit() print("\n=== OLS重回帰結果(標準化変数) ===") print(ols_result.summary()) coefs = ols_result.params[1:] ci = ols_result.conf_int().iloc[1:] pvals = ols_result.pvalues[1:] r2_val = ols_result.rsquared adj_r2 = ols_result.rsquared_adj def sig_star(p): if p < 0.001: return '***' elif p < 0.01: return '**' elif p < 0.05: return '*' else: return 'n.s.' labels_plot = [var_labels[v] for v in X_vars] coef_vals = coefs.values ci_lo = ci.iloc[:, 0].values ci_hi = ci.iloc[:, 1].values xerr_lo = coef_vals - ci_lo xerr_hi = ci_hi - coef_vals colors4 = ['#e05c5c' if c > 0 else '#4e9af1' for c in coef_vals] fig4, ax4 = plt.subplots(figsize=(10, 5.5)) y4_pos = np.arange(len(X_vars)) ax4.barh(y4_pos, coef_vals, xerr=[xerr_lo, xerr_hi], color=colors4, alpha=0.82, edgecolor='white', error_kw=dict(ecolor='#333', linewidth=1.5, capsize=5), height=0.55) ax4.axvline(0, color='black', linewidth=1.2, linestyle='-') for i, (c, p) in enumerate(zip(coef_vals, pvals.values)): star = sig_star(p) offset = 0.03 if c >= 0 else -0.03 ha_a = 'left' if c >= 0 else 'right' color_s = '#c0392b' if star != 'n.s.' else '#888' ax4.text(c + offset, i, star, va='center', ha=ha_a, fontsize=12, fontweight='bold', color=color_s) ax4.set_yticks(y4_pos) ax4.set_yticklabels(labels_plot, fontsize=10) ax4.set_xlabel('標準化偏回帰係数(β)', fontsize=12) ax4.set_title(f'図4:重回帰分析 — 標準化偏回帰係数と95%信頼区間(2023年)\n' f'R² = {r2_val:.3f}, Adj.R² = {adj_r2:.3f} ' f'* p<0.05 ** p<0.01 *** p<0.001', fontsize=12, fontweight='bold', pad=12) ax4.grid(axis='x', alpha=0.3, linewidth=0.8) ax4.spines['top'].set_visible(False) ax4.spines['right'].set_visible(False) plt.tight_layout() fig4.savefig(os.path.join(FIG_DIR, '2018_H4_fig4.png'), bbox_inches='tight') plt.close(fig4) print("fig4 saved.") |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。fig, ax = plt.subplots(...) — 図全体(fig)と軸(ax)を作る定番。以降は ax.bar(...) 等で操作。ax.axhline / ax.axvline — 水平/垂直の点線。平均線や基準線として定番。sm.add_constant(X) — 切片項(定数1の列)を先頭に追加。statsmodelsで必須。sm.OLS(y, X).fit() — 最小二乗法でモデルを推定。model.params, model.pvalues, model.conf_int() で結果取得。r, p = stats.pearsonr(...) — Pythonは複数戻り値を同時に受け取れる(タプルアンパック)。277 278 279 280 281 282 283 284 285 286 287 288 289 | # ── 最終サマリー出力 ────────────────────────────────────────────── print("\n=== 分析結果サマリー ===") print(f"分析年度: {latest_yr}年 N={len(df_cross)}") print(f"目的変数: 保健医療費(二人以上の世帯)[代理変数]") print(f"消費支出との相関(Pearson r): r={r2:.3f}, p={p2_str}") print(f"Kruskal-Wallis(地域別): H={kw_stat:.3f}, p={kw_p_str}") print(f"重回帰 R²={r2_val:.3f}, Adj.R²={adj_r2:.3f}") print("\n標準化偏回帰係数:") for v, c, p in zip(X_vars, coef_vals, pvals.values): short_label = var_labels[v].split('\n')[0] print(f" {short_label:25s}: β={c:+.4f} {sig_star(p)} (p={p:.4f})") print("\nDONE: 2018_H4_katsuyo") |
=== OLS重回帰結果(標準化変数) ===
OLS Regression Results
==============================================================================
Dep. Variable: 保健医療費(二人以上の世帯)_z R-squared: 0.506
Model: OLS Adj. R-squared: 0.458
Method: Least Squares F-statistic: 10.49
Date: Mon, 18 May 2026 Prob (F-statistic): 6.01e-06
Time: 11:23:22 Log-Likelihood: -48.550
No. Observations: 46 AIC: 107.1
Df Residuals: 41 BIC: 116.2
Df Model: 4
Covariance Type: nonrobust
==============================================================================
coef std err t P>|t| [0.025 0.975]
------------------------------------------------------------------------------
const -5.551e-17 0.109 -5.11e-16 1.000 -0.219 0.219
消費支出_万_z 0.2980 0.202 1.473 0.148 -0.110 0.707
教養娯楽費_千_z 0.3181 0.209 1.523 0.135 -0.104 0.740
高齢化率_z -0.2226 0.142 -1.573 0.123 -0.508 0.063
光熱水道費_千_z -0.0609 0.132 -0.463 0.646 -0.327 0.205
==============================================================================
Omnibus: 3.180 Durbin-Watson: 1.884
Prob(Omnibus): 0.204 Jarque-Bera (JB): 2.356
Skew: 0.545 Prob(JB): 0.308
Kurtosis: 3.207 Cond. No. 3.68
==============================================================================
Notes:
[1] Standard Errors assume that the covariance matrix of the errors is correctly specified.
fig4 saved.
=== 分析結果サマリー ===
分析年度: 2023年 N=46
目的変数: 保健医療費(二人以上の世帯)[代理変数]
消費支出との相関(Pearson r): r=0.605, p=8.57e-06
Kruskal-Wallis(地域別): H=5.055, p=0.4092
重回帰 R²=0.506, Adj.R²=0.458
標準化偏回帰係数:
消費支出(万円/月) : β=+0.2980 n.s. (p=0.1483)
教養娯楽費(千円/月) : β=+0.3181 n.s. (p=0.1354)
高齢化率(%) : β=-0.2226 n.s. (p=0.1234)
光熱・水道費(千円/月) : β=-0.0609 n.s. (p=0.6459)
DONE: 2018_H4_katsuyox if cond else y は三項演算子。リスト内包表記と組み合わせると、forとifを1行で書けます。SSDSE-Bの47都道府県・2023年断面データを用いた分析結果:
「保健医療費が高い = 健康」なのか「保健医療費が高い = 病気が多い(不健康)」なのか?この方向性の問題を「逆因果(Reverse Causality)」という。横断データ(断面データ)では因果の方向を特定できないため、政策的な解釈には慎重さが必要。因果推論には操作変数法(IV)や差分の差分法(DID)、ランダム化比較試験(RCT)が用いられる。
本分析は2018年度コンペ高校生の部の入賞作品として見事に完成している。ただし学術研究として批判的に読むと、6つの根本的な制約がある。以下では「何が不十分か・なぜ不十分か・どう改善できるか」を分析手順に沿って整理する。
① 多重共線性VIF(分散拡大係数)は各説明変数を他の変数で回帰したときのR²から計算する。VIF > 5 で警戒、VIF > 10 で深刻とされる。Ridge回帰はL2ペナルティで係数を縮小し、多重共線性下でも安定した推定を得る。
固定効果(FE)推定は、各都道府県の平均値を引く「within変換」によって観察されない個体間異質性を除去する。「裕福な都道府県はスポーツも医療費も多い」という交絡が消え、「同じ都道府県でスポーツ支出が増えたとき医療費はどう変わったか」を推定できる。
Moran's Iは−1(完全な負の空間相関)から+1(完全な正の空間相関)の値をとる。都道府県の隣接関係を重み行列Wで定義し、OLS残差の空間パターンを検定する。pysal / libpysal で実装できる。
| 問題 | 種類 | 主な影響 | 修正難易度 |
|---|---|---|---|
| ① 多重共線性 | 設計 | 個別係数が信頼できない | 低(VIF確認+Ridge) |
| ② 代理変数の構成概念妥当性 | 測定 | タイトルと分析が乖離 | 中(別データ要) |
| ③ 目的変数の曖昧さ | 測定 | 解釈の方向が定まらない | 低(SSDSE-B内で代替可) |
| ④ 横断データ・逆因果 | 因果推論 | 政策含意が導けない | 中(複数年SSDSE-B利用) |
| ⑤ 空間的自己相関 | 前提違反 | p値が過小推定される | 中(libpysal導入) |
| ⑥ 地域比較の検出力不足 | 検定設計 | 「差なし」結論が無意味 | 低(群数を減らす) |
| データ | 出典 | 備考 |
|---|---|---|
| SSDSE-B 都道府県統計 | 統計数理研究所 SSDSE(社会・人口統計体系) | 2023年断面、46都道府県(欠損1件除く) |
| 保健医療費(家計調査) | 総務省統計局 家計調査(二人以上の世帯) | 月次支出額(円) |
| 教養娯楽費(家計調査) | 総務省統計局 家計調査(二人以上の世帯) | スポーツ・余暇支出の代理指標 |
| 人口構成データ | 総務省統計局 住民基本台帳 | 高齢化率の算出に使用 |
本教育用再現コードはSSDSE-B-2026(実公的データ)を使用。合成データは一切使用していない。
統計分析の解釈で初心者がやりがちな勘違いをまとめます。特に「相関と因果の混同」「p値の過信」は研究現場でもよく起きる落とし穴です。本文を読む前にも、読んだ後にも、目を通してみてください。
統計の基本用語を初心者向けに解説します。本文中で見慣れない言葉が出てきたら、ここに戻って確認してください。
統計手法について「何のためか」「結果をどう読むか」を初心者向けに解説します。
この研究をさらに発展させるための3つの方向性を示します。「今回わかったこと(X)」から「次に検証すべき仮説(Y)」を立て、「具体的に何をするか(Z)」まで考えてみましょう。
学んだだけでは身につきません。実際に手を動かすのが最強の学習方法です。本論文のスクリプトをベースに、以下のチャレンジに挑戦してみてください。難易度別に5つ用意しました。
本論文で学んだ手法は、研究の世界だけでなく、行政・企業・NPO の現場でも様々に活用されています。具体的なシーンを紹介します。
この論文を読んで初心者が抱きやすい疑問に、教育的観点から答えます。