はじめに
機械学習やデータサイエンスの世界に足を踏み入れると、必ず最初に登場するのがEDA(Exploratory Data Analysis:探索的データ分析)という工程です。
「とりあえずPythonでscikit-learnにデータを流し込んで、サクッと予測モデルを作ればいいんじゃないの?」
「わざわざグラフをたくさん出力する意味ってあるの?」
初学者の頃はそう思いがちですが、いきなりモデリング(実装)に飛びつくのは、システム開発において「要件定義やQA(品質保証)をスキップして、いきなり本番環境のコードを書き始める」のと同じくらい危険な行為です。
この記事では、Google Colabで実際に動かせる「花巻東高校の卒業生年収データ」のシミュレーションを通じて、データ分析に潜む罠と「EDA」の絶対的な必要性について解説します。
1. 悲劇:「平均値」は平気で嘘をつく
ある高校(ここでは花巻東高校とします)の「卒業生の年収予測モデル」を作るとしましょう。
直近20年分の一般卒業生(約2,000人)の平均年収は450万円、標準偏差150万円の現実的な分布だとします。
しかし、この卒業生データの中には大谷翔平選手(推定年収100億円=1,000,000万円)と菊池雄星選手(推定年収20億円=200,000万円)の2人のデータが混ざっています。
まずはGoogle Colabを開き、以下のコードを実行してデータを生成し、Pandasの df.describe() で基本統計量を確認してみましょう。
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
# 1. ダミーデータの生成(直近20年分・約2000人)
np.random.seed(42)
normal_incomes = np.random.normal(loc=450, scale=150, size=2000)
normal_incomes = np.clip(normal_incomes, 200, 1500) # 200万〜1500万の範囲
# 2. 異常値(メジャーリーガー2名)の追加
# 大谷選手(約100億円)、菊池選手(約20億円)
super_stars = np.array([1000000, 200000])
# データの結合
all_incomes = np.concatenate([normal_incomes, super_stars])
df = pd.DataFrame({'年収(万円)': all_incomes})
# 基本統計量の出力
print(df.describe())
出力結果のイメージ
実行すると、コンソールには以下のような驚くべき結果が出力されます。
=== 花巻東高校 卒業生年収の基本統計量(describe) ===
年収(万円)
count 2,002.0
mean 1,048.9 <-- !?平均年収 約1,050万円!?
std 22,810.0
min 200.0
25% 349.5
50% 449.2
75% 553.8
max 1,000,000.0 <-- 最大値 100億円
一般的な卒業生は年収450万円前後であるにもかかわらず、たった2人の超絶な外れ値(ノイズ)が混ざるだけで、「この高校の卒業生は平均して1,050万円稼いでいる」という実態とかけ離れたデータが弾き出されてしまいます。
この「mean(平均値)の数字だけ」を見て重回帰分析などの予測モデルを作れば、当然ながら現場では全く役に立たないゴミの数式が出来上がります(Garbage In, Garbage Out)。
2. グラフが「ぶっ壊れる」瞬間(大谷・菊池バグ)
数字の嘘を見抜くために、データを可視化(グラフ化)してみます。Colabの次のセルで以下を実行してください。
import seaborn as sns
import matplotlib.pyplot as plt
import japanize_matplotlib # 日本語表示用
sns.set_theme(style="whitegrid")
plt.rcParams['font.family'] = 'IPAexGothic'
fig, axes = plt.subplots(1, 2, figsize=(15, 6))
# 左側:そのまま可視化(大谷・菊池バグの発生)
sns.boxplot(data=df, y='年収(万円)', ax=axes[0])
axes[0].set_title('① そのまま可視化した場合\n(大谷・菊池バグでグラフが崩壊)')
# 右側:異常値をフィルタリング後(現実的な分布)
df_filtered = df[df['年収(万円)'] < 5000]
sns.histplot(df_filtered['年収(万円)'], bins=30, kde=True, ax=axes[1], color='coral')
axes[1].set_title('② 異常値をフィルタリング後\n(本来の年収分布が現れる)')
plt.tight_layout()
plt.show()
可視化結果の評価
出力されたグラフの左側(①)を見ると、大谷選手の「100億円」という点が遥か上空にプロットされるため、グラフのY軸スケールが完全にバグってしまいます。その結果、残り2,000人の一般卒業生のデータが「ただの地面の線(ゼロ付近)」に押しつぶされ、分布の形が何も見えなくなります。
これが、重回帰分析などの機械学習アルゴリズムの内部で起きていることと同じです。アルゴリズムはこの極端な外れ値に強烈に引っ張られ、全体の予測線を完全に狂わせてしまいます。
3. EDAはデータに対する「シフトレフト」である
昨今のソフトウェア開発では、不具合を上流工程で未然に防ぐ「シフトレフト(Shift-Left)」が重視されますが、EDAはまさにデータに対するシフトレフトテストです。
先ほどのコードの右側のグラフ(②)を見てください。EDAの段階で「明らかに全体の傾向を破壊している2件の異常値」をコードでフィルタリングして除外した結果、初めて「平均450万円を中心とした綺麗な山の形(正規分布)」という、データの真の姿が現れました。
直線を引いて予測する重回帰分析モデルには、この「綺麗に整えられたデータ」を渡してあげなければ正しく機能しません。
4. 重回帰分析におけるEDAの3つの目的
外れ値の処理以外にも、EDAには重要な役割があります。特に複数の変数から1つの結果を予測する「重回帰分析」においては、以下の3つをモデリング前にクリアするために行います。
-
ノイズの排除
今回のように、直線を極端に狂わせる外れ値を視覚的に見つけ出し、事前に取り除く。 -
真の説明変数の選定
変数を何でもかんでも突っ込めば精度が上がるわけではない。目的変数(予測したい値)と本当に相関のある変数だけをあぶり出し、無関係なノイズ変数を捨てる。 -
多重共線性(マルチコ)の回避
似たような動きをする説明変数同士(例:「日経平均」と「S&P500」など)を同時にモデルに入れると、計算ロジックが破綻する。散布図行列全体を俯瞰し、競合する変数を事前に削ることでモデルを安定させる。
まとめ:「とりあえず実装」から「データ品質の設計」へ
モデルの精度を決めるのは、アルゴリズムの複雑さではなく「入力データの品質」です。
目的変数の1列だけを見ていると、外れ値による平均の罠や、変数同士の被りに気づけません。いきなりコードを書いて数式を組む前に、まずはデータを俯瞰して「モデルが正しく機能するための条件」を整えましょう。
次に分析用ノートブックを開くときは、ぜひ「まずはデータを俯瞰する(EDA)」ことを意識してみてください。
