論文一覧に戻る 📚 用語解説(ジャストインタイム型データサイエンス教育)
Elastic Net
Elastic Net (L1+L2)
LASSO(L1)とRidge(L2)の両方のペナルティを混ぜた正則化。両者のいいとこ取り。
正則化L1+L2Elastic Netエラスティックネット

🔖 キーワード索引(拡張)

Elastic Net と関連する正則化用語:

L1 ペナルティ(Lasso) L2 ペナルティ(Ridge) α と l1_ratio グループ選択 座標降下法 正則化パス ElasticNetCV 標準化忘れ 情報リーク α の探索範囲 sklearn ElasticNet sklearn ElasticNetCV SGDRegressor (elasticnet) glmnet-python

💡 30秒で分かる結論

🍰 まずはやさしく

2つの手法を混ぜたハイブリッドな方法です。

データの分析をより安定させるために使います。

スマホのアプリで便利な機能だけを集める感覚です。

この手法の特徴とメリットについて読みます。

👁️ 直感 — Elastic Netは「Ridge + LASSO」

Elastic Net は Ridge と LASSO のペナルティを組み合わせた回帰:

$$ L = \sum_i (y_i - X_i \beta)^2 + \alpha [\rho \sum_j |\beta_j| + (1-\rho)/2 \sum_j \beta_j^2] $$

ρ で LASSO vs Ridge の比率を調整。 ρ=1 → LASSO、 ρ=0 → Ridge、 中間 → ハイブリッド。

利点

📍 あなたが今見ているもの

🍰 まずはやさしく

データ分析でよく使われる重要な考え方です。

正しい予測モデルを作るために活用します。

都道府県のデータ分析などで役立ちます。

定義から実装までを順番に学んでいきましょう。

論文中に 「Elastic Net」として登場する用語。

Elastic Net とは:LASSO(L1)とRidge(L2)の両方のペナルティを混ぜた正則化。両者のいいとこ取り。

本ページでは「elastic net」を扱う。 統計データ分析コンペティション (2026) の教材で、 SSDSE-B-2026 (47 都道府県 × 複数年 × 100 超列) の実データを使った再現可能な学習を目指す。

「elastic net」は統計・データサイエンスの体系における重要概念のひとつ。 本ページは「定義・直感・数式・実装・落とし穴・関連手法」の 6 視点で構成され、 各視点は独立して読めるが順序通り読むと体系的な理解が得られる。

🎨 直感で掴む — Lasso と Ridge の「いいとこ取り」

🍰 まずはやさしく

2つの手法のいいとこ取りをした方法です。

似たデータがあるときに正しく扱うためです。

部活のメンバーをグループごと選ぶ感覚です。

なぜこの方法が安定するのかを解説します。

SSDSE-B-2026 の 47 都道府県データには、 「総人口(A1101)」「15歳未満人口(A1301)」「65歳以上人口(A1303)」のように互いに相関 0.99 超の指標が並びます。 Lasso 単独では強相関のうち 1 つだけ残して他はゼロにしてしまい、 解が不安定になります。 Ridge は全変数を縮小するだけで選択しません。 Elastic Net は「グループ効果」を持ち、 強相関グループを丸ごと残すか丸ごと縮めるかのバランスをとる「Lasso と Ridge の融合」です。

📐 数式 — Elastic Net の目的関数

$$ \hat{\beta}^{EN} = \arg\min_{\beta} \left\{ \frac{1}{2n} \sum_{i=1}^{n} (y_i - x_i^\top \beta)^2 + \lambda \left( \alpha \|\beta\|_1 + \frac{1-\alpha}{2} \|\beta\|_2^2 \right) \right\} $$

$\alpha=1$ で純 Lasso、 $\alpha=0$ で純 Ridge、 $0 < \alpha < 1$ が Elastic Net。 sklearn 既定は $\alpha = 0.5$(混合比率)です。

🎨 概念図で押さえる

Elastic Net の核心を 3 つの図で押さえる。 ① L1+L2 罰則項の幾何学的形状、 ② α (Lasso 比率) の役割、 ③ 相関高変数群への扱いの違い。

L1+L2 罰則項の制約領域
図 A: 制約領域の形状 — Lasso は菱形 (角で疎解)、 Ridge は円 (縮小のみ)、 Elastic Net は両者の融合形。
α (Lasso 比率) の役割
図 B: α の役割 — 0 で Ridge、 1 で Lasso、 中間で両者のバランス。 sklearn 既定は 0.5。
相関高変数群への扱い (Lasso vs Elastic Net)
図 C: 相関群への扱い — Lasso は 1 つだけ残して不安定、 Elastic Net はグループとして扱う。

図のまとめ: Elastic Net は L1+L2 罰則を α で混ぜることで、 Lasso の変数選択能力と Ridge の安定性を両立する。 相関の強い変数群を「グループ」として丸ごと残せるのが最大の強み。

🚧 Elastic Net の前提条件・限界・誤解回避

Elastic Net を実際に SSDSE-B-2026 等の都道府県データへ適用する前に、 押さえておきたい前提と落とし穴を整理する。

前提条件 1: 標準化は必須

L1 罰則も L2 罰則も「係数の大きさ」に対して働くため、 スケールが大きい特徴量ほど罰則を受けにくい。 例えば「総人口 (百万単位)」と「失業率 (%)」を混ぜると、 失業率は係数が大きくならざるを得ず、 罰則の影響を強く受けて消えやすくなる。 必ず StandardScaler で平均 0・分散 1 にそろえる。

前提条件 2: ハイパーパラメータ 2 つ (α, l1_ratio) の CV 選択

Elastic Net は「正則化強度 α」と「L1 比率 l1_ratio」の 2 つを同時に決める必要がある。 sklearn では ElasticNetCV で交差検証付き探索を一発で行える。 l1_ratio は [0.1, 0.5, 0.7, 0.9, 0.99] あたりを試すのが定石。

限界 1: 因果効果の推定には使えない

Elastic Net は予測誤差を最小化する正則化推定であって、 偏微分係数を因果効果と読むためには、 さらに DML や Double Machine Learning などの手続きが必要。 「相関の強い 2 変数が両方残った」事実だけから因果関係を主張してはならない。

よくある誤解: 「Elastic Net は Lasso よりいつでも優れている」

特徴量が真にスパース (= ほとんどがゼロでよい) なケースでは、 純粋な Lasso (l1_ratio=1) の方が変数選択が明確になり解釈しやすい。 Elastic Net が真価を発揮するのは「相関が高い特徴量群が複数ある」場面。 データの相関構造を見てから決める。

→ Elastic Net は「Lasso と Ridge の中間」だが、 中間が常に最良ではない。 ElasticNetCV で α, l1_ratio を同時探索し、 線形モデルとの比較・特徴量の標準化を必ず行うこと。

📐 数式または定義

🍰 まずはやさしく

計算式で表したこの手法のルールです。

正確に分析を行うための基準になります。

テストの点数を計算する式のようなものです。

数式に登場する記号の意味を確認しましょう。

elastic net の定義や代表的な数式を以下に示す。 数式の各記号の意味は次節で言葉に翻訳する。

elastic net は文脈に応じて複数の定式化があるが、 教育目的では最も基本的な形を抑えることが重要。 具体的な値での計算例は後続セクションを参照。

$$\text{elastic net}: f(\mathbf{X}, \boldsymbol{\theta}) \to y$$

