このページの分析を自分で再現するには、以下の手順でデータを準備してください。コードの編集は不要です。
data/raw/ フォルダに入れます。html/figures/ に自動保存されます。
「豊かになるほどゴミが増える」は本当か? 本研究は、環境経済学の重要仮説である環境クズネッツ曲線(EKC: Environmental Kuznets Curve)を、都道府県別のごみ排出量データで検証する。
まず「都道府県別ごみ排出量とリサイクル率の決定要因— EKC仮説の検証 —」を統計的にとらえることが有効だと考えられる。 その理由は感覚や経験則だけでは、複雑な社会要因の中で「何が本当に効いているか」を見極めにくいからである。 本研究では公開データと統計手法を組み合わせ、この問いに定量的な答えを出すことを目指す。
SSDSE-B 都道府県 Pearson相関 EKC 2次回帰 時系列分析
SSDSE-B-2026.csv(社会・人口統計体系 都道府県データ)から、2012〜2023年度の47都道府県パネルデータを使用。断面分析には最新年度(2023年度)を採用。
| 変数の役割 | 変数名 | 単位 | 説明 |
|---|---|---|---|
| 目的変数① | 1人1日あたりごみ排出量 | g/人/日 | 廃棄物行政からの環境負荷指標 |
| 目的変数② | ごみのリサイクル率 | % | 3R政策の達成度指標 |
| 所得代理変数 | 消費支出(二人以上の世帯) | 円/月 | EKC仮説の所得軸として使用 |
| 統制変数① | 光熱・水道費(二人以上の世帯) | 円/月 | 寒冷地効果・生活スタイルの代理 |
| 統制変数② | 高齢化率(65歳以上/総人口) | % | 世帯規模・ごみ排出行動の違い |
| 統制変数③ | 年平均気温 | ℃ | 気候条件・暖房需要の代理 |
| 変数 | 平均 | 標準偏差 | 最小 | 最大 |
|---|---|---|---|---|
| 1人1日排出量(g/人/日) | 878 | 60 | 749(京都) | 989(富山) |
| ごみリサイクル率(%) | 18.1 | 4.8 | 12.6(青森) | 34.4(岡山) |
| 消費支出(円/月) | 295,856 | 24,144 | 223,423 | 344,092 |
| 光熱水道費(円/月) | 24,150 | 3,119 | 18,655 | 31,954 |
| 高齢化率(%) | 31.6 | 3.3 | 22.8 | 39.1 |
| 年平均気温(℃) | 16.8 | 2.0 | 11.0 | 23.8 |
EKCを統計的に検証するには、所得の1次項と2次項の両方を重回帰モデルに投入する。2次項の係数(β₂)の符号と有意性が仮説の判定基準となる。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | 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 scipy import stats from matplotlib.patches import Patch plt.rcParams['font.family'] = 'Hiragino Sans' plt.rcParams['axes.unicode_minus'] = False plt.rcParams['figure.dpi'] = 150 FIG_DIR = 'html/figures' DATA_B = 'data/raw/SSDSE-B-2026.csv' os.makedirs(FIG_DIR, exist_ok=True) 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)].copy() df_b['年度'] = df_b['年度'].astype(int) print("=== 利用可能な列 ===") print(df_b.columns.tolist()) print(f"\n年度: {sorted(df_b['年度'].unique())}") print(f"都道府県数: {df_b['都道府県'].nunique()}") |
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)。pd.read_csv(...) でCSVを読み込みます。encoding='cp932' は日本語Windows由来の文字コード、header=1 は「2行目を列名として使う」。df['地域コード'].str.match(r'^R\d{5}', ...) — 正規表現で「R+数字5桁」の行(47都道府県)だけTrueにし、真偽値で行をフィルタ。.astype(int) — 列を整数に変換(年度などを数値比較するため)。f"...{x}..." はf-string。文字列の中に {変数} と書くだけで埋め込めて、{x:.2f} のように書式も指定できます。27 28 29 30 31 32 33 34 35 36 | # 列名エイリアス COL_PERCAP = '1人1日当たりの排出量' # g/人/日 COL_RECYCLE = 'ごみのリサイクル率' # % COL_CONSUME = '消費支出(二人以上の世帯)' # 円/月 COL_ENERGY = '光熱・水道費(二人以上の世帯)' # 円/月 COL_POP = '総人口' COL_AGED = '65歳以上人口' COL_TEMP = '年平均気温' COL_PREF = '都道府県' COL_YEAR = '年度' |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。df['A'] / df['B'] — pandasの列同士の四則演算は要素ごと(element-wise)。forループ不要なのが強み。37 38 39 40 41 42 43 44 | # 都道府県名の正規化(語尾除去) def clean_pref(s): for suffix in ['県', '府', '都', '道']: if s.endswith(suffix): return s[:-1] return s df_b['都道府県短'] = df_b[COL_PREF].apply(clean_pref) |
=== 利用可能な列 === ['年度', '地域コード', '都道府県', '総人口', '総人口(男)', '総人口(女)', '日本人人口', '日本人人口(男)', '日本人人口(女)', '15歳未満人口', '15歳未満人口(男)', '15歳未満人口(女)', '15~64歳人口', '15~64歳人口(男)', '15~64歳人口(女)', '65歳以上人口', '65歳以上人口(男)', '65歳以上人口(女)', '出生数', '出生数(男)', '出生数(女)', '合計特殊出生率', '死亡数', '死亡数(男)', '死亡数(女)', '転入者数(日本人移動者)', '転入者数(日本人移動者)(男)', '転入者数(日本人移動者)(女)', '転出者数(日本人移動者)', '転出者数(日本人移動者)(男)', '転出者数(日本人移動者)(女)', '婚姻件数', '離婚件数', '年平均気温', '最高気温(日最高気温の月平均の最高値)', '最低気温(日最低気温の月平均の最低値)', '降水日数(年間)', '降水量(年間)', '着工建築物数', '着工建築物床面積', '旅館営業施設数(ホテルを含む)', '旅館営業施設客室数(ホテルを含む)', '標準価格(平均価格)(住宅地)', '標準価格(平均価格)(商業地)', '幼稚園数', '幼稚園教員数', '幼稚園在園者数', '小学校数', '小学校教員数', '小学校児童数', '中学校数', '中学校教員数', '中学校生徒数', '中学校卒業者数', '中学校卒業者のうち進学者数', '高等学校数', '高等学校教員数', '高等学校生徒数', '高等学校卒業者数', '高等学校卒業者のうち進学者数', '短期大学数', '大学数', '短期大学教員数', '大学教員数', '短期大学学生数', '大学学生数', '短期大学卒業者数', '短期大学卒業者のうち進学者数', '大学卒業者数', '大学卒業者のうち進学者数', '専修学校数', '各種学校数', '専修学校生徒数', '各種学校生徒数', '新規求職申込件数(一般)', '月間有効求職者数(一般)', '月間有効求人数(一般)', '充足数(一般)', '就職件数(一般)', '一般旅券発行件数', '延べ宿泊者数', '外国人延べ宿泊者数', '着工新設住宅戸数', '着工新設持家数', '着工新設貸家数', '着工新設分譲住宅数', '着工新設住宅床面積', '着工新設持家床面積', '着工新設分譲住宅床面積', '着工新設貸家床面積', 'ごみ総排出量(総量)', '1人1日当たりの排出量', 'ごみのリサイクル率', '一般病院数', '一般診療所数', '歯科診療所数', '保育所等数', '保育所等定員数', '保育所等利用待機児童数', '保育所等在所児数', '保育所等保育士数', '消費支出(二人以上の世帯)', '食料費(二人以上の世帯)', '住居費(二人以上の世帯)', '光熱・水道費(二人以上の世帯)', '家具・家事用品費(二人以上の世帯)', '被服及び履物費(二人以上の世帯)', '保健医療費(二人以上の世帯)', '交通・通信費(二人以上の世帯)', '教育費(二人以上の世帯)', '教養娯楽費(二人以上の世帯)', 'その他の消費支出(二人以上の世帯)'] 年度: [np.int64(2012), np.int64(2013), np.int64(2014), np.int64(2015), np.int64(2016), np.int64(2017), np.int64(2018), np.int64(2019), np.int64(2020), np.int64(2021), np.int64(2022), np.int64(2023)] 都道府県数: 47
.map() は「1対1の置き換え」、.apply() は「関数を当てる」。辞書なら .map()、ロジックなら .apply()。45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 | region_map = { '北海道': '北海道・東北', '青森': '北海道・東北', '岩手': '北海道・東北', '宮城': '北海道・東北', '秋田': '北海道・東北', '山形': '北海道・東北', '福島': '北海道・東北', '茨城': '関東', '栃木': '関東', '群馬': '関東', '埼玉': '関東', '千葉': '関東', '東京': '関東', '神奈川': '関東', '新潟': '中部', '富山': '中部', '石川': '中部', '福井': '中部', '山梨': '中部', '長野': '中部', '岐阜': '中部', '静岡': '中部', '愛知': '中部', '三重': '近畿', '滋賀': '近畿', '京都': '近畿', '大阪': '近畿', '兵庫': '近畿', '奈良': '近畿', '和歌山': '近畿', '鳥取': '中国・四国', '島根': '中国・四国', '岡山': '中国・四国', '広島': '中国・四国', '山口': '中国・四国', '徳島': '中国・四国', '香川': '中国・四国', '愛媛': '中国・四国', '高知': '中国・四国', '福岡': '九州・沖縄', '佐賀': '九州・沖縄', '長崎': '九州・沖縄', '熊本': '九州・沖縄', '大分': '九州・沖縄', '宮崎': '九州・沖縄', '鹿児島': '九州・沖縄', '沖縄': '九州・沖縄' } region_colors = { '北海道・東北': '#4e9af1', '関東': '#e05c5c', '中部': '#f0a500', '近畿': '#5cb85c', '中国・四国': '#9b59b6', '九州・沖縄': '#f39c12' } region_order = ['北海道・東北', '関東', '中部', '近畿', '中国・四国', '九州・沖縄'] df_b['地域'] = df_b['都道府県短'].map(region_map) |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。df['A'] / df['B'] — pandasの列同士の四則演算は要素ごと(element-wise)。forループ不要なのが強み。70 71 72 73 74 75 76 77 78 79 80 81 82 | LATEST = df_b[COL_YEAR].max() df_cross = df_b[df_b[COL_YEAR] == LATEST].copy() print(f"\n最新年: {LATEST}, N={len(df_cross)}") # 派生変数の計算 df_cross['高齢化率'] = pd.to_numeric(df_cross[COL_AGED], errors='coerce') / \ pd.to_numeric(df_cross[COL_POP], errors='coerce') * 100 df_cross['消費支出'] = pd.to_numeric(df_cross[COL_CONSUME], errors='coerce') df_cross['光熱水道費'] = pd.to_numeric(df_cross[COL_ENERGY], errors='coerce') df_cross['気温'] = pd.to_numeric(df_cross[COL_TEMP], errors='coerce') df_cross['1人排出量'] = pd.to_numeric(df_cross[COL_PERCAP], errors='coerce') df_cross['リサイクル率'] = pd.to_numeric(df_cross[COL_RECYCLE], errors='coerce') df_cross['消費支出_sq'] = df_cross['消費支出'] ** 2 |
最新年: 2023, N=47
.map() は「1対1の置き換え」、.apply() は「関数を当てる」。辞書なら .map()、ロジックなら .apply()。83 84 85 86 87 88 89 90 91 92 93 94 95 | Y_COL = '1人排出量' Y_LABEL = '1人1日あたりごみ排出量(g/人/日)' X_COL = '消費支出' X_LABEL = '消費支出(円/月,二人以上世帯)—— 所得代理変数' CTRL_COLS = ['光熱水道費', '高齢化率', '気温'] CTRL_LABELS = ['光熱水道費', '高齢化率', '年平均気温'] need_cols = [Y_COL, X_COL, '消費支出_sq'] + CTRL_COLS df_reg = df_cross[need_cols + ['都道府県短', '地域', COL_PREF, 'リサイクル率']]\ .dropna(subset=need_cols).copy() print(f"回帰用 N={len(df_reg)}") print(df_reg[need_cols].describe().round(1)) |
回帰用 N=47
1人排出量 消費支出 消費支出_sq 光熱水道費 高齢化率 気温
count 47.0 47.0 4.700000e+01 47.0 47.0 47.0
mean 878.1 295856.0 8.810132e+10 24149.7 31.6 16.8
std 60.2 24144.1 1.401341e+10 3119.4 3.3 2.0
min 749.0 223423.0 4.991784e+10 18655.0 22.8 11.0
25% 837.5 279506.0 7.812374e+10 21808.5 30.0 16.2
50% 877.0 300652.0 9.039163e+10 23593.0 31.8 17.4
75% 923.0 307501.5 9.455727e+10 25742.5 34.0 18.0
max 989.0 344092.0 1.183993e+11 31954.0 39.1 23.8.describe() — 件数・平均・標準偏差・四分位・最大/最小を一括計算。データの素性チェックに必須。[式 for x in リスト] はリスト内包表記。forループでappendする代わりに1行でリストを作れます。2023年度の47都道府県の1人1日あたりごみ排出量を多い順に並べた。地域ごとの色分けにより、北海道・東北地方が排出量上位に集中するパターンが鮮明になる。
| 順位 | 都道府県 | 地域 | 1人1日排出量(g) | リサイクル率(%) |
|---|---|---|---|---|
| 1位(最多) | 富山 | 中部 | 989 | 21.4 |
| 2位 | 福島 | 北海道・東北 | 968 | 13.2 |
| 3位 | 青森 | 北海道・東北 | 967 | 12.6 |
| …中略… | ||||
| 45位 | 神奈川 | 関東 | 769 | 24.2 |
| 46位 | 滋賀 | 近畿 | 761 | 15.7 |
| 47位(最少) | 京都 | 近畿 | 749 | 14.6 |
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 128 129 130 131 132 133 134 135 136 137 138 | fig1, ax1 = plt.subplots(figsize=(14, 7)) df_rank = df_reg[['都道府県短', '地域', Y_COL]].sort_values(Y_COL, ascending=True).copy() national_mean = df_reg[Y_COL].mean() colors_bar = [region_colors.get(r, '#999') for r in df_rank['地域']] ax1.barh(df_rank['都道府県短'], df_rank[Y_COL], color=colors_bar, edgecolor='white', linewidth=0.4) ax1.axvline(national_mean, color='#333', linestyle='--', linewidth=1.8, label=f'全国平均 {national_mean:.0f} g/人/日', zorder=5) legend_elements = [Patch(facecolor=region_colors[r], label=r) for r in region_order] legend_elements.append( plt.Line2D([0], [0], color='#333', linestyle='--', linewidth=1.8, label=f'全国平均 {national_mean:.0f} g')) ax1.legend(handles=legend_elements, loc='lower right', fontsize=9, framealpha=0.9) ax1.set_xlabel('1人1日あたりごみ排出量(g/人/日)', fontsize=12) ax1.set_title(f'図1:都道府県別 1人1日あたりごみ排出量ランキング({LATEST}年度)\n' f'北海道・東北が上位に集中(寒冷地効果・ごみ焼却量の影響)', fontsize=13, fontweight='bold') ax1.tick_params(axis='y', labelsize=8) ax1.tick_params(axis='x', labelsize=10) ax1.set_xlim(0, df_rank[Y_COL].max() * 1.12) ax1.grid(axis='x', alpha=0.3) max_val = df_rank[Y_COL].max() min_val = df_rank[Y_COL].min() max_pref = df_rank.loc[df_rank[Y_COL].idxmax(), '都道府県短'] min_pref = df_rank.loc[df_rank[Y_COL].idxmin(), '都道府県短'] n_rank = len(df_rank) ax1.annotate(f'最大: {max_pref} {max_val:.0f} g', xy=(max_val, n_rank - 1), xytext=(max_val - 90, n_rank - 4), fontsize=8.5, color='#c0392b', fontweight='bold') ax1.annotate(f'最小: {min_pref} {min_val:.0f} g', xy=(min_val, 0), xytext=(min_val + 8, 3), fontsize=8.5, color='#2980b9', fontweight='bold') plt.tight_layout() fig1.savefig(os.path.join(FIG_DIR, '2019_H5_3_fig1.png'), bbox_inches='tight') plt.close(fig1) print("図1 保存完了") |
図1 保存完了
fig, ax = plt.subplots(...) — 図全体(fig)と軸(ax)を作る定番。以降は ax.bar(...) 等で操作。sort_values('列名', ascending=False) — 指定列で並べ替え(降順)。ax.axhline / ax.axvline — 水平/垂直の点線。平均線や基準線として定番。df[col](1列)と df[[col1, col2]](複数列)でカッコの数が違います。リストを渡していると覚えるとミスを減らせます。消費支出(所得代理変数)と1人1日排出量の関係を散布図で確認し、線形フィットと2次フィット(EKC型)を重ねて描く。転換点の位置がデータ範囲内にあれば、EKCが現実のデータで観察される可能性が示唆される。
| 変数 | 1人排出量との相関 r | p値 | 有意性 |
|---|---|---|---|
| 消費支出 | −0.268 | 0.069 | n.s.(傾向) |
| 光熱水道費 | +0.260 | 0.078 | n.s.(傾向) |
| 高齢化率 | +0.560 | <0.001 | *** |
| 年平均気温 | −0.248 | 0.092 | n.s. |
| 変数 | リサイクル率との相関 r | p値 | 有意性 |
|---|---|---|---|
| 消費支出 | +0.344 | 0.018 | * |
| 高齢化率 | −0.254 | 0.085 | n.s.(傾向) |
| 年平均気温 | +0.035 | 0.815 | n.s. |
EKCモデル Y = β₀ + β₁X + β₂X² の転換点は微分して求める。β₂ の有意性がEKCの成否を決定づける。
140 141 142 143 144 145 146 | fig2, ax2 = plt.subplots(figsize=(9, 7)) for region in region_order: df_r = df_reg[df_reg['地域'] == region] ax2.scatter(df_r[X_COL], df_r[Y_COL], color=region_colors[region], label=region, s=65, alpha=0.88, edgecolors='white', linewidth=0.5, zorder=4) |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。fig, ax = plt.subplots(...) — 図全体(fig)と軸(ax)を作る定番。以降は ax.bar(...) 等で操作。s[:-n]「末尾n文字を除く」/s[n:]「先頭n文字を除く」。スライス [start:stop:step] はリスト・タプル・文字列共通の基本ワザです。147 148 149 150 151 152 153 154 155 156 157 158 159 160 | # 都道府県ラベル(上位・下位 + 特徴的な都市) label_prefs = set() for col_val, ascending in [(Y_COL, False), (Y_COL, True), (X_COL, False), (X_COL, True)]: for _, row in df_reg.sort_values(col_val, ascending=ascending).head(4).iterrows(): label_prefs.add(row['都道府県短']) for _, row in df_reg.iterrows(): if row['都道府県短'] in label_prefs: ax2.annotate(row['都道府県短'], (row[X_COL], row[Y_COL]), textcoords='offset points', xytext=(5, 2), fontsize=8, color='#333', fontweight='bold') x_sorted = np.linspace(df_reg[X_COL].min(), df_reg[X_COL].max(), 300) |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。sort_values('列名', ascending=False) — 指定列で並べ替え(降順)。for _, row in df.iterrows() — DataFrameを1行ずつ取り出すループ。1点ずつ描画したいときに使用。np.cumsum(arr) は累積和、np.linspace(a, b, n) は「aからbを等間隔でn個」。NumPyの定石です。161 162 163 164 165 166 167 168 169 | # 線形フィット slope_l, intercept_l, r_l, p_l, _ = stats.linregress(df_reg[X_COL], df_reg[Y_COL]) ax2.plot(x_sorted, intercept_l + slope_l * x_sorted, 'k--', linewidth=1.5, alpha=0.6, label='線形フィット') # 2次フィット(EKC) z2 = np.polyfit(df_reg[X_COL], df_reg[Y_COL], 2) p2 = np.poly1d(z2) ax2.plot(x_sorted, p2(x_sorted), 'r-', linewidth=2.2, alpha=0.85, label='2次フィット(EKC)') |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。stats.linregress(x, y) — 単回帰の傾き・切片・r値・p値・標準誤差を返します。使わない値は _ で受け取り。{値:.2f}(小数2桁)、{値:,}(3桁区切り)、{値:>10}(右寄せ10桁)など、覚えると出力が一気に整います。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 | # 転換点マーク if z2[0] != 0: tp = -z2[1] / (2 * z2[0]) x_min, x_max = df_reg[X_COL].min(), df_reg[X_COL].max() if x_min <= tp <= x_max: ax2.axvline(tp, color='#c0392b', linestyle=':', linewidth=1.5, alpha=0.8) ax2.text(tp + 2000, df_reg[Y_COL].min() + 5, f'転換点\n{tp/10000:.1f}万円', fontsize=9, color='#c0392b', va='bottom', fontweight='bold') r_lin, p_lin = stats.pearsonr(df_reg[X_COL], df_reg[Y_COL]) sig_lin = '***' if p_lin < 0.001 else '**' if p_lin < 0.01 else '*' if p_lin < 0.05 else 'n.s.' ax2.text(0.04, 0.97, f'Pearson r = {r_lin:.3f} ({sig_lin})\np = {p_lin:.4f}', transform=ax2.transAxes, fontsize=10, va='top', bbox=dict(boxstyle='round', facecolor='white', alpha=0.85, edgecolor='#ccc')) ax2.set_xlabel(X_LABEL, fontsize=11) ax2.set_ylabel(Y_LABEL, fontsize=11) ax2.set_title(f'図2:消費支出(所得代理)とごみ排出量の関係(EKC検討)\n{LATEST}年度 47都道府県', fontsize=12, fontweight='bold') ax2.legend(loc='upper right', fontsize=9, framealpha=0.9) ax2.grid(alpha=0.3) plt.tight_layout() fig2.savefig(os.path.join(FIG_DIR, '2019_H5_3_fig2.png'), bbox_inches='tight') plt.close(fig2) print("図2 保存完了") |
図2 保存完了
ax.axhline / ax.axvline — 水平/垂直の点線。平均線や基準線として定番。stats.pearsonr(x, y) — Pearson相関係数 r と p値を同時に返します。plt.subplots(figsize=(W, H)) で図サイズ指定、fig.savefig(..., bbox_inches='tight') で余白を自動で詰めて保存。2012〜2023年度の12年間にわたる地域別の1人1日排出量の推移を確認する。日本の3R政策(リデュース・リユース・リサイクル)の効果が時系列データに現れているか検証する。
気候条件はごみ排出量に構造的な影響を与える。年平均気温が低い地域では暖房由来の廃棄物・灰が増え、食品保存のための包装も厚くなる傾向がある。回帰分析の際には気候変数の制御が不可欠。
198 199 200 201 202 203 | fig3, ax3 = plt.subplots(figsize=(11, 6)) years_range = sorted(df_b[COL_YEAR].unique()) # COVID グレー帯(2020〜2021) ax3.axvspan(2019.5, 2021.5, alpha=0.12, color='gray', label='COVID-19期間(参考)') |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。fig, ax = plt.subplots(...) — 図全体(fig)と軸(ax)を作る定番。以降は ax.bar(...) 等で操作。np.cumsum(arr) は累積和、np.linspace(a, b, n) は「aからbを等間隔でn個」。NumPyの定石です。204 205 206 207 208 209 210 211 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 | # 全国平均 national_ts = df_b.groupby(COL_YEAR)[COL_PERCAP].apply( lambda x: pd.to_numeric(x, errors='coerce').mean()) ax3.plot(years_range, national_ts[years_range].values, 'k-', linewidth=3, alpha=0.5, label='全国平均', zorder=3) for region in region_order: df_r = df_b[df_b['地域'] == region].copy() df_r[COL_PERCAP] = pd.to_numeric(df_r[COL_PERCAP], errors='coerce') ts = df_r.groupby(COL_YEAR)[COL_PERCAP].mean().reindex(years_range) ax3.plot(years_range, ts.values, marker='o', markersize=4, color=region_colors[region], label=region, linewidth=2, alpha=0.9) ax3.set_xlabel('年度', fontsize=12) ax3.set_ylabel('1人1日あたりごみ排出量(g/人/日)', fontsize=12) ax3.set_title('図3:地域別 1人1日ごみ排出量の時系列推移(2012〜2023年度)\n' '全地域で一貫した削減トレンド(3R政策の効果)', fontsize=13, fontweight='bold') ax3.legend(loc='upper right', fontsize=9, framealpha=0.9) ax3.set_xticks(years_range) ax3.tick_params(axis='x', rotation=45) ax3.grid(alpha=0.3) y_start = float(national_ts.iloc[0]) y_end = float(national_ts.iloc[-1]) change_pct = (y_end - y_start) / y_start * 100 ax3.annotate(f'全国平均 {change_pct:.1f}%\n({y_start:.0f}→{y_end:.0f} g)', xy=(years_range[-1], y_end), xytext=(years_range[-4], y_end + 30), arrowprops=dict(arrowstyle='->', color='#333', lw=1.5), fontsize=9, color='#333', fontweight='bold') plt.tight_layout() fig3.savefig(os.path.join(FIG_DIR, '2019_H5_3_fig3.png'), bbox_inches='tight') plt.close(fig3) print("図3 保存完了") |
図3 保存完了
df.groupby('列').apply(関数) — グループごとに関数を適用。時系列や地域別の集計でよく使います。{値:.2f}(小数2桁)、{値:,}(3桁区切り)、{値:>10}(右寄せ10桁)など、覚えると出力が一気に整います。1人1日排出量を目的変数として、消費支出(1次・2次項)、光熱水道費、高齢化率、年平均気温を説明変数とするEKC重回帰を実施。全変数を標準化することで、変数間の効果量を直接比較する。
| 変数 | 標準化β | p値 | 有意性 | 解釈 |
|---|---|---|---|---|
| 消費支出(β₁) | +1.79 | 0.395 | n.s. | EKC左辺(上昇)方向 |
| 消費支出²(β₂) | −1.96 | 0.355 | n.s. | EKC曲率(下降)方向 — 非有意 |
| 光熱水道費 | +0.19 | 0.354 | n.s. | 寒冷地効果(傾向のみ) |
| 高齢化率 | +0.45 | 0.008 | ** | 高齢化→排出量増加(最強効果) |
| 年平均気温 | +0.08 | 0.692 | n.s. | 気候の直接効果は小 |
R²=0.353, Adj.R²=0.275, F(5,41)=4.48, p=0.002(モデル全体は有意)。N=47都道府県(2023年度)。
日本は2000年「循環型社会形成推進基本法」以降、3R(Reduce・Reuse・Recycle)政策を強化。時系列データは12年間の継続的な排出量削減を示しており、政策効果の統計的評価に活用できる。
241 242 243 244 245 246 247 248 249 250 251 252 253 | print("\n=== Pearson相関(1人排出量 vs 説明変数) ===") for col, label in [(X_COL, '消費支出'), ('光熱水道費', '光熱水道費'), ('高齢化率', '高齢化率'), ('気温', '年平均気温')]: r, p = stats.pearsonr(df_reg[col], df_reg[Y_COL]) sig = '***' if p < 0.001 else '**' if p < 0.01 else '*' if p < 0.05 else 'n.s.' print(f" {label}: r={r:.3f}, p={p:.4f} {sig}") df_rec = df_reg.dropna(subset=['リサイクル率']) print("\n=== Pearson相関(リサイクル率 vs 説明変数) ===") for col, label in [(X_COL, '消費支出'), ('高齢化率', '高齢化率'), ('気温', '年平均気温')]: r, p = stats.pearsonr(df_rec[col], df_rec['リサイクル率']) sig = '***' if p < 0.001 else '**' if p < 0.01 else '*' if p < 0.05 else 'n.s.' print(f" {label}: r={r:.3f}, p={p:.4f} {sig}") |
=== Pearson相関(1人排出量 vs 説明変数) === 消費支出: r=-0.268, p=0.0687 n.s. 光熱水道費: r=0.260, p=0.0781 n.s. 高齢化率: r=0.560, p=0.0000 *** 年平均気温: r=-0.248, p=0.0922 n.s. === Pearson相関(リサイクル率 vs 説明変数) === 消費支出: r=0.344, p=0.0179 * 高齢化率: r=-0.254, p=0.0845 n.s. 年平均気温: r=0.035, p=0.8150 n.s.
stats.pearsonr(x, y) — Pearson相関係数 r と p値を同時に返します。r, p = stats.pearsonr(...) — Pythonは複数戻り値を同時に受け取れる(タプルアンパック)。254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 | print("\n=== EKC 重回帰分析(1人1日排出量)===") exog_cols = [X_COL, '消費支出_sq'] + CTRL_COLS exog_labels = ['消費支出(β₁)', '消費支出²(β₂)'] + CTRL_LABELS # 標準化 df_std = (df_reg[exog_cols + [Y_COL]] - df_reg[exog_cols + [Y_COL]].mean()) / \ df_reg[exog_cols + [Y_COL]].std() X_ekc = sm.add_constant(df_std[exog_cols].astype(float)) res_ekc = sm.OLS(df_std[Y_COL].astype(float), X_ekc).fit() print(res_ekc.summary()) coef_std = res_ekc.params[1:] ci = res_ekc.conf_int().iloc[1:] pvals = res_ekc.pvalues[1:] print("\n=== 標準化偏回帰係数 ===") for name, c, p in zip(exog_labels, coef_std, pvals): sig = '***' if p < 0.001 else '**' if p < 0.01 else '*' if p < 0.05 else 'n.s.' print(f" {name}: β={c:.4f}, p={p:.4f} {sig}") print(f"\nR² = {res_ekc.rsquared:.4f}, Adj.R² = {res_ekc.rsquared_adj:.4f}") print(f"F({res_ekc.df_model:.0f},{res_ekc.df_resid:.0f}) = {res_ekc.fvalue:.3f}, p = {res_ekc.f_pvalue:.6f}") |
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行で書けます。277 278 279 280 281 282 283 284 285 286 | # EKC判定 beta_sq = coef_std.iloc[1] p_sq = pvals.iloc[1] if beta_sq < 0 and p_sq < 0.05: ekc_result = "EKC仮説を支持(逆U字型:β₂ < 0, 有意)" elif beta_sq > 0 and p_sq < 0.05: ekc_result = "EKC仮説を棄却(U字型:β₂ > 0, 有意)" else: ekc_result = "EKC仮説の証拠なし(β₂ 非有意)" print(f"\nEKC検定: {ekc_result}") |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。df[col](1列)と df[[col1, col2]](複数列)でカッコの数が違います。リストを渡していると覚えるとミスを減らせます。287 288 289 290 291 292 293 294 295 | # 転換点(非標準化) X_raw_reg = sm.add_constant(df_reg[exog_cols].astype(float)) res_raw = sm.OLS(df_reg[Y_COL].astype(float), X_raw_reg).fit() a1 = res_raw.params[X_COL] a2 = res_raw.params['消費支出_sq'] if a2 != 0: turning_point = -a1 / (2 * a2) print(f"転換点(消費支出): {turning_point:,.0f} 円/月") print(f" → データ範囲: {df_reg[X_COL].min():,.0f}〜{df_reg[X_COL].max():,.0f} 円/月") |
=== EKC 重回帰分析(1人1日排出量)===
OLS Regression Results
==============================================================================
Dep. Variable: 1人排出量 R-squared: 0.353
Model: OLS Adj. R-squared: 0.275
Method: Least Squares F-statistic: 4.482
Date: Mon, 18 May 2026 Prob (F-statistic): 0.00238
Time: 11:23:29 Log-Likelihood: -55.938
No. Observations: 47 AIC: 123.9
Df Residuals: 41 BIC: 135.0
Df Model: 5
Covariance Type: nonrobust
==============================================================================
coef std err t P>|t| [0.025 0.975]
------------------------------------------------------------------------------
const -2.012e-16 0.124 -1.62e-15 1.000 -0.251 0.251
消費支出 1.7898 2.084 0.859 0.395 -2.419 5.999
消費支出_sq -1.9556 2.089 -0.936 0.355 -6.173 2.262
光熱水道費 0.1912 0.204 0.938 0.354 -0.221 0.603
高齢化率 0.4537 0.162 2.805 0.008 0.127 0.780
気温 0.0848 0.212 0.399 0.692 -0.344 0.513
==============================================================================
Omnibus: 0.769 Durbin-Watson: 2.055
Prob(Omnibus): 0.681 Jarque-Bera (JB): 0.865
Skew: -0.228 Prob(JB): 0.649
Kurtosis: 2.518 Cond. No. 36.4
==============================================================================
Notes:
[1] Standard Errors assume that the covariance matrix of the errors is correctly specified.
=== 標準化偏回帰係数 ===
消費支出(β₁): β=1.7898, p=0.3954 n.s.
消費支出²(β₂): β=-1.9556, p=0.3546 n.s.
光熱水道費: β=0.1912, p=0.3538 n.s.
高齢化率: β=0.4537, p=0.0077 **
年平均気温: β=0.0848, p=0.6916 n.s.
R² = 0.3534, Adj.R² = 0.2746
F(5,41) = 4.482, p = 0.002382
EKC検定: EKC仮説の証拠なし(β₂ 非有意)
転換点(消費支出): 265,599 円/月
→ データ範囲: 223,423〜344,092 円/月sm.add_constant(X) — 切片項(定数1の列)を先頭に追加。statsmodelsで必須。sm.OLS(y, X).fit() — 最小二乗法でモデルを推定。model.params, model.pvalues, model.conf_int() で結果取得。s[:-n]「末尾n文字を除く」/s[n:]「先頭n文字を除く」。スライス [start:stop:step] はリスト・タプル・文字列共通の基本ワザです。296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 | fig4, ax4 = plt.subplots(figsize=(8, 5)) colors_coef = [] for c, p in zip(coef_std.values, pvals.values): if p < 0.05: colors_coef.append('#e05c5c' if c > 0 else '#4e9af1') else: colors_coef.append('#bbb') y_pos = np.arange(len(exog_labels)) ci_lo = ci.iloc[:, 0].values ci_hi = ci.iloc[:, 1].values xerr_lo = coef_std.values - ci_lo xerr_hi = ci_hi - coef_std.values ax4.barh(y_pos, coef_std.values, color=colors_coef, edgecolor='white', xerr=np.array([xerr_lo, xerr_hi]), error_kw={'ecolor': '#555', 'capsize': 4, 'lw': 1.5}, height=0.6) ax4.axvline(0, color='#333', linewidth=1.2) for i, (c, p) in enumerate(zip(coef_std.values, pvals.values)): sig = '***' if p < 0.001 else '**' if p < 0.01 else '*' if p < 0.05 else '' if sig: x_pos = ci_hi[i] + 0.02 if c >= 0 else ci_lo[i] - 0.02 ha = 'left' if c >= 0 else 'right' ax4.text(x_pos, i, sig, va='center', ha=ha, fontsize=13, color='#333', fontweight='bold') ax4.set_yticks(y_pos) ax4.set_yticklabels(exog_labels, fontsize=11) ax4.set_xlabel('標準化偏回帰係数(β)', fontsize=12) ax4.set_title(f'図4:ごみ排出量の決定要因 — EKC重回帰(標準化係数){LATEST}年度\n' f'R²={res_ekc.rsquared:.3f}, Adj.R²={res_ekc.rsquared_adj:.3f} ' f'(N=47都道府県)', fontsize=12, fontweight='bold') ax4.grid(axis='x', alpha=0.3) legend_coef = [ Patch(facecolor='#e05c5c', label='正の効果(有意, p<0.05)'), Patch(facecolor='#4e9af1', label='負の効果(有意, p<0.05)'), Patch(facecolor='#bbb', label='非有意'), ] ax4.legend(handles=legend_coef, loc='lower right', fontsize=9) plt.tight_layout() fig4.savefig(os.path.join(FIG_DIR, '2019_H5_3_fig4.png'), bbox_inches='tight') plt.close(fig4) print("図4 保存完了") |
図4 保存完了
fig, ax = plt.subplots(...) — 図全体(fig)と軸(ax)を作る定番。以降は ax.bar(...) 等で操作。ax.axhline / ax.axvline — 水平/垂直の点線。平均線や基準線として定番。{値:.2f}(小数2桁)、{値:,}(3桁区切り)、{値:>10}(右寄せ10桁)など、覚えると出力が一気に整います。345 346 347 348 349 350 351 352 353 | print("\n=== 1人1日排出量 上位・下位5都道府県 ===") df_summary = df_reg[['都道府県短', '地域', Y_COL, 'リサイクル率', X_COL, '高齢化率']]\ .sort_values(Y_COL, ascending=False) print("上位5(排出量多):") print(df_summary.head(5).to_string(index=False)) print("下位5(排出量少):") print(df_summary.tail(5).to_string(index=False)) print("\nDONE: 2019_H5_3_shorei") |
=== 1人1日排出量 上位・下位5都道府県 === 上位5(排出量多): 都道府県短 地域 1人排出量 リサイクル率 消費支出 高齢化率 富山 中部 989 21.4 327503 33.068520 福島 北海道・東北 968 13.2 307186 33.163554 青森 北海道・東北 967 12.6 263371 35.219595 鳥取 中国・四国 963 28.2 272599 33.333333 秋田 北海道・東北 957 13.6 272086 39.059081 下位5(排出量少): 都道府県短 地域 1人排出量 リサイクル率 消費支出 高齢化率 埼玉 関東 790 24.3 344092 27.445096 長野 中部 770 22.0 313991 32.684631 神奈川 関東 769 24.2 306565 25.896630 滋賀 近畿 761 15.7 305586 27.007818 京都 近畿 749 14.6 314636 29.704142 DONE: 2019_H5_3_shorei
sort_values('列名', ascending=False) — 指定列で並べ替え(降順)。plt.subplots(figsize=(W, H)) で図サイズ指定、fig.savefig(..., bbox_inches='tight') で余白を自動で詰めて保存。SSDSE-B-2026の47都道府県データ(2023年度断面 + 2012〜2023年度時系列)を用いた分析の結論:
| データ | 出典 | 変数数 |
|---|---|---|
| SSDSE-B-2026 都道府県データ | 統計数理研究所 SSDSE(社会・人口統計体系) | 47都道府県×12年度 |
本コードは SSDSE-B-2026.csv の実データのみを使用(合成データ不使用)。
統計分析の解釈で初心者がやりがちな勘違いをまとめます。特に「相関と因果の混同」「p値の過信」は研究現場でもよく起きる落とし穴です。本文を読む前にも、読んだ後にも、目を通してみてください。
統計の基本用語を初心者向けに解説します。本文中で見慣れない言葉が出てきたら、ここに戻って確認してください。
統計手法について「何のためか」「結果をどう読むか」を初心者向けに解説します。
この研究をさらに発展させるための3つの方向性を示します。「今回わかったこと(X)」から「次に検証すべき仮説(Y)」を立て、「具体的に何をするか(Z)」まで考えてみましょう。
学んだだけでは身につきません。実際に手を動かすのが最強の学習方法です。本論文のスクリプトをベースに、以下のチャレンジに挑戦してみてください。難易度別に5つ用意しました。
本論文で学んだ手法は、研究の世界だけでなく、行政・企業・NPO の現場でも様々に活用されています。具体的なシーンを紹介します。
この論文を読んで初心者が抱きやすい疑問に、教育的観点から答えます。