K_DM Book
用語集
対象者 標準
対象者別の入口 →

対象者の設定はこのブラウザの中にだけ保存されます。

言語 JA

04 変換(Box-Cox)

中級

04.11.4

04 変換(Box-Cox)

最終更新 2025-11-05 読了時間 5 分
まとめ
右に長い裾を引く分布をそのまま回帰の目的変数にすると、大きな値の少数の点が二乗誤差を支配し、残差が片側に偏ります。Box-Cox 変換は正の値に \(\lambda\) を 1 つ推定して当てる「対数の一般化」で、scipy.stats.boxcox(x) が最尤推定の \(\lambda\) と変換後の値を同時に返します。ゼロや負の値を含むデータには、同じ考え方を拡張した yeojohnson(x) を使います。このページでは Weibull 分布と対数ガンマ分布の 50,000 点で変換前後のヒストグラムを比べ、Ridge(alpha=1.0) の目的変数を yeojohnson で変換すると残差の分布がほぼ対称になることを確かめます。

STL で季節性とトレンドを分解する で季節性とトレンドを取り除いた残差を先に見ておくと、このページの「残った値の分布の歪みをどう直すか」という問題設定がはっきりします。


直感 #

年収の分布を思い浮かべてください。大半の人は 300〜600 万円に集まり、少数の人が 3,000 万円や 1 億円で右に長い裾を作ります。この値をそのまま二乗誤差で回帰すると、1 億円の 1 人の誤差が 300 万円の 100 人分の誤差を上回り、モデルは少数の高額者に合わせて歪みます。対数を取ると 300 万は 14.9、1 億は 18.4 と差が縮んで分布が左右対称に近づきます。Box-Cox はこの「どれくらい強く縮めるか」を \(\lambda\) という 1 つのパラメータで調整し、データから最も正規分布に近づく \(\lambda\) を自動で選ぶ仕組みです。

1. ライブラリ #

1
2
3
4
5
import matplotlib.pyplot as plt
import numpy as np
from scipy import stats
from scipy.stats import boxcox, yeojohnson
from sklearn.linear_model import Ridge

何が起きているか(ステップで見る) #

コードを読む前に、この手法が図の上で何をしているかを追ってみましょう。「再生」を押すか、スライダーを動かすと 1 手順ずつ進みます。

見るポイント:対数は大きな値ほど強く縮める。右に裾を引く Weibull のような分布を対称に近づける定番の変換。

式で書くと λ が何を変えているか #

Box-Cox 変換は正の \(x\) に対して

$$ x^{(\lambda)} = \begin{cases} \dfrac{x^{\lambda} - 1}{\lambda} & (\lambda \neq 0) \\\\ \log x & (\lambda = 0) \end{cases} $$

で定義されます。\(\lambda = 1\) なら \(x - 1\) でほぼ何もせず、\(\lambda = 0.5\) なら平方根、\(\lambda = 0\) なら対数と、小さくするほど大きな値を強く縮めます。boxcox は変換後の値が最も正規分布に近くなる \(\lambda\) を最尤法で選びます。Yeo-Johnson は \(x \geq 0\) では \(x + 1\) に Box-Cox を、\(x < 0\) では \(1 - x\) に \(2 - \lambda\) の Box-Cox を当てて符号を戻す拡張で、ゼロをまたぐデータでも 1 つの \(\lambda\) で連続につながります。

2. 歪んだ分布を可視化 #

形状パラメータ 2.0 の Weibull 分布(正の値のみ、右に裾)と対数ガンマ分布(負の値を含み、左に裾)をそれぞれ 50,000 点生成してヒストグラムにします。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
plt.figure(figsize=(12, 5))
data_wb = np.random.weibull(2.0, size=50_000)
plt.hist(data_wb, bins=30, rwidth=0.9)
plt.title("Weibull 分布")
plt.show()

plt.figure(figsize=(12, 5))
data_lg = stats.loggamma.rvs(2.0, size=50_000)
plt.hist(data_lg, bins=30, rwidth=0.9)
plt.title("対数ガンマ分布")
plt.show()

裾が長い(heavy-tail)ため、そのまま二乗誤差で学習させると、裾の少数の点が損失の大部分を占めて係数が不安定になります。


3. Box-Cox 変換(正の値のみ) #

boxcox はデータがすべて正のときに使います。\(\lambda\) は最尤推定で自動的に決まり、戻り値の 2 番目に入っています。