記号の対応はこうです。 $\beta$ は推定する回帰係数のベクトル、 $y$ は目的変数、 $X$ は説明変数の行列。 第 1 項 $\|y - X\beta\|_2^2$ はふつうの二乗誤差で、 当てはまりの良さを測ります。 $\alpha$ は罰則全体の強さで、 0 なら通常の最小二乗、 大きくするほど係数がゼロへ縮みます。 $\rho$(scikit-learn の l1_ratio)はL1 と L2 の配合比で、 $\rho = 1$ なら Lasso(係数がぴったり 0 になる)、 $\rho = 0$ なら Ridge(0 に近づくが 0 にはならない)。 $\|\beta\|_1 = \sum_j |\beta_j|$、 $\|\beta\|_2^2 = \sum_j \beta_j^2$ です。 $1/2$ が L2 側だけに付くのは、 微分したときに係数 2 が消えて式が綺麗になるためで、 意味上の非対称ではありません。

🔬 「Elastic Net」を深く理解する

🔬 「Elastic Net」を深く理解する

応用シナリオ

パラメータ調整

📝 練習問題 — 理解度チェック

  1. この用語の基本定義を、 自分の言葉で説明できますか?
  2. この手法が使われる典型的なシナリオを3つ挙げられますか?
  3. この手法の前提条件・仮定を確認できますか?
  4. 結果を解釈する際の注意点は何ですか?
  5. 類似手法との違いを説明できますか?
  6. Python(または他言語)で実装できますか?
  7. SSDSE データで応用例を作成できますか?

Elastic Net の罰則項は α [(1-l1_ratio)/2 · ||β||² + l1_ratio · ||β||₁] という二項式。 α が「罰則の総量」、 l1_ratio が「Lasso 寄りか Ridge 寄りか」を制御する。 ここでは SSDSE-B-2026(2023 年・47 都道府県)を使い、 相関 0.99 超の人口 3 変数に対して Lasso と Elastic Net の「グループ選択」の違いを実データで確かめる。

▶ ブロック 1: データ準備 + 標準化 + 相関確認

このコードでやること: SSDSE-B-2026 の 2023 年断面を読み込み、 「出生数(A4101)」を目的変数、 7 つの実在列を特徴量にする。 正則化は係数の大きさに働くので必ず標準化する。 総人口・15歳未満人口・65歳以上人口は互いに相関 0.99 超(強い多重共線性)。

📥 入力例(SSDSE-B-2026 の 2023 年・47 都道府県から 3 行) 都道府県 A1101(総人口) A1301(15歳未満人口) A1303(65歳以上人口) A4101(出生数) A5101(転入者数(日本人移動者)) 北海道 5,092,000 514,000 1,681,000 24,430 47,388 東京都 14,086,000 1,513,000 3,205,000 86,348 406,749 沖縄県 1,468,000 236,000 350,000 12,549 26,410 …(全 47 行)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
import pandas as pd, numpy as np
from sklearn.preprocessing import StandardScaler

# SSDSE-B-2026 は cp932・2 行目に日本語見出し行 → skiprows=[1]
df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
df = df[df['SSDSE-B-2026'] == 2023].copy()      # 2023 年・47 都道府県

feats = ['A1101', 'A1301', 'A1303', 'A5101', 'C3301', 'E3501', 'B4101']
#         総人口  15歳未満 65歳以上 転入者数 着工数  中学生  平均気温
X = StandardScaler().fit_transform(df[feats].values.astype(float))
y = df['A4101'].values.astype(float)            # 出生数

print(f'X: {X.shape}, y: {y.shape}')
print(f'corr(総人口, 15歳未満人口) = {np.corrcoef(df["A1101"], df["A1301"])[0, 1]:.3f}')
print(f'corr(総人口, 65歳以上人口) = {np.corrcoef(df["A1101"], df["A1303"])[0, 1]:.3f}')

📤 実行例:

X: (47, 7), y: (47,) corr(総人口, 15歳未満人口) = 0.997 corr(総人口, 65歳以上人口) = 0.991

💬 人口系 3 変数の相関は 0.99 超。 OLS では係数が不安定になり、 Lasso は 1 つを恣意的に選びがちな場面。 ここで Elastic Net の出番。

▶ ブロック 2: ElasticNetCV で α と l1_ratio を同時探索

このコードでやること: ElasticNetCV は α(罰則強度)と l1_ratio(Lasso 比率)を交差検証で同時に選ぶ。 「予測誤差最小」の観点で最良パラメータを取り出す。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
from sklearn.linear_model import ElasticNetCV

enet = ElasticNetCV(
    l1_ratio=[0.1, 0.3, 0.5, 0.7, 0.9, 1.0],
    alphas=[0.01, 0.1, 1, 10, 100],
    cv=5, max_iter=100000, random_state=0)
enet.fit(X, y)

print(f'best alpha    : {enet.alpha_:.1f}')
print(f'best l1_ratio : {enet.l1_ratio_:.1f}')
print(f'非ゼロ係数数  : {(enet.coef_ != 0).sum()} / {X.shape[1]}')

📤 実行例:

best alpha : 100.0 best l1_ratio : 1.0 非ゼロ係数数 : 3 / 7

💬 このデータ・この目的(予測誤差最小)では CV が l1_ratio=1.0=純 Lasso を選んだ。 「Elastic Net が常に Lasso より良い」わけではない実例(前掲の誤解回避と整合)。 では Elastic Net の価値はどこに出るのか? 次のブロックで係数の安定性を見る。

▶ ブロック 3: 相関群の係数を Lasso と比較(グループ効果)

このコードでやること: 同じ α=10 で Lasso と Elastic Net(l1_ratio=0.3)を当てはめ、 相関 0.99 超の人口 3 変数の係数を並べる。 Lasso は群のうち一部を恣意的に落とし符号も暴れるが、 Elastic Net は群をまとめて近い値で残す。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
from sklearn.linear_model import Lasso, ElasticNet

# 相関 0.99 超の人口 3 変数(総人口・15歳未満・65歳以上)に注目
lasso = Lasso(alpha=10, max_iter=100000).fit(X, y)
enet3 = ElasticNet(alpha=10, l1_ratio=0.3, max_iter=100000).fit(X, y)

for i, name in enumerate(['総人口', '15歳未満人口', '65歳以上人口']):
    print(f'  {name}: Lasso={lasso.coef_[i]:+.0f}  ElasticNet={enet3.coef_[i]:+.0f}')
print(f'人口群の生存数  Lasso={(lasso.coef_[:3] != 0).sum()}/3  '
      f'ElasticNet={(enet3.coef_[:3] != 0).sum()}/3')

📤 実行例:

総人口: Lasso=+0 ElasticNet=+1317 15歳未満人口: Lasso=+19544 ElasticNet=+1321 65歳以上人口: Lasso=-1800 ElasticNet=+1291 人口群の生存数 Lasso=2/3 ElasticNet=3/3

💬 Lasso は総人口をゼロに落とし、 15歳未満に係数を集中させ 65歳以上には負号すら付く(不安定)。 Elastic Net は 3 変数をほぼ等しい正の係数(約 +1300)でまとめて残す=これが「グループ効果」。 相関構造を壊さず解釈が安定する。

▶ ブロック 4: α を変えたときの群の生存数

このコードでやること: 罰則 α を強めながら、 人口 3 変数のうち何個が非ゼロで残るかを Lasso と Elastic Net で数える。 Elastic Net は α を強めても群を保ちやすい。

1
2
3
4
5
for a in [1, 10, 30]:
    l = Lasso(alpha=a, max_iter=100000).fit(X, y)
    e = ElasticNet(alpha=a, l1_ratio=0.3, max_iter=100000).fit(X, y)
    print(f'  alpha={a:>2}:  Lasso 人口群={(l.coef_[:3] != 0).sum()}/3   '
          f'ElasticNet 人口群={(e.coef_[:3] != 0).sum()}/3')

📤 実行例:

alpha= 1: Lasso 人口群=3/3 ElasticNet 人口群=3/3 alpha=10: Lasso 人口群=2/3 ElasticNet 人口群=3/3 alpha=30: Lasso 人口群=1/3 ElasticNet 人口群=3/3

