この教材は原論文と同じデータを読み込み、同じ手順で計算し直しています。画面に出る数値と図は、あなたのブラウザがその場で計算した結果です。
⚠ ただし一部は再現していません:総面積・可住地面積は現行 SSDSE のどの版にも収録列が無いため、 その説明変数を含む部分は実再現していません。 ランダムフォレストの出力(原論文 表1・図2)は報告値の可視化で、 再計算ではありません。
| 原論文が使ったデータ | 教育用標準データセット 分析単位:市区町村 中核手法:ランダムフォレスト・線形回帰・相関分析 |
|---|---|
| この教材が使うデータ |
|
| 原論文(PDF) | 機械学習による15歳未満人口の推定 特別賞/伊藤 寛子(渋谷教育学園幕張高等学校) |
▶ ここに書いてある分析は、ページ下部の「🐍 ブラウザで動かす」でそのまま実行できます(インストール不要)。動かしているのは code/2018_H4_katsuyo.py(264 行)そのものです。
このページで実SSDSEから実再現できるのは図3・図4だけです。コードの編集は不要です。図1・図2は、原論文が使った「総面積・可住地面積」や市町村単位のデータ、そして機械学習の学習結果そのものが現行SSDSEでは再取得・再計算できないため、原論文 表1・図2 の報告値を転記して可視化します。
data/raw/ フォルダに入れます。html/figures/ に自動保存されます。図1・図2は原論文 表1・図2 の報告値から、図3・図4は SSDSE-B の総人口・15歳未満人口から実再現します。
都市への人口集中や村落の少子化が報じられる中、著者は「少子化が進む地域は村落が多い」という見方に着目した。そして、地域の村落度合いを表すデータ——人口総数・総面積・可住地面積——があれば、その市町村の15歳未満人口を機械学習で推定できるのではないか、と考えた。
本研究は、全国1740市町村のデータを教師データにして、ランダムフォレスト回帰で15歳未満人口を推定する試みである。実現すれば、人口急増時の教育施設・福祉施設の需要予測や、公立学校運営・少子化対策に役立つ、というのが著者の動機だ。審査委員長講評は「機械学習で人口統計を分析し、その過程を詳細に記した技術的に優れた論文」と評価し、社会課題解決というより「技術賞」として特別賞に位置づけている。
RandomForestRegressor(ランダムフォレスト回帰)を使っている。本ページは実装どおりランダムフォレストとして解説する。ランダムフォレストは多数の決定木を束ねるアンサンブル学習で、線形回帰とは別物である。
高校生の部・特別賞(技術賞) 教育用標準データセット(市町村) ランダムフォレスト回帰 決定係数・相関係数で評価
原論文は教育用標準データセット(当時の市町村版)から、人口総数・総面積(北方地域及び竹島を除く)・可住地面積・15歳未満人口の4列を抽出し、総面積順に並べた test2.csv(1740件・UTF-8)を作って学習に使った。
| 役割 | 変数 | 出典 | 本ページでの扱い |
|---|---|---|---|
| 目的変数 | 15歳未満人口 | 教育用標準データセット(市町村・1740件) | 実SSDSE-Bに都道府県版が実在(図3・図4で実再現) |
| 説明変数 | 人口総数 | 同上 | 実SSDSE-Bに総人口が実在(図3・図4で実再現) |
| 説明変数 | 総面積(北方地域及び竹島を除く)・可住地面積 | 同上 | 現行SSDSE-A/B/C/Eに収録列なし(実再現不可) |
| 学習結果 | 5回のスコア・回帰直線・相関係数 | 原論文 表1・図2(ランダムフォレストの出力) | 報告値の可視化(再計算ではない) |
全1740件を、学習に使う訓練データと、精度確認に使うテストデータに分ける。テストが少なすぎると評価が不安定になるため、著者は「テストを300件以上」という条件から分割比を test_size=0.2(=20%)に設定した。分割の乱数シード random_state=70 は再現性のための任意の固定値である。
人口(数十万)と面積(数千km²)では桁が大きく違う。そこで StandardScaler で各変数を平均0・分散1に標準化し、尺度の違いが学習に悪影響を与えないようにする。標準化は訓練データで基準を作り、テストデータにも同じ基準を当てる。
ランダムフォレスト回帰は、ランダムに選んだデータで多数の決定木を育て、その平均を予測とする手法。乱数を使うため実行のたびに結果が少し変わる。著者はこの偶然誤差を減らすため、同じ学習を5回繰り返して平均し、決定係数で精度を測った。
まず、原論文がランダムフォレストを5回実行して得た決定係数(表1)を見る。これは学習済みモデルの出力で、説明変数・データ単位が現行SSDSEと異なるため再計算できない。原論文 表1 の報告値を転記して可視化する(再計算ではない)。
65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 | # --- 原論文 表1「スコア(決定係数)」5回分をそのまま転記【報告値】 --- # ※ これは原論文がランダムフォレストで学習した結果であり、 # 本スクリプトが計算した値ではない(報告値の転記)。 scores = [0.9886389795692969, 0.9908011704351442, 0.9854055839021446, 0.9814707632552758, 0.9905348525568049] score_mean = 0.987370269943733 # 原論文 表1 の平均 # --- 原論文 図2・4.2節の報告統計量【報告値】 --- trend_slope = 0.9635 # 図2 トレンドライン y = 0.9635x + 272.21 trend_intercept = 272.21 corr_report = 0.9948 # 5回平均と実際の15歳未満人口の相関係数 r2_report = 0.9896 # 同・決定係数 mean_rel_err = 0.038 # 図3 相対誤差の平均 print("原論文 表1 スコア(決定係数, 5回):") for i, s in enumerate(scores, 1): print(f" {i}回目: {s}") print(f" 平均 : {score_mean}") print(f"報告トレンドライン: y = {trend_slope}x + {trend_intercept}") print(f"報告 相関係数 r = {corr_report} / 決定係数 R^2 = {r2_report}") print(f"報告 相対誤差の平均 = {mean_rel_err}") |
=== [1] 原論文の報告値(表1・図2)を転記 === 原論文 表1 スコア(決定係数, 5回): 1回目: 0.9886389795692969 2回目: 0.9908011704351442 3回目: 0.9854055839021446 4回目: 0.9814707632552758 5回目: 0.9905348525568048 平均 : 0.987370269943733 報告トレンドライン: y = 0.9635x + 272.21 報告 相関係数 r = 0.9948 / 決定係数 R^2 = 0.9896 報告 相対誤差の平均 = 0.038
93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 | # --- 原論文 表1 の5回スコアを棒グラフにする(報告値の可視化・再計算ではない) --- fig1, ax1 = plt.subplots(figsize=(9, 5.5)) labels = [f'{i}回目' for i in range(1, 6)] bars = ax1.bar(labels, scores, color='#4e9af1', edgecolor='white', linewidth=0.6, alpha=0.9, width=0.62) ax1.axhline(score_mean, color='#c0392b', linewidth=1.8, linestyle='--', label=f'5回平均: {score_mean:.4f}') for b, s in zip(bars, scores): ax1.text(b.get_x() + b.get_width() / 2, s + 0.0004, f'{s:.4f}', ha='center', va='bottom', fontsize=9.5) ax1.set_ylim(0.975, 0.995) ax1.set_ylabel('決定係数(スコア)', fontsize=12) ax1.set_title('図1:ランダムフォレストの決定係数(原論文 表1・5回)\n' '【報告値の可視化(再計算ではない)】', fontsize=13, fontweight='bold', pad=12) ax1.grid(axis='y', alpha=0.3, linewidth=0.8) ax1.legend(fontsize=10, loc='lower right') ax1.spines['top'].set_visible(False) ax1.spines['right'].set_visible(False) plt.tight_layout() fig1.savefig(os.path.join(FIG_DIR, '2018_H4_fig1.png'), bbox_inches='tight') plt.close(fig1) |
=== [2] 図1:表1スコアの可視化【報告値の可視化】 === 5回スコアの範囲: 0.9815 〜 0.9908(平均 0.9874) fig1 saved.(報告値の可視化)
原論文の核心的な結果が、「5回平均の推定値」と「実際の15歳未満人口」の関係(原論文 図2)だ。最小二乗法のトレンドラインは y=0.9635x+272.21、相関係数0.9948。個々のテスト推定値は原論文に数値が無いため、報告された直線と統計量だけを可視化する(再計算ではない)。
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 | # --- 原論文 図2 の報告回帰直線 y=0.9635x+272.21 を描画(報告値の可視化) --- # ※ 個々のテスト推定値(散布点)は原論文に数値が無いため描かない。 # 報告された直線と統計量だけを転記して可視化する。 xr = np.linspace(0, 300000, 200) y_trend = trend_slope * xr + trend_intercept fig2, ax2 = plt.subplots(figsize=(8.5, 7)) ax2.plot(xr, y_trend, color='#c0392b', linewidth=2.2, label=f'報告トレンドライン: y = {trend_slope}x + {trend_intercept}') ax2.plot(xr, xr, color='#888', linewidth=1.2, linestyle=':', label='参照線 y = x(完全一致)') ax2.set_xlim(0, 300000) ax2.set_ylim(0, 300000) ax2.set_xlabel('実際の15歳未満人口(人)', fontsize=12) ax2.set_ylabel('5回の推定値の平均(人)', fontsize=12) ax2.set_title('図2:実際の15歳未満人口 vs 推定値の報告関係\n' f'【報告値の可視化】 r = {corr_report}, R² = {r2_report}', fontsize=13, fontweight='bold', pad=12) ax2.text(0.04, 0.90, f'相関係数 r = {corr_report}\n決定係数 R² = {r2_report}\n' f'相対誤差の平均 = {mean_rel_err}', transform=ax2.transAxes, fontsize=10.5, bbox=dict(boxstyle='round', facecolor='#fff6e5', edgecolor='#f0a500')) ax2.legend(fontsize=9.5, loc='lower right') ax2.grid(alpha=0.25, linewidth=0.8) ax2.spines['top'].set_visible(False) ax2.spines['right'].set_visible(False) plt.tight_layout() fig2.savefig(os.path.join(FIG_DIR, '2018_H4_fig2.png'), bbox_inches='tight') plt.close(fig2) |
=== [3] 図2:報告トレンドラインの可視化【報告値の可視化】 === 報告トレンドライン: y = 0.9635x + 272.21 傾きは1に近く(0.9635)、切片は+272.21人 → 推定値はほぼ実測に一致 fig2 saved.(報告値の可視化)
ここからが実データによる再現だ。原論文の中核前提は「人口規模が分かれば15歳未満人口を精度よく推定できる」というもの。それを、SSDSEに実在する総人口と15歳未満人口で確かめる。原論文の3変数のうち総面積・可住地面積は現行SSDSEに無いため、実在する総人口1変数で前提を検証する。
cp932 で読み、都道府県行のみを抽出、年度==2023 に絞る。総人口と15歳未満人口の実測の相関係数と回帰直線を計算し、散布図にする。原論文の前提が現行データでも成り立つかを実データで確かめる。164 165 166 167 168 169 170 171 172 173 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 | # --- SSDSE-B-2026 を cp932 で読み込み、都道府県行のみ・2023年に絞る --- df_b = pd.read_csv(DATA_B, encoding='cp932', header=1) df_b = df_b[df_b['地域コード'].astype(str).str.match(r'^R\d{5}', na=False)].copy() df_b['年度'] = df_b['年度'].astype(int) df23 = df_b[df_b['年度'] == 2023].dropna(subset=['総人口', '15歳未満人口']).copy() xp = df23['総人口'].astype(float).values / 10000 # 万人 yp = df23['15歳未満人口'].astype(float).values / 10000 # 万人 # 実データの相関と回帰直線(実python実行=実再現値) r_real, p_real = stats.pearsonr(xp, yp) slope_real, intercept_real = np.polyfit(xp, yp, 1) fig3, ax3 = plt.subplots(figsize=(9, 7)) ax3.scatter(xp, yp, s=60, color='#5cb85c', alpha=0.8, edgecolors='white', linewidths=0.7, zorder=3) xr3 = np.linspace(xp.min(), xp.max(), 100) ax3.plot(xr3, slope_real * xr3 + intercept_real, 'k-', linewidth=1.8, alpha=0.75, label=f'回帰直線 y = {slope_real:.3f}x + {intercept_real:.2f}') # 突出する県にラベル for name, xv, yv in zip(df23['都道府県'], xp, yp): if xv > 500 or name in ('沖縄県', '鳥取県'): ax3.annotate(name.replace('県', '').replace('都', '').replace('府', ''), (xv, yv), xytext=(4, 3), textcoords='offset points', fontsize=8, color='#333') ax3.set_xlabel('総人口(万人・SSDSE-B 2023)', fontsize=12) ax3.set_ylabel('15歳未満人口(万人・SSDSE-B 2023)', fontsize=12) ax3.set_title('図3:総人口 vs 15歳未満人口(47都道府県・2023年)\n' f'【実SSDSE-B・実再現】 実測 r = {r_real:.4f}, R² = {r_real**2:.4f}', fontsize=13, fontweight='bold', pad=12) ax3.legend(fontsize=10, loc='upper left') ax3.grid(alpha=0.25, linewidth=0.8) ax3.spines['top'].set_visible(False) ax3.spines['right'].set_visible(False) plt.tight_layout() fig3.savefig(os.path.join(FIG_DIR, '2018_H4_fig3.png'), bbox_inches='tight') plt.close(fig3) |
=== [4] 図3:SSDSE実データで総人口×15歳未満人口【実再現】 === 分析対象: 47 都道府県(2023年) 総人口 × 15歳未満人口 の実測相関 r = 0.9967, R² = 0.9934, p = 1.16e-50 回帰直線: 15歳未満人口 ≒ 0.1112 × 総人口 + 0.74(万人) fig3 saved.(実再現)
最後に、原論文と同じ RandomForestRegressor を実際に動かす。SSDSEに実在する総人口だけを使い、原論文と同じ test_size=0.2, random_state=70・標準化で学習し、テストデータでの実測精度を確かめる。データは都道府県×全年(2012–2023)で件数を確保する。
RandomForestRegressor を、SSDSEの総人口→15歳未満人口で実際に学習する。都道府県×2012–2023年の全データを訓練:テスト=8:2に分け(random_state=70)、StandardScalerで標準化してからテストデータの決定係数と相関を測る。原論文の手法を、実在する変数だけで動かして再現する。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 240 241 242 243 244 245 | # --- 原論文と同じ RandomForestRegressor を、SSDSEの総人口→15歳未満人口で実学習 --- # 全都道府県×全年(2012-2023)を使い、原論文と同じ test_size=0.2, random_state=70。 # 説明変数は SSDSE に実在する「総人口」のみ(総面積・可住地面積は現行SSDSEに無い)。 df_ml = df_b.dropna(subset=['総人口', '15歳未満人口']).copy() X = df_ml[['総人口']].astype(float).values y = df_ml['15歳未満人口'].astype(float).values X_tr, X_te, y_tr, y_te = train_test_split(X, y, test_size=0.2, random_state=70) scaler = StandardScaler().fit(X_tr) # 原論文どおり標準化 model = RandomForestRegressor(random_state=70).fit(scaler.transform(X_tr), y_tr) pred = model.predict(scaler.transform(X_te)) r2_rf = model.score(scaler.transform(X_te), y_te) # 実測の決定係数 r_rf, _ = stats.pearsonr(y_te, pred) # 実測の相関係数 fig4, ax4 = plt.subplots(figsize=(8.5, 7)) ax4.scatter(y_te / 10000, pred / 10000, s=45, color='#9b59b6', alpha=0.7, edgecolors='white', linewidths=0.6, zorder=3) lim = max(y_te.max(), pred.max()) / 10000 * 1.05 ax4.plot([0, lim], [0, lim], color='#888', linestyle=':', linewidth=1.3, label='y = x(完全一致)') ax4.set_xlim(0, lim); ax4.set_ylim(0, lim) ax4.set_xlabel('実際の15歳未満人口(万人)', fontsize=12) ax4.set_ylabel('ランダムフォレストの推定値(万人)', fontsize=12) ax4.set_title('図4:ランダムフォレストによる推定(総人口のみ・都道府県×年)\n' f'【実SSDSE-B・実学習・実再現】 実測 R² = {r2_rf:.4f}, r = {r_rf:.4f}', fontsize=12.5, fontweight='bold', pad=12) ax4.legend(fontsize=10, loc='upper left') ax4.grid(alpha=0.25, linewidth=0.8) ax4.spines['top'].set_visible(False) ax4.spines['right'].set_visible(False) plt.tight_layout() fig4.savefig(os.path.join(FIG_DIR, '2018_H4_fig4.png'), bbox_inches='tight') plt.close(fig4) |
=== [5] 図4:ランダムフォレストを実際に学習【実再現】 === 学習データ全体: 564 件(47都道府県 × 2012-2023年) 訓練 451 件 / テスト 113 件(test_size=0.2, random_state=70) 実再現の決定係数 R² = 0.9950, 相関係数 r = 0.9975 fig4 saved.(実再現)
test_size=0.2, random_state=70)。原論文は市町村・3変数のため報告値(R²=0.9896)とは一致しないが、手法を実データで動かした再現である。著者は結果(原論文4章・5章)を次のように読み解いた。
5回平均の相関係数・決定係数はいずれも0.9を大きく超え、1回ごとの決定係数も0.98超で安定していた。著者は「概ね推定精度は良好で、かなり高い」と結論した。人口総数・総面積・可住地面積という入手しやすい3変数だけで、15歳未満人口をほぼ言い当てられたことになる。
一方、15歳未満人口が概ね100人未満の市町村では、相対誤差が1.0を超える地域も現れた。テストデータのうち1〜100人未満の8市町村の相対誤差平均は0.5769と、全体の傾向より大幅に悪い。著者はこれを「人口が少ない地域の学習データ不足」が原因と考察した。
そこで著者は「15歳未満人口が概ね100人以上の地域では本手法は有効だが、100人未満では精度が著しく低下するため利用に注意が必要」と結論。今後は、ランダムフォレストを多層にしたディープ・フォレストの活用や、誰でも機械学習分析を行えるソフトウェア開発を展望として挙げた。
本研究は、市町村の人口総数・総面積・可住地面積から15歳未満人口を機械学習で推定できるかという問いに、ランダムフォレスト回帰で取り組んだ。全1740市町村を訓練:テスト=8:2に分け、標準化して5回学習した結果、5回平均の決定係数0.9874、推定値と実測の相関0.9948・決定係数0.9896という高精度を報告した。ただし15歳未満人口が概ね100人未満の市町村では精度が急落し、学習データ不足という課題も明らかにした。著者はディープ・フォレストの活用や誰でも使える分析ソフトの開発を今後の展望とした。本ページでは、学習結果(表1・図2)は報告値として可視化し、SSDSEに実在する総人口・15歳未満人口で実際にランダムフォレストを学習し直して切り分けた。
このページの図は、以下から再現できます。
🐍 再現コード(.py) 📊 SSDSE-B-2026.csv
※ 図3(総人口×15歳未満人口)・図4(ランダムフォレスト実学習)はSSDSE-B-2026から算出した実再現です。図1(5回スコア)・図2(実測 vs 推定の回帰直線)は、原論文の説明変数(総面積・可住地面積)と市町村単位データが現行SSDSEに無く、学習結果そのものは再計算できないため、原論文 表1・図2 の報告値を転記して可視化したものです。実再現(都道府県・総人口のみ)は原論文(市町村・3変数)と単位・変数が異なるため、報告値とは一致しません(新たな数値の捏造はしていません)。
この研究を読むとき・まねするときに陥りやすい誤解を整理する。
random_state)を固定すれば同じ結果を再現できる。クリックすると各用語の詳しい解説ページに移動できます。
RandomForestRegressor を使う。test_size=0.2)。未知データでの実力を測るために分ける。この研究の中心であるランダムフォレスト回帰と、その前提となる分割・標準化・評価指標を、注意点まで順に説明する。
test_size=0.2(8:2)を選び、random_state=70 で分割を固定した。この研究の「人口・面積から15歳未満人口を高精度に推定できる」という結果は、次の研究の出発点になる。
再現コードを少し変えるだけで試せる課題を、易しい順に用意した。
年度==2023。ここを 2015 や 2020 に変えて、総人口×15歳未満人口の相関が年によってどう変わるか(ほとんど変わらないはず)を確かめよう。random_state=70 を 0 や 123 に変えて、テストの決定係数がどれくらい動くか(乱数による変動)を体感しよう。X = df_ml[['総人口']] に '65歳以上人口' を加えて2変数にし、決定係数が上がるか下がるかを確かめよう(総人口だけでほぼ十分なはず)。sklearn.linear_model.LinearRegression でも学習し、ランダムフォレストと決定係数を比較してみよう(総人口が支配的なので大差ないはず)。「複数の指標から目的の値を機械学習で推定する」発想は、行政・不動産・防災の現場で広く使われている。
この研究を読んで浮かびやすい疑問に答える。
RandomForestRegressor(ランダムフォレスト回帰)です。両者は別の手法なので、本ページは実装どおりランダムフォレストとして解説しています。この論文を理解できたか、クリックで確認しましょう。間違えても解説が出ます。
このページの分析は、この画面の中でそのまま実行できます。
Python をインストールする必要も、CSV をダウンロードする必要もありません。
下のセルの 「▶ ブラウザで実行」 を上から順に押すか、
「▶ 最初から全部実行」 で一気に流してください。
表示されるのは、本文の図表とまったく同じ計算の結果です
(動かしているのは再現スクリプト code/2018_H4_katsuyo.py そのもの)。
コードは書き換えられます。「✏️ 書き換える」で数字や変数を変えて ▶ を押すと、 結果がどう変わるかをその場で確かめられます(元に戻すボタンつき)。 ページを開いた時点で裏で実行環境の準備が始まっているので、待ち時間はほとんどありません。 初回だけ実行環境の取得にインターネット接続が必要です。