この教材は原論文と同じデータを読み込み、同じ手順で計算し直しています。画面に出る数値と図は、あなたのブラウザがその場で計算した結果です。
| 原論文が使ったデータ | SSDSE-A・都道府県・市区町村のすがた(社会・人口統計体系) 分析単位:市区町村 中核手法:重回帰分析・Best Subset Selection・決定木回帰・ランダムフォレスト・10-fold クロスバリデーション |
|---|---|
| この教材が使うデータ |
|
| 原論文(PDF) | 外国人人口と市区町村の特性との関係性 特別賞/西尾 春香(関西学院大学経済学部) |
▶ ここに書いてある分析は、ページ下部の「🐍 ブラウザで動かす」でそのまま実行できます(インストール不要)。動かしているのは code/2019_U5_2_shorei.py(264 行)そのものです。
このページの実データ計算(図1・図3・図4と実行結果)を自分で再現するには、以下の手順でデータを準備してください。コードの編集は不要です。図2は原論文 表2 の報告値を可視化するもので、追加データは不要です。
data/raw/ フォルダに入れます。html/figures/ に自動保存されます。
日本に住む外国人への関心が高まっている。外国人人口は1980年代後半から増加が顕著になり、リーマンショックや東日本大震災の影響で一時的に減少する時期はあったものの、増加傾向が続いてきた。総務省の住民基本台帳に基づく人口調査(2019年1月1日時点)によると、外国人人口は前年より約17万人増えて過去最多となり、全国の人口総数に占める外国人住民の割合が初めて2%を超えた。一方で日本人人口は前年より約43万人少なく、10年連続の減少となった。
さらに2019年4月1日には、人手不足が深刻な産業分野を対象とする在留資格「特定技能」が新設され、一定の専門性・技能を持つ新たな外国人材の受入れが可能になった(熟練者を想定した特定技能2号では家族の帯同を含む長期滞在が可能)。在留外国人の増加傾向は今後も続くと考えられ、受け入れ側の自治体には、災害時の多言語対応や子どもの就学支援など、生活環境の整備が求められている。
そこで原論文は「外国人人口と自治体が持つ特性との間に結びつきがあるか。あるならどの特性と、どの程度の強さか」を計量分析で調べる。 自治体は人口・経済・行政・教育・社会保障など多面的な特性を持つが、外国人人口との関連を市区町村レベルで幅広く調べた研究は少なく、この点を明らかにすることは現在の日本の社会構造の理解につながる。
外国人人口に関する先行研究としては、外国人の新規流入と国内移動(石川ほか 2014)、市区町村における外国人の社会増加(清水 2017)、外国人女性の出生数と出生率(山内 2010、是川 2013)、外国人の居住地選択(是川 2008)、外国人労働者導入の影響(中村 2009)、都道府県別の外国人の自然動態(中川ほか 2018)などがある。しかし外国人人口と市区町村の特性との関連性は十分に調べられておらず、原論文は教育用標準データセット(SSDSE)の市区町村データを使った統計分析でこの点の考察を深めた。
SSDSE-A 重回帰分析 Best Subset Selection 決定木・ランダムフォレスト 10-fold クロスバリデーション
原論文は、独立行政法人統計センターが公開する教育用標準データセット SSDSE-2019A(市区町村データ)を主に用い、e-Stat「都道府県・市区町村のすがた(社会・人口統計体系)」から4変数(未婚者割合=2015年、課税対象所得=2017年、学歴割合(大卒)=2010年、刑法犯認知件数=2008年)を追加した。目的変数は外国人人口(10万人あたり)、説明変数は自治体の多面的な特性を表す29変数である。
| 分野 | 変数 | 計算式(原論文 表1) | SSDSE-A-2025での再現 |
|---|---|---|---|
| 目的変数 | 外国人人口(10万人あたり) | log(外国人人口 / 総人口 × 100000 + 1) | ○ |
| 人口・世帯 | 総人口 | log(総人口) | ○ |
| 自然人口増減率 | (出生数 − 死亡数) / 総人口 | ○ | |
| 社会的人口増減率 | (転入者数 − 転出者数) / 総人口 | △(日本人移動者のみ収録) | |
| 核家族世帯割合・単独世帯割合 | 各世帯数 / 一般世帯数 × 100 | ○ | |
| 人口密度 | log(総人口 / 可住地面積) | ○ | |
| 未婚者割合(15歳以上人口) | e-Stat 追加(2015年) | ×(未収録) | |
| 経済基盤 | 従業者割合(11産業) | 従業者数(産業別) / 従業者数 × 100 (農林業・建設・製造・情報通信・運輸郵便・卸売小売・学術研究・宿泊飲食・教育学習支援・医療福祉・その他サービス) |
○ |
| 農家数(販売農家、10万人あたり) | log(農家数 / 総人口 × 100000 + 1) | ○ | |
| 行政基盤 | 経常収支比率 | 経常収支比率(市町村財政) | ○ |
| 歳出における土木費割合・教育費割合 | 各費目 / 歳出決算総額 × 100 | ○ | |
| 課税対象所得(納税義務者1人あたり) | e-Stat 追加(2017年)・log変換 | ×(未収録) | |
| 教育 | 学歴割合(大卒) | e-Stat 追加(2010年) | ×(未収録) |
| 労働 | 完全失業率 | 完全失業者数 / (完全失業者数 + 就業者数) × 100 | ○ |
| 文化・スポーツ | 図書館数(10万人あたり) | log(図書館数 / 総人口 × 100000 + 1) | ○ |
| 健康・医療 | 医師数(10万人あたり) | log(医師数 / 総人口 × 100000 + 1) | ○ |
| 福祉・社会保障 | 保育所等数(10万人あたり) | log(保育所等数 / 総人口 × 100000 + 1) | ○ |
| 安全 | 刑法犯認知件数(10万人あたり) | e-Stat 追加(2008年)・log変換 | ×(未収録) |
※ 従業者割合の11産業は「外国人労働者数が比較的多い産業」として原論文が選択したもの(前田 2019 を参照)。分布が大きく歪んだ非負データは対数変換(0を含む場合は+1してから変換)。
leaps、決定木=tree、ランダムフォレスト=RandomForest)。本ページの再現は Python(statsmodels・scikit-learn)| 手法 | 目的 | 原論文(R) | 本再現(Python) |
|---|---|---|---|
| 重回帰分析 | 外国人人口とすべての特性の関係を同時に推定 | lm | statsmodels OLS |
| Best Subset Selection | Cp基準で説明変数の最良の部分集合を選び過適合を防ぐ | leaps | —(報告値のみ) |
| 決定木回帰 | 線形性を仮定せずデータを段階的に分割 | tree | scikit-learn |
| ランダムフォレスト | 複数の木を組み合わせて予測精度を向上 | RandomForest | scikit-learn |
| 10-fold クロスバリデーション | 4手法の妥当性を平均二乗誤差で比較 | — | scikit-learn |
25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | 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 sklearn.tree import DecisionTreeRegressor, plot_tree from sklearn.ensemble import RandomForestRegressor from sklearn.linear_model import LinearRegression from sklearn.model_selection import KFold, cross_val_score 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' os.makedirs(FIG_DIR, exist_ok=True) |
print はしません。設定やデータが裏で更新されただけ。次のステップへ進みましょう。statsmodels は重回帰の推定・検定に、scikit-learn は決定木・ランダムフォレスト・クロスバリデーションに使います。原論文は R(leaps・tree・RandomForest)で同じ分析を行っています。matplotlib.use('Agg') — グラフを画面に出さずファイル保存するための設定。plt.rcParams['font.family'] — 図の日本語表示用フォント指定(Macは Hiragino Sans、Windowsなら Yu Gothic 等に変更)。from sklearn.model_selection import KFold, cross_val_score のように、必要な関数だけをインポートすると名前が短く書けます。45 46 47 48 49 50 51 52 53 54 55 | # ===== ステップ1: SSDSE-A(市区町村)から原論文 表1 の変数を構成 ===== # 1行目=変数コード、2行目=年度、3行目=日本語名 → コード行を列名に使う raw = pd.read_csv(DATA_A, encoding='cp932', header=0, skiprows=[1, 2]) raw = raw.rename(columns={raw.columns[0]: 'code', 'Prefecture': '都道府県', 'Municipality': '市区町村'}) num = raw.apply(pd.to_numeric, errors='coerce') print(f"【SSDSE-A-2025】{len(raw)}市区町村") print(' 原論文は SSDSE-2019A(2015年国勢調査ベース)+e-Stat追加4変数の29変数。') print(' SSDSE-A-2025 では 未婚者割合・課税対象所得・学歴割合(大卒)・') print(' 刑法犯認知件数 の4変数が未収録 → 25変数で部分再現する') |
【SSDSE-A-2025】1741市区町村 原論文は SSDSE-2019A(2015年国勢調査ベース)+e-Stat追加4変数の29変数。 SSDSE-A-2025 では 未婚者割合・課税対象所得・学歴割合(大卒)・ 刑法犯認知件数 の4変数が未収録 → 25変数で部分再現する
pd.read_csv(..., encoding='cp932', header=0, skiprows=[1, 2]) — SSDSE-A は1行目が変数コード、2行目が年度、3行目が日本語名。コード行(A1101・A1700 など)を列名に使います。raw.apply(pd.to_numeric, errors='coerce') — 全列を数値化し、数値にできないセルは欠損(NaN)にします。errors='coerce' は「変換できない値はNaNに」という意味。データクリーニングの定番オプションです。57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 | df = pd.DataFrame({'都道府県': raw['都道府県'], '市区町村': raw['市区町村']}) pop = num['A1101'].replace(0, np.nan) # 総人口(2020)。人口0の町村は除外 # 目的変数: 外国人人口(10万人あたり) = log(外国人/総人口*100000 + 1) df['外国人人口'] = np.log(num['A1700'] / pop * 100000 + 1) # 説明変数(原論文 表1 の計算式に従う。単位は標準化するため影響しない) df['総人口'] = np.log(pop) df['自然人口増減率'] = (num['A4101'] - num['A4200']) / pop * 100 df['社会的人口増減率'] = (num['A5101'] - num['A5102']) / pop * 100 df['核家族世帯割合'] = num['A810102'] / num['A710101'] * 100 df['単独世帯割合'] = num['A810105'] / num['A710101'] * 100 df['人口密度'] = np.log(pop / num['B1103']) # 可住地面積あたり emp = {'農業林業': 'C220832', '建設業': 'C220836', '製造業': 'C220837', '情報通信業': 'C220839', '運輸郵便業': 'C220840', '卸売小売業': 'C220841', '学術研究専門技術': 'C220846', '宿泊飲食サービス業': 'C220847', '教育学習支援業': 'C220849', '医療福祉': 'C220850', 'サービス業他': 'C220852'} for name, code in emp.items(): # 従業者割合(外国人労働者が多い11産業) df[f'従業者割合({name})'] = num[code] / num['C2208'] * 100 df['農家数'] = np.log(num['C310201'] / pop * 100000 + 1) # 販売農家 df['経常収支比率'] = num['D2203'] df['土木費割合'] = num['D320308'] / num['D3203'] * 100 df['教育費割合'] = num['D320310'] / num['D3203'] * 100 df['完全失業率'] = num['F1107'] / (num['F1107'] + num['F1102']) * 100 df['図書館数'] = np.log(num['G1401'] / pop * 100000 + 1) df['医師数'] = np.log(num['I6100'] / pop * 100000 + 1) df['保育所等数'] = np.log(num['J250302'] / pop * 100000 + 1) |
print はしません。設定やデータが裏で更新されただけ。次のステップへ進みましょう。log(外国人人口 / 総人口 × 100000 + 1)。原論文は分布が大きく歪んだ非負データに対数変換を施し、0を含む変数には1を加えてから変換しています(表1)。辞書+forループで11列を一気に作っています。完全失業者数/(完全失業者数+就業者数) — いずれも原論文 表1 の定義どおり。for name, code in emp.items(): — 辞書の「キーと値」を同時に取り出すループ。列の量産に便利です。86 87 88 89 90 91 92 93 | # 総人口ゼロ(2020年国勢調査の福島県双葉町)と欠損値のある行を除外 df = df.dropna().reset_index(drop=True) X_cols = [c for c in df.columns if c not in ('都道府県', '市区町村', '外国人人口')] print(f"\n分析対象: {len(df)}市区町村 / 説明変数 {len(X_cols)}個") # 原論文と同じく全変数を平均0・分散1に標準化 Z = df[X_cols + ['外国人人口']].copy() Z = (Z - Z.mean()) / Z.std(ddof=0) |
分析対象: 1740市区町村 / 説明変数 25個
(Z - Z.mean()) / Z.std(ddof=0) — 全変数を平均0・分散1に標準化。原論文と同じ処理で、これにより回帰係数の絶対値の大小で関係の強さを直接比較できるようになります。原論文はまず全29変数の重回帰モデルを推定し、残差プロット(原論文 図1)で外れ値と考えられる6箇所を確認してデータから除外した。比較の公平のため、以降のすべての手法で同じデータを用いている。本ページの図1は SSDSE-A-2025(25変数)による実データ部分再現で、|スチューデント化残差|>4 を基準にしたところ同じく6件が外れ値として検出された。
95 96 97 98 99 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 | # ===== ステップ2: 重回帰(全変数)と残差プロット → 図1 ===== X_full = sm.add_constant(Z[X_cols]) ols_full = sm.OLS(Z['外国人人口'], X_full).fit() stud = ols_full.get_influence().resid_studentized_external out_mask = np.abs(stud) > 4 # 外れ値: スチューデント化残差 |t|>4 print('\n=== 残差プロットによる外れ値の確認(原論文 4.1節の手順) ===') print(f"外れ値と判定: {out_mask.sum()}市区町村(|スチューデント化残差|>4)") print(df.loc[out_mask, ['都道府県', '市区町村']].to_string(index=False)) print(' ※ 原論文はSSDSE-2019Aの残差プロットで6箇所を外れ値として除外') fig1, ax1 = plt.subplots(figsize=(8, 6)) ax1.scatter(ols_full.fittedvalues, ols_full.resid, s=12, alpha=0.5, color='#3F51B5', label='市区町村') ax1.scatter(ols_full.fittedvalues[out_mask], ols_full.resid[out_mask], s=140, facecolors='none', edgecolors='#C62828', linewidths=1.8, label=f'外れ値として除外({out_mask.sum()}件)') ax1.axhline(0, color='gray', linewidth=1) ax1.set_xlabel('予測値(標準化後)') ax1.set_ylabel('残差') ax1.set_title('図1: 重回帰(25変数)の残差プロット\n' '【SSDSE-A-2025 による実データ部分再現。原論文 図1 は' 'SSDSE-2019A・29変数】', fontsize=11) ax1.legend() fig1.tight_layout() fig1.savefig(os.path.join(FIG_DIR, '2019_U5_2_fig1.png'), bbox_inches='tight') plt.close(fig1) print('図1 保存完了(実データ部分再現)') # 外れ値を除いて以降の分析に使う(原論文と同じく全手法で共通のデータ) dfc = df.loc[~out_mask].reset_index(drop=True) Zc = Z.loc[~out_mask].reset_index(drop=True) y = Zc['外国人人口'] X = Zc[X_cols] |
=== 残差プロットによる外れ値の確認(原論文 4.1節の手順) === 外れ値と判定: 6市区町村(|スチューデント化残差|>4) 都道府県 市区町村 青森県 西目屋村 青森県 大鰐町 福島県 檜枝岐村 東京都 青ヶ島村 長野県 川上村 和歌山県 北山村 ※ 原論文はSSDSE-2019Aの残差プロットで6箇所を外れ値として除外 図1 保存完了(実データ部分再現)
sm.OLS(y, X).fit() — 最小二乗法による重回帰。sm.add_constant で切片を追加します。get_influence().resid_studentized_external — スチューデント化残差。原論文は残差プロットを目視して6箇所を外れ値として除外しました。本再現では |スチューデント化残差|>4 を基準にしたところ、偶然にも同じ6件が検出されました(ただし原論文と同じ市区町村とは限りません)。df.loc[~out_mask] — ~ はブール値の反転。「外れ値ではない行」だけを残します。外れ値除去後のデータで重回帰モデルを推定した結果(原論文 表2)では、さまざまな分野の項目の係数が有意に推定された。全変数が標準化されているため、係数の絶対値の大小で目的変数との関係の強さを比較できる。
| 変数 | 回帰係数 | P値 | 解釈 |
|---|---|---|---|
| 人口密度 | +0.375 | 2.44E-16 | 絶対値が最大。都市的な地域ほど外国人人口が多い |
| 従業者割合(製造業) | +0.287 | 2.69E-09 | 製造業が盛んな地域で外国人人口が多い |
| 課税対象所得 | +0.236 | 7.95E-12 | 所得水準の高い地域で多い(SSDSE-A-2025未収録) |
| 未婚者割合 | +0.196 | 7.82E-11 | 未婚者の多い都市的地域で多い(同上) |
| 単独世帯割合 | +0.157 | 1.93E-09 | 単身世帯が多い地域で多い |
| 農家数 | +0.141 | 6.04E-06 | 販売農家が多い地域でも多い(農業分野の労働) |
| 従業者割合(宿泊業、飲食サービス業) | +0.138 | 8.76E-06 | 宿泊・飲食業の集積地で多い |
| 従業者割合(農業、林業) | +0.121 | 1.18E-06 | 農林業比率の高い地域で多い |
| 完全失業率 | −0.123 | 2.76E-08 | 失業率が低い(=働き手が不足する)地域で外国人人口が多い |
| 自然人口増減率 | −0.105 | 1.01E-04 | 自然減の進む地域で外国人人口が多い |
| 核家族世帯割合 | −0.083 | 7.63E-04 | 核家族中心の住宅地的な地域では少ない |
| 図書館数 | +0.044 | 0.009 | 文化施設の多い地域で多い(正で有意) |
値はいずれも原論文 表2 の報告値(標準化データでの回帰係数)。このほか刑法犯認知件数(+0.059)・歳出における教育費割合(+0.047)・従業者割合(情報通信業)(−0.047)・従業者割合(医療、福祉)(−0.067)なども5%水準で有意。総人口・社会的人口増減率・経常収支比率・医師数・学歴割合(大卒)などは非有意だった。
129 130 131 132 133 134 135 136 137 | # ===== ステップ3: 重回帰の推定(実データ部分再現) ===== ols = sm.OLS(y, sm.add_constant(X)).fit() coef = pd.DataFrame({'係数': ols.params, 'P値': ols.pvalues}).drop('const') coef_sorted = coef.reindex(coef['係数'].abs().sort_values(ascending=False).index) print('\n=== 重回帰の推定結果(実データ部分再現・係数の絶対値順) ===') print(coef_sorted.round(4).to_string()) print(f"決定係数 R2 = {ols.rsquared:.4f} / N = {len(y)}") print(' ※ 原論文(29変数)の報告値では 人口密度 0.375 が最大で、') print(' 製造業 0.287・課税対象所得 0.236・未婚者割合 0.196 が続く') |
=== 重回帰の推定結果(実データ部分再現・係数の絶対値順) ===
係数 P値
人口密度 0.3763 0.0000
従業者割合(製造業) 0.2661 0.0000
単独世帯割合 0.2363 0.0000
従業者割合(農業林業) 0.2046 0.0000
総人口 0.1757 0.0000
従業者割合(医療福祉) -0.1218 0.0011
従業者割合(建設業) -0.1181 0.0005
自然人口増減率 0.1172 0.0000
核家族世帯割合 -0.0911 0.0012
従業者割合(宿泊飲食サービス業) 0.0865 0.0193
従業者割合(運輸郵便業) 0.0809 0.0007
経常収支比率 -0.0794 0.0001
従業者割合(卸売小売業) -0.0737 0.0161
農家数 0.0532 0.0771
完全失業率 -0.0490 0.0124
従業者割合(学術研究専門技術) -0.0367 0.0859
図書館数 0.0337 0.0578
社会的人口増減率 -0.0335 0.1128
従業者割合(情報通信業) -0.0236 0.3190
医師数 -0.0163 0.4601
従業者割合(教育学習支援業) -0.0124 0.5845
教育費割合 0.0122 0.5098
保育所等数 0.0115 0.5325
従業者割合(サービス業他) -0.0076 0.7579
土木費割合 -0.0064 0.7232
決定係数 R2 = 0.4218 / N = 1734
※ 原論文(29変数)の報告値では 人口密度 0.375 が最大で、
製造業 0.287・課税対象所得 0.236・未婚者割合 0.196 が続くcoef.reindex(coef['係数'].abs().sort_values(ascending=False).index) — 「絶対値でソートした順番」を reindex で適用する小技。139 140 141 142 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 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 | # ===== ステップ4: 図2 原論文 表2 の報告値の可視化(再計算ではない) ===== # 原論文 表2「重回帰分析の推定結果」の29変数の係数とP値。 # ※印は SSDSE-A-2025 未収録(e-Stat追加)の4変数。 # 「従業者割合(卸売業、小売業)」は原論文表2では行ラベルが # 「宿泊、飲食サービス」と表記されているが、表1・表3の変数順との # 対応から卸売業、小売業の誤記とみられる(値はそのまま用いる)。 paper_tab2 = [ # (変数名, 係数, P値) ('人口密度', 0.375057, 2.44e-16), ('従業者割合(製造業)', 0.287207, 2.69e-09), ('課税対象所得※', 0.236293, 7.95e-12), ('未婚者割合※', 0.196142, 7.82e-11), ('単独世帯割合', 0.156925, 1.93e-09), ('農家数', 0.141056, 6.04e-06), ('従業者割合(宿泊業、飲食サービス業)', 0.137586, 8.76e-06), ('完全失業率', -0.123417, 2.76e-08), ('従業者割合(農業、林業)', 0.120916, 1.18e-06), ('自然人口増減率', -0.10529, 0.000101), ('核家族世帯割合', -0.08347, 0.000763), ('従業者割合(医療、福祉)', -0.067113, 0.034773), ('刑法犯認知件数※', 0.059249, 0.008059), ('総人口', 0.050128, 0.101313), ('歳出における教育費割合', 0.047338, 0.007494), ('従業者割合(情報通信業)', -0.047043, 0.036935), ('図書館数', 0.043784, 0.009019), ('従業者割合(運輸業、郵便業)', 0.04116, 0.053491), ('従業者割合(建設業)', -0.038802, 0.178217), ('従業者割合(教育、学習支援業)', -0.038387, 0.08657), ('経常収支比率', -0.032771, 0.103782), ('社会的人口増減率', -0.03223, 0.105888), ('学歴割合(大卒)※', -0.026888, 0.497624), ('保育所等数', 0.025764, 0.150709), ('従業者割合(サービス業他)', -0.011944, 0.573534), ('従業者割合(卸売業、小売業)', 0.010875, 0.696603), ('従業者割合(学術研究、専門・技術)', -0.005469, 0.78124), ('医師数', 0.005705, 0.792476), ('歳出における土木費割合', 0.002082, 0.902164), ] labels = [t[0] for t in paper_tab2][::-1] betas = [t[1] for t in paper_tab2][::-1] pvals = [t[2] for t in paper_tab2][::-1] colors = ['#C62828' if (p < 0.05 and b > 0) else '#1565C0' if (p < 0.05 and b < 0) else '#B0BEC5' for b, p in zip(betas, pvals)] fig2, ax2 = plt.subplots(figsize=(9, 10)) ax2.barh(range(len(labels)), betas, color=colors) ax2.set_yticks(range(len(labels))) ax2.set_yticklabels(labels, fontsize=9) ax2.axvline(0, color='gray', linewidth=1) ax2.set_xlabel('標準化データでの回帰係数(原論文 表2 の報告値)') ax2.set_title('図2: 重回帰分析(29変数)の推定係数\n' '【原論文 表2 の報告値の可視化(再計算ではない)。' '赤=正で有意・青=負で有意(P<0.05)・灰=非有意】\n' '※印は SSDSE-A-2025 未収録の e-Stat 追加変数', fontsize=11) fig2.tight_layout() fig2.savefig(os.path.join(FIG_DIR, '2019_U5_2_fig2.png'), bbox_inches='tight') plt.close(fig2) print('\n図2 保存完了(原論文 表2 の報告値の可視化)') |
図2 保存完了(原論文 表2 の報告値の可視化)
paper_tab2 のリストは原論文 表2 に記載された29変数の係数とP値をそのまま転記したもの。本スクリプトで計算した値ではありません。[(名前, 係数, P値), ...] は、小さな表データをコード内に埋め込むときの定番の書き方です。説明変数が29と多いため、そのすべてが外国人人口と関係するとは限らず、パラメータが多すぎると過適合を招く。原論文は Best Subset Selection により Cp基準(AICに基づく変数選択と同様の結果になることが知られる)で、目的変数をよく説明する変数の部分集合を選んだ。
leaps に固有の出力であり、本ページでは再現していない。グラフは原論文(図2)を参照。
197 198 199 200 201 202 | # ===== ステップ5: Best Subset Selection の結果(原論文の報告) ===== print('\n=== Best Subset Selection(原論文 4.2節の報告) ===') print(' 原論文はRのleapsで全29変数からCp基準の変数選択を行い、') print(' 23変数のモデルが最良(Cp最小)。除外されたのは 歳出の土木費割合・') print(' 医師数・大卒者割合・従業者割合(卸売、学術研究、サービス) の6つ。') print(' Cpプロット(原論文 図2)は本スクリプトでは再現しない(原論文参照)') |
=== Best Subset Selection(原論文 4.2節の報告) === 原論文はRのleapsで全29変数からCp基準の変数選択を行い、 23変数のモデルが最良(Cp最小)。除外されたのは 歳出の土木費割合・ 医師数・大卒者割合・従業者割合(卸売、学術研究、サービス) の6つ。 Cpプロット(原論文 図2)は本スクリプトでは再現しない(原論文参照)
leaps で全29変数から Cp 基準の変数選択を行い、23変数のモデルが最良となりました。sklearn.feature_selection.SequentialFeatureSelector(逐次選択)が手軽な代替になります。重回帰は目的変数と説明変数の線形関係を仮定する。原論文はこの仮定を置かない機械学習手法として、データを段階的に分割する木構造に基づく決定木回帰を追加した。図の上にある変数ほど早い段階で分割に使われ、重要度が高い。
204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 | # ===== ステップ6: 決定木回帰 → 図3(実データ部分再現) ===== tree = DecisionTreeRegressor(max_leaf_nodes=9, random_state=0).fit(X, y) fig3, ax3 = plt.subplots(figsize=(13, 7)) plot_tree(tree, feature_names=X_cols, filled=True, rounded=True, fontsize=8, impurity=False, ax=ax3) ax3.set_title('図3: 決定木回帰(25変数・葉9個)\n' '【SSDSE-A-2025 による実データ部分再現。原論文 図3 の木' '(最初の分割は課税対象所得)とは変数構成が異なる】', fontsize=11) fig3.tight_layout() fig3.savefig(os.path.join(FIG_DIR, '2019_U5_2_fig3.png'), bbox_inches='tight') plt.close(fig3) used = [X_cols[i] for i in sorted(set(tree.tree_.feature)) if i >= 0] print('\n=== 決定木回帰(実データ部分再現) ===') print(f"分割に使われた変数: {'、'.join(used)}") print(' ※ 原論文の木は 課税対象所得 → 大卒割合・未婚割合 の順に分割し、') print(' ほかに製造業・医療福祉・農家数・完全失業率・核家族世帯割合を使用。') print(' 課税対象所得・大卒割合・未婚割合は SSDSE-A-2025 未収録') print('図3 保存完了(実データ部分再現)') |
=== 決定木回帰(実データ部分再現) ===
分割に使われた変数: 自然人口増減率、単独世帯割合、従業者割合(建設業)、従業者割合(製造業)、従業者割合(教育学習支援業)、従業者割合(医療福祉)
※ 原論文の木は 課税対象所得 → 大卒割合・未婚割合 の順に分割し、
ほかに製造業・医療福祉・農家数・完全失業率・核家族世帯割合を使用。
課税対象所得・大卒割合・未婚割合は SSDSE-A-2025 未収録
図3 保存完了(実データ部分再現)DecisionTreeRegressor(max_leaf_nodes=9) — 葉の数を原論文の木(葉9個程度)に合わせています。原論文は R の tree パッケージを使用。plot_tree(..., filled=True) — 色が濃いノードほど予測値(外国人人口)が高いグループ。tree.tree_.feature には各ノードの分割に使った変数の番号が入っています(葉は−2)。set で重複を除いて「使われた変数」を一覧できます。ランダムフォレストは複数の木の平均で予測を改善する。決定木の長所であるモデルの説明力(構造の見やすさ)は失われるが、変数をモデルから外した場合に予測がどれだけ悪化するかに基づいて各説明変数の重要度を計算できる。
224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 | # ===== ステップ7: ランダムフォレスト → 図4(実データ部分再現) ===== rf = RandomForestRegressor(n_estimators=500, random_state=0, n_jobs=-1).fit(X, y) imp = pd.Series(rf.feature_importances_, index=X_cols).sort_values() fig4, ax4 = plt.subplots(figsize=(9, 8)) ax4.barh(range(len(imp)), imp.values, color='#2E7D32') ax4.set_yticks(range(len(imp))) ax4.set_yticklabels(imp.index, fontsize=9) ax4.set_xlabel('変数重要度(不純度減少に基づく)') ax4.set_title('図4: ランダムフォレストの変数重要度(25変数)\n' '【SSDSE-A-2025 による実データ部分再現。原論文 図4 は' 'R の RandomForest・29変数で、課税対象所得が最重要】', fontsize=11) fig4.tight_layout() fig4.savefig(os.path.join(FIG_DIR, '2019_U5_2_fig4.png'), bbox_inches='tight') plt.close(fig4) print('\n=== ランダムフォレストの変数重要度(実データ部分再現・上位8) ===') print(imp.sort_values(ascending=False).head(8).round(4).to_string()) print(' ※ 原論文(29変数)では 課税対象所得 が最重要、未婚割合、') print(' 従業者割合(製造業、医療・福祉)、犯罪認知件数などが続く') print('図4 保存完了(実データ部分再現)') |
=== ランダムフォレストの変数重要度(実データ部分再現・上位8) ===
従業者割合(建設業) 0.1944
従業者割合(医療福祉) 0.0960
単独世帯割合 0.0626
人口密度 0.0561
従業者割合(製造業) 0.0529
自然人口増減率 0.0515
農家数 0.0356
従業者割合(卸売小売業) 0.0353
※ 原論文(29変数)では 課税対象所得 が最重要、未婚割合、
従業者割合(製造業、医療・福祉)、犯罪認知件数などが続く
図4 保存完了(実データ部分再現)RandomForestRegressor(n_estimators=500) — 500本の木の平均で予測。原論文は R の RandomForest ライブラリを使用しています。feature_importances_ は「その変数での分割が不純度(分散)をどれだけ減らしたか」に基づく重要度。原論文の図4(Rの重要度)とは定義が完全には一致しない点に注意。n_jobs=-1 で全CPUコアを使って並列学習。500本の木でも数秒で終わります。最後に原論文は10-fold クロスバリデーションで4手法の妥当性を比較した。
| 手法 | 重回帰分析 | Best Subset Selection | 決定木分析 | ランダムフォレスト |
|---|---|---|---|---|
| 原論文 表4(29変数・R) | 0.4686977 | 0.4641824 | 0.527091 | 0.4305073 |
| 本再現(25変数・Python) | 0.5125672 | —(未実施) | 0.6266032 | 0.4904763 |
どちらでもランダムフォレストが最小=最も妥当、決定木が最大。原論文は「決定木は使用する説明変数が少なくデータの構造を捉えるには不十分な可能性がある。Best Subset Selection は変数選択の分だけ重回帰より予測が少し改善。ランダムフォレストと Best Subset Selection の差はそれほど大きくなく、線形回帰モデルでもデータの関係性をある程度うまく捉えられている」と解釈している。
246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 | # ===== ステップ8: 10-fold クロスバリデーションで手法を比較 ===== cv = KFold(n_splits=10, shuffle=True, random_state=1) models = {'重回帰分析': LinearRegression(), '決定木分析': DecisionTreeRegressor(max_leaf_nodes=9, random_state=0), 'ランダムフォレスト': RandomForestRegressor( n_estimators=500, random_state=0, n_jobs=-1)} print('\n=== 10-fold クロスバリデーション: 平均二乗誤差の平均値 ===') print('【SSDSE-A-2025・25変数による実データ部分再現】') for name, model in models.items(): mse = -cross_val_score(model, X, y, cv=cv, scoring='neg_mean_squared_error').mean() print(f" {name:<12s}: {mse:.7f}") print('【原論文 表4 の報告値(SSDSE-2019A・29変数、R による)】') print(' 重回帰分析: 0.4686977 / Best Subset Selection: 0.4641824') print(' 決定木分析: 0.527091 / ランダムフォレスト: 0.4305073') print(' → 原論文でも本再現でもランダムフォレストが最小(=最も妥当)。') print(' Best Subset Selection は本再現では実行していない(原論文参照)') |
=== 10-fold クロスバリデーション: 平均二乗誤差の平均値 ===
【SSDSE-A-2025・25変数による実データ部分再現】
重回帰分析 : 0.5125672
決定木分析 : 0.6266032
ランダムフォレスト : 0.4904763
【原論文 表4 の報告値(SSDSE-2019A・29変数、R による)】
重回帰分析: 0.4686977 / Best Subset Selection: 0.4641824
決定木分析: 0.527091 / ランダムフォレスト: 0.4305073
→ 原論文でも本再現でもランダムフォレストが最小(=最も妥当)。
Best Subset Selection は本再現では実行していない(原論文参照)KFold(n_splits=10, shuffle=True) — データを10分割し、9割で学習・1割で検証を10回繰り返して平均二乗誤差(MSE)を平均します。cross_val_score は「大きいほど良い」スコアを返す設計なので、誤差系は neg_mean_squared_error(負のMSE)を指定し、符号を反転して使います。個々の特性と外国人人口の相関係数を1つずつ計算する方法では、説明変数同士の相関を無視することになる。重回帰分析はすべての特性の関係を同時に推定することでこの問題に対処する。
原論文は分析前に全変数を平均0・分散1に標準化しているため、推定された係数は標準化係数に相当し、単位の異なる変数どうしでも絶対値の大小で関係の強さを比較できる。
人口密度(人/ha)と完全失業率(%)の係数は、そのままでは単位が違って比べられない。標準化後の係数は「その変数が1標準偏差増えると目的変数が何標準偏差変わるか」を表し、横並びの比較が可能になる。
説明変数のすべての組み合わせ(229通り)についてモデルを評価し、最良の部分集合を選ぶ方法。評価には Mallows の Cp を使う。変数を増やすと当てはまり(RSS)は必ず良くなるが、Cp は変数の数 p にペナルティを課すことで過適合を防ぐ。
Cp基準による選択はAICに基づく変数選択と同様の結果になることが知られている(原論文 4.2節)。原論文では29変数中23変数のモデルが最良となった。
決定木回帰は「変数xがc以上か未満か」でデータを再帰的に分割し、各グループ(葉)の平均値で予測する。線形性を仮定しないため、非線形な関係や変数の組み合わせ効果を捉えられる。ランダムフォレストは、ブートストラップ標本と変数のランダムな部分集合で育てた多数の木の予測を平均することで、単一の木より予測を安定させる。
原論文の面白い発見:大卒割合は重回帰・変数選択では選ばれない(非有意)のに、決定木・ランダムフォレストでは重要変数と認識された。原論文はここから「大卒割合と外国人人口の間には非線形な関係が存在するのかもしれない」と考察している。手法によって見えるものが違う——だから複数の手法で見るのが探索的データ分析の鉄則。
データを10個のブロックに分け、9ブロックで学習・残り1ブロックで検証を10回繰り返し、平均二乗誤差(MSE)の平均で手法を比較する。学習に使っていないデータで測ることで、過適合したモデルを不当に高評価することを避けられる。
29変数の重回帰はR²(当てはまり)では複雑なモデルほど有利。しかしCVで測る「未知データへの予測力」では、変数選択したモデル(Best Subset)が全変数モデルを上回った——これが過適合の実例。原論文の表4はこの教科書的な現象を実データで示している。
| 課題 | 内容 | 対処・考え方 |
|---|---|---|
| 過適合 | 説明変数が30近くあると、データへの当てはまり過ぎで汎化性能が落ちる | Best Subset Selection・AIC/Cp基準、交差検証での評価(原論文の設計そのもの) |
| 因果ではなく相関 | 「製造業割合が高い→外国人が増える」とは限らない(逆・共通要因もあり得る) | 傾向スコアなど因果推論の手法(原論文 5節が指摘) |
| 欠落変数 | 大学の有無・家賃水準など、外国人人口と関係が深そうな変数が含まれていない | 変数を追加した場合、大卒割合や犯罪認知件数の係数が変化する可能性に留意(原論文 5節) |
| 一時点データ | 社会構造は時間と共に変化するが、分析は一時点の断面のみ | 複数時点のデータセットで時間構造を明示的に取り込む(原論文 5節) |
| 統計間の乖離 | 国勢調査の外国人数は在留外国人統計の78.5%(2015年)に留まる | 使用統計の定義・捕捉率の違いを明記して解釈する(原論文 5節) |
| 生態学的誤謬 | 市区町村集計の知見を個人(個々の外国人の行動)に一般化する危険 | 分析単位を明記し、個人レベルの推論は避ける |
原論文は仮説検証型ではなく探索型の設計:多数の変数×複数の手法で「どの特性が効いていそうか」の地図を描き、都市化・働き手不足・特定産業という3つの解釈仮説に絞り込んだ。審査会コメントも「機械学習による政策分析の可能性を示す論文」と評価している。次の段階は、この仮説を因果推論の設計(傾向スコア・パネルデータなど)で検証すること。
| データ名 | 収録内容 | 出典機関 |
|---|---|---|
| SSDSE-2019A(原論文が使用) | 市区町村別の社会・人口統計(2015年国勢調査ほか) | 独立行政法人統計センター(教育用標準データセット SSDSE) |
| SSDSE-A-2025(本ページの再現が使用) | 市区町村別の社会・人口統計(2020年国勢調査ほか、1,741市区町村) | 独立行政法人統計センター(教育用標準データセット SSDSE) |
| 都道府県・市区町村のすがた(社会・人口統計体系) | 原論文が追加した4変数:未婚者割合(2015)・課税対象所得(2017)・学歴割合(大卒)(2010)・刑法犯認知件数(2008) | 総務省統計局(e-Stat) |
本ページの図1・図3・図4と「実行結果」はすべて SSDSE-A-2025 の実データによる部分再現(25変数)です。図2および本文中の「原論文の報告値」(回帰係数・P値・CVの平均二乗誤差・変数選択の結果)は原論文の記載値をそのまま転記したもので、本ページで再計算したものではありません。原論文はRによる29変数の分析であり、本再現の数値は原論文と一致しません。合成データ・架空の数値は一切含みません。
この論文(と同種の探索的分析)を読むときに陥りやすい誤解を整理します。
本文中の枠線付き用語はクリックすると詳しい解説がポップアップします。ここでは特に重要な用語をまとめます。
本論文で使われた統計・機械学習手法を「何?」「どう使う?」「注意点は?」の順で整理します。注意点は手法ごとに異なります。
この研究をさらに発展させるための3つの方向性を示します。「今回わかったこと(X)」から「次に検証すべき仮説(Y)」を立て、「具体的に何をするか(Z)」まで考えてみましょう。
学んだだけでは身につきません。実際に手を動かすのが最強の学習方法です。本ページのスクリプトをベースに、以下のチャレンジに挑戦してみてください。難易度別に5つ用意しました。
max_leaf_nodes を 3〜50 まで動かして10-fold CVのMSEをプロットしてください。
sklearn.linear_model.LassoCV で係数が0にならなかった変数と、原論文のBest Subset Selectionが選んだ23変数(本ページ4.2節)を見比べてください。
本論文で学んだ手法(重回帰+変数選択+木ベースの機械学習+CV)は、行政・企業・研究の現場で広く使われています。
この論文を読んで初心者が抱きやすい疑問に、教育的観点から答えます。
この論文を理解できたか、クリックで確認しましょう。間違えても解説が出ます。
このページの分析は、この画面の中でそのまま実行できます。
Python をインストールする必要も、CSV をダウンロードする必要もありません。
下のセルの 「▶ ブラウザで実行」 を上から順に押すか、
「▶ 最初から全部実行」 で一気に流してください。
表示されるのは、本文の図表とまったく同じ計算の結果です
(動かしているのは再現スクリプト code/2019_U5_2_shorei.py そのもの)。
コードは書き換えられます。「✏️ 書き換える」で数字や変数を変えて ▶ を押すと、 結果がどう変わるかをその場で確かめられます(元に戻すボタンつき)。 ページを開いた時点で裏で実行環境の準備が始まっているので、待ち時間はほとんどありません。 初回だけ実行環境の取得にインターネット接続が必要です。