💬 α を強めるほど Lasso は群を 3→2→1 と削るが、 Elastic Net は 3 のまま。 Lasso の変数選択能力と Ridge の安定性を両立するのが Elastic Net の核心。 相関構造のあるデータでの既定候補になる。

🧮 SSDSE-B 実値計算 — 47 都道府県の「出生数」を Elastic Net で予測

SSDSE-B-2026(2023 年・47 都道府県)の 7 つの実在列を説明変数、 出生数(A4101)を目的変数として、 OLS / Ridge / Lasso / Elastic Net を 5 分割 CV の RMSE で比較する。

🎯 解説: Elastic Net は L1(Lasso)と L2(Ridge)を組み合わせた正則化回帰。 変数選択(L1)と多重共線性対策(L2)を同時に扱う。 SSDSE-B-2026 の都道府県データで、 標準化 → CV 比較の型を実データで確認する。
📥 入力例(SSDSE-B-2026 の 2023 年・47 都道府県から 3 行) 都道府県 A1101(総人口) A1301(15歳未満人口) A1303(65歳以上人口) A4101(出生数) A5101(転入者数(日本人移動者)) 北海道 5,092,000 514,000 1,681,000 24,430 47,388 東京都 14,086,000 1,513,000 3,205,000 86,348 406,749 沖縄県 1,468,000 236,000 350,000 12,549 26,410 …(全 47 行)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import numpy as np, pandas as pd
from sklearn.linear_model import LinearRegression, Ridge, Lasso, ElasticNet
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline
from sklearn.model_selection import KFold, cross_val_score

df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
df = df[df['SSDSE-B-2026'] == 2023].copy()       # 2023 年・47 都道府県
feats = ['A1101', 'A1301', 'A1303', 'A5101', 'C3301', 'E3501', 'B4101']
X = df[feats].values.astype(float)
y = df['A4101'].values.astype(float)             # 出生数

cv = KFold(n_splits=5, shuffle=True, random_state=0)
models = {
    'OLS'           : LinearRegression(),
    'Ridge(a=1)'    : Ridge(alpha=1.0),
    'Lasso(a=1)'    : Lasso(alpha=1.0, max_iter=100000),
    'ENet(a=1,r=.5)': ElasticNet(alpha=1.0, l1_ratio=.5, max_iter=100000),
}
for name, m in models.items():
    pipe = Pipeline([('sc', StandardScaler()), ('m', m)])
    rmse = -cross_val_score(pipe, X, y, cv=cv,
                            scoring='neg_root_mean_squared_error').mean()
    print(f'{name:<16s} CV RMSE = {rmse:8.1f}')

📤 実行例:

OLS CV RMSE = 752.1 Ridge(a=1) CV RMSE = 1513.7 Lasso(a=1) CV RMSE = 786.6 ENet(a=1,r=.5) CV RMSE = 1957.0

💬 このデータは n=47・特徴量 7 と低次元なので、 予測 RMSE では正則化なしの OLS が最良(752)。 Elastic Net の真価は予測精度そのものより、 相関 0.99 超の人口 3 変数を「まとめて残す」係数の安定性にある(上の🔬グループ選択の実測を参照)。 高次元・強い多重共線性ほど正則化・Elastic Net の価値が出る。

🧮 数式に値を入れて手で計算する — Elastic Net 損失の完全展開

2 サンプル × 2 特徴量の小さな合成データで、 Elastic Net の目的関数 (二乗誤差 + L1 + L2 ペナルティ) を完全に展開する。 係数 β を固定して各構成要素の値を手計算し、 同じ計算を NumPy / scikit-learn で再現する。 これにより「正則化が最終ロスにどう寄与するか」を一行ずつ追える。

📐 用語の主要数式 (再掲)

$$ L(\beta) = \|y - X\beta\|_2^2 + \alpha \left[\rho \|\beta\|_1 + \frac{1-\rho}{2}\|\beta\|_2^2 \right] $$

α : 正則化強度、 ρ (sklearn の l1_ratio) : L1 / L2 の混合比。 ρ=1 で Lasso、 ρ=0 で Ridge、 ρ=0.5 が Elastic Net の典型値。

Step 1: データ準備 (合成、 多様な整数)

項目値
X (2 サンプル × 2 特徴)[[1, 2], [3, 4]]
y[5, 11]
β (検証する係数)[1, 1]
α (正則化強度)1
ρ (L1 比率)0.5

Step 2: 予測値と残差二乗和 (RSS)

項目計算結果
ŷ₁ = 1·1 + 2·11 + 23
ŷ₂ = 3·1 + 4·13 + 47
残差 r₁ = y₁ - ŷ₁5 - 32
残差 r₂ = y₂ - ŷ₂11 - 74
RSS = ||y - Xβ||²2² + 4² = 4 + 1620

Step 3: L1 / L2 ペナルティ

項目計算結果
L1 ノルム ||β||₁|1| + |1|2
L2² ノルム ||β||²1² + 1²2
L1 寄与 ρ · ||β||₁0.5 · 21.0
L2 寄与 (1-ρ)/2 · ||β||²0.5 / 2 · 2 = 0.25 · 20.5
ペナルティ合計 α·[…]1 · (1.0 + 0.5)1.5

Step 4: 最終ロス

項目計算結果
L(β) = RSS + ペナルティ20 + 1.521.5
RSS 占有率20 / 21.593.0%
ペナルティ占有率1.5 / 21.57.0%

🐍 同じ計算を Python で再現

 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
import numpy as np

# Step 1: 合成データ
X = np.array([[1, 2], [3, 4]])
y = np.array([5, 11])
beta = np.array([1, 1])
alpha = 1.0
rho = 0.5  # sklearn の l1_ratio

# Step 2: 予測値・残差・RSS
y_hat = X @ beta
residual = y - y_hat
rss = (residual ** 2).sum()
print(f'y_hat   = {y_hat}')
print(f'残差    = {residual}')
print(f'RSS     = {rss}')

# Step 3: L1 / L2 ペナルティ
l1 = np.abs(beta).sum()
l2_sq = (beta ** 2).sum()
penalty = alpha * (rho * l1 + (1 - rho) / 2 * l2_sq)
print(f'||β||1  = {l1}')
print(f'||β||2² = {l2_sq}')
print(f'penalty = {penalty}')

# Step 4: 最終ロス
loss = rss + penalty
print(f'Loss    = {loss}')

📤 実行結果

y_hat = [3 7] 残差 = [2 4] RSS = 20 ||β||1 = 2 ||β||2² = 2 penalty = 1.5 Loss = 21.5

💬 手計算 (Step 4: Loss = 21.5) と Python 出力 (21.5) が完全一致。 RSS が 20、 ペナルティが 1.5 で合計 21.5。 ペナルティ比率は約 7% で、 β を 1 に近づけるほど RSS は減るがペナルティは増える「綱引き」が起きていることが数値で確認できる。 α や ρ を変えれば最適 β がどう動くかを LASSO / Ridge と比較する出発点になる。

🐍 Python での Elastic Net

🎯 解説: Elastic Net は L1(Lasso)と L2(Ridge)を組み合わせた正則化回帰。 変数選択(L1)と多重共線性対策(L2)を同時実現。 SSDSE-B-2026 の高次元都道府県データで安定した推定が可能。
📥 入力例(SSDSE-B-2026 の 2023 年・47 都道府県から 3 行) 都道府県 総人口 出生数 死亡数 婚姻件数 食料費 消費支出(二人以上の世帯) 北海道 5,092,000 24,430 75,120 17,281 74,341 296,888 東京都 14,086,000 86,348 137,241 71,774 97,776 341,320 沖縄県 1,468,000 12,549 15,110 6,316 73,453 251,222 …(全 47 行)
 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
# ── Elastic Net の例で使うデータを用意します(47 都道府県 × 複数の説明変数)──
import numpy as np
import pandas as pd
from sklearn.preprocessing import StandardScaler

