対数変換は右に裾が長い分布を正規分布に近づける強力な手法だが、すべてのデータに対して最適とは限らない。データによっては平方根変換や逆数変換のほうが効果的な場合もある。
Box-Cox変換は、対数変換やべき乗変換を一般化した変換で、パラメータ \lambda を調整することで、データに最適な変換を見つけることができる。1964年にBox と Cox によって提案されたこの手法は、回帰分析の前処理や分布の正規化に広く使われている。
Box-Cox変換の定義
Box-Cox変換は、正の値 x > 0 に対して、パラメータ \lambda を用いて次のように定義される。
この定義で重要なのは、\lambda = 0 のとき対数変換になることである。実際、\lambda \to 0 の極限を取ると、ロピタルの定理により
となり、連続的に対数変換につながる。
Box-Cox変換は \lambda を変えることで、べき乗変換(\lambda \neq 0)と対数変換(\lambda = 0)を統一的に扱える。対数変換を一般化した変換と考えてよい。
パラメータλの意味
\lambda の値によって、変換の形が大きく変わる。代表的な値での変換は以下のようになる。
| \lambda | 変換 | 名称 |
|---|---|---|
| 2 | (x^2 - 1)/2 | 二乗変換(の線形変換) |
| 1 | x - 1 | 恒等変換(平行移動) |
| 0.5 | 2(\sqrt{x} - 1) | 平方根変換 |
| 0 | \log x | 対数変換 |
| −0.5 | -2(1/\sqrt{x} - 1) | 逆平方根変換 |
| −1 | -(1/x - 1) | 逆数変換 |
\lambda = 1 のとき変換後は x - 1 となり、元のデータを平行移動しただけである(分布の形は変わらない)。\lambda < 1 では大きな値が圧縮され、\lambda > 1 では大きな値が拡大される。
すべての曲線が x = 1 で y = 0 を通ることに注目しよう。これは (1^\lambda - 1)/\lambda = 0 であることから明らかである。また、どの \lambda でも x = 1 での微分係数は 1 となるように設計されている。
最適なλの推定
データに対して最適な \lambda を求めるには、最尤法を用いる。変換後のデータが正規分布に従うと仮定し、対数尤度を最大化する \lambda を求める。
変換後のデータ y_1, \ldots, y_n が正規分布 N(\mu, \sigma^2) に従うとき、対数尤度は
となる。第3項は変数変換のヤコビアンから来ている。この対数尤度を \lambda について最大化することで、最適な \lambda を得る。
実用上は、\lambda を −2 から 2 の範囲でグリッドサーチするか、数値最適化で求めることが多い。Pythonの scipy.stats.boxcox を使えば、最適な \lambda を自動で推定できる。
具体例:年収データの変換
対数変換で使った年収データに Box-Cox 変換を適用し、対数変換との違いを確認する。
データ
| 200 | 310 | 390 | 430 | 470 | 490 | 510 | 530 | 550 | 580 |
| 630 | 720 | 810 | 920 | 1050 | 1200 | 1400 | 1650 | 1950 | 2400 |
変換結果の比較
| 変換 | λ | 歪度 |
|---|---|---|
| 変換なし | 1 | 1.27 |
| 平方根変換 | 0.5 | 0.79 |
| 対数変換 | 0 | 0.20 |
| Box-Cox(最適) | −0.13 | 0.01 |
最尤法で推定された最適な \lambda は約 −0.13 であった。この値で変換すると、歪度は 0.01 とほぼ 0 になり、対数変換(歪度 0.20)よりもさらに正規分布に近い分布が得られた。
Box-Cox変換は正のデータ(x > 0)にのみ適用可能である。0 や負の値を含むデータには、事前にシフト(定数を加える)するか、他の変換(Yeo-Johnson変換など)を検討する必要がある。
練習問題
λ = 0.5 の場合:
λ = 0 の場合:
\lambda = 0.5(平方根変換)のほうが大きな値を返す。\lambda が大きいほど、変換後の値の増加率が大きくなる傾向がある。
λ = 0.5 での変換:
- (1^{0.5} - 1)/0.5 = 0
- (2^{0.5} - 1)/0.5 = (1.414 - 1)/0.5 \approx 0.828
- (4^{0.5} - 1)/0.5 = (2 - 1)/0.5 = 2.000
- (8^{0.5} - 1)/0.5 = (2.828 - 1)/0.5 \approx 3.657
- (16^{0.5} - 1)/0.5 = (4 - 1)/0.5 = 6.000
変換後: 0, 0.828, 2.000, 3.657, 6.000
最適な λ の予想:
等比数列は乗法的な関係を持つので、対数変換(\lambda = 0)で等差数列に変換される。実際に計算すると最適な \lambda \approx 0 となる。
x = 1 で y = 0 となること:
\lambda = 0 の場合も \log 1 = 0 なので、すべての \lambda で成立する。
x = 1 での微分係数が 1:
y = (x^\lambda - 1)/\lambda を x で微分すると
x = 1 を代入すると 1^{\lambda - 1} = 1 となる。
\lambda = 0 の場合も d(\log x)/dx = 1/x より、x = 1 で微分係数は 1 である。
まとめ
| 項目 | 内容 |
|---|---|
| 定義 | y = \dfrac{x^\lambda - 1}{\lambda}(\lambda = 0 で対数変換) |
| 適用条件 | x > 0(正の値のみ) |
| 特徴 | べき乗変換と対数変換を統一的に扱える |
| λの推定 | 最尤法で最適値を求める |
| 用途 | 回帰分析の前処理、分布の正規化 |
| 注意点 | 0以下の値には適用不可 |
Pythonで実装する
scipy.stats.boxcox を使うと、最適な \lambda を自動で推定してくれる。
import numpy as np
from scipy import stats
# 年収データ(万円)
income = np.array([200, 310, 390, 430, 470, 490, 510, 530, 550, 580,
630, 720, 810, 920, 1050, 1200, 1400, 1650, 1950, 2400])
# Box-Cox変換(最適λを自動推定)
transformed, optimal_lambda = stats.boxcox(income)
print(f"最適なλ: {optimal_lambda:.4f}")
print(f"\n元データの歪度: {stats.skew(income):.3f}")
print(f"変換後の歪度: {stats.skew(transformed):.3f}")
# 対数変換との比較
log_income = np.log(income)
print(f"対数変換の歪度: {stats.skew(log_income):.3f}")
指定した \lambda で変換したい場合は、scipy.special.boxcox を使う。
from scipy.special import boxcox
# 各λでの変換
x = 4
for lam in [1, 0.5, 0, -0.5, -1]:
y = boxcox(x, lam)
print(f"λ={lam:4}: Box-Cox({x}) = {y:.4f}")
最適λの可視化
各 \lambda での歪度をプロットすると、最適な \lambda がわかりやすい。
import matplotlib.pyplot as plt
# 各λでの歪度を計算
lambdas = np.linspace(-1, 2, 50)
skewnesses = []
for lam in lambdas:
if lam == 0:
trans = np.log(income)
else:
trans = (income**lam - 1) / lam
skewnesses.append(stats.skew(trans))
# プロット
plt.figure(figsize=(8, 5))
plt.plot(lambdas, skewnesses, 'b-', linewidth=2)
plt.axhline(y=0, color='r', linestyle='--', label='歪度=0')
plt.xlabel('λ')
plt.ylabel('歪度')
plt.title('Box-Cox変換: λと歪度の関係')
plt.legend()
plt.grid(True, alpha=0.3)
plt.savefig('boxcox_lambda.png', dpi=150)
plt.show()