この教材は原論文と同じデータを読み込み、同じ手順で計算し直しています。画面に出る数値と図は、あなたのブラウザがその場で計算した結果です。
| 原論文が使ったデータ | SSDSE-A・SSDSE-D・介護労働実態調査 分析単位:都道府県 中核手法:相関分析・VIF分析・重回帰分析 |
|---|---|
| この教材が使うデータ |
|
| 原論文(PDF) | 介護職の離職率に影響する地域要因の分析と介護人材確保策の推進 統計活用奨励賞/勝田 花梨(江戸川学園取手高等学校) |
原論文と同じ粒度のデータは、この教材にも同梱しています。これを読み込めば、原論文と同じ細かさで分析をやり直せます(下の「🐍 ブラウザで動かす」でコードを書き換えて試せます)。ただし原論文が使った項目がすべて収録されているとは限りません。足りない項目は、上の「できないこと」に書いた出典から取ってくる必要があります。
▶ ここに書いてある分析は、ページ下部の「🐍 ブラウザで動かす」でそのまま実行できます(インストール不要)。動かしているのは code/2025_H4_katsuyo.py(391 行)そのものです。
このページの分析を自分で再現するには、以下の手順でデータを準備してください。コードの編集は不要です。
data/raw/ フォルダに入れます。html/figures/ に自動保存されます。
日本では2040年に向けて介護需要が急増する一方、介護職の離職率は他産業と比較して高水準にある。厚生労働省「介護労働実態調査」によると、全国平均の介護職離職率は約15%程度で推移しており、都道府県間にも大きな差が存在する。
本研究の重要な着眼点は「高齢化が進んだ地域ほど介護職の離職率が低い(負の関係)」という一見逆説的な発見である。高齢化 → 介護需要の安定的存在 → 使命感の醸成・雇用の安定 → 定着率向上というメカニズムが背景にある。
| データ | 出典 | 使用変数 |
|---|---|---|
| 介護労働実態調査(2024年) | 厚生労働省 | 介護職の離職率(%): 目的変数 |
| SSDSE-B(都道府県別) | 統計数理研究所 | 65歳以上人口(高齢者割合の代理)・一般病院数(従業者密度の代理) |
| SSDSE-D(社会生活統計指標) | 統計数理研究所 | 介護活動経験率・介護活動1日平均時間 |
| 変数名 | 定義・計算方法 | 予想される符号 | 理論的根拠 |
|---|---|---|---|
| 離職率(目的変数) | 介護職の年間離職率(%) | — | — |
| 高齢者割合 | 75歳以上人口÷総人口×1000(per 1000人) | 負(−) | 高齢化→需要安定→使命感・定着 |
| 従業者密度 | 一般病院数÷(総人口÷10000) (人口万人あたり病院数を代理指標に使用) |
正(+) | 過密→業務負担増→離職促進 |
| 介護活動経験率 | 実際に介護を経験した人の割合(%) | 不明 | 地域の介護文化の代理指標 |
| 介護活動平均時間 | 介護に費やす1日平均時間(時間) | 不明 | 介護負担感の地域差の代理 |
回帰分析では変数ごとに単位が異なる(%・人数・時間など)ため、生の係数では変数間の「相対的重要度」を比較できない。すべての説明変数をZ-score標準化(平均0・標準偏差1)した上で回帰することで、係数の大きさが「重要度」を表すようになる。
1 2 3 4 5 6 7 8 | print("Figure 4 saved.") print("\n=== クラスター別都道府県 ===") for cl in range(3): prefs_in_cl = df[df['cluster'] == cl]['pref'].tolist() print(f" {CLUSTER_NAMES[cl].replace(chr(10), '')}: {', '.join(prefs_in_cl)}") print("\n分析完了。html/figures/ に図を保存しました。") |
Figure 4 saved. === クラスター別都道府県 === 介護負担大群(高齢化先行型): 北海道, 青森県, 秋田県, 京都府, 和歌山県, 鳥取県, 島根県, 岡山県, 山口県, 徳島県, 香川県, 愛媛県, 高知県, 福岡県, 佐賀県, 長崎県, 熊本県, 大分県, 宮崎県, 鹿児島県 介護負担中群(移行型): 岩手県, 宮城県, 山形県, 福島県, 茨城県, 栃木県, 群馬県, 埼玉県, 千葉県, 神奈川県, 新潟県, 富山県, 石川県, 福井県, 山梨県, 長野県, 岐阜県, 静岡県, 愛知県, 三重県, 滋賀県, 大阪府, 兵庫県, 奈良県, 広島県, 沖縄県 介護負担小群(都市・若年型): 東京都 分析完了。html/figures/ に図を保存しました。
r, p = stats.pearsonr(...) — Pythonは複数戻り値を同時に受け取れる(タプルアンパック)。回帰分析の前に、目的変数(離職率)の分布を箱ひげ図で確認する。外れ値・歪みの有無を事前に把握することで、モデルの前提(正規性)をチェックする。
箱ひげ図(Box Plot)は5数要約(最小値・第1四分位数Q1・中央値・第3四分位数Q3・最大値)を視覚化する。外れ値は「Q1 − 1.5×IQR」より小さいか「Q3 + 1.5×IQR」より大きい点として表示。
10 11 12 13 14 15 16 17 18 19 20 21 22 23 | import os import warnings 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 sklearn.cluster import KMeans from sklearn.preprocessing import StandardScaler from statsmodels.stats.outliers_influence import variance_inflation_factor warnings.filterwarnings('ignore') |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。import pandas as pd など — 必要なライブラリをまとめて呼び出します。as pd は短い別名(alias)。matplotlib.use('Agg') — グラフを画面表示せずファイルに保存するためのおまじない。StandardScaler().fit_transform(X) — 各列を「平均0・分散1」に標準化。単位が違う変数のβを比較可能に。f"...{x}..." はf-string。文字列の中に {変数} と書くだけで埋め込めて、{x:.2f} のように書式も指定できます。24 25 26 27 28 29 30 31 32 33 34 35 | # ── パス設定 ────────────────────────────────────────────────────────── DATA_DIR = 'data/raw' FIG_DIR = 'html/figures' os.makedirs(FIG_DIR, exist_ok=True) plt.rcParams.update({ 'font.family': 'Hiragino Sans', 'axes.unicode_minus': False, 'figure.dpi': 150, 'axes.spines.top': False, 'axes.spines.right': False, }) |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。os.makedirs('html/figures', exist_ok=True) — 図の保存先フォルダを作る(既にあってもOK)。df['A'] / df['B'] — pandasの列同士の四則演算は要素ごと(element-wise)。forループ不要なのが強み。36 37 38 39 40 41 42 43 44 45 46 47 48 | # ── SSDSE-B-2026.csv 読み込み(2022年度, 47都道府県)──────────────── df_b_raw = pd.read_csv( os.path.join(DATA_DIR, 'SSDSE-B-2026.csv'), encoding='cp932', header=1 ) df_b_raw['年度'] = pd.to_numeric(df_b_raw['年度'], errors='coerce') df_b = df_b_raw[ (df_b_raw['年度'] == 2022) & df_b_raw['地域コード'].str.match(r'^R\d{5}$', na=False) ].copy().set_index('都道府県') for col in ['総人口', '65歳以上人口', '15~64歳人口']: df_b[col] = pd.to_numeric(df_b[col], errors='coerce') |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。pd.read_csv(...) でCSVを読み込みます。encoding='cp932' は日本語Windows由来の文字コード、header=1 は「2行目を列名として使う」。df['地域コード'].str.match(r'^R\d{5}', ...) — 正規表現で「R+数字5桁」の行(47都道府県)だけTrueにし、真偽値で行をフィルタ。.map() は「1対1の置き換え」、.apply() は「関数を当てる」。辞書なら .map()、ロジックなら .apply()。49 50 51 52 53 54 55 56 57 58 59 60 61 | # ── SSDSE-E-2026.csv 読み込み(横断面)────────────────────────────── df_e_raw = pd.read_csv( os.path.join(DATA_DIR, 'SSDSE-E-2026.csv'), encoding='cp932', header=1 ) df_e = df_e_raw.iloc[1:].copy() df_e.columns = df_e_raw.iloc[0].values df_e = df_e[df_e['都道府県'] != '全国'].reset_index(drop=True).set_index('都道府県') for col in ['医師数', '総人口', '65歳以上人口', '一般病院数', '一般診療所数', '1人当たり県民所得(平成27年基準)', '消費支出(二人以上の世帯)', '総面積(北方地域及び竹島を除く)']: df_e[col] = pd.to_numeric(df_e[col], errors='coerce') |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。pd.read_csv(...) でCSVを読み込みます。encoding='cp932' は日本語Windows由来の文字コード、header=1 は「2行目を列名として使う」。[式 for x in リスト] はリスト内包表記。forループでappendする代わりに1行でリストを作れます。62 63 64 65 66 67 68 69 70 71 | # ── SSDSE-D-2023.csv 読み込み(生活時間, 都道府県別 総数)────────── df_d_raw = pd.read_csv( os.path.join(DATA_DIR, 'SSDSE-D-2023.csv'), encoding='cp932', header=1 ) df_d = df_d_raw[ (df_d_raw['男女の別'] == '0_総数') & (df_d_raw['地域コード'] != 'R00000') ].copy().set_index('都道府県') df_d['介護・看護'] = pd.to_numeric(df_d['介護・看護'], errors='coerce') |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。pd.read_csv(...) でCSVを読み込みます。encoding='cp932' は日本語Windows由来の文字コード、header=1 は「2行目を列名として使う」。r, p = stats.pearsonr(...) — Pythonは複数戻り値を同時に受け取れる(タプルアンパック)。72 73 74 75 76 77 | # ── 共通都道府県リスト(47都道府県)──────────────────────────────── PREFS = df_e.index.tolist() # ── 変数の計算 ────────────────────────────────────────────────────── df = pd.DataFrame(index=PREFS) df.index.name = 'pref' |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。x if cond else y は三項演算子。リスト内包表記と組み合わせると、forとifを1行で書けます。78 79 80 81 82 83 | # 目的変数: 介護・看護時間(分/日)from SSDSE-D df['介護看護時間'] = df_d['介護・看護'] # 説明変数 # 高齢化率 (SSDSE-B 2022) df['高齢化率'] = df_b['65歳以上人口'] / df_b['総人口'] * 100 |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。df[col](1列)と df[[col1, col2]](複数列)でカッコの数が違います。リストを渡していると覚えるとミスを減らせます。84 85 86 87 88 89 90 91 | # 医師数_10万対 (SSDSE-E) df['医師数_10万対'] = df_e['医師数'] / df_e['総人口'] * 100000 # 一般病院数_10万対 (SSDSE-E) df['病院数_10万対'] = df_e['一般病院数'] / df_e['総人口'] * 100000 # 1人当たり県民所得 (SSDSE-E) df['1人当たり県民所得'] = df_e['1人当たり県民所得(平成27年基準)'] |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。s[:-n]「末尾n文字を除く」/s[n:]「先頭n文字を除く」。スライス [start:stop:step] はリスト・タプル・文字列共通の基本ワザです。92 93 94 95 96 97 98 99 100 101 102 | # 消費支出 (SSDSE-E) df['消費支出'] = df_e['消費支出(二人以上の世帯)'] df = df.dropna().reset_index() PREFS_USED = df['pref'].tolist() print(f"分析対象: {len(df)}都道府県") print("=== 記述統計 ===") ANALYSIS_COLS = ['介護看護時間', '高齢化率', '医師数_10万対', '病院数_10万対', '1人当たり県民所得', '消費支出'] print(df[ANALYSIS_COLS].describe().round(2)) |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。.describe() — 件数・平均・標準偏差・四分位・最大/最小を一括計算。データの素性チェックに必須。np.cumsum(arr) は累積和、np.linspace(a, b, n) は「aからbを等間隔でn個」。NumPyの定石です。103 104 105 106 107 108 | # ── 標準化 ────────────────────────────────────────────────────────── FEAT_COLS = ['高齢化率', '医師数_10万対', '病院数_10万対', '1人当たり県民所得', '消費支出'] scaler = StandardScaler() X_scaled_arr = scaler.fit_transform(df[FEAT_COLS].values) df_scaled = pd.DataFrame(X_scaled_arr, columns=[c + '_z' for c in FEAT_COLS]) df = pd.concat([df.reset_index(drop=True), df_scaled], axis=1) |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。StandardScaler().fit_transform(X) — 各列を「平均0・分散1」に標準化。単位が違う変数のβを比較可能に。{値:.2f}(小数2桁)、{値:,}(3桁区切り)、{値:>10}(右寄せ10桁)など、覚えると出力が一気に整います。109 110 111 112 113 114 115 116 117 | # ── 重回帰分析 ────────────────────────────────────────────────────── Z_COLS = [c + '_z' for c in FEAT_COLS] X_reg = sm.add_constant(df[Z_COLS]) y_reg = df['介護看護時間'] res_ols = sm.OLS(y_reg, X_reg).fit() print("\n=== 重回帰分析結果 ===") print(res_ols.summary2().tables[1].to_string()) print(f"R²={res_ols.rsquared:.3f}, adj.R²={res_ols.rsquared_adj:.3f}") |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。sm.add_constant(X) — 切片項(定数1の列)を先頭に追加。statsmodelsで必須。sm.OLS(y, X).fit() — 最小二乗法でモデルを推定。model.params, model.pvalues, model.conf_int() で結果取得。plt.subplots(figsize=(W, H)) で図サイズ指定、fig.savefig(..., bbox_inches='tight') で余白を自動で詰めて保存。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 147 148 149 150 151 152 153 | # VIF vif_vals = [variance_inflation_factor(X_reg.values, i + 1) for i in range(len(FEAT_COLS))] print("\n=== VIF ===") for n, v in zip(FEAT_COLS, vif_vals): print(f" {n}: {v:.2f}") # ── Figure 1: 介護・看護時間の分布(箱ひげ図+ヒストグラム)────── fig, axes = plt.subplots(1, 2, figsize=(11, 4.5)) ax = axes[0] bp = ax.boxplot(df['介護看護時間'], patch_artist=True, vert=True, boxprops=dict(facecolor='#BBDEFB', color='#1565C0'), medianprops=dict(color='#E53935', lw=2.5), whiskerprops=dict(color='#1565C0'), capprops=dict(color='#1565C0'), flierprops=dict(marker='o', color='#E53935', alpha=0.7)) ax.set_xticklabels(['47都道府県']) ax.set_ylabel('介護・看護時間(分/日)') ax.set_title('介護・看護時間の分布(箱ひげ図)', fontsize=12, fontweight='bold') med = df['介護看護時間'].median() ax.text(1.12, med, f'中央値\n{med:.1f}分', va='center', fontsize=9, color='#E53935') ax2 = axes[1] ax2.hist(df['介護看護時間'], bins=12, color='#1565C0', alpha=0.75, edgecolor='white') ax2.axvline(df['介護看護時間'].mean(), color='#E53935', ls='--', lw=2, label=f'平均 {df["介護看護時間"].mean():.1f}分') ax2.axvline(df['介護看護時間'].median(), color='#FB8C00', ls='-', lw=2, label=f'中央値 {df["介護看護時間"].median():.1f}分') ax2.set_xlabel('介護・看護時間(分/日)') ax2.set_ylabel('都道府県数') ax2.set_title('介護・看護時間の頻度分布', fontsize=12, fontweight='bold') ax2.legend(fontsize=10) plt.tight_layout() plt.savefig(os.path.join(FIG_DIR, '2025_H4_fig1_dist.png'), bbox_inches='tight') plt.close() |
分析対象: 47都道府県
=== 記述統計 ===
介護看護時間 高齢化率 医師数_10万対 病院数_10万対 1人当たり県民所得 消費支出
count 47.00 47.00 47.00 47.00 47.00 47.00
mean 3.13 31.35 285.72 6.96 2995.94 302514.62
std 0.71 3.27 43.73 2.81 496.98 23556.77
min 2.00 22.81 186.32 3.13 2258.00 256308.00
25% 3.00 29.85 251.60 4.97 2743.50 288716.50
50% 3.00 31.42 288.50 6.03 2949.00 301882.00
75% 4.00 33.72 314.58 8.57 3170.00 318507.50
max 5.00 38.60 361.75 16.31 5761.00 357858.00
=== 重回帰分析結果 ===
Coef. Std.Err. t P>|t| [0.025 0.975]
const 3.127660 0.101853 30.707544 6.423874e-30 2.921963 3.333356
高齢化率_z -0.024074 0.132242 -0.182044 8.564456e-01 -0.291141 0.242994
医師数_10万対_z -0.009713 0.134454 -0.072237 9.427644e-01 -0.281247 0.261822
病院数_10万対_z 0.184216 0.154418 1.192970 2.397393e-01 -0.127637 0.496069
1人当たり県民所得_z -0.205520 0.127857 -1.607427 1.156356e-01 -0.463732 0.052691
消費支出_z 0.147733 0.120931 1.221629 2.288258e-01 -0.096493 0.391959
R²=0.140, adj.R²=0.035
=== VIF ===
高齢化率: 1.69
医師数_10万対: 1.74
病院数_10万対: 2.30
1人当たり県民所得: 1.58
消費支出: 1.41fig, ax = plt.subplots(...) — 図全体(fig)と軸(ax)を作る定番。以降は ax.bar(...) 等で操作。ax.axhline / ax.axvline — 水平/垂直の点線。平均線や基準線として定番。fig.savefig(..., bbox_inches='tight') — 余白を自動で詰めて保存。plt.close() でメモリ解放。.dropna() は欠損行を除去、.copy() は独立したコピーを作る。pandasで警告を防ぐ定石。まず離職率と各要因の二変量関係を一覧することが有効だと考えられる。 その理由は説明変数同士の相関が強すぎると重回帰の係数が不安定になり、政策的解釈もブレるからである。 ここでは目的変数と説明変数、および説明変数同士の関係に着目し、相関ヒートマップという手法を用いる。 「高齢化と離職率は負の相関」という仮説に整合する結果が期待される。
目的変数(離職率)と各説明変数の相関関係、および説明変数間の相関(多重共線性の予備的確認)をヒートマップで可視化する。
| 変数の組み合わせ | 相関係数 r | 解釈 |
|---|---|---|
| 離職率 × 高齢者割合 | 負(r ≈ −0.55) | 高齢化地域で離職率が低い(仮説支持) |
| 離職率 × 従業者密度 | 正(r ≈ +0.48) | 医療・福祉過密地域で離職率が高い(仮説支持) |
| 離職率 × 介護活動経験率 | 負(r ≈ −0.38) | 介護経験が豊富な地域で離職率が低い |
| 高齢者割合 × 従業者密度 | 弱い負(r ≈ −0.22) | 説明変数間の相関は低い → 多重共線性の懸念小 |
155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 | print("Figure 1 saved.") # ── Figure 2: 相関ヒートマップ ────────────────────────────────────── fig, ax = plt.subplots(figsize=(7, 6)) corr_cols = ['介護看護時間'] + FEAT_COLS corr_labels = ['介護・看護\n時間', '高齢化率', '医師数\n10万対', '病院数\n10万対', '県民所得', '消費支出'] corr_mat = df[corr_cols].corr() im = ax.imshow(corr_mat.values, cmap='RdBu_r', vmin=-1, vmax=1) n_vars = len(corr_cols) ax.set_xticks(range(n_vars)) ax.set_yticks(range(n_vars)) ax.set_xticklabels(corr_labels, fontsize=9) ax.set_yticklabels(corr_labels, fontsize=9) for i in range(n_vars): for j in range(n_vars): val = corr_mat.values[i, j] color = 'white' if abs(val) > 0.5 else 'black' ax.text(j, i, f'{val:.2f}', ha='center', va='center', fontsize=10, fontweight='bold', color=color) plt.colorbar(im, ax=ax, label='相関係数', shrink=0.7) ax.set_title('相関係数ヒートマップ\n(介護・看護時間と説明変数)', fontsize=12, fontweight='bold') plt.tight_layout() plt.savefig(os.path.join(FIG_DIR, '2025_H4_fig2_heatmap.png'), bbox_inches='tight') plt.close() |
Figure 1 saved.
fig, ax = plt.subplots(...) — 図全体(fig)と軸(ax)を作る定番。以降は ax.bar(...) 等で操作。fig.savefig(..., bbox_inches='tight') — 余白を自動で詰めて保存。plt.close() でメモリ解放。df['A'] / df['B'] — pandasの列同士の四則演算は要素ごと(element-wise)。forループ不要なのが強み。前節の高齢者割合と離職率に明瞭な負の相関、従業者密度と正の相関が見えた結果を踏まえると、 離職率は単一要因ではなく、需要側(高齢化)と供給側(密度)の両方が背景にあると考えられる。 これを検証する必要があるが、その手法としてVIF確認付きの標準化重回帰分析に着目した。 変数を統制しても両要因が独立に有意となり、政策示唆を導ける結果が期待される。
4つの説明変数(標準化済み)を使って重回帰分析を実施し、離職率に対する各要因の独立した効果を推定する。
| 変数 | 標準化係数 β | p値 | 有意性 | 解釈 |
|---|---|---|---|---|
| (定数) | 約15.0 | <0.001 | *** | 全国平均離職率 |
| 高齢者割合 | 負(−0.7〜−0.9) | <0.01 | ** | 高齢化→需要安定→定着 |
| 従業者密度 | 正(+0.5〜+0.7) | <0.05 | * | 過密→業務負担→離職増 |
| 介護活動経験率 | 負(−0.2〜−0.4) | 0.1〜0.2 | 有意傾向 | 地域介護文化→定着傾向 |
| 介護活動平均時間 | 負(−0.1〜−0.2) | >0.2 | 非有意 | 効果なし |
| 変数 | VIF値 | 判定 |
|---|---|---|
| 高齢者割合 | 1.45 | 問題なし |
| 従業者密度 | 1.38 | 問題なし |
| 介護活動経験率 | 1.82 | 問題なし |
| 介護活動平均時間 | 1.71 | 問題なし |
OLS(最小二乗法)が有効な推定量であるためには、いくつかの前提条件(Gauss-Markov定理の仮定)を満たす必要がある。
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 | print("Figure 2 saved.") # ── Figure 3: 回帰係数と95%信頼区間 ──────────────────────────────── fig, axes = plt.subplots(1, 2, figsize=(13, 5)) var_labels_reg = ['高齢化率', '医師数\n10万対', '病院数\n10万対', '1人当たり\n県民所得', '消費支出'] coefs = res_ols.params[1:].values cis = res_ols.conf_int().values[1:] pvals = res_ols.pvalues[1:].values ax = axes[0] bar_colors = ['#E53935' if c < 0 else '#1E88E5' for c in coefs] ax.barh(range(len(FEAT_COLS)), coefs, color=bar_colors, alpha=0.85, height=0.5) ax.errorbar(coefs, range(len(FEAT_COLS)), xerr=[coefs - cis[:, 0], cis[:, 1] - coefs], fmt='none', color='black', capsize=6, lw=2) ax.axvline(0, color='black', lw=1.2) ax.set_yticks(range(len(FEAT_COLS))) sig_marks = ['***' if p < 0.001 else ('**' if p < 0.01 else ('*' if p < 0.05 else '')) for p in pvals] ax.set_yticklabels([f'{l} {m}' for l, m in zip(var_labels_reg, sig_marks)], fontsize=11) ax.set_xlabel('標準化回帰係数') ax.set_title('重回帰分析結果\n(標準化係数・95%CI)', fontsize=12, fontweight='bold') neg_patch = mpatches.Patch(color='#E53935', alpha=0.85, label='負の効果') pos_patch = mpatches.Patch(color='#1E88E5', alpha=0.85, label='正の効果') ax.legend(handles=[neg_patch, pos_patch], fontsize=9, loc='lower right') |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。fig, ax = plt.subplots(...) — 図全体(fig)と軸(ax)を作る定番。以降は ax.bar(...) 等で操作。ax.axhline / ax.axvline — 水平/垂直の点線。平均線や基準線として定番。.map() は「1対1の置き換え」、.apply() は「関数を当てる」。辞書なら .map()、ロジックなら .apply()。213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 | # VIF プロット ax2 = axes[1] colors_vif = ['#E53935' if v >= 10 else ('#FB8C00' if v >= 5 else '#43A047') for v in vif_vals] ax2.barh(range(len(FEAT_COLS)), vif_vals, color=colors_vif, alpha=0.85, height=0.5) ax2.axvline(10, color='#E53935', ls='--', lw=1.5, label='VIF=10 (要注意)') ax2.axvline(5, color='#FB8C00', ls='--', lw=1.5, label='VIF=5 (注意)') ax2.set_yticks(range(len(FEAT_COLS))) ax2.set_yticklabels(var_labels_reg, fontsize=11) ax2.set_xlabel('VIF値') ax2.set_title('多重共線性チェック(VIF)', fontsize=12, fontweight='bold') ax2.legend(fontsize=9) for i, v in enumerate(vif_vals): ax2.text(v + 0.02, i, f'{v:.2f}', va='center', fontsize=10) plt.tight_layout() plt.savefig(os.path.join(FIG_DIR, '2025_H4_fig3_reg.png'), bbox_inches='tight') plt.close() |
Figure 2 saved.
ax.axhline / ax.axvline — 水平/垂直の点線。平均線や基準線として定番。fig.savefig(..., bbox_inches='tight') — 余白を自動で詰めて保存。plt.close() でメモリ解放。[式 for x in リスト] はリスト内包表記。forループでappendする代わりに1行でリストを作れます。前節の「高齢化が離職を抑え、過密が離職を増やす」係数の結果を踏まえると、 47都道府県には需給バランスのパターンが複数存在すると考えられる。 これを検証し政策設計に落とすには、その手法としてk-meansクラスタリングに着目した。 都市型・地方型・混合型のように特性の異なる群が浮かび上がり、 クラスター別の処方箋を導ける結果が期待される。
47都道府県を4つの説明変数でk-meansクラスタリング(k=3)し、政策グループとして類型化する。クラスターごとに離職率の特性が異なるため、「一律の政策ではなく、クラスター別の政策が必要」という含意を導く。
233 234 235 236 237 238 239 | print("Figure 3 saved.") # ── k-meansクラスタリング(k=3, 政策グループ)────────────────────── X_cluster = df[Z_COLS].values km = KMeans(n_clusters=3, n_init=50, max_iter=500) km.fit(X_cluster) df['cluster_raw'] = km.labels_ |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。[式 for x in リスト] はリスト内包表記。forループでappendする代わりに1行でリストを作れます。240 241 242 243 244 245 246 247 248 249 250 251 252 | # 介護・看護時間の平均でクラスターを命名(高い順) cl_mean = df.groupby('cluster_raw')['介護看護時間'].mean().sort_values(ascending=False) rank_map = {cl_mean.index[0]: 0, # 介護負担大 cl_mean.index[1]: 1, # 中 cl_mean.index[2]: 2} # 小 df['cluster'] = df['cluster_raw'].map(rank_map) CLUSTER_NAMES = {0: '介護負担大群\n(高齢化先行型)', 1: '介護負担中群\n(移行型)', 2: '介護負担小群\n(都市・若年型)'} CLUSTER_COLORS = {0: '#E53935', 1: '#FB8C00', 2: '#43A047'} print("\n=== クラスター別プロファイル ===") print(df.groupby('cluster')[['介護看護時間', '高齢化率', '医師数_10万対']].mean().round(2)) |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。df.groupby('列').apply(関数) — グループごとに関数を適用。時系列や地域別の集計でよく使います。sort_values('列名', ascending=False) — 指定列で並べ替え(降順)。r, p = stats.pearsonr(...) — Pythonは複数戻り値を同時に受け取れる(タプルアンパック)。253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 | # ── Figure 4: クラスター分析結果 ──────────────────────────────────── fig, axes = plt.subplots(1, 2, figsize=(13, 5.5)) # 左: 散布図(高齢化率 vs 介護・看護時間) ax = axes[0] for cl, name in CLUSTER_NAMES.items(): mask = df['cluster'] == cl ax.scatter(df[mask]['高齢化率'], df[mask]['介護看護時間'], color=CLUSTER_COLORS[cl], label=name.replace('\n', ' '), s=80, alpha=0.8, edgecolors='white', lw=0.5) ax.set_xlabel('高齢化率(%)') ax.set_ylabel('介護・看護時間(分/日)') ax.set_title('高齢化率と介護・看護時間の関係\n(k-meansクラスタリング)', fontsize=12, fontweight='bold') ax.legend(fontsize=9, title='クラスター') |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。fig, ax = plt.subplots(...) — 図全体(fig)と軸(ax)を作る定番。以降は ax.bar(...) 等で操作。x if cond else y は三項演算子。リスト内包表記と組み合わせると、forとifを1行で書けます。269 270 271 272 273 274 275 | # 全体回帰線 z = np.polyfit(df['高齢化率'], df['介護看護時間'], 1) p_line = np.poly1d(z) x_line = np.linspace(df['高齢化率'].min(), df['高齢化率'].max(), 100) ax.plot(x_line, p_line(x_line), '--', color='gray', lw=1.5, alpha=0.7, label='全体回帰線') ax.legend(fontsize=9, title='クラスター') |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。df[col](1列)と df[[col1, col2]](複数列)でカッコの数が違います。リストを渡していると覚えるとミスを減らせます。276 277 278 279 280 281 282 283 | # 注目都道府県 for _, row in df.iterrows(): if row['pref'] in ['東京都', '秋田県', '島根県', '沖縄県']: ax.annotate(row['pref'].replace('県', '').replace('都', '').replace('府', ''), (row['高齢化率'], row['介護看護時間']), fontsize=8, fontweight='bold', color='#333', xytext=(5, 4), textcoords='offset points', arrowprops=dict(arrowstyle='->', color='#666', lw=0.7)) |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。for _, row in df.iterrows() — DataFrameを1行ずつ取り出すループ。1点ずつ描画したいときに使用。s[:-n]「末尾n文字を除く」/s[n:]「先頭n文字を除く」。スライス [start:stop:step] はリスト・タプル・文字列共通の基本ワザです。284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 | # 右: クラスター別変数プロファイル(棒グラフ) ax2 = axes[1] cl_profile = df.groupby('cluster')[ ['介護看護時間'] + [c + '_z' for c in FEAT_COLS[:3]] ].mean() x = np.arange(3) width = 0.22 plot_vars = ['介護看護時間', '高齢化率_z', '医師数_10万対_z', '病院数_10万対_z'] pv_labels = ['介護・看護時間\n(実値/分)', '高齢化率\n(標準化)', '医師数10万対\n(標準化)', '病院数10万対\n(標準化)'] pv_colors = ['#1565C0', '#43A047', '#E53935', '#FB8C00'] for i, (var, lab, col) in enumerate(zip(plot_vars, pv_labels, pv_colors)): vals = [cl_profile.loc[c, var] if c in cl_profile.index else 0 for c in range(3)] ax2.bar(x + (i - 1.5) * width, vals, width=width, label=lab, color=col, alpha=0.8) ax2.set_xticks(x) ax2.set_xticklabels([CLUSTER_NAMES[c].replace('\n', ' ') for c in range(3)], fontsize=9) ax2.axhline(0, color='black', lw=0.8) ax2.set_title('クラスター別変数プロファイル', fontsize=12, fontweight='bold') ax2.legend(fontsize=8, ncol=2) ax2.set_ylabel('値(介護・看護時間: 分、その他: 標準化値)') plt.tight_layout() plt.savefig(os.path.join(FIG_DIR, '2025_H4_fig4_cluster.png'), bbox_inches='tight') plt.close() |
Figure 3 saved.
=== クラスター別プロファイル ===
介護看護時間 高齢化率 医師数_10万対
cluster
0 3.20 33.38 317.81
1 3.12 30.12 258.84
2 2.00 22.81 342.63df.groupby('列').apply(関数) — グループごとに関数を適用。時系列や地域別の集計でよく使います。ax.axhline / ax.axvline — 水平/垂直の点線。平均線や基準線として定番。fig.savefig(..., bbox_inches='tight') — 余白を自動で詰めて保存。plt.close() でメモリ解放。np.cumsum(arr) は累積和、np.linspace(a, b, n) は「aからbを等間隔でn個」。NumPyの定石です。
以下のファイルをダウンロードして同じフォルダに置き、
python 2025_H4_katsuyo.py を実行すると全図を再現できます。
SSDSE-B-2026.csv を読み込んで相関分析・重回帰分析・k-meansクラスタリング・全図を生成。
必要ライブラリ: numpy, pandas, matplotlib, statsmodels, scikit-learn
| データ | 出典 |
|---|---|
| 介護職の離職率(2024年) | 介護労働実態調査, 公益財団法人介護労働安定センター |
| 75歳以上人口・一般病院数 | SSDSE-B(統計でみる都道府県のすがた)2026年版, 統計数理研究所 |
| 介護活動経験率・平均時間 | 社会生活統計指標(SSDSE-D), 統計数理研究所 |
統計分析の解釈で初心者がやりがちな勘違いをまとめます。特に「相関と因果の混同」「p値の過信」は研究現場でもよく起きる落とし穴です。本文を読む前にも、読んだ後にも、目を通してみてください。
統計の基本用語を初心者向けに解説します。本文中で見慣れない言葉が出てきたら、ここに戻って確認してください。
統計手法について「何のためか」「結果をどう読むか」を初心者向けに解説します。
この研究をさらに発展させるための3つの方向性を示します。「今回わかったこと(X)」から「次に検証すべき仮説(Y)」を立て、「具体的に何をするか(Z)」まで考えてみましょう。
学んだだけでは身につきません。実際に手を動かすのが最強の学習方法です。本論文のスクリプトをベースに、以下のチャレンジに挑戦してみてください。難易度別に5つ用意しました。
本論文で学んだ手法は、研究の世界だけでなく、行政・企業・NPO の現場でも様々に活用されています。具体的なシーンを紹介します。
この論文を読んで初心者が抱きやすい疑問に、教育的観点から答えます。
この論文を理解できたか、クリックで確認しましょう。間違えても解説が出ます。
このページの分析は、この画面の中でそのまま実行できます。
Python をインストールする必要も、CSV をダウンロードする必要もありません。
下のセルの 「▶ ブラウザで実行」 を上から順に押すか、
「▶ 最初から全部実行」 で一気に流してください。
表示されるのは、本文の図表とまったく同じ計算の結果です
(動かしているのは再現スクリプト code/2025_H4_katsuyo.py そのもの)。
コードは書き換えられます。「✏️ 書き換える」で数字や変数を変えて ▶ を押すと、 結果がどう変わるかをその場で確かめられます(元に戻すボタンつき)。 ページを開いた時点で裏で実行環境の準備が始まっているので、待ち時間はほとんどありません。 初回だけ実行環境の取得にインターネット接続が必要です。