IQR(四分位範囲)とは?わかりやすく解説

IQR(Interquartile Range: 四分位範囲)は、データの散らばり具合を表す基本的な統計量です。標準偏差と並んで、データのばらつきを測るための重要な指標として、論文やデータ分析の場面で非常によく登場します。

本記事では、IQRの定義から外れ値検出への応用、箱ひげ図との関係、Pythonでの実装までを解説します。

本記事の内容

  • 四分位数とIQRの定義
  • IQRによる外れ値検出
  • 箱ひげ図との関係
  • Pythonでの実装と可視化

前提知識

この記事を読む前に、以下の概念を理解しておくと読みやすくなります。

  • 中央値の定義
  • 基本的な記述統計(平均、分散、標準偏差)

四分位数とは

IQRを理解するために、まず四分位数(Quartile)について確認しましょう。

データを小さい順に並べたとき、

  • 第1四分位数(Q1): データの25%点(下位25%と上位75%の境界)
  • 第2四分位数(Q2): データの50%点(中央値)
  • 第3四分位数(Q3): データの75%点(下位75%と上位25%の境界)

例えば、データ $\{2, 4, 6, 8, 10, 12, 14, 16, 18, 20\}$ の場合:

$$ Q1 = 5, \quad Q2 = 11, \quad Q3 = 17 $$

四分位数は、データを4等分する3つの値です。

IQRの定義

IQR(四分位範囲)は、第3四分位数と第1四分位数の差として定義されます。

$$ \begin{equation} \text{IQR} = Q3 – Q1 \end{equation} $$

上の例では $\text{IQR} = 17 – 5 = 12$ です。

IQRは「データの中央50%が広がっている範囲」を表しています。

IQRの特徴

特徴 説明
ロバスト性 外れ値の影響を受けにくい(標準偏差と異なる)
直感的 「データの真ん中半分の広がり」という明快な意味
分布非依存 正規分布を仮定しない
順序統計量 データの値ではなく順位に基づく

IQRと標準偏差の比較

データの散らばりを測る指標として、IQRと標準偏差 $\sigma$ はよく比較されます。

データが正規分布 $\mathcal{N}(\mu, \sigma^2)$ に従う場合、四分位数は次のようになります。

$$ \begin{align} Q1 &= \mu – 0.6745\sigma \\ Q3 &= \mu + 0.6745\sigma \end{align} $$

したがって、

$$ \begin{equation} \text{IQR} = Q3 – Q1 = 2 \times 0.6745\sigma = 1.349\sigma \end{equation} $$

正規分布のもとでは $\text{IQR} \approx 1.35\sigma$ という関係があります。

しかし、外れ値が存在する場合、標準偏差は大きく影響を受けるのに対し、IQRはほとんど影響を受けません。これがIQRの「ロバスト性」です。

IQRによる外れ値検出

IQRは外れ値の検出に広く使われています。Tukey(テューキー)の方法では、次の範囲外のデータ点を外れ値とみなします。

$$ \begin{equation} \text{外れ値}: x < Q1 - 1.5 \times \text{IQR} \quad \text{または} \quad x > Q3 + 1.5 \times \text{IQR} \end{equation} $$

さらに極端な値($1.5$ の代わりに $3.0$ を使う)は極端な外れ値とみなされます。

なぜ1.5倍なのか?

データが正規分布に従う場合、$Q1 – 1.5 \times \text{IQR}$ と $Q3 + 1.5 \times \text{IQR}$ はそれぞれ

$$ \begin{align} Q1 – 1.5 \times \text{IQR} &= \mu – 0.6745\sigma – 1.5 \times 1.349\sigma = \mu – 2.698\sigma \\ Q3 + 1.5 \times \text{IQR} &= \mu + 0.6745\sigma + 1.5 \times 1.349\sigma = \mu + 2.698\sigma \end{align} $$

に対応します。正規分布で $\mu \pm 2.698\sigma$ の外側に入る確率は約0.7%(両側合計)です。つまり、正規分布を仮定した場合、約99.3%のデータがこの範囲内に収まります。

箱ひげ図(Box Plot)との関係

箱ひげ図は、IQRを視覚的に表現したグラフです。

箱ひげ図の各要素:

  • 箱の下端: Q1
  • 箱の中の線: Q2(中央値)
  • 箱の上端: Q3
  • 箱の高さ: IQR
  • 下のひげ: $Q1 – 1.5 \times \text{IQR}$ 以上の最小値
  • 上のひげ: $Q3 + 1.5 \times \text{IQR}$ 以下の最大値
  • : ひげの外側の値(外れ値)

Pythonでの実装

IQRの計算、外れ値検出、箱ひげ図の描画をPythonで実装します。

import numpy as np
import matplotlib.pyplot as plt

np.random.seed(42)

# データ生成(正規分布 + 外れ値)
n = 200
data_normal = np.random.normal(loc=50, scale=10, size=n)
# 外れ値を追加
outliers = np.array([5, 10, 95, 100, 110])
data = np.concatenate([data_normal, outliers])

# IQRの計算
Q1 = np.percentile(data, 25)
Q2 = np.percentile(data, 50)
Q3 = np.percentile(data, 75)
IQR = Q3 - Q1

# 外れ値の検出
lower_fence = Q1 - 1.5 * IQR
upper_fence = Q3 + 1.5 * IQR
outlier_mask = (data < lower_fence) | (data > upper_fence)
n_outliers = np.sum(outlier_mask)

