Step 17:四分位数と箱ひげ図の見方

📦 ステップ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

📋 過去のメモ一覧
#artnasekai #学習メモ
LINE