このページの分析を自分で再現するには、以下の手順でデータを準備してください。コードの編集は不要です。
data/raw/ フォルダに入れます。html/figures/ に自動保存されます。
日本は人口100万人あたりのMRI設置台数が世界最多クラスです。 OECD Health Statistics(2021)によると、日本のMRI密度は約57台/100万人で、 OECD平均(約19台)の3倍に達します。
まず「日本におけるMRI設置の現状と過剰導入の実証的検証」を統計的にとらえることが有効だと考えられる。 その理由は感覚や経験則だけでは、複雑な社会要因の中で「何が本当に効いているか」を見極めにくいからである。 本研究では公開データと統計手法を組み合わせ、この問いに定量的な答えを出すことを目指す。
本論文はBresnahan & Reiss (1991)の産業組織論的エントリーモデルを 医療機器市場に適用し、二次医療圏・市区町村を「市場」と定義して、 人口規模とMRI設置台数の関係から必要人口閾値を推定・比較することで 過剰設置を実証しようとしたものです。
Bresnahan & Reiss (1991) は、競争市場への参入(entry)行動を構造モデルで分析した先駆的論文です。 鍵となる洞察は:
MRI台数を5カテゴリ(0台, 1-2台, 3-5台, 6-10台, 11台以上)に離散化し、順序プロビットで推定します。
通常の回帰分析(縮約型)は「何が相関しているか」を推定しますが、 BR型の構造モデルは「利潤関数の形状」という経済理論から导かれるパラメータを推定します。 構造モデルは反実仮想(「もし規制があったら?」)に答えられる点が強みです。
| データソース | 内容 | 取得方法 |
|---|---|---|
| 病床機能報告(施設票) | 病院・二次医療圏別のMRI台数(3T・1.5T・1.5T未満)、病院数 | 厚生労働省 病床機能報告制度(2017・2018・2020年度) |
| SSDSE-A-2025 | 市区町村別総人口 | 統計センター(教育用標準データセット) |
| 変数名 | 定義 | 単位 |
|---|---|---|
mri_total | 二次医療圏内の総MRI台数(施設票から集計) | 台 |
mri_3t | 3T MRI台数(高精度機) | 台 |
mri_15t | 1.5T MRI台数(標準機) | 台 |
mri_bin | 台数カテゴリ(0/1/2/3/4 = 0/1-2/3-5/6-10/11+) | — |
population | 二次医療圏内推計人口 | 人 |
ln_pop | 人口の自然対数 | — |
mri_per_100k | 人口10万人あたりMRI台数 | 台/10万人 |
n_hospitals | 二次医療圏内の病院数 | 施設 |
1 2 3 4 5 6 7 8 9 10 11 12 13 | import os import warnings import numpy as np import pandas as pd import matplotlib matplotlib.use('Agg') import matplotlib.pyplot as plt import matplotlib.ticker as mticker from scipy import stats from scipy.optimize import minimize from scipy.special import ndtr # Φ(x) warnings.filterwarnings('ignore') |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。import pandas as pd など — 必要なライブラリをまとめて呼び出します。as pd は短い別名(alias)。matplotlib.use('Agg') — グラフを画面表示せずファイルに保存するためのおまじない。f"...{x}..." はf-string。文字列の中に {変数} と書くだけで埋め込めて、{x:.2f} のように書式も指定できます。14 15 16 17 18 19 20 21 22 23 24 25 | # ── パス設定 ────────────────────────────────────────────────────────── DATA_DIR = 'data/2025_U4' 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ループ不要なのが強み。26 27 28 29 30 31 32 33 | # ── データ読み込み ───────────────────────────────────────────────────── df = pd.read_csv(os.path.join(DATA_DIR, '2025_U4_panel.csv')) df = df.dropna(subset=['population', 'mri_total']) df['ln_pop'] = np.log(df['population'].clip(lower=1)) df['ln_pop65'] = np.log((df['population'] * 0.28).clip(lower=1)) # 高齢化率約28%を推計に使用 print(f"データ: {len(df)}レコード, {df['year'].nunique()}年度") print(f"MRI台数統計:\n{df['mri_total'].describe()}\n") |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。pd.read_csv(...) でCSVを読み込みます。encoding='cp932' は日本語Windows由来の文字コード、header=1 は「2行目を列名として使う」。.describe() — 件数・平均・標準偏差・四分位・最大/最小を一括計算。データの素性チェックに必須。.map() は「1対1の置き換え」、.apply() は「関数を当てる」。辞書なら .map()、ロジックなら .apply()。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 | # ── Figure 1: MRI台数分布(年度別) ────────────────────────────────── fig, axes = plt.subplots(1, 3, figsize=(14, 4), sharey=True) YEARS = [2017, 2018, 2020] colors = ['#2196F3', '#4CAF50', '#FF9800'] bins_label = ['0', '1-2', '3-5', '6-10', '11+'] for ax, year, col in zip(axes, YEARS, colors): d = df[df['year'] == year] counts = [ (d['mri_total'] == 0).sum(), ((d['mri_total'] >= 1) & (d['mri_total'] <= 2)).sum(), ((d['mri_total'] >= 3) & (d['mri_total'] <= 5)).sum(), ((d['mri_total'] >= 6) & (d['mri_total'] <= 10)).sum(), (d['mri_total'] >= 11).sum(), ] bars = ax.bar(bins_label, counts, color=col, alpha=0.85, edgecolor='white', linewidth=0.8) for bar, cnt in zip(bars, counts): ax.text(bar.get_x() + bar.get_width()/2, bar.get_height() + 1, str(cnt), ha='center', va='bottom', fontsize=9) ax.set_title(f'{year}年度', fontsize=12, fontweight='bold') ax.set_xlabel('MRI台数(二次医療圏)', fontsize=10) ax.set_ylim(0, max(counts) * 1.18) axes[0].set_ylabel('二次医療圏数', fontsize=10) fig.suptitle('二次医療圏別MRI台数分布(2017〜2020年度)', fontsize=13, fontweight='bold', y=1.01) fig.tight_layout() fig.savefig(os.path.join(FIG_DIR, '2025_U4_dist.png'), bbox_inches='tight', dpi=150) plt.close(fig) |
データ: 980レコード, 3年度 MRI台数統計: count 980.000000 mean 12.982653 std 14.373918 min 0.000000 25% 4.000000 50% 8.000000 75% 16.000000 max 129.000000 Name: mri_total, dtype: float64
fig, ax = plt.subplots(...) — 図全体(fig)と軸(ax)を作る定番。以降は ax.bar(...) 等で操作。fig.savefig(..., bbox_inches='tight') — 余白を自動で詰めて保存。plt.close() でメモリ解放。[式 for x in リスト] はリスト内包表記。forループでappendする代わりに1行でリストを作れます。62 63 64 65 66 67 | print("Figure 1 保存: 2025_U4_dist.png") # ── Figure 2: 人口 vs MRI台数 散布図(2020年) ─────────────────────── d20 = df[df['year'] == 2020].copy() fig, axes = plt.subplots(1, 2, figsize=(12, 5)) |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。fig, ax = plt.subplots(...) — 図全体(fig)と軸(ax)を作る定番。以降は ax.bar(...) 等で操作。df['A'] / df['B'] — pandasの列同士の四則演算は要素ごと(element-wise)。forループ不要なのが強み。68 69 70 71 72 73 74 75 76 77 | # 左: 散布図(生値) ax = axes[0] sc = ax.scatter(d20['population'] / 1e4, d20['mri_total'], alpha=0.5, s=30, c=d20['mri_total'], cmap='YlOrRd', vmin=0, vmax=40, edgecolors='none') plt.colorbar(sc, ax=ax, label='MRI台数') ax.set_xlabel('人口(万人)', fontsize=11) ax.set_ylabel('MRI台数', fontsize=11) ax.set_title('人口規模とMRI台数(2020年)', fontsize=12, fontweight='bold') ax.set_xlim(0, d20['population'].max() / 1e4 * 1.05) |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。.map() は「1対1の置き換え」、.apply() は「関数を当てる」。辞書なら .map()、ロジックなら .apply()。78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 | # 右: 対数スケール ax = axes[1] ax.scatter(d20['ln_pop'], d20['mri_total'], alpha=0.5, s=30, c='#1976D2', edgecolors='none') z = np.polyfit(d20['ln_pop'].dropna(), d20.loc[d20['ln_pop'].notna(), 'mri_total'], 1) p = np.poly1d(z) xr = np.linspace(d20['ln_pop'].min(), d20['ln_pop'].max(), 100) ax.plot(xr, p(xr), 'r-', linewidth=1.5, label=f'線形近似 (β={z[0]:.2f})') ax.set_xlabel('ln(人口)', fontsize=11) ax.set_ylabel('MRI台数', fontsize=11) ax.set_title('対数人口とMRI台数(2020年)', fontsize=12, fontweight='bold') ax.legend(fontsize=10) fig.tight_layout() fig.savefig(os.path.join(FIG_DIR, '2025_U4_scatter.png'), bbox_inches='tight', dpi=150) plt.close(fig) |
Figure 1 保存: 2025_U4_dist.png
fig.savefig(..., bbox_inches='tight') — 余白を自動で詰めて保存。plt.close() でメモリ解放。[式 for x in リスト] はリスト内包表記。forループでappendする代わりに1行でリストを作れます。94 95 96 97 98 99 | print("Figure 2 保存: 2025_U4_scatter.png") # ── 順序プロビット推定(Ordered Probit)────────────────────────────── print("\n" + "=" * 55) print("■ 順序プロビット推定(Bresnahan-Reiss型)") print("=" * 55) |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。.map() は「1対1の置き換え」、.apply() は「関数を当てる」。辞書なら .map()、ロジックなら .apply()。100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 | # 2020年データで推定 d = d20.copy() d = d.dropna(subset=['mri_bin', 'ln_pop']) y = d['mri_bin'].values.astype(int) # 0,1,2,3,4 X = d['ln_pop'].values # K=5カテゴリのordered probit # P(Y=k) = Φ(μ_k - β*X) - Φ(μ_{k-1} - β*X) # パラメータ: β, μ_1, δ_2=μ_2-μ_1>0, δ_3>0, δ_4>0 def op_loglik(params, y, X): beta = params[0] mu1 = params[1] d2 = np.exp(params[2]) d3 = np.exp(params[3]) d4 = np.exp(params[4]) mu2 = mu1 + d2 mu3 = mu2 + d3 mu4 = mu3 + d4 thresholds = [-np.inf, mu1, mu2, mu3, mu4, np.inf] lp = beta * X ll = 0.0 for k in range(5): idx = y == k if idx.sum() == 0: continue p = ndtr(thresholds[k+1] - lp[idx]) - ndtr(thresholds[k] - lp[idx]) p = np.clip(p, 1e-15, 1.0) ll += np.log(p).sum() return -ll |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。.astype(int) — 列を整数に変換(年度などを数値比較するため)。[式 for x in リスト] はリスト内包表記。forループでappendする代わりに1行でリストを作れます。130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 | # 初期値 p0 = [1.0, 5.0, np.log(1.5), np.log(1.5), np.log(1.5)] result = minimize(op_loglik, p0, args=(y, X), method='Nelder-Mead', options={'maxiter': 20000, 'xatol': 1e-8, 'fatol': 1e-8}) beta_hat = result.x[0] mu1_hat = result.x[1] mu2_hat = mu1_hat + np.exp(result.x[2]) mu3_hat = mu2_hat + np.exp(result.x[3]) mu4_hat = mu3_hat + np.exp(result.x[4]) print(f"\n 係数 β (ln_pop): {beta_hat:.4f}") print(f" 閾値 μ₁: {mu1_hat:.4f}") print(f" 閾値 μ₂: {mu2_hat:.4f}") print(f" 閾値 μ₃: {mu3_hat:.4f}") print(f" 閾値 μ₄: {mu4_hat:.4f}") print(f" 対数尤度: {-result.fun:.2f}") |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。r, p = stats.pearsonr(...) — Pythonは複数戻り値を同時に受け取れる(タプルアンパック)。148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 | # 必要人口閾値(βが正なら μ_k/β で求まる) # P(Y>=k) >= 0.5 となる X = μ_k / β if beta_hat > 0: thresholds_pop = { 1: np.exp(mu1_hat / beta_hat), 2: np.exp(mu2_hat / beta_hat), 3: np.exp(mu3_hat / beta_hat), 4: np.exp(mu4_hat / beta_hat), } print(f"\n 【必要人口閾値(P(MRI≥N)=0.5)】") for n, pop in thresholds_pop.items(): print(f" MRI {n}台以上: {pop/1e4:.1f}万人") # 競争効果: 1台目→2台目の必要人口比 (BR比) # 1台あたりの必要人口増加率が1を超えると競争で利潤が圧縮されている ratios = {} pops = list(thresholds_pop.values()) for i in range(1, 4): ratios[i+1] = pops[i] / pops[i-1] print(f"\n 【競争効果(BR比): N台→N+1台の必要人口比】") for n, r in ratios.items(): print(f" {n-1}台→{n}台: {r:.3f} (>1.0 → 競争で利潤圧縮)") |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。x if cond else y は三項演算子。リスト内包表記と組み合わせると、forとifを1行で書けます。170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 | # ── Figure 3: 必要人口閾値と予測確率 ───────────────────────────────── fig, axes = plt.subplots(1, 2, figsize=(13, 5)) # 左: 予測確率曲線 ax = axes[0] ln_range = np.linspace(8, 14, 200) pop_range = np.exp(ln_range) / 1e4 mus = [mu1_hat, mu2_hat, mu3_hat, mu4_hat] mu_ext = [-np.inf] + mus + [np.inf] labels_p = ['0台', '1-2台', '3-5台', '6-10台', '11台+'] cm = plt.cm.get_cmap('tab10') for k in range(5): p_k = ndtr(mu_ext[k+1] - beta_hat * ln_range) - ndtr(mu_ext[k] - beta_hat * ln_range) ax.plot(pop_range, p_k, label=labels_p[k], linewidth=2, color=cm(k)) ax.set_xlabel('人口(万人)', fontsize=11) ax.set_ylabel('確率', fontsize=11) ax.set_title('MRI台数カテゴリの予測確率\n(順序プロビット)', fontsize=11, fontweight='bold') ax.legend(fontsize=9, loc='center right') ax.set_xlim(0, 250) ax.set_ylim(0, 1) |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。fig, ax = plt.subplots(...) — 図全体(fig)と軸(ax)を作る定番。以降は ax.bar(...) 等で操作。df[col](1列)と df[[col1, col2]](複数列)でカッコの数が違います。リストを渡していると覚えるとミスを減らせます。190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 | # 右: 必要人口閾値の棒グラフ(競争効果可視化) ax = axes[1] if beta_hat > 0: ns = list(thresholds_pop.keys()) pops_wan = [v / 1e4 for v in thresholds_pop.values()] bars = ax.bar([f'{n}台以上' for n in ns], pops_wan, color=['#42A5F5', '#26C6DA', '#66BB6A', '#FFA726'], edgecolor='white', linewidth=0.8, alpha=0.9) for bar, v in zip(bars, pops_wan): ax.text(bar.get_x() + bar.get_width()/2, bar.get_height() + 0.5, f'{v:.1f}万人', ha='center', va='bottom', fontsize=9) ax.set_ylabel('必要人口(万人)', fontsize=11) ax.set_title('MRI設置に必要な人口閾値\n(Bresnahan-Reiss推定値)', fontsize=11, fontweight='bold') fig.tight_layout() fig.savefig(os.path.join(FIG_DIR, '2025_U4_br.png'), bbox_inches='tight', dpi=150) plt.close(fig) |
Figure 2 保存: 2025_U4_scatter.png ======================================================= ■ 順序プロビット推定(Bresnahan-Reiss型) ======================================================= 係数 β (ln_pop): 2.0918 閾値 μ₁: 18.5179 閾値 μ₂: 22.5217 閾値 μ₃: 24.1827 閾値 μ₄: 25.8013 対数尤度: -224.43 【必要人口閾値(P(MRI≥N)=0.5)】 MRI 1台以上: 0.7万人 MRI 2台以上: 4.7万人 MRI 3台以上: 10.5万人 MRI 4台以上: 22.7万人 【競争効果(BR比): N台→N+1台の必要人口比】 1台→2台: 6.780 (>1.0 → 競争で利潤圧縮) 2台→3台: 2.212 (>1.0 → 競争で利潤圧縮) 3台→4台: 2.168 (>1.0 → 競争で利潤圧縮)
fig.savefig(..., bbox_inches='tight') — 余白を自動で詰めて保存。plt.close() でメモリ解放。s[:-n]「末尾n文字を除く」/s[n:]「先頭n文字を除く」。スライス [start:stop:step] はリスト・タプル・文字列共通の基本ワザです。208 209 210 211 212 213 214 215 | print("\nFigure 3 保存: 2025_U4_br.png") # ── Figure 4: 地域別過剰設置マップ(散布図で代替)────────────────── fig, ax = plt.subplots(figsize=(10, 6)) d20 = d20.copy() d20['expected_bin'] = (beta_hat * d20['ln_pop'] - mu1_hat) / max(abs(beta_hat), 0.01) d20['excess'] = d20['mri_total'] - d20['mri_bin'] # 実際 - ビン中央 |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。fig, ax = plt.subplots(...) — 図全体(fig)と軸(ax)を作る定番。以降は ax.bar(...) 等で操作。[式 for x in リスト] はリスト内包表記。forループでappendする代わりに1行でリストを作れます。216 217 218 219 220 221 222 | # 人口 vs mri_per_100k(人口100万人あたりMRI台数)で過剰を可視化 sc = ax.scatter(d20['population'] / 1e4, d20['mri_per_100k'], c=d20['mri_per_100k'], cmap='RdYlGn_r', s=50, alpha=0.7, edgecolors='none', vmin=0, vmax=d20['mri_per_100k'].quantile(0.95)) plt.colorbar(sc, ax=ax, label='MRI台数/人口10万人') |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。r, p = stats.pearsonr(...) — Pythonは複数戻り値を同時に受け取れる(タプルアンパック)。223 224 225 226 227 228 229 230 231 232 233 | # 日本平均線 avg_per100k = df[df['year'] == 2020]['mri_per_100k'].mean() ax.axhline(avg_per100k, color='red', linewidth=1.5, linestyle='--', label=f'全国平均 {avg_per100k:.1f}台/10万人') ax.set_xlabel('人口(万人)', fontsize=11) ax.set_ylabel('MRI台数(人口10万人あたり)', fontsize=11) ax.set_title('人口規模と人口当たりMRI密度(2020年)\n色が赤いほどMRI密度が高い', fontsize=11, fontweight='bold') ax.legend(fontsize=10) ax.set_xlim(0, d20['population'].max() / 1e4 * 1.05) |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。ax.axhline / ax.axvline — 水平/垂直の点線。平均線や基準線として定番。x if cond else y は三項演算子。リスト内包表記と組み合わせると、forとifを1行で書けます。234 235 236 237 238 239 240 241 242 243 | # 上位二次医療圏にラベル top = d20.nlargest(5, 'mri_per_100k') for _, row in top.iterrows(): ax.annotate(row['sec_name'], (row['population']/1e4, row['mri_per_100k']), fontsize=7, alpha=0.8, xytext=(5, 2), textcoords='offset points') fig.tight_layout() fig.savefig(os.path.join(FIG_DIR, '2025_U4_excess.png'), bbox_inches='tight', dpi=150) plt.close(fig) |
Figure 3 保存: 2025_U4_br.png
for _, row in df.iterrows() — DataFrameを1行ずつ取り出すループ。1点ずつ描画したいときに使用。fig.savefig(..., bbox_inches='tight') — 余白を自動で詰めて保存。plt.close() でメモリ解放。df[col](1列)と df[[col1, col2]](複数列)でカッコの数が違います。リストを渡していると覚えるとミスを減らせます。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 277 278 279 | print("Figure 4 保存: 2025_U4_excess.png") # ── Figure 5: 年度変化(MRI台数の推移) ────────────────────────────── fig, axes = plt.subplots(1, 2, figsize=(12, 5)) # 共通の二次医療圏のみ追跡(2017〜2020で重複する sec_code) common_secs = set(df[df['year']==2017]['sec_code']) & \ set(df[df['year']==2018]['sec_code']) & \ set(df[df['year']==2020]['sec_code']) df_common = df[df['sec_code'].isin(common_secs)].copy() pivot = df_common.pivot_table(index='sec_code', columns='year', values='mri_total') ax = axes[0] yr_totals = df.groupby('year')['mri_total'].sum() ax.bar([str(y) for y in yr_totals.index], yr_totals.values, color=['#2196F3', '#4CAF50', '#FF9800'], alpha=0.85, edgecolor='white') for i, (yr, v) in enumerate(yr_totals.items()): ax.text(i, v + 20, f'{v:,}台', ha='center', va='bottom', fontsize=11) ax.set_ylabel('MRI台数合計', fontsize=11) ax.set_title('全国MRI台数の推移(二次医療圏集計)', fontsize=11, fontweight='bold') ax = axes[1] change_17_20 = pivot[2020] - pivot[2017] ax.hist(change_17_20.dropna(), bins=20, color='#7B1FA2', alpha=0.75, edgecolor='white') ax.axvline(0, color='red', linewidth=1.5, linestyle='--') ax.set_xlabel('MRI台数変化(2017→2020)', fontsize=11) ax.set_ylabel('二次医療圏数', fontsize=11) ax.set_title('MRI台数変化の分布(2017→2020)', fontsize=11, fontweight='bold') mean_change = change_17_20.mean() ax.axvline(mean_change, color='blue', linewidth=1.5, linestyle=':', label=f'平均変化 {mean_change:.1f}台') ax.legend(fontsize=10) fig.tight_layout() fig.savefig(os.path.join(FIG_DIR, '2025_U4_trend.png'), bbox_inches='tight', dpi=150) plt.close(fig) |
Figure 4 保存: 2025_U4_excess.png
df.groupby('列').apply(関数) — グループごとに関数を適用。時系列や地域別の集計でよく使います。fig, ax = plt.subplots(...) — 図全体(fig)と軸(ax)を作る定番。以降は ax.bar(...) 等で操作。ax.axhline / ax.axvline — 水平/垂直の点線。平均線や基準線として定番。fig.savefig(..., bbox_inches='tight') — 余白を自動で詰めて保存。plt.close() でメモリ解放。r, p = stats.pearsonr(...) — Pythonは複数戻り値を同時に受け取れる(タプルアンパック)。280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 | print("Figure 5 保存: 2025_U4_trend.png") # ── 記述統計サマリー ────────────────────────────────────────────────── print("\n" + "=" * 55) print("■ 記述統計(2020年)") print("=" * 55) desc_cols = ['mri_total', 'mri_3t', 'mri_15t', 'population', 'mri_per_100k'] print(d20[desc_cols].describe().round(2).to_string()) print(f"\n [MRI密度] 全国平均: {avg_per100k:.2f}台/10万人") print(f" [最大] {d20.loc[d20['mri_per_100k'].idxmax(), 'sec_name']}: " f"{d20['mri_per_100k'].max():.1f}台/10万人") print(f" [台数最大] {d20.loc[d20['mri_total'].idxmax(), 'sec_name']}: " f"{d20['mri_total'].max()}台") print("\n■ 完了。全figureを html/figures/ に保存しました。") |
Figure 5 保存: 2025_U4_trend.png
=======================================================
■ 記述統計(2020年)
=======================================================
mri_total mri_3t mri_15t population mri_per_100k
count 329.00 329.00 329.00 329.00 329.00
mean 13.38 2.88 8.97 287859.69 6.09
std 14.82 4.24 9.66 314641.77 8.30
min 0.00 0.00 0.00 7042.00 0.00
25% 4.00 0.00 3.00 79904.00 3.41
50% 9.00 2.00 6.00 178044.00 4.40
75% 16.00 4.00 11.00 377040.00 6.03
max 128.00 36.00 86.00 1993903.00 77.87
[MRI密度] 全国平均: 6.09台/10万人
[最大] 福岡・糸島: 77.9台/10万人
[台数最大] 札幌: 128台
■ 完了。全figureを html/figures/ に保存しました。.describe() — 件数・平均・標準偏差・四分位・最大/最小を一括計算。データの素性チェックに必須。x if cond else y は三項演算子。リスト内包表記と組み合わせると、forとifを1行で書けます。
3年間を通じて、11台以上の大規模設置圏が最多を占め、次いで6-10台の中規模圏が続きます。 0台または1-2台の小規模圏(農村部・離島等)は少数派です。 2017年から2020年にかけて、高台数カテゴリへのシフトが確認でき、 全体的なMRI設置の増加傾向を反映しています。
前節のMRI台数分布が大きく右に歪み、一部の圏域に台数が集中している結果を踏まえると、 「人口で説明できる部分」と「説明できない過剰部分」が混在すると考えられる。 これを切り分ける必要があるが、その手法として対数人口とMRI台数の散布図と線形近似に着目した。 回帰直線より上方に位置する圏域が「過剰設置」候補として浮かび上がる結果が期待される。
人口とMRI台数には明確な正の相関があります(右図の線形近似係数β≈9.5)。 ただし、同規模の人口でもMRI台数に大きなばらつきがあり、 一部の圏域では人口規模から期待される水準を大きく超えた台数が設置されています。 これが「過剰設置」の候補地域です。
前節の散布図で人口とMRI台数に正相関が見えるが、ばらつきも大きい結果を踏まえると、 競争が利潤を圧縮することで、台数が増えるほど必要人口閾値が非線形に上昇すると考えられる。 これを検証する必要があるが、その手法としてBresnahan-Reiss型エントリーモデル(順序プロビット)に着目した。 推定したBR比が1を大きく超え、過剰参入のインセンティブが定量的に裏付けられる結果が期待される。
| パラメータ | 推定値 | 解釈 |
|---|---|---|
| β (ln_pop係数) | 2.09 | 人口が1%増えるとMRI台数カテゴリが上昇する弾力性 |
| μ₁ (閾値1) | 18.52 | 0台→1-2台カテゴリへの転換点 |
| μ₂ (閾値2) | 22.52 | 1-2台→3-5台への転換点 |
| μ₃ (閾値3) | 24.18 | 3-5台→6-10台への転換点 |
| μ₄ (閾値4) | 25.80 | 6-10台→11台以上への転換点 |
| 対数尤度 | −224.4 | モデルの当てはまり |
| MRI台数 | 必要人口(万人) | BR比(前段階比) | 解釈 |
|---|---|---|---|
| 1台以上 | 0.7万人 | — | 小規模市場でも1台は設置可能 |
| 2台以上 | 4.7万人 | 6.78 | 競争効果が大きい(1台目の7倍の人口が必要) |
| 3台以上 | 10.5万人 | 2.21 | 競争で利潤が圧縮されている |
| 4台以上 | 22.7万人 | 2.17 | さらに市場規模が必要 |
順序プロビットで推定した閾値 μ_k と係数 β から、 「P(Y≥k) = 0.5」となる人口規模(中央値閾値)を以下で求めます:
人口10万人あたりMRI台数(MRI密度)で見ると、全国平均は約6.1台ですが、 一部の圏域では大幅に平均を超えた高密度設置が確認されます。
論文では市区町村レベルのより細かい分析も実施し、 「人口規模から予測される必要台数」を大きく超えた自治体を過剰設置地域として特定しています。
全国のMRI台数総計は2017年の4,730台から2020年の4,855台へと約2.6%増加しています。 二次医療圏別の変化をみると、大半の圏域では変化がほとんどなく、 一部の圏域で台数が増加しているという右歪みの分布が確認されます。
ここまでのBR比1超および高密度設置圏の存在という結果を踏まえると、 MRI市場には競争による過剰参入のインセンティブが働いていると考えられる。 実務的には地域医療構想による設備配置のガイドライン整備や、二次医療圏単位での共同利用促進が必要であり、 本節ではエビデンスに基づく政策パッケージの方向性を整理する。
分析結果は、日本のMRI設置市場に競争的な過剰参入が存在することを示唆します。 日本では「医療計画」によりCT・MRIの設置数に整備目標は設定されていますが、 法的な台数規制は存在せず、病院が自由に導入できます。
以下のファイルをダウンロードして、自分でも分析を再現できます。
data/2025_U4/とcode/を同じディレクトリに配置して実行してください。
| ファイル | 内容 | サイズ目安 | ダウンロード |
|---|---|---|---|
2025_U4_panel.csv |
二次医療圏×3年パネルデータ(MRI台数・人口) | 約60KB | CSV |
2025_U4_data_prep.py |
病床機能報告→CSVデータ前処理スクリプト | 約8KB | Python |
2025_U4_katsuyo.py |
BR型順序プロビット分析・図表生成スクリプト | 約10KB | Python |
| データ | 入手先 | ファイル形式 |
|---|---|---|
| 病床機能報告(施設票) | 厚生労働省 病床機能報告制度(各年度) | Excel (.xlsx) |
| SSDSE-A-2025 | 統計センター(ssdse.nstac.go.jp) | CSV |
pip install pandas numpy scipy matplotlib openpyxl # データ前処理(hospital_2017/2018/2020.xlsx が data/raw/mri/ に必要) python3 code/2025_U4_data_prep.py # 分析・図表生成(data/2025_U4/2025_U4_panel.csv が必要) python3 code/2025_U4_katsuyo.py
統計分析の解釈で初心者がやりがちな勘違いをまとめます。特に「相関と因果の混同」「p値の過信」は研究現場でもよく起きる落とし穴です。本文を読む前にも、読んだ後にも、目を通してみてください。
統計の基本用語を初心者向けに解説します。本文中で見慣れない言葉が出てきたら、ここに戻って確認してください。
統計手法について「何のためか」「結果をどう読むか」を初心者向けに解説します。
この研究をさらに発展させるための3つの方向性を示します。「今回わかったこと(X)」から「次に検証すべき仮説(Y)」を立て、「具体的に何をするか(Z)」まで考えてみましょう。
学んだだけでは身につきません。実際に手を動かすのが最強の学習方法です。本論文のスクリプトをベースに、以下のチャレンジに挑戦してみてください。難易度別に5つ用意しました。
本論文で学んだ手法は、研究の世界だけでなく、行政・企業・NPO の現場でも様々に活用されています。具体的なシーンを紹介します。
この論文を読んで初心者が抱きやすい疑問に、教育的観点から答えます。