この教材は原論文と同じ種類のデータで計算し直しています。ただし工程の一部は、学習しやすさのために簡略化しています(下の「できないこと」を参照)。
| 原論文が使ったデータ | 論文本文からは特定できなかった 分析単位:都道府県 中核手法:LASSO回帰・クロスバリデーション |
|---|---|
| この教材が使うデータ |
|
| 原論文(PDF) | 日本の電力需要量の影響調査 審査員奨励賞/北村 宙士、杉澤 直斗(名古屋大学大学院創薬科学研究科) |
▶ ここに書いてある分析は、ページ下部の「🐍 ブラウザで動かす」でそのまま実行できます(インストール不要)。動かしているのは code/2022_U5_5_shorei.py(374 行)そのものです。
このページの分析を自分で再現するには、以下の手順でデータを準備してください。コードの編集は不要です。
data/raw/ フォルダに入れます。html/figures/ に自動保存されます。
2022年のロシアによるウクライナ侵攻に端を発するエネルギー価格の高騰は、日本においても電力・ガス代の急上昇として家計に直撃した。一方で、日本は2050年カーボンニュートラルを国家目標に掲げ、電力需要の構造的変化(省エネ・再エネ普及)が求められている。
この論文が挑んだ問いは「家計の電力需要(光熱費割合)の地域差を生む要因は何か」(テーマ:環境・エネルギー)。 著者はLASSO・時系列分析・相関分析を軸にこの問いへ定量的に答えを出した。 本ページではその分析の流れを実データで再現しながら、使われた統計手法を一つずつ学んでいく。
本研究は、家計の電力消費に影響を与える要因を統計的に明らかにすることを目的とする。直接的な電力消費量データは都道府県別に整備されていないため、家計調査の光熱・水道費を電力需要の代理変数として活用する。
SSDSE-B 代理変数 OLS重回帰 相関分析 エネルギー政策
都道府県別の電力消費量は直接公表されていない(工業・業務用含む総量のみ)。本研究では、家計調査に収録される「光熱・水道費(二人以上の世帯)」を消費支出で割った「光熱費割合」を、家計レベルの電力需要を捉える代理変数とする。
| 説明変数 | 想定される効果 | 根拠 |
|---|---|---|
| 年平均気温(℃) | 負(低温→暖房増) | 寒冷地ほど暖房需要が高い |
| 総人口 | 規模要因 | 人口規模が電力需要の基礎 |
| 消費支出(円) | 正(所得水準) | 生活水準が高いほど消費電力増 |
| 高齢化率(%) | 正(在宅時間増) | 高齢者は在宅が長く暖冷房使用増 |
| 住宅地価格(円/m²) | 都市度の代理 | 都市部は省エネ住宅が普及している場合も |
代理変数は「測定困難な変数」を「測定可能な関連変数」で置き換える手法。重要なのは代替妥当性(construct validity)の確認:光熱費は電力・ガス・水道を合計するため、エネルギーミックス(電力 vs ガス)の違いを吸収できない点に注意が必要。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | 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() # 数値変換 num_cols = [ '光熱・水道費(二人以上の世帯)', '消費支出(二人以上の世帯)', '年平均気温', '最高気温(日最高気温の月平均の最高値)', '最低気温(日最低気温の月平均の最低値)', '総人口', '65歳以上人口', '標準価格(平均価格)(住宅地)', ] for c in num_cols: df_b[c] = pd.to_numeric(df_b[c], errors='coerce') |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。pd.read_csv(...) でCSVを読み込みます。encoding='cp932' は日本語Windows由来の文字コード、header=1 は「2行目を列名として使う」。df['地域コード'].str.match(r'^R\d{5}', ...) — 正規表現で「R+数字5桁」の行(47都道府県)だけTrueにし、真偽値で行をフィルタ。df['A'] / df['B'] — pandasの列同士の四則演算は要素ごと(element-wise)。forループ不要なのが強み。17 18 19 20 21 22 23 24 25 | # 派生変数 df_b['光熱費割合'] = df_b['光熱・水道費(二人以上の世帯)'] / df_b['消費支出(二人以上の世帯)'] * 100 df_b['高齢化率'] = df_b['65歳以上人口'] / df_b['総人口'] * 100 print("=" * 60) print("■ SSDSE-B 読み込み完了") print(f" 全レコード数: {len(df_b)}(都道府県×年度)") print(f" 年度範囲: {df_b['年度'].min()}〜{df_b['年度'].max()}") print("=" * 60) |
============================================================ ■ SSDSE-B 読み込み完了 全レコード数: 564(都道府県×年度) 年度範囲: 2012〜2023 ============================================================
.map() は「1対1の置き換え」、.apply() は「関数を当てる」。辞書なら .map()、ロジックなら .apply()。SSDSE-B の都道府県パネルデータを用いて、2012〜2023年の全国平均光熱費割合を集計した。陰影は都道府県間の±1標準偏差(ばらつき)を示す。
本分析は「年次データ(年度単位)」を用いているため、月次データで問題となる季節性(夏の冷房・冬の暖房)は自動的に平均化される。季節調整(X-13-ARIMA など)は月次・四半期データに対して適用するものであり、年次集計済みデータには不要。
27 28 29 30 31 32 33 34 35 36 | fig1, ax1 = plt.subplots(figsize=(9, 5)) years = ts['年度'].values means = ts['光熱費割合_mean'].values stds = ts['光熱費割合_std'].values ax1.fill_between(years, means - stds, means + stds, alpha=0.18, color='#1565C0', label='±1SD(都道府県間ばらつき)') ax1.plot(years, means, 'o-', color='#1565C0', linewidth=2.2, markersize=7, markerfacecolor='white', markeredgewidth=2, label='全国平均') |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。fig, ax = plt.subplots(...) — 図全体(fig)と軸(ax)を作る定番。以降は ax.bar(...) 等で操作。ax.fill_between(...) — 2つの曲線で囲まれた領域を塗りつぶし。Lorenz曲線の格差面積などを可視化。r, p = stats.pearsonr(...) — Pythonは複数戻り値を同時に受け取れる(タプルアンパック)。37 38 39 40 41 42 43 44 | # 2020年(コロナ禍)にアノテーション if 2020 in years: idx20 = list(years).index(2020) ax1.annotate('2020年\n(コロナ禍)', xy=(2020, means[idx20]), xytext=(2020 - 1.0, means[idx20] + 0.4), arrowprops=dict(arrowstyle='->', color='#C62828'), fontsize=9, color='#C62828') |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。x if cond else y は三項演算子。リスト内包表記と組み合わせると、forとifを1行で書けます。45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 | # 2022年(エネルギー価格高騰) if 2022 in years: idx22 = list(years).index(2022) ax1.annotate('2022年\n(エネルギー高騰)', xy=(2022, means[idx22]), xytext=(2021.0, means[idx22] + 0.5), arrowprops=dict(arrowstyle='->', color='#E65100'), fontsize=9, color='#E65100') ax1.set_xlabel('年度', fontsize=12) ax1.set_ylabel('光熱費割合(光熱・水道費 / 消費支出 × 100, %)', fontsize=11) ax1.set_title('光熱・水道費割合の時系列推移(全国平均)\n2012〜2023年 都道府県データ(SSDSE-B)', fontsize=12, fontweight='bold') ax1.legend(fontsize=10) ax1.grid(True, alpha=0.3) ax1.set_xticks(years) plt.tight_layout() fig1.savefig(os.path.join(FIG_DIR, '2022_U5_5_fig1_ts.png'), bbox_inches='tight', dpi=150) plt.close(fig1) print("\n図1保存: 2022_U5_5_fig1_ts.png") |
図1保存: 2022_U5_5_fig1_ts.png
df[col](1列)と df[[col1, col2]](複数列)でカッコの数が違います。リストを渡していると覚えるとミスを減らせます。2022年度の47都道府県のデータを用い、光熱費割合と各説明変数間のピアソン相関係数を算出した。ヒートマップは変数間の相関構造を一覧する。
| 変数 | 相関方向の予測 | 解釈 |
|---|---|---|
| 年平均気温(℃) | 負(強) | 寒冷な都道府県ほど光熱費割合が高い(暖房需要) |
| 高齢化率(%) | 正 | 高齢者が多い地域ほど光熱費割合が高い(在宅時間増) |
| 消費支出(円) | 弱または混合 | 光熱費割合の分母でもあるため解釈注意 |
| 住宅地価格(円/m²) | 負 | 都市部(地価高)は省エネ住宅・集合住宅が多い傾向 |
| 総人口 | 弱 | 人口規模より地域特性が支配的 |
66 67 68 69 70 71 72 73 74 75 76 77 78 | corr_plot_cols = [YVAR] + VARNAMES_EN corr_labels = ['光熱費割合'] + VARNAMES_JP C = df_ols[corr_plot_cols].corr().values fig2, ax2 = plt.subplots(figsize=(8, 7)) n = len(corr_labels) im = ax2.imshow(C, cmap='RdBu_r', vmin=-1, vmax=1, aspect='auto') plt.colorbar(im, ax=ax2, shrink=0.8, label='ピアソン相関係数') ax2.set_xticks(range(n)) ax2.set_xticklabels(corr_labels, rotation=30, ha='right', fontsize=10) ax2.set_yticks(range(n)) ax2.set_yticklabels(corr_labels, fontsize=10) |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。fig, ax = plt.subplots(...) — 図全体(fig)と軸(ax)を作る定番。以降は ax.bar(...) 等で操作。x if cond else y は三項演算子。リスト内包表記と組み合わせると、forとifを1行で書けます。79 80 81 82 83 84 85 86 87 88 89 90 91 92 | # セル内に相関係数を表示 for i in range(n): for j in range(n): val = C[i, j] color = 'white' if abs(val) > 0.6 else 'black' ax2.text(j, i, f'{val:.2f}', ha='center', va='center', fontsize=9, color=color, fontweight='bold') ax2.set_title('相関ヒートマップ:光熱費割合と各説明変数\n(2022年度, N=47都道府県)', fontsize=12, fontweight='bold') plt.tight_layout() fig2.savefig(os.path.join(FIG_DIR, '2022_U5_5_fig2_corr.png'), bbox_inches='tight', dpi=150) plt.close(fig2) print("図2保存: 2022_U5_5_fig2_corr.png") |
図2保存: 2022_U5_5_fig2_corr.png
df[col](1列)と df[[col1, col2]](複数列)でカッコの数が違います。リストを渡していると覚えるとミスを減らせます。5変数を全て投入したOLS重回帰(N=47都道府県、2022年度)を実施。説明変数を標準化(平均0・標準偏差1)したうえで推定することで、単位の異なる変数の相対的な重要度を比較する。
| 変数 | VIF(再現値、2022年度) | 判定 |
|---|---|---|
| 年平均気温(℃) | 1.44 | VIF < 5:問題なし |
| 総人口 | 6.17 | 5 ≤ VIF < 10:要注意 |
| 消費支出(円) | 1.26 | VIF < 5:問題なし |
| 高齢化率(%) | 2.67 | VIF < 5:問題なし |
| 住宅地価格(円/m²) | 5.61 | 5 ≤ VIF < 10:要注意 |
※判定の目安:VIF < 5 問題なし/5〜10 要注意/10以上 深刻な多重共線性。総人口と住宅地価格は互いに r=0.895 と強く相関しており、VIFが5を超える原因になっている。
標準化係数(ベータ係数)は「他の変数を固定したとき、X が1SD変化すると Y が何SD変化するか」を示す。単位が統一されるため変数間の重要度比較が可能。ただし多重共線性(変数間の強い相関)があると係数が不安定になるため、VIF < 5 の確認が前提となる。
94 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 | betas = np.asarray(model_std.params[1:]) ses = np.asarray(model_std.bse[1:]) pvals = np.asarray(model_std.pvalues[1:]) fig3, ax3 = plt.subplots(figsize=(9, 5)) y_pos = np.arange(len(VARNAMES_JP)) colors3 = ['#C62828' if p < 0.01 else '#FF8F00' if p < 0.05 else '#9E9E9E' for p in pvals] ax3.barh(y_pos, betas, color=colors3, alpha=0.78, edgecolor='white', height=0.55) ax3.errorbar(betas, y_pos, xerr=1.96 * ses, fmt='none', color='#333', capsize=4, linewidth=1.3) ax3.axvline(0, color='gray', linestyle='--', linewidth=1.0) ax3.set_yticks(y_pos) ax3.set_yticklabels(VARNAMES_JP, fontsize=11) ax3.set_xlabel('標準化偏回帰係数(±1.96SE)', fontsize=11) ax3.set_title( f'OLS重回帰:標準化偏回帰係数\n' f'R²={model_std.rsquared:.3f}, Adj.R²={model_std.rsquared_adj:.3f}, N={N}都道府県', fontsize=12, fontweight='bold') ax3.invert_yaxis() ax3.grid(axis='x', alpha=0.3) from matplotlib.patches import Patch ax3.legend(handles=[ Patch(color='#C62828', alpha=0.78, label='p < 0.01'), Patch(color='#FF8F00', alpha=0.78, label='p < 0.05'), Patch(color='#9E9E9E', alpha=0.78, label='n.s.') ], fontsize=9, loc='lower right') |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。import pandas as pd など — 必要なライブラリをまとめて呼び出します。as pd は短い別名(alias)。fig, ax = plt.subplots(...) — 図全体(fig)と軸(ax)を作る定番。以降は ax.bar(...) 等で操作。ax.axhline / ax.axvline — 水平/垂直の点線。平均線や基準線として定番。df[col](1列)と df[[col1, col2]](複数列)でカッコの数が違います。リストを渡していると覚えるとミスを減らせます。122 123 124 125 126 127 128 129 130 131 132 | # 係数値をバーに付記 for i, (b, p) in enumerate(zip(betas, pvals)): sig = '***' if p < 0.001 else '**' if p < 0.01 else '*' if p < 0.05 else '' ha = 'left' if b >= 0 else 'right' ax3.text(b + (0.01 if b >= 0 else -0.01), i, f' {b:.3f}{sig}', va='center', ha=ha, fontsize=8.5) plt.tight_layout() fig3.savefig(os.path.join(FIG_DIR, '2022_U5_5_fig3_coef.png'), bbox_inches='tight', dpi=150) plt.close(fig3) print("図3保存: 2022_U5_5_fig3_coef.png") |
図3保存: 2022_U5_5_fig3_coef.png
s[:-n]「末尾n文字を除く」/s[n:]「先頭n文字を除く」。スライス [start:stop:step] はリスト・タプル・文字列共通の基本ワザです。気温と光熱費割合の関係を都道府県別の散布図で可視化する。北海道・東北など寒冷地と沖縄など温暖地の対比が明確に現れ、気温が電力需要の主要な規定因であることを示す。
気温と光熱費の関係は「低温→暖房増(正の効果)、高温→冷房増(暑い地域でも上昇)」というU字型(二次関数)になると考えられる。線形OLSは中間温度帯での関係を捉えるが、極端な気温域では非線形項(気温²)の導入が有効。気候変動(平均気温上昇)はU字の右側を押し上げ、将来の電力需要増を示唆する。
134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 | 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 statsmodels.stats.outliers_influence import variance_inflation_factor 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} のように書式も指定できます。153 154 155 156 157 158 159 160 161 162 163 164 | df_2022 = df_b[df_b['年度'] == 2022].copy() # 説明変数リスト XVARS = { '年平均気温(℃)' : '年平均気温', '総人口(万人)' : '総人口', '消費支出(円)' : '消費支出(二人以上の世帯)', '高齢化率(%)' : '高齢化率', '住宅地価格(円/m²)' : '標準価格(平均価格)(住宅地)', } # 目的変数 YVAR = '光熱費割合' |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。.map() は「1対1の置き換え」、.apply() は「関数を当てる」。辞書なら .map()、ロジックなら .apply()。165 166 167 168 169 170 171 172 173 174 175 176 | # 分析用データフレーム作成 cols_use = [YVAR, '年平均気温', '総人口', '消費支出(二人以上の世帯)', '高齢化率', '標準価格(平均価格)(住宅地)', '都道府県'] df_ols = df_2022[cols_use].dropna().copy().reset_index(drop=True) N = len(df_ols) # 変数名マッピング(表示用) VARNAMES_JP = list(XVARS.keys()) VARNAMES_EN = list(XVARS.values()) print(f"\n■ 2022年度 有効データ: N={N}都道府県") print(df_ols[[YVAR] + VARNAMES_EN].describe().round(2)) |
print 出力は、下の「相関行列」ステップの実行結果にまとめて表示しています(冒頭の「■ 2022年度 有効データ: N=47都道府県」の部分)。.describe() — 件数・平均・標準偏差・四分位・最大/最小を一括計算。データの素性チェックに必須。[式 for x in リスト] はリスト内包表記。forループでappendする代わりに1行でリストを作れます。177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 | # ── OLS 標準化係数 ───────────────────────────────────────────── X_raw = df_ols[VARNAMES_EN].values.astype(float) y_raw = df_ols[YVAR].values.astype(float) # 標準化 X_std = (X_raw - X_raw.mean(axis=0)) / X_raw.std(axis=0, ddof=1) y_std = (y_raw - y_raw.mean()) / y_raw.std(ddof=1) model_std = sm.OLS(y_std, sm.add_constant(X_std)).fit() model_raw = sm.OLS(y_raw, sm.add_constant(X_raw)).fit() print("\n" + "=" * 60) print("■ OLS 重回帰(標準化)") print("=" * 60) print(f" N={N}, R²={model_std.rsquared:.4f}, Adj.R²={model_std.rsquared_adj:.4f}") print(f"\n {'変数':<22} {'標準化β':>8} {'p値':>10} {'有意':>6}") print(" " + "-" * 50) for name, b, p in zip(VARNAMES_JP, model_std.params[1:], model_std.pvalues[1:]): sig = '***' if p < 0.001 else '**' if p < 0.01 else '*' if p < 0.05 else 'n.s.' print(f" {name:<22} {b:>8.4f} {p:>10.4f} {sig:>6}") |
print 出力は、下の「相関行列」ステップの実行結果にまとめて表示しています(「■ OLS 重回帰(標準化)」の部分)。sm.add_constant(X) — 切片項(定数1の列)を先頭に追加。statsmodelsで必須。sm.OLS(y, X).fit() — 最小二乗法でモデルを推定。model.params, model.pvalues, model.conf_int() で結果取得。r, p = stats.pearsonr(...) — Pythonは複数戻り値を同時に受け取れる(タプルアンパック)。199 200 201 202 203 204 205 206 207 208 209 | # ── VIF 計算 ──────────────────────────────────────────────── print("\n" + "=" * 60) print("■ VIF(多重共線性診断)") print("=" * 60) X_vif = sm.add_constant(X_raw) vifs = [variance_inflation_factor(X_vif, i + 1) for i in range(X_raw.shape[1])] print(f"\n {'変数':<22} {'VIF':>8}") print(" " + "-" * 32) for name, v in zip(VARNAMES_JP, vifs): flag = ' ← 注意(>5)' if v > 5 else '' print(f" {name:<22} {v:>8.2f}{flag}") |
print 出力は、下の「相関行列」ステップの実行結果にまとめて表示しています(「■ VIF(多重共線性診断)」の部分)。sm.add_constant(X) — 切片項(定数1の列)を先頭に追加。statsmodelsで必須。x if cond else y は三項演算子。リスト内包表記と組み合わせると、forとifを1行で書けます。210 211 212 213 214 215 216 | # ── 相関行列 ──────────────────────────────────────────────── print("\n" + "=" * 60) print("■ 相関行列(ピアソン)") print("=" * 60) corr_cols = [YVAR] + VARNAMES_EN corr_matrix = df_ols[corr_cols].corr() print(corr_matrix.round(3)) |
■ 2022年度 有効データ: N=47都道府県
光熱費割合 年平均気温 総人口 消費支出(二人以上の世帯) 高齢化率 標準価格(平均価格)(住宅地)
count 47.00 47.00 47.00 47.00 47.00 47.00
mean 8.71 16.07 2658425.53 289630.36 31.35 53372.34
std 1.31 2.29 2793536.00 19186.91 3.27 61991.62
min 6.00 10.20 544000.00 245054.00 22.81 13200.00
25% 7.93 15.25 1046500.00 276834.50 29.85 25300.00
50% 8.45 16.40 1563000.00 287781.00 31.42 30800.00
75% 9.20 17.35 2655000.00 302255.00 33.72 55500.00
max 12.96 23.70 14038000.00 324793.00 38.60 389100.00
============================================================
■ OLS 重回帰(標準化)
============================================================
N=47, R²=0.7286, Adj.R²=0.6955
変数 標準化β p値 有意
--------------------------------------------------
年平均気温(℃) -0.7747 0.0000 ***
総人口(万人) -0.3882 0.0618 n.s.
消費支出(円) -0.4313 0.0000 ***
高齢化率(%) -0.1242 0.3560 n.s.
住宅地価格(円/m²) 0.1458 0.4536 n.s.
============================================================
■ VIF(多重共線性診断)
============================================================
変数 VIF
--------------------------------
年平均気温(℃) 1.44
総人口(万人) 6.17 ← 注意(>5)
消費支出(円) 1.26
高齢化率(%) 2.67
住宅地価格(円/m²) 5.61 ← 注意(>5)
============================================================
■ 相関行列(ピアソン)
============================================================
光熱費割合 年平均気温 総人口 消費支出(二人以上の世帯) 高齢化率 標準価格(平均価格)(住宅地)
光熱費割合 1.000 -0.682 -0.379 -0.405 0.494 -0.414
年平均気温 -0.682 1.000 0.061 -0.097 -0.378 0.189
総人口 -0.379 0.061 1.000 0.374 -0.699 0.895
消費支出(二人以上の世帯) -0.405 -0.097 0.374 1.000 -0.361 0.351
高齢化率 0.494 -0.378 -0.699 -0.361 1.000 -0.694
標準価格(平均価格)(住宅地) -0.414 0.189 0.895 0.351 -0.694 1.000df[col](1列)と df[[col1, col2]](複数列)でカッコの数が違います。リストを渡していると覚えるとミスを減らせます。217 218 219 220 221 222 223 | ts = df_b.groupby('年度').agg( 光熱費割合_mean=('光熱費割合', 'mean'), 光熱費割合_std =('光熱費割合', 'std'), ).reset_index() print("\n■ 光熱費割合 全国平均(年度別)") print(ts.round(3)) |
■ 光熱費割合 全国平均(年度別)
年度 光熱費割合_mean 光熱費割合_std
0 2012 8.029 0.966
1 2013 8.119 1.022
2 2014 8.275 1.145
3 2015 8.138 1.060
4 2016 7.542 0.964
5 2017 7.724 1.042
6 2018 7.902 1.010
7 2019 7.733 1.087
8 2020 7.938 1.049
9 2021 7.893 1.144
10 2022 8.707 1.314
11 2023 8.193 1.101df.groupby('列').apply(関数) — グループごとに関数を適用。時系列や地域別の集計でよく使います。[式 for x in リスト] はリスト内包表記。forループでappendする代わりに1行でリストを作れます。224 225 226 227 228 229 230 231 232 233 | x4 = df_ols['年平均気温'].values.astype(float) y4 = df_ols[YVAR].values.astype(float) pref = df_ols['都道府県'].values fig4, ax4 = plt.subplots(figsize=(9, 6)) # 散布図 scatter = ax4.scatter(x4, y4, c=y4, cmap='RdYlBu_r', s=80, edgecolors='gray', linewidths=0.5, alpha=0.85, zorder=3) plt.colorbar(scatter, ax=ax4, label='光熱費割合(%)', shrink=0.8) |
print はしません。データや図が裏で更新されただけ。次のステップへ進みましょう。fig, ax = plt.subplots(...) — 図全体(fig)と軸(ax)を作る定番。以降は ax.bar(...) 等で操作。s[:-n]「末尾n文字を除く」/s[n:]「先頭n文字を除く」。スライス [start:stop:step] はリスト・タプル・文字列共通の基本ワザです。234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 | # 回帰直線 z4 = np.polyfit(x4, y4, 1) xs4 = np.linspace(x4.min() - 0.5, x4.max() + 0.5, 100) ax4.plot(xs4, np.poly1d(z4)(xs4), 'r--', linewidth=1.8, alpha=0.8, label='線形回帰') r4, p4 = stats.pearsonr(x4, y4) # 主要都道府県にラベル label_prefs = ['北海道', '沖縄県', '東京都', '大阪府', '秋田県', '鹿児島県', '長野県'] for i, pname in enumerate(pref): if any(lp in pname for lp in label_prefs): ax4.annotate(pname, (x4[i], y4[i]), textcoords='offset points', xytext=(6, 3), fontsize=8.5, color='#333') ax4.set_xlabel('年平均気温(℃)', fontsize=12) ax4.set_ylabel('光熱費割合(光熱・水道費 / 消費支出 × 100, %)', fontsize=11) ax4.set_title( f'年平均気温と光熱費割合の関係(2022年度, N={N}都道府県)\n' f'r = {r4:.3f}(p = {p4:.4f})', fontsize=12, fontweight='bold') ax4.legend(fontsize=10) ax4.grid(True, alpha=0.3) plt.tight_layout() fig4.savefig(os.path.join(FIG_DIR, '2022_U5_5_fig4_scatter.png'), bbox_inches='tight', dpi=150) plt.close(fig4) print("図4保存: 2022_U5_5_fig4_scatter.png") print("\n" + "=" * 60) print("全図の生成完了(4枚)") print(" fig1_ts.png : 光熱費割合の時系列(全国平均 2012-2023)") print(" fig2_corr.png : 相関ヒートマップ(光熱費と説明変数)") print(" fig3_coef.png : OLS標準化係数プロット") print(" fig4_scatter.png : 年平均気温 vs 光熱費割合 散布図") print("=" * 60) |
図4保存: 2022_U5_5_fig4_scatter.png ============================================================ 全図の生成完了(4枚) fig1_ts.png : 光熱費割合の時系列(全国平均 2012-2023) fig2_corr.png : 相関ヒートマップ(光熱費と説明変数) fig3_coef.png : OLS標準化係数プロット fig4_scatter.png : 年平均気温 vs 光熱費割合 散布図 ============================================================
stats.pearsonr(x, y) — Pearson相関係数 r と p値を同時に返します。np.cumsum(arr) は累積和、np.linspace(a, b, n) は「aからbを等間隔でn個」。NumPyの定石です。原論文の分析の中核は、ふつうの重回帰ではなく LASSO 回帰です。説明変数の候補が多いとき、普通の線形回帰は過学習しやすい——そこで原論文は、損失関数に L1 正則化項を加えることで過学習を抑えつつ、重要でない変数の係数を自動的に 0 に潰す LASSO を採用しました。
電力需要量そのものは SSDSE に無いため、家計側のプロキシ(光熱・水道費割合、2022年度・47都道府県)を目的変数に、気候・人口・経済など 10 個の候補変数から LASSO に変数を選ばせます。λ は原論文と同様、クロスバリデーションで選びます。
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 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 | import numpy as np import pandas as pd from sklearn.linear_model import LassoCV, Lasso from sklearn.preprocessing import StandardScaler df_raw = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', header=1) df = df_raw[(df_raw['年度'] == 2022) & df_raw['地域コード'].str.match(r'^R\d{5}$', na=False)].reset_index(drop=True) # 目的変数: 光熱・水道費割合(電力需要の家計側プロキシ) y = (df['光熱・水道費(二人以上の世帯)'] / df['消費支出(二人以上の世帯)'] * 100).values # 説明変数: 気候・人口・経済・住宅など 10 変数 X = pd.DataFrame({ '年平均気温': pd.to_numeric(df['年平均気温'], errors='coerce'), '降水量': pd.to_numeric(df['降水量(年間)'], errors='coerce'), '高齢化率': df['65歳以上人口'] / df['総人口'] * 100, '人口密度代理(住宅地価格)': pd.to_numeric(df['標準価格(平均価格)(住宅地)'], errors='coerce') / 10000, '持家延べ面積': pd.to_numeric(df['着工新設持家床面積'], errors='coerce') / pd.to_numeric(df['着工新設持家数'], errors='coerce'), '消費支出': df['消費支出(二人以上の世帯)'] / 100000, '合計特殊出生率': pd.to_numeric(df['合計特殊出生率'], errors='coerce'), '転入率': df['転入者数(日本人移動者)'] / df['総人口'] * 100, '宿泊施設数': pd.to_numeric(df['旅館営業施設数(ホテルを含む)'], errors='coerce') / df['総人口'] * 10000, 'ごみ排出量': pd.to_numeric(df['1人1日当たりの排出量'], errors='coerce'), }) # LASSO は変数のスケールに敏感 → 標準化してから使う(原論文と同じ発想) scaler = StandardScaler() Xs = scaler.fit_transform(X) # λ(正則化の強さ)は 5-fold クロスバリデーションで選ぶ lasso = LassoCV(cv=5, random_state=0).fit(Xs, y) print(f'CV で選ばれた λ (alpha) = {lasso.alpha_:.4f}') print(f'決定係数 R2 = {lasso.score(Xs, y):.3f}') print() print('=== LASSO の標準化係数(0 になった変数は「選ばれなかった」) ===') for name, c in sorted(zip(X.columns, lasso.coef_), key=lambda t: -abs(t[1])): mark = '← 選択' if abs(c) > 1e-10 else '(脱落)' print(f' {name:16s} {c:+.4f} {mark}') # λ を強くすると係数が 0 に潰れていく(LASSO の変数選択機能) print() for a in [0.1, 0.3]: m = Lasso(alpha=a).fit(Xs, y) kept = [(n, c) for n, c in zip(X.columns, m.coef_) if abs(c) > 1e-10] print(f'λ = {a}: 残った変数 {len(kept)} 個 →', ', '.join(f'{n}({c:+.2f})' for n, c in sorted(kept, key=lambda t: -abs(t[1])))) |
LassoCV(cv=5) は λ の候補を自動生成して 5-fold CV で最良の λ を選んでくれます。LASSO の前には必ず標準化(StandardScaler)——スケールが違うと罰則のかかり方が不公平になります。SSDSE-B の都道府県パネルデータ(2012〜2023年)を用い、光熱・水道費割合を電力需要の代理変数として分析した結果:
| データ | 出典 |
|---|---|
| SSDSE-B-2026.csv(都道府県パネルデータ 2012〜2023年) | 独立行政法人統計センター SSDSE(教育用標準データセット) |
| 光熱・水道費(二人以上の世帯)、消費支出 | 家計調査(総務省統計局) |
| 年平均気温 | 気象庁 気候統計情報 |
| 住宅地価格(標準価格・平均) | 国土交通省 地価公示 |
実公的データ(SSDSE-B)のみ使用。合成データ・乱数データは使用していない。
統計分析の解釈で初心者がやりがちな勘違いをまとめます。特に「相関と因果の混同」「p値の過信」は研究現場でもよく起きる落とし穴です。本文を読む前にも、読んだ後にも、目を通してみてください。
統計の基本用語を初心者向けに解説します。本文中で見慣れない言葉が出てきたら、ここに戻って確認してください。
統計手法について「何のためか」「結果をどう読むか」を初心者向けに解説します。
以下は本論文では使われていませんが、同じデータ・問いを発展させる際の候補となる手法です。「なぜ本論文では未使用か」も添えます。
この研究をさらに発展させるための3つの方向性を示します。「今回わかったこと(X)」から「次に検証すべき仮説(Y)」を立て、「具体的に何をするか(Z)」まで考えてみましょう。
学んだだけでは身につきません。実際に手を動かすのが最強の学習方法です。本論文のスクリプトをベースに、以下のチャレンジに挑戦してみてください。難易度別に5つ用意しました。
本論文で学んだ手法は、研究の世界だけでなく、行政・企業・NPO の現場でも様々に活用されています。具体的なシーンを紹介します。
この論文を読んで初心者が抱きやすい疑問に、教育的観点から答えます。
この論文を理解できたか、クリックで確認しましょう。間違えても解説が出ます。
このページの分析は、この画面の中でそのまま実行できます。
Python をインストールする必要も、CSV をダウンロードする必要もありません。
下のセルの 「▶ ブラウザで実行」 を上から順に押すか、
「▶ 最初から全部実行」 で一気に流してください。
表示されるのは、本文の図表とまったく同じ計算の結果です
(動かしているのは再現スクリプト code/2022_U5_5_shorei.py そのもの)。
コードは書き換えられます。「✏️ 書き換える」で数字や変数を変えて ▶ を押すと、 結果がどう変わるかをその場で確かめられます(元に戻すボタンつき)。 ページを開いた時点で裏で実行環境の準備が始まっているので、待ち時間はほとんどありません。 初回だけ実行環境の取得にインターネット接続が必要です。