04 変換(Box-Cox)

Timeseries

チェック4: Box-Cox/Yeo-Johnson で分布を整える

作成日: 最終更新: 読了時間: 2 分
まとめ
  • Weibull 分布や対数ガンマ分布など歪んだデータを生成し、ヒストグラムで偏りを確認します。
  • scipy.stats.boxcox で正の値のみを扱う場合と、負の値を含む場合は yeojohnson を使うケースを比較します。
  • 変換前後で線形回帰(Ridge)の残差を観察し、分布を整えるメリットを実感します。

1. ライブラリ #

import japanize_matplotlib
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

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

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)は最尤推定で自動的に決まります。

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 を使います。

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 はデータを正規性に近づけるよう λ を推定し、ゼロをまたぐ場合も自動で扱ってくれます。


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

目的変数 y に歪みがあると、線形回帰の残差に偏りが生じます。以下の例では Ridge 回帰を用います。

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()

変換なし #

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()

偏り(ヒストグラムの歪み)が大きいのが分かります。

Yeo-Johnson 変換後 #

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()

残差がほぼ対称になり、誤差分布が改善されました。推定 λ は yeojohnson(y)[1] から取得でき、逆変換には scipy.stats.yeojohnson_normmax / yeojohnsoninv_boxcox 相当を使用します。


6. 実務での指針 #

変換適用条件補足
Box-Cox0 より大きい実数のみλ=0 に近いと log 変換に近づく
Yeo-Johnson負値・ゼロを含むscikit-learn にも PowerTransformer 実装あり
Log正の値かつスケールが大きいときの手軽な選択肢零点回避のため log1p を使うことも多い
  • 予測後に逆変換するロジックを忘れずに実装する。
  • λ は訓練データから求め、本番では固定して使う。
  • 変換後に標準化(StandardScaler)を挟むとより安定します。

7. まとめ #

  • 目的変数が正の値のみ → Box-Cox、負値を含む → Yeo-Johnson を検討。
  • 変換後はモデルの残差を再チェックし、改善がみられるかを指標(MSE / MAE)で確認。
  • 逆変換の実装漏れが事故につながりやすいので、ユーティリティ関数としてまとめておくと安全です。