📦 ステップ17: 四分位数と箱ひげ図の見方
データの分布を視覚的に理解しよう!
ステップ16では、相関関係について学びました。今回は、データの「分布」を理解するための「四分位数」と「箱ひげ図」を学びます。これらを使うと、データの散らばり具合や外れ値を一目で把握できます。
📖 このステップで学ぶこと
・四分位数とは何か
・四分位範囲(IQR)の計算
・箱ひげ図の読み方
・外れ値の検出方法
学習時間の目安: 2.5〜3時間
🎯 1. 四分位数とは?
まず、「四分位数」の基本を理解しましょう。
🔰 データを4等分する
四分位数(しぶんいすう)とは、データを小さい順に並べて4等分したときの区切りの値です。
💡 四分位数のイメージ
100人のテスト結果を点数順に並べたとき:
・25人目の点数 → Q1(第1四分位数)
・50人目の点数 → Q2(第2四分位数)= 中央値
・75人目の点数 → Q3(第3四分位数)
📘 四分位数の種類
📌 3つの四分位数
| 名前 | 位置 | 意味 |
| Q1(第1四分位数) | 下から25% | 下位25%と上位75%の境目 |
| Q2(第2四分位数) | 下から50% | 中央値(ちょうど真ん中) |
| Q3(第3四分位数) | 下から75% | 下位75%と上位25%の境目 |
📘 簡単な例で理解しよう
9個のデータで四分位数を確認してみましょう。
コード:四分位数の基本
# テストの点数(9人分)
scores = [30, 40, 50, 60, 70, 80, 85, 90, 95]
# データをソート(並べ替え)※すでに順番通りだが確認のため
sorted_scores = sorted(scores)
print("データ(小さい順):")
print(sorted_scores)
print()
# データ数
n = len(sorted_scores)
print(f"データ数: {n}個")
print()
# 位置を確認
print("【データの位置】")
for i, score in enumerate(sorted_scores):
position = (i + 1) / n * 100
print(f"位置{i+1}: {score}点 (下から{position:.0f}%)")
※ 画面が小さい場合は、コードブロックを横にスクロールできます
実行結果
データ(小さい順): [30, 40, 50, 60, 70, 80, 85, 90, 95] データ数: 9個 【データの位置】 位置1: 30点 (下から11%) 位置2: 40点 (下から22%) 位置3: 50点 (下から33%) 位置4: 60点 (下から44%) 位置5: 70点 (下から56%) 位置6: 80点 (下から67%) 位置7: 85点 (下から78%) 位置8: 90点 (下から89%) 位置9: 95点 (下から100%)
💡 この例での四分位数
Q1(25%の位置):50点付近(位置3付近)
Q2(50%の位置):70点(位置5、ちょうど真ん中)
Q3(75%の位置):85点付近(位置7付近)
📘 四分位数を計算する関数
四分位数を正確に計算する関数を作りましょう。
コード:四分位数を計算する関数
def calculate_quartiles(data):
"""
四分位数を計算する関数
引数:
data: 数値のリスト
戻り値:
(Q1, Q2, Q3) のタプル
"""
# データをソート(小さい順に並べる)
sorted_data = sorted(data)
n = len(sorted_data)
# Q2(中央値)を計算
if n % 2 == 0:
# データ数が偶数の場合:真ん中2つの平均
q2 = (sorted_data[n//2 - 1] + sorted_data[n//2]) / 2
else:
# データ数が奇数の場合:真ん中の値
q2 = sorted_data[n//2]
# Q1(下半分の中央値)を計算
lower_half = sorted_data[:n//2]
if len(lower_half) % 2 == 0:
q1 = (lower_half[len(lower_half)//2 - 1] + lower_half[len(lower_half)//2]) / 2
else:
q1 = lower_half[len(lower_half)//2]
# Q3(上半分の中央値)を計算
if n % 2 == 0:
upper_half = sorted_data[n//2:]
else:
upper_half = sorted_data[n//2 + 1:]
if len(upper_half) % 2 == 0:
q3 = (upper_half[len(upper_half)//2 - 1] + upper_half[len(upper_half)//2]) / 2
else:
q3 = upper_half[len(upper_half)//2]
return q1, q2, q3
※ 画面が小さい場合は、コードブロックを横にスクロールできます
💡 関数の解説
sorted(data)
・リストを小さい順にソートした新しいリストを返します
・元のリストは変更されません
n % 2 == 0
・%は「余り」を計算する演算子
・n % 2 が 0 なら偶数、1 なら奇数
sorted_data[:n//2]
・リストの前半(下半分)を取り出します
・//は整数除算(小数点以下切り捨て)
関数をテストしてみましょう。
コード:四分位数の計算テスト
# テスト1:9個のデータ(奇数個)
data1 = [10, 20, 30, 40, 50, 60, 70, 80, 90]
q1, q2, q3 = calculate_quartiles(data1)
print("【テスト1:データ数が奇数】")
print(f"データ: {data1}")
print(f"Q1(第1四分位数): {q1}")
print(f"Q2(第2四分位数): {q2} ← 中央値")
print(f"Q3(第3四分位数): {q3}")
print()
# テスト2:8個のデータ(偶数個)
data2 = [10, 20, 30, 40, 50, 60, 70, 80]
q1, q2, q3 = calculate_quartiles(data2)
print("【テスト2:データ数が偶数】")
print(f"データ: {data2}")
print(f"Q1(第1四分位数): {q1}")
print(f"Q2(第2四分位数): {q2} ← 中央値")
print(f"Q3(第3四分位数): {q3}")
※ 画面が小さい場合は、コードブロックを横にスクロールできます
実行結果
【テスト1:データ数が奇数】 データ: [10, 20, 30, 40, 50, 60, 70, 80, 90] Q1(第1四分位数): 25.0 Q2(第2四分位数): 50 ← 中央値 Q3(第3四分位数): 75.0 【テスト2:データ数が偶数】 データ: [10, 20, 30, 40, 50, 60, 70, 80] Q1(第1四分位数): 25.0 Q2(第2四分位数): 45.0 ← 中央値 Q3(第3四分位数): 65.0
📊 2. 四分位範囲(IQR)
四分位数を使って、データのばらつきを測る方法を学びましょう。
🔰 四分位範囲とは?
四分位範囲(IQR: Interquartile Range)とは、Q3からQ1を引いた値で、データの中央50%の広がりを示します。
📝 書き方:IQRの計算式
IQR = Q3 – Q1
IQRが大きい → データのばらつきが大きい
IQRが小さい → データが集中している
💡 IQRが便利な理由
IQRは外れ値の影響を受けにくいという特徴があります。
・標準偏差:全てのデータを使う → 外れ値に影響される
・IQR:中央の50%だけを見る → 外れ値に強い
コード:IQRの計算
def calculate_iqr(data):
"""
四分位数とIQRを計算する関数
戻り値:
辞書形式で Q1, Q2, Q3, IQR を返す
"""
q1, q2, q3 = calculate_quartiles(data)
iqr = q3 - q1
return {
'Q1': q1,
'Q2': q2,
'Q3': q3,
'IQR': iqr
}
# テスト1:ばらつきが小さいデータ
data1 = [48, 49, 50, 51, 52]
stats1 = calculate_iqr(data1)
print("【ばらつきが小さいデータ】")
print(f"データ: {data1}")
print(f"Q1: {stats1['Q1']}")
print(f"Q2: {stats1['Q2']}")
print(f"Q3: {stats1['Q3']}")
print(f"IQR: {stats1['IQR']}")
print()
print("=" * 40)
print()
# テスト2:ばらつきが大きいデータ
data2 = [10, 30, 50, 70, 90]
stats2 = calculate_iqr(data2)
print("【ばらつきが大きいデータ】")
print(f"データ: {data2}")
print(f"Q1: {stats2['Q1']}")
print(f"Q2: {stats2['Q2']}")
print(f"Q3: {stats2['Q3']}")
print(f"IQR: {stats2['IQR']}")
print()
print("💡 IQRが大きいほど、データのばらつきが大きい")
※ 画面が小さい場合は、コードブロックを横にスクロールできます
実行結果
【ばらつきが小さいデータ】 データ: [48, 49, 50, 51, 52] Q1: 48.5 Q2: 50 Q3: 51.5 IQR: 3.0 ======================================== 【ばらつきが大きいデータ】 データ: [10, 30, 50, 70, 90] Q1: 20.0 Q2: 50 Q3: 80.0 IQR: 60.0 💡 IQRが大きいほど、データのばらつきが大きい
📦 3. 箱ひげ図(ボックスプロット)
四分位数を視覚的に表現する「箱ひげ図」を学びましょう。
🔰 箱ひげ図とは?
箱ひげ図(はこひげず)は、四分位数を使ってデータの分布を視覚化したグラフです。英語では「Box Plot(ボックスプロット)」と呼ばれます。
📌 箱ひげ図の構成要素
| 部分 | 意味 |
| 箱(ボックス) | Q1からQ3まで(中央50%のデータ) |
| 箱の中の線 | Q2(中央値) |
| 下のひげ | 最小値(または正常範囲の下限) |
| 上のひげ | 最大値(または正常範囲の上限) |
| 点(●) | 外れ値(ひげの外にある値) |
📘 テキストで箱ひげ図を表現
実際にグラフを描く前に、テキストで箱ひげ図の構造を理解しましょう。
コード:箱ひげ図をテキストで表示
def show_box_plot_text(data, name="データ"):
"""
箱ひげ図をテキストで表示する関数
"""
# 基本統計量を計算
sorted_data = sorted(data)
q1, q2, q3 = calculate_quartiles(data)
iqr = q3 - q1
min_val = min(data)
max_val = max(data)
print(f"【{name}の箱ひげ図情報】")
print()
print(f"最小値: {min_val}")
print(f"Q1: {q1}")
print(f"Q2: {q2} ← 中央値")
print(f"Q3: {q3}")
print(f"最大値: {max_val}")
print(f"IQR: {iqr}")
print()
# 視覚的なイメージ
print("【箱ひげ図のイメージ】")
print()
print(f" 最小値 Q1 Q2 Q3 最大値")
print(f" {min_val} {q1} {q2} {q3} {max_val}")
print(f" |---------------[=========|=========]---------------|")
print(f" ひげ 箱(IQR = {iqr}) ひげ")
# テスト
test_scores = [45, 55, 60, 65, 70, 75, 80, 85, 90, 95]
show_box_plot_text(test_scores, "テストの点数")
※ 画面が小さい場合は、コードブロックを横にスクロールできます
実行結果
【テストの点数の箱ひげ図情報】
最小値: 45
Q1: 57.5
Q2: 72.5 ← 中央値
Q3: 87.5
最大値: 95
IQR: 30.0
【箱ひげ図のイメージ】
最小値 Q1 Q2 Q3 最大値
45 57.5 72.5 87.5 95
|---------------[=========|=========]---------------|
ひげ 箱(IQR = 30.0) ひげ
💡 箱ひげ図の読み方
箱の部分([ ]の中):データの中央50%がここに入る
箱の幅(IQR):広いほどばらつきが大きい
中央値の位置:箱の真ん中にあれば左右対称の分布
ひげの長さ:データの広がりを示す
🎯 4. 外れ値の検出
箱ひげ図の重要な機能の1つが「外れ値の検出」です。
🔰 外れ値とは?
外れ値(アウトライア)とは、他のデータから大きく離れた値のことです。データ入力ミスや特殊なケースを発見するのに役立ちます。
📝 書き方:外れ値の判定基準
下限 = Q1 – 1.5 × IQR
上限 = Q3 + 1.5 × IQR
この範囲の外にある値が外れ値と判定されます。
💡 なぜ1.5倍なのか?
1.5という数値は経験則に基づいています。
・正規分布の場合、この範囲外に入るデータは約0.7%
・厳しく検出したい場合は1.0倍を使うこともあります
・緩くしたい場合は2.0倍や3.0倍を使うこともあります
📘 外れ値を検出する関数
コード:外れ値検出関数
def detect_outliers(data):
"""
外れ値を検出する関数
戻り値:
辞書形式で各種統計量と外れ値リストを返す
"""
# 四分位数とIQRを計算
q1, q2, q3 = calculate_quartiles(data)
iqr = q3 - q1
# 外れ値の境界を計算
lower_bound = q1 - 1.5 * iqr
upper_bound = q3 + 1.5 * iqr
# 外れ値と正常値を分類
outliers = [] # 外れ値リスト
normal = [] # 正常値リスト
for x in data:
if x < lower_bound or x > upper_bound:
outliers.append(x)
else:
normal.append(x)
return {
'Q1': q1,
'Q2': q2,
'Q3': q3,
'IQR': iqr,
'lower_bound': lower_bound,
'upper_bound': upper_bound,
'outliers': outliers,
'normal': normal
}
※ 画面が小さい場合は、コードブロックを横にスクロールできます
💡 関数の解説
lower_bound = q1 – 1.5 * iqr
・Q1から IQR×1.5 を引いた値が下限
・これより小さい値は外れ値
if x < lower_bound or x > upper_bound:
・orは「または」の意味
・下限未満、または上限超過なら外れ値
関数をテストしてみましょう。
コード:外れ値の検出テスト
# 外れ値を含むデータ
data = [10, 12, 13, 14, 15, 16, 17, 18, 19, 20, 100]
result = detect_outliers(data)
print("【外れ値の検出】")
print()
print(f"データ: {data}")
print()
print(f"Q1: {result['Q1']}")
print(f"Q2: {result['Q2']}")
print(f"Q3: {result['Q3']}")
print(f"IQR: {result['IQR']}")
print()
print(f"正常範囲: {result['lower_bound']:.1f} 〜 {result['upper_bound']:.1f}")
print()
print(f"外れ値: {result['outliers']}")
print(f"正常値: {result['normal']}")
print()
if result['outliers']:
print("⚠️ 外れ値が見つかりました!")
print(" → データ入力ミスか、特殊なケースか確認してください")
※ 画面が小さい場合は、コードブロックを横にスクロールできます
実行結果
【外れ値の検出】 データ: [10, 12, 13, 14, 15, 16, 17, 18, 19, 20, 100] Q1: 13.0 Q2: 16 Q3: 19.0 IQR: 6.0 正常範囲: 4.0 〜 28.0 外れ値: [100] 正常値: [10, 12, 13, 14, 15, 16, 17, 18, 19, 20] ⚠️ 外れ値が見つかりました! → データ入力ミスか、特殊なケースか確認してください
⚠️ 外れ値の扱い方
外れ値を見つけたら、すぐに削除してはいけません!
1. 確認する
・データ入力ミスではないか?
・測定機器の異常ではないか?
2. 原因を調べる
・本当に異常な値なのか?
・特別な理由があるのか?
3. 判断する
・修正するか、削除するか、別途分析するか
📊 5. 実践例:ビジネスデータの分析
📘 例1:給与データの分析
コード:給与データの外れ値分析
# 社員の月給データ(万円)
salaries = [20, 22, 23, 24, 25, 26, 27, 28, 29, 30, 32, 35, 80]
result = detect_outliers(salaries)
print("【給与データ分析】")
print()
print(f"社員数: {len(salaries)}人")
print(f"給与データ: {salaries}")
print()
print("【四分位数】")
print(f"Q1(下位25%): {result['Q1']}万円")
print(f"Q2(中央値): {result['Q2']}万円")
print(f"Q3(上位25%): {result['Q3']}万円")
print(f"IQR: {result['IQR']}万円")
print()
print("【外れ値分析】")
print(f"正常範囲: {result['lower_bound']:.1f}万円 〜 {result['upper_bound']:.1f}万円")
if result['outliers']:
print(f"外れ値: {result['outliers']}万円")
print("→ 役員など特別な役職の可能性")
else:
print("外れ値: なし")
print()
# 平均と中央値の比較
mean_salary = sum(salaries) / len(salaries)
print("【平均と中央値の比較】")
print(f"平均給与: {mean_salary:.1f}万円")
print(f"中央給与: {result['Q2']}万円")
print()
print("💡 外れ値(80万円)があるため、平均が引き上げられています")
print(" 実態を見るなら中央値(27万円)の方が適切です")
※ 画面が小さい場合は、コードブロックを横にスクロールできます
実行結果
【給与データ分析】 社員数: 13人 給与データ: [20, 22, 23, 24, 25, 26, 27, 28, 29, 30, 32, 35, 80] 【四分位数】 Q1(下位25%): 23.5万円 Q2(中央値): 27万円 Q3(上位25%): 30.5万円 IQR: 7.0万円 【外れ値分析】 正常範囲: 13.0万円 〜 41.0万円 外れ値: [80]万円 → 役員など特別な役職の可能性 【平均と中央値の比較】 平均給与: 32.4万円 中央給与: 27万円 💡 外れ値(80万円)があるため、平均が引き上げられています 実態を見るなら中央値(27万円)の方が適切です
📘 例2:2つのクラスの比較
コード:クラス間の成績比較
# 2つのクラスのテスト結果
class_a = [65, 70, 72, 75, 78, 80, 82, 85, 88, 90]
class_b = [50, 60, 70, 75, 80, 85, 90, 95, 100, 100]
def analyze_class(scores, class_name):
"""クラスの分析"""
result = detect_outliers(scores)
print(f"【{class_name}】")
print(f"Q1: {result['Q1']:.1f}点")
print(f"Q2: {result['Q2']:.1f}点 ← 中央値")
print(f"Q3: {result['Q3']:.1f}点")
print(f"IQR: {result['IQR']:.1f}点 ← ばらつきの指標")
if result['outliers']:
print(f"外れ値: {result['outliers']}")
else:
print("外れ値: なし")
print()
analyze_class(class_a, "クラスA")
analyze_class(class_b, "クラスB")
# 比較
iqr_a = calculate_iqr(class_a)['IQR']
iqr_b = calculate_iqr(class_b)['IQR']
print("💡 分析結果:")
print(f" クラスA: IQR = {iqr_a:.1f}点(点数が集中している)")
print(f" クラスB: IQR = {iqr_b:.1f}点(点数にばらつきがある)")
※ 画面が小さい場合は、コードブロックを横にスクロールできます
実行結果
【クラスA】 Q1: 72.5点 Q2: 79.0点 ← 中央値 Q3: 85.5点 IQR: 13.0点 ← ばらつきの指標 外れ値: なし 【クラスB】 Q1: 67.5点 Q2: 82.5点 ← 中央値 Q3: 96.25点 IQR: 28.75点 ← ばらつきの指標 外れ値: なし 💡 分析結果: クラスA: IQR = 13.0点(点数が集中している) クラスB: IQR = 28.75点(点数にばらつきがある)
📝 練習問題
ここまで学んだことを、実際に手を動かして確認しましょう。
問題1:四分位数の計算(初級)
📋 問題
リスト [10, 20, 30, 40, 50, 60, 70, 80, 90] のQ1、Q2、Q3を計算してください。
解答例を見る
コード
data = [10, 20, 30, 40, 50, 60, 70, 80, 90]
q1, q2, q3 = calculate_quartiles(data)
iqr = q3 - q1
print(f"データ: {data}")
print()
print(f"Q1: {q1}")
print(f"Q2: {q2}")
print(f"Q3: {q3}")
print(f"IQR: {iqr}")
※ 画面が小さい場合は、コードブロックを横にスクロールできます
実行結果
データ: [10, 20, 30, 40, 50, 60, 70, 80, 90] Q1: 25.0 Q2: 50 Q3: 75.0 IQR: 50.0
問題2:外れ値の検出(中級)
📋 問題
リスト [12, 14, 15, 16, 17, 18, 19, 20, 21, 50] から外れ値を検出してください。
解答例を見る
コード
data = [12, 14, 15, 16, 17, 18, 19, 20, 21, 50]
result = detect_outliers(data)
print(f"データ: {data}")
print()
print(f"Q1: {result['Q1']}")
print(f"Q3: {result['Q3']}")
print(f"IQR: {result['IQR']}")
print()
print(f"正常範囲: {result['lower_bound']:.1f} 〜 {result['upper_bound']:.1f}")
print()
print(f"外れ値: {result['outliers']}")
print(f"正常値の個数: {len(result['normal'])}")
※ 画面が小さい場合は、コードブロックを横にスクロールできます
実行結果
データ: [12, 14, 15, 16, 17, 18, 19, 20, 21, 50] Q1: 15.25 Q3: 20.25 IQR: 5.0 正常範囲: 7.8 〜 27.8 外れ値: [50] 正常値の個数: 9
問題3:外れ値の影響分析(上級)
📋 問題
データ [20, 22, 24, 26, 28, 30, 32, 34, 36, 200] について、外れ値を除いた場合と除かない場合で、平均値と中央値がどう変わるか分析してください。
解答例を見る
コード
data = [20, 22, 24, 26, 28, 30, 32, 34, 36, 200]
result = detect_outliers(data)
# 全データの統計
mean_all = sum(data) / len(data)
_, q2_all, _ = calculate_quartiles(data)
# 外れ値を除いたデータの統計
normal_data = result['normal']
mean_normal = sum(normal_data) / len(normal_data)
_, q2_normal, _ = calculate_quartiles(normal_data)
print("【外れ値の影響分析】")
print()
print(f"元データ: {data}")
print(f"外れ値: {result['outliers']}")
print()
print("【全データ】")
print(f"平均値: {mean_all:.1f}")
print(f"中央値: {q2_all}")
print()
print("【外れ値除外後】")
print(f"平均値: {mean_normal:.1f}")
print(f"中央値: {q2_normal}")
print()
print("【変化】")
print(f"平均値の変化: {mean_all:.1f} → {mean_normal:.1f} ({mean_normal-mean_all:+.1f})")
print(f"中央値の変化: {q2_all} → {q2_normal} ({q2_normal-q2_all:+.1f})")
print()
print("💡 外れ値は平均値に大きく影響するが、中央値への影響は小さい")
※ 画面が小さい場合は、コードブロックを横にスクロールできます
実行結果
【外れ値の影響分析】 元データ: [20, 22, 24, 26, 28, 30, 32, 34, 36, 200] 外れ値: [200] 【全データ】 平均値: 45.2 中央値: 29.0 【外れ値除外後】 平均値: 28.0 中央値: 28.0 【変化】 平均値の変化: 45.2 → 28.0 (-17.2) 中央値の変化: 29.0 → 28.0 (-1.0) 💡 外れ値は平均値に大きく影響するが、中央値への影響は小さい
🎯 このステップのまとめ
✅ 学んだこと
✓ 四分位数:データを4等分する値(Q1、Q2、Q3)
✓ Q2は中央値と同じ
✓ IQR = Q3 – Q1(中央50%の広がり)
✓ 箱ひげ図:四分位数を視覚化したグラフ
✓ 外れ値:Q1 – 1.5×IQR 未満、または Q3 + 1.5×IQR 超過の値
✓ 外れ値は平均値に影響するが、中央値には影響が小さい
💡 次のステップに進む前に確認
以下のことができるようになったか確認しましょう:
□ 四分位数(Q1、Q2、Q3)を計算できる
□ IQRの意味を説明できる
□ 箱ひげ図の読み方を理解している
□ 外れ値の検出方法を理解している
これらができたら、Part 4のNumPyに進みましょう!
❓ よくある質問
Q1: Q2と中央値は同じですか?
A: はい、Q2(第2四分位数)は中央値と同じです。データを2等分する値です。
Q2: 外れ値は必ず削除すべきですか?
A: いいえ、自動的に削除してはいけません。まずデータ入力ミスかどうか確認し、本当に異常な値なのか、特別な意味がある値なのかを判断してから対処します。
Q3: IQRと標準偏差の違いは?
A: IQRは中央50%の広がり、標準偏差は全データのばらつきです。IQRは外れ値の影響を受けにくいという利点があります。外れ値が多いデータではIQRの方が適切なことがあります。
Q4: なぜ1.5×IQRなのですか?
A: 経験則です。正規分布の場合、この範囲外に約0.7%のデータが入ります。厳しく検出したい場合は1.0×IQR、緩くしたい場合は2.0×IQRを使うこともあります。
Q5: 箱ひげ図はいつ使いますか?
A: 複数のグループを比較したい時に便利です。例えば、複数の店舗の売上分布、複数のクラスのテスト結果などを並べて比較できます。データの分布の形が一目で分かります。
学習メモ
Pythonデータ分析入門 - Step 17