_df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', header=1)
_df = _df[_df['地域コード'].astype(str).str.match(r'^R\d{5}$', na=False)].copy()
_df['年度'] = pd.to_numeric(_df['年度'], errors='coerce')
_df = _df[_df['年度'] == _df['年度'].max()]
_feats = ['総人口', '出生数', '死亡数', '婚姻件数', '食料費(二人以上の世帯)']
for _c in _feats + ['消費支出(二人以上の世帯)']:
    _df[_c] = pd.to_numeric(_df[_c], errors='coerce')
_df = _df.dropna(subset=_feats + ['消費支出(二人以上の世帯)'])

X = _df[_feats].to_numpy(dtype=float)
y = _df['消費支出(二人以上の世帯)'].to_numpy(dtype=float)
Xs = StandardScaler().fit_transform(X)
ys = (y - y.mean()) / y.std()
feature_names = _feats

from sklearn.linear_model import ElasticNet, ElasticNetCV
from sklearn.preprocessing import StandardScaler

scaler = StandardScaler()
X_std = scaler.fit_transform(X)

# α と l1_ratio を指定
en = ElasticNet(alpha=0.1, l1_ratio=0.5).fit(X_std, y)
print(f'係数: {en.coef_}')

# CV で両方を自動選択
en_cv = ElasticNetCV(l1_ratio=[0.1, 0.5, 0.7, 0.9, 0.95, 0.99, 1.0], cv=5).fit(X_std, y)
print(f'最適α: {en_cv.alpha_}, 最適l1_ratio: {en_cv.l1_ratio_}')
📤 実行例(実測) 係数: [ 1483.84379321 -6788.27563959 1226.06514038 -361.23060265 18273.2594074 ] 最適α: 1738.68872918638, 最適l1_ratio: 1.0
💬 読み方: 最適α が 1738.7 と大きいのは、y(消費支出)を円のまま入れているため。罰則の強さは y の単位に比例して見積もられるので、X だけ標準化しても α の値そのものは比べられない。CV が選んだ l1_ratio は 1.0(純 Lasso)で、α=0.1 の当てはめでは食料費が +18,273 円/SD と最大、出生数が -6,788 円/SD と負になっている。

🚧 落とし穴と注意点

🐍 Python 実装バリエーション — sklearn / SGDRegressor / glmnet-python

1. sklearn.linear_model.ElasticNet(座標降下法)

🎯 解説: Elastic Net は L1(Lasso)と L2(Ridge)を組み合わせた正則化回帰。 変数選択(L1)と多重共線性対策(L2)を同時実現。 SSDSE-B-2026 の高次元都道府県データで安定した推定が可能。
1
2
3
4
5
6
from sklearn.linear_model import ElasticNet
en = ElasticNet(alpha=0.1, l1_ratio=0.5,
                max_iter=20000, tol=1e-5, random_state=0,
                selection='random')  # 'cyclic' or 'random'
en.fit(Xs, ys)
print('非ゼロ係数:', np.sum(en.coef_ != 0))
📤 実行例(実測) 非ゼロ係数: 2

💬 5 変数のうち残ったのは出生数(-0.065)と食料費(0.640)の 2 本。総人口・出生数・死亡数・婚姻件数は互いの相関が 0.96〜0.995 の「人口規模グループ」で、l1_ratio=0.5 なら群ごと残る grouping effect が期待されるが、α=0.1 ではまだ L1 側が勝って出生数 1 本で代表させている。selection を 'cyclic' に変えても係数は同じで、違うのは収束までの反復回数(random 18 回、cyclic 13 回)だけ。

⚠️ Elastic Net の落とし穴 — 6 つの典型ミス

① 標準化せずに ENet を回す

L1/L2 正則化は係数の絶対値・二乗値にペナルティを課すので、 スケールが大きい変数(例:総人口 10⁶)の係数は強く罰せられ、 スケールが小さい変数(例:失業率 0.03)は罰せられない。 結果としてスケールが大きい変数だけが落ちて選ばれない。 必ず StandardScaler を Pipeline に入れ、 fit/transform をクロスバリデーションの fold 内で完結させる。 標準化なしの ENet は意味のある係数解釈もできない。

② α と l1_ratio の探索範囲が狭い

α は対数スケールで {0.001, 0.01, 0.1, 1, 10} の 5 段階を基本にすべきで、 [0.1, 1, 10] の 3 点だけでは粗すぎる。 同様に l1_ratio も [0.1, 0.3, 0.5, 0.7, 0.9, 0.95, 0.99, 1.0] のように 1.0 寄りを密にする(Lasso との比較が欲しいので)。 ElasticNetCV は内部で alphas を自動生成するが、 範囲指定は明示した方が再現性が高い。 探索範囲の境界に最適値が当たったら範囲を広げ直す。

③ ElasticNetCV の最適 α と「正則化パス上の good α」を混同

CV で最小 MSE を与える α は「最尤」だが、 1-SE rule(CV の最小値から SE 1 個分以内で最大の α、 つまり最大限スパースな選択)を採るのも標準的な慣行。 glmnet の R 版では既定で 1-SE rule が出る。 sklearn ElasticNetCV は最尤のみなので、 1-SE を欲しいときは自前で計算する。 解釈性 vs 予測性能のトレードオフでどちらを採るか決める。

④ 係数の有意性検定をしてしまう

Lasso / ENet の係数には標準的な t 検定や信頼区間は適用できない。 OLS の漸近正規理論は罰則項の存在で崩れる。 有意性が必要なら post-selection inference(Lockhart et al. の covariance test)、 selectiveInference パッケージ、 bootstrap、 もしくはベイズ Lasso を使う。 「ENet で残った変数 = 重要」は強い主張なので、 安定性選択(Stability Selection)でブートストラップを併用するのが安全。

⑤ CV 分割の前に標準化やターゲット変換をする

「全データで StandardScaler.fit_transform → 5-fold CV」とすると、 テスト fold の統計量が訓練側に漏れる(データリーク)。 結果として CV スコアが楽観的に見える。 必ず Pipeline で StandardScaler を組み込み、 cross_val_score 内で各 fold ごとに fit を完結。 また、 y のターゲット変換(log 変換等)も fold 内でやるのが理想。 これは sklearn Pipeline の最大の存在意義の一つ。

⑥ 「相関の強い変数群はまとめて残る」を期待しすぎる

ENet の売り文句の一つは「Lasso と違って相関の強い変数を全部残せる(grouping effect)」だが、 これは l1_ratio < 1 のときに限る。 l1_ratio が 0.95 以上だとほぼ Lasso と同じで、 相関グループから 1 つだけ選ぶ挙動が再現される。 グループ選択を強く期待するなら l1_ratio = 0.3〜0.5 程度に下げる。 さらに本格的にはグループ Lasso(pyglmnet)を使う方が直接的。

🗺️ 概念マップ — 3つの視点で体系を理解する

Elastic Net がデータサイエンスの体系の中でどこに位置するかを、 3つの異なる視点で可視化します。 同じ情報でも見方を変えると気付きが変わります。

📍 体系階層のパス

🌐 統計・データサイエンス › 関連・回帰 › 回帰 › Elastic Net

① 🔗 関係マップ — 「他の手法とどう繋がっているか」

中心に Elastic Net を置き、 そこから Ridge回帰・LASSO・多重共線性対策 計 3 個の用語へ、 前提・兄弟・発展形といった関係を矢印で結んでいます。 線がどちら向きかを見れば、 先に読むべき用語とあとから読む用語が分かります。 ノードはドラッグで動かせ、 ホイールでズーム、 クリックでその用語のページへ遷移します。

凡例:現在の用語上位カテゴリ兄弟(並列)前提発展形応用先2階層先

② ⭕ 包含マップ — 「どのカテゴリに含まれているか」

大きな円が小さな円を包含する Circle Packing 図。 「Elastic Net」は緑色でハイライト。