print(f"Q1 = {Q1:.2f}")
print(f"Q2 (中央値) = {Q2:.2f}")
print(f"Q3 = {Q3:.2f}")
print(f"IQR = {IQR:.2f}")
print(f"下限フェンス = {lower_fence:.2f}")
print(f"上限フェンス = {upper_fence:.2f}")
print(f"外れ値の数 = {n_outliers}")
print(f"外れ値: {data[outlier_mask]}")

# IQRと標準偏差の比較(ロバスト性のデモ)
# 汚染率を変えて比較
contamination_rates = np.linspace(0, 0.2, 20)
iqr_values = []
std_values = []

for rate in contamination_rates:
    n_contam = int(n * rate)
    clean_data = np.random.normal(50, 10, n)
    if n_contam > 0:
        contam_data = np.random.normal(150, 5, n_contam)  # 外れ値
        mixed_data = np.concatenate([clean_data, contam_data])
    else:
        mixed_data = clean_data
    iqr_values.append(np.percentile(mixed_data, 75) - np.percentile(mixed_data, 25))
    std_values.append(np.std(mixed_data))

# 可視化
fig, axes = plt.subplots(2, 2, figsize=(14, 10))

# 左上: ヒストグラムと四分位数
axes[0, 0].hist(data, bins=30, alpha=0.7, color='steelblue', edgecolor='black')
axes[0, 0].axvline(Q1, color='orange', linewidth=2, linestyle='--', label=f'Q1 = {Q1:.1f}')
axes[0, 0].axvline(Q2, color='red', linewidth=2, linestyle='-', label=f'Q2 = {Q2:.1f}')
axes[0, 0].axvline(Q3, color='green', linewidth=2, linestyle='--', label=f'Q3 = {Q3:.1f}')
axes[0, 0].axvline(lower_fence, color='purple', linewidth=1.5, linestyle=':',
                   label=f'Lower fence = {lower_fence:.1f}')
axes[0, 0].axvline(upper_fence, color='purple', linewidth=1.5, linestyle=':',
                   label=f'Upper fence = {upper_fence:.1f}')
axes[0, 0].set_xlabel('Value')
axes[0, 0].set_ylabel('Count')
axes[0, 0].set_title('Histogram with Quartiles and Fences')
axes[0, 0].legend(fontsize=8)

# 右上: 箱ひげ図
bp = axes[0, 1].boxplot(data, vert=True, patch_artist=True,
                        boxprops=dict(facecolor='lightblue', color='navy'),
                        medianprops=dict(color='red', linewidth=2),
                        flierprops=dict(marker='o', markerfacecolor='red', markersize=8))
axes[0, 1].set_ylabel('Value')
axes[0, 1].set_title('Box Plot')
# IQRの範囲を注釈
axes[0, 1].annotate('', xy=(1.15, Q3), xytext=(1.15, Q1),
                    arrowprops=dict(arrowstyle='<->', color='orange', lw=2))
axes[0, 1].text(1.2, (Q1+Q3)/2, f'IQR = {IQR:.1f}', fontsize=11, color='orange',
               va='center')

# 左下: 外れ値の検出結果
normal_mask = ~outlier_mask
axes[1, 0].scatter(np.arange(len(data))[normal_mask], data[normal_mask],
                  c='steelblue', s=15, alpha=0.5, label='Normal')
axes[1, 0].scatter(np.arange(len(data))[outlier_mask], data[outlier_mask],
                  c='red', s=50, marker='x', linewidths=2, label='Outlier')
axes[1, 0].axhline(upper_fence, color='purple', linestyle='--', alpha=0.5,
                   label=f'Upper fence ({upper_fence:.1f})')
axes[1, 0].axhline(lower_fence, color='purple', linestyle='--', alpha=0.5,
                   label=f'Lower fence ({lower_fence:.1f})')
axes[1, 0].set_xlabel('Index')
axes[1, 0].set_ylabel('Value')
axes[1, 0].set_title('Outlier Detection using IQR')
axes[1, 0].legend(fontsize=9)

# 右下: IQRとstdのロバスト性比較
axes[1, 1].plot(contamination_rates * 100, iqr_values / iqr_values[0], 'b-o',
               markersize=4, label='IQR (normalized)')
axes[1, 1].plot(contamination_rates * 100, std_values / std_values[0], 'r-o',
               markersize=4, label='Std (normalized)')
axes[1, 1].set_xlabel('Contamination Rate [%]')
axes[1, 1].set_ylabel('Normalized Value')
axes[1, 1].set_title('Robustness: IQR vs Standard Deviation')
axes[1, 1].legend()
axes[1, 1].grid(True, alpha=0.3)

plt.tight_layout()
plt.show()

左上のグラフでは、ヒストグラム上にQ1、Q2(中央値)、Q3とフェンス(外れ値の判定閾値)を示しています。右上の箱ひげ図では、IQRが箱の高さとして視覚的に表現されています。左下では、IQR法による外れ値検出の結果を示し、右下ではIQRと標準偏差のロバスト性を比較しています。外れ値の混入率が増えても、IQRはほとんど変化しないのに対し、標準偏差は大きく膨らんでいることがわかります。

まとめ

本記事では、IQR(四分位範囲)について解説しました。

  • IQRは $Q3 – Q1$ で定義される、データの中央50%の広がりを表す統計量である
  • 外れ値に対してロバスト(頑健)で、標準偏差よりも汚染データに強い
  • Tukeyの方法により、$Q1 – 1.5 \times \text{IQR}$ 未満または $Q3 + 1.5 \times \text{IQR}$ 超のデータを外れ値として検出できる
  • 箱ひげ図はIQRを視覚的に表現するグラフであり、データの分布を直感的に把握できる

次のステップとして、以下の記事も参考にしてください。