1
2
3
4
plt.figure(figsize=(12, 5))
plt.hist(boxcox(data_wb)[0], bins=30, rwidth=0.9)
plt.title("Weibull → Box-Cox")
plt.show()

4. Yeo-Johnson 変換(負の値もOK) #

Box-Cox はゼロや負の値を扱えないため、対数ガンマのような分布には Yeo-Johnson を使います。

1
2
3
4
plt.figure(figsize=(12, 5))
plt.hist(yeojohnson(data_lg)[0], bins=30, rwidth=0.9)
plt.title("log-gamma → Yeo-Johnson")
plt.show()

yeojohnson はデータを正規性に近づけるよう \(\lambda\) を推定し、ゼロをまたぐ場合も自動で扱います。


5. 変換前後で回帰の残差を比較 #

目的変数 y に歪みがあると、線形回帰の残差に偏りが生じます。Weibull 分布の y と、その平方根にノイズを足した特徴量を 1 列だけ持つデータを作り、Ridge 回帰の残差を変換前後で比べます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
N = 1000
rng = np.random.RandomState(0)
y = np.random.weibull(2.0, size=N)

X = rng.randn(N, 5)
X[:, 0] = np.sqrt(y) + np.random.rand(N) / 10

plt.figure(figsize=(12, 5))
plt.hist(y, bins=20, rwidth=0.9)
plt.title("目的変数 y の分布")
plt.show()

変換なし #

歪んだ y をそのまま目的変数にして Ridge を学習し、予測と実測の散布図と残差のヒストグラムを並べます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
clf = Ridge(alpha=1.0)
clf.fit(X, y)
pred = clf.predict(X)

plt.figure(figsize=(12, 6))
plt.subplot(121)
plt.title("予測 vs 実測")
plt.scatter(y, pred)
plt.plot([0, 2], [0, 2], "r")
plt.grid()
plt.subplot(122)
plt.title("残差の分布")
plt.hist(y - pred)
plt.show()

残差のヒストグラムが右に偏り、平均 0 の対称な分布から外れていることが分かります。

Yeo-Johnson 変換後 #

目的変数を yeojohnson で変換してから同じ Ridge を学習し、変換後の尺度で残差を比べます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
y_trans, lmbda = yeojohnson(y)
clf = Ridge(alpha=1.0)
clf.fit(X, y_trans)
pred = clf.predict(X)

plt.figure(figsize=(12, 6))
plt.subplot(121)
plt.title("予測 vs 実測(変換後)")
plt.scatter(y_trans, pred)
plt.plot([0, 2], [0, 2], "r")
plt.grid()
plt.subplot(122)
plt.title("残差の分布(変換後)")
plt.hist(y_trans - pred)
plt.show()

残差 y_trans - pred が 0 を中心にほぼ対称になり、誤差分布が改善されました。推定 \(\lambda\) は yeojohnson(y)[1] から取得できます。Box-Cox の逆変換は scipy.special.inv_boxcox(y_trans, lmbda) で行えますが、Yeo-Johnson には scipy に逆変換関数がないため、sklearn.preprocessing.PowerTransformer(method="yeo-johnson") を使って fit_transform / inverse_transform の組で扱うのが実務では安全です。


6. 実務での指針 #

変換適用条件補足
Box-Cox0 より大きい実数のみλ=0 に近いと log 変換に近づく
Yeo-Johnson負値・ゼロを含むscikit-learn にも PowerTransformer 実装あり
Log正の値かつスケールが大きいときの手軽な選択肢零点回避のため log1p を使うことも多い

予測後に inverse_transform で元の単位に戻すロジックを忘れると、売上の予測が「2.3」のような意味のない数字で出てしまうので、変換と逆変換は必ず対にして実装します。\(\lambda\) は訓練データだけから求め、テストデータや本番では同じ値に固定します。テストデータで再推定すると、訓練と本番で尺度が変わってモデルが壊れます。変換後に StandardScaler で平均 0・分散 1 に揃えると、正則化の alpha の効き方が特徴量間で揃って学習が安定します。


7. まとめ #

目的変数が正の値だけなら boxcox、ゼロや負を含むなら yeojohnson を選び、どちらも最尤推定された \(\lambda\) を 1 つ保存しておきます。変換後は Ridge などの残差ヒストグラムを再確認し、このページの例のように右への偏りが消えて 0 を中心に対称になっているか、元の単位に逆変換した MSE や MAE が改善しているかを見ます。逆変換の実装漏れが最も多い事故なので、PowerTransformer のように transform と inverse_transform を対で持つユーティリティにまとめておくのが安全です。