📍現在地:統計・データサイエンス

③ 🌳 ツリーマップ — 「面積で見るボリューム比較」

長方形を入れ子に分割した Treemap 図。 各分野の規模感を面積で比較。 「Elastic Net」は緑色でハイライト。

🎯 3つのマップの使い分け

マップ 分かること こんな時に見る
🔗 関係マップ手法間の横の関係(前提→発展→応用)「次に何を学べばよい?」 学習順序の判断
⭕ 包含マップ分類体系の入れ子構造(上位⊃下位)「この手法はどんなジャンルに属する?」
🌳 ツリーマップ分野の規模比較(面積=ボリューム)「データサイエンス全体の俯瞰像」

💡 3 つの図は同じ位置関係を別の角度から描いています。 関係マップは Elastic Net の隣に何が並ぶかを、 包含マップとツリーマップは 統計・データサイエンス → 回帰 → Elastic Net という入れ子の位置を示します。 「回帰には他に何があったか」を思い出したいときは包含マップを、 「次に何を読むか」を決めたいときは関係マップを開いてください。

🔗 隣接手法への橋渡し

Elastic Net は単独の回帰モデルではなく、 Lasso (L1) と Ridge (L2) の中間で変数選択と係数縮小を両立させる正則化族の一員である。 多重共線性 ・特徴量数 ≫ サンプル数 の状況で α と l1_ratio を交差検証で決める。

Elastic Net は「L1 と L2 を混合した正則化回帰」で、 上流の標準化と多重共線性チェックを前提とし、 並列の Lasso (L1)・Ridge (L2) と α・l1_ratio で連続的に切り替え、 下流の交差検証で最適パラメータを決定する。

🌳 手法選択フロー

「elastic net」を含む手法選択は、 データの性質・分析目的・運用制約の 3 軸で決まる。 以下に典型シナリオと対応する手法の組合せを示す。

典型シナリオ別の手法選択

シナリオ重視する観点候補手法
データが大規模 (n が多い、 高次元)計算コスト / メモリ / 並列化が必要ビジネス理解 等
外れ値が多い / ノイズあり頑健性 / 外れ値除去 / 中央値ベースデータ理解 等
解釈性を重視する線形 / 木構造 / ルールベースデータ準備 等
予測精度を最優先するアンサンブル / ハイパラ調整 / 交差検証モデリング 等
データが少ない正則化 / ベイズ / 転移学習 / 簡素なモデルデータアナリスト 等
リアルタイム/オンライン処理ストリーミング / 軽量モデル / 逐次更新データサイエンティスト 等

選んだ後の検証ステップ

  1. 前処理確認: 標準化 / 欠損処理 / カテゴリ変数のエンコード が適切か
  2. ハイパラ調整: 交差検証で安定する値を選ぶ
  3. 性能評価: タスクに応じた指標 (RMSE / Accuracy / F1 / AUC / シルエット等) を複数併用
  4. 頑健性チェック: 別手法 / 別シードで結果が大きく違わないか確認
  5. 解釈: 結果を分野知識と照らし合わせ、 意味のある発見になっているか検討

ElasticNetCV で α と l1_ratio を同時最適化

🎯 解説: Elastic Net は L1(Lasso)と L2(Ridge)を組み合わせた正則化回帰。 変数選択(L1)と多重共線性対策(L2)を同時実現。 SSDSE-B-2026 の高次元都道府県データで安定した推定が可能。
📥 入力例(SSDSE-B-2026 の 2023 年・47 都道府県から 3 行) 都道府県 総人口 出生数 死亡数 婚姻件数 食料費 消費支出(二人以上の世帯) 北海道 5,092,000 24,430 75,120 17,281 74,341 296,888 東京都 14,086,000 86,348 137,241 71,774 97,776 341,320 沖縄県 1,468,000 12,549 15,110 6,316 73,453 251,222 …(全 47 行)
 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
# ── Elastic Net の例で使うデータを用意します(47 都道府県 × 複数の説明変数)──
import numpy as np
import pandas as pd
from sklearn.preprocessing import StandardScaler

_df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', header=1)
_df = _df[_df['地域コード'].astype(str).str.match(r'^R\d{5}$', na=False)].copy()
_df['年度'] = pd.to_numeric(_df['年度'], errors='coerce')
_df = _df[_df['年度'] == _df['年度'].max()]
_feats = ['総人口', '出生数', '死亡数', '婚姻件数', '食料費(二人以上の世帯)']
for _c in _feats + ['消費支出(二人以上の世帯)']:
    _df[_c] = pd.to_numeric(_df[_c], errors='coerce')
_df = _df.dropna(subset=_feats + ['消費支出(二人以上の世帯)'])

X = _df[_feats].to_numpy(dtype=float)
y = _df['消費支出(二人以上の世帯)'].to_numpy(dtype=float)
Xs = StandardScaler().fit_transform(X)
ys = (y - y.mean()) / y.std()
feats = _feats            # 以降 feats という名前で使う
feature_names = _feats

from sklearn.linear_model import ElasticNetCV

enet_cv = ElasticNetCV(
    l1_ratio=[.1, .3, .5, .7, .9, .95, .99, 1.0],
    alphas=np.logspace(-3, 1, 50),
    cv=5, max_iter=10000, random_state=0)
enet_cv.fit(Xs, ys)
print(f'best α       : {enet_cv.alpha_:.4f}')
print(f'best l1_ratio: {enet_cv.l1_ratio_:.2f}')
print(f'採択された変数(非ゼロ係数 / 全変数)= '
      f'{np.sum(enet_cv.coef_ != 0)} / {len(feats)}')

coef_df = pd.DataFrame({'feature': feats, 'coef': enet_cv.coef_}) \
            .sort_values('coef', key=abs, ascending=False)
print(coef_df)
📤 実行例(実測) best α : 0.0754 best l1_ratio: 1.00 採択された変数(非ゼロ係数 / 全変数)= 2 / 5 feature coef 4 食料費(二人以上の世帯) 0.625458 1 出生数 -0.033134 0 総人口 -0.000000 2 死亡数 -0.000000 3 婚姻件数 -0.000000

💬 CV が選んだのは l1_ratio=1.00、つまり純粋な Lasso で、Elastic Net の混合は採用されなかった。ys も Xs も標準化してあるので係数は SD 単位で読め、食料費が 1 SD 高い県は消費支出が 0.63 SD 高い。出生数の -0.033 は人口規模グループの代表として残っただけの小さな値で、「出生数が多いと消費が減る」と因果的に読むのは誤り。-0.000000 の 3 変数は罰則で落ちたもので、効果が無いと証明されたわけではない。

2. ElasticNetCV — α・l1_ratio の同時探索

🎯 解説: Elastic Net は L1(Lasso)と L2(Ridge)を組み合わせた正則化回帰。 変数選択(L1)と多重共線性対策(L2)を同時実現。 SSDSE-B-2026 の高次元都道府県データで安定した推定が可能。
📥 入力例(SSDSE-B-2026 の 2023 年・47 都道府県から 3 行) 都道府県 総人口 出生数 死亡数 婚姻件数 食料費 消費支出(二人以上の世帯) 北海道 5,092,000 24,430 75,120 17,281 74,341 296,888 東京都 14,086,000 86,348 137,241 71,774 97,776 341,320 沖縄県 1,468,000 12,549 15,110 6,316 73,453 251,222 …(全 47 行)
 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
# ── Elastic Net の例で使うデータを用意します(47 都道府県 × 複数の説明変数)──
import numpy as np
import pandas as pd
from sklearn.preprocessing import StandardScaler

