📚 背景知識(読んでから問題へ)
平均・中央値・最頻値、何が違う?
全部「代表値」(データを1つの数で代表させる値)ですが、性質が違います。
| 代表値 | 意味 | 弱点 |
|---|---|---|
| 平均(mean) | 全部足して個数で割る | 外れ値(極端な値)に引っ張られる |
| 中央値(median) | 並べて真ん中の値 | 計算に全データを使わない |
| 最頻値(mode) | 最も多く登場する値 | 連続データだと意味が薄い |
外れ値がある時のイメージ
年収データで「1億円の人が1人いる」場合:
- 平均: 大幅に上がる(1億円に引っ張られる)
- 中央値: ほとんど変わらない(真ん中の順位が変わらないので)
→ 外れ値がある時は中央値の方が「実態を表す代表値」になりやすい
Kaggleでの活用場面
Titanicコンペの欠損値補完でも「Age(年齢)の欠損を何で埋めるか?」という問題があります。
平均で埋めるか、中央値で埋めるか→ 分布の形によって正解が変わります。
📝 問題
あなたはECサイトの1日の注文データを分析しています。
以下の購入金額リストに対して、Pythonで記述統計を計算し、「外れ値が代表値に与える影響」 を確認してください。
purchases = [500, 800, 750, 600, 900, 1200, 850, 700, 650, 15000, 550, 820, 780, 670, 900]
# ↑ 15000円の注文が1件混入している(高額注文 or データ入力ミス)
タスク1: 基本統計量を計算する
以下を全て出力するコードを書いてください:
- 平均(mean)
- 中央値(median)
- 最頻値(mode)
- 最小値・最大値
- 標準偏差(std)
タスク2: 外れ値を除いた場合と比較する
15000 を除いたリストで同じ統計量を計算し、外れ値あり/なしの比較表を出力してください。
タスク3: 考察(コードなし)
外れ値(15000円)が含まれる場合、平均と中央値のどちらが「今日の典型的な注文金額」を表すのに適切ですか?理由も答えてください。
使用ライブラリ: numpy と statistics(pandas は使わなくてOK)
🔍 ヒント(段階的開示)
ヒント1(方向性)
numpy の関数 np.mean(), np.median() と、Python標準の statistics モジュールの mode() を組み合わせましょう。
ヒント2(アプローチ)
import numpy as np
import statistics
# 基本統計量はnumpyで
mean = np.mean(data)
median = np.median(data)
# 最頻値はstatisticsモジュール
mode = statistics.mode(data)
# 外れ値を除く
data_clean = [x for x in data if x != 15000]
ヒント3(コード骨格)
import numpy as np
import statistics
purchases = [500, 800, 750, 600, 900, 1200, 850, 700, 650, 15000, 550, 820, 780, 670, 900]
purchases_clean = [x for x in purchases if x != 15000]
def describe(data: list, label: str) -> None:
print(f"\n=== {label} ===")
print(f"平均: {np.mean(data):.1f}円")
print(f"中央値: {np.median(data):.1f}円")
print(f"最頻値: {statistics.mode(data)}円")
print(f"最小値: {np.min(data)}円")
print(f"最大値: {np.max(data)}円")
print(f"標準偏差: {np.std(data):.1f}円")
describe(purchases, "外れ値あり")
describe(purchases_clean, "外れ値なし")
✅ 模範解答
import numpy as np
import statistics
purchases = [500, 800, 750, 600, 900, 1200, 850, 700, 650, 15000, 550, 820, 780, 670, 900]
purchases_clean = [x for x in purchases if x != 15000]
def describe(data: list[int], label: str) -> None:
"""リストの基本統計量を出力する。
Args:
data: 数値リスト
label: 出力ラベル
"""
print(f"\n{'='*30}")
print(f" {label}(n={len(data)}件)")
print(f"{'='*30}")
print(f" 平均: {np.mean(data):>10.1f} 円")
print(f" 中央値: {np.median(data):>10.1f} 円")
print(f" 最頻値: {statistics.mode(data):>10} 円")
print(f" 最小値: {np.min(data):>10} 円")
print(f" 最大値: {np.max(data):>10} 円")
print(f" 標準偏差: {np.std(data):>10.1f} 円")
describe(purchases, "外れ値あり")
describe(purchases_clean, "外れ値なし")
# 影響の大きさを確認
mean_diff = np.mean(purchases) - np.mean(purchases_clean)
median_diff = np.median(purchases) - np.median(purchases_clean)
print(f"\n【外れ値1件による変化】")
print(f" 平均の変化: +{mean_diff:.1f} 円({mean_diff/np.mean(purchases_clean)*100:.1f}%上昇)")
print(f" 中央値の変化: +{median_diff:.1f} 円({median_diff/np.median(purchases_clean)*100:.1f}%上昇)")
実行結果:
▶ 出力を見る
==============================
外れ値あり(n=15件)
==============================
平均: 1.729.3 円 ← 15000円に引っ張られて高い
中央値: 780.0 円 ← 影響ほぼなし
最頻値: 900 円
最小値: 500 円
最大値: 15000 円
標準偏差: 3657.4 円 ← バラツキが大きく見える
==============================
外れ値なし(n=14件)
==============================
平均: 744.3 円 ← 実態に近い
中央値: 775.0 円 ← ほぼ変わらず
最頻値: 900 円
最小値: 500 円
最大値: 1200 円
標準偏差: 183.1 円
【外れ値1件による変化】
平均の変化: +985.0 円(132.3%上昇)
中央値の変化: +5.0 円(0.6%上昇)タスク3 解答:
外れ値がある場合は 中央値(780円) の方が適切。
理由: 15000円という1件の異常値が平均を1729円まで引き上げており、通常の注文(500〜1200円台)を代表していない。中央値は並び順の「真ん中」を使うため、極端な値の影響を受けにくい。
🪜 Step-by-Step 解説
import numpy as np
data = [500, 800, 750]
print(np.mean(data)) # 683.33... ← 全部足して3で割る
print(np.median(data)) # 750.0 ← 並べて真ん中: [500, 750, 800]
print(np.std(data)) # 122.47... ← バラツキの大きさ
np. をつけるのは「numpyの関数を使う」という意味。
リストを渡すと自動で計算してくれる。
import statistics
print(statistics.mode([1, 2, 2, 3, 3, 3])) # → 3(最も多い値)
numpy に mode はない(numpyは数値計算専門のため)。
最頻値は Python 標準の statistics モジュールを使う。
purchases = [500, 800, 15000, 600]
clean = [x for x in purchases if x != 15000]
# → [500, 800, 600]
「for x in purchases で1つずつ取り出して、x != 15000 の条件を満たすものだけ残す」
📐 数学・統計の補足(文系向け)
標準偏差(std) はデータの「バラツキ具合」の指標。
- 標準偏差が小さい → データが平均の近くに固まっている
- 標準偏差が大きい → データが平均から遠くに散らばっている
外れ値ありの標準偏差: 3657円 vs 外れ値なし: 183円
→ 15000円1件だけで「バラツキが20倍大きく見えてしまう」のが分かる。
🏆 Kaggleでの実践的な使い方
Titanicコンペの Age(年齢)欠損値補完の判断:
import pandas as pd
df = pd.read_csv("titanic.csv")
# 年齢の分布を確認
mean_age = df["Age"].mean() # 29.7歳(欠損除く)
median_age = df["Age"].median() # 28.0歳
年齢は右裾が長い分布(高齢者が少しいる)なので:
- 平均(29.7): 高齢者に若干引っ張られる
- 中央値(28.0): より「典型的な乗客の年齢」
→ 外れ値・偏りがある列の欠損補完には中央値が使われることが多い。
上位Kagglerのコードでも fillna(df["Age"].median()) が頻出。
⚠️ よくある誤解・ミス
| 誤解・ミス | なぜ起こるか | 正しい理解 |
|---|---|---|
| 「平均が一番正確」と思う | 学校で平均をよく使うから | 外れ値があれば中央値の方が代表値として適切なことが多い |
statistics.mean() と np.mean() を混同 | 両方 mean という名前 | 結果は同じ。リストならstatistics、配列ならnpでOK |
| 最頻値が「一番重要な値」と思う | 「最も多い=代表的」と感じる | 連続データ(身長、金額)では意味が薄い。カテゴリデータ向き |
np.std() の結果が教科書と違う | numpyはデフォルトで母標準偏差 | 標本標準偏差は np.std(data, ddof=1) |
🚀 次のステップ
- 発展: ヒストグラム(
matplotlib)で分布の形を可視化し、平均と中央値のズレを視覚的に確認する - 次回予告: テーマ2「分散・標準偏差・外れ値」へ(3問連続理解で昇格)