このページの分析を自分で再現するには、以下の手順でデータを準備してください。コードの編集は不要です。
data/raw/ フォルダに入れます。html/figures/ に自動保存されます。
日本における医療資源(医師・病院・病床)の地域格差は、長年にわたる重大な政策課題である。 厚生労働省のデータによれば、人口万人あたりの一般病院数は、最多の高知県(1.62件)と最少の神奈川県(0.31件)の間で 約5倍以上の開きがある。このような医療資源の地域不均衡は、 住民の健康格差や死亡率の格差に繋がると考えられている。
まず「都道府県別医療資源の偏在と健康格差医師数・病床数の地域不均衡分析」を統計的にとらえることが有効だと考えられる。 その理由は感覚や経験則だけでは、複雑な社会要因の中で「何が本当に効いているか」を見極めにくいからである。 本研究では公開データと統計手法を組み合わせ、この問いに定量的な答えを出すことを目指す。
SSDSE-B-2026 Gini係数 Lorenz曲線 Pearson相関 重回帰分析 総務大臣賞
本分析は、統計数理研究所が公開する SSDSE-B-2026(社会・人口統計体系データセット) の2019年度(令和元年度)データを使用した。N=47都道府県、全数調査であるため標本誤差はなく、 記述統計・推測統計の両面からアプローチできる。
| カテゴリ | 変数名 | 出典統計 | 説明 |
|---|---|---|---|
| 医療資源 | 一般病院数(/万人) | 医療施設調査 | 主要な医療資源指標。病床数の代理変数としても機能 |
| 医療資源 | 一般診療所数(/万人) | 医療施設調査 | プライマリケアへのアクセス指標 |
| 健康アウトカム | 死亡数→死亡率(/千人) | 人口動態統計 | 健康格差の代理変数。年齢調整なし粗死亡率 |
| 人口構造 | 65歳以上人口→高齢化率(%) | 人口推計 | 死亡率の主要交絡因子 |
| 社会経済 | 保健医療費(円/月) | 家計調査 | 二人以上世帯の保健医療支出。医療需要代理 |
| 規模 | 総人口→log変換 | 国勢調査 | 都市規模効果を制御 |
| 変数 | 平均 | 標準偏差 | 最小 | 最大 |
|---|---|---|---|---|
| 一般病院数(/万人) | 0.690 | 0.279 | 0.313 (神奈川) | 1.617 (高知) |
| 一般診療所数(/万人) | 8.213 | 1.195 | 5.963 (埼玉) | 11.010 (東京) |
| 死亡率(/千人) | 11.37 | 1.68 | 8.24 (沖縄) | 14.76 (高知) |
| 高齢化率(%) | 28.94 | 3.39 | 21.8 (沖縄) | 35.2 (高知) |
| 保健医療費(円) | 13,069 | 1,474 | 9,925 | 17,031 |
まず47都道府県の一般病院数(人口万人あたり)を横棒グラフで可視化し、地域差のパターンを把握する。 棒の色は地域ブロック(北海道・東北・関東・中部・近畿・中国・四国・九州)を表している。
| 順位 | 都道府県 | 病院数/万人 | 地域 |
|---|---|---|---|
| 1 | 高知県 | 1.617 | 四国 |
| 2 | 鹿児島県 | 1.273 | 九州 |
| 3 | 徳島県 | 1.264 | 四国 |
| 4 | 大分県 | 1.146 | 九州 |
| 5 | 宮崎県 | 1.114 | 九州 |
| 順位 | 都道府県 | 病院数/万人 | 地域 |
|---|---|---|---|
| 43 | 神奈川県 | 0.313 | 関東 |
| 44 | 滋賀県 | 0.353 | 近畿 |
| 45 | 愛知県 | 0.378 | 中部 |
| 46 | 静岡県 | 0.394 | 中部 |
| 47 | 埼玉県 | 0.402 | 関東 |
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 matplotlib.patches as mpatches 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) |
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 | # ── データ読み込み ────────────────────────────────────────────────────────────── 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:", df_b.columns.tolist()) print("Available years:", sorted(df_b['年度'].unique())) |
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ループ不要なのが強み。25 26 27 28 29 30 31 32 33 34 35 36 | # 2019年度データを使用 df = df_b[df_b['年度'] == 2019].copy().reset_index(drop=True) print(f"\n2019年度 都道府県数: {len(df)}") # ── 派生変数の計算 ───────────────────────────────────────────────────────────── df['人口万人'] = df['総人口'] / 10000 df['病院数per万人'] = df['一般病院数'] / df['人口万人'] df['診療所数per万人'] = df['一般診療所数'] / df['人口万人'] df['死亡率per千人'] = df['死亡数'] / df['総人口'] * 1000 df['高齢化率'] = df['65歳以上人口'] / df['総人口'] * 100 df['保健医療費'] = df['保健医療費(二人以上の世帯)'] df['log人口'] = np.log(df['総人口']) |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。.map() は「1対1の置き換え」、.apply() は「関数を当てる」。辞書なら .map()、ロジックなら .apply()。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 | # 地域分類 region_map = { '北海道': '北海道', '青森県': '東北', '岩手県': '東北', '宮城県': '東北', '秋田県': '東北', '山形県': '東北', '福島県': '東北', '茨城県': '関東', '栃木県': '関東', '群馬県': '関東', '埼玉県': '関東', '千葉県': '関東', '東京都': '関東', '神奈川県': '関東', '新潟県': '中部', '富山県': '中部', '石川県': '中部', '福井県': '中部', '山梨県': '中部', '長野県': '中部', '岐阜県': '中部', '静岡県': '中部', '愛知県': '中部', '三重県': '近畿', '滋賀県': '近畿', '京都府': '近畿', '大阪府': '近畿', '兵庫県': '近畿', '奈良県': '近畿', '和歌山県': '近畿', '鳥取県': '中国', '島根県': '中国', '岡山県': '中国', '広島県': '中国', '山口県': '中国', '徳島県': '四国', '香川県': '四国', '愛媛県': '四国', '高知県': '四国', '福岡県': '九州', '佐賀県': '九州', '長崎県': '九州', '熊本県': '九州', '大分県': '九州', '宮崎県': '九州', '鹿児島県': '九州', '沖縄県': '九州', } region_colors = { '北海道': '#1f77b4', '東北': '#ff7f0e', '関東': '#2ca02c', '中部': '#d62728', '近畿': '#9467bd', '中国': '#8c564b', '四国': '#e377c2', '九州': '#bcbd22', } df['地域'] = df['都道府県'].map(region_map) df['色'] = df['地域'].map(region_colors) |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。[式 for x in リスト] はリスト内包表記。forループでappendする代わりに1行でリストを作れます。65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 | # ── Gini係数計算関数 ─────────────────────────────────────────────────────────── def gini(values): """Gini係数を計算する(0=完全平等, 1=完全不平等)""" v = np.sort(np.array(values, dtype=float)) n = len(v) cumv = np.cumsum(v) return (n + 1 - 2 * np.sum(cumv) / cumv[-1]) / n def lorenz_curve(values): """Lorenz曲線の座標を返す""" v = np.sort(np.array(values, dtype=float)) n = len(v) cum_pop = np.concatenate([[0], np.arange(1, n + 1) / n]) cum_val = np.concatenate([[0], np.cumsum(v) / v.sum()]) return cum_pop, cum_val |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。gini(arr) — Gini係数(0=完全平等、1=完全不平等)を計算。ソート → 累積和 → 公式という単純実装。r, p = stats.pearsonr(...) — Pythonは複数戻り値を同時に受け取れる(タプルアンパック)。80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 | # ── Gini係数の計算 ──────────────────────────────────────────────────────────── g_hospital = gini(df['病院数per万人'].values) g_clinic = gini(df['診療所数per万人'].values) print(f"\nGini係数(病院数/万人): {g_hospital:.4f}") print(f"Gini係数(診療所数/万人): {g_clinic:.4f}") # ── 上位・下位5都道府県 ──────────────────────────────────────────────────────── top5 = df.nlargest(5, '病院数per万人')[['都道府県', '病院数per万人', '死亡率per千人', '高齢化率']] bottom5 = df.nsmallest(5, '病院数per万人')[['都道府県', '病院数per万人', '死亡率per千人', '高齢化率']] print("\n病院数/万人 上位5:") print(top5.to_string(index=False)) print("\n病院数/万人 下位5:") print(bottom5.to_string(index=False)) fig, ax = plt.subplots(figsize=(10, 13)) df_sorted = df.sort_values('病院数per万人', ascending=True).reset_index(drop=True) bars = ax.barh( df_sorted['都道府県'], df_sorted['病院数per万人'], color=df_sorted['色'], edgecolor='white', linewidth=0.5, height=0.75 ) |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。gini(arr) — Gini係数(0=完全平等、1=完全不平等)を計算。ソート → 累積和 → 公式という単純実装。fig, ax = plt.subplots(...) — 図全体(fig)と軸(ax)を作る定番。以降は ax.bar(...) 等で操作。sort_values('列名', ascending=False) — 指定列で並べ替え(降順)。x if cond else y は三項演算子。リスト内包表記と組み合わせると、forとifを1行で書けます。103 104 105 106 107 108 109 110 111 112 | # 全国平均線 nat_avg = df['病院数per万人'].mean() ax.axvline(nat_avg, color='#333333', linewidth=1.8, linestyle='--', label=f'全国平均: {nat_avg:.3f}件/万人') ax.set_xlabel('一般病院数(人口10,000人あたり)', fontsize=12) ax.set_title('都道府県別 一般病院数(人口万人あたり)\n2019年度', fontsize=14, fontweight='bold', pad=15) ax.set_xlim(0, df_sorted['病院数per万人'].max() * 1.15) ax.grid(axis='x', alpha=0.3, linestyle=':') ax.spines['top'].set_visible(False) ax.spines['right'].set_visible(False) |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。ax.axhline / ax.axvline — 水平/垂直の点線。平均線や基準線として定番。df[col](1列)と df[[col1, col2]](複数列)でカッコの数が違います。リストを渡していると覚えるとミスを減らせます。113 114 115 116 117 118 119 120 121 122 123 124 125 126 | # 凡例(地域) handles = [mpatches.Patch(color=c, label=r) for r, c in region_colors.items()] handles.append(plt.Line2D([0], [0], color='#333333', linewidth=1.8, linestyle='--', label=f'全国平均: {nat_avg:.3f}')) ax.legend(handles=handles, loc='lower right', fontsize=9, framealpha=0.8, ncol=2) # 値ラベル(右端に) for bar, val in zip(bars, df_sorted['病院数per万人']): ax.text(val + 0.01, bar.get_y() + bar.get_height() / 2, f'{val:.2f}', va='center', ha='left', fontsize=7.5) fig.tight_layout() fig.savefig(os.path.join(FIG_DIR, '2019_H1_fig1.png'), bbox_inches='tight') plt.close(fig) print("\n図1 保存完了: 2019_H1_fig1.png") |
df_b columns: ['年度', '地域コード', '都道府県', '総人口', '総人口(男)', '総人口(女)', '日本人人口', '日本人人口(男)', '日本人人口(女)', '15歳未満人口', '15歳未満人口(男)', '15歳未満人口(女)', '15~64歳人口', '15~64歳人口(男)', '15~64歳人口(女)', '65歳以上人口', '65歳以上人口(男)', '65歳以上人口(女)', '出生数', '出生数(男)', '出生数(女)', '合計特殊出生率', '死亡数', '死亡数(男)', '死亡数(女)', '転入者数(日本人移動者)', '転入者数(日本人移動者)(男)', '転入者数(日本人移動者)(女)', '転出者数(日本人移動者)', '転出者数(日本人移動者)(男)', '転出者数(日本人移動者)(女)', '婚姻件数', '離婚件数', '年平均気温', '最高気温(日最高気温の月平均の最高値)', '最低気温(日最低気温の月平均の最低値)', '降水日数(年間)', '降水量(年間)', '着工建築物数', '着工建築物床面積', '旅館営業施設数(ホテルを含む)', '旅館営業施設客室数(ホテルを含む)', '標準価格(平均価格)(住宅地)', '標準価格(平均価格)(商業地)', '幼稚園数', '幼稚園教員数', '幼稚園在園者数', '小学校数', '小学校教員数', '小学校児童数', '中学校数', '中学校教員数', '中学校生徒数', '中学校卒業者数', '中学校卒業者のうち進学者数', '高等学校数', '高等学校教員数', '高等学校生徒数', '高等学校卒業者数', '高等学校卒業者のうち進学者数', '短期大学数', '大学数', '短期大学教員数', '大学教員数', '短期大学学生数', '大学学生数', '短期大学卒業者数', '短期大学卒業者のうち進学者数', '大学卒業者数', '大学卒業者のうち進学者数', '専修学校数', '各種学校数', '専修学校生徒数', '各種学校生徒数', '新規求職申込件数(一般)', '月間有効求職者数(一般)', '月間有効求人数(一般)', '充足数(一般)', '就職件数(一般)', '一般旅券発行件数', '延べ宿泊者数', '外国人延べ宿泊者数', '着工新設住宅戸数', '着工新設持家数', '着工新設貸家数', '着工新設分譲住宅数', '着工新設住宅床面積', '着工新設持家床面積', '着工新設分譲住宅床面積', '着工新設貸家床面積', 'ごみ総排出量(総量)', '1人1日当たりの排出量', 'ごみのリサイクル率', '一般病院数', '一般診療所数', '歯科診療所数', '保育所等数', '保育所等定員数', '保育所等利用待機児童数', '保育所等在所児数', '保育所等保育士数', '消費支出(二人以上の世帯)', '食料費(二人以上の世帯)', '住居費(二人以上の世帯)', '光熱・水道費(二人以上の世帯)', '家具・家事用品費(二人以上の世帯)', '被服及び履物費(二人以上の世帯)', '保健医療費(二人以上の世帯)', '交通・通信費(二人以上の世帯)', '教育費(二人以上の世帯)', '教養娯楽費(二人以上の世帯)', 'その他の消費支出(二人以上の世帯)'] Available years: [np.int64(2012), np.int64(2013), np.int64(2014), np.int64(2015), np.int64(2016), np.int64(2017), np.int64(2018), np.int64(2019), np.int64(2020), np.int64(2021), np.int64(2022), np.int64(2023)] 2019年度 都道府県数: 47 Gini係数(病院数/万人): 0.2142 Gini係数(診療所数/万人): 0.0813 病院数/万人 上位5: 都道府県 病院数per万人 死亡率per千人 高齢化率 高知県 1.616595 14.759657 35.193133 鹿児島県 1.273408 13.629213 31.960050 徳島県 1.263736 13.909341 33.653846 大分県 1.146384 12.887125 32.892416 宮崎県 1.114206 12.802228 32.126277 病院数/万人 下位5: 都道府県 病院数per万人 死亡率per千人 高齢化率 神奈川県 0.313313 9.103209 25.249350 滋賀県 0.353107 9.336864 25.988701 愛知県 0.378457 9.253937 25.036390 静岡県 0.394197 11.549411 29.811114 埼玉県 0.401798 9.471125 26.709344 図1 保存完了: 2019_H1_fig1.png
fig.savefig(..., bbox_inches='tight') — 余白を自動で詰めて保存。plt.close() でメモリ解放。s[:-n]「末尾n文字を除く」/s[n:]「先頭n文字を除く」。スライス [start:stop:step] はリスト・タプル・文字列共通の基本ワザです。地域間格差を単なる「最大値÷最小値」ではなく、分布全体を考慮して定量化するために Lorenz曲線とGini係数を用いる。 これはもともと所得不平等の測定に開発された手法だが、医療資源の地域格差分析にも広く応用されている。
Lorenz曲線は、人口を小さい順に並べたときの「累積人口割合」と 「累積資源割合」の関係を示したグラフである。完全平等(すべての都道府県で等しい密度)なら 45度の対角線に一致し、格差が大きいほど曲線は下に膨らむ。
Gini係数は「45度線とLorenz曲線に囲まれた面積」÷「45度線以下の三角形の面積」で定義される。 0(完全平等)から1(完全不平等)の値を取る。
本分析では一般病院数のGini係数 = 0.214、一般診療所数 = 0.081。 病院数の方が診療所数より格差が大きいことが分かる。
| 指標 | Gini係数 | 格差の程度 | 解釈 |
|---|---|---|---|
| 一般病院数(/万人) | 0.214 | 中程度 | 地方と都市圏で顕著な偏在。政策対応が必要なレベル |
| 一般診療所数(/万人) | 0.081 | 軽度 | 病院より均等に分布。プライマリケアの格差は小さい |
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 | fig, axes = plt.subplots(1, 2, figsize=(12, 5.5)) for ax, col, label, color, g in [ (axes[0], '病院数per万人', '一般病院数', '#1f77b4', g_hospital), (axes[1], '診療所数per万人', '一般診療所数', '#e377c2', g_clinic), ]: cum_pop, cum_val = lorenz_curve(df[col].values) ax.plot([0, 1], [0, 1], 'k--', linewidth=1.5, label='完全平等線', zorder=1) ax.fill_between(cum_pop, cum_pop, cum_val, alpha=0.18, color=color) ax.plot(cum_pop, cum_val, '-', color=color, linewidth=2.5, label=f'Lorenz曲線\nGini係数 = {g:.4f}', zorder=2) ax.set_xlabel('累積人口割合', fontsize=11) ax.set_ylabel('累積医療資源割合', fontsize=11) ax.set_title(f'{label}のLorenz曲線(都道府県間格差)', fontsize=12, fontweight='bold') ax.legend(fontsize=10, loc='upper left') ax.set_xlim(0, 1); ax.set_ylim(0, 1) ax.grid(alpha=0.3, linestyle=':') ax.text(0.65, 0.08, f'Gini = {g:.4f}', fontsize=13, fontweight='bold', color=color, transform=ax.transAxes) ax.spines['top'].set_visible(False) ax.spines['right'].set_visible(False) fig.suptitle('医療資源の地域格差:Lorenz曲線分析(2019年度)', fontsize=13, fontweight='bold', y=1.02) fig.tight_layout() fig.savefig(os.path.join(FIG_DIR, '2019_H1_fig2.png'), bbox_inches='tight') plt.close(fig) print("図2 保存完了: 2019_H1_fig2.png") |
図2 保存完了: 2019_H1_fig2.png
fig, ax = plt.subplots(...) — 図全体(fig)と軸(ax)を作る定番。以降は ax.bar(...) 等で操作。ax.fill_between(...) — 2つの曲線で囲まれた領域を塗りつぶし。Lorenz曲線の格差面積などを可視化。fig.savefig(..., bbox_inches='tight') — 余白を自動で詰めて保存。plt.close() でメモリ解放。df['A'] / df['B'] — pandasの列同士の四則演算は要素ごと(element-wise)。forループ不要なのが強み。医療資源の多寡が実際の健康アウトカムと関連するかを検証するため、 一般病院数(/万人)と死亡率(/千人)の散布図を描き、Pearson相関係数と回帰直線を算出した。
相関係数は「2変数の線形関係の強さ」を示すが、「AがBを引き起こす」という因果関係を保証しない。 今回の「病院数が多い→死亡率が高い」という正の相関は、「高齢化率」という第三の変数 (交絡因子)が両者を同時に引き上げているために生じている。
これを「交絡(confounding)」という。交絡因子を無視して 「病院を増やすと死亡率が上がる」と結論づけるのは誤りである。 重回帰分析によって交絡因子を統制することで、真の関係が明らかになる。
| 相関係数(Pearson r) | p値 | 判定 | 解釈 |
|---|---|---|---|
| 病院数密度 vs 死亡率:r = 0.492 | p < 0.001 | 有意(正の相関) | 交絡(高齢化率)の影響を含む |
| 高齢化率 vs 死亡率:r = 0.954 | p < 0.001 | 強い有意(正の相関) | 最も強い関連。主要交絡因子 |
| 診療所数密度 vs 死亡率:r = 0.238 | p = 0.108 | 非有意 | 診療所数は死亡率と独立 |
157 158 159 160 161 162 163 164 165 | fig, ax = plt.subplots(figsize=(9, 7)) x = df['病院数per万人'].values y = df['死亡率per千人'].values # 地域ごとに散布 for region, color in region_colors.items(): mask = df['地域'] == region ax.scatter(x[mask], y[mask], c=color, s=60, zorder=3, label=region, edgecolors='white', linewidth=0.5) |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。fig, ax = plt.subplots(...) — 図全体(fig)と軸(ax)を作る定番。以降は ax.bar(...) 等で操作。.map() は「1対1の置き換え」、.apply() は「関数を当てる」。辞書なら .map()、ロジックなら .apply()。166 167 168 169 170 171 172 173 | # 都道府県ラベル for _, row in df.iterrows(): ax.annotate( row['都道府県'].replace('県', '').replace('府', '').replace('都', '').replace('道', ''), (row['病院数per万人'], row['死亡率per千人']), fontsize=7, ha='center', va='bottom', color='#333333', xytext=(0, 3), textcoords='offset points' ) |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。for _, row in df.iterrows() — DataFrameを1行ずつ取り出すループ。1点ずつ描画したいときに使用。[式 for x in リスト] はリスト内包表記。forループでappendする代わりに1行でリストを作れます。174 175 176 177 178 179 180 181 182 183 184 185 186 | # 回帰直線 slope, intercept, r, p, se = stats.linregress(x, y) xline = np.linspace(x.min(), x.max(), 100) ax.plot(xline, intercept + slope * xline, 'r-', linewidth=2, label=f'回帰直線\nr = {r:.3f}, p = {p:.4f}', zorder=2) ax.set_xlabel('一般病院数(人口万人あたり)', fontsize=12) ax.set_ylabel('死亡率(人口千人あたり)', fontsize=12) ax.set_title('医療資源密度と死亡率の関係\n都道府県別(2019年度)', fontsize=13, fontweight='bold') ax.legend(fontsize=9, loc='upper left', framealpha=0.85) ax.grid(alpha=0.25, linestyle=':') ax.spines['top'].set_visible(False) ax.spines['right'].set_visible(False) |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。stats.linregress(x, y) — 単回帰の傾き・切片・r値・p値・標準誤差を返します。使わない値は _ で受け取り。r, p = stats.pearsonr(...) — Pythonは複数戻り値を同時に受け取れる(タプルアンパック)。187 188 189 190 191 192 193 194 195 196 197 198 199 200 | # 注釈: Pearson r ax.text(0.97, 0.07, f'Pearson r = {r:.3f}\np = {p:.4f}', transform=ax.transAxes, fontsize=10, ha='right', bbox=dict(boxstyle='round,pad=0.4', facecolor='#fff3cd', edgecolor='#f0ad4e')) print(f"\n【散布図】病院数/万人 vs 死亡率") print(f" Pearson r = {r:.4f}, p = {p:.4f}") print(f" 回帰式: y = {slope:.4f}x + {intercept:.4f}") fig.tight_layout() fig.savefig(os.path.join(FIG_DIR, '2019_H1_fig3.png'), bbox_inches='tight') plt.close(fig) print("図3 保存完了: 2019_H1_fig3.png") |
【散布図】病院数/万人 vs 死亡率 Pearson r = 0.4921, p = 0.0004 回帰式: y = 3.2067x + 9.9051 図3 保存完了: 2019_H1_fig3.png
fig.savefig(..., bbox_inches='tight') — 余白を自動で詰めて保存。plt.close() でメモリ解放。x if cond else y は三項演算子。リスト内包表記と組み合わせると、forとifを1行で書けます。死亡率を目的変数、医療資源密度・高齢化率・都市規模・保健医療費を説明変数とした OLS重回帰分析を実施した。変数はすべて標準化(平均0、標準偏差1)して 標準化偏回帰係数(β)を比較した。
単位が異なる変数(病院数/万人、高齢化率%、保健医療費円)を同じ土俵で比較するには、 すべての変数を平均0・標準偏差1に標準化(z-score化)してから回帰する。 得られる係数を標準化偏回帰係数(β)と呼び、 「標準偏差1単位の変化がアウトカムを何SD変化させるか」として解釈できる。
Gini係数・Lorenz曲線のほかに、地域格差を測る指標として偏在指数(Location Quotient: LQ)がある。 LQ > 1 ならば全国平均より医療資源が多く(過剰供給)、LQ < 1 ならば不足を示す。
| 説明変数 | 標準化係数(β) | p値 | 有意 | 解釈 |
|---|---|---|---|---|
| 高齢化率 | +0.899 | p < 0.001 | *** | 死亡率の最大規定因子。高齢者比率が高いほど死亡率上昇 |
| 保健医療費 | −0.086 | p = 0.096 | — | 医療費支出が多いほど死亡率低下傾向(有意でない) |
| log人口 | −0.076 | p = 0.249 | — | 都市規模効果(非有意) |
| 診療所数密度 | −0.050 | p = 0.281 | — | 診療所アクセスの死亡率抑制効果(有意でない) |
| 病院数密度 | −0.025 | p = 0.649 | — | 高齢化統制後の病院効果(非有意) |
| モデル適合度 | R² = 0.925 / adj.R² = 0.916 / F(5,41) = 100.7, p < 0.001 | |||
202 203 204 205 206 207 208 209 | X_vars = { '病院数\n(/万人)': df['病院数per万人'].values, '診療所数\n(/万人)': df['診療所数per万人'].values, '高齢化率\n(%)': df['高齢化率'].values, 'log人口': df['log人口'].values, '保健医療費\n(円)': df['保健医療費'].values, } y_reg = df['死亡率per千人'].values |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。[式 for x in リスト] はリスト内包表記。forループでappendする代わりに1行でリストを作れます。210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 | # 標準化 def standardize(arr): return (arr - arr.mean()) / arr.std() X_std_list = [standardize(v) for v in X_vars.values()] y_std = standardize(y_reg) X_std_arr = np.column_stack(X_std_list) X_const = sm.add_constant(X_std_arr) model = sm.OLS(y_std, X_const).fit() print("\n【重回帰分析結果】") print(model.summary()) coefs = np.array(model.params[1:], dtype=float) # 定数項を除く ci_all = model.conf_int() ci = np.array(ci_all[1:], dtype=float) # (n, 2) numpy array pvals = np.array(model.pvalues[1:], dtype=float) labels = list(X_vars.keys()) |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。sm.add_constant(X) — 切片項(定数1の列)を先頭に追加。statsmodelsで必須。sm.OLS(y, X).fit() — 最小二乗法でモデルを推定。model.params, model.pvalues, model.conf_int() で結果取得。r, p = stats.pearsonr(...) — Pythonは複数戻り値を同時に受け取れる(タプルアンパック)。228 229 230 231 232 233 234 | # 係数を昇順にソート(横棒グラフ用) order = np.argsort(coefs) labels_o = [labels[i] for i in order] coefs_o = coefs[order] ci_lo_o = ci[order, 0] ci_hi_o = ci[order, 1] pvals_o = pvals[order] |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。x if cond else y は三項演算子。リスト内包表記と組み合わせると、forとifを1行で書けます。235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 | # 有意マーク sig_marks = ['***' if p < 0.001 else '**' if p < 0.01 else '*' if p < 0.05 else '' for p in pvals_o] bar_colors = ['#d62728' if c < 0 else '#1f77b4' for c in coefs_o] fig, ax = plt.subplots(figsize=(8, 5.5)) ypos = np.arange(len(labels_o)) bars = ax.barh(ypos, coefs_o, color=bar_colors, alpha=0.85, edgecolor='white', height=0.55, zorder=3) ax.errorbar(coefs_o, ypos, xerr=[coefs_o - ci_lo_o, ci_hi_o - coefs_o], fmt='none', ecolor='#333333', elinewidth=1.5, capsize=4, zorder=4) ax.axvline(0, color='#333333', linewidth=1.2, zorder=2) ax.set_yticks(ypos) ax.set_yticklabels(labels_o, fontsize=10) ax.set_xlabel('標準化偏回帰係数(β)', fontsize=11) ax.set_title('重回帰分析:死亡率への影響要因\n標準化偏回帰係数と95%信頼区間(2019年度)', fontsize=12, fontweight='bold') ax.grid(axis='x', alpha=0.3, linestyle=':') ax.spines['top'].set_visible(False) ax.spines['right'].set_visible(False) |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。fig, ax = plt.subplots(...) — 図全体(fig)と軸(ax)を作る定番。以降は ax.bar(...) 等で操作。ax.axhline / ax.axvline — 水平/垂直の点線。平均線や基準線として定番。df[col](1列)と df[[col1, col2]](複数列)でカッコの数が違います。リストを渡していると覚えるとミスを減らせます。258 259 260 261 262 263 264 | # 有意マーク & 係数値 for i, (c, mark, p) in enumerate(zip(coefs_o, sig_marks, pvals_o)): txt = f'{c:+.3f}{mark}' ax.text(c + (0.02 if c >= 0 else -0.02), i, txt, va='center', ha='left' if c >= 0 else 'right', fontsize=9.5, fontweight='bold') |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。s[:-n]「末尾n文字を除く」/s[n:]「先頭n文字を除く」。スライス [start:stop:step] はリスト・タプル・文字列共通の基本ワザです。265 266 267 268 269 270 271 272 273 274 275 276 277 278 | # R² ax.text(0.98, 0.05, f'R² = {model.rsquared:.3f}\nadj.R² = {model.rsquared_adj:.3f}', transform=ax.transAxes, fontsize=9.5, ha='right', bbox=dict(boxstyle='round,pad=0.4', facecolor='#e8f4f8', edgecolor='#aec6cf')) # 凡例 red_patch = mpatches.Patch(color='#d62728', alpha=0.85, label='負の効果(死亡率を下げる)') blue_patch = mpatches.Patch(color='#1f77b4', alpha=0.85, label='正の効果(死亡率を上げる)') ax.legend(handles=[blue_patch, red_patch], fontsize=8.5, loc='lower right') fig.tight_layout() fig.savefig(os.path.join(FIG_DIR, '2019_H1_fig4.png'), bbox_inches='tight') plt.close(fig) print("図4 保存完了: 2019_H1_fig4.png") |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。fig.savefig(..., bbox_inches='tight') — 余白を自動で詰めて保存。plt.close() でメモリ解放。np.cumsum(arr) は累積和、np.linspace(a, b, n) は「aからbを等間隔でn個」。NumPyの定石です。279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 | # ── 最終サマリー印刷 ─────────────────────────────────────────────────────────── print("\n" + "=" * 60) print("分析サマリー") print("=" * 60) print(f"使用データ: SSDSE-B-2026.csv (2019年度, N=47都道府県)") print(f"Gini係数(一般病院数/万人): {g_hospital:.4f}") print(f"Gini係数(一般診療所数/万人): {g_clinic:.4f}") print(f"\n相関分析 (病院数密度 vs 死亡率): r={r:.3f}, p={p:.4f}") print(f"重回帰 R²={model.rsquared:.3f}, adj.R²={model.rsquared_adj:.3f}") print("\n病院数/万人 上位5:") print(df.nlargest(5, '病院数per万人')[['都道府県', '病院数per万人']].to_string(index=False)) print("\n病院数/万人 下位5:") print(df.nsmallest(5, '病院数per万人')[['都道府県', '病院数per万人']].to_string(index=False)) print("\nDONE: 2019_H1_daijin") |
【重回帰分析結果】
OLS Regression Results
==============================================================================
Dep. Variable: y R-squared: 0.925
Model: OLS Adj. R-squared: 0.916
Method: Least Squares F-statistic: 100.7
Date: Mon, 18 May 2026 Prob (F-statistic): 6.45e-22
Time: 11:23:24 Log-Likelihood: -5.9153
No. Observations: 47 AIC: 23.83
Df Residuals: 41 BIC: 34.93
Df Model: 5
Covariance Type: nonrobust
==============================================================================
coef std err t P>|t| [0.025 0.975]
------------------------------------------------------------------------------
const 3.033e-15 0.043 7.08e-14 1.000 -0.087 0.087
x1 -0.0245 0.053 -0.458 0.649 -0.132 0.083
x2 -0.0500 0.046 -1.092 0.281 -0.142 0.042
x3 0.8987 0.062 14.455 0.000 0.773 1.024
x4 -0.0758 0.065 -1.170 0.249 -0.207 0.055
x5 -0.0862 0.051 -1.706 0.096 -0.188 0.016
==============================================================================
Omnibus: 3.702 Durbin-Watson: 1.615
Prob(Omnibus): 0.157 Jarque-Bera (JB): 3.438
Skew: -0.113 Prob(JB): 0.179
Kurtosis: 4.306 Cond. No. 2.95
==============================================================================
Notes:
[1] Standard Errors assume that the covariance matrix of the errors is correctly specified.
図4 保存完了: 2019_H1_fig4.png
============================================================
分析サマリー
============================================================
使用データ: SSDSE-B-2026.csv (2019年度, N=47都道府県)
Gini係数(一般病院数/万人): 0.2142
Gini係数(一般診療所数/万人): 0.0813
相関分析 (病院数密度 vs 死亡率): r=0.492, p=0.0000
重回帰 R²=0.925, adj.R²=0.916
病院数/万人 上位5:
都道府県 病院数per万人
高知県 1.616595
鹿児島県 1.273408
徳島県 1.263736
大分県 1.146384
宮崎県 1.114206
病院数/万人 下位5:
都道府県 病院数per万人
神奈川県 0.313313
滋賀県 0.353107
愛知県 0.378457
静岡県 0.394197
埼玉県 0.401798
DONE: 2019_H1_daijin{値:.2f}(小数2桁)、{値:,}(3桁区切り)、{値:>10}(右寄せ10桁)など、覚えると出力が一気に整います。1. Gini係数:0(完全平等)〜1(完全不平等)。所得格差だけでなく医療資源・教育資源の地域格差にも使える汎用的な格差指標。
2. Lorenz曲線:Gini係数を「見える化」するグラフ。45度線からの乖離が格差を示す。
3. Pearson相関分析:2変数の線形関係を測る。ただし因果を示さない。交絡因子に要注意。
4. 標準化偏回帰係数:単位が異なる変数を同じスケールで比較できる。「どの変数が最も重要か」を把握するのに有効。
| データ・資料 | 出典・説明 |
|---|---|
| SSDSE-B-2026(都道府県データ) | 統計数理研究所 社会・人口統計体系データセット。本分析では2019年度データを使用。 |
| 一般病院数・一般診療所数 | 厚生労働省 医療施設(動態)調査(SSDSE-Bに収録) |
| 人口・死亡数・高齢化率 | 総務省 人口推計・厚生労働省 人口動態統計(SSDSE-Bに収録) |
| 保健医療費 | 総務省 家計調査(二人以上の世帯)(SSDSE-Bに収録) |
本教育用コードはSSDSE-B-2026の実データのみを使用(合成データ・乱数生成なし)。 図は2019年度の47都道府県実績値に基づく。
統計分析の解釈で初心者がやりがちな勘違いをまとめます。特に「相関と因果の混同」「p値の過信」は研究現場でもよく起きる落とし穴です。本文を読む前にも、読んだ後にも、目を通してみてください。
統計の基本用語を初心者向けに解説します。本文中で見慣れない言葉が出てきたら、ここに戻って確認してください。
統計手法について「何のためか」「結果をどう読むか」を初心者向けに解説します。
この研究をさらに発展させるための3つの方向性を示します。「今回わかったこと(X)」から「次に検証すべき仮説(Y)」を立て、「具体的に何をするか(Z)」まで考えてみましょう。
学んだだけでは身につきません。実際に手を動かすのが最強の学習方法です。本論文のスクリプトをベースに、以下のチャレンジに挑戦してみてください。難易度別に5つ用意しました。
本論文で学んだ手法は、研究の世界だけでなく、行政・企業・NPO の現場でも様々に活用されています。具体的なシーンを紹介します。
この論文を読んで初心者が抱きやすい疑問に、教育的観点から答えます。