_df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', header=1)
_df = _df[_df['地域コード'].astype(str).str.match(r'^R\d{5}$', na=False)].copy()
_df['年度'] = pd.to_numeric(_df['年度'], errors='coerce')
_df = _df[_df['年度'] == _df['年度'].max()]
_feats = ['総人口', '出生数', '死亡数', '婚姻件数', '食料費(二人以上の世帯)']
for _c in _feats + ['消費支出(二人以上の世帯)']:
    _df[_c] = pd.to_numeric(_df[_c], errors='coerce')
_df = _df.dropna(subset=_feats + ['消費支出(二人以上の世帯)'])

X = _df[_feats].to_numpy(dtype=float)
y = _df['消費支出(二人以上の世帯)'].to_numpy(dtype=float)
Xs = StandardScaler().fit_transform(X)
ys = (y - y.mean()) / y.std()
feats = _feats            # 以降 feats という名前で使う
feature_names = _feats

from sklearn.linear_model import ElasticNetCV
# alpha の候補は「本数」ではなく配列で渡すと、どの版の scikit-learn でも通る
encv = ElasticNetCV(l1_ratio=[.1, .5, .7, .9, .95, .99, 1.0],
                    alphas=np.logspace(-3, 1, 100), cv=10, random_state=0)
encv.fit(Xs, ys)
print(encv.alpha_, encv.l1_ratio_)
📤 実行例(実測) 0.01788649529057435 0.95

💬 同じデータでも cv=10 にすると α=0.0179、l1_ratio=0.95 が選ばれ、cv=5 の α=0.0754・l1_ratio=1.00 より正則化が弱く Ridge 寄りになる。分割の数を変えただけで最適点が動くのは、CV 誤差の谷が平らで候補間の差が小さいため。max_iter 既定の 1000 回では小さい α で収束しきらず ConvergenceWarning が大量に出るが、max_iter=100000 で解き直しても α と l1_ratio は同じで、係数は出生数 -0.206・食料費 0.798 の 2 本になる。

正則化パス(α を変えたときの係数の挙動)

🎯 解説: Elastic Net は L1(Lasso)と L2(Ridge)を組み合わせた正則化回帰。 変数選択(L1)と多重共線性対策(L2)を同時実現。 SSDSE-B-2026 の高次元都道府県データで安定した推定が可能。
📥 入力例(SSDSE-B-2026 の 2023 年・47 都道府県から 3 行) 都道府県 総人口 出生数 死亡数 婚姻件数 食料費 消費支出(二人以上の世帯) 北海道 5,092,000 24,430 75,120 17,281 74,341 296,888 東京都 14,086,000 86,348 137,241 71,774 97,776 341,320 沖縄県 1,468,000 12,549 15,110 6,316 73,453 251,222 …(全 47 行)
 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
# ── Elastic Net の例で使うデータを用意します(47 都道府県 × 複数の説明変数)──
import numpy as np
import pandas as pd
from sklearn.preprocessing import StandardScaler

_df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', header=1)
_df = _df[_df['地域コード'].astype(str).str.match(r'^R\d{5}$', na=False)].copy()
_df['年度'] = pd.to_numeric(_df['年度'], errors='coerce')
_df = _df[_df['年度'] == _df['年度'].max()]
_feats = ['総人口', '出生数', '死亡数', '婚姻件数', '食料費(二人以上の世帯)']
for _c in _feats + ['消費支出(二人以上の世帯)']:
    _df[_c] = pd.to_numeric(_df[_c], errors='coerce')
_df = _df.dropna(subset=_feats + ['消費支出(二人以上の世帯)'])

X = _df[_feats].to_numpy(dtype=float)
y = _df['消費支出(二人以上の世帯)'].to_numpy(dtype=float)
Xs = StandardScaler().fit_transform(X)
ys = (y - y.mean()) / y.std()
feats = _feats            # 以降 feats という名前で使う
feature_names = _feats

import matplotlib.pyplot as plt
from sklearn.linear_model import enet_path

alphas, coefs, _ = enet_path(Xs, ys, l1_ratio=.5,
                             alphas=np.logspace(-3, 1, 100))
fig, ax = plt.subplots(figsize=(10, 5.5))
for j, name in enumerate(feats):
    ax.plot(alphas, coefs[j], label=name)
ax.set_xscale('log')
ax.set_xlabel('α (対数目盛)')
ax.set_ylabel('係数')
ax.set_title('Elastic Net の係数パス (l1_ratio=0.5)')
ax.legend(fontsize=9)
ax.grid(alpha=0.3)
plt.tight_layout()
plt.savefig('enet_path.png', dpi=120)
print('saved: enet_path.png')
📤 実行例(実測) saved: enet_path.png
💬 読み方: α を 10 から小さくしていくと、最初に食料費が α≈1.29 で 0 から離れ、次に出生数が α≈0.14 で加わる。死亡数・総人口・婚姻件数が入ってくるのは α≈0.018 以下で、α=0.001 では総人口 +2.14・出生数 -2.27 と大きな逆符号の係数が並ぶ。人口規模の 4 変数は相関 0.96 超なので、罰則が弱まると互いに打ち消し合う係数になる。

3. SGDRegressor で大規模データに対応

🎯 解説: 標準化済みの Xs, ys(2023 年度 47 都道府県、説明変数 5 個・目的変数は消費支出)に、確率的勾配降下法の SGDRegressor で Elastic Net 罰則(alpha=0.001, l1_ratio=0.15)を付けて当てはめ、係数・反復回数・R² を出す。
1
2
3
4
5
6
7
8
9
10
11
from sklearn.linear_model import SGDRegressor
# learning_rate='optimal' は alpha が小さいと歩幅が大きすぎて係数が発散するので、
# eta0=0.01 から徐々に歩幅を縮める 'invscaling' を使う
sgd = SGDRegressor(loss='squared_error', penalty='elasticnet',
                   alpha=0.001, l1_ratio=0.15,
                   learning_rate='invscaling', eta0=0.01,
                   max_iter=2000, tol=1e-6, random_state=0)
sgd.fit(Xs, ys)
for f, c in zip(feature_names, sgd.coef_):
    print(f'{f:<16s} {c:+.3f}')
print(f'反復回数 = {sgd.n_iter_}, R² = {sgd.score(Xs, ys):.3f}')
📤 実行例(実測) 総人口 +0.006 出生数 -0.190 死亡数 +0.001 婚姻件数 -0.050 食料費(二人以上の世帯) +0.829 反復回数 = 147, R² = 0.497

💬 SGD は 147 回で止まり、R² = 0.497 のうち大半を食料費(+0.829)が担っていて、人口系 4 変数の係数は −0.190〜+0.006 とほぼ 0 に近い。同じ alpha=0.001, l1_ratio=0.15 を座標降下法の ElasticNet で解くと、総人口 +1.952・出生数 −2.188 のように相関 0.99 の人口系どうしが大きく打ち消し合う解になり R² は 0.538 まで上がる。SGD の係数が小さいのは、小さな歩幅で少しずつ進むうちに収束判定で止まったためで、罰則だけで決まった最適解ではない。食料費 1 本の単回帰でも R² は 0.461(相関 0.679)あるので、残りの 0.04 ほどを人口系が説明している、と読むのが妥当。

4. glmnet-python(R 互換、 1-SE rule 内蔵)

🎯 解説: R の glmnet と同じ実装を Python から呼び、10 分割 CV で CV 誤差最小の λ と 1-SE ルールの λ を出す(python-glmnet が必要。この環境では実行していない)。
1
2
3
4
5
6
7
from glmnet import ElasticNet as GlmnetEN
# R の glmnet と等価
g = GlmnetEN(alpha=0.5, n_splits=10, scoring='mean_squared_error',
             random_state=0)
g.fit(Xs, ys)
print('λ_min (CV最小):', g.lambda_max_)
print('λ_1se (1-SE) :', g.lambda_best_)
💬 読み方: glmnet(python-glmnet)はこの環境に入っていないので実測は無い。lambda_max_ が CV 誤差が最小になる λ(R の lambda.min)、lambda_best_ が最小値から 1 標準誤差以内で最も大きい λ(lambda.1se)で、変数名と R の名前が逆に見えるので取り違えに注意。glmnet の alpha=0.5 は sklearn の l1_ratio=0.5 にあたる。

