このページの分析を自分で再現するには、以下の手順でデータを準備してください。コードの編集は不要です。
data/raw/ フォルダに入れます。html/figures/ に自動保存されます。
睡眠不足は健康・生産性・精神衛生に深刻な影響を及ぼす。日本は国際的にみても睡眠時間が短い国として知られているが、都道府県間にも有意な差がある(平均 476.6 分/日、最長:青森県 488分 ⇔ 最短:東京都・神奈川県 468分)。 本研究は「どのような社会構造変数が睡眠時間の地域差を説明するか」を探るため、ランダムフォレストの変数重要度で主要因子を特定し、それをもとに 47都道府県を Ward法で階層クラスタリングした。
まず「変数重要度に着目したクラスタリングによる社会構造と睡眠時間の関係性の解析」を統計的にとらえることが有効だと考えられる。 その理由は感覚や経験則だけでは、複雑な社会要因の中で「何が本当に効いているか」を見極めにくいからである。 本研究では公開データと統計手法を組み合わせ、この問いに定量的な答えを出すことを目指す。
SSDSE-B SSDSE-D RandomForest 変数重要度 Ward法クラスタリング 47都道府県
| データセット | 内容 | 使用変数 |
|---|---|---|
| SSDSE-D-2023 | 社会生活基本調査 2021年 47都道府県(総数) |
睡眠時間(分/日) 仕事時間(分/日) |
| SSDSE-B-2026 | 都道府県別統計 2022年度 47都道府県 |
高齢化率、消費支出、住宅地価格 有効求人倍率、TFR、大学進学率 宿泊者数 per capita、年平均気温 |
| 変数名 | 計算式 | 出典 | 想定される関連 |
|---|---|---|---|
| 高齢化率(%) | 65歳以上人口 / 総人口 × 100 | SSDSE-B | 高齢化 → 睡眠長? |
| 消費支出(円/月) | 二人以上世帯の月間消費支出 | SSDSE-B | 豊かさの代理指標 |
| 住宅地価格(円/m²) | 標準価格(平均) | SSDSE-B | 都市集中度の代理 |
| 有効求人倍率(倍) | 月間有効求人数 / 月間有効求職者数 | SSDSE-B | 労働市場の逼迫度 |
| TFR(合計特殊出生率) | 合計特殊出生率 | SSDSE-B | ライフスタイルの違い |
| 大学進学率(%) | 高校卒進学者数 / 高校卒業者数 × 100 | SSDSE-B | 教育水準・都市度 |
| 宿泊者数 per capita | 延べ宿泊者数 / 総人口 | SSDSE-B | 観光地特性 |
| 年平均気温(℃) | 年平均気温 | SSDSE-B | 気候・地域差の代理 |
| 仕事時間(分/日) | 1日の仕事時間(平均) | SSDSE-D | 仕事長 → 睡眠短 |
1 2 3 4 5 6 | print("\n" + "=" * 65) print("■ Step2. 社会指標の特徴量エンジニアリング") print("=" * 65) # 高齢化率 = 65歳以上人口 / 総人口 × 100 df['高齢化率'] = df['65歳以上人口'] / df['総人口'] * 100 |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。df['A'] / df['B'] — pandasの列同士の四則演算は要素ごと(element-wise)。forループ不要なのが強み。7 8 9 10 11 12 13 14 | # 消費支出(二人以上の世帯, 円/月) df['消費支出'] = df['消費支出(二人以上の世帯)'] # 住宅地価格(円/m²) df['住宅地価格'] = df['標準価格(平均価格)(住宅地)'] # 有効求人倍率 = 月間有効求人数 / 月間有効求職者数 df['有効求人倍率'] = df['月間有効求人数(一般)'] / df['月間有効求職者数(一般)'] |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。.map() は「1対1の置き換え」、.apply() は「関数を当てる」。辞書なら .map()、ロジックなら .apply()。15 16 17 18 19 20 | # 合計特殊出生率(TFR) df['TFR'] = df['合計特殊出生率'] # 大学進学率 = 高校卒進学者数 / 高校卒業者数 × 100 df['大学進学率'] = (df['高等学校卒業者のうち進学者数'] / df['高等学校卒業者数']) * 100 |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。[式 for x in リスト] はリスト内包表記。forループでappendする代わりに1行でリストを作れます。21 22 23 24 25 26 27 28 | # 宿泊者数 per capita = 延べ宿泊者数 / 総人口 df['宿泊者数per_capita'] = df['延べ宿泊者数'] / df['総人口'] # 年平均気温 df['年平均気温'] = df['年平均気温'] # 仕事時間(SSDSE-Dから) df['仕事_min'] = df['仕事_min'] |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。r, p = stats.pearsonr(...) — Pythonは複数戻り値を同時に受け取れる(タプルアンパック)。29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 | # 特徴量列とラベル定義 FEATURE_COLS = [ '高齢化率', '消費支出', '住宅地価格', '有効求人倍率', 'TFR', '大学進学率', '宿泊者数per_capita', '年平均気温', '仕事_min', ] FEATURE_LABELS = { '高齢化率': '高齢化率(%)', '消費支出': '消費支出(円/月)', '住宅地価格': '住宅地価格(円/m²)', '有効求人倍率': '有効求人倍率(倍)', 'TFR': '合計特殊出生率', '大学進学率': '大学進学率(%)', '宿泊者数per_capita': '宿泊者数per capita', '年平均気温': '年平均気温(℃)', '仕事_min': '仕事時間(分/日)', } |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。x if cond else y は三項演算子。リスト内包表記と組み合わせると、forとifを1行で書けます。53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 | # 欠損値除去 df_model = df[['地域コード', '都道府県', '睡眠_min'] + FEATURE_COLS].dropna().reset_index(drop=True) print(f" 有効観測数: {len(df_model)} 都道府県") X = df_model[FEATURE_COLS].values y = df_model['睡眠_min'].values print(f"\n 目的変数(睡眠時間):") print(f" 平均: {y.mean():.1f} 分/日 ({y.mean()/60:.2f} 時間/日)") print(f" SD: {y.std():.1f} 分 範囲: {y.min():.0f}〜{y.max():.0f} 分") print(f"\n 睡眠時間 上位5都道府県:") top5 = df_model.nlargest(5, '睡眠_min')[['都道府県', '睡眠_min']] for _, row in top5.iterrows(): print(f" {row['都道府県']}: {row['睡眠_min']:.0f} 分 ({row['睡眠_min']/60:.2f} 時間)") print(f" 睡眠時間 下位5都道府県:") bot5 = df_model.nsmallest(5, '睡眠_min')[['都道府県', '睡眠_min']] for _, row in bot5.iterrows(): print(f" {row['都道府県']}: {row['睡眠_min']:.0f} 分 ({row['睡眠_min']/60:.2f} 時間)") |
================================================================= ■ Step2. 社会指標の特徴量エンジニアリング ================================================================= # 実行時エラーで途中まで
for _, row in df.iterrows() — DataFrameを1行ずつ取り出すループ。1点ずつ描画したいときに使用。df[col](1列)と df[[col1, col2]](複数列)でカッコの数が違います。リストを渡していると覚えるとミスを減らせます。72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 | print("\n" + "=" * 65) print("■ 全図の生成完了(4枚)") print("=" * 65) print(f"\n データ: SSDSE-B-2026(2022年度)+ SSDSE-D-2023(実公的統計データ)") print(f" 総観測数: {len(df_model)} 都道府県") print(f" 目的変数: 睡眠時間 平均 {y.mean():.1f} 分/日" f" ({y.mean()/60:.2f} 時間/日) SD: {y.std():.1f} 分") print(f"\n 【変数重要度 Top5(RF, Impurity-based)】") for rank, idx in enumerate(order[:5]): fn = FEATURE_COLS[idx] print(f" {rank+1}. {FEATURE_LABELS[fn]:<24} {importances[idx]:.4f}") print(f"\n 【クラスタ別 睡眠時間平均】") for c in sorted(df_model['cluster'].unique()): m = df_model[df_model['cluster'] == c]['睡眠_min'].mean() n = (df_model['cluster'] == c).sum() print(f" クラスタ {c} (n={n:2d}): {m:.1f} 分 = {m/60:.2f} 時間/日") print(f"\n 【主要知見】") print(f" - 仕事時間(SSDSE-D)が睡眠時間予測の最重要因子の一つ") print(f" - 高齢化率・消費支出などの社会構造変数も睡眠時間と関連") print(f" - Ward法クラスタリングで社会構造が類似する都道府県群が睡眠パターンを共有") print(f" - 大都市圏(仕事時間長)と地方(睡眠時間長)の対照的なパターン") |
================================================================= ■ 全図の生成完了(4枚) ================================================================= データ: SSDSE-B-2026(2022年度)+ SSDSE-D-2023(実公的統計データ) # 実行時エラーで途中まで
{値:.2f}(小数2桁)、{値:,}(3桁区切り)、{値:>10}(右寄せ10桁)など、覚えると出力が一気に整います。睡眠時間を目的変数として 9 つの社会指標で RandomForestRegressor(n_estimators=100, random_state=42)を学習し、不純度ベースの特徴量重要度(Gini Importance)を算出した。学習データへの当てはまりは R²=0.907 であった。
Random Forest の変数重要度は「各分岐でその変数を使うことで不純度(Gini 係数)がどれだけ減少したかの平均」。全ての木・全ての分岐で集計するため、外れ値に頑健で安定している。ただし、スケールの大きい変数や基数の多いカテゴリ変数に過大評価されやすい(バイアス)点に注意。
97 98 99 100 101 102 103 104 105 106 107 108 109 | print("\n図1: RF変数重要度棒グラフを作成中...") fig1, ax1 = plt.subplots(figsize=(10, 6)) fig1.suptitle('ランダムフォレストによる変数重要度\n(目的変数:都道府県別 1日平均睡眠時間)', fontsize=13, fontweight='bold') imp_sorted = importances[order] labels_sorted = [FEATURE_LABELS[FEATURE_COLS[i]] for i in order] colors_bar = ['#E53935' if i < top_n else '#78909C' for i in range(len(order))] bars = ax1.barh(np.arange(len(order)), imp_sorted, color=colors_bar, alpha=0.85, edgecolor='white', linewidth=0.7) |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。fig, ax = plt.subplots(...) — 図全体(fig)と軸(ax)を作る定番。以降は ax.bar(...) 等で操作。x if cond else y は三項演算子。リスト内包表記と組み合わせると、forとifを1行で書けます。110 111 112 113 114 115 116 117 118 119 120 | # 重要度の数値ラベル for bar, val in zip(bars, imp_sorted): ax1.text(val + 0.002, bar.get_y() + bar.get_height() / 2, f'{val:.4f}', va='center', fontsize=9, color='#333') ax1.set_yticks(np.arange(len(order))) ax1.set_yticklabels(labels_sorted, fontsize=10) ax1.set_xlabel('変数重要度(不純度減少の平均, Gini Importance)', fontsize=11) ax1.set_title(f'上位{top_n}変数(赤)をクラスタリングに使用', fontsize=10, color='#555') ax1.invert_yaxis() ax1.grid(axis='x', alpha=0.3) |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。df[col](1列)と df[[col1, col2]](複数列)でカッコの数が違います。リストを渡していると覚えるとミスを減らせます。121 122 123 124 125 126 127 128 129 130 131 132 133 134 | # 凡例 patch_top = mpatches.Patch(color='#E53935', alpha=0.85, label=f'上位{top_n}変数(クラスタリング使用)') patch_rest = mpatches.Patch(color='#78909C', alpha=0.85, label='その他の変数') ax1.legend(handles=[patch_top, patch_rest], fontsize=9, loc='lower right') ax1.text(0.98, 0.02, f'RF: n_estimators=100\nTrain R²={train_r2:.3f}', transform=ax1.transAxes, fontsize=8.5, ha='right', va='bottom', bbox=dict(boxstyle='round', facecolor='white', alpha=0.85, edgecolor='#aaa')) plt.tight_layout() fig1.savefig(os.path.join(FIG_DIR, '2023_U5_2_fig1_importance.png'), bbox_inches='tight', dpi=150) plt.close(fig1) print(" -> 2023_U5_2_fig1_importance.png 保存完了") |
図1: RF変数重要度棒グラフを作成中... # 実行時エラーで途中まで
s[:-n]「末尾n文字を除く」/s[n:]「先頭n文字を除く」。スライス [start:stop:step] はリスト・タプル・文字列共通の基本ワザです。RF変数重要度上位4変数(大学進学率・住宅地価格・年平均気温・消費支出)を標準化(z スコア化)し、Ward法(最小分散法)で 47都道府県を階層クラスタリングした。切断閾値は クラスタ数 k=4 に対応する高さに設定した。
Ward法は「クラスタを結合した際の群内分散の増加量を最小化する」結合基準。k-means より安定的で、デンドログラムでクラスタ構造を視覚的に確認できるメリットがある。N=47 程度の小標本で全体構造を把握するのに適している。
136 137 138 139 140 141 142 143 | print("図2: Ward法デンドログラムを作成中...") fig2, ax2 = plt.subplots(figsize=(16, 8)) fig2.suptitle(f'Ward法 階層クラスタリング デンドログラム\n' f'(使用特徴量: {", ".join(top_feat_labels[:2])} 他計{top_n}変数)', fontsize=13, fontweight='bold') pref_names = df_model['都道府県'].tolist() |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。fig, ax = plt.subplots(...) — 図全体(fig)と軸(ax)を作る定番。以降は ax.bar(...) 等で操作。df[col](1列)と df[[col1, col2]](複数列)でカッコの数が違います。リストを渡していると覚えるとミスを減らせます。144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 | # クラスタ境界色を計算するためのリーフ順序 dn = dendrogram( Z, labels=pref_names, orientation='bottom', color_threshold=Z[-N_CLUSTERS + 1, 2], above_threshold_color='#555', ax=ax2, leaf_font_size=8, leaf_rotation=90, ) ax2.set_xlabel('都道府県', fontsize=11) ax2.set_ylabel('Ward距離(不純度増加量)', fontsize=11) ax2.axhline(y=Z[-N_CLUSTERS + 1, 2], color='#E53935', linestyle='--', linewidth=2, label=f'カット線(クラスタ数={N_CLUSTERS})') ax2.legend(fontsize=10, loc='upper left') ax2.grid(axis='y', alpha=0.2) plt.tight_layout() fig2.savefig(os.path.join(FIG_DIR, '2023_U5_2_fig2_dendrogram.png'), bbox_inches='tight', dpi=150) plt.close(fig2) print(" -> 2023_U5_2_fig2_dendrogram.png 保存完了") |
図2: Ward法デンドログラムを作成中... # 実行時エラーで途中まで
ax.axhline / ax.axvline — 水平/垂直の点線。平均線や基準線として定番。s[:-n]「末尾n文字を除く」/s[n:]「先頭n文字を除く」。スライス [start:stop:step] はリスト・タプル・文字列共通の基本ワザです。RF変数重要度1位(大学進学率)と2位(住宅地価格)を横軸に、睡眠時間を縦軸にした散布図。点の色はWard法で決定したクラスタ番号に対応する。
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 201 202 203 204 205 206 207 208 209 210 211 212 213 214 | print("図3: 散布図(最重要変数 vs 睡眠時間)を作成中...") top1_col = top_feat_cols[0] top1_label = top_feat_labels[0] top2_col = top_feat_cols[1] if len(top_feat_cols) > 1 else top_feat_cols[0] top2_label = top_feat_labels[1] if len(top_feat_labels) > 1 else top_feat_labels[0] fig3, axes3 = plt.subplots(1, 2, figsize=(14, 6)) fig3.suptitle('社会指標(上位重要変数)と睡眠時間の関係\n(点の色 = Ward法クラスタ)', fontsize=13, fontweight='bold') for ax, x_col, x_label in zip(axes3, [top1_col, top2_col], [top1_label, top2_label]): for c in sorted(df_model['cluster'].unique()): mask = df_model['cluster'] == c ax.scatter(df_model.loc[mask, x_col], df_model.loc[mask, '睡眠_min'], color=CLUSTER_COLORS[c], alpha=0.85, s=70, edgecolors='white', linewidth=0.7, label=f'クラスタ {c}', zorder=3) # 全体回帰直線 coef = np.polyfit(df_model[x_col], df_model['睡眠_min'], 1) x_range = np.linspace(df_model[x_col].min(), df_model[x_col].max(), 200) ax.plot(x_range, np.polyval(coef, x_range), 'k--', linewidth=1.5, alpha=0.7, label='全体回帰直線') # 都道府県ラベル(上位・下位) q_high = df_model['睡眠_min'].quantile(0.85) q_low = df_model['睡眠_min'].quantile(0.15) for _, row in df_model.iterrows(): if row['睡眠_min'] >= q_high or row['睡眠_min'] <= q_low: ax.annotate(row['都道府県'][:2], (row[x_col], row['睡眠_min']), fontsize=7.5, ha='left', va='bottom', color='#333') ax.set_xlabel(x_label, fontsize=11) ax.set_ylabel('1日平均睡眠時間(分)', fontsize=11) ax.set_title(f'{x_label}\nvs 睡眠時間', fontsize=11, fontweight='bold') ax.legend(fontsize=9) ax.grid(True, alpha=0.25) plt.tight_layout() fig3.savefig(os.path.join(FIG_DIR, '2023_U5_2_fig3_scatter.png'), bbox_inches='tight', dpi=150) plt.close(fig3) print(" -> 2023_U5_2_fig3_scatter.png 保存完了") |
図3: 散布図(最重要変数 vs 睡眠時間)を作成中... # 実行時エラーで途中まで
fig, ax = plt.subplots(...) — 図全体(fig)と軸(ax)を作る定番。以降は ax.bar(...) 等で操作。for _, row in df.iterrows() — DataFrameを1行ずつ取り出すループ。1点ずつ描画したいときに使用。s[:-n]「末尾n文字を除く」/s[n:]「先頭n文字を除く」。スライス [start:stop:step] はリスト・タプル・文字列共通の基本ワザです。4クラスタの睡眠時間分布をボックスプロット(左)で比較し、各クラスタの社会指標プロファイルを z スコア棒グラフ(右)で確認する。
| クラスタ | 都道府県数 | 睡眠時間 平均(分) | 睡眠時間(時間) | 特徴 |
|---|---|---|---|---|
| クラスタ 1 | 7 | 471.0 | 7.85 時間 | 大都市圏(東京・大阪圏) |
| クラスタ 2 | 17 | 477.9 | 7.96 時間 | 南西・温暖・中間層 |
| クラスタ 3 | 5 | 483.4 | 8.06 時間 | 東北(最長睡眠) |
| クラスタ 4 | 18 | 475.7 | 7.93 時間 | 関東〜中国・中間層 |
Ward法の最適クラスタ数は「デンドログラムで長いジャンプ(高さが急増する結合)の直前でカットする」ことで決定する。本研究では k=4 が地域構造(大都市圏・東北・南西・中間層)と対応しており解釈性が高い。
216 217 218 219 220 221 222 223 224 225 226 227 228 229 | import os import numpy as np import pandas as pd import matplotlib matplotlib.use('Agg') import matplotlib.pyplot as plt import matplotlib.patches as mpatches from matplotlib.gridspec import GridSpec from sklearn.ensemble import RandomForestRegressor from sklearn.preprocessing import StandardScaler from sklearn.metrics import r2_score from scipy.cluster.hierarchy import dendrogram, linkage, fcluster import warnings warnings.filterwarnings('ignore') |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。import pandas as pd など — 必要なライブラリをまとめて呼び出します。as pd は短い別名(alias)。matplotlib.use('Agg') — グラフを画面表示せずファイルに保存するためのおまじない。StandardScaler().fit_transform(X) — 各列を「平均0・分散1」に標準化。単位が違う変数のβを比較可能に。f"...{x}..." はf-string。文字列の中に {変数} と書くだけで埋め込めて、{x:.2f} のように書式も指定できます。230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 | # ── パス設定 ───────────────────────────────────────────────────────────────── _dir = os.path.dirname(os.path.abspath(__file__)) if '__file__' in dir() else os.getcwd() FIG_DIR = os.path.join(_dir, '..', 'html', 'figures') DATA_B = os.path.join(_dir, '..', 'data', 'raw', 'SSDSE-B-2026.csv') DATA_D = os.path.join(_dir, '..', 'data', 'raw', 'SSDSE-D-2023.csv') 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("=" * 65) print("■ 実データ読み込み(SSDSE-B-2026 + SSDSE-D-2023)") print("=" * 65) |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。os.makedirs('html/figures', exist_ok=True) — 図の保存先フォルダを作る(既にあってもOK)。df['A'] / df['B'] — pandasの列同士の四則演算は要素ごと(element-wise)。forループ不要なのが強み。248 249 250 251 252 253 254 255 256 257 258 259 260 261 | # ── SSDSE-D-2023(社会生活基本調査、47都道府県、総数) ──────────────────── df_d = pd.read_csv(DATA_D, encoding='cp932', header=1) df_d_total = df_d[ (df_d['男女の別'] == '0_総数') & (df_d['地域コード'] != 'R00000') ].copy() df_d_total['睡眠_min'] = pd.to_numeric(df_d_total['睡眠'], errors='coerce') df_d_total['仕事_min'] = pd.to_numeric(df_d_total['仕事'], errors='coerce') print(f" SSDSE-D(総数): {len(df_d_total)} 都道府県") print(f" 睡眠時間 平均: {df_d_total['睡眠_min'].mean():.1f} 分/日" f" ({df_d_total['睡眠_min'].mean()/60:.2f} 時間/日)") print(f" 睡眠時間 範囲: {df_d_total['睡眠_min'].min()}〜{df_d_total['睡眠_min'].max()} 分") print(f" 仕事時間 平均: {df_d_total['仕事_min'].mean():.1f} 分/日") |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。pd.read_csv(...) でCSVを読み込みます。encoding='cp932' は日本語Windows由来の文字コード、header=1 は「2行目を列名として使う」。.map() は「1対1の置き換え」、.apply() は「関数を当てる」。辞書なら .map()、ロジックなら .apply()。262 263 264 265 266 267 268 269 | # ── SSDSE-B-2026(2022年度、47都道府県) ────────────────────────────────── df_b = pd.read_csv(DATA_B, encoding='cp932', header=1) df_b = df_b[ (df_b['地域コード'].str.match(r'^R\d{5}$', na=False)) & (df_b['年度'] == 2022) ].copy() print(f"\n SSDSE-B(2022年度): {len(df_b)} 都道府県") |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。pd.read_csv(...) でCSVを読み込みます。encoding='cp932' は日本語Windows由来の文字コード、header=1 は「2行目を列名として使う」。df['地域コード'].str.match(r'^R\d{5}', ...) — 正規表現で「R+数字5桁」の行(47都道府県)だけTrueにし、真偽値で行をフィルタ。[式 for x in リスト] はリスト内包表記。forループでappendする代わりに1行でリストを作れます。270 271 272 273 274 275 276 277 278 279 280 281 | # 数値型変換 num_cols = [ '総人口', '65歳以上人口', '消費支出(二人以上の世帯)', '標準価格(平均価格)(住宅地)', '月間有効求人数(一般)', '月間有効求職者数(一般)', '合計特殊出生率', '高等学校卒業者のうち進学者数', '高等学校卒業者数', '延べ宿泊者数', '年平均気温', ] for c in num_cols: df_b[c] = pd.to_numeric(df_b[c], errors='coerce') |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。r, p = stats.pearsonr(...) — Pythonは複数戻り値を同時に受け取れる(タプルアンパック)。282 283 284 285 286 287 288 289 290 | # ── 地域コードでマージ ───────────────────────────────────────────────────── df = df_d_total[['地域コード', '都道府県', '睡眠_min', '仕事_min']].merge( df_b[['地域コード', '都道府県'] + num_cols], on='地域コード', how='inner', suffixes=('_d', '_b') ) df['都道府県'] = df['都道府県_d'] df = df.drop(columns=['都道府県_d', '都道府県_b']) print(f"\n マージ後: {len(df)} 都道府県") |
================================================================= ■ 実データ読み込み(SSDSE-B-2026 + SSDSE-D-2023) ================================================================= # 実行時エラーで途中まで
x if cond else y は三項演算子。リスト内包表記と組み合わせると、forとifを1行で書けます。291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 | print("\n" + "=" * 65) print("■ Step3. ランダムフォレスト回帰(変数重要度の算出)") print("=" * 65) # random_state=42: アルゴリズムの再現性確保(合成データではなく算法固定) rf = RandomForestRegressor(n_estimators=100, random_state=42) rf.fit(X, y) importances = rf.feature_importances_ train_r2 = r2_score(y, rf.predict(X)) print(f"\n 学習用R²(全データ): {train_r2:.4f}") print(f"\n 変数重要度(Impurity-based Feature Importance):") order = np.argsort(importances)[::-1] for rank, idx in enumerate(order): fn = FEATURE_COLS[idx] print(f" {rank+1:2d}. {FEATURE_LABELS[fn]:<24} {importances[idx]:.4f}") |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。.map() は「1対1の置き換え」、.apply() は「関数を当てる」。辞書なら .map()、ロジックなら .apply()。309 310 311 312 313 314 315 316 | # 上位特徴量(クラスタリングに使用) top_n = 4 # 上位4特徴量でクラスタリング top_idx = order[:top_n] top_feat_cols = [FEATURE_COLS[i] for i in top_idx] top_feat_labels = [FEATURE_LABELS[FEATURE_COLS[i]] for i in top_idx] print(f"\n クラスタリングに使用する上位{top_n}特徴量:") for i, (col, lbl) in enumerate(zip(top_feat_cols, top_feat_labels)): print(f" {i+1}. {lbl}") |
================================================================= ■ Step3. ランダムフォレスト回帰(変数重要度の算出) ================================================================= # 実行時エラーで途中まで
[式 for x in リスト] はリスト内包表記。forループでappendする代わりに1行でリストを作れます。317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 | print("\n" + "=" * 65) print("■ Step4. Ward法 階層クラスタリング(47都道府県)") print("=" * 65) # 上位重要特徴量で標準化 X_top = df_model[top_feat_cols].values scaler = StandardScaler() X_top_sc = scaler.fit_transform(X_top) # Ward法(最小分散法)でリンケージ行列を計算 Z = linkage(X_top_sc, method='ward', metric='euclidean') # クラスタ数 = 4 に分割 N_CLUSTERS = 4 cluster_labels = fcluster(Z, N_CLUSTERS, criterion='maxclust') df_model['cluster'] = cluster_labels print(f" クラスタ数: {N_CLUSTERS}") for c in sorted(df_model['cluster'].unique()): prefs = df_model[df_model['cluster'] == c]['都道府県'].tolist() mean_sleep = df_model[df_model['cluster'] == c]['睡眠_min'].mean() print(f"\n クラスタ {c}(睡眠時間平均: {mean_sleep:.1f} 分):") print(f" {', '.join(prefs)}") |
================================================================= ■ Step4. Ward法 階層クラスタリング(47都道府県) ================================================================= # 実行時エラーで途中まで
StandardScaler().fit_transform(X) — 各列を「平均0・分散1」に標準化。単位が違う変数のβを比較可能に。[式 for x in リスト] はリスト内包表記。forループでappendする代わりに1行でリストを作れます。340 | CLUSTER_COLORS = {1: '#1565C0', 2: '#E65100', 3: '#2E7D32', 4: '#6A1B9A'} |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。r, p = stats.pearsonr(...) — Pythonは複数戻り値を同時に受け取れる(タプルアンパック)。341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 | print("図4: クラスタ別睡眠時間ボックスプロットを作成中...") fig4, axes4 = plt.subplots(1, 2, figsize=(14, 6)) fig4.suptitle('クラスタ別 1日平均睡眠時間の分布\n(Ward法 階層クラスタリング, 47都道府県)', fontsize=13, fontweight='bold') ax4a = axes4[0] cluster_data = [df_model[df_model['cluster'] == c]['睡眠_min'].values for c in sorted(df_model['cluster'].unique())] cluster_means = [d.mean() for d in cluster_data] cluster_ns = [len(d) for d in cluster_data] bp = ax4a.boxplot(cluster_data, patch_artist=True, notch=False, medianprops=dict(color='white', linewidth=2.5), whiskerprops=dict(linewidth=1.5), capprops=dict(linewidth=1.5), flierprops=dict(marker='o', markersize=5, alpha=0.6)) box_colors = [CLUSTER_COLORS[c] for c in sorted(df_model['cluster'].unique())] for patch, color in zip(bp['boxes'], box_colors): patch.set_facecolor(color) patch.set_alpha(0.75) |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。fig, ax = plt.subplots(...) — 図全体(fig)と軸(ax)を作る定番。以降は ax.bar(...) 等で操作。np.cumsum(arr) は累積和、np.linspace(a, b, n) は「aからbを等間隔でn個」。NumPyの定石です。363 364 365 366 367 368 369 370 371 372 373 374 375 | # 平均値マーカー for i, (mean_val, color) in enumerate(zip(cluster_means, box_colors)): ax4a.scatter(i + 1, mean_val, color=color, s=80, zorder=5, marker='D', edgecolors='white', linewidth=1.5, label=f'クラスタ{i+1} 平均: {mean_val:.1f}分') ax4a.set_xticks(range(1, N_CLUSTERS + 1)) ax4a.set_xticklabels([f'クラスタ {c}\n(n={cluster_ns[c-1]})' for c in range(1, N_CLUSTERS + 1)], fontsize=10) ax4a.set_ylabel('1日平均睡眠時間(分)', fontsize=11) ax4a.set_title('クラスタ別 睡眠時間分布\n(ひし形=平均値)', fontsize=11, fontweight='bold') ax4a.legend(fontsize=9, loc='upper right') ax4a.grid(axis='y', alpha=0.3) |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。{値:.2f}(小数2桁)、{値:,}(3桁区切り)、{値:>10}(右寄せ10桁)など、覚えると出力が一気に整います。376 377 378 379 380 381 382 383 384 385 386 387 388 | # 全国平均線 national_mean = df_model['睡眠_min'].mean() ax4a.axhline(national_mean, color='#333', linestyle=':', linewidth=1.5, label=f'全国平均: {national_mean:.1f}分') # パネル(b): クラスタ別 社会指標プロファイル(レーダー不使用、棒グラフで) ax4b = axes4[1] feat_for_profile = top_feat_cols # 上位4変数を使ってプロファイル比較 cluster_means_feat = [] for c in sorted(df_model['cluster'].unique()): means = df_model[df_model['cluster'] == c][feat_for_profile].mean().values cluster_means_feat.append(means) |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。ax.axhline / ax.axvline — 水平/垂直の点線。平均線や基準線として定番。plt.subplots(figsize=(W, H)) で図サイズ指定、fig.savefig(..., bbox_inches='tight') で余白を自動で詰めて保存。389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 | # 標準化して比較(各特徴量をzスコア化) cluster_means_arr = np.array(cluster_means_feat) feat_overall_mean = df_model[feat_for_profile].mean().values feat_overall_std = df_model[feat_for_profile].std().values + 1e-9 cluster_means_z = (cluster_means_arr - feat_overall_mean) / feat_overall_std x_pos = np.arange(top_n) bar_w = 0.2 offsets = [-1.5 * bar_w, -0.5 * bar_w, 0.5 * bar_w, 1.5 * bar_w] for ci, (c, offset) in enumerate(zip(sorted(df_model['cluster'].unique()), offsets)): n_pref = cluster_ns[ci] ax4b.bar(x_pos + offset, cluster_means_z[ci], width=bar_w, color=CLUSTER_COLORS[c], alpha=0.8, label=f'クラスタ {c}(n={n_pref})', edgecolor='white') ax4b.set_xticks(x_pos) ax4b.set_xticklabels(top_feat_labels, fontsize=9, rotation=15, ha='right') ax4b.set_ylabel('zスコア(全体平均=0)', fontsize=11) ax4b.set_title('クラスタ別 社会指標プロファイル\n(上位重要変数のzスコア比較)', fontsize=11, fontweight='bold') ax4b.axhline(0, color='gray', linewidth=0.8, linestyle='--') ax4b.legend(fontsize=9) ax4b.grid(axis='y', alpha=0.3) plt.tight_layout() fig4.savefig(os.path.join(FIG_DIR, '2023_U5_2_fig4_boxplot.png'), bbox_inches='tight', dpi=150) plt.close(fig4) print(" -> 2023_U5_2_fig4_boxplot.png 保存完了") |
図4: クラスタ別睡眠時間ボックスプロットを作成中... # 実行時エラーで途中まで
ax.axhline / ax.axvline — 水平/垂直の点線。平均線や基準線として定番。.dropna() は欠損行を除去、.copy() は独立したコピーを作る。pandasで警告を防ぐ定石。| データ | 出典 |
|---|---|
| SSDSE-B-2026(都道府県別統計) | 統計数理研究所 SSDSE(教育用標準データセット) |
| SSDSE-D-2023(社会生活基本調査) | 統計数理研究所 SSDSE(教育用標準データセット) |
本コードは SSDSE-B-2026 および SSDSE-D-2023 の実公的統計データのみを使用しています(合成データ・乱数データは一切使用していません)。np.random.seed() は使用せず、アルゴリズム再現性のみ random_state=42 を RandomForestRegressor に指定しています。
統計分析の解釈で初心者がやりがちな勘違いをまとめます。特に「相関と因果の混同」「p値の過信」は研究現場でもよく起きる落とし穴です。本文を読む前にも、読んだ後にも、目を通してみてください。
統計の基本用語を初心者向けに解説します。本文中で見慣れない言葉が出てきたら、ここに戻って確認してください。
統計手法について「何のためか」「結果をどう読むか」を初心者向けに解説します。
この研究をさらに発展させるための3つの方向性を示します。「今回わかったこと(X)」から「次に検証すべき仮説(Y)」を立て、「具体的に何をするか(Z)」まで考えてみましょう。
学んだだけでは身につきません。実際に手を動かすのが最強の学習方法です。本論文のスクリプトをベースに、以下のチャレンジに挑戦してみてください。難易度別に5つ用意しました。
本論文で学んだ手法は、研究の世界だけでなく、行政・企業・NPO の現場でも様々に活用されています。具体的なシーンを紹介します。
この論文を読んで初心者が抱きやすい疑問に、教育的観点から答えます。