ヘロログ
統計学

Box-Cox変換

対数変換は右に裾が長い分布を正規分布に近づける強力な手法だが、すべてのデータに対して最適とは限らない。データによっては平方根変換や逆数変換のほうが効果的な場合もある。

Box-Cox変換は、対数変換やべき乗変換を一般化した変換で、パラメータ \lambda を調整することで、データに最適な変換を見つけることができる。1964年にBox と Cox によって提案されたこの手法は、回帰分析の前処理や分布の正規化に広く使われている。

Box-Cox変換の定義

Box-Cox変換は、正の値 x > 0 に対して、パラメータ \lambda を用いて次のように定義される。

Box-Cox変換
y = \begin{cases} \dfrac{x^\lambda - 1}{\lambda} & (\lambda \neq 0) \\[10pt] \log x & (\lambda = 0) \end{cases}

この定義で重要なのは、\lambda = 0 のとき対数変換になることである。実際、\lambda \to 0 の極限を取ると、ロピタルの定理により

\lim_{\lambda \to 0} \frac{x^\lambda - 1}{\lambda} = \lim_{\lambda \to 0} \frac{x^\lambda \log x}{1} = \log x

となり、連続的に対数変換につながる。

ポイント

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 y 0 1 2 3 0 1 2 3 −1 λ=2 λ=1 λ=0.5 λ=0 λ=−0.5 (1, 0)
図1: 各λでのBox-Cox変換。すべての曲線は (1, 0) を通る

すべての曲線が x = 1y = 0 を通ることに注目しよう。これは (1^\lambda - 1)/\lambda = 0 であることから明らかである。また、どの \lambda でも x = 1 での微分係数は 1 となるように設計されている。

最適なλの推定

データに対して最適な \lambda を求めるには、最尤法を用いる。変換後のデータが正規分布に従うと仮定し、対数尤度を最大化する \lambda を求める。

変換後のデータ y_1, \ldots, y_n正規分布 N(\mu, \sigma^2) に従うとき、対数尤度は

\ell(\lambda, \mu, \sigma^2) = -\frac{n}{2}\log(2\pi\sigma^2) - \frac{1}{2\sigma^2}\sum_{i=1}^{n}(y_i - \mu)^2 + (\lambda - 1)\sum_{i=1}^{n}\log x_i

となる。第3項は変数変換のヤコビアンから来ている。この対数尤度を \lambda について最大化することで、最適な \lambda を得る。

実用上は、\lambda を −2 から 2 の範囲でグリッドサーチするか、数値最適化で求めることが多い。Pythonの scipy.stats.boxcox を使えば、最適な \lambda を自動で推定できる。

具体例:年収データの変換

対数変換で使った年収データに Box-Cox 変換を適用し、対数変換との違いを確認する。

データ

200310390430470 490510530550580
6307208109201050 12001400165019502400

変換結果の比較

変換 λ 歪度
変換なし 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変換など)を検討する必要がある。

練習問題

問1. x = 4 に対して、\lambda = 0.5\lambda = 0 でのBox-Cox変換の値をそれぞれ計算せよ。

λ = 0.5 の場合:

y = \frac{4^{0.5} - 1}{0.5} = \frac{2 - 1}{0.5} = \frac{1}{0.5} = 2

λ = 0 の場合:

y = \log 4 = \log 2^2 = 2\log 2 \approx 1.386

\lambda = 0.5(平方根変換)のほうが大きな値を返す。\lambda が大きいほど、変換後の値の増加率が大きくなる傾向がある。

問2. データ 1, 2, 4, 8, 16(等比数列)に対して、\lambda = 0.5 でBox-Cox変換を行い、変換後のデータを求めよ。また、この等比数列に対して最適な \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 となる。

問3. Box-Cox変換の定義式 (x^\lambda - 1)/\lambda において、x = 1 のとき変換後の値が常に 0 になることを示せ。また、x = 1 での微分係数が 1 になることを示せ。

x = 1 で y = 0 となること:

y = \frac{1^\lambda - 1}{\lambda} = \frac{1 - 1}{\lambda} = 0

\lambda = 0 の場合も \log 1 = 0 なので、すべての \lambda で成立する。

x = 1 での微分係数が 1:

y = (x^\lambda - 1)/\lambdax で微分すると

\frac{dy}{dx} = \frac{\lambda x^{\lambda - 1}}{\lambda} = x^{\lambda - 1}

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 を自動で推定してくれる。

boxcox_transform.py
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}")
最適なλ: -0.1327 元データの歪度: 1.272 変換後の歪度: 0.013 対数変換の歪度: 0.198

指定した \lambda で変換したい場合は、scipy.special.boxcox を使う。

boxcox_manual.py
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}")
λ= 1: Box-Cox(4) = 3.0000 λ= 0.5: Box-Cox(4) = 2.0000 λ= 0: Box-Cox(4) = 1.3863 λ=-0.5: Box-Cox(4) = 1.0000 λ= -1: Box-Cox(4) = 0.7500

最適λの可視化

\lambda での歪度をプロットすると、最適な \lambda がわかりやすい。

lambda_search.py
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()