データの分布を正しく理解することは、機械学習モデリングの成功に不可欠です。多くの統計手法やアルゴリズムは特定の分布を前提としており、データの実際の分布を把握しておくことで、適切な前処理手法やモデルの選択が可能になります。この記事では、分布の可視化手法から統計的な正規性検定、分布フィッティング、そして分析結果をモデリングに活かす方法まで包括的に解説します。
なぜ分布分析が重要なのか
機械学習において分布分析が重要な理由は大きく3つあります。第一に、線形回帰やt検定など多くの手法は残差や入力データの正規性を仮定しています。第二に、歪んだ分布はモデルの学習を阻害し、外れ値に対する感度を高めます。第三に、分布の形状を理解することで、対数変換やBox-Cox変換などの前処理の必要性を判断できます。分布分析は「データに合った手法を選ぶ」ための羅針盤です。
ヒストグラム+KDEプロット — 分布の全体像を掴む
ヒストグラムはビン(区間)ごとの頻度を棒グラフで表現しますが、ビン幅の選択によって見え方が変わるという欠点があります。KDE(カーネル密度推定)はデータからなめらかな確率密度関数を推定する手法で、ヒストグラムと重ねて表示することで、ビン幅に依存しない分布の形状を把握できます。二峰性(2つのピーク)や裾の厚さなど、ヒストグラムだけでは見逃しやすい特徴も KDE で発見できます。
import matplotlib.pyplot as plt
import seaborn as sns
import numpy as np
fig, axes = plt.subplots(2, 3, figsize=(15, 10))
numeric_cols = df.select_dtypes(include=["number"]).columns[:6]
for ax, col in zip(axes.flat, numeric_cols):
data = df[col].dropna()
# ヒストグラム + KDE
sns.histplot(data, kde=True, ax=ax, stat="density", alpha=0.6)
# 平均と中央値のライン
ax.axvline(data.mean(), color="red", linestyle="--",
label=f"平均: {data.mean():.2f}")
ax.axvline(data.median(), color="green", linestyle="-.",
label=f"中央値: {data.median():.2f}")
ax.set_title(f"{col} (歪度: {data.skew():.2f})")
ax.legend(fontsize=8)
plt.suptitle("各カラムのヒストグラム + KDE", fontsize=14)
plt.tight_layout()
plt.savefig("histogram_kde.png", dpi=150)
plt.show()Q-Qプロット — 正規分布との視覚的比較
Q-Qプロット(Quantile-Quantile Plot)は、データの分位数と理論的な正規分布の分位数を散布図にプロットしたものです。データが正規分布に従う場合、点は45度の直線上に並びます。直線から外れるパターンによって、分布の歪みや裾の厚さを視覚的に判断できます。S字カーブは裾が厚い分布を、上方への反りは右に歪んだ分布を、下方への反りは左に歪んだ分布を示します。
from scipy import stats
import matplotlib.pyplot as plt
fig, axes = plt.subplots(2, 3, figsize=(15, 10))
numeric_cols = df.select_dtypes(include=["number"]).columns[:6]
for ax, col in zip(axes.flat, numeric_cols):
data = df[col].dropna()
# Q-Qプロット
stats.probplot(data, dist="norm", plot=ax)
ax.set_title(f"Q-Q Plot: {col}")
ax.get_lines()[0].set_markerfacecolor("steelblue")
ax.get_lines()[0].set_markersize(3)
ax.get_lines()[1].set_color("red")
plt.suptitle("正規Q-Qプロット", fontsize=14)
plt.tight_layout()
plt.savefig("qq_plots.png", dpi=150)
plt.show()Q-Qプロットは正規分布だけでなく、任意の理論分布との比較にも使えます。例えば、対数正規分布が疑われるデータでは、対数変換後のデータで正規Q-Qプロットを描くことで、対数正規性を視覚的に確認できます。
正規性検定 — 統計的に正規分布を検証する
視覚的な判断に加えて、統計的な正規性検定を実施することで、客観的な根拠を持った判断ができます。代表的な正規性検定には Shapiro-Wilk 検定、Anderson-Darling 検定、Kolmogorov-Smirnov(KS)検定、D'Agostino-Pearson 検定の4つがあります。各検定にはそれぞれ得意分野があり、複数の検定を併用することで信頼性の高い判断が可能です。
- 1
Shapiro-Wilk 検定
最も検出力が高い正規性検定です。小〜中規模のデータ(5,000件程度まで)に適しています。帰無仮説は「データは正規分布に従う」で、p < 0.05 なら正規性を棄却します。サンプルサイズが大きい場合は検出力が高すぎて、わずかな偏りでも棄却してしまう点に注意が必要です。
- 2
Anderson-Darling 検定
KS 検定を改良し、分布の裾(テール)部分に重みを置いた検定です。裾の挙動が重要なリスク分析や金融データの分析で重宝されます。有意水準に応じた臨界値と検定統計量を比較して判断します。
- 3
Kolmogorov-Smirnov 検定(KS 検定)
データの経験累積分布関数(ECDF)と理論的な正規分布の最大乖離(D統計量)を検定します。サンプルサイズに制限がなく大規模データにも適用できますが、裾の検出力がやや低い傾向があります。
- 4
D'Agostino-Pearson 検定
歪度と尖度を組み合わせた検定で、正規分布からの逸脱の種類(歪みか尖りか)を区別できます。サンプルサイズ20以上で信頼性のある結果が得られます。
from scipy import stats
import pandas as pd
def normality_test_suite(data: pd.Series) -> pd.DataFrame:
"""4つの正規性検定を一括実行する。"""
clean = data.dropna()
results = []
# 1. Shapiro-Wilk
if len(clean) <= 5000:
stat, pval = stats.shapiro(clean)
results.append({"検定名": "Shapiro-Wilk", "統計量": stat, "p値": pval})
# 2. Anderson-Darling
ad_result = stats.anderson(clean, dist="norm")
results.append({
"検定名": "Anderson-Darling",
"統計量": ad_result.statistic,
"p値": None, # 臨界値で判断
})
# 3. Kolmogorov-Smirnov
stat, pval = stats.kstest(clean, "norm",
args=(clean.mean(), clean.std()))
results.append({"検定名": "Kolmogorov-Smirnov", "統計量": stat, "p値": pval})
# 4. D'Agostino-Pearson
if len(clean) >= 20:
stat, pval = stats.normaltest(clean)
results.append({"検定名": "D'Agostino-Pearson", "統計量": stat, "p値": pval})
return pd.DataFrame(results)
for col in df.select_dtypes(include=["number"]).columns:
print(f"\n=== {col} ===")
result = normality_test_suite(df[col])
print(result.to_string(index=False))分布フィッティング — 最適な理論分布を見つける
正規分布以外にも、データの分布を記述する理論分布は多数存在します。分布フィッティングとは、データに最もよく適合する理論分布を統計的に特定するプロセスです。対数正規分布(所得や都市の人口)、指数分布(故障までの時間)、ガンマ分布(待ち時間の合計)、ベータ分布(確率や割合)など、データの性質に応じた分布を候補として検討します。
- 1
正規分布(Normal)
最も一般的な分布で、ベルカーブの形をしています。中心極限定理により、多くのサンプル平均は正規分布に近づきます。パラメータは平均(μ)と標準偏差(σ)です。
- 2
対数正規分布(Lognormal)
対数をとると正規分布になるデータに適合します。所得、株価、都市の人口など、正の値で右に歪んだ分布によく見られます。
- 3
指数分布(Exponential)
事象が一定の確率で発生する場合の待ち時間の分布です。機械の故障間隔やWebサイトへのアクセス間隔などに適合します。
- 4
ガンマ分布(Gamma)
指数分布の一般化で、形状パラメータにより柔軟な分布を表現できます。保険金額や降水量などのデータに適合することがあります。
from scipy import stats
import numpy as np
import pandas as pd
def fit_distributions(data: pd.Series) -> pd.DataFrame:
"""複数の理論分布をフィッティングし、AIC/BICで比較する。"""
clean = data.dropna().values
n = len(clean)
candidates = {
"正規分布": stats.norm,
"対数正規分布": stats.lognorm,
"指数分布": stats.expon,
"ガンマ分布": stats.gamma,
"ベータ分布": stats.beta,
}
results = []
for name, dist in candidates.items():
try:
# 対数正規・ガンマ・ベータは正の値が必要
if name in ["対数正規分布", "ガンマ分布", "ベータ分布"]:
if clean.min() <= 0:
continue
if name == "ベータ分布":
if clean.max() >= 1 or clean.min() <= 0:
continue
params = dist.fit(clean)
k = len(params) # パラメータ数
log_lik = np.sum(dist.logpdf(clean, *params))
aic = 2 * k - 2 * log_lik
bic = k * np.log(n) - 2 * log_lik
# KS検定で適合度を評価
ks_stat, ks_pval = stats.kstest(clean, dist.cdf, args=params)
results.append({
"分布": name,
"AIC": round(aic, 2),
"BIC": round(bic, 2),
"KS統計量": round(ks_stat, 4),
"KS p値": round(ks_pval, 4),
})
except Exception:
continue
return pd.DataFrame(results).sort_values("AIC")
for col in df.select_dtypes(include=["number"]).columns[:3]:
print(f"\n=== {col} の分布フィッティング ===")
result = fit_distributions(df[col])
print(result.to_string(index=False))AIC と BIC — モデル選択の情報量基準
AIC(赤池情報量基準)と BIC(ベイズ情報量基準)は、モデル(ここでは理論分布)の良さを評価する指標です。どちらも「データへの適合度」と「モデルの複雑さ」のバランスを取ります。値が小さいほど良いモデルとされます。AIC はパラメータ数の2倍をペナルティとし、BIC はパラメータ数×log(サンプルサイズ)をペナルティとします。BIC の方がサンプルサイズが大きい場合にペナルティが大きくなるため、よりシンプルなモデルを選択する傾向があります。
AIC と BIC で異なる分布が選ばれた場合は、BIC の結果を優先するのが一般的です。BIC はより保守的な基準であり、過学習を避ける傾向があります。ただし、最終的にはQ-Qプロットによる視覚的な確認も併用しましょう。
分布分析結果のモデリングへの活用
分布分析の結果は、前処理の設計とモデル選択に直結します。正規分布に従わないデータに対しては、対数変換や Box-Cox 変換で正規化することで、線形回帰や距離ベースのアルゴリズム(KNN、SVM)の性能が向上します。一方、決定木ベースのアルゴリズム(Random Forest、XGBoost)は分布の形状に影響されにくいため、変換の必要性は低くなります。
対数変換と Box-Cox 変換 — 分布の正規化
対数変換(log transformation)は右に歪んだ分布をほぼ対称な分布に近づける最もシンプルな手法です。ただし、0以下の値を含むデータには適用できないため、log(x + 1) のようにオフセットを加えることがあります。Box-Cox 変換は対数変換の一般化であり、最適な変換パラメータ(λ)を自動的に推定します。λ=0 が対数変換、λ=1 が変換なしに相当し、データに最も適した変換を選択できます。
from scipy import stats
import numpy as np
import pandas as pd
# 右に歪んだ分布に対する変換の比較
col = "price" # 例: 右に歪んだカラム
data = df[col].dropna()
# 1. 対数変換
log_data = np.log1p(data) # log(x + 1) で 0 を含むデータに対応
# 2. Box-Cox 変換(正の値のみ)
if data.min() > 0:
boxcox_data, lambda_opt = stats.boxcox(data)
print(f"Box-Cox 最適 λ: {lambda_opt:.4f}")
# 変換前後の歪度を比較
print(f"\n歪度の変化:")
print(f" 変換前: {data.skew():.4f}")
print(f" 対数変換後: {log_data.skew():.4f}")
if data.min() > 0:
print(f" Box-Cox後: {pd.Series(boxcox_data).skew():.4f}")
# 変換後の正規性検定
print(f"\nShapiro-Wilk 検定 (p値):")
print(f" 変換前: {stats.shapiro(data[:5000])[1]:.6f}")
print(f" 対数変換後: {stats.shapiro(log_data[:5000])[1]:.6f}")
if data.min() > 0:
print(f" Box-Cox後: {stats.shapiro(boxcox_data[:5000])[1]:.6f}")Qast の分布分析機能
Qast の EDA 機能では、各数値カラムに対してヒストグラム+KDE、Q-Qプロット、4種類の正規性検定、分布フィッティング(AIC/BIC 比較付き)が自動的に実行されます。正規分布から大きく逸脱しているカラムにはアラートが表示され、対数変換や Box-Cox 変換の適用結果もプレビューで確認できます。さらに、学習パイプラインにこれらの変換を組み込む際にも、Qast が自動的に変換・逆変換を管理するため、予測結果の解釈にも支障が出ません。
分布分析は探索的データ分析(EDA)の中でも特に重要なステップです。しかし、すべてのカラムを正規分布に変換する必要はありません。ツリーベースのモデル(XGBoost、LightGBMなど)は分布の形状に頑健であるため、変換なしでも高い性能を発揮します。Qast では各アルゴリズムに最適な前処理パイプラインが自動選択されるため、ユーザーが個々のカラムの変換要否を判断する必要はありません。
まとめ — 分布を理解してデータに合った分析を
分布分析は、データの本質を理解するための強力なツールです。ヒストグラムとKDEで全体像を掴み、Q-Qプロットで視覚的に正規性を確認し、統計的検定で客観的に検証し、分布フィッティングで最適な理論分布を特定する——この一連のプロセスにより、前処理の設計やモデルの選択を根拠に基づいて行えるようになります。Qast を活用すれば、これらの高度な分析がコードを書くことなくワンクリックで完了し、機械学習プロジェクトの初期段階を大幅に効率化できます。


