この教材は原論文と同じデータを読み込み、同じ手順で計算し直しています。画面に出る数値と図は、あなたのブラウザがその場で計算した結果です。
⚠ ただし一部は再現していません:原論文 図1 の全国学力調査正答率は SSDSE に収録されていないため、 進学率×正答率の散布図(相関係数 0.226)は再現していません。 それ以外は同じデータで再計算しています。
| 原論文が使ったデータ | SSDSE-A・国勢調査報告(2015)・経済センサス-基礎調査(2014)・全国都道府県市区町村別面積調(2016) 分析単位:市区町村 中核手法:マルチレベル分析 |
|---|---|
| この教材が使うデータ |
|
| 原論文(PDF) | マルチレベル分析を用いた市町村大学等進学率の決定要因分析 統計数理賞/松本 洋輔(一橋大学経済学部) |
▶ ここに書いてある分析は、ページ下部の「🐍 ブラウザで動かす」でそのまま実行できます(インストール不要)。動かしているのは code/2019_U3_suri.py(235 行)そのものです。
このページの実データ計算(図1・図4・中心化デモ)を自分で動かすには、以下の手順でデータを準備してください。コードの編集は不要です。
data/raw/ フォルダに入れます。html/figures/ に自動保存されます。
※ 原論文は SSDSE と e-Stat(学校基本調査・市町村税課税状況等の調・国勢調査・経済センサス基礎調査2014)・国土地理院のデータから「高等学校卒業生が存在する1304市町村」の階層データを構築しました。被説明変数の市町村別大学等進学率と一人当たり課税対象所得は現行 SSDSE-A-2025 に未収録のため、原論文のモデル(表7・表8)は再推定できません。本ページでは図2・図3を原論文の報告値の可視化(再計算ではない)、図1・図4をSSDSE の実データによる計算として明確に区別します(詳細は「データと変数」の再現範囲を参照)。
日本の大学等進学率※は戦後から上昇傾向を維持し、近年は全国平均で50%超を記録している。しかし進学率には無視できない大きさの地域格差が依然として存在する。興味深いことに、原論文が図1で示すとおり、都道府県別の大学等進学率と中学校第3学年対象の全国学力調査正答率の相関係数はわずか0.226——中学時代の学力では、この格差をほとんど説明できないのである。
※ 大学等進学率=短大への入学も含む進学率。多くの先行研究が用いる大学学部進学率ではなく、市町村単位で入手可能なデータの一貫性を保つためこちらを採用(原論文 脚注1)。原論文 図1(進学率×正答率の散布図)は、全国学力調査の正答率が SSDSE 未収録のため本ページでは再現していない。グラフは原論文参照。
進学率の地域格差は、機会均等や人材の有効活用の観点から数多く研究されてきた。上山(2011)は親の所得・職業・学歴や地域の大学収容率を、友田(1970)はそれらに加えて人口を、小林(2009)は大学への距離を要因として挙げる。大井(2013)は「経済的要因だけでは説明不十分」とし、地域特有の要因の存在を示唆した。しかし——これらはすべて都道府県単位の研究である。都道府県の内部にも都市部と郊外の格差が明らかに存在するのに、市町村単位の分析はされてこなかった。市町村単位の分析がなければ、都道府県がどのような市町村を重点的に支援すべきかも考察できない。
マルチレベル分析 ランダム切片モデル ランダム切片・傾きモデル 級内相関(ICC) 集団平均中心化
31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 | 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 import statsmodels.formula.api as smf plt.rcParams['font.family'] = 'Hiragino Sans' plt.rcParams['axes.unicode_minus'] = False plt.rcParams['figure.dpi'] = 150 FIG_DIR = 'html/figures' DATA_A = 'data/raw/SSDSE-A-2025.csv' DATA_B = 'data/raw/SSDSE-B-2026.csv' os.makedirs(FIG_DIR, exist_ok=True) |
print はしません。ライブラリの読み込みと図の保存先の設定だけです。次のステップへ進みましょう。statsmodels.formula.api の mixedlm が、本論文の核であるマルチレベルモデル(線形混合モデル)を推定する関数です(原論文は R 3.6.0 の lme4 系の枠組み、本ページは Python で同型のモデルを扱います)。matplotlib.use('Agg') は画面表示なしで PNG を保存するバックエンド指定。サーバやスクリプト実行では最初に呼ぶのが安全です。71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 | # ===== ステップ2: 図1 都道府県別の高等学校卒業者進学率(SSDSE-B 実データ) ===== # 原論文 図1 は「都道府県別大学等進学率 × 全国学力調査正答率」の散布図 # (相関係数 0.226 = 中学時代の学力では進学率格差をほぼ説明できない)。 # 正答率は SSDSE 未収録のため散布図は原論文参照とし、ここでは格差の存在 # そのもの(研究の出発点)を最新の実データで確認する。 dfB = pd.read_csv(DATA_B, encoding='cp932', header=1) dfB = dfB[dfB['地域コード'].str.match(r'^R\d{5}', na=False)].copy() latest_y = dfB['年度'].max() b = dfB[dfB['年度'] == latest_y].copy() b['高等学校卒業者進学率'] = b['高等学校卒業者のうち進学者数'] / b['高等学校卒業者数'] * 100 b = b.sort_values('高等学校卒業者進学率') fig, ax = plt.subplots(figsize=(8.5, 11)) cols = ['#C62828' if v >= b['高等学校卒業者進学率'].nlargest(5).min() else ('#1565C0' if v <= b['高等学校卒業者進学率'].nsmallest(5).max() else '#90A4AE') for v in b['高等学校卒業者進学率']] ax.barh(range(len(b)), b['高等学校卒業者進学率'], color=cols, height=0.65) ax.set_yticks(range(len(b))) ax.set_yticklabels(b['都道府県'], fontsize=9) mean_v = b['高等学校卒業者進学率'].mean() ax.axvline(mean_v, color='#555', lw=1, ls='--') ax.text(mean_v + 0.3, 0.5, f'47都道府県平均 {mean_v:.1f}%', fontsize=9, color='#555') ax.set_xlabel('高等学校卒業者進学率 [%](大学・短大等への進学)') ax.set_title(f'図1:都道府県別 高等学校卒業者進学率({latest_y}年度、SSDSE-B-2026 実データ)\n' '赤=上位5 / 青=下位5。原論文の問題意識である「進学率の地域格差」は現在も大きい\n' '(原論文 図1〔進学率×全国学力調査正答率の散布図、相関係数0.226〕は原論文参照)', fontsize=11) ax.grid(axis='x', alpha=0.3) fig.tight_layout() fig.savefig(f'{FIG_DIR}/2019_U3_fig1.png', bbox_inches='tight') plt.close(fig) gap = b['高等学校卒業者進学率'].max() - b['高等学校卒業者進学率'].min() print(f'【図1】{latest_y}年度の高等学校卒業者進学率(実データ): ' f'最高 {b.iloc[-1]["都道府県"]} {b["高等学校卒業者進学率"].max():.1f}% / ' f'最低 {b.iloc[0]["都道府県"]} {b["高等学校卒業者進学率"].min():.1f}% ' f'(差 {gap:.1f}ポイント)') |
【図1】2023年度の高等学校卒業者進学率(実データ): 最高 東京都 74.1% / 最低 沖縄県 46.7% (差 27.4ポイント)
Series.nlargest(5).min() で「上位5位の境界値」が取れます。ランキングの色分けによく使うイディオムです。市町村は都道府県の中に入れ子(階層)になっている。本研究では市町村をレベル1、都道府県をレベル2と考える。ここで、同じ都道府県に属する市町村は共通の都道府県要因の影響を受けるため、互いに似た振る舞いをする——つまり級内相関が生じる。
Yij は都道府県 j に属する市町村 i の大学等進学率、Xij は市町村レベルの説明変数、Rij は市町村レベルの残差。通常の回帰と決定的に違うのは、切片 β0j に添え字 j が付いていること——切片が都道府県ごとに変動する(=切片におけるランダム効果)。さらにレベル2の式で、その切片を都道府県レベル変数 Zj(大学収容率・距離)で予測できる部分と、未採択の都道府県要因 Uj に分解する。Uj の分散の変化を見ることで、採択した都道府県レベル変数が都道府県要因をどれだけ説明したかを確認できるのがこのモデルの強みである。
今度は傾き β1j にも添え字 j が付く。「所得が進学率に効く強さ」自体が都道府県によって異なることを許すモデルである。原論文のモデル4では、計算が収束しかつ当てはまりが改善した一人当たり課税対象所得の傾きのみにランダム効果を仮定した(他の変数の傾きと、都道府県レベル変数との交差レベル交互作用は考慮していない——原論文 脚注4)。
| モデル | 内容 | 目的 |
|---|---|---|
| モデル1 | 定数項のみのヌルモデル(NULL MODEL) | 級内相関(ICC)を計算し、都道府県要因の存在を確認 |
| モデル2 | +市町村レベルの説明変数4つ | 市町村レベル変数の影響を確認 |
| モデル3 | +都道府県レベルの説明変数2つ(切片に投入) | 都道府県レベル変数の説明力と、都道府県要因に占める割合を確認 |
| モデル4 | 所得の傾きにもランダム効果(ランダム切片・傾き) | 都道府県要因が市町村レベル変数の「傾き」に与える影響を考察 |
第4章の分析では、説明変数の分散を市町村レベルと都道府県レベルに分離するため、市町村レベル変数を集団平均中心化(各市町村の値から所属都道府県の平均を引く)、都道府県レベル変数を全体平均中心化(全体平均を引く)している(中心化の選択は Enders and Tofighi 2007 に依拠、分析はすべて R 3.6.0)。
174 175 176 177 178 179 180 181 182 183 184 | # ===== ステップ5: 集団平均中心化を実データでやってみる(SSDSE-A) ===== # 原論文は市町村レベル変数を「集団平均中心化」(各市町村の値から所属都道府県の # 平均を引く)してから投入した。都道府県間の差を取り除き、係数を「同じ県の中で # 値が高い市町村ほど…」という市町村レベルの効果として解釈できるようにする操作。 dfA['知識率_県平均'] = dfA.groupby('都道府県')['知識集約型産業従事者率'].transform('mean') dfA['知識率_集団平均中心化'] = dfA['知識集約型産業従事者率'] - dfA['知識率_県平均'] chk = dfA.groupby('都道府県')['知識率_集団平均中心化'].mean().abs().max() print('【集団平均中心化】知識集約型産業従事者率(SSDSE-A 実データ)') print(f' 中心化後の都道府県平均の最大絶対値 = {chk:.1e} (どの県でも平均0になった)') print(f" 例)東京都 千代田区: 元の値 {dfA.loc[dfA['市区町村']=='千代田区','知識集約型産業従事者率'].iloc[0]:.1f}%" f" → 中心化後 {dfA.loc[dfA['市区町村']=='千代田区','知識率_集団平均中心化'].iloc[0]:+.1f}") |
【集団平均中心化】知識集約型産業従事者率(SSDSE-A 実データ) 中心化後の都道府県平均の最大絶対値 = 1.9e-15 (どの県でも平均0になった) 例)東京都 千代田区: 元の値 46.5% → 中心化後 +28.9
groupby(...).transform('mean') は「グループ平均を元の行数のまま返す」ので、そのまま引き算できます。agg との違いはここ。SSDSE(独立行政法人統計センターの教育用標準データセット)とe-Stat から入手可能なデータを主に使用。市町村レベルは国勢調査(2015)・経済センサス基礎調査(2014)・学校基本調査(2013〜2015)・市町村税課税状況等の調(2015・2016)、都道府県レベルは学校基本調査(2014・2015)と国土地理院の都道府県庁間距離である。高等学校卒業生が存在しない市町村はサンプルから除外し、n=1304市町村。なお市町村別高等学校卒業生は「高校所在市町村」の卒業生であり、生徒の居住市町村ではない点に原論文自身が注意を促している。
| レベル | 変数 | 加工方法(原論文の定義) |
|---|---|---|
| 市町村 | 大学等進学率(%)[被説明変数] | 100×大学等進学者数÷高校卒業者数(学校基本調査) |
| 一人当たり課税対象所得(十万円) | 課税対象所得÷(所得割の納税義務者数×100) | |
| 知識集約型産業従事者率(%) | 100×(情報通信+金融保険+不動産物品賃貸+学術研究専門技術+教育学習支援の従業者数)÷従業者総数 | |
| 人口密度 | 人口総数÷総面積 | |
| 65歳以上人口比率(%) | 65歳以上人口の比率(100×人口比、国勢調査2015) | |
| 都道府県 | 都道府県別大学等収容率(%) | 100×(2015年大学学部生・短大学生数−2014年同−+2015年大学学部・短大卒業者数)÷高等学校卒業者数 |
| min(東京までの距離, 京都までの距離) | 各道府県庁から東京都庁・京都府庁までの距離の小さい方(km、国土地理院) |
※ 変数選択は先行研究に基づく:親の所得・職業・学歴(上山2011)→所得・産業従事者率、居住地の環境(友田1970)→都市化の代理として人口密度・年齢構成、大学への距離(小林2009)→大学等収容率が非常に高い東京都・京都府までの距離。島(1999)の「大学進学便益」は進学率との同時決定性の懸念から不採用。15〜64歳人口比率とその他産業従事者率は、それぞれ65歳以上人口比率・知識集約型産業従事者率と強い相関があり多重共線性回避のため除外された(原論文 3.2)。
| 大学等進学率(%) | 一人当たり課税対象所得(十万) | 知識集約型産業従事者率(%) | 人口密度 | 65歳以上人口比率(%) | |
|---|---|---|---|---|---|
| 最小値 | 0 | 19.94 | 0 | 0 | 0 |
| 中央値 | 41.14 | 27.6 | 9.852 | 2.5806 | 30.1 |
| 平均値 | 40.08 | 28.64 | 10.787 | 13.1081 | 30.61 |
| 最大値 | 94.01 | 102.35 | 37.494 | 223.8025 | 100 |
| 度数 | 1304 | 1304 | 1304 | 1304 | 1304 |
| 都道府県別大学等収容率(%) | min(東京までの距離, 京都までの距離)(km) | |
|---|---|---|
| 最小値 | 19.09 | 0 |
| 中央値 | 41.48 | 227.6 |
| 平均値 | 47.12 | 307.5 |
| 最大値 | 159.71 | 1245.2 |
| 度数 | 47 | 47 |
※ 原論文 表5・表6 の報告値の転記(第一・第三四分位数は省略)。進学率の最小0%・最大94.01%、人口密度の最大223.8=市町村間の格差は都道府県単位で見るよりはるかに大きい。大学等収容率の最大159.71%は「県外からの進学者も収容している」ことを意味する(京都・東京など)。
49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 | # ===== ステップ1: SSDSE-A(市区町村)から原論文と同型の説明変数を作る ===== # 原論文 表3 の加工方法に従う(経済センサスの産業分類は同一。年次は異なる)。 # 知識集約型産業従事者率 = (情報通信+金融保険+不動産物品賃貸+ # 学術研究専門技術+教育学習支援) / 従業者総数 ×100 # 人口密度 [人/km2] = 総人口 / 総面積(SSDSE-A の面積単位は ha → /100 で km2) # 65歳以上人口比率 = 65歳以上人口 / 総人口 ×100 dfA = pd.read_csv(DATA_A, encoding='cp932', skiprows=2) knowledge = ['従業者数(民営)(情報通信業)', '従業者数(民営)(金融業、保険業)', '従業者数(民営)(不動産業、物品賃貸業)', '従業者数(民営)(学術研究、専門・技術サービス業)', '従業者数(民営)(教育、学習支援業)'] dfA = dfA[dfA['従業者数(民営)'] > 0].copy() dfA['知識集約型産業従事者率'] = dfA[knowledge].sum(axis=1) / dfA['従業者数(民営)'] * 100 dfA['人口密度'] = dfA['総人口'] / (dfA['総面積(北方地域及び竹島を除く)'] / 100) dfA['65歳以上人口比率'] = dfA['65歳以上人口'] / dfA['総人口'] * 100 print(f"【SSDSE-A-2025】{len(dfA)}市区町村 × {dfA['都道府県'].nunique()}都道府県の階層データ") print(' ※ 原論文は高等学校卒業生が存在する 1304市町村(学校基本調査ベース)') print(' ※ 被説明変数「市町村別大学等進学率」と「一人当たり課税対象所得」は') print(' SSDSE-A 未収録 → 原論文のモデルの再推定は不可(表7・表8 は報告値で提示)') q = dfA[['知識集約型産業従事者率', '人口密度', '65歳以上人口比率']].describe().round(2) print(q.loc[['min', '25%', '50%', 'mean', '75%', 'max']].to_string()) |
【SSDSE-A-2025】1741市区町村 × 47都道府県の階層データ
※ 原論文は高等学校卒業生が存在する 1304市町村(学校基本調査ベース)
※ 被説明変数「市町村別大学等進学率」と「一人当たり課税対象所得」は
SSDSE-A 未収録 → 原論文のモデルの再推定は不可(表7・表8 は報告値で提示)
知識集約型産業従事者率 人口密度 65歳以上人口比率
min 0.00 0.00 10.27
25% 4.11 52.30 28.92
50% 6.41 189.63 34.57
mean 7.37 1069.22 34.75
75% 9.02 766.26 40.06
max 46.53 23182.09 65.24df[cols].sum(axis=1) は複数列の横方向の合計。axis=0(縦・列ごと)との違いを意識しましょう。マルチレベル分析を使う妥当性は、まず級内相関(ICC)の存在で確かめる。定数項のみのモデル1(ヌルモデル)を推定すると、進学率の全分散が「都道府県間の分散」と「市町村間の分散」に分解される。原論文の結果は——
ICC=0.244——大学等進学率のばらつきの約24.4%は、市町村間の差ではなく都道府県間の差によって説明される(原論文 4.1)。無視するには大きすぎる値であり、マルチレベル分析の妥当性が確認された。原論文はさらに図2〜図5(市町村別進学率×各説明変数の都道府県別散布図)でも都道府県要因の存在を視覚的に確認しているが、これらは被説明変数が SSDSE 未収録のためグラフは原論文参照。
かわりに本ページでは、SSDSE-A-2025 の実データを使って「級内相関がある」とはどういう状態かを体感する。市区町村の知識集約型産業従事者率(原論文 表3 と同じ定義で計算)を47都道府県別に並べた箱ひげ図が図4だ。これは手法のデモであり、原論文の ICC=0.244 の検算ではない(変数が違う)。
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 218 219 220 221 222 | # ===== ステップ6: 図4 級内相関(ICC)を実データで体感する(手法のデモ) ===== # 市区町村の「知識集約型産業従事者率」が都道府県というグループでどれだけ # 塊になっているかを、(1)箱ひげ図 と (2)ヌルモデル(ランダム切片のみの # マルチレベルモデル)の ICC で確かめる。原論文のモデル1(被説明変数は # 大学等進学率、ICC=0.244)と同じ手順を、SSDSE-A で使える別の変数に適用 # した「手法のデモ」であり、原論文の数値の検算ではない。 null_model = smf.mixedlm('知識集約型産業従事者率 ~ 1', dfA, groups=dfA['都道府県']) res = null_model.fit(reml=True) var_u = res.cov_re.iloc[0, 0] # 都道府県レベルの分散 var_e = res.scale # 市区町村レベルの残差分散 icc = var_u / (var_u + var_e) print('【図4】ヌルモデル(ランダム切片のみ)による級内相関 — SSDSE-A 実データ') print(f' 都道府県レベル分散 = {var_u:.2f} / 市区町村レベル分散 = {var_e:.2f}') print(f' ICC = {var_u:.2f} / ({var_u:.2f} + {var_e:.2f}) = {icc:.3f}') print(f' → 知識集約型産業従事者率のばらつきの約{icc*100:.1f}%は都道府県間の差') print(' (原論文の ICC=0.244 は「市町村別大学等進学率」についての報告値。別の変数)') order = dfA.groupby('都道府県')['知識集約型産業従事者率'].median().sort_values().index data = [dfA.loc[dfA['都道府県'] == p, '知識集約型産業従事者率'].values for p in order] fig, ax = plt.subplots(figsize=(9, 11)) bp = ax.boxplot(data, vert=False, patch_artist=True, showfliers=False, widths=0.6) for patch in bp['boxes']: patch.set_facecolor('#BBDEFB'); patch.set_edgecolor('#1565C0') for med in bp['medians']: med.set_color('#C62828') ax.set_yticklabels(order, fontsize=9) ax.set_xlabel('市区町村の知識集約型産業従事者率 [%](原論文 表3 と同じ定義で計算)') ax.set_title('図4:級内相関を体感する — 市区町村の知識集約型産業従事者率の都道府県別分布\n' f'(SSDSE-A-2025 実データによる手法デモ。ヌルモデルの ICC = {icc:.3f})\n' '箱の位置が県ごとに系統的にずれる=都道府県レベルの要因が存在する', fontsize=11) ax.grid(axis='x', alpha=0.3) fig.tight_layout() fig.savefig(f'{FIG_DIR}/2019_U3_fig4.png', bbox_inches='tight') plt.close(fig) top3 = dfA.groupby('都道府県')['知識集約型産業従事者率'].median().nlargest(3) print(' 都道府県中央値の上位3:', ' / '.join(f'{k} {v:.1f}%' for k, v in top3.items())) |
【図4】ヌルモデル(ランダム切片のみ)による級内相関 — SSDSE-A 実データ 都道府県レベル分散 = 4.77 / 市区町村レベル分散 = 20.86 ICC = 4.77 / (4.77 + 20.86) = 0.186 → 知識集約型産業従事者率のばらつきの約18.6%は都道府県間の差 (原論文の ICC=0.244 は「市町村別大学等進学率」についての報告値。別の変数) 都道府県中央値の上位3: 東京都 16.6% / 神奈川県 12.3% / 大阪府 11.3%
mixedlm('y ~ 1', groups=都道府県) がヌルモデル(定数項+ランダム切片のみ)。原論文のモデル1と同じ構造です。res.cov_re、残差分散が res.scale に入っています。ICC は自分で割り算して求めます。モデル1〜4の推定結果が原論文 表7 である。下表はその報告値の転記(カッコ内は標準誤差。市町村レベル変数は集団平均中心化後、都道府県レベル変数は全体平均中心化後の値)。
| 被説明変数:大学等進学率 | モデル1 | モデル2 | モデル3 | モデル4 |
|---|---|---|---|---|
| (Intercept) | 40.88*** (1.30) | 40.89*** (1.31) | 40.91*** (0.94) | 40.81*** (0.95) |
| 〔市町村〕一人当たり課税対象所得 | — | 0.46** (0.14) | 0.46** (0.14) | 1.16*** (0.24) |
| 〔市町村〕知識集約型産業従事者率 | — | 0.67*** (0.14) | 0.67*** (0.14) | 0.63*** (0.14) |
| 〔市町村〕人口密度 | — | 0.09*** (0.03) | 0.09*** (0.03) | 0.09*** (0.03) |
| 〔市町村〕65歳以上人口比率 | — | −0.64*** (0.09) | −0.64*** (0.09) | −0.48*** (0.09) |
| 〔都道府県〕都道府県別大学収容率 | — | — | 0.16*** (0.04) | 0.14*** (0.03) |
| 〔都道府県〕min(東京までの距離, 京都までの距離) | — | — | −0.02*** (0.00) | −0.02*** (0.00) |
| AIC | 11260.38 | 11038.80 | 11023.46 | 11011.03 |
| BIC | 11275.90 | 11075.01 | 11070.02 | 11067.94 |
| Log Likelihood | −5627.19 | −5512.40 | −5502.73 | −5494.52 |
| Var: prefecture (Intercept) | 66.42 | 69.24 | 29.1 | 29.91 |
| Var: Residual | 306.79 | 254.34 | 254.4 | 247.64 |
| Var: prefecture 一人当たり課税対象所得 | — | — | — | 0.52 |
| Cov: prefecture (Intercept)×一人当たり課税対象所得 | — | — | — | −1.19 |
※ 原論文 表7 の報告値の転記。*** p<0.001、** p<0.01、* p<0.05。Num. obs.=1304、Num. groups: prefecture=47(全モデル共通)。緑=正で有意、赤=負で有意。
一人当たり課税対象所得・知識集約型産業従事者率・人口密度は有意に正、65歳以上人口比率は有意に負。これは都道府県単位の先行研究と概ね一致するが、決定的に新しいのは市町村単位でも同じ変数が有意だと示したこと——進学率格差は県と県の間だけでなく、同じ県の中の市町村間でも所得や産業構造によって生じている。
さらにモデル4はモデル3より AIC・BIC が小さく、都道府県間で所得の係数が異なるモデルの方が当てはまりがよい。そしてランダム効果の共分散(切片×所得傾き)は−1.19 と負:切片が大きい(=県要因として進学率の底が高い)都道府県ほど、所得が進学率に与える影響が小さい。
先行研究と同様に、都道府県別大学等収容率は有意に正(地元に進学先の「席」が多いほど進学しやすい)、東京又は京都までの距離は有意に負(大学集積地から遠いほど進学しにくい)。
107 108 109 110 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 | # ===== ステップ3: 図2 原論文 表7 の係数の可視化(報告値。再計算ではない) ===== # (変数名, モデル3係数, SE, モデル4係数, SE, レベル) … 原論文 表7 の転記 paper_t7 = [ ('一人当たり課税対象所得(十万円)', 0.46, 0.14, 1.16, 0.24, '市町村'), ('知識集約型産業従事者率(%)', 0.67, 0.14, 0.63, 0.14, '市町村'), ('人口密度', 0.09, 0.03, 0.09, 0.03, '市町村'), ('65歳以上人口比率(%)', -0.64, 0.09, -0.48, 0.09, '市町村'), ('都道府県別大学収容率(%)', 0.16, 0.04, 0.14, 0.03, '都道府県'), ('min(東京までの距離,京都までの距離)', -0.02, 0.00, -0.02, 0.00, '都道府県'), ] fig, ax = plt.subplots(figsize=(9.6, 5.6)) ys = np.arange(len(paper_t7))[::-1] for y, (name, b3, se3, b4, se4, lv) in zip(ys, paper_t7): for b_, se_, dy, c, m in [(b3, se3, 0.16, '#1565C0', 'o'), (b4, se4, -0.16, '#E65100', 's')]: ax.plot([b_ - 1.96 * se_, b_ + 1.96 * se_], [y + dy, y + dy], color=c, lw=2) ax.plot(b_, y + dy, m, color=c, ms=7) ax.axvline(0, color='#555', lw=0.8, ls='--') ax.set_yticks(ys) ax.set_yticklabels([f"〔{r[5]}〕{r[0]}" for r in paper_t7], fontsize=10) ax.plot([], [], 'o-', color='#1565C0', label='モデル3(ランダム切片)') ax.plot([], [], 's-', color='#E65100', label='モデル4(ランダム切片・傾き)') ax.legend(loc='lower right', fontsize=10) ax.set_xlabel('大学等進学率への係数(点=係数、横線=係数±1.96×標準誤差)') ax.set_title('図2:マルチレベル分析の係数(原論文 表7 の報告値の可視化。再計算ではない)\n' 'いずれの係数もモデル3・4で5%水準より強い有意(所得は**、他は***)\n' '市町村レベル変数は集団平均中心化後、都道府県レベル変数は全体平均中心化後の値', fontsize=11) ax.grid(axis='x', alpha=0.3) fig.tight_layout() fig.savefig(f'{FIG_DIR}/2019_U3_fig2.png', bbox_inches='tight') plt.close(fig) print('【図2】原論文 表7 の報告値(モデル3 → モデル4)') for name, b3, se3, b4, se4, lv in paper_t7: print(f' 〔{lv}〕{name}: {b3:+.2f} → {b4:+.2f}') print(' ※ モデル4では所得の傾きにランダム効果(分散0.52、切片との共分散 -1.19)') |
【図2】原論文 表7 の報告値(モデル3 → モデル4) 〔市町村〕一人当たり課税対象所得(十万円): +0.46 → +1.16 〔市町村〕知識集約型産業従事者率(%): +0.67 → +0.63 〔市町村〕人口密度: +0.09 → +0.09 〔市町村〕65歳以上人口比率(%): -0.64 → -0.48 〔都道府県〕都道府県別大学収容率(%): +0.16 → +0.14 〔都道府県〕min(東京までの距離,京都までの距離): -0.02 → -0.02 ※ モデル4では所得の傾きにランダム効果(分散0.52、切片との共分散 -1.19)
paper_t7 の数値は原論文 表7 の転記(係数と標準誤差)であり、再計算ではありません。ax.plot([lo, hi], [y, y]) + ax.plot(b, y, 'o') の2行で描けます。フォレストプロットの基本形です。224 225 226 227 228 229 230 231 232 233 234 | # ===== ステップ7: 原論文 表7 のモデル比較指標(報告値の整理) ===== print('\n【原論文 表7 の報告値】モデルの当てはまり(AIC・BIC は小さいほどよい)') t7 = pd.DataFrame({ 'AIC': [11260.38, 11038.80, 11023.46, 11011.03], 'BIC': [11275.90, 11075.01, 11070.02, 11067.94], 'LogLik': [-5627.19, -5512.40, -5502.73, -5494.52], }, index=['モデル1(ヌル)', 'モデル2(市町村)', 'モデル3(+都道府県)', 'モデル4(+傾き)']) print(t7.to_string()) print(' → モデル4が最良: 都道府県間で「所得の傾き」が異なるモデルの当てはまりが最もよい') print(' → 切片と所得傾きのランダム効果の共分散 -1.19(負): 都道府県要因の切片が') print(' 大きい県ほど、所得が進学率に与える影響が小さい(原論文 4.2-1)') |
【原論文 表7 の報告値】モデルの当てはまり(AIC・BIC は小さいほどよい)
AIC BIC LogLik
モデル1(ヌル) 11260.38 11275.90 -5627.19
モデル2(市町村) 11038.80 11075.01 -5512.40
モデル3(+都道府県) 11023.46 11070.02 -5502.73
モデル4(+傾き) 11011.03 11067.94 -5494.52
→ モデル4が最良: 都道府県間で「所得の傾き」が異なるモデルの当てはまりが最もよい
→ 切片と所得傾きのランダム効果の共分散 -1.19(負): 都道府県要因の切片が
大きい県ほど、所得が進学率に与える影響が小さい(原論文 4.2-1)pd.DataFrame(dict, index=[...]) + to_string() で、報告値の整理表をコンソールに揃えて出力できます。採択した2つの都道府県レベル変数が「都道府県要因」のどの程度を占めるのかを確認するため、原論文はモデル2に大学等収容率のみを加えたモデル3-1と、距離のみを加えたモデル3-2を推定した(表8)。判定に使うのは都道府県間による切片のばらつき Var: prefecture (Intercept) の減少である。
| モデル2 | モデル3-1 (+大学収容率) | モデル3-2 (+距離) | モデル3 (+両方) | |
|---|---|---|---|---|
| 都道府県別大学収容率 | — | 0.20*** (0.04) | — | 0.16*** (0.04) |
| min(東京までの距離, 京都までの距離) | — | — | −0.02*** (0.00) | −0.02*** (0.00) |
| Var: prefecture (Intercept) | 69.24 | 42.72 | 46.38 | 29.1 |
| Var: Residual | 254.34 | 254.34 | 254.32 | 254.4 |
| AIC | 11038.80 | 11026.01 | 11033.27 | 11023.46 |
| Log Likelihood | −5512.40 | −5505.00 | −5508.64 | −5502.73 |
※ 原論文 表8 の報告値の転記(市町村レベル変数4つの係数は4モデルすべてで表7のモデル2と同一のため省略)。Num. obs.=1304、Num. groups=47。
結論として、大学等収容率と東京・京都までの距離のどちらも、都道府県要因の決して少なくない部分を占める。ただし両方を入れても29.1の切片分散が残ることに原論文は注意を促している。
143 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 | # ===== ステップ4: 図3 都道府県切片分散の比較(原論文 表7・表8 の報告値) ===== # 「採択した都道府県レベル変数が、都道府県要因(切片のばらつき)をどれだけ # 説明したか」を Var: prefecture (Intercept) の減少で確認する(原論文 4.2-2)。 models = ['モデル1\n(ヌル)', 'モデル2\n(+市町村変数)', 'モデル3-1\n(+大学収容率)', 'モデル3-2\n(+距離)', 'モデル3\n(+両方)', 'モデル4\n(+所得の傾き)'] var_pref = [66.42, 69.24, 42.72, 46.38, 29.10, 29.91] # 表7・表8の報告値 var_res = [306.79, 254.34, 254.34, 254.32, 254.40, 247.64] fig, axes = plt.subplots(1, 2, figsize=(11, 4.6)) colors = ['#78909C', '#78909C', '#42A5F5', '#42A5F5', '#1565C0', '#E65100'] axes[0].bar(models, var_pref, color=colors) for i, v in enumerate(var_pref): axes[0].text(i, v + 1, f'{v:.1f}', ha='center', fontsize=10) axes[0].set_ylabel('Var: prefecture (Intercept)') axes[0].set_title('都道府県切片の分散(都道府県要因の残り)', fontsize=11) axes[1].bar(models, var_res, color=colors) for i, v in enumerate(var_res): axes[1].text(i, v + 3, f'{v:.0f}', ha='center', fontsize=10) axes[1].set_ylabel('Var: Residual') axes[1].set_title('市町村レベルの残差分散', fontsize=11) for ax in axes: ax.tick_params(axis='x', labelsize=8.5) ax.grid(axis='y', alpha=0.3) fig.suptitle('図3:分散成分の比較(原論文 表7・表8 の報告値の可視化。再計算ではない)\n' '大学収容率と距離を両方入れると都道府県切片の分散は 69.24 → 29.10 に減少(それでも残る)', fontsize=11) fig.tight_layout(rect=[0, 0, 1, 0.90]) fig.savefig(f'{FIG_DIR}/2019_U3_fig3.png', bbox_inches='tight') plt.close(fig) print('【図3】都道府県切片の分散(原論文の報告値): ' f'モデル2 {var_pref[1]} → モデル3-1 {var_pref[2]} → モデル3-2 {var_pref[3]} → モデル3 {var_pref[4]}') |
【図3】都道府県切片の分散(原論文の報告値): モデル2 69.24 → モデル3-1 42.72 → モデル3-2 46.38 → モデル3 29.1
fig.suptitle + fig.tight_layout(rect=[0,0,1,0.90]) で、全体タイトルとサブプロットの重なりを防げます。所得・知識集約型産業従事者率・人口密度・65歳以上人口比率に目を向けた政策は、都道府県単位だけでなく市町村単位でも有用と予測できる。どの市町村を重点支援すべきかの指標になる。
市町村要因では説明できない部分が確かに存在する。都道府県単位の適切な政策は、県内の市町村全体に一括して波及しうる。
両変数は都道府県切片のばらつきを大きく減少させた(69.24→29.10)。大学の収容力・大学集積地へのアクセスに焦点を置いた政策は有用と推測される。
切片と所得傾きの共分散は負(−1.19)。都道府県要因の切片を底上げする政策を行えば、所得格差が進学率格差に転化する度合いを間接的に減らせる可能性がある。
この論文(とマルチレベル分析全般)を読むときに、初心者が陥りやすい誤解を整理します。
本文中の 用語 をクリックすると詳しい解説がポップアップします。ここでは特に重要な概念だけまとめます。
統計手法について「何のためか」「結果をどう読むか」を初心者向けに解説します。
この研究をさらに発展させるための3つの方向性を示します。「今回わかったこと(X)」から「次に検証すべき仮説(Y)」を立て、「具体的に何をするか(Z)」まで考えてみましょう。
このページのスクリプト(code/2019_U3_suri.py)を出発点に、難易度順の5つの課題に挑戦してみましょう。
mixedlm の被説明変数を 65歳以上人口比率 や 人口密度 に変えて、ICCを計算してみよう。どの変数が最も「都道府県で塊になっている」だろうか? 人口密度で試すなら対数変換(np.log1p)した方がよい理由も考えてみよう。
mixedlm('知識集約型産業従事者率 ~ 人口密度 + 65歳以上人口比率', ...) のように市区町村レベル変数を投入してみよう(原論文のモデル1→モデル2に相当する操作)。投入前後で res.cov_re(都道府県分散)と res.scale(残差分散)はどう変わるか?
mixedlm(..., re_formula='~人口密度') のように re_formula を指定すると傾きにもランダム効果が入る(原論文のモデル4に相当)。切片と傾きの共分散の符号はどうなったか? 収束しない場合は変数の標準化やスケーリングを試そう——「収束との闘い」自体が原論文の追体験である(原論文も計算の収束の問題で交差レベル交互作用を断念している)。
マルチレベル分析(階層線形モデル)は「個人がグループに入れ子になったデータ」がある所ならどこでも使われています。具体的なシーンを紹介します。
この論文を読んで初心者が抱きやすい疑問に、教育的観点から答えます。
MixedLM、R なら lme4 パッケージのドキュメントが実践的です。本サイトの他の論文(ランダム効果モデルを使った2019年優秀賞など)との読み比べも効果的です。この論文を理解できたか、クリックで確認しましょう。間違えても解説が出ます。
このページの分析は、この画面の中でそのまま実行できます。
Python をインストールする必要も、CSV をダウンロードする必要もありません。
下のセルの 「▶ ブラウザで実行」 を上から順に押すか、
「▶ 最初から全部実行」 で一気に流してください。
表示されるのは、本文の図表とまったく同じ計算の結果です
(動かしているのは再現スクリプト code/2019_U3_suri.py そのもの)。
コードは書き換えられます。「✏️ 書き換える」で数字や変数を変えて ▶ を押すと、 結果がどう変わるかをその場で確かめられます(元に戻すボタンつき)。 ページを開いた時点で裏で実行環境の準備が始まっているので、待ち時間はほとんどありません。 初回だけ実行環境の取得にインターネット接続が必要です。