このページの分析を自分で再現するには、以下の手順でデータを準備してください。コードの編集は不要です。
data/raw/ フォルダに入れます。html/figures/ に自動保存されます。
日本の死亡率は都道府県によって大きく異なります。高齢化が急速に進む地方では死亡率が高く、 経済的に豊かな都市部では比較的低い傾向があります。しかし、この単純な「高齢化が原因」という説明だけでは、 地域間格差の全体像を把握することはできません。
まず「都道府県別死亡率の規定要因分析高齢化・医療アクセス・経済格差の複合効果」を統計的にとらえることが有効だと考えられる。 その理由は感覚や経験則だけでは、複雑な社会要因の中で「何が本当に効いているか」を見極めにくいからである。 本研究では公開データと統計手法を組み合わせ、この問いに定量的な答えを出すことを目指す。
本論文の特徴は、高齢化・医療アクセス・経済格差という3つの要因を同時に分析モデルに投入し、 それぞれの純粋な(交絡を除いた)効果を標準化偏回帰係数で比較した点です。 さらにLorenz曲線とGini係数を用いて医療格差の不平等構造を視覚化しています。
本分析はSSDSE-B-2026(社会・人口統計体系 都道府県データセット)の 最新年次(2023年度)47都道府県の断面データを用いる。 人口・死亡・医療・雇用・消費支出の各変数を統合して分析モデルを構築した。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | 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 # 図の保存先フォルダを用意(プロジェクトルートから実行する前提) os.makedirs('html/figures', exist_ok=True) # CSV読み込み → 47都道府県 → 最新年度 に絞り込み df_b = pd.read_csv('data/raw/SSDSE-B-2026.csv', 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) latest_year = df_b['年度'].max() df_cross = df_b[df_b['年度'] == latest_year].copy().reset_index(drop=True) print(f"最新年度: {latest_year}, n={len(df_cross)}") |
最新年度: 2023, n=47
import pandas as pd — pandasに pd という短い別名を付ける慣習。以降は pd.read_csv(...) のように呼べます。matplotlib.use('Agg') — グラフを画面表示せずファイルに保存するための設定。サーバーやCIで重宝します。pd.read_csv(..., encoding='cp932', header=1) — SSDSEは日本語Windowsの cp932 配布。header=1 は「2行目をヘッダーに」という意味。df_b['地域コード'].str.match(r'^R\d{5}', na=False) — 正規表現で「R+数字5桁」の行(47都道府県)だけTrueにし、その真偽値で行をフィルタ。df_b['年度'].max() — 最新年度(=2023)を取得して断面データに絞り込み。文字列のままだと辞書順比較になるので astype(int) で数値化しています。f"...{x}..." はf-string。文字列の中に {変数} と書くだけで埋め込めて、{x:.2f} のように書式も指定できます。| 変数 | 定義・算出方法 | データソース | 想定符号 |
|---|---|---|---|
| 死亡率(目的変数) | 死亡数 ÷ 総人口 × 1000(人口千人当たり) | SSDSE-B 死亡数・総人口 | — |
| 高齢化率 (%) | 65歳以上人口 ÷ 総人口 × 100 | SSDSE-B 65歳以上人口 | +(正) |
| 病院数(人口10万対) | 一般病院数 ÷ 総人口 × 100,000 | SSDSE-B 一般病院数 | ±(不明) |
| 有効求人倍率 | 月間有効求人数 ÷ 月間有効求職者数(一般) | SSDSE-B 求人・求職者数 | −(経済活性化 → 死亡率低下) |
| 保健医療費(円) | 二人以上世帯の月間保健医療費支出 | SSDSE-B 家計支出 | −(医療投資 → 死亡率低下) |
| 消費支出(円) | 二人以上世帯の月間消費支出総額 | SSDSE-B 家計支出 | −(豊かさ → 死亡率低下) |
25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 | # 目的変数:死亡率(人口千人当たり) df_cross['死亡率'] = df_cross['死亡数'] / df_cross['総人口'] * 1000 # 高齢化率(65歳以上人口割合 %) df_cross['高齢化率'] = df_cross['65歳以上人口'] / df_cross['総人口'] * 100 # 医療アクセス指標:人口10万人当たり一般病院数 df_cross['病院数_10万対'] = df_cross['一般病院数'] / df_cross['総人口'] * 100000 # 保健医療費(世帯) df_cross['保健医療費'] = df_cross['保健医療費(二人以上の世帯)'] # 有効求人倍率(月間有効求人数 / 月間有効求職者数) df_cross['有効求人倍率'] = df_cross['月間有効求人数(一般)'] / df_cross['月間有効求職者数(一般)'] # 消費支出 df_cross['消費支出'] = df_cross['消費支出(二人以上の世帯)'] # 都道府県名の「県・都・道・府」を省略(表示用) def shorten_pref(name): for suffix in ['道', '都', '府', '県']: if name.endswith(suffix) and len(name) > len(suffix): return name[:-len(suffix)] return name df_cross['pref_short'] = df_cross['都道府県'].apply(shorten_pref) |
print はしません。df_cross に 死亡率・高齢化率・病院数_10万対… など新しい列が追加されただけです。次のステップで describe() で中身を確認します。df['A'] / df['B'] * 1000 — 列同士の四則演算は要素ごと(element-wise)に行われます。forループ不要なのがpandasの強み。df_cross['死亡率'] = ... — 左辺に新しい列名を書くだけで列が追加されます。既存列名なら上書き。def shorten_pref(name): ... — 「東京都 → 東京」のように末尾の都道府県記号を取り除く関数。name[:-len(suffix)] はスライス記法で「末尾n文字を除いた部分」を意味します。.apply(shorten_pref) — Seriesの各要素に関数を適用。同じ長さのSeriesが返ります。s[:-n]「末尾n文字を除く」/s[n:]「先頭n文字を除く」。スライス [start:stop:step] はリスト・タプル・文字列に共通の基本ワザです。| 変数 | 平均 | 標準偏差 | 最小値 | 最大値 |
|---|---|---|---|---|
| 死亡率(‰) | 14.10 | 2.09 | 9.74(沖縄) | 19.17(秋田) |
| 高齢化率(%) | 31.59 | 3.34 | 22.75(沖縄) | 39.06(秋田) |
| 病院数(10万対) | 6.90 | 2.77 | 3.13 | 16.07 |
| 有効求人倍率(倍) | 1.35 | 0.22 | 0.90 | 1.86 |
| 保健医療費(円) | 14,423 | 2,143 | 11,052 | 21,000 |
| 消費支出(円) | 295,856 | 24,144 | 223,423 | 344,092 |
52 53 54 | print("\n変数サマリー:") print(df_cross[['都道府県', '死亡率', '高齢化率', '病院数_10万対', '有効求人倍率', '保健医療費']].describe()) |
変数サマリー:
死亡率 高齢化率 病院数_10万対 有効求人倍率 保健医療費
count 47.000000 47.000000 47.000000 47.000000 47.000000
mean 14.098860 31.585892 6.899454 1.348651 14423.319149
std 2.094861 3.338168 2.766785 0.219321 2143.237495
min 9.743078 22.753088 3.131434 0.897270 11052.000000
25% 12.819427 30.047119 4.919272 1.190760 12619.000000
50% 14.086792 31.783920 5.993691 1.346675 14503.000000
75% 15.590075 34.012991 8.474145 1.506012 15512.500000
max 19.165208 39.059081 16.066066 1.863492 21000.000000df[['列1','列2',...]] — 列名のリストを渡すと複数列を選択したDataFrameが返ります([] が二重なのに注意)。.describe() — 数値列の記述統計を一括計算:件数(count)・平均(mean)・標準偏差(std)・最小・四分位(25/50/75%)・最大。データの素性チェックに必須。df[col](1列)と df[[col1, col2]](複数列)でカッコの数が違います。リストを渡している、と覚えるとミスを減らせます。
死亡率が最も高いのは秋田県(19.17‰)、続いて青森・島根・高知などの地方県。 最も低いのは沖縄県(9.74‰)で、東京・神奈川・滋賀など大都市圏も低い傾向を示します。
重回帰を行う前に、まず各説明変数と死亡率の単変量相関を確認する。この「相関→回帰」の順序が実証分析の基本ステップ。
| 変数 | 相関係数 r | p値 | 判定 |
|---|---|---|---|
| 高齢化率 | +0.972 | p <0.001 | *** 強い正相関 |
| 病院数(10万対) | +0.524 | p <0.001 | *** 中程度の正相関 |
| 有効求人倍率 | +0.308 | p = 0.035 | * 有意な正相関 |
| 保健医療費 | −0.537 | p <0.001 | *** 中程度の負相関 |
| 消費支出 | −0.361 | p = 0.013 | * 有意な負相関 |
56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 | region_colors = { '北海道・東北': '#4e9af1', '関東': '#e05c5c', '中部': '#f0a500', '近畿': '#5cb85c', '中国・四国': '#9b59b6', '九州・沖縄': '#f39c12', } region_map_full = { '北海道': '北海道・東北', '青森県': '北海道・東北', '岩手県': '北海道・東北', '宮城県': '北海道・東北', '秋田県': '北海道・東北', '山形県': '北海道・東北', '福島県': '北海道・東北', '茨城県': '関東', '栃木県': '関東', '群馬県': '関東', '埼玉県': '関東', '千葉県': '関東', '東京都': '関東', '神奈川県': '関東', '新潟県': '中部', '富山県': '中部', '石川県': '中部', '福井県': '中部', '山梨県': '中部', '長野県': '中部', '岐阜県': '中部', '静岡県': '中部', '愛知県': '中部', '三重県': '近畿', '滋賀県': '近畿', '京都府': '近畿', '大阪府': '近畿', '兵庫県': '近畿', '奈良県': '近畿', '和歌山県': '近畿', '鳥取県': '中国・四国', '島根県': '中国・四国', '岡山県': '中国・四国', '広島県': '中国・四国', '山口県': '中国・四国', '徳島県': '中国・四国', '香川県': '中国・四国', '愛媛県': '中国・四国', '高知県': '中国・四国', '福岡県': '九州・沖縄', '佐賀県': '九州・沖縄', '長崎県': '九州・沖縄', '熊本県': '九州・沖縄', '大分県': '九州・沖縄', '宮崎県': '九州・沖縄', '鹿児島県': '九州・沖縄', '沖縄県': '九州・沖縄', } df_cross['地域'] = df_cross['都道府県'].map(region_map_full) df_cross['color'] = df_cross['地域'].map(region_colors) |
print はしません。df_cross に '地域' と 'color' の2列が追加され、次のグラフ作成で「同じ地域は同じ色」で表示できる準備が整いました。region_colors, region_map_full — Pythonの辞書(dict)。「キー:値」の対応表で、文字列キーから値を引きます。df_cross['都道府県'].map(region_map_full) — Seriesの各値を辞書で置き換え。「東京都」→「関東」のように一気に変換できます。.map() は「1対1の置き換え」、.apply() は「関数を当てる」。辞書で置き換えたいときは .map()、ロジックが必要なときは .apply() と使い分けます。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 | df_sorted = df_cross.sort_values('死亡率', ascending=False).reset_index(drop=True) national_avg = df_cross['死亡率'].mean() fig, ax = plt.subplots(figsize=(14, 7)) ax.bar(range(len(df_sorted)), df_sorted['死亡率'], color=df_sorted['color'], edgecolor='white', linewidth=0.5) ax.axhline(national_avg, color='black', linestyle='--', linewidth=1.5, label=f'全国平均 {national_avg:.2f}‰') ax.set_xticks(range(len(df_sorted))) ax.set_xticklabels(df_sorted['pref_short'], rotation=90, fontsize=8) ax.set_ylabel('死亡率(人口千人当たり)', fontsize=12) ax.set_title(f'図1:都道府県別死亡率ランキング({latest_year}年)', fontsize=13, fontweight='bold') ax.set_ylim(0, df_sorted['死亡率'].max() * 1.15) legend_handles = [plt.Rectangle((0, 0), 1, 1, color=c, label=r) for r, c in region_colors.items()] legend_handles.append(plt.Line2D([0], [0], color='black', linestyle='--', linewidth=1.5, label=f'全国平均 {national_avg:.2f}‰')) ax.legend(handles=legend_handles, loc='upper right', fontsize=9, ncol=2) ax.spines['top'].set_visible(False); ax.spines['right'].set_visible(False) plt.tight_layout() fig.savefig('html/figures/2018_H1_fig1.png', dpi=150, bbox_inches='tight') plt.close() |
図1 作成中... → 2018_H1_fig1.png 保存完了
sort_values('死亡率', ascending=False) — 死亡率の降順に並べ替え。reset_index(drop=True) でインデックスを 0,1,2,... に振り直し。fig, ax = plt.subplots(...) — 図全体(fig)と1つの軸(ax)を作る定番。以降 ax に対して .bar(...), .set_title(...) のように操作します。ax.bar(..., color=df_sorted['color']) — 棒の色を都道府県ごとに(地域色で)一括指定。ax.axhline(..., linestyle='--') — 水平点線。全国平均線として使うのが定番。fig.savefig(..., bbox_inches='tight') — 余白を自動で詰めて保存。plt.close() でメモリ解放。[plt.Rectangle(...) for r, c in region_colors.items()] はリスト内包表記。forループでappendする代わりに1行でリストを作れます。単純な統計の比較だけでなく、都道府県間の格差の構造を把握するために、 Lorenz曲線とGini係数を用いて医療アクセス指標の不平等を可視化する。
Lorenz曲線が「完全平等線(45度線)」から離れるほど格差が大きいことを示す。 病院数の曲線が保健医療費の曲線より大きく凹んでおり、 施設格差 > 費用格差であることが視覚的に確認できる。
Gini係数はLorenz曲線と完全平等線の間の面積(格差面積)を、完全不平等時の最大面積で割った値。0(完全平等)〜1(完全不平等)の範囲をとる。
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 | def gini(arr): v = np.sort(arr) n = len(v) cumv = np.cumsum(v) return (n + 1 - 2 * np.sum(cumv) / cumv[-1]) / n health_vals = df_cross['保健医療費'].dropna().values g_health = gini(health_vals) hosp_vals = df_cross['病院数_10万対'].dropna().values g_hosp = gini(hosp_vals) fig, axes = plt.subplots(1, 2, figsize=(12, 5)) # 左右2つのサブプロットの設定をリストにまとめる plot_configs = [ (axes[0], health_vals, '保健医療費(世帯月額・円)', g_health, '#1565C0'), (axes[1], hosp_vals, '一般病院数(人口10万人当たり)', g_hosp, '#00695C'), ] for ax, vals, label, g, color in plot_configs: sorted_vals = np.sort(vals) cum_pop = np.linspace(0, 1, len(sorted_vals) + 1) cum_val = np.concatenate([[0], np.cumsum(sorted_vals) / sorted_vals.sum()]) ax.fill_between(cum_pop, cum_pop, cum_val, alpha=0.3, color=color, label='格差面積') ax.plot(cum_pop, cum_val, color=color, linewidth=2.5, label=f'Lorenz曲線 (Gini={g:.3f})') ax.plot([0, 1], [0, 1], 'k--', linewidth=1.5, label='完全平等線') ax.set_xlabel('都道府県の累積人口割合'); ax.set_ylabel('累積シェア') ax.set_title(f'{label}\nGini係数 = {g:.3f}', fontweight='bold') ax.legend(fontsize=9); ax.set_xlim(0, 1); ax.set_ylim(0, 1); ax.grid(alpha=0.3) ax.spines['top'].set_visible(False); ax.spines['right'].set_visible(False) plt.tight_layout() fig.savefig('html/figures/2018_H1_fig2.png', dpi=150, bbox_inches='tight') plt.close() print(f"保健医療費Gini={g_health:.3f}, 病院数Gini={g_hosp:.3f}") |
図2 作成中... → 2018_H1_fig2.png 保存完了 (保健医療費Gini=0.082, 病院数Gini=0.214) 保健医療費Gini=0.082, 病院数Gini=0.214
def gini(arr): ... — ソート → 累積和 → 公式に代入するだけのシンプル実装。np.sort(arr) / np.cumsum(v) — NumPyのソートと累積和。Lorenz曲線は「下位X%の人が全体の何%を持つか」を描くので、累積和が必須。plt.subplots(1, 2, ...) — 横並びに2つの軸を作成。axes[0] と axes[1] で個別アクセス。for ax, vals, label, g, color in [...] — タプルのリストをアンパックしながらループ。「同じ処理を2つのデータに繰り返す」ときの定番パターン。ax.fill_between(cum_pop, cum_pop, cum_val, alpha=0.3) — 完全平等線とLorenz曲線の間を半透明で塗りつぶし、格差の面積を視覚化。np.linspace(0, 1, n)「0〜1を等間隔でn個」/np.concatenate([[0], arr])「先頭に0を足して連結」。Lorenz曲線の原点(0,0)を含めるための定石です。
有効求人倍率と死亡率の間には、正の相関(r=+0.308)が観察される。 これは一見直感に反する結果に見えるが、交絡因子(高齢化率)の存在で説明できる。 高齢化が進む地方では求職者数が多く求人倍率が下がりやすく、死亡率も高い傾向がある。 一方、人口構成が若い都市部(東京・神奈川)は経済活発で求人倍率が高い。
「AとBが相関する」は「AがBの原因」を意味しない。第3の変数C(交絡因子)がAとBの両方と関連していると、見かけ上の相関が生じる(疑似相関)。これを解決するのが重回帰分析による「交絡の制御」。
148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 | x_var = df_cross['有効求人倍率'].values y_var = df_cross['死亡率'].values r_val, p_val = stats.pearsonr(x_var, y_var) slope, intercept, _, _, _ = stats.linregress(x_var, y_var) x_line = np.linspace(x_var.min(), x_var.max(), 100) y_line = slope * x_line + intercept fig, ax = plt.subplots(figsize=(11, 8)) for _, row in df_cross.iterrows(): ax.scatter(row['有効求人倍率'], row['死亡率'], color=row['color'], s=60, zorder=3, alpha=0.85, edgecolors='white', linewidth=0.5) ax.annotate(row['pref_short'], xy=(row['有効求人倍率'], row['死亡率']), xytext=(3, 3), textcoords='offset points', fontsize=7, color='#333333') ax.plot(x_line, y_line, 'k-', linewidth=1.8, alpha=0.7, label=f'回帰直線 (r={r_val:.3f}, p={p_val:.4f})') ax.set_xlabel('有効求人倍率', fontsize=12) ax.set_ylabel('死亡率(人口千人当たり)', fontsize=12) ax.set_title(f'図3:有効求人倍率と死亡率({latest_year}年) r={r_val:.3f}, p={p_val:.4f}', fontweight='bold') ax.grid(alpha=0.25) ax.spines['top'].set_visible(False); ax.spines['right'].set_visible(False) plt.tight_layout() fig.savefig('html/figures/2018_H1_fig3.png', dpi=150, bbox_inches='tight') plt.close() print(f"r={r_val:.3f}, p={p_val:.4f}") |
図3 作成中... → 2018_H1_fig3.png 保存完了 (r=0.308, p=0.0352) r=0.308, p=0.0352
stats.pearsonr(x, y) — Pearson相関係数 r と p値を同時に返します。r, p = ... と2つの値を同時に受け取れるのがPython。stats.linregress(x, y) — 単回帰の傾き・切片・r値・p値・標準誤差を返します。使わない値は _(捨て変数)で受け取り。for _, row in df_cross.iterrows() — DataFrameを1行ずつ取り出すループ。点とラベルを1点ずつ描きたいときに使用(大規模データでは遅いので注意)。ax.annotate(text, xy=..., xytext=(3,3), textcoords='offset points') — 点から右上に少しズラしてテキストを描画。ラベル重なりを軽減できます。slope, intercept, _, _, _ = stats.linregress(...) の _ は「受け取るけど使わない」という慣習的な変数名。複数戻り値のうち一部だけ使いたいときの定番テクです。5つの説明変数(高齢化率・病院数・有効求人倍率・保健医療費・消費支出)を同時に投入した OLS重回帰分析を行い、各変数の純粋な(交絡を除いた)効果を 標準化偏回帰係数で比較した。
| 説明変数 | 標準化β | 95% CI | p値 | 有意性 | 解釈 |
|---|---|---|---|---|---|
| 高齢化率 | +0.929 | [+0.840, +1.019] | p <0.001 | *** | 最も強力な正の規定要因 |
| 病院数(10万対) | −0.025 | [−0.109, +0.059] | p = 0.552 | n.s. | 高齢化制御後は非有意 |
| 有効求人倍率 | +0.028 | [−0.048, +0.103] | p = 0.463 | n.s. | 交絡除去後は非有意 |
| 保健医療費 | −0.042 | [−0.140, +0.057] | p = 0.395 | n.s. | 単変量では有意だが制御後は弱まる |
| 消費支出 | −0.057 | [−0.147, +0.034] | p = 0.212 | n.s. | 制御後は非有意 |
通常の回帰係数は変数の単位に依存するため、異なる変数(%と円)を直接比較できない。標準化(z-score変換)した変数で回帰すると、係数βを直接比較して「どの変数が最も重要か」を判断できる。
179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 | from sklearn.preprocessing import StandardScaler df_reg = df_cross[['死亡率', '高齢化率', '病院数_10万対', '有効求人倍率', '保健医療費', '消費支出']].dropna() X_cols = ['高齢化率', '病院数_10万対', '有効求人倍率', '保健医療費', '消費支出'] y_col = '死亡率' # 標準化(各列を平均0・分散1に揃える) scaler = StandardScaler() X_scaled = scaler.fit_transform(df_reg[X_cols]) y_scaled = (df_reg[y_col].values - df_reg[y_col].mean()) / df_reg[y_col].std() # 切片項を追加してOLS推定 X_sm = sm.add_constant(X_scaled) model = sm.OLS(y_scaled, X_sm).fit() print(model.summary()) |
OLS Regression Results
==============================================================================
Dep. Variable: y R-squared: 0.952
Model: OLS Adj. R-squared: 0.946
Method: Least Squares F-statistic: 162.3
Prob (F-statistic): 6.83e-26
No. Observations: 47
Df Residuals: 41
Df Model: 5
==============================================================================
coef std err t P>|t| [0.025 0.975]
------------------------------------------------------------------------------
const 1.291e-15 0.034 3.81e-14 1.000 -0.068 0.068
x1 0.9293 0.044 21.030 0.000 0.840 1.019
x2 -0.0250 0.042 -0.600 0.552 -0.109 0.059
x3 0.0277 0.037 0.741 0.463 -0.048 0.103
x4 -0.0419 0.049 -0.860 0.395 -0.140 0.057
x5 -0.0567 0.045 -1.267 0.212 -0.147 0.034
==============================================================================StandardScaler().fit_transform(X) — 各列を「平均0・分散1」に変換。単位の違う変数同士のβを同じものさしで比較できるようになります。sm.add_constant(X_scaled) — 切片項(定数1の列)を先頭に追加。statsmodelsは明示的に切片を入れる必要があります。sm.OLS(y, X).fit() — 最小二乗法でモデルを推定。model.params, model.pvalues, model.conf_int() で結果にアクセス。X_cols の順(高齢化率・病院数・有効求人倍率・保健医療費・消費支出)。x1(高齢化率)のβ=0.929 が圧倒的に大きく、p=0.000で有意。(x - mean) / std。これで「単位を無視して、変数間で影響の大きさを比較できる」ようになります。197 198 199 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 235 236 237 238 239 | betas = model.params[1:] # 切片を除く pvalues = model.pvalues[1:] conf_int = model.conf_int()[1:] var_labels = ['高齢化率 (%)', '病院数\n(10万対)', '有効求人倍率', '保健医療費\n(円)', '消費支出\n(円)'] fig, ax = plt.subplots(figsize=(9, 5)) bar_colors = ['#1565C0' if b > 0 else '#C62828' for b in betas] y_pos = np.arange(len(betas)) ax.barh(y_pos, betas, color=bar_colors, alpha=0.8, height=0.55) # 95% 信頼区間のエラーバー for i, (lo, hi) in enumerate(conf_int): ax.plot([lo, hi], [i, i], 'k-', linewidth=2, zorder=4) ax.plot([lo, lo], [i - 0.12, i + 0.12], 'k-', linewidth=2) ax.plot([hi, hi], [i - 0.12, i + 0.12], 'k-', linewidth=2) # 有意性マーク for i, p in enumerate(pvalues): if p < 0.001: mark = '***' elif p < 0.01: mark = '**' elif p < 0.05: mark = '*' else: mark = 'n.s.' x_off = betas[i] + (0.03 if betas[i] >= 0 else -0.03) ha = 'left' if betas[i] >= 0 else 'right' ax.text(x_off, i, mark, va='center', ha=ha, fontsize=11, color='#C62828' if mark != 'n.s.' else '#888888', fontweight='bold') ax.axvline(0, color='black', linewidth=1.0) ax.set_yticks(y_pos); ax.set_yticklabels(var_labels, fontsize=11) ax.set_xlabel('標準化偏回帰係数(β)', fontsize=12) ax.set_title(f'図4:重回帰の標準化偏回帰係数(n=47, {latest_year}年)', fontweight='bold') ax.grid(axis='x', alpha=0.3) ax.spines['top'].set_visible(False); ax.spines['right'].set_visible(False) ax.text(0.98, 0.04, f'R² = {model.rsquared:.3f} adj.R² = {model.rsquared_adj:.3f}', transform=ax.transAxes, fontsize=10, ha='right', bbox=dict(boxstyle='round,pad=0.3', facecolor='#EFF3FF', alpha=0.8)) plt.tight_layout() fig.savefig('html/figures/2018_H1_fig4.png', dpi=150, bbox_inches='tight') plt.close() |
図4 作成中... → 2018_H1_fig4.png 保存完了
model.params[1:] — 切片を除いた5つのβ係数を取得(先頭は定数項なのでスキップ)。['#1565C0' if b > 0 else '#C62828' for b in betas] — リスト内包表記+三項演算子。正なら青、負なら赤の色リストを1行で作成。ax.plot(横線・左端の縦短線・右端の縦短線)で95%信頼区間の「エラーバー」を手描き。if p < 0.001: '***' ... — if/elifで ***/**/*/n.s. を判定して描画。ax.text(0.98, 0.04, ..., transform=ax.transAxes) — 軸座標系(0〜1)での位置指定。データ値に関係なく右下にR²を表示できます。x if cond else y は三項演算子(条件式)。リスト内包表記と組み合わせると、forとifを1行で書けて読みやすい。241 242 243 244 245 246 247 248 249 250 251 252 253 | print("\n=== Pearson相関係数(vs 死亡率)===") for col in X_cols: r, p = stats.pearsonr(df_cross[col].dropna(), df_cross['死亡率'][df_cross[col].notna()]) if p < 0.001: sig = '***' elif p < 0.01: sig = '**' elif p < 0.05: sig = '*' else: sig = 'n.s.' print(f" {col:20s}: r={r:+.4f}, p={p:.4f} {sig}") print(f"\nGini係数:") print(f" 保健医療費 : {g_health:.3f}") print(f" 病院数(10万対) : {g_hosp:.3f}") |
=== Pearson相関係数(vs 死亡率)=== 高齢化率 : r=+0.9720, p=0.0000 *** 病院数_10万対 : r=+0.5243, p=0.0002 *** 有効求人倍率 : r=+0.3079, p=0.0352 * 保健医療費 : r=-0.5366, p=0.0001 *** 消費支出 : r=-0.3614, p=0.0126 * Gini係数: 保健医療費 : 0.082 病院数(10万対) : 0.214
for col in X_cols — リスト X_cols の各要素(変数名)を順に取り出してループ。df_cross[col].dropna() / df_cross['死亡率'][df_cross[col].notna()] — 欠損のある行をxとyの両方から揃えて除外(揃えないと長さが合わずエラー)。f"{col:20s}: r={r:+.4f}, p={p:.4f}" — f-stringの書式指定。:20s「左寄せ20桁の文字列」、:+.4f「符号付き小数点以下4桁」。表のように整列できます。{値:書式} はC言語の printf 由来。:.2f(小数2桁)、:,(3桁区切り)、:>10(右寄せ10桁)など。覚えると出力が一気に整います。SSDSE-B(2023年)の47都道府県データを用いた重回帰分析から、以下の知見が得られた。
| データ | 出典・説明 |
|---|---|
| SSDSE-B-2026(都道府県データ) | 統計数理研究所 SSDSE(社会・人口統計体系)。47都道府県の多分野統計を収録。 |
| 使用変数 | 死亡数、総人口、65歳以上人口、一般病院数、月間有効求人数・求職者数、保健医療費、消費支出(二人以上世帯) |
| 分析年度 | 2023年(SSDSE-B-2026の最新年次)、n=47都道府県の断面分析 |
本教育用コードは SSDSE-B-2026.csv の実データのみを使用(合成データ・乱数シード不使用)。
統計分析の解釈で初心者がやりがちな勘違いをまとめます。特に「相関と因果の混同」「p値の過信」は研究現場でもよく起きる落とし穴です。本文を読む前にも、読んだ後にも、目を通してみてください。
統計の基本用語を初心者向けに解説します。本文中で見慣れない言葉が出てきたら、ここに戻って確認してください。
統計手法について「何のためか」「結果をどう読むか」を初心者向けに解説します。
この研究をさらに発展させるための3つの方向性を示します。「今回わかったこと(X)」から「次に検証すべき仮説(Y)」を立て、「具体的に何をするか(Z)」まで考えてみましょう。
学んだだけでは身につきません。実際に手を動かすのが最強の学習方法です。本論文のスクリプトをベースに、以下のチャレンジに挑戦してみてください。難易度別に5つ用意しました。
本論文で学んだ手法は、研究の世界だけでなく、行政・企業・NPO の現場でも様々に活用されています。具体的なシーンを紹介します。
この論文を読んで初心者が抱きやすい疑問に、教育的観点から答えます。