5. Pipeline + GridSearchCV で安全な探索

🎯 解説: 2023 年度 47 都道府県の 5 変数(総人口・出生数・死亡数・婚姻件数・食料費)から消費支出を予測する Elastic Net を、標準化と一体にした Pipeline に入れ、α(30 通り)と l1_ratio(8 通り)を 5 分割 CV の RMSE で選ぶ。
📥 入力例(SSDSE-B-2026 の 2023 年度・47 都道府県から 3 行。説明変数 5 個と目的変数の消費支出) 都道府県 総人口 出生数 死亡数 婚姻件数 食料費 消費支出(二人以上の世帯) 北海道 5,092,000 24,430 75,120 17,281 74,341 296,888 東京都 14,086,000 86,348 137,241 71,774 97,776 341,320 沖縄県 1,468,000 12,549 15,110 6,316 73,453 251,222 …(全 47 行)
 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
# ── Elastic Net の例で使うデータを用意します(47 都道府県 × 複数の説明変数)──
import numpy as np
import pandas as pd
from sklearn.preprocessing import StandardScaler

_df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', header=1)
_df = _df[_df['地域コード'].astype(str).str.match(r'^R\d{5}$', na=False)].copy()
_df['年度'] = pd.to_numeric(_df['年度'], errors='coerce')
_df = _df[_df['年度'] == _df['年度'].max()]
_feats = ['総人口', '出生数', '死亡数', '婚姻件数', '食料費(二人以上の世帯)']
for _c in _feats + ['消費支出(二人以上の世帯)']:
    _df[_c] = pd.to_numeric(_df[_c], errors='coerce')
_df = _df.dropna(subset=_feats + ['消費支出(二人以上の世帯)'])

X = _df[_feats].to_numpy(dtype=float)
y = _df['消費支出(二人以上の世帯)'].to_numpy(dtype=float)
Xs = StandardScaler().fit_transform(X)
ys = (y - y.mean()) / y.std()
feature_names = _feats

from sklearn.pipeline import Pipeline
from sklearn.model_selection import GridSearchCV
pipe = Pipeline([('sc', StandardScaler()), ('en', ElasticNet(max_iter=20000))])
param_grid = {
    'en__alpha'   : np.logspace(-3, 1, 30),
    'en__l1_ratio': [.1, .3, .5, .7, .9, .95, .99, 1.0],
}
gs = GridSearchCV(pipe, param_grid, cv=5,
                  scoring='neg_root_mean_squared_error', n_jobs=-1)
gs.fit(X, y)
print(gs.best_params_)
📤 実行例(実測) {'en__alpha': np.float64(0.41753189365604004), 'en__l1_ratio': 0.7}
💬 読み方: GridSearchCV が選んだのは α=0.418、l1_ratio=0.7。この Pipeline は y を円のまま使うので α は ElasticNetCV(y を標準化)の 0.0754 とは比べられず、最良の CV RMSE は約 19,200 円(gs.best_score_ の符号を反転した値)で、消費支出の SD 約 23,900 円の 8 割ほどの誤差が残っている。

6. 安定性選択(Stability Selection)

🎯 解説: 47 都道府県から 30 県を 200 回抜き出して Elastic Net(α=0.1, l1_ratio=0.5)を当てはめ直し、各変数の係数が 0 でなかった割合(選択頻度)を数える安定性選択。
📥 入力例(SSDSE-B-2026 の 2023 年度・47 都道府県から 3 行。説明変数 5 個と目的変数の消費支出) 都道府県 総人口 出生数 死亡数 婚姻件数 食料費 消費支出(二人以上の世帯) 北海道 5,092,000 24,430 75,120 17,281 74,341 296,888 東京都 14,086,000 86,348 137,241 71,774 97,776 341,320 沖縄県 1,468,000 12,549 15,110 6,316 73,453 251,222 …(全 47 行)
 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
# ── Elastic Net の例で使うデータを用意します(47 都道府県 × 複数の説明変数)──
import numpy as np
import pandas as pd
from sklearn.preprocessing import StandardScaler

_df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', header=1)
_df = _df[_df['地域コード'].astype(str).str.match(r'^R\d{5}$', na=False)].copy()
_df['年度'] = pd.to_numeric(_df['年度'], errors='coerce')
_df = _df[_df['年度'] == _df['年度'].max()]
_feats = ['総人口', '出生数', '死亡数', '婚姻件数', '食料費(二人以上の世帯)']
for _c in _feats + ['消費支出(二人以上の世帯)']:
    _df[_c] = pd.to_numeric(_df[_c], errors='coerce')
_df = _df.dropna(subset=_feats + ['消費支出(二人以上の世帯)'])

X = _df[_feats].to_numpy(dtype=float)
y = _df['消費支出(二人以上の世帯)'].to_numpy(dtype=float)
Xs = StandardScaler().fit_transform(X)
ys = (y - y.mean()) / y.std()
feature_names = _feats

import numpy as np
rng = np.random.RandomState(0)
n_iter, n_sub = 200, 30
counts = np.zeros(X.shape[1])
for _ in range(n_iter):
    idx = rng.choice(len(X), n_sub, replace=False)
    en  = ElasticNet(alpha=0.1, l1_ratio=0.5).fit(Xs[idx], ys[idx])
    counts += (en.coef_ != 0)
sel_freq = counts / n_iter
for f, p in sorted(zip(feats, sel_freq), key=lambda x: -x[1]):
    print(f'{f:<25s}  selection freq = {p:.2f}')
📤 実行例(実測) 食料費(二人以上の世帯) selection freq = 1.00 出生数 selection freq = 0.68 死亡数 selection freq = 0.24 婚姻件数 selection freq = 0.16 総人口 selection freq = 0.00
💬 読み方: 30 県を 200 回抜き出して α=0.1, l1_ratio=0.5 で当てはめると、食料費は毎回(1.00)残り、出生数は 68%、死亡数 24%、婚姻件数 16%、総人口は一度も残らなかった。人口規模グループの 4 変数は相関 0.96 超なので、どれが代表として残るかは抜き出した県の組合せで入れ替わる。単発の当てはめで「出生数が選ばれた」と読むより、この頻度で読む方が安全。

🔎 さらに深掘り — 幾何・グループ効果・実データ検証(追記)

ここまでの本文・ウィジェットで掴んだ直感を、 ① L1+L2 の幾何、 ② グループ効果がなぜ起きるかの数式、 ③ SSDSE-B-2026 実データでの Lasso との比較、 ④ 使い分け・λパス/α探索・adaptive Elastic Net・座標降下法、 の順でもう一段深めます。 既存セクションの補強であり、 新規の数値は SSDSE-B-2026(2023 年・47 都道府県)の実測のみです。

① 直感の深掘り — なぜ「角」と「丸み」の両方が要るのか

Elastic Net の罰則 λ(α‖β‖₁ + (1-α)/2‖β‖₂²) の制約領域は、 Lasso の菱形(頂点=座標軸上の角)と Ridge の円をブレンドした形です。 角があるおかげでスパース性(係数がちょうど 0 になる変数選択)が生まれ、 辺が外に膨らんで丸みを帯びるおかげで、 相関の強い変数の間で解が一意に定まり安定します。 純 Lasso(α=1)の菱形は辺が直線なので、 相関の強い 2 変数がつくる細長い誤差の谷に対して頂点が「たまたま」触れた 1 変数だけを選び、 データが少し変わると別の頂点に飛び移って不安定になります。 少しでも L2 を混ぜる(α<1)と辺が厳密凸になり、 谷の底で解が一意化されます(下の実データ検証がこの効果を示します)。

