このページの分析を自分で再現するには、以下の手順でデータを準備してください。コードの編集は不要です。
data/raw/ フォルダに入れます。html/figures/ に自動保存されます。
日本では都市部と農村部の経済格差が長年の課題となっている。本研究は 都道府県別の消費支出を所得水準の代理変数として用い、 雇用環境(求人倍率)・住宅地価・人口動態の観点から 都市農村間の格差構造を統計的に明らかにすることを目的とする。
まず「都市農村間の所得格差消費支出と雇用環境の地域差分析」を統計的にとらえることが有効だと考えられる。 その理由は感覚や経験則だけでは、複雑な社会要因の中で「何が本当に効いているか」を見極めにくいからである。 本研究では公開データと統計手法を組み合わせ、この問いに定量的な答えを出すことを目指す。
SSDSE-B 消費支出(所得代理) 求人倍率 住宅地価(都市度) Mann-Whitney U検定
SSDSE-B(社会・人口統計体系 都道府県データ) を使用。 47都道府県 × 2012〜2023年の12年間のパネルデータ(564行)を分析対象とする。 横断面分析(2022年)と時系列分析(2012〜2023年)を組み合わせた。
住宅地価の全国中央値(2022年:30,800円/m²)を閾値として、 都道府県を2グループに分類した。住宅地価は地域の経済活力・利便性を総合的に反映するため、 都市度の代理指標として適切である(ヘドニック価格法の考え方、下記 DS LEARNING POINT 3 参照)。
| 区分 | 条件 | 都道府県数 | 代表的な都道府県 |
|---|---|---|---|
| 都市部 | 住宅地価 ≥ 30,800円/m² | 24都道府県 | 東京都・神奈川県・愛知県・大阪府など |
| 地方 | 住宅地価 < 30,800円/m² | 23都道府県 | 秋田県・島根県・高知県・宮崎県など |
所得データが直接入手できない場合、消費支出を所得の「代理変数(proxy variable)」として 使うことがある。この背景には2つの経済理論がある。
エンゲルの法則(1857年):所得が高い家計ほど、食費の割合が低く、 文化・娯楽・教育費の割合が高い。→ 消費の総額・構成比が所得水準を反映する。
恒常所得仮説(Friedman, 1957):家計の消費は「今月の所得」ではなく 「将来にわたる恒常的な所得水準」に依存する。一時的な変動には左右されにくい。 → 消費支出は「生涯所得の期待値」をよく近似する。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | df_raw = pd.read_csv(DATA_B, encoding='shift-jis', header=0) # 行0がラベル行、行1以降が実データ df_all = df_raw.iloc[1:].copy() df_all.columns = df_raw.columns # 数値列に変換 NUM_COLS = ['A1101', 'A1303', 'A5101', 'A5102', 'C5401', 'E6302', 'F3102', 'F3103', 'L3221'] for c in NUM_COLS: df_all[c] = pd.to_numeric(df_all[c], errors='coerce') df_all['年度'] = df_all['SSDSE-B-2026'].astype(int) df_all['都道府県'] = df_all['Prefecture'].astype(str) print("=" * 65) print("■ SSDSE-B-2026 読み込み完了") print(f" 年度範囲: {df_all['年度'].min()}〜{df_all['年度'].max()}") print(f" 都道府県数(各年): {df_all.groupby('年度')['都道府県'].count().iloc[0]}件") print("=" * 65) |
================================================================= ■ SSDSE-B-2026 読み込み完了 年度範囲: 2012〜2023 都道府県数(各年): 47件 =================================================================
pd.read_csv(...) でCSVを読み込みます。encoding='cp932' は日本語Windows由来の文字コード、header=1 は「2行目を列名として使う」。.astype(int) — 列を整数に変換(年度などを数値比較するため)。df.groupby('列').apply(関数) — グループごとに関数を適用。時系列や地域別の集計でよく使います。df['A'] / df['B'] — pandasの列同士の四則演算は要素ごと(element-wise)。forループ不要なのが強み。20 21 22 23 24 25 | df_all['消費支出_万円'] = df_all['L3221'] / 10000 df_all['求人倍率'] = df_all['F3103'] / df_all['F3102'] df_all['住宅地価_log'] = np.log(df_all['C5401']) df_all['高齢化率'] = df_all['A1303'] / df_all['A1101'] * 100 df_all['大学学生数_千人'] = df_all['E6302'] / 1000 df_all['純移動率'] = (df_all['A5101'] - df_all['A5102']) / df_all['A1101'] * 1000 |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。.map() は「1対1の置き換え」、.apply() は「関数を当てる」。辞書なら .map()、ロジックなら .apply()。26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 | # 2022年データ抽出 df_2022 = df_all[df_all['年度'] == 2022].copy().reset_index(drop=True) # 都市部 / 地方 分類(住宅地価の全国中央値を閾値) land_median = df_2022['C5401'].median() df_2022['地域区分'] = df_2022['C5401'].apply( lambda x: '都市部' if x >= land_median else '地方' ) df_all['地域区分'] = df_all.apply( lambda row: '都市部' if row['C5401'] >= land_median else '地方', axis=1 ) print(f"\n■ 2022年データ: {len(df_2022)}都道府県") print(f" 住宅地価 中央値(分類閾値): {land_median:,.0f} 円/m²") print(f" 都市部: {(df_2022['地域区分']=='都市部').sum()}都道府県") print(f" 地方 : {(df_2022['地域区分']=='地方').sum()}都道府県") print(f" 消費支出(平均): {df_2022['消費支出_万円'].mean():.2f}万円/月") print(f" 求人倍率 (平均): {df_2022['求人倍率'].mean():.3f}") print(f" 高齢化率 (平均): {df_2022['高齢化率'].mean():.1f}%") |
■ 2022年データ: 47都道府県 住宅地価 中央値(分類閾値): 30,800 円/m² 都市部: 24都道府県 地方 : 23都道府県 消費支出(平均): 28.96万円/月 求人倍率 (平均): 1.394 高齢化率 (平均): 31.4%
[式 for x in リスト] はリスト内包表記。forループでappendする代わりに1行でリストを作れます。46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 | REGION_MAP = { '北海道': '北海道・東北', '青森県': '北海道・東北', '岩手県': '北海道・東北', '宮城県': '北海道・東北', '秋田県': '北海道・東北', '山形県': '北海道・東北', '福島県': '北海道・東北', '茨城県': '関東', '栃木県': '関東', '群馬県': '関東', '埼玉県': '関東', '千葉県': '関東', '東京都': '関東', '神奈川県': '関東', '新潟県': '中部', '富山県': '中部', '石川県': '中部', '福井県': '中部', '山梨県': '中部', '長野県': '中部', '岐阜県': '中部', '静岡県': '中部', '愛知県': '中部', '三重県': '近畿', '滋賀県': '近畿', '京都府': '近畿', '大阪府': '近畿', '兵庫県': '近畿', '奈良県': '近畿', '和歌山県': '近畿', '鳥取県': '中国・四国', '島根県': '中国・四国', '岡山県': '中国・四国', '広島県': '中国・四国', '山口県': '中国・四国', '徳島県': '中国・四国', '香川県': '中国・四国', '愛媛県': '中国・四国', '高知県': '中国・四国', '福岡県': '九州・沖縄', '佐賀県': '九州・沖縄', '長崎県': '九州・沖縄', '熊本県': '九州・沖縄', '大分県': '九州・沖縄', '宮崎県': '九州・沖縄', '鹿児島県': '九州・沖縄', '沖縄県': '九州・沖縄', } REGION_ORDER = ['北海道・東北', '関東', '中部', '近畿', '中国・四国', '九州・沖縄'] REGION_COLORS = { '北海道・東北': '#1565C0', '関東': '#C62828', '中部': '#2E7D32', '近畿': '#6A1B9A', '中国・四国': '#795548', '九州・沖縄': '#00695C', } df_all['地方'] = df_all['都道府県'].map(REGION_MAP) df_2022['地方'] = df_2022['都道府県'].map(REGION_MAP) |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。[式 for x in リスト] はリスト内包表記。forループでappendする代わりに1行でリストを作れます。全国47都道府県を6地方ブロック(北海道・東北 / 関東 / 中部 / 近畿 / 中国・四国 / 九州・沖縄) に分類し、各ブロックの平均消費支出の推移を可視化した。
| 地方ブロック | 2022年 平均消費支出 | 特徴 |
|---|---|---|
| 関東 | 約31〜33万円 | 最高水準。東京圏の集積効果が顕著 |
| 近畿 | 約29〜30万円 | 大阪・京都が牽引。関東に次ぐ水準 |
| 中部 | 約28〜30万円 | 愛知(自動車産業)が高水準 |
| 中国・四国 | 約27〜28万円 | 地方中核都市の影響 |
| 九州・沖縄 | 約26〜28万円 | 沖縄が独自の低水準パターン |
| 北海道・東北 | 約26〜27万円 | 最低水準グループ。高齢化・人口流出が影響 |
75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 | print("\n" + "=" * 65) print("■ 図1: 消費支出の時系列推移(地域別平均)") print("=" * 65) df_region_ts = ( df_all.groupby(['年度', '地方'])['消費支出_万円'] .mean() .reset_index() ) fig1, ax1 = plt.subplots(figsize=(11, 6)) for region in REGION_ORDER: sub = df_region_ts[df_region_ts['地方'] == region].sort_values('年度') if len(sub) == 0: continue ax1.plot(sub['年度'], sub['消費支出_万円'], marker='o', linewidth=2.2, markersize=5.5, color=REGION_COLORS[region], label=region) |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。df.groupby('列').apply(関数) — グループごとに関数を適用。時系列や地域別の集計でよく使います。fig, ax = plt.subplots(...) — 図全体(fig)と軸(ax)を作る定番。以降は ax.bar(...) 等で操作。sort_values('列名', ascending=False) — 指定列で並べ替え(降順)。x if cond else y は三項演算子。リスト内包表記と組み合わせると、forとifを1行で書けます。94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 | # 2020年にCOVID-19 影響を示す縦線 ax1.axvline(2020, color='#FF8F00', linestyle=':', linewidth=1.5, alpha=0.8) ax1.text(2020.08, ax1.get_ylim()[0] + 0.5, 'COVID-19\n(2020)', ha='left', va='bottom', fontsize=8.5, color='#FF8F00') # 2022年に分析基準年の縦線 ax1.axvline(2022, color='#C62828', linestyle='--', linewidth=1.5, alpha=0.8) ax1.text(2022.08, ax1.get_ylim()[1] - 0.8, '2022年\n(分析基準年)', ha='left', va='top', fontsize=8.5, color='#C62828', fontweight='bold') ax1.set_xlabel('年度', fontsize=12) ax1.set_ylabel('消費支出(万円/月)', fontsize=12) ax1.set_title('消費支出の時系列推移(地方別平均, 2012〜2023年)\n〜関東が最高、九州・東北が低水準で推移〜', fontsize=13, fontweight='bold') ax1.set_xticks(sorted(df_all['年度'].unique())) ax1.xaxis.set_tick_params(rotation=45) ax1.legend(loc='lower right', fontsize=9.5, framealpha=0.88) ax1.grid(True, alpha=0.3) plt.tight_layout() fig1.savefig(os.path.join(FIG_DIR, '2022_H5_14_fig1_timeseries.png'), bbox_inches='tight', dpi=150) plt.close(fig1) print("図1保存: 2022_H5_14_fig1_timeseries.png") |
================================================================= ■ 図1: 消費支出の時系列推移(地域別平均) ================================================================= 図1保存: 2022_H5_14_fig1_timeseries.png
ax.axhline / ax.axvline — 水平/垂直の点線。平均線や基準線として定番。df[col](1列)と df[[col1, col2]](複数列)でカッコの数が違います。リストを渡していると覚えるとミスを減らせます。住宅地価(対数変換)と消費支出の散布図を描き、都市度と所得水準(消費支出)の関係を視覚化した。 赤丸(○)が都市部、青三角(△)が地方を示す。
なぜ住宅地価が「都市度」を表すのか。その理論的根拠はヘドニック価格法(hedonic pricing)にある。 不動産の価格は、その物件が持つ様々な属性(立地・交通利便性・商業集積・雇用アクセス)の価値の総和として 決まると考える手法である。
都市部の住宅地価が高いのは、単に土地が「希少」なだけでなく、 周辺に集積する「雇用機会・商業施設・交通インフラ・高収入職種」への アクセス価値が価格に反映されているためである。
118 119 120 121 122 123 124 125 126 127 128 129 130 | print("\n■ 図2: 住宅地価 vs 消費支出 散布図(2022年)") fig2, ax2 = plt.subplots(figsize=(11, 7)) color_map = {'都市部': '#C62828', '地方': '#1565C0'} marker_map = {'都市部': 'o', '地方': '^'} for grp in ['都市部', '地方']: sub2 = df_2022[df_2022['地域区分'] == grp] ax2.scatter(sub2['住宅地価_log'], sub2['消費支出_万円'], c=color_map[grp], marker=marker_map[grp], s=80, alpha=0.80, edgecolors='#333', linewidth=0.4, label=grp, zorder=3) |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。fig, ax = plt.subplots(...) — 図全体(fig)と軸(ax)を作る定番。以降は ax.bar(...) 等で操作。df[col](1列)と df[[col1, col2]](複数列)でカッコの数が違います。リストを渡していると覚えるとミスを減らせます。131 132 133 134 135 136 137 138 | # 回帰直線(全データ) valid2 = df_2022[['住宅地価_log', '消費支出_万円']].dropna() x2v = valid2['住宅地価_log'].values y2v = valid2['消費支出_万円'].values z2 = np.polyfit(x2v, y2v, 1) xs2 = np.linspace(x2v.min(), x2v.max(), 100) ax2.plot(xs2, np.poly1d(z2)(xs2), 'k--', linewidth=1.8, alpha=0.65, label=f'回帰直線 (r={r_land:.3f}, p={p_land:.4f})') |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。s[:-n]「末尾n文字を除く」/s[n:]「先頭n文字を除く」。スライス [start:stop:step] はリスト・タプル・文字列共通の基本ワザです。139 140 141 142 143 144 | # 中央値の閾値ライン log_med = np.log(land_median) ax2.axvline(log_med, color='#FF8F00', linestyle=':', linewidth=1.5, alpha=0.8) ax2.text(log_med + 0.02, ax2.get_ylim()[0] + 0.3, f'中央値\n({land_median:,.0f}円/m²)', ha='left', va='bottom', fontsize=8, color='#FF8F00') |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。ax.axhline / ax.axvline — 水平/垂直の点線。平均線や基準線として定番。np.cumsum(arr) は累積和、np.linspace(a, b, n) は「aからbを等間隔でn個」。NumPyの定石です。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 | # 都道府県ラベル(注目都市) LABEL_PREFS = { '東京都', '北海道', '沖縄県', '秋田県', '青森県', '神奈川県', '愛知県', '大阪府', '京都府', '福岡県', '高知県', '島根県', '鳥取県', '宮崎県', } for _, row2 in df_2022.iterrows(): if row2['都道府県'] in LABEL_PREFS: short = row2['都道府県'].replace('県', '').replace('都', '').replace('道', '').replace('府', '') ax2.annotate(short, (row2['住宅地価_log'], row2['消費支出_万円']), xytext=(4, 3), textcoords='offset points', fontsize=7.5, color='#1A237E', fontweight='bold') ax2.set_xlabel('住宅地価(log 変換, 円/m²)', fontsize=12) ax2.set_ylabel('消費支出(万円/月)', fontsize=12) ax2.set_title('住宅地価(都市度の代理)と消費支出の関係(2022年, 都道府県別)\n' '〜都市部(赤○)は高地価・高消費支出, 地方(青△)は低水準〜', fontsize=13, fontweight='bold') ax2.legend(fontsize=9.5) ax2.grid(True, alpha=0.3) plt.tight_layout() fig2.savefig(os.path.join(FIG_DIR, '2022_H5_14_fig2_scatter.png'), bbox_inches='tight', dpi=150) plt.close(fig2) print("図2保存: 2022_H5_14_fig2_scatter.png") |
■ 図2: 住宅地価 vs 消費支出 散布図(2022年) 図2保存: 2022_H5_14_fig2_scatter.png
for _, row in df.iterrows() — DataFrameを1行ずつ取り出すループ。1点ずつ描画したいときに使用。{値:.2f}(小数2桁)、{値:,}(3桁区切り)、{値:>10}(右寄せ10桁)など、覚えると出力が一気に整います。消費支出(万円/月)を目的変数とし、求人倍率・住宅地価(log)・高齢化率・ 大学学生数・純移動率の5変数を説明変数とした OLS 重回帰分析(2022年, N=47)を行った。 変数はすべて標準化し、係数の大きさを比較可能にした。
| 変数 | 標準化係数(β) | SE | p値 | 有意性 | 解釈 |
|---|---|---|---|---|---|
| 純移動率(‰) | +0.809 | 0.390 | 0.044 | * | 人口流入が多い都道府県で消費支出が高い |
| 求人倍率 | +0.621 | 0.309 | 0.051 | n.s. | 雇用需給の改善が消費押し上げの傾向(境界) |
| 住宅地価(log) | +0.954 | 0.613 | 0.127 | n.s. | 単変量では有意(r=0.41, p=0.004)だが多重共線性の影響あり |
| 高齢化率(%) | +0.253 | 0.479 | 0.600 | n.s. | 単独効果は消費支出に明確な影響なし |
| 大学学生数(千人) | -0.302 | 0.436 | 0.492 | n.s. | 若年学生が多い県で消費がやや低い(収入制約) |
R² = 0.286, 自由度修正済 R² = 0.199(モデル全体 p = 0.014)
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 201 202 203 204 205 | print("\n■ 図3: OLS回帰係数プロット") coefs3 = np.asarray(ols_model.params[1:]) ses3 = np.asarray(ols_model.bse[1:]) pvals3 = np.asarray(ols_model.pvalues[1:]) COEF_COLORS = [] for p in pvals3: if p < 0.01: COEF_COLORS.append('#C62828') elif p < 0.05: COEF_COLORS.append('#FF8F00') elif p < 0.10: COEF_COLORS.append('#FDD835') else: COEF_COLORS.append('#9E9E9E') fig3, ax3 = plt.subplots(figsize=(9, 5.5)) y_pos3 = np.arange(len(REG_LABELS)) ax3.barh(y_pos3, coefs3, color=COEF_COLORS, alpha=0.82, edgecolor='white', height=0.55) ax3.errorbar(coefs3, y_pos3, xerr=1.96 * ses3, fmt='none', color='#222', capsize=5, linewidth=1.5) ax3.axvline(0, color='gray', linestyle='--', linewidth=1.0) ax3.set_yticks(y_pos3) ax3.set_yticklabels(REG_LABELS, fontsize=11) ax3.set_xlabel('標準化回帰係数(±1.96SE)', fontsize=11) ax3.set_title( f'消費支出の決定要因 — OLS重回帰係数(2022年, N={len(df_reg)}都道府県)\n' f'R²={ols_model.rsquared:.3f}(adj. R²={ols_model.rsquared_adj:.3f})', fontsize=12, fontweight='bold' ) ax3.invert_yaxis() ax3.grid(axis='x', alpha=0.3) |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。fig, ax = plt.subplots(...) — 図全体(fig)と軸(ax)を作る定番。以降は ax.bar(...) 等で操作。ax.axhline / ax.axvline — 水平/垂直の点線。平均線や基準線として定番。s[:-n]「末尾n文字を除く」/s[n:]「先頭n文字を除く」。スライス [start:stop:step] はリスト・タプル・文字列共通の基本ワザです。206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 | # p値ラベル for i, (c, p) in enumerate(zip(coefs3, pvals3)): sig = '***' if p < 0.001 else '**' if p < 0.01 else '*' if p < 0.05 else 'n.s.' offset = 0.01 ha = 'left' if c >= 0 else 'right' ax3.text(c + (offset if c >= 0 else -offset), i, f' {c:.3f} {sig}', va='center', ha=ha, fontsize=8.5) from matplotlib.patches import Patch ax3.legend(handles=[ Patch(color='#C62828', alpha=0.85, label='p<0.01'), Patch(color='#FF8F00', alpha=0.85, label='p<0.05'), Patch(color='#FDD835', alpha=0.85, label='p<0.10'), Patch(color='#9E9E9E', alpha=0.85, label='n.s.'), ], fontsize=9, loc='lower right') plt.tight_layout() fig3.savefig(os.path.join(FIG_DIR, '2022_H5_14_fig3_coef.png'), bbox_inches='tight', dpi=150) plt.close(fig3) print("図3保存: 2022_H5_14_fig3_coef.png") |
■ 図3: OLS回帰係数プロット 図3保存: 2022_H5_14_fig3_coef.png
import pandas as pd など — 必要なライブラリをまとめて呼び出します。as pd は短い別名(alias)。np.cumsum(arr) は累積和、np.linspace(a, b, n) は「aからbを等間隔でn個」。NumPyの定石です。住宅地価の中央値(30,800円/m²)で分類した「都市部」(24都道府県)と「地方」(23都道府県)の 消費支出を箱ひげ図で比較し、Mann-Whitney U 検定で差の統計的有意性を確認した。 また、格差が時系列でどう推移してきたかを可視化した。
| 指標 | 都市部(n=24) | 地方(n=23) | 差・検定統計量 |
|---|---|---|---|
| 消費支出 中央値 | 30.0 万円 | 27.9 万円 | +2.1 万円 |
| Mann-Whitney U | U = 412.0, p = 0.004 ** | ||
| ジニ係数 | 0.0389 | 0.0253 | 都市部内のほうが格差大 |
2グループの平均値の差を検定するとき、まず思い浮かぶのは t 検定である。 しかし t 検定は「両グループのデータが正規分布に従う」ことを前提とする。 都道府県データは N=47 と小さく、正規性の検証が難しい。 そこで使われるノンパラメトリック検定が Mann-Whitney U 検定である。
Mann-Whitney U 検定は「データの順位」に基づき、分布の形状を仮定しない。 帰無仮説は「2グループの分布は等しい(中央値の差はない)」である。
格差の大きさを表す指標として最もよく使われるのがジニ係数(Gini coefficient)である。 0〜1の値をとり、0は「完全平等」(全員同じ所得)、1は「完全不平等」(一人が全てを独占)を表す。 OECD諸国の所得ジニ係数はおおむね0.25〜0.40の範囲にある。
本分析では消費支出の都道府県間ジニ係数を計算した結果、全国で0.037、都市部で0.039、地方で0.025 となった。 消費支出は所得よりも「均等化」されているため、全体的にジニ係数は小さい。
228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 | import numpy as np import pandas as pd import matplotlib matplotlib.use('Agg') import matplotlib.pyplot as plt import statsmodels.api as sm from scipy import stats import warnings warnings.filterwarnings('ignore') plt.rcParams['font.family'] = 'Hiragino Sans' plt.rcParams['axes.unicode_minus'] = False plt.rcParams['figure.dpi'] = 150 import os FIG_DIR = 'html/figures' DATA_B = 'data/raw/SSDSE-B-2026.csv' os.makedirs(FIG_DIR, exist_ok=True) |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。import pandas as pd など — 必要なライブラリをまとめて呼び出します。as pd は短い別名(alias)。matplotlib.use('Agg') — グラフを画面表示せずファイルに保存するためのおまじない。plt.rcParams['font.family'] — グラフの日本語表示用フォント指定(Macは Hiragino Sans、Windowsなら Yu Gothic 等)。os.makedirs('html/figures', exist_ok=True) — 図の保存先フォルダを作る(既にあってもOK)。f"...{x}..." はf-string。文字列の中に {変数} と書くだけで埋め込めて、{x:.2f} のように書式も指定できます。246 247 248 249 250 251 252 253 254 255 | print("\n" + "=" * 65) print("■ OLS重回帰分析(2022年)") print("=" * 65) REG_VARS = ['求人倍率', '住宅地価_log', '高齢化率', '大学学生数_千人', '純移動率'] REG_LABELS = ['求人倍率', '住宅地価(log)', '高齢化率(%)', '大学学生数(千人)', '純移動率(‰)'] df_reg = df_2022[['消費支出_万円'] + REG_VARS].dropna() y_ols = df_reg['消費支出_万円'].values X_ols = df_reg[REG_VARS].values |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。r, p = stats.pearsonr(...) — Pythonは複数戻り値を同時に受け取れる(タプルアンパック)。256 257 258 259 260 261 262 263 | # 標準化(係数の大きさを比較するため) X_std = (X_ols - X_ols.mean(axis=0)) / X_ols.std(axis=0) X_with_const = sm.add_constant(X_std) ols_model = sm.OLS(y_ols, X_with_const).fit() print(ols_model.summary2()) print(f"\n R² = {ols_model.rsquared:.4f}") print(f" 自由度修正済R² = {ols_model.rsquared_adj:.4f}") |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。sm.add_constant(X) — 切片項(定数1の列)を先頭に追加。statsmodelsで必須。sm.OLS(y, X).fit() — 最小二乗法でモデルを推定。model.params, model.pvalues, model.conf_int() で結果取得。x if cond else y は三項演算子。リスト内包表記と組み合わせると、forとifを1行で書けます。264 265 266 267 268 269 270 | # 個別相関分析 r_land, p_land = stats.pearsonr(df_2022['住宅地価_log'].dropna(), df_2022['消費支出_万円'].dropna()) r_job, p_job = stats.pearsonr(df_2022['求人倍率'].dropna(), df_2022['消費支出_万円'].dropna()) print(f"\n■ 相関分析(住宅地価_log vs 消費支出): r={r_land:.4f}, p={p_land:.4f}") print(f"■ 相関分析(求人倍率 vs 消費支出) : r={r_job:.4f}, p={p_job:.4f}") |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。stats.pearsonr(x, y) — Pearson相関係数 r と p値を同時に返します。df[col](1列)と df[[col1, col2]](複数列)でカッコの数が違います。リストを渡していると覚えるとミスを減らせます。271 272 273 274 275 276 277 278 | # Mann-Whitney U検定(都市部 vs 地方 消費支出) urban = df_2022[df_2022['地域区分']=='都市部']['消費支出_万円'].dropna() rural = df_2022[df_2022['地域区分']=='地方' ]['消費支出_万円'].dropna() u_stat, u_pval = stats.mannwhitneyu(urban, rural, alternative='two-sided') print(f"\n■ Mann-Whitney U検定(都市部 vs 地方 消費支出):") print(f" U={u_stat:.1f}, p={u_pval:.4f}") print(f" 都市部: n={len(urban)}, 中央値={urban.median():.2f}万円") print(f" 地方 : n={len(rural)}, 中央値={rural.median():.2f}万円") |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。s[:-n]「末尾n文字を除く」/s[n:]「先頭n文字を除く」。スライス [start:stop:step] はリスト・タプル・文字列共通の基本ワザです。279 280 281 282 283 284 285 286 287 288 289 290 291 292 | # ジニ係数の計算 def gini(arr): """ジニ係数を計算する(実データ用)""" a = np.sort(arr) n = len(a) return (2 * np.sum(np.arange(1, n + 1) * a) - (n + 1) * np.sum(a)) / (n * np.sum(a)) g_all = gini(df_2022['消費支出_万円'].dropna().values) g_urban = gini(urban.values) g_rural = gini(rural.values) print(f"\n■ ジニ係数(消費支出, 2022年):") print(f" 全国: {g_all:.4f}") print(f" 都市部のみ: {g_urban:.4f}") print(f" 地方のみ : {g_rural:.4f}") |
=================================================================
■ OLS重回帰分析(2022年)
=================================================================
Results: Ordinary least squares
=================================================================
Model: OLS Adj. R-squared: 0.199
Dependent Variable: y AIC: 189.8121
Date: 2026-05-18 11:24 BIC: 200.9130
No. Observations: 47 Log-Likelihood: -88.906
Df Model: 5 F-statistic: 3.279
Df Residuals: 41 Prob (F-statistic): 0.0139
R-squared: 0.286 Scale: 2.9504
-------------------------------------------------------------------
Coef. Std.Err. t P>|t| [0.025 0.975]
-------------------------------------------------------------------
const 28.9630 0.2505 115.5989 0.0000 28.4570 29.4690
x1 0.6212 0.3090 2.0103 0.0510 -0.0029 1.2453
x2 0.9542 0.6129 1.5569 0.1272 -0.2836 2.1920
x3 0.2530 0.4792 0.5279 0.6004 -0.7148 1.2208
x4 -0.3021 0.4356 -0.6935 0.4919 -1.1819 0.5777
x5 0.8094 0.3900 2.0755 0.0443 0.0218 1.5970
-----------------------------------------------------------------
Omnibus: 3.431 Durbin-Watson: 1.908
Prob(Omnibus): 0.180 Jarque-Bera (JB): 2.433
Skew: -0.524 Prob(JB): 0.296
Kurtosis: 3.379 Condition No.: 6
=================================================================
Notes:
[1] Standard Errors assume that the covariance matrix of the
errors is correctly specified.
R² = 0.2857
自由度修正済R² = 0.1986
■ 相関分析(住宅地価_log vs 消費支出): r=0.4107, p=0.0041
■ 相関分析(求人倍率 vs 消費支出) : r=-0.0198, p=0.8948
■ Mann-Whitney U検定(都市部 vs 地方 消費支出):
U=412.0, p=0.0039
都市部: n=24, 中央値=30.02万円
地方 : n=23, 中央値=27.91万円
■ ジニ係数(消費支出, 2022年):
全国: 0.0371
都市部のみ: 0.0389
地方のみ : 0.0253gini(arr) — Gini係数(0=完全平等、1=完全不平等)を計算。ソート → 累積和 → 公式という単純実装。np.cumsum(arr) は累積和、np.linspace(a, b, n) は「aからbを等間隔でn個」。NumPyの定石です。293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 | print("\n■ 図4: 都市部 vs 地方 消費支出比較 箱ひげ図") fig4, axes4 = plt.subplots(1, 2, figsize=(12, 6)) # 左パネル: 箱ひげ図 ax4L = axes4[0] data_urban = urban.values data_rural = rural.values bp = ax4L.boxplot([data_urban, data_rural], labels=['都市部', '地方'], patch_artist=True, widths=0.5, medianprops=dict(color='white', linewidth=2.5), boxprops=dict(linewidth=1.5), whiskerprops=dict(linewidth=1.5), capprops=dict(linewidth=1.5), flierprops=dict(marker='o', markersize=5, alpha=0.6)) bp['boxes'][0].set_facecolor('#C62828') bp['boxes'][0].set_alpha(0.78) bp['boxes'][1].set_facecolor('#1565C0') bp['boxes'][1].set_alpha(0.78) |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。fig, ax = plt.subplots(...) — 図全体(fig)と軸(ax)を作る定番。以降は ax.bar(...) 等で操作。np.cumsum(arr) は累積和、np.linspace(a, b, n) は「aからbを等間隔でn個」。NumPyの定石です。315 316 317 318 319 320 | # 個別データ点をジッタで表示 rng = np.random.default_rng(seed=42) # 表示用のジッタのみ(データ生成ではない) for i, (data, color) in enumerate(zip([data_urban, data_rural], ['#C62828', '#1565C0'])): jitter = rng.uniform(-0.12, 0.12, size=len(data)) ax4L.scatter(np.ones(len(data)) * (i + 1) + jitter, data, color=color, alpha=0.5, s=30, zorder=3) |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。{値:.2f}(小数2桁)、{値:,}(3桁区切り)、{値:>10}(右寄せ10桁)など、覚えると出力が一気に整います。321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 | # 平均値をダイヤモンドでプロット for i, data in enumerate([data_urban, data_rural]): ax4L.scatter(i + 1, data.mean(), marker='D', color='white', s=60, zorder=5, edgecolors='#333', linewidths=1.5) # Mann-Whitney U検定の結果を注記 sig_txt = '***' if u_pval < 0.001 else '**' if u_pval < 0.01 else '*' if u_pval < 0.05 else 'n.s.' ax4L.text(1.5, max(data_urban.max(), data_rural.max()) * 1.01, f'Mann-Whitney U検定\nU={u_stat:.0f}, p={u_pval:.4f} {sig_txt}', ha='center', va='bottom', fontsize=9, color='#333', bbox=dict(boxstyle='round,pad=0.3', facecolor='#FFF9C4', edgecolor='#F9A825')) ax4L.set_ylabel('消費支出(万円/月)', fontsize=11) ax4L.set_title('都市部 vs 地方\n消費支出の分布(2022年)', fontsize=12, fontweight='bold') ax4L.grid(axis='y', alpha=0.3) |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。plt.subplots(figsize=(W, H)) で図サイズ指定、fig.savefig(..., bbox_inches='tight') で余白を自動で詰めて保存。336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 | # 右パネル: 年度別 格差推移(都市部平均 - 地方平均) ax4R = axes4[1] gap_ts = ( df_all.groupby(['年度', '地域区分'])['消費支出_万円'] .mean() .unstack('地域区分') ) if '都市部' in gap_ts.columns and '地方' in gap_ts.columns: gap_ts['格差'] = gap_ts['都市部'] - gap_ts['地方'] years = gap_ts.index.tolist() ax4R.fill_between(years, gap_ts['格差'], alpha=0.3, color='#C62828') ax4R.plot(years, gap_ts['格差'], marker='o', linewidth=2.2, markersize=5.5, color='#C62828', label='都市部 - 地方(格差)') ax4R.axhline(0, color='gray', linestyle='--', linewidth=1.0) ax4R.set_xlabel('年度', fontsize=11) ax4R.set_ylabel('消費支出の格差(万円/月)', fontsize=11) ax4R.set_title('都市農村間 消費支出格差の推移\n(都市部平均 − 地方平均)', fontsize=12, fontweight='bold') ax4R.set_xticks(years) ax4R.xaxis.set_tick_params(rotation=45) ax4R.legend(fontsize=9.5) ax4R.grid(True, alpha=0.3) plt.tight_layout() fig4.savefig(os.path.join(FIG_DIR, '2022_H5_14_fig4_boxplot.png'), bbox_inches='tight', dpi=150) plt.close(fig4) print("図4保存: 2022_H5_14_fig4_boxplot.png") |
■ 図4: 都市部 vs 地方 消費支出比較 箱ひげ図 図4保存: 2022_H5_14_fig4_boxplot.png
df.groupby('列').apply(関数) — グループごとに関数を適用。時系列や地域別の集計でよく使います。ax.axhline / ax.axvline — 水平/垂直の点線。平均線や基準線として定番。ax.fill_between(...) — 2つの曲線で囲まれた領域を塗りつぶし。Lorenz曲線の格差面積などを可視化。.dropna() は欠損行を除去、.copy() は独立したコピーを作る。pandasで警告を防ぐ定石。366 367 368 369 370 371 372 373 374 375 376 377 378 | print("\n" + "=" * 65) print("■ 全図生成完了(4枚)") print(f" fig1_timeseries.png : 消費支出の時系列推移(地方別平均)") print(f" fig2_scatter.png : 住宅地価 vs 消費支出 散布図(2022年)") print(f" fig3_coef.png : OLS回帰係数プロット") print(f" fig4_boxplot.png : 都市部 vs 地方 消費支出比較") print("=" * 65) print(f"\n OLS結果サマリ(2022年, N={len(df_reg)}):") print(f" R²={ols_model.rsquared:.3f}, adj.R²={ols_model.rsquared_adj:.3f}") for label, coef, pval in zip(REG_LABELS, coefs3, pvals3): sig = '***' if pval < 0.001 else '**' if pval < 0.01 else '*' if pval < 0.05 else 'n.s.' print(f" {label}: β={coef:.3f} (p={pval:.4f}) {sig}") print(f"\n ジニ係数(消費支出, 2022年): 全国={g_all:.4f}, 都市部={g_urban:.4f}, 地方={g_rural:.4f}") |
=================================================================
■ 全図生成完了(4枚)
fig1_timeseries.png : 消費支出の時系列推移(地方別平均)
fig2_scatter.png : 住宅地価 vs 消費支出 散布図(2022年)
fig3_coef.png : OLS回帰係数プロット
fig4_boxplot.png : 都市部 vs 地方 消費支出比較
=================================================================
OLS結果サマリ(2022年, N=47):
R²=0.286, adj.R²=0.199
求人倍率: β=0.621 (p=0.0510) n.s.
住宅地価(log): β=0.954 (p=0.1272) n.s.
高齢化率(%): β=0.253 (p=0.6004) n.s.
大学学生数(千人): β=-0.302 (p=0.4919) n.s.
純移動率(‰): β=0.809 (p=0.0443) *
ジニ係数(消費支出, 2022年): 全国=0.0371, 都市部=0.0389, 地方=0.0253{値:.2f}(小数2桁)、{値:,}(3桁区切り)、{値:>10}(右寄せ10桁)など、覚えると出力が一気に整います。SSDSE-B(都道府県データ, 2012〜2023年)を用いた分析から、以下が明らかになった:
| データ・コード | 内容 | 出典 |
|---|---|---|
| SSDSE-B-2026.csv | 47都道府県 × 2012〜2023年 パネルデータ | 統計数理研究所・統計センター(実データ) |
| L3221:消費支出(二人以上の世帯) | 月間消費支出(円/月) | 家計調査(総務省) |
| F3103/F3102:求人数/求職者数(一般) | 月間有効求人数・求職者数 | 職業安定業務統計(厚生労働省) |
| C5401:標準価格(住宅地) | 平均住宅地価(円/m²) | 地価公示(国土交通省) |
| A5101/A5102:転入・転出者数 | 日本人移動者数(人) | 住民基本台帳人口移動報告(総務省) |
本教育用コードは実データ(SSDSE-B-2026.csv)のみを使用しています。 合成データ(np.random.seed等による乱数生成)は一切使用していません。 図4の個別データ点のジッタ表示のみ np.random.default_rng(seed=42) を使用(データ生成ではなく表示のみ)。
統計分析の解釈で初心者がやりがちな勘違いをまとめます。特に「相関と因果の混同」「p値の過信」は研究現場でもよく起きる落とし穴です。本文を読む前にも、読んだ後にも、目を通してみてください。
統計の基本用語を初心者向けに解説します。本文中で見慣れない言葉が出てきたら、ここに戻って確認してください。
統計手法について「何のためか」「結果をどう読むか」を初心者向けに解説します。
この研究をさらに発展させるための3つの方向性を示します。「今回わかったこと(X)」から「次に検証すべき仮説(Y)」を立て、「具体的に何をするか(Z)」まで考えてみましょう。
学んだだけでは身につきません。実際に手を動かすのが最強の学習方法です。本論文のスクリプトをベースに、以下のチャレンジに挑戦してみてください。難易度別に5つ用意しました。
本論文で学んだ手法は、研究の世界だけでなく、行政・企業・NPO の現場でも様々に活用されています。具体的なシーンを紹介します。
この論文を読んで初心者が抱きやすい疑問に、教育的観点から答えます。