このページの分析を自分で再現するには、以下の手順でデータを準備してください。コードの編集は不要です。
data/raw/ フォルダに入れます。html/figures/ に自動保存されます。
都道府県間の安全・健康指標には大きな地域差がある。本研究は、死亡率(人口千人あたり)を安全・健康水準の代理指標として、経済的活力(有効求人倍率)・都市化(人口密度)・医療アクセス(人口10万対病院数)・消費水準(消費支出)・高齢化(高齢化率)の5変数を説明変数とし、重回帰分析(OLS)によって規定要因を同定する。
まず「都道府県別交通事故・安全指標の規定要因経済・都市化・医療アクセスの複合分析」を統計的にとらえることが有効だと考えられる。 その理由は感覚や経験則だけでは、複雑な社会要因の中で「何が本当に効いているか」を見極めにくいからである。 本研究では公開データと統計手法を組み合わせ、この問いに定量的な答えを出すことを目指す。
SSDSE-B-2026 SSDSE-E-2026 重回帰分析(OLS) Pearson相関分析 標準化偏回帰係数
SSDSE(社会・人口統計体系)-B(2023年度・47都道府県)および SSDSE-E(最新横断面・47都道府県)を使用。欠損値なし・n=47の完全データ。
| 変数の役割 | 変数名 | 出典 | 想定メカニズム |
|---|---|---|---|
| 目的変数 | 死亡率(人口千人あたり) | SSDSE-B (死亡数 / 総人口 × 1000) |
安全・健康水準の複合指標 |
| 説明変数 | 高齢化率(%) | SSDSE-B | 正(高齢者比率が高いほど死亡率上昇) |
| 有効求人倍率 | SSDSE-B (有効求人数 / 有効求職者数) |
負(経済活力高 → 健康水準向上か) | |
| 人口密度(人/km²) | SSDSE-E (総人口 / 可住地面積) |
負(都市部は医療アクセス良) | |
| 消費支出(万円/月) | SSDSE-B (二人以上の世帯) |
負(豊かさ → 健康投資増) | |
| 人口10万対病院数 | SSDSE-B (一般病院数 / 総人口 × 10万) |
不明(医療アクセス指標) |
| 変数 | 平均 | 標準偏差 | 最小値 | 最大値 |
|---|---|---|---|---|
| 死亡率(‰) | 14.10 | 2.09 | 9.74(東京) | 19.17(秋田) |
| 高齢化率(%) | 31.59 | 3.34 | 22.75(沖縄) | 39.06(秋田) |
| 有効求人倍率 | 1.349 | 0.219 | 0.897(沖縄) | 1.863(福井) |
| 人口密度(人/km²) | 1,328 | 1,797 | 222(北海道) | 9,924(東京) |
| 消費支出(万円/月) | 29.59 | 2.41 | 22.34(沖縄) | 34.41(神奈川) |
| 人口10万対病院数 | 6.90 | 2.77 | 3.13(神奈川) | 16.07(高知) |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | 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 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' DATA_E = 'data/raw/SSDSE-E-2026.csv' os.makedirs(FIG_DIR, exist_ok=True) 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("SSDSE-B columns:", df_b.columns.tolist()) print("Years available:", sorted(df_b['年度'].unique())) |
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)。pd.read_csv(...) でCSVを読み込みます。encoding='cp932' は日本語Windows由来の文字コード、header=1 は「2行目を列名として使う」。df['地域コード'].str.match(r'^R\d{5}', ...) — 正規表現で「R+数字5桁」の行(47都道府県)だけTrueにし、真偽値で行をフィルタ。.astype(int) — 列を整数に変換(年度などを数値比較するため)。f"...{x}..." はf-string。文字列の中に {変数} と書くだけで埋め込めて、{x:.2f} のように書式も指定できます。26 27 28 29 30 31 32 33 34 35 36 37 | # 最新年(2023年)データ抽出 latest_year = df_b['年度'].max() d = df_b[df_b['年度'] == latest_year].copy().reset_index(drop=True) print(f"\n{latest_year}年 都道府県数: {len(d)}") # SSDSE-E(横断面・都道府県別) df_e_raw = pd.read_csv(DATA_E, encoding='cp932', header=0) col_names_e = df_e_raw.iloc[1].tolist() df_e = df_e_raw.iloc[2:].copy() df_e.columns = col_names_e df_e = df_e[df_e['地域コード'].str.match(r'R\d{5}', na=False)].copy() df_e = df_e[df_e['地域コード'] != 'R00000'].copy().reset_index(drop=True) |
SSDSE-B columns: ['年度', '地域コード', '都道府県', '総人口', '総人口(男)', '総人口(女)', '日本人人口', '日本人人口(男)', '日本人人口(女)', '15歳未満人口', '15歳未満人口(男)', '15歳未満人口(女)', '15~64歳人口', '15~64歳人口(男)', '15~64歳人口(女)', '65歳以上人口', '65歳以上人口(男)', '65歳以上人口(女)', '出生数', '出生数(男)', '出生数(女)', '合計特殊出生率', '死亡数', '死亡数(男)', '死亡数(女)', '転入者数(日本人移動者)', '転入者数(日本人移動者)(男)', '転入者数(日本人移動者)(女)', '転出者数(日本人移動者)', '転出者数(日本人移動者)(男)', '転出者数(日本人移動者)(女)', '婚姻件数', '離婚件数', '年平均気温', '最高気温(日最高気温の月平均の最高値)', '最低気温(日最低気温の月平均の最低値)', '降水日数(年間)', '降水量(年間)', '着工建築物数', '着工建築物床面積', '旅館営業施設数(ホテルを含む)', '旅館営業施設客室数(ホテルを含む)', '標準価格(平均価格)(住宅地)', '標準価格(平均価格)(商業地)', '幼稚園数', '幼稚園教員数', '幼稚園在園者数', '小学校数', '小学校教員数', '小学校児童数', '中学校数', '中学校教員数', '中学校生徒数', '中学校卒業者数', '中学校卒業者のうち進学者数', '高等学校数', '高等学校教員数', '高等学校生徒数', '高等学校卒業者数', '高等学校卒業者のうち進学者数', '短期大学数', '大学数', '短期大学教員数', '大学教員数', '短期大学学生数', '大学学生数', '短期大学卒業者数', '短期大学卒業者のうち進学者数', '大学卒業者数', '大学卒業者のうち進学者数', '専修学校数', '各種学校数', '専修学校生徒数', '各種学校生徒数', '新規求職申込件数(一般)', '月間有効求職者数(一般)', '月間有効求人数(一般)', '充足数(一般)', '就職件数(一般)', '一般旅券発行件数', '延べ宿泊者数', '外国人延べ宿泊者数', '着工新設住宅戸数', '着工新設持家数', '着工新設貸家数', '着工新設分譲住宅数', '着工新設住宅床面積', '着工新設持家床面積', '着工新設分譲住宅床面積', '着工新設貸家床面積', 'ごみ総排出量(総量)', '1人1日当たりの排出量', 'ごみのリサイクル率', '一般病院数', '一般診療所数', '歯科診療所数', '保育所等数', '保育所等定員数', '保育所等利用待機児童数', '保育所等在所児数', '保育所等保育士数', '消費支出(二人以上の世帯)', '食料費(二人以上の世帯)', '住居費(二人以上の世帯)', '光熱・水道費(二人以上の世帯)', '家具・家事用品費(二人以上の世帯)', '被服及び履物費(二人以上の世帯)', '保健医療費(二人以上の世帯)', '交通・通信費(二人以上の世帯)', '教育費(二人以上の世帯)', '教養娯楽費(二人以上の世帯)', 'その他の消費支出(二人以上の世帯)'] Years available: [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)] 2023年 都道府県数: 47
pd.read_csv(...) でCSVを読み込みます。encoding='cp932' は日本語Windows由来の文字コード、header=1 は「2行目を列名として使う」。df['地域コード'].str.match(r'^R\d{5}', ...) — 正規表現で「R+数字5桁」の行(47都道府県)だけTrueにし、真偽値で行をフィルタ。df['A'] / df['B'] — pandasの列同士の四則演算は要素ごと(element-wise)。forループ不要なのが強み。38 39 40 41 42 43 44 45 46 47 48 49 50 51 | def normalize_pref(name): name = str(name).strip() if name == '北海道': return '北海道' for s in ['都', '道', '府', '県']: if name.endswith(s): return name[:-1] return name d['pref_key'] = d['都道府県'].apply(normalize_pref) df_e['pref_key'] = df_e['都道府県'].apply(normalize_pref) print("\nSSDSE-B 都道府県 sample:", d['pref_key'].tolist()[:5]) print("SSDSE-E 都道府県 sample:", df_e['pref_key'].tolist()[:5]) |
SSDSE-B 都道府県 sample: ['北海道', '青森', '岩手', '宮城', '秋田'] SSDSE-E 都道府県 sample: ['北海道', '青森', '岩手', '宮城', '秋田']
df['A'] / df['B'] — pandasの列同士の四則演算は要素ごと(element-wise)。forループ不要なのが強み。52 53 54 55 56 | df_e['可住地面積_num'] = pd.to_numeric(df_e['可住地面積'], errors='coerce') df_e['総人口_e_num'] = pd.to_numeric(df_e['総人口'], errors='coerce') # 説明変数5: 人口密度(人/km²) 可住地面積は ha 単位 → / 100 で km² df_e['人口密度'] = df_e['総人口_e_num'] / (df_e['可住地面積_num'] / 100) |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。[式 for x in リスト] はリスト内包表記。forループでappendする代わりに1行でリストを作れます。57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 | region_map = { '北海道': '北海道・東北', '青森': '北海道・東北', '岩手': '北海道・東北', '宮城': '北海道・東北', '秋田': '北海道・東北', '山形': '北海道・東北', '福島': '北海道・東北', '茨城': '関東', '栃木': '関東', '群馬': '関東', '埼玉': '関東', '千葉': '関東', '東京': '関東', '神奈川': '関東', '新潟': '中部', '富山': '中部', '石川': '中部', '福井': '中部', '山梨': '中部', '長野': '中部', '岐阜': '中部', '静岡': '中部', '愛知': '中部', '三重': '近畿', '滋賀': '近畿', '京都': '近畿', '大阪': '近畿', '兵庫': '近畿', '奈良': '近畿', '和歌山': '近畿', '鳥取': '中国・四国', '島根': '中国・四国', '岡山': '中国・四国', '広島': '中国・四国', '山口': '中国・四国', '徳島': '中国・四国', '香川': '中国・四国', '愛媛': '中国・四国', '高知': '中国・四国', '福岡': '九州・沖縄', '佐賀': '九州・沖縄', '長崎': '九州・沖縄', '熊本': '九州・沖縄', '大分': '九州・沖縄', '宮崎': '九州・沖縄', '鹿児島': '九州・沖縄', '沖縄': '九州・沖縄' } region_colors = { '北海道・東北': '#4e9af1', '関東': '#e05c5c', '中部': '#f0a500', '近畿': '#5cb85c', '中国・四国': '#9b59b6', '九州・沖縄': '#f39c12' } df_merge['地域'] = df_merge['pref_key'].map(region_map) df_merge['地域色'] = df_merge['地域'].map(region_colors) |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。x if cond else y は三項演算子。リスト内包表記と組み合わせると、forとifを1行で書けます。76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 | vars_analysis = ['死亡率', '高齢化率', '有効求人倍率', '人口密度', '消費支出_万円', '人口10万対病院数'] var_labels = { '死亡率': '死亡率\n(‰)', '高齢化率': '高齢化率\n(%)', '有効求人倍率': '有効求人\n倍率', '人口密度': '人口密度\n(人/km²)', '消費支出_万円': '消費支出\n(万円/月)', '人口10万対病院数': '病院数\n(10万対)', } print("\n=== 記述統計 ===") print(df_merge[vars_analysis].describe().round(3).to_string()) # Pearson相関行列 print("\n=== Pearson相関行列 ===") corr_mat = df_merge[vars_analysis].corr() print(corr_mat.round(3).to_string()) |
=== 記述統計 ===
死亡率 高齢化率 有効求人倍率 人口密度 消費支出_万円 人口10万対病院数
count 47.000 47.000 47.000 47.000 47.000 47.000
mean 14.099 31.586 1.349 1328.481 29.586 6.899
std 2.095 3.338 0.219 1796.896 2.414 2.767
min 9.743 22.753 0.897 222.249 22.342 3.131
25% 12.819 30.047 1.191 576.396 27.951 4.919
50% 14.087 31.784 1.347 750.514 30.065 5.994
75% 15.590 34.013 1.506 1226.349 30.750 8.474
max 19.165 39.059 1.863 9923.776 34.409 16.066
=== Pearson相関行列 ===
死亡率 高齢化率 有効求人倍率 人口密度 消費支出_万円 人口10万対病院数
死亡率 1.000 0.972 0.308 -0.613 -0.361 0.524
高齢化率 0.972 1.000 0.287 -0.671 -0.306 0.543
有効求人倍率 0.308 0.287 1.000 -0.188 0.000 0.115
人口密度 -0.613 -0.671 -0.188 1.000 0.244 -0.349
消費支出_万円 -0.361 -0.306 0.000 0.244 1.000 -0.366
人口10万対病院数 0.524 0.543 0.115 -0.349 -0.366 1.000.describe() — 件数・平均・標準偏差・四分位・最大/最小を一括計算。データの素性チェックに必須。df[col](1列)と df[[col1, col2]](複数列)でカッコの数が違います。リストを渡していると覚えるとミスを減らせます。47都道府県の死亡率(人口千人あたり)を降順に並べ、地域ブロック別に色分けした。全国平均(14.10‰)を破線で示す。
棒グラフから「北東地方ほど死亡率が高い」という相関は観察できるが、「北東地方だから死亡率が高い」という因果関係は示せない。死亡率が高い真の原因は「高齢化率の高さ」であり、地域は単なる代理変数にすぎない可能性が高い。この区別は統計分析の基本中の基本。
94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 | df_sorted = df_merge.sort_values('死亡率', ascending=False).reset_index(drop=True) colors_bar = [region_colors[r] for r in df_sorted['地域']] mean_val = df_merge['死亡率'].mean() fig1, ax1 = plt.subplots(figsize=(15, 7)) ax1.bar(range(len(df_sorted)), df_sorted['死亡率'], color=colors_bar, width=0.7, edgecolor='white', linewidth=0.4) ax1.axhline(mean_val, color='#333', linewidth=2.0, linestyle='--', zorder=5) ax1.set_xticks(range(len(df_sorted))) ax1.set_xticklabels(df_sorted['pref_key'], rotation=90, fontsize=8.5) ax1.set_ylabel('死亡率(人口千人あたり)', fontsize=11) ax1.set_title(f'都道府県別 死亡率ランキング({latest_year}年・47都道府県)\n' '高齢化・医療アクセス・経済水準の複合指標', fontsize=13, fontweight='bold', pad=12) ax1.set_xlim(-0.7, len(df_sorted) - 0.3) ax1.set_ylim(0, df_sorted['死亡率'].max() * 1.15) ax1.spines['top'].set_visible(False) ax1.spines['right'].set_visible(False) ax1.yaxis.grid(True, alpha=0.3) ax1.set_axisbelow(True) |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。fig, ax = plt.subplots(...) — 図全体(fig)と軸(ax)を作る定番。以降は ax.bar(...) 等で操作。sort_values('列名', ascending=False) — 指定列で並べ替え(降順)。ax.axhline / ax.axvline — 水平/垂直の点線。平均線や基準線として定番。np.cumsum(arr) は累積和、np.linspace(a, b, n) は「aからbを等間隔でn個」。NumPyの定石です。115 116 117 118 119 120 121 122 123 124 125 126 127 128 | # 全国平均ラベル ax1.text(len(df_sorted) - 1, mean_val + 0.2, f'全国平均\n{mean_val:.2f}‰', ha='right', va='bottom', fontsize=9, color='#333') # 地域凡例 legend_els = [Patch(facecolor=c, label=r) for r, c in region_colors.items()] legend_els.append(plt.Line2D([0], [0], color='#333', linewidth=2.0, linestyle='--', label=f'全国平均 {mean_val:.2f}‰')) ax1.legend(handles=legend_els, fontsize=9, loc='upper right', framealpha=0.9) plt.tight_layout() fig1.savefig(os.path.join(FIG_DIR, '2019_H5_1_fig1.png'), bbox_inches='tight') plt.close(fig1) print("\nFig1 saved.") |
Fig1 saved.
{値:.2f}(小数2桁)、{値:,}(3桁区切り)、{値:>10}(右寄せ10桁)など、覚えると出力が一気に整います。有効求人倍率(横軸)と死亡率(縦軸)の散布図に、47都道府県のラベルと回帰直線を重ねて表示した。
| 変数 | Pearson r | p値 | 解釈 |
|---|---|---|---|
| 高齢化率 | +0.972 | <0.001 | 最も強い正の相関 |
| 人口密度 | -0.613 | <0.001 | 都市部ほど低い死亡率 |
| 人口10万対病院数 | +0.524 | <0.001 | 農村部は病院が多い(=高齢者多い) |
| 消費支出 | -0.361 | 0.013 | 豊かなほど低い死亡率 |
| 有効求人倍率 | +0.308 | 0.035 | 交絡あり(高齢化率と混同) |
Pearson相関係数 r は2変数間の線形関係の強さを示す。|r|≥0.5 を「強い相関」、|r|≥0.3 を「中程度」、|r|≥0.1 を「弱い相関」とするのが一般的(Cohen, 1988)。しかし、n=47 では |r|≥0.29 程度で p<0.05 となるため、統計的有意性と実質的な意味を区別する必要がある。
130 131 132 133 134 135 136 137 | fig2, ax2 = plt.subplots(figsize=(11, 7)) for region, color in region_colors.items(): mask = df_merge['地域'] == region ax2.scatter(df_merge.loc[mask, '有効求人倍率'], df_merge.loc[mask, '死亡率'], color=color, s=65, alpha=0.85, edgecolors='white', linewidths=0.5, label=region, zorder=3) |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。fig, ax = plt.subplots(...) — 図全体(fig)と軸(ax)を作る定番。以降は ax.bar(...) 等で操作。{値:.2f}(小数2桁)、{値:,}(3桁区切り)、{値:>10}(右寄せ10桁)など、覚えると出力が一気に整います。138 139 140 141 142 143 | # 都道府県ラベル for _, row in df_merge.iterrows(): ax2.annotate(row['pref_key'], (row['有効求人倍率'], row['死亡率']), fontsize=6.5, ha='left', va='bottom', xytext=(2, 2), textcoords='offset points', color='#333') |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。for _, row in df.iterrows() — DataFrameを1行ずつ取り出すループ。1点ずつ描画したいときに使用。plt.subplots(figsize=(W, H)) で図サイズ指定、fig.savefig(..., bbox_inches='tight') で余白を自動で詰めて保存。144 145 146 147 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 | # 回帰直線 x_sc = df_merge['有効求人倍率'].values y_sc = df_merge['死亡率'].values r_val, p_val = stats.pearsonr(x_sc, y_sc) slope, intercept, _, _, _ = stats.linregress(x_sc, y_sc) x_line = np.linspace(x_sc.min() - 0.05, x_sc.max() + 0.05, 200) ax2.plot(x_line, slope * x_line + intercept, color='#333', linewidth=1.8, linestyle='--', zorder=2) p_str = f'p = {p_val:.4f}' if p_val >= 0.001 else 'p < 0.001' ax2.text(0.05, 0.95, f'r = {r_val:.3f}\n{p_str}', transform=ax2.transAxes, fontsize=11, va='top', ha='left', bbox=dict(boxstyle='round,pad=0.4', facecolor='white', edgecolor='#aaa', alpha=0.9)) ax2.set_xlabel('有効求人倍率(月間有効求人数 / 月間有効求職者数)', fontsize=11) ax2.set_ylabel('死亡率(人口千人あたり)', fontsize=11) ax2.set_title(f'有効求人倍率 vs 死亡率({latest_year}年・47都道府県)\n' '経済的活力と健康・安全指標の地域差', fontsize=13, fontweight='bold', pad=12) legend_els2 = [Patch(facecolor=c, label=r) for r, c in region_colors.items()] ax2.legend(handles=legend_els2, fontsize=9, loc='lower right', framealpha=0.9) ax2.spines['top'].set_visible(False) ax2.spines['right'].set_visible(False) plt.tight_layout() fig2.savefig(os.path.join(FIG_DIR, '2019_H5_1_fig2.png'), bbox_inches='tight') plt.close(fig2) print("Fig2 saved.") |
Fig2 saved.
stats.pearsonr(x, y) — Pearson相関係数 r と p値を同時に返します。stats.linregress(x, y) — 単回帰の傾き・切片・r値・p値・標準誤差を返します。使わない値は _ で受け取り。.dropna() は欠損行を除去、.copy() は独立したコピーを作る。pandasで警告を防ぐ定石。6変数間の Pearson相関行列をヒートマップで可視化した。有意マーク(*, **, ***)を各セルに表示。
| 変数ペア | 相関 r | 有意性 | 解釈 |
|---|---|---|---|
| 死亡率 × 高齢化率 | +0.972 | *** | 最強の正相関・交絡の主因 |
| 死亡率 × 人口密度 | -0.613 | *** | 都市化は死亡率を下げる |
| 高齢化率 × 人口密度 | -0.671 | *** | 都市部は若い(多重共線性リスク) |
| 病院数 × 高齢化率 | +0.543 | *** | 農村は病院密度高いが高齢者も多い |
| 消費支出 × 病院数 | -0.366 | * | 豊かな地域は大病院に集約 |
| 有効求人倍率 × 消費支出 | 0.000 | n.s. | 無相関 |
高齢化率は死亡率の最強の規定因子(r=0.972)であり、他の変数との交絡を引き起こす「潜在変数」でもある。例えば「農村は有効求人倍率が高い」かつ「農村は高齢化率が高い」という両方の関係があるため、有効求人倍率と死亡率の正の相関は見かけ上のものにすぎない。重回帰で高齢化率を統制することで、他の変数の「純粋な」効果を推定できる。
174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 | from matplotlib.colors import TwoSlopeNorm n_v = len(vars_analysis) pval_mat = np.ones((n_v, n_v)) for i in range(n_v): for j in range(n_v): if i != j: _, p_ij = stats.pearsonr(df_merge[vars_analysis[i]], df_merge[vars_analysis[j]]) pval_mat[i, j] = p_ij corr_arr = corr_mat.values norm = TwoSlopeNorm(vmin=-1, vcenter=0, vmax=1) fig3, ax3 = plt.subplots(figsize=(9, 7)) im = ax3.imshow(corr_arr, cmap='RdBu_r', norm=norm, aspect='auto') tick_labels = [var_labels[v] for v in vars_analysis] ax3.set_xticks(range(n_v)) ax3.set_yticks(range(n_v)) ax3.set_xticklabels(tick_labels, fontsize=9) ax3.set_yticklabels(tick_labels, fontsize=9) for i in range(n_v): for j in range(n_v): r_ij = corr_arr[i, j] p_ij = pval_mat[i, j] sig_mark = ('***' if p_ij < 0.001 else '**' if p_ij < 0.01 else '*' if p_ij < 0.05 else '') text_color = 'white' if abs(r_ij) > 0.6 else 'black' ax3.text(j, i, f'{r_ij:.2f}{sig_mark}', ha='center', va='center', fontsize=9, color=text_color, fontweight='bold' if sig_mark else 'normal') plt.colorbar(im, ax=ax3, shrink=0.8, label='Pearson r') ax3.set_title(f'変数間Pearson相関行列({latest_year}年・47都道府県)\n' '*p<0.05 **p<0.01 ***p<0.001', fontsize=12, fontweight='bold', pad=14) plt.tight_layout() fig3.savefig(os.path.join(FIG_DIR, '2019_H5_1_fig3.png'), bbox_inches='tight') plt.close(fig3) print("Fig3 saved.") |
Fig3 saved.
import pandas as pd など — 必要なライブラリをまとめて呼び出します。as pd は短い別名(alias)。fig, ax = plt.subplots(...) — 図全体(fig)と軸(ax)を作る定番。以降は ax.bar(...) 等で操作。stats.pearsonr(x, y) — Pearson相関係数 r と p値を同時に返します。plt.subplots(figsize=(W, H)) で図サイズ指定、fig.savefig(..., bbox_inches='tight') で余白を自動で詰めて保存。5つの説明変数を同時投入した OLS 重回帰分析(標準化)を実施した。標準化偏回帰係数(β)と95%信頼区間を横棒グラフで示す。
| 説明変数 | β(標準化) | SE | p値 | 有意性 | 解釈 |
|---|---|---|---|---|---|
| 高齢化率 | +1.003 | 0.052 | <0.001 | *** | 最強の正の効果(死亡率を規定する主因) |
| 消費支出(万円/月) | −0.083 | 0.036 | 0.027 | * | 豊かさが健康維持に貢献 |
| 人口密度 | +0.078 | 0.045 | 0.090 | n.s. | 高齢化率統制後は非有意 |
| 有効求人倍率 | +0.038 | 0.035 | 0.287 | n.s. | 高齢化率で交絡が説明される |
| 人口10万対病院数 | −0.028 | 0.041 | 0.507 | n.s. | 単独では有意効果なし |
標準化偏回帰係数(β)は「他の変数を統制したもとで、当該変数が1標準偏差増加したとき、目的変数が何標準偏差変化するか」を示す。単位が異なる変数(高齢化率% vs 人口密度 人/km²)を横断的に比較できる点が最大の利点。高齢化率のβ≈1.0は、「高齢化率が1SD(≈3.3%)増えると死亡率も1SD(≈2.1‰)増える」ことを意味する。
219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 | d['総人口_num'] = pd.to_numeric(d['総人口'], errors='coerce') d['65歳以上_num'] = pd.to_numeric(d['65歳以上人口'], errors='coerce') d['死亡数_num'] = pd.to_numeric(d['死亡数'], errors='coerce') d['有効求人数_num'] = pd.to_numeric(d['月間有効求人数(一般)'], errors='coerce') d['有効求職者数_num'] = pd.to_numeric(d['月間有効求職者数(一般)'], errors='coerce') d['病院数_num'] = pd.to_numeric(d['一般病院数'], errors='coerce') d['消費支出_num'] = pd.to_numeric(d['消費支出(二人以上の世帯)'], errors='coerce') # 目的変数: 死亡率(人口千人あたり) d['死亡率'] = d['死亡数_num'] / d['総人口_num'] * 1000 # 説明変数1: 高齢化率(%) d['高齢化率'] = d['65歳以上_num'] / d['総人口_num'] * 100 # 説明変数2: 有効求人倍率 d['有効求人倍率'] = d['有効求人数_num'] / d['有効求職者数_num'] # 説明変数3: 人口10万対病院数(医療アクセス) d['人口10万対病院数'] = d['病院数_num'] / d['総人口_num'] * 100000 # 説明変数4: 消費支出(万円換算) d['消費支出_万円'] = d['消費支出_num'] / 10000 |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。.map() は「1対1の置き換え」、.apply() は「関数を当てる」。辞書なら .map()、ロジックなら .apply()。241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 | df_merge = d[['pref_key', '都道府県', '死亡率', '高齢化率', '有効求人倍率', '人口10万対病院数', '消費支出_万円']].merge( df_e[['pref_key', '人口密度']], on='pref_key', how='inner' ) print(f"\nマージ後データ件数: {len(df_merge)}") print(df_merge[['pref_key', '死亡率', '高齢化率', '有効求人倍率', '人口密度', '消費支出_万円', '人口10万対病院数']].head(10).to_string()) # 欠損値確認・除去 print("\n欠損値:") print(df_merge.isnull().sum()) df_merge = df_merge.dropna().reset_index(drop=True) print(f"欠損除去後: {len(df_merge)} 都道府県") |
マージ後データ件数: 47 pref_key 死亡率 高齢化率 有効求人倍率 人口密度 消費支出_万円 人口10万対病院数 0 北海道 14.752553 33.012569 1.076607 222.249056 29.6888 9.112333 1 青森 17.597128 35.219595 1.189889 358.165088 26.3371 6.081081 2 岩手 16.863285 34.995701 1.270049 305.218571 29.8536 6.534824 3 宮城 12.650177 29.240283 1.429065 705.580299 30.5541 4.770318 4 秋田 19.165208 39.059081 1.400598 277.426398 27.2086 5.251641 5 山形 16.544834 35.185185 1.454657 351.861455 32.2992 5.068226 6 福島 15.571024 33.163554 1.438160 411.961295 30.7186 5.602716 7 茨城 13.310796 30.619469 1.412729 721.397959 30.7817 5.380531 8 栃木 13.204533 30.205588 1.193315 627.275329 32.5226 4.691618 9 群馬 14.060463 30.967403 1.332115 832.958722 28.3599 5.993691 欠損値: pref_key 0 都道府県 0 死亡率 0 高齢化率 0 有効求人倍率 0 人口10万対病院数 0 消費支出_万円 0 人口密度 0 dtype: int64 欠損除去後: 47 都道府県
r, p = stats.pearsonr(...) — Pythonは複数戻り値を同時に受け取れる(タプルアンパック)。256 257 258 259 260 261 262 | y = df_merge['死亡率'].values X_vars = ['高齢化率', '有効求人倍率', '人口密度', '消費支出_万円', '人口10万対病院数'] X = df_merge[X_vars].values # 標準化(標準化偏回帰係数のため) y_std = (y - y.mean()) / y.std() X_std = (X - X.mean(axis=0)) / X.std(axis=0) |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。s[:-n]「末尾n文字を除く」/s[n:]「先頭n文字を除く」。スライス [start:stop:step] はリスト・タプル・文字列共通の基本ワザです。263 264 265 266 267 268 | # 標準化モデル X_std_const = sm.add_constant(X_std) model_std = sm.OLS(y_std, X_std_const).fit() print("\n=== 重回帰分析(標準化) ===") print(model_std.summary()) |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。sm.add_constant(X) — 切片項(定数1の列)を先頭に追加。statsmodelsで必須。sm.OLS(y, X).fit() — 最小二乗法でモデルを推定。model.params, model.pvalues, model.conf_int() で結果取得。np.cumsum(arr) は累積和、np.linspace(a, b, n) は「aからbを等間隔でn個」。NumPyの定石です。269 270 271 272 273 274 | # 非標準化モデル(Cook's distance 等) X_const = sm.add_constant(X) model = sm.OLS(y, X_const).fit() print("\n=== 重回帰分析(非標準化) ===") print(model.summary()) |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。sm.add_constant(X) — 切片項(定数1の列)を先頭に追加。statsmodelsで必須。sm.OLS(y, X).fit() — 最小二乗法でモデルを推定。model.params, model.pvalues, model.conf_int() で結果取得。{値:.2f}(小数2桁)、{値:,}(3桁区切り)、{値:>10}(右寄せ10桁)など、覚えると出力が一気に整います。275 276 277 278 279 280 281 282 283 284 285 286 287 288 | # 標準化偏回帰係数の抽出 betas = model_std.params[1:] beta_se = model_std.bse[1:] beta_pvals = model_std.pvalues[1:] print("\n=== 標準化偏回帰係数 ===") for i, v in enumerate(X_vars): sig = ('***' if beta_pvals[i] < 0.001 else '**' if beta_pvals[i] < 0.01 else '*' if beta_pvals[i] < 0.05 else 'n.s.') print(f" {v}: β={betas[i]:.4f}, SE={beta_se[i]:.4f}, p={beta_pvals[i]:.4f} {sig}") print(f"\nR² = {model_std.rsquared:.4f}") print(f"Adj.R² = {model_std.rsquared_adj:.4f}") |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。plt.subplots(figsize=(W, H)) で図サイズ指定、fig.savefig(..., bbox_inches='tight') で余白を自動で詰めて保存。289 290 291 292 293 294 | # Cook's distance influence = model.get_influence() cooks_d = influence.cooks_distance[0] print("\n=== Cook's Distance(上位5) ===") top5_cooks = pd.DataFrame({'都道府県': df_merge['pref_key'].values, 'Cooks_D': cooks_d}) print(top5_cooks.nlargest(5, 'Cooks_D').to_string(index=False)) |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。.dropna() は欠損行を除去、.copy() は独立したコピーを作る。pandasで警告を防ぐ定石。295 296 297 298 299 | # 上位・下位 5 都道府県(死亡率) print("\n=== 死亡率 上位5都道府県 ===") print(df_merge[['pref_key', '死亡率']].nlargest(5, '死亡率').to_string(index=False)) print("\n=== 死亡率 下位5都道府県 ===") print(df_merge[['pref_key', '死亡率']].nsmallest(5, '死亡率').to_string(index=False)) |
=== 重回帰分析(標準化) ===
OLS Regression Results
==============================================================================
Dep. Variable: y R-squared: 0.954
Model: OLS Adj. R-squared: 0.949
Method: Least Squares F-statistic: 171.6
Date: Mon, 18 May 2026 Prob (F-statistic): 2.32e-26
Time: 11:23:28 Log-Likelihood: 5.8678
No. Observations: 47 AIC: 0.2645
Df Residuals: 41 BIC: 11.37
Df Model: 5
Covariance Type: nonrobust
==============================================================================
coef std err t P>|t| [0.025 0.975]
------------------------------------------------------------------------------
const 1.312e-15 0.033 3.93e-14 1.000 -0.067 0.067
x1 1.0031 0.052 19.403 0.000 0.899 1.107
x2 0.0378 0.035 1.079 0.287 -0.033 0.108
x3 0.0782 0.045 1.734 0.090 -0.013 0.169
x4 -0.0832 0.036 -2.287 0.027 -0.157 -0.010
x5 -0.0275 0.041 -0.669 0.507 -0.110 0.055
==============================================================================
Omnibus: 5.939 Durbin-Watson: 1.333
Prob(Omnibus): 0.051 Jarque-Bera (JB): 6.471
Skew: -0.373 Prob(JB): 0.0393
Kurtosis: 4.658 Cond. No. 2.92
==============================================================================
Notes:
[1] Standard Errors assume that the covariance matrix of the errors is correctly specified.
=== 重回帰分析(非標準化) ===
OLS Regression Results
==============================================================================
Dep. Variable: y R-squared: 0.954
Model: OLS Adj. R-squared: 0.949
Method: Least Squares F-statistic: 171.6
Date: Mon, 18 May 2026 Prob (F-statistic): 2.32e-26
Time: 11:23:28 Log-Likelihood: -28.383
No. Observations: 47 AIC: 68.77
Df Residuals: 41 BIC: 79.87
Df Model: 5
Covariance Type: nonrobust
==============================================================================
coef std err t P>|t| [0.025 0.975]
------------------------------------------------------------------------------
const -4.1110 1.440 -2.856 0.007 -7.018 -1.204
x1 0.6295 0.032 19.403 0.000 0.564 0.695
x2 0.3608 0.334 1.079 0.287 -0.314 1.036
x3 9.118e-05 5.26e-05 1.734 0.090 -1.5e-05 0.000
x4 -0.0722 0.032 -2.287 0.027 -0.136 -0.008
x5 -0.0208 0.031 -0.669 0.507 -0.084 0.042
=========================
…(長いため省略)f"...{x}..." はf-string。文字列の中に {変数} と書くだけで埋め込めて、{x:.2f} のように書式も指定できます。300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 | x_labels = ['高齢化率', '有効求人倍率', '人口密度\n(都市化)', '消費支出\n(万円/月)', '病院数\n(10万対)'] ci_95 = 1.96 * beta_se fig4, ax4 = plt.subplots(figsize=(9, 6)) y_pos = np.arange(len(X_vars))[::-1] bar_colors = [] for i, pv in enumerate(beta_pvals): if pv < 0.05: bar_colors.append('#e05c5c' if betas[i] > 0 else '#4e9af1') else: bar_colors.append('#cccccc') ax4.barh(y_pos, betas, xerr=ci_95, color=bar_colors, height=0.55, edgecolor='white', linewidth=0.5, error_kw=dict(ecolor='#555', capsize=4, lw=1.5)) ax4.axvline(0, color='#333', linewidth=1.2) ax4.set_yticks(y_pos) ax4.set_yticklabels(x_labels, fontsize=10) ax4.set_xlabel('標準化偏回帰係数(β)', fontsize=11) ax4.set_title('重回帰分析:標準化偏回帰係数と95%信頼区間\n' '目的変数:死亡率(安全・健康指標)', fontsize=13, fontweight='bold', pad=12) |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。fig, ax = plt.subplots(...) — 図全体(fig)と軸(ax)を作る定番。以降は ax.bar(...) 等で操作。ax.axhline / ax.axvline — 水平/垂直の点線。平均線や基準線として定番。.dropna() は欠損行を除去、.copy() は独立したコピーを作る。pandasで警告を防ぐ定石。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 | # 有意水準マーク for i, (b, pv, ci) in enumerate(zip(betas, beta_pvals, ci_95)): sig_mark = ('***' if pv < 0.001 else '**' if pv < 0.01 else '*' if pv < 0.05 else 'n.s.') offset = 0.025 x_text = b + ci + offset if b >= 0 else b - ci - offset ha = 'left' if b >= 0 else 'right' ax4.text(x_text, y_pos[i], sig_mark, ha=ha, va='center', fontsize=9, color='#c00' if pv < 0.05 else '#888') legend_els4 = [ Patch(facecolor='#e05c5c', label='正の有意効果 (p<0.05)'), Patch(facecolor='#4e9af1', label='負の有意効果 (p<0.05)'), Patch(facecolor='#cccccc', label='非有意 (n.s.)'), ] ax4.legend(handles=legend_els4, fontsize=9, loc='lower right') ax4.text(0.02, 0.02, f'R²={model_std.rsquared:.3f} Adj.R²={model_std.rsquared_adj:.3f} N={len(df_merge)}', transform=ax4.transAxes, fontsize=9, color='#555') ax4.spines['top'].set_visible(False) ax4.spines['right'].set_visible(False) plt.tight_layout() fig4.savefig(os.path.join(FIG_DIR, '2019_H5_1_fig4.png'), bbox_inches='tight') plt.close(fig4) print("Fig4 saved.") print("\nDONE: 2019_H5_1_shorei") |
Fig4 saved. DONE: 2019_H5_1_shorei
f"...{x}..." はf-string。文字列の中に {変数} と書くだけで埋め込めて、{x:.2f} のように書式も指定できます。SSDSE-B/E の47都道府県(2023年)データを用いた重回帰分析の結果:
| データ | 出典・説明 |
|---|---|
| SSDSE-B-2026(都道府県別・時系列) | 統計数理研究所 SSDSE — 死亡数・総人口・65歳以上人口・有効求人数/求職者数・病院数・消費支出等 |
| SSDSE-E-2026(都道府県別・横断面) | 統計数理研究所 SSDSE — 可住地面積(人口密度算出に使用) |
本教育用コードは SSDSE-B-2026・SSDSE-E-2026 の実データを使用。合成データは一切使用していない。
統計分析の解釈で初心者がやりがちな勘違いをまとめます。特に「相関と因果の混同」「p値の過信」は研究現場でもよく起きる落とし穴です。本文を読む前にも、読んだ後にも、目を通してみてください。
統計の基本用語を初心者向けに解説します。本文中で見慣れない言葉が出てきたら、ここに戻って確認してください。
統計手法について「何のためか」「結果をどう読むか」を初心者向けに解説します。
この研究をさらに発展させるための3つの方向性を示します。「今回わかったこと(X)」から「次に検証すべき仮説(Y)」を立て、「具体的に何をするか(Z)」まで考えてみましょう。
学んだだけでは身につきません。実際に手を動かすのが最強の学習方法です。本論文のスクリプトをベースに、以下のチャレンジに挑戦してみてください。難易度別に5つ用意しました。
本論文で学んだ手法は、研究の世界だけでなく、行政・企業・NPO の現場でも様々に活用されています。具体的なシーンを紹介します。
この論文を読んで初心者が抱きやすい疑問に、教育的観点から答えます。