② グループ効果の数学 — 相関 1 の 2 変数は係数が等しくなる

標準化済みの 2 変数 x_j, x_k が完全相関(x_j = x_k)のとき、 Elastic Net 解は必ず β̂_j = β̂_k となります(Zou & Hastie 2005 の grouping effect 定理)。 一般に係数差は次で上から抑えられます:

$$ \frac{|\hat\beta_j - \hat\beta_k|}{\|y\|_1} \le \frac{1}{\lambda(1-\alpha)}\sqrt{2\,(1-\rho_{jk})} $$

ここで ρ_jk は 2 変数の相関。 相関 ρ_jk が 1 に近いほど右辺が 0 に近づき、 2 つの係数はほぼ等しくなります(=群として一緒に残る/一緒に縮む)。 分母に (1-α) があるのがポイントで、 L2 成分(1-α)が大きいほどグループ化が強く効きます。 純 Lasso(α=1)では右辺が発散し、 この保証が消える=群から 1 本だけ選ぶ挙動になります。 落とし穴⑥「l1_ratio が 0.95 以上だとほぼ Lasso」はこの分母から理解できます。

③ 実データ検証 — SSDSE-B-2026(2023 年・47 都道府県)で Lasso と比較

目的変数を 出生数(A4101)、 説明変数に相関の強い人口系 5 指標を使い、 標準化後に Lasso(α=0.1)と Elastic Net(α=0.1, l1_ratio=0.5)を当てはめた実測結果です。 説明変数どうしの相関は下表のとおりいずれも 0.96 超で、 典型的な「相関変数群」を成します。

説明変数(標準化済) 総人口との相関 Lasso 係数
(α=0.1)
Elastic Net 係数
(α=0.1, l1_ratio=0.5)
総人口(A1101)1.0000.0000.173
15歳未満人口(A1301)0.9970.6450.261
15~64歳人口(A1302)0.9990.0000.179
65歳以上人口(A1303)0.9910.0000.104
婚姻件数(A9101)0.9890.2570.224
非ゼロ係数の本数 / 訓練 R²—2 / 5 本 R²=0.98855 / 5 本 R²=0.9917

読み取り: Lasso は相関 0.99 超の人口 5 指標のうち 2 本だけを残し、 総人口・生産年齢人口・高齢人口を 0 にしました(どれが残るかは相関のわずかな差やスケールに左右され不安定)。 Elastic Net は 5 本すべてに符号の同じ係数を配分し(グループ効果)、 説明力もわずかに高い(R² 0.9885→0.9917)。 これは②の grouping effect が実データで現れた例です。 なお R² は同一データへの当てはめ値であり、 汎化性能ではありません(交差検証で評価すべき点に注意)。 このデータは 多重共線性 が極端に強いケースで、 ステップワイズ法だと選ばれる変数が実行ごとに揺れやすい典型でもあります。

④ Lasso / Ridge との使い分け

状況 推奨 理由
真にスパース・変数どうしほぼ無相関Lasso明確な変数選択・解釈が容易
全変数が効く・強い多重共線性・選択は不要Ridge全係数を安定に縮小、 0 にはしない
相関変数群が複数・変数選択も欲しい・p>nElastic Net群ごと選択(grouping)+スパース性の両立

Elastic Net は Lasso と Ridge を連続的につなぐ 正則化 の一般形なので、 「どれを使うべきか分からない」ときは l1_ratio を [0.1, 0.5, 0.7, 0.9, 0.95, 0.99, 1.0] のように振ってデータに選ばせるのが実務的な回答です。

⑤ λパスと α 探索 — 2 次元グリッドを効率よく

Elastic Net は λ(強度)と α(=l1_ratio, 混合比)の 2 パラメータを同時に決めます。 各 α について λ を大きい順に下げていくと、 係数が次々と 0 から立ち上がる正則化パスが描けます。 座標降下法は「一つ前の λ の解を初期値に使う(warm start)」ことで、 パス全体を 1 本の λ 単独フィットとほぼ同じコストで計算できます。 sklearn の ElasticNetCV は l1_ratio ごとに λ パスを引き、 交差検証(交差検証)で最良の (λ, α) を選びます。 α は数点、 λ は各 α につきパスで一気に、 という「α は外側ループ・λ は内側パス」構造が計算の勘所です。 落とし穴②のとおり探索の境界に最適値が当たったら範囲を広げ直します。

⑥ Adaptive Elastic Net と座標降下法

通常の Elastic Net は大きい係数ほど強く縮める(係数バイアス)ため、 真に重要な変数まで過度に縮む欠点があります。 Adaptive Elastic Net は、 各変数の L1 罰則に「予備推定 β̃ の逆数に比例する重み w_j = 1/|β̃_j|^γ」を掛け、 大きい係数は軽く・小さい係数は重く罰することで、 一致性(オラクル性質)を回復させる拡張です(Zou & Zhang 2009)。 予備推定にはリッジ解や周辺回帰係数を使います。

座標降下法は Elastic Net の標準解法で、 本ページのウィジェットにも実装されています。 1 変数ずつ「他を固定して最適化」を繰り返し、 各更新は軟閾値処理と L2 の縮小の合成で閉形式に解けます:

$$ \hat\beta_j \leftarrow \frac{S(\,\rho_j,\ \lambda\alpha\,)}{1 + \lambda(1-\alpha)},\qquad S(z,\gamma)=\mathrm{sign}(z)\,(|z|-\gamma)_+ $$

分子の軟閾値 S が L1 由来のスパース性(小さい相関は 0 にする)、 分母の 1+λ(1-α) が L2 由来の一様縮小を担います。 この 1 本の式に Elastic Net の「変数選択+縮小」の両面が凝縮されています(ウィジェットの fit() と同じ更新式)。

🔗 関連ページ

🎮 触って理解する

2 つのスライダーを動かすと、 Elastic Net の係数がどう縮み・ゼロになるかがリアルタイムで変わります。 λ(正則化強度)を上げるほど全体が縮み、 α(L1 割合)を上げるほどスパース(ゼロが増える)になります。 α=1 で Lasso、 α=0 で Ridge。 左図の制約領域はドラッグ/スワイプでも操作できます(横→α, 縦→λ)。


← 0(罰則なし=OLS) …… 強い罰則 →

← 0(Ridge=一様縮小) 0.5(Elastic Net) 1(Lasso=スパース) →
制約領域(L1 菱形 × L2 円 の混合)。 ドラッグ/スワイプで α・λ を操作。
推定された係数(緑=正, 赤=負, 灰=ゼロ)。 座標降下法で計算。

※ このデモは挙動を体感するための合成データ(A群・B群それぞれ相関 r≈0.98 の変数対+無関係変数 2 本、標準化済み n=48)で、 実測値ではありません。 SSDSE-B-2026 の実データによる計算結果は本ページの実データ計算セクションを参照してください。

👁️ 直感 — 「Lasso と Ridge のいいとこ取り」を手で確かめる

α を 1 に寄せると A群・B群の相関対から片方だけが残り、 無関係変数も消えます(スパース化)。 α を 0 に寄せると全係数が一様に小さく縮むだけでゼロにはなりません(縮小のみ)。 中間(およそ 0.3〜0.7)では、 相関の強い A群・B群を「まとめて残す」グループ選択が起きます — これが Elastic Net の核心です。 λ を上げると、 左図の緑の制約領域が原点に向かって縮み、 係数がより強く 0 へ押しつけられる様子が見えます。

🚧 よくある落とし穴

🚀 発展

上のデモで α→1 が Lasso(L1)、 α→0 が Ridge(L2) に一致することを確認できます。 Elastic Net はこの 2 つを連続的につなぐ 正則化 の一般形です。 実務では ElasticNetCV で (λ, l1_ratio) を交差検証により同時決定し、 相関構造が強いデータほど中間の α が選ばれやすくなります。