このページの分析を自分で再現するには、以下の手順でデータを準備してください。コードの編集は不要です。
data/raw/ フォルダに入れます。html/figures/ に自動保存されます。
日本では急速な高齢化と所得格差の拡大により、社会保障費の増加が財政上の重大な課題となっている。一方で、都道府県間には社会保障給付の水準や生活保護受給率に大きなばらつきが存在する。この地域格差を生み出す要因は何か。本研究は、高齢化率・労働市場・所得水準・医療インフラという4つの視点から、都道府県別の社会保障需要の規定要因を重回帰分析によって探求する。
まず「都道府県別社会保障給付費と生活保護受給率の規定要因分析」を統計的にとらえることが有効だと考えられる。 その理由は感覚や経験則だけでは、複雑な社会要因の中で「何が本当に効いているか」を見極めにくいからである。 本研究では公開データと統計手法を組み合わせ、この問いに定量的な答えを出すことを目指す。
SSDSE-B-2026 重回帰分析(OLS) Kruskal-Wallis検定 標準化偏回帰係数
SSDSE-B-2026(社会・人口統計体系データセット、都道府県版)の2023年データを使用。全47都道府県のうち、欠損値処理後に46都道府県が分析対象となった。
| 変数の役割 | 使用変数(SSDSE-B) | 元論文の変数 | 単位 |
|---|---|---|---|
| 目的変数 社会保障需要の代理 |
保健医療費(二人以上の世帯) | 社会保障給付費・生活保護受給率 | 円/月 |
| 説明変数① 高齢化 |
高齢化率(65歳以上/総人口) | 高齢化率 | % |
| 説明変数② 労働市場 |
有効求人倍率(月間有効求人数/求職者数) | 失業率・有効求人倍率 | 倍 |
| 説明変数③ 医療インフラ |
病院数(人口10万対) | 医療施設数 | 施設/10万人 |
| 説明変数④ 所得水準 |
消費支出(二人以上の世帯) | 1人当たり消費支出・所得 | 万円/月 |
| 変数 | 平均 | 標準偏差 | 最小 | 最大 |
|---|---|---|---|---|
| 保健医療費(円/月) | 14,400 | 2,161 | 11,052 | 21,000 |
| 高齢化率(%) | 31.6 | 3.4 | 22.8 | 39.1 |
| 有効求人倍率(倍) | 1.35 | 0.22 | 0.90 | 1.86 |
| 病院数(人口10万対) | 6.85 | 2.78 | 3.13 | 16.07 |
| 消費支出(万円/月) | 29.6 | 2.4 | 22.3 | 34.4 |
まず、47都道府県の保健医療費(社会保障需要の代理変数)を降順に並べ、地域間格差の全体像を把握する。棒グラフは6つの地域ブロックで色分けされている。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 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 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} のように書式も指定できます。18 19 20 21 22 23 24 25 26 27 28 | # ── データ読み込み ──────────────────────────────────────────────── 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("=== df_b.head(3) ===") print(df_b.head(3)) 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ループ不要なのが強み。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 | # ── 地域区分マッピング ──────────────────────────────────────────── # 都道府県名は「県/都/道/府」付き → 短縮名でマッピング 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()。62 63 64 65 66 67 | # ── 分析用データセット作成(2023年断面) ───────────────────────── latest_yr = 2023 df_cross = df_b[df_b['年度'] == latest_yr].copy() # 短縮都道府県名 df_cross['short_name'] = df_cross['都道府県'].apply(get_short_name) |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。[式 for x in リスト] はリスト内包表記。forループでappendする代わりに1行でリストを作れます。68 69 70 71 72 73 74 75 | # 地域区分 df_cross['region'] = df_cross['short_name'].map(region_map_raw) # プロキシ変数の計算 df_cross['高齢化率'] = df_cross['65歳以上人口'] / df_cross['総人口'] * 100 df_cross['有効求人倍率'] = df_cross['月間有効求人数(一般)'] / df_cross['月間有効求職者数(一般)'] df_cross['病院数_per10万'] = df_cross['一般病院数'] / df_cross['総人口'] * 100000 df_cross['消費支出_万'] = df_cross['消費支出(二人以上の世帯)'] / 10000 |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。r, p = stats.pearsonr(...) — Pythonは複数戻り値を同時に受け取れる(タプルアンパック)。76 77 78 79 80 81 82 83 84 85 | # 目的変数:保健医療費(代理変数) y_col = '保健医療費(二人以上の世帯)' df_cross = df_cross.dropna(subset=[y_col, '高齢化率', '有効求人倍率', '病院数_per10万', '消費支出_万', 'region']) print(f"分析サンプル数: {len(df_cross)} 都道府県({latest_yr}年)") print() print("=== 基本統計量 ===") stats_cols = [y_col, '高齢化率', '有効求人倍率', '病院数_per10万', '消費支出_万'] print(df_cross[stats_cols].describe().round(2)) print() |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。.describe() — 件数・平均・標準偏差・四分位・最大/最小を一括計算。データの素性チェックに必須。x if cond else y は三項演算子。リスト内包表記と組み合わせると、forとifを1行で書けます。86 87 88 89 90 91 92 93 94 95 96 | # ── 全国平均 ────────────────────────────────────────────────────── national_avg = df_cross[y_col].mean() print(f"保健医療費(全国平均): {national_avg:.0f} 円/月") 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']] bars = ax1.bar(range(len(df_sorted)), df_sorted[y_col] / 1000, color=colors_bar, edgecolor='white', linewidth=0.5, alpha=0.88) |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。fig, ax = plt.subplots(...) — 図全体(fig)と軸(ax)を作る定番。以降は ax.bar(...) 等で操作。sort_values('列名', ascending=False) — 指定列で並べ替え(降順)。df[col](1列)と df[[col1, col2]](複数列)でカッコの数が違います。リストを渡していると覚えるとミスを減らせます。97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 | # 全国平均線 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.legend(fontsize=11, loc='upper right') ax1.grid(axis='y', alpha=0.3, linewidth=0.8) ax1.spines['top'].set_visible(False) ax1.spines['right'].set_visible(False) |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。ax.axhline / ax.axvline — 水平/垂直の点線。平均線や基準線として定番。s[:-n]「末尾n文字を除く」/s[n:]「先頭n文字を除く」。スライス [start:stop:step] はリスト・タプル・文字列共通の基本ワザです。112 113 114 115 116 117 118 119 120 121 122 123 124 | # 地域凡例 from matplotlib.patches import Patch legend_elements = [Patch(facecolor=region_colors[r], label=r, alpha=0.88) for r in region_order] ax1.legend(handles=legend_elements + [ plt.Line2D([0], [0], color='#333333', linewidth=1.8, linestyle='--', label=f'全国平均: {national_avg/1000:.1f} 千円/月')], fontsize=9, loc='upper right', ncol=2) plt.tight_layout() fig1.savefig(os.path.join(FIG_DIR, '2019_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日当たりの排出量', 'ごみのリサイクル率', '一般病院数', '一般診療所数', '歯科診療所数', '保育所等数', '保育所等定員数', '保育所等利用待機児童数', '保育所等在所児数', '保育所等保育士数', '消費支出(二人以上の世帯)', '食料費(二人以上の世帯)', '住居費(二人以上の世帯)', '光熱・水道費(二人以上の世帯)', '家具・家事用品費(二人以上の世帯)', '被服及び履物費(二人以上の世帯)', '保健医療費(二人以上の世帯)', '交通・通信費(二人以上の世帯)', '教育費(二人以上の世帯)', '教養娯楽費(二人以上の世帯)', 'その他の消費支出(二人以上の世帯)']
=== df_b.head(3) ===
年度 地域コード 都道府県 ... 教育費(二人以上の世帯) 教養娯楽費(二人以上の世帯) その他の消費支出(二人以上の世帯)
0 2023 R01000 北海道 ... 6911 25661 48694
1 2022 R01000 北海道 ... 9551 27234 46466
2 2021 R01000 北海道 ... 9913 23762 51583
[3 rows x 112 columns]
分析サンプル数: 46 都道府県(2023年)
=== 基本統計量 ===
保健医療費(二人以上の世帯) 高齢化率 有効求人倍率 病院数_per10万 消費支出_万
count 46.00 46.00 46.00 46.00 46.00
mean 14400.11 31.55 1.35 6.85 29.58
std 2160.94 3.37 0.22 2.78 2.44
min 11052.00 22.75 0.90 3.13 22.34
25% 12596.00 30.00 1.19 4.84 27.93
50% 14420.00 31.74 1.37 5.94 30.08
75% 15478.25 34.09 1.51 8.33 30.77
max 21000.00 39.06 1.86 16.07 34.41
保健医療費(全国平均): 14400 円/月
fig1 saved.import pandas as pd など — 必要なライブラリをまとめて呼び出します。as pd は短い別名(alias)。np.cumsum(arr) は累積和、np.linspace(a, b, n) は「aからbを等間隔でn個」。NumPyの定石です。「高齢化が進むほど社会保障需要が高まる」という仮説H1を検証するため、高齢化率と保健医療費の散布図を描き、Pearsonの積率相関係数を算出した。
社会保障を統計的に捉える指標には複数ある。家計調査の「保健医療費」は自己負担分を反映するが、公的給付(医療費の7割)は含まれない。理想的な指標は国民医療費や社会保障給付費(厚生労働省)だが、都道府県別の細かい年次データは入手が難しい。
126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 | x2 = df_cross['高齢化率'].values y2 = df_cross[y_col].values / 1000 # 千円 r2, p2 = stats.pearsonr(x2, y2) fig2, ax2 = plt.subplots(figsize=(9, 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) |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。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ループ不要なのが強み。141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 | # 回帰直線 m, b = np.polyfit(x2, y2, 1) xr = np.linspace(x2.min() - 0.5, x2.max() + 0.5, 200) ax2.plot(xr, m * xr + b, 'k-', linewidth=1.8, alpha=0.7, label=f'回帰直線') 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'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, '2019_H4_fig2.png'), bbox_inches='tight') plt.close(fig2) print(f"fig2 saved. r={r2:.3f}, p={p2_str}") |
fig2 saved. r=-0.497, p=0.0004
.map() は「1対1の置き換え」、.apply() は「関数を当てる」。辞書なら .map()、ロジックなら .apply()。仮説H4「地域ブロックによって保健医療費の水準に有意差がある」を検定するため、6地域ブロック別の箱ひげ図を描き、Kruskal-Wallis検定を実施した。
3群以上の比較にはANOVA(分散分析)が使われるが、ANOVAは正規性・等分散性を前提とする。Kruskal-Wallis検定はこれらを仮定しないノンパラメトリック検定で、都道府県数が少ない(各群n=7〜9)場合に適している。
【帰無仮説と対立仮説】
H₀: 全地域ブロックの保健医療費の中央値は等しい
H₁: 少なくとも1つの地域ブロックの中央値が異なる
今回はp = 0.41 > 0.05 → H₀を棄却できない(有意差なし)
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 194 | 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) print(f"\nKruskal-Wallis: H={kw_stat:.3f}, p={kw_p:.4f}") 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) kw_p_str = f'{kw_p:.4f}' if kw_p >= 0.0001 else f'{kw_p:.2e}' 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()。195 196 197 198 199 200 201 202 203 | # 各ブロックのn数表示 for i, (region, grp) in enumerate(zip(region_order, groups)): ax3.text(i + 1, ax3.get_ylim()[0] + 0.2, f'n={len(grp)}', ha='center', va='bottom', fontsize=9, color='#555') plt.tight_layout() fig3.savefig(os.path.join(FIG_DIR, '2019_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-score変換)した上で回帰を行った。
| 変数 | 標準化偏回帰係数 β | 95%信頼区間 | p値 | 有意性 |
|---|---|---|---|---|
| 高齢化率(%) | −0.259 | [−0.534, +0.015] | 0.064 | n.s.(†) |
| 有効求人倍率(倍) | −0.221 | [−0.452, +0.010] | 0.061 | n.s.(†) |
| 病院数(人口10万対) | −0.021 | [−0.291, +0.248] | 0.874 | n.s. |
| 消費支出(万円/月) | +0.518 | [+0.278, +0.757] | <0.001 | *** |
|
R² = 0.517, Adj.R² = 0.470, F(4,41) = 10.96, p < 0.001 † 周辺有意(0.05 < p < 0.10) |
||||
通常の回帰係数(非標準化)は変数の単位に依存するため、異なる変数間で直接比較できない。標準化偏回帰係数(β)はすべての変数をz-score化した後の回帰係数で、「1標準偏差分の変化に対応するyの標準偏差変化」を表し、変数間の相対的重要度を比較できる。
【効果量の目安(Cohen 1988)】
β の絶対値: 0.1 = 小, 0.3 = 中, 0.5 = 大
消費支出のβ = 0.518 → 「大」の効果量
回帰分析は変数間の「関連」を示すが、「因果」を証明するものではない。たとえば「消費支出が高い都道府県で保健医療費が高い」という関係は、以下のどの解釈も成立しうる:
統計的な関連から政策提言を行う際は、「Aだから→Bを増やせばCが改善する」という単純な因果推論の飛躍に注意が必要。
205 206 207 208 209 210 211 212 213 | X_vars = ['高齢化率', '有効求人倍率', '病院数_per10万', '消費支出_万'] var_labels = { '高齢化率': '高齢化率(%)', '有効求人倍率': '有効求人倍率', '病院数_per10万': '病院数(人口10万対)', '消費支出_万': '消費支出(万円/月)' } 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 | # 標準化 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()) |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。sm.add_constant(X) — 切片項(定数1の列)を先頭に追加。statsmodelsで必須。sm.OLS(y, X).fit() — 最小二乗法でモデルを推定。model.params, model.pvalues, model.conf_int() で結果取得。r, p = stats.pearsonr(...) — Pythonは複数戻り値を同時に受け取れる(タプルアンパック)。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 | # 標準化係数・信頼区間・p値 coefs = ols_result.params[1:] # 定数項除く ci = ols_result.conf_int().iloc[1:] # 95%CI 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=(9, 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.025 if c >= 0 else -0.025 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=11) ax4.set_xlabel('標準化偏回帰係数(β)', fontsize=12) ax4.set_title(f'図4:重回帰分析 — 標準化偏回帰係数(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, '2019_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 — 水平/垂直の点線。平均線や基準線として定番。x if cond else y は三項演算子。リスト内包表記と組み合わせると、forとifを1行で書けます。277 278 279 280 281 282 283 284 285 286 287 288 | # ── 最終サマリー出力 ────────────────────────────────────────────── print("\n=== 分析結果サマリー ===") print(f"分析年度: {latest_yr}年 N={len(df_cross)}") print(f"目的変数: 保健医療費(二人以上の世帯)[代理変数]") print(f"高齢化率との相関: 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): print(f" {var_labels[v]:20s}: β={c:+.4f} {sig_star(p)} (p={p:.4f})") print("\nDONE: 2019_H4_katsuyo") |
=== OLS重回帰結果(標準化変数) ===
OLS Regression Results
==============================================================================
Dep. Variable: 保健医療費(二人以上の世帯)_z R-squared: 0.517
Model: OLS Adj. R-squared: 0.470
Method: Least Squares F-statistic: 10.96
Date: Mon, 18 May 2026 Prob (F-statistic): 3.89e-06
Time: 11:23:28 Log-Likelihood: -48.040
No. Observations: 46 AIC: 106.1
Df Residuals: 41 BIC: 115.2
Df Model: 4
Covariance Type: nonrobust
================================================================================
coef std err t P>|t| [0.025 0.975]
--------------------------------------------------------------------------------
const -5.551e-17 0.107 -5.17e-16 1.000 -0.217 0.217
高齢化率_z -0.2593 0.136 -1.908 0.063 -0.534 0.015
有効求人倍率_z -0.2210 0.115 -1.928 0.061 -0.452 0.010
病院数_per10万_z -0.0213 0.133 -0.160 0.874 -0.291 0.248
消費支出_万_z 0.5175 0.119 4.364 0.000 0.278 0.757
==============================================================================
Omnibus: 2.587 Durbin-Watson: 1.783
Prob(Omnibus): 0.274 Jarque-Bera (JB): 2.227
Skew: 0.535 Prob(JB): 0.328
Kurtosis: 2.868 Cond. No. 2.10
==============================================================================
Notes:
[1] Standard Errors assume that the covariance matrix of the errors is correctly specified.
fig4 saved.
=== 分析結果サマリー ===
分析年度: 2023年 N=46
目的変数: 保健医療費(二人以上の世帯)[代理変数]
高齢化率との相関: r=-0.497, p=0.0004
Kruskal-Wallis: H=5.055, p=0.4092
重回帰 R²=0.517, Adj.R²=0.470
標準化偏回帰係数:
高齢化率(%) : β=-0.2593 n.s. (p=0.0635)
有効求人倍率 : β=-0.2210 n.s. (p=0.0608)
病院数(人口10万対) : β=-0.0213 n.s. (p=0.8740)
消費支出(万円/月) : β=+0.5175 *** (p=0.0001)
DONE: 2019_H4_katsuyodf[col](1列)と df[[col1, col2]](複数列)でカッコの数が違います。リストを渡していると覚えるとミスを減らせます。SSDSE-B-2026の47都道府県・2023年断面データを用いた分析の結果:
| データ・ツール | 出典・説明 |
|---|---|
| SSDSE-B-2026(都道府県版) | 統計数理研究所 SSDSE(社会・人口統計体系データセット)2026年版。47都道府県×複数年次の統計データ。 |
| statsmodels(OLS回帰) | Python統計モデリングライブラリ。sm.OLS().fit() で重回帰・標準誤差・信頼区間を算出。 |
| scipy.stats.kruskal(検定) | SciPy科学計算ライブラリのKruskal-Wallis検定関数。ノンパラメトリックな3群以上比較。 |
本教育用コードはSSDSE-B-2026の実公的統計データを使用(合成データ不使用)。出典:統計数理研究所。
統計分析の解釈で初心者がやりがちな勘違いをまとめます。特に「相関と因果の混同」「p値の過信」は研究現場でもよく起きる落とし穴です。本文を読む前にも、読んだ後にも、目を通してみてください。
統計の基本用語を初心者向けに解説します。本文中で見慣れない言葉が出てきたら、ここに戻って確認してください。
統計手法について「何のためか」「結果をどう読むか」を初心者向けに解説します。
この研究をさらに発展させるための3つの方向性を示します。「今回わかったこと(X)」から「次に検証すべき仮説(Y)」を立て、「具体的に何をするか(Z)」まで考えてみましょう。
学んだだけでは身につきません。実際に手を動かすのが最強の学習方法です。本論文のスクリプトをベースに、以下のチャレンジに挑戦してみてください。難易度別に5つ用意しました。
本論文で学んだ手法は、研究の世界だけでなく、行政・企業・NPO の現場でも様々に活用されています。具体的なシーンを紹介します。
この論文を読んで初心者が抱きやすい疑問に、教育的観点から答えます。