第4回 相関分析
はじめに
本シリーズ「Pythonで学ぶデータ分析実践」の第4回では、相関分析について解説します。
前回までの基本統計量の計算では、主に一つの変数(単変量)を対象にしていました。しかし、実際のデータ分析では複数の変数を同時に扱い、変数間の関係性を明らかにすることが重要です。本記事では、2つの変数がどのような関係にあるかを数値で表す「相関分析」について、共分散・相関係数・相関比・連関係数の計算方法と実践的なPythonコードを交えて解説します。
シリーズ全体の構成(全12回)
| 回 | テーマ |
|---|---|
| 第1回 | データ分析とは |
| 第2回 | データの前処理 |
| 第3回 | データの把握 |
| 第4回 | 相関分析(本記事) |
| 第5回 | 統計的推定 |
| 第6回 | 統計的検定 |
| 第7回 | 分散分析 |
| 第8回 | 回帰分析 |
| 第9回 | 時系列データ分析 |
| 第10回 | クラス分類 |
| 第11回 | クラスタリング |
| 第12回 | 次元削減 |
4-1. 相関分析とは
2つの変数がお互いに関係し合い、一方が変化すると他方もそれに応じて変化するような関係を持つ場合、2つの変数には相関関係があるとみなされます。この2変数間に強い相関があるかどうかを明らかにする分析を相関分析といいます。
変数の種類と分析手法
相関分析を行う前に、扱うデータが「単変量」「二変量」「多変量」のどれに該当するかを理解しておく必要があります。
単変量(一変量)
一つの対象に変数が1つしかないデータです。平均、分散、標準偏差などの基本統計量で分析します。
| ID | 国語 |
|---|---|
| 1 | 53 |
| 2 | 92 |
| 3 | 75 |
| 4 | 46 |
二変量
1つの対象に2種類の変数があるデータです。相関分析、単回帰分析に適しています。
| ID | 国語 | 英語 |
|---|---|---|
| 1 | 53 | 60 |
| 2 | 92 | 77 |
| 3 | 75 | 80 |
| 4 | 46 | 35 |
多変量
1つの対象に3つ以上の変数があるデータです。重回帰分析、主成分分析、クラスタ分析などに用います。
| ID | 国語 | 英語 | 数学 |
|---|---|---|---|
| 1 | 53 | 60 | 55 |
| 2 | 92 | 77 | 85 |
| 3 | 75 | 80 | 56 |
| 4 | 46 | 35 | 72 |
量的変数と質的変数
分析対象となる変数はその性質により2つに分類されます。
| 変数の種類 | 説明 | 例 |
|---|---|---|
| 量的変数 | 連続した値を持ち数値化されている | 売上数、気温、消費電力、身長 |
| 質的変数 | 連続しない値(カテゴリ) | 都道府県、商品名、性別、血液型 |
相関分析の種類
2変数間の関連を調べるには、変数の組み合わせにより以下の3つのパターンがあります。
| 組み合わせ | 分析手法 | 指標 | 例 |
|---|---|---|---|
| 量的変数 × 量的変数 | 相関係数 | ピアソンの積率相関係数 R | 気温とビール売上 |
| 量的変数 × 質的変数 | 相関比 | η²(イータ二乗) | 地域と消費電力 |
| 質的変数 × 質的変数 | 連関係数 | クラメールのV | 季節と売れ筋商品 |
相関の方向
| 種類 | 説明 | 散布図の傾向 |
|---|---|---|
| 正の相関 | 一方が増加するともう一方も増加 | 右上がり |
| 負の相関 | 一方が増加するともう一方は減少 | 右下がり |
| 無相関 | 明確な傾向がない | バラバラ |
4-2. 共分散
相関係数を理解するためには、まず共分散の概念を知る必要があります。共分散は2変数の偏差の積の平均で、2つの変数がどの程度一緒に変動するかを表します。
共分散の定義
n個の2変数データ $(x_1, y_1), (x_2, y_2), \ldots, (x_n, y_n)$ に対して、XとYの共分散は以下で定義されます。
$$S_{xy} = \frac{1}{n}\sum_{i=1}^{n}(x_i - \bar{x})(y_i - \bar{y})$$
共分散の解釈
共分散は、Xの平均とYの平均が交差する点を原点とした4象限で考えると直感的に理解できます。
Y
↑
II | I
(−)(+) | (+)(+) → 正の積
--------|-------→ X
III | IV
(−)(−) | (+)(−) → 負の積
|
- I象限(右上): X > 平均、Y > 平均 → 偏差の積は正
- III象限(左下): X < 平均、Y < 平均 → 偏差の積は正
- II象限(左上): X < 平均、Y > 平均 → 偏差の積は負
- IV象限(右下): X > 平均、Y < 平均 → 偏差の積は負
データが主にI・III象限に分布 → 共分散は正(正の相関)
データが主にII・IV象限に分布 → 共分散は負(負の相関)
Pythonでの共分散の計算
import numpy as np
import pandas as pd
# サンプルデータ(気温とビール売上本数)
temperature = np.array([22, 25, 28, 30, 32, 35, 27, 24, 33, 29])
beer_sales = np.array([150, 180, 220, 250, 280, 320, 200, 170, 300, 230])
# 共分散の計算
# 方法1: NumPyで計算
cov_matrix = np.cov(temperature, beer_sales, ddof=0) # 母共分散
print(f"共分散行列:\n{cov_matrix}")
print(f"XとYの共分散: {cov_matrix[0, 1]:.2f}")
# 方法2: 手動計算
mean_x = np.mean(temperature)
mean_y = np.mean(beer_sales)
covariance = np.mean((temperature - mean_x) * (beer_sales - mean_y))
print(f"手動計算の共分散: {covariance:.2f}")
# 方法3: Pandasで計算
df = pd.DataFrame({"気温": temperature, "ビール売上": beer_sales})
print(f"\nPandas共分散行列:\n{df.cov()}")
共分散の限界
共分散の値はデータの単位に依存するため、異なるデータ同士の比較が困難です。
例:
- 気温(℃)とビール売上(本)→ 共分散の単位は「℃×本」
- 身長(cm)と体重(kg)→ 共分散の単位は「cm×kg」
この問題を解決するために、共分散を標準化したのが相関係数です。
4-3. 相関係数
量的変数 × 量的変数の関係を調べる場合、相関係数R(ピアソンの積率相関係数)を算出するのが一般的です。相関係数は共分散をそれぞれの標準偏差で割ることで正規化した値で、-1から1の範囲をとります。
相関係数の定義
$$R = \frac{S_{xy}}{S_x \cdot S_y} = \frac{\sum_{i=1}^{n}(x_i - \bar{x})(y_i - \bar{y})}{\sqrt{\sum_{i=1}^{n}(x_i - \bar{x})^2} \cdot \sqrt{\sum_{i=1}^{n}(y_i - \bar{y})^2}}$$
- $S_{xy}$: XとYの共分散
- $S_x$: Xの標準偏差
- $S_y$: Yの標準偏差
相関係数と相関の強さの目安
| 相関係数の範囲 | 相関の強さ |
|---|---|
| 0.0 〜 ±0.2 | (ほとんど)相関がない |
| ±0.2 〜 ±0.4 | 弱い相関がある |
| ±0.4 〜 ±0.7 | 相関がある |
| ±0.7 〜 ±0.9 | 強い相関がある |
| ±0.9 〜 ±1.0 | (ほぼ)完全な相関がある |
散布図と相関係数の関係
import numpy as np
import matplotlib.pyplot as plt
import japanize_matplotlib
np.random.seed(42)
fig, axes = plt.subplots(2, 3, figsize=(15, 10))
fig.suptitle("相関係数と散布図の関係", fontsize=16)
# さまざまな相関を持つデータを生成
correlations = [1.0, 0.8, 0.4, 0.0, -0.6, -0.9]
titles = ["R≈1.0(完全な正の相関)", "R≈0.8(強い正の相関)", "R≈0.4(中程度の相関)",
"R≈0.0(無相関)", "R≈-0.6(負の相関)", "R≈-0.9(強い負の相関)"]
for idx, (r, title) in enumerate(zip(correlations, titles)):
ax = axes[idx // 3, idx % 3]
# 相関のあるデータを生成
mean = [0, 0]
cov = [[1, r], [r, 1]]
x, y = np.random.multivariate_normal(mean, cov, 100).T
ax.scatter(x, y, alpha=0.6, s=30, edgecolors='black', linewidths=0.5)
ax.set_title(title, fontsize=11)
ax.set_xlim(-4, 4)
ax.set_ylim(-4, 4)
ax.grid(alpha=0.3)
ax.axhline(0, color='gray', linewidth=0.5)
ax.axvline(0, color='gray', linewidth=0.5)
plt.tight_layout()
plt.show()
Pythonでの相関係数の計算
import numpy as np
import pandas as pd
# サンプルデータ(気温とビール売上)
temperature = np.array([22, 25, 28, 30, 32, 35, 27, 24, 33, 29])
beer_sales = np.array([150, 180, 220, 250, 280, 320, 200, 170, 300, 230])
# 方法1: NumPyで計算
corr_matrix = np.corrcoef(temperature, beer_sales)
print(f"相関係数: {corr_matrix[0, 1]:.4f}")
# 方法2: Pandasで計算
df = pd.DataFrame({"気温": temperature, "ビール売上": beer_sales})
print(f"\n相関行列:\n{df.corr()}")
# 方法3: SciPyで計算(p値付き)
from scipy import stats
r, p_value = stats.pearsonr(temperature, beer_sales)
print(f"\n相関係数 R: {r:.4f}")
print(f"p値: {p_value:.6f}")
if p_value < 0.05:
print("→ 統計的に有意な相関あり(p < 0.05)")
else:
print("→ 統計的に有意な相関なし")
相関行列とヒートマップ
複数の変数間の相関を一度に確認する場合、相関行列をヒートマップで可視化すると便利です。
import seaborn as sns
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np
import japanize_matplotlib
# 複数変数のサンプルデータ
np.random.seed(42)
n = 100
df = pd.DataFrame({
"気温": np.random.normal(25, 5, n),
"湿度": np.random.normal(60, 10, n),
"ビール売上": None,
"アイス売上": None,
"コート売上": None,
})
df["ビール売上"] = 50 + 8 * df["気温"] + np.random.normal(0, 20, n)
df["アイス売上"] = 30 + 10 * df["気温"] + np.random.normal(0, 15, n)
df["コート売上"] = 500 - 12 * df["気温"] + np.random.normal(0, 25, n)
# 相関行列のヒートマップ
plt.figure(figsize=(8, 6))
corr = df.corr()
sns.heatmap(corr, annot=True, cmap="coolwarm", fmt=".2f",
vmin=-1, vmax=1, square=True, linewidths=0.5)
plt.title("相関行列のヒートマップ", fontsize=14)
plt.show()
スピアマンの順位相関係数
ピアソンの相関係数は線形関係を前提としていますが、非線形な単調関係を捉えたい場合はスピアマンの順位相関係数を使います。
from scipy import stats
# スピアマンの順位相関係数
rho, p_value = stats.spearmanr(temperature, beer_sales)
print(f"スピアマンの順位相関係数: {rho:.4f}")
print(f"p値: {p_value:.6f}")
# ケンドールの順位相関係数
tau, p_value = stats.kendalltau(temperature, beer_sales)
print(f"ケンドールの順位相関係数: {tau:.4f}")
| 相関係数の種類 | 特徴 | 使用場面 |
|---|---|---|
| ピアソン | 線形関係の強さを測定 | 両変数が正規分布に近い場合 |
| スピアマン | 単調関係の強さを測定(順位ベース) | 外れ値がある場合、非線形の場合 |
| ケンドール | 順位の一致度を測定 | データ数が少ない場合 |
4-4. 相関比
量的変数 × 質的変数の関係を調べるには相関比(η²: イータ二乗)を算出します。相関比は、質的変数のカテゴリによって量的変数の値がどの程度異なるかを表す指標です。
例)都道府県と消費電力、旅行先と年齢、性別とテストスコア
相関比の定義
$$\eta^2 = \frac{グループ間変動}{全体変動}$$
- 全体変動: 全データの偏差平方和(個々のデータと全体平均の差の2乗の合計)
- グループ間変動: 各グループの平均と全体平均の差の2乗に各グループのデータ数を掛けたものの合計
相関比の閾値の目安
| 相関比の範囲 | 相関の強さ |
|---|---|
| 0.0 〜 0.1 | (ほとんど)相関がない |
| 0.1 〜 0.3 | 弱い相関がある |
| 0.3 〜 1.0 | 強い相関がある |
相関比は必ず0〜1の値をとります(負の値はありません)。
相関比の計算例
旅行先(質的変数)と年齢(量的変数)の関係を調べます。
| 年齢 | 旅行先 |
|---|---|
| 35 | 北海道 |
| 19 | 沖縄 |
| 23 | 沖縄 |
| 18 | 沖縄 |
| 42 | 北海道 |
| 54 | 北海道 |
| 26 | 沖縄 |
| 21 | 沖縄 |
| 48 | 北海道 |
| 61 | 北海道 |
計算手順
(1) グループ別の平均を計算
| 旅行先 | 平均年齢 | データ数 |
|---|---|---|
| 北海道 | 48.0 | 5 |
| 沖縄 | 21.4 | 5 |
| 全体 | 34.7 | 10 |
(2) グループ間変動を計算
$$グループ間変動 = n_{北海道} \times (48.0 - 34.7)^2 + n_{沖縄} \times (21.4 - 34.7)^2$$
$$= 5 \times 13.3^2 + 5 \times (-13.3)^2 = 5 \times 176.89 + 5 \times 176.89 = 1768.9$$
(3) 全体変動を計算
$$全体変動 = \sum_{i=1}^{n}(x_i - \bar{x})^2 = (35-34.7)^2 + (19-34.7)^2 + \cdots = 2480.1$$
(4) 相関比を算出
$$\eta^2 = \frac{1768.9}{2480.1} \approx 0.713$$
→ 相関比0.713は「強い相関がある」に該当し、旅行先によって年齢層に明確な差があることがわかります。
Pythonでの相関比の計算
import numpy as np
import pandas as pd
# サンプルデータ
df = pd.DataFrame({
"age": [35, 19, 23, 18, 42, 54, 26, 21, 48, 61],
"destination": ["北海道", "沖縄", "沖縄", "沖縄", "北海道", "北海道", "沖縄", "沖縄", "北海道", "北海道"]
})
def correlation_ratio(categories, values):
"""相関比(η²)を計算する関数"""
categories = np.array(categories)
values = np.array(values)
# 全体平均
overall_mean = values.mean()
# 全体変動
total_variation = np.sum((values - overall_mean) ** 2)
# グループ間変動
between_variation = 0
for cat in np.unique(categories):
group = values[categories == cat]
group_mean = group.mean()
between_variation += len(group) * (group_mean - overall_mean) ** 2
# 相関比
eta_squared = between_variation / total_variation
return eta_squared
# 相関比の計算
eta2 = correlation_ratio(df["destination"], df["age"])
print(f"相関比 η²: {eta2:.4f}")
# 判定
if eta2 < 0.1:
print("→ ほとんど相関がない")
elif eta2 < 0.3:
print("→ 弱い相関がある")
else:
print("→ 強い相関がある")
相関比の可視化
量的変数×質的変数の関係は、箱ひげ図やバイオリンプロットで可視化すると直感的に把握できます。
import seaborn as sns
import matplotlib.pyplot as plt
import japanize_matplotlib
fig, axes = plt.subplots(1, 2, figsize=(12, 5))
# 箱ひげ図
sns.boxplot(x="destination", y="age", data=df, ax=axes[0], palette="Set2")
axes[0].set_title(f"旅行先別の年齢分布(箱ひげ図)\nη²={eta2:.3f}", fontsize=12)
axes[0].set_xlabel("旅行先")
axes[0].set_ylabel("年齢")
# ストリップロット(個々のデータ点を表示)
sns.stripplot(x="destination", y="age", data=df, ax=axes[1],
size=10, palette="Set2", jitter=True)
axes[1].set_title(f"旅行先別の年齢分布(ストリップロット)\nη²={eta2:.3f}", fontsize=12)
axes[1].set_xlabel("旅行先")
axes[1].set_ylabel("年齢")
plt.tight_layout()
plt.show()
複数カテゴリの相関比
# より実践的な例: 部署と年収の関係
np.random.seed(42)
df_company = pd.DataFrame({
"salary": np.concatenate([
np.random.normal(500, 50, 30), # 営業部
np.random.normal(650, 80, 30), # 開発部
np.random.normal(550, 60, 30), # マーケ部
]),
"department": ["営業部"]*30 + ["開発部"]*30 + ["マーケ部"]*30
})
eta2 = correlation_ratio(df_company["department"], df_company["salary"])
print(f"部署と年収の相関比: {eta2:.4f}")
# 可視化
plt.figure(figsize=(8, 5))
sns.boxplot(x="department", y="salary", data=df_company, palette="Set3")
plt.title(f"部署別の年収分布(η²={eta2:.3f})", fontsize=13)
plt.xlabel("部署")
plt.ylabel("年収(万円)")
plt.show()
4-5. 連関係数
質的変数 × 質的変数の関係を調べるためには、クロス集計表を作成し、**連関係数(クラメールのV)**を算出します。
例)季節と売れ筋商品、理系/文系と数学の好き嫌い、性別と購入商品カテゴリ
連関係数の閾値の目安
| 連関係数の範囲 | 相関の強さ |
|---|---|
| 0.0 〜 0.1 | (ほとんど)相関がない |
| 0.1 〜 0.3 | 弱い相関がある |
| 0.3 〜 1.0 | 強い相関がある |
連関係数は必ず0〜1の値をとります。
クラメールのVの定義
$$V = \sqrt{\frac{\chi^2}{N \times (k - 1)}}$$
- $\chi^2$: カイ二乗値
- $N$: 全体データ数
- $k$: 2つの項目のうち、種類数が少ない方の種類数 - 1
カイ二乗値とは
カイ二乗値は、期待値(データに偏りがなかった場合の値)と観測値(実際の値)とのズレを数値化したものです。
$$\chi^2 = \sum \frac{(観測値 - 期待値)^2}{期待値}$$
計算例:理系/文系と数学の好き嫌い
150名を対象に、理系/文系の別と数学の好き嫌いを調査した結果です。
(1) 観測値(実際のデータ)
| 好き | 嫌い | 合計 | |
|---|---|---|---|
| 理系 | 43 | 37 | 80 |
| 文系 | 10 | 60 | 70 |
| 合計 | 53 | 97 | 150 |
(2) 期待値の計算
期待値 = (行合計 × 列合計) / 全体合計
| 好き | 嫌い | |
|---|---|---|
| 理系 | 53×80/150 = 28.3 | 97×80/150 = 51.7 |
| 文系 | 53×70/150 = 24.7 | 97×70/150 = 45.3 |
(3) ズレの計算
$$\frac{(観測値 - 期待値)^2}{期待値}$$
| 好き | 嫌い | |
|---|---|---|
| 理系 | (43-28.3)²/28.3 = 7.64 | (37-51.7)²/51.7 = 4.18 |
| 文系 | (10-24.7)²/24.7 = 8.74 | (60-45.3)²/45.3 = 4.77 |
(4) カイ二乗値
$$\chi^2 = 7.64 + 4.18 + 8.74 + 4.77 = 25.33$$
(5) 連関係数(クラメールのV)
$$V = \sqrt{\frac{25.33}{150 \times (2-1)}} = \sqrt{\frac{25.33}{150}} = \sqrt{0.169} \approx 0.41$$
→ 連関係数0.41は「強い相関がある」に該当し、理系/文系の別と数学の好き嫌いには関連があることがわかります。
Pythonでの連関係数の計算
import numpy as np
import pandas as pd
from scipy import stats
# クロス集計表の作成
observed = np.array([[43, 37], # 理系: 好き, 嫌い
[10, 60]]) # 文系: 好き, 嫌い
# カイ二乗検定
chi2, p_value, dof, expected = stats.chi2_contingency(observed)
print(f"カイ二乗値: {chi2:.4f}")
print(f"p値: {p_value:.6f}")
print(f"自由度: {dof}")
print(f"期待値:\n{expected}")
# クラメールのVの計算
n = observed.sum()
k = min(observed.shape) - 1
cramers_v = np.sqrt(chi2 / (n * k))
print(f"\nクラメールのV: {cramers_v:.4f}")
# 判定
if cramers_v < 0.1:
print("→ ほとんど相関がない")
elif cramers_v < 0.3:
print("→ 弱い相関がある")
else:
print("→ 強い相関がある")
実践的な例:クロス集計表の作成と連関係数
import pandas as pd
import numpy as np
from scipy import stats
# より実践的なデータ
np.random.seed(42)
n = 200
# 季節と売れ筋ドリンクのデータを生成
seasons = np.random.choice(["春", "夏", "秋", "冬"], n)
drinks = []
for s in seasons:
if s == "夏":
drinks.append(np.random.choice(["アイスコーヒー", "ホットコーヒー", "お茶"], p=[0.6, 0.1, 0.3]))
elif s == "冬":
drinks.append(np.random.choice(["アイスコーヒー", "ホットコーヒー", "お茶"], p=[0.1, 0.6, 0.3]))
else:
drinks.append(np.random.choice(["アイスコーヒー", "ホットコーヒー", "お茶"], p=[0.3, 0.3, 0.4]))
df = pd.DataFrame({"季節": seasons, "ドリンク": drinks})
# クロス集計表
cross_table = pd.crosstab(df["季節"], df["ドリンク"])
print("クロス集計表:")
print(cross_table)
# カイ二乗検定とクラメールのV
chi2, p, dof, expected = stats.chi2_contingency(cross_table)
n_total = cross_table.sum().sum()
k = min(cross_table.shape) - 1
cramers_v = np.sqrt(chi2 / (n_total * k))
print(f"\nカイ二乗値: {chi2:.4f}")
print(f"p値: {p:.6f}")
print(f"クラメールのV: {cramers_v:.4f}")
クロス集計表の可視化
import seaborn as sns
import matplotlib.pyplot as plt
import japanize_matplotlib
fig, axes = plt.subplots(1, 2, figsize=(14, 5))
# ヒートマップ
sns.heatmap(cross_table, annot=True, fmt="d", cmap="YlOrRd", ax=axes[0])
axes[0].set_title("クロス集計表(ヒートマップ)", fontsize=13)
# 積み上げ棒グラフ
cross_table.plot(kind="bar", stacked=True, ax=axes[1], colormap="Set2")
axes[1].set_title(f"季節別ドリンク構成比(V={cramers_v:.3f})", fontsize=13)
axes[1].set_xlabel("季節")
axes[1].set_ylabel("人数")
axes[1].legend(title="ドリンク")
axes[1].tick_params(axis='x', rotation=0)
plt.tight_layout()
plt.show()
カイ二乗分布について
カイ二乗値は「事象が起こりやすいかどうか」を判断するために使われ、カイ二乗値と起こりやすさの関係をグラフにしたものをカイ二乗分布といいます。カイ二乗分布は自由度によって形が変化します。
from scipy import stats
import numpy as np
import matplotlib.pyplot as plt
import japanize_matplotlib
x = np.linspace(0, 15, 200)
plt.figure(figsize=(10, 6))
for k in [1, 2, 3, 5, 7]:
y = stats.chi2.pdf(x, df=k)
plt.plot(x, y, label=f'自由度 k={k}', linewidth=2)
plt.xlabel("カイ二乗値", fontsize=12)
plt.ylabel("確率密度", fontsize=12)
plt.title("カイ二乗分布(自由度別)", fontsize=14)
plt.legend(fontsize=11)
plt.grid(alpha=0.3)
plt.ylim(0, 0.5)
plt.show()
4-6. 相関と因果関係
相関分析で最も注意すべき点は、「相関関係があること」と「因果関係があること」は別の概念であるということです。
因果関係と相関関係の違い
| 概念 | 定義 | 証明方法 |
|---|---|---|
| 因果関係 | ある出来事が別の出来事を直接的に引き起こす関係 | 科学的な実験や観察が必要 |
| 相関関係 | 2つの出来事の間に統計的な関連が見られること | 相関係数で数値化可能 |
重要: 高い相関関係が見られたとしても、それが因果関係を意味するとは限りません。
相関関係のパターン
【真の因果関係】
A → B (AがBの原因)
【逆の因果関係】
A ← B (BがAの原因)
【第3の変数(交絡因子)】
C → A
C → B (Cが両方の原因)
【偶然の一致(擬似相関)】
A ≠ B (たまたま相関が見られるだけ)
因果関係が明確な相関の事例
| 原因 | 結果 | 根拠 |
|---|---|---|
| 喫煙 | 肺がん発症率の上昇 | 長年の疫学調査により因果関係が証明 |
| フロンガスの使用 | オゾン層の破壊 | 化学反応メカニズムが科学的に解明 |
| 運動量の増加 | 心疾患リスクの低下 | 大規模な介入研究で実証 |
第3の要因による擬似相関の事例
| 変数A | 変数B | 第3の要因(交絡因子) |
|---|---|---|
| アイスクリーム消費量↑ | 溺死事故↑ | 夏の気温の上昇 |
| 傘を持つ人↑ | 交通事故↑ | 雨天 |
| 消防車の出動台数↑ | 火災の被害額↑ | 火災の規模 |
| 有機食品の売上↑ | 自閉症の診断数↑ | 時代の変化(偶然の一致) |
偶然による擬似相関の事例
| 変数A | 変数B | 実際の関係 |
|---|---|---|
| PCの普及率 | 地球温暖化の進行 | 同時期に起きた無関係な現象 |
| ニコラス・ケイジの映画本数 | プール溺死者数 | 全くの偶然(有名な擬似相関) |
Pythonで擬似相関を確認する
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import japanize_matplotlib
np.random.seed(42)
# 第3の変数(気温)が2つの変数に影響を与える例
n = 50
temperature = np.random.normal(25, 8, n) # 気温(交絡因子)
# 気温が高いとアイスが売れる
ice_cream_sales = 20 + 3 * temperature + np.random.normal(0, 10, n)
# 気温が高いと水難事故が増える
drowning_incidents = 1 + 0.3 * temperature + np.random.normal(0, 2, n)
# 相関係数の確認
from scipy import stats
r, p = stats.pearsonr(ice_cream_sales, drowning_incidents)
print(f"アイスクリーム売上と溺死事故の相関係数: {r:.4f}")
print(f"→ 高い正の相関があるが、因果関係ではない!")
# 可視化
fig, axes = plt.subplots(1, 3, figsize=(15, 4))
# アイスvs溺死(擬似相関)
axes[0].scatter(ice_cream_sales, drowning_incidents, alpha=0.7, color='coral')
axes[0].set_xlabel("アイスクリーム売上")
axes[0].set_ylabel("水難事故件数")
axes[0].set_title(f"擬似相関 (R={r:.3f})\n※因果関係ではない", fontsize=11)
# 真の原因:気温vsアイス
r2, _ = stats.pearsonr(temperature, ice_cream_sales)
axes[1].scatter(temperature, ice_cream_sales, alpha=0.7, color='steelblue')
axes[1].set_xlabel("気温(℃)")
axes[1].set_ylabel("アイスクリーム売上")
axes[1].set_title(f"真の関係 (R={r2:.3f})\n気温→アイス売上", fontsize=11)
# 真の原因:気温vs溺死
r3, _ = stats.pearsonr(temperature, drowning_incidents)
axes[2].scatter(temperature, drowning_incidents, alpha=0.7, color='green')
axes[2].set_xlabel("気温(℃)")
axes[2].set_ylabel("水難事故件数")
axes[2].set_title(f"真の関係 (R={r3:.3f})\n気温→水難事故", fontsize=11)
plt.tight_layout()
plt.show()
因果関係を判断するためのポイント
相関関係から因果関係を推定する際は、以下の条件を確認します(ブラッドフォード・ヒルの基準)。
| 基準 | 説明 |
|---|---|
| 時間的先行性 | 原因が結果より先に発生しているか |
| 関連の強さ | 相関係数が十分に大きいか |
| 整合性 | 複数の研究で一貫した結果が得られるか |
| 特異性 | 特定の原因が特定の結果と結びついているか |
| 生物学的妥当性 | メカニズムとして説明可能か |
| 実験的証拠 | 介入実験で確認できるか |
分析時の注意事項
- 相関係数だけで判断しない — 必ず散布図も確認する
- 非線形な関係に注意 — ピアソンの相関係数は線形関係のみを捉える
- 外れ値の影響を確認 — 1つの外れ値で相関係数が大きく変わることがある
- 第3の変数を意識する — 高い相関が見られたら交絡因子の存在を疑う
- 十分なサンプル数を確保する — 少ないデータでの相関は偶然の可能性が高い
# アンスコムの4つのデータセット — 相関係数が同じでも散布図は全く異なる
import seaborn as sns
anscombe = sns.load_dataset("anscombe")
fig, axes = plt.subplots(2, 2, figsize=(10, 8))
for idx, dataset in enumerate(["I", "II", "III", "IV"]):
ax = axes[idx // 2, idx % 2]
subset = anscombe[anscombe["dataset"] == dataset]
r = subset["x"].corr(subset["y"])
ax.scatter(subset["x"], subset["y"], s=50, edgecolors='black')
ax.set_title(f"Dataset {dataset} (R={r:.3f})", fontsize=11)
ax.set_xlim(2, 20)
ax.set_ylim(2, 14)
plt.suptitle("アンスコムの4つのデータセット\n相関係数はほぼ同じだが散布図は全く異なる", fontsize=13)
plt.tight_layout()
plt.show()
補足:実践的な相関分析の進め方
分析フロー
1. 変数の種類を確認(量的/質的)
2. 適切な相関指標を選択
- 量的×量的 → 相関係数(ピアソン or スピアマン)
- 量的×質的 → 相関比
- 質的×質的 → 連関係数(クラメールのV)
3. 散布図やクロス集計表で可視化
4. 相関の強さを数値で確認
5. p値で統計的有意性を確認
6. 因果関係の有無を慎重に判断
全変数の相関を一括で確認する関数
import numpy as np
import pandas as pd
from scipy import stats
def analyze_all_correlations(df, numeric_cols, categorical_cols):
"""全変数の相関を一括で分析する関数"""
results = []
# 量的×量的(ピアソン相関係数)
for i, col1 in enumerate(numeric_cols):
for col2 in numeric_cols[i+1:]:
r, p = stats.pearsonr(df[col1], df[col2])
results.append({
"変数1": col1, "変数2": col2,
"指標": "相関係数(R)", "値": r, "p値": p
})
# 量的×質的(相関比)
for num_col in numeric_cols:
for cat_col in categorical_cols:
eta2 = correlation_ratio(df[cat_col], df[num_col])
results.append({
"変数1": num_col, "変数2": cat_col,
"指標": "相関比(η²)", "値": eta2, "p値": None
})
# 質的×質的(クラメールのV)
for i, col1 in enumerate(categorical_cols):
for col2 in categorical_cols[i+1:]:
cross = pd.crosstab(df[col1], df[col2])
chi2, p, dof, _ = stats.chi2_contingency(cross)
n_total = cross.sum().sum()
k = min(cross.shape) - 1
v = np.sqrt(chi2 / (n_total * k)) if k > 0 else 0
results.append({
"変数1": col1, "変数2": col2,
"指標": "クラメールのV", "値": v, "p値": p
})
return pd.DataFrame(results)
# 使用例
# result_df = analyze_all_correlations(df, ["age", "salary"], ["department", "gender"])
# print(result_df.sort_values("値", ascending=False))
まとめ
本記事では、相関分析として以下の内容を解説しました。
| 項目 | ポイント |
|---|---|
| 相関分析とは | 2変数間の関係の強さを明らかにする分析 |
| 共分散 | 2変数の偏差の積の平均、単位に依存するため比較が困難 |
| 相関係数 | 量的×量的の関係を-1〜1で表す(ピアソン、スピアマン) |
| 相関比 | 量的×質的の関係を0〜1で表す(η²) |
| 連関係数 | 質的×質的の関係を0〜1で表す(クラメールのV) |
| 相関と因果 | 相関≠因果、第3の変数(交絡因子)に注意 |
相関分析のチェックリスト
- 変数の種類(量的/質的)を正しく識別したか
- 適切な相関指標を選択したか
- 散布図やクロス集計表で可視化して確認したか
- 相関係数だけでなくp値も確認したか
- 外れ値の影響を確認したか
- 非線形な関係がないか散布図で確認したか
- 相関関係と因果関係を混同していないか
- 第3の変数(交絡因子)の存在を検討したか
シリーズ記事一覧
- 【第1回】Pythonで学ぶデータ分析実践 〜データ分析とは〜
- 【第2回】データの前処理(欠損値・外れ値・ダミー変数・データスケーリングなど)
- 【第3回】データの把握(基本統計量・ヒストグラム・箱ひげ図・散布図など)
- 【第4回】相関分析(共分散・相関係数・相関比・連関係数など)
- 【第5回】統計的推定(母集団と標本・点推定・区間推定・信頼区間)
- 【第6回】統計的検定(仮説検定・p値・t検定・カイ二乗検定など)
- 【第7回】分散分析(一元分散分析・二元分散分析・多重比較)
- 【第8回】回帰分析(単回帰・重回帰・一般化線形モデル・モデル評価)
- 【第9回】時系列データ分析(トレンド・季節性・ARIMA・SARIMAなど)
- 【第10回】クラス分類(決定木・ランダムフォレスト・ロジスティック回帰など)
- 【第11回】クラスタリング(k-means・階層クラスタリング・エルボー法など)
- 【第12回】次元削減(主成分分析・寄与率・可視化)
- 【発展編】分析精度の向上(特徴量エンジニアリング・ハイパーパラメータ調整・アンサンブル学習など)
- 【発展編】ニューラルネットワーク入門(パーセプトロン・誤差逆伝播法・TensorFlow・Kerasなど)
次回の第5回では、標本データから母集団の特性を推定する「統計的推定」について解説します。母集団と標本の関係、点推定と区間推定、信頼区間の考え方をPythonの実装とともに学んでいきます。