상관과 회귀
상관분석(Correlation Analysis)은 두 변수의 선형 관계 강도를 측정하고, 회귀분석(Regression Analysis)은 한 변수로 다른 변수를 예측하는 수학적 모델을 구축합니다. 이 두 기법은 EDA의 핵심이자 머신러닝의 이론적 기반입니다.
학습 목표
- 피어슨 상관계수와 스피어만 상관계수의 차이를 이해한다
- 상관계수를 계산하고 올바르게 해석할 수 있다
- 단순 선형회귀 모델을 구축하고 해석할 수 있다
- 결정계수(R-squared)로 모델 적합도를 평가할 수 있다
- 잔차 분석으로 모델의 가정을 검증할 수 있다
왜 중요한가
상관분석은 피처 선택의 첫 단계이고, 선형회귀는 가장 기본적인 예측 모델입니다. 선형회귀의 원리를 이해하면 로지스틱회귀, 릿지, 라쏘 등 고급 회귀 모델의 개념을 쉽게 확장할 수 있습니다.
피어슨 상관계수
피어슨 상관계수(Pearson Correlation Coefficient)는 두 변수의 선형 관계 강도를 -1에서 1 사이의 값으로 나타냅니다.
import numpy as np
import pandas as pd
from scipy import stats
import matplotlib.pyplot as plt
import seaborn as sns
np.random.seed(42)
# 예시 데이터
n = 100
experience = np.random.uniform(1, 20, n)
salary = 3000 + experience * 200 + np.random.normal(0, 500, n)
performance = np.random.normal(75, 10, n)
df = pd.DataFrame({
'experience': experience,
'salary': salary,
'performance': performance
})
# 피어슨 상관계수
corr_matrix = df.corr(method='pearson')
print("피어슨 상관 행렬:")
print(corr_matrix.round(3))
# 개별 상관계수와 p-value
r, p_value = stats.pearsonr(df['experience'], df['salary'])
print(f"\n경력-연봉 상관: r={r:.3f}, p={p_value:.6f}")
| 상관계수 범위 | 해석 |
|---|
| 0.9 ~ 1.0 | 매우 강한 양의 상관 |
| 0.7 ~ 0.9 | 강한 양의 상관 |
| 0.4 ~ 0.7 | 중간 양의 상관 |
| 0.2 ~ 0.4 | 약한 양의 상관 |
| -0.2 ~ 0.2 | 거의 무상관 |
상관관계는 인과관계가 아닙니다. 아이스크림 판매량과 익사 사고 수는 높은 상관을 보이지만, 둘 다 여름(기온)이라는 공통 원인에 의한 것입니다.
스피어만 상관계수
스피어만 상관계수(Spearman Rank Correlation)는 순위 기반으로 단조 관계를 측정합니다. 비선형이지만 단조적인 관계도 포착할 수 있습니다.
# 비선형 관계 데이터
x = np.linspace(1, 10, 100)
y_linear = 2 * x + np.random.normal(0, 1, 100)
y_monotone = np.exp(0.3 * x) + np.random.normal(0, 2, 100)
fig, axes = plt.subplots(1, 2, figsize=(12, 5))
for ax, y, title in zip(axes, [y_linear, y_monotone], ['선형 관계', '비선형 단조 관계']):
ax.scatter(x, y, alpha=0.6, color='#4a9eca')
pearson_r, _ = stats.pearsonr(x, y)
spearman_r, _ = stats.spearmanr(x, y)
ax.set_title(f'{title}\nPearson: {pearson_r:.3f}, Spearman: {spearman_r:.3f}')
ax.set_xlabel('X')
ax.set_ylabel('Y')
plt.tight_layout()
plt.show()
| 상관계수 | 관계 유형 | 데이터 요건 | 이상치 민감도 |
|---|
| 피어슨 | 선형 | 연속형, 정규분포 권장 | 높음 |
| 스피어만 | 단조(비선형 포함) | 순서형/연속형 | 낮음 |
단순 선형회귀
단순 선형회귀(Simple Linear Regression)는 하나의 독립변수(X)로 종속변수(Y)를 예측하는 모델입니다.
from scipy.stats import linregress
# 경력으로 연봉 예측
slope, intercept, r_value, p_value, std_err = linregress(
df['experience'], df['salary']
)
print(f"회귀식: 연봉 = {intercept:.0f} + {slope:.0f} x 경력")
print(f"R-squared: {r_value**2:.3f}")
print(f"p-value: {p_value:.6f}")
print(f"표준오차: {std_err:.1f}")
# 시각화
fig, ax = plt.subplots(figsize=(8, 6))
ax.scatter(df['experience'], df['salary'], alpha=0.6, color='#4a9eca', label='데이터')
# 회귀선
x_line = np.linspace(df['experience'].min(), df['experience'].max(), 100)
y_line = intercept + slope * x_line
ax.plot(x_line, y_line, 'r-', linewidth=2, label=f'회귀선 (R²={r_value**2:.3f})')
ax.set_xlabel('경력 (년)')
ax.set_ylabel('연봉 (만원)')
ax.set_title('경력 vs 연봉 선형회귀')
ax.legend()
plt.tight_layout()
plt.show()
결정계수 (R-squared)
# R² 해석
# R² = 0.75: 경력이 연봉 변동의 75%를 설명
# R² = 1.0: 완벽한 적합 (과적합 의심)
# R² = 0.0: 모델이 평균보다 나을 것이 없음
# 예측
new_experience = np.array([5, 10, 15])
predicted_salary = intercept + slope * new_experience
for exp, sal in zip(new_experience, predicted_salary):
print(f"경력 {exp}년 → 예상 연봉: {sal:.0f}만원")
scikit-learn으로 선형회귀
from sklearn.linear_model import LinearRegression
from sklearn.metrics import r2_score, mean_squared_error
# 모델 학습
X = df[['experience']] # 2D 배열 필요
y = df['salary']
model = LinearRegression()
model.fit(X, y)
print(f"절편: {model.intercept_:.0f}")
print(f"기울기: {model.coef_[0]:.0f}")
print(f"R²: {model.score(X, y):.3f}")
# 예측 및 평가
y_pred = model.predict(X)
rmse = np.sqrt(mean_squared_error(y, y_pred))
print(f"RMSE: {rmse:.0f}만원")
잔차 분석
회귀 모델의 가정을 검증합니다.
# 잔차 계산
residuals = y - y_pred
fig, axes = plt.subplots(1, 3, figsize=(15, 4))
# 1. 잔차 vs 예측값 (등분산성 확인)
axes[0].scatter(y_pred, residuals, alpha=0.5, color='#4a9eca')
axes[0].axhline(y=0, color='red', linestyle='--')
axes[0].set_xlabel('예측값')
axes[0].set_ylabel('잔차')
axes[0].set_title('잔차 vs 예측값')
# 2. 잔차 히스토그램 (정규성 확인)
axes[1].hist(residuals, bins=20, color='#4a9eca', alpha=0.7, edgecolor='white')
axes[1].set_xlabel('잔차')
axes[1].set_title('잔차 분포')
# 3. QQ-plot (정규성 확인)
from scipy.stats import probplot
probplot(residuals, plot=axes[2])
axes[2].set_title('잔차 QQ-Plot')
plt.tight_layout()
plt.show()
# 잔차의 정규성 검정
_, p_shapiro = stats.shapiro(residuals)
print(f"Shapiro-Wilk p-value: {p_shapiro:.4f}")
잔차 분석에서 확인할 세 가지: 1) 잔차의 무작위 분포(패턴 없음), 2) 잔차의 정규성, 3) 잔차의 등분산성. 이 조건을 만족해야 회귀 결과를 신뢰할 수 있습니다.
다중공선성 확인
# 상관 행렬 히트맵으로 다중공선성 탐지
fig, ax = plt.subplots(figsize=(8, 6))
sns.heatmap(df.corr(), annot=True, fmt='.2f', cmap='RdBu_r',
center=0, vmin=-1, vmax=1, square=True, ax=ax)
ax.set_title('상관 행렬')
plt.tight_layout()
plt.show()
# VIF (분산팽창인수) 계산
from sklearn.linear_model import LinearRegression
def calculate_vif(df, features):
"""각 피처의 VIF를 계산합니다."""
vif_data = []
for feature in features:
X = df[features].drop(columns=feature)
y = df[feature]
model = LinearRegression().fit(X, y)
r2 = model.score(X, y)
vif = 1 / (1 - r2) if r2 < 1 else float('inf')
vif_data.append({'feature': feature, 'VIF': vif})
return pd.DataFrame(vif_data)
vif_result = calculate_vif(df, ['experience', 'salary', 'performance'])
print(vif_result)
# VIF > 10: 심각한 다중공선성
# VIF > 5: 주의 필요
AI/ML에서의 활용
- 피처 선택: 타겟과 높은 상관을 가진 피처를 우선 선택합니다
- 다중공선성 탐지: 상관계수 0.9 이상인 피처 쌍을 식별하여 하나를 제거합니다
- 기준 모델: 선형회귀를 baseline 모델로 사용하여 복잡한 모델과 비교합니다
- 해석 가능성: 회귀 계수로 각 피처의 영향 방향과 크기를 해석합니다
네. 피어슨 상관계수는 선형 관계만 측정합니다. 포물선 형태(U자형)의 관계에서는 상관계수가 0에 가깝지만, 실제로는 강한 비선형 관계가 존재합니다. 산점도를 반드시 함께 확인하세요.
반드시 그렇지는 않습니다. 피처를 추가하면 R-squared는 항상 증가합니다. 조정된 R-squared(Adjusted R-squared)를 사용하면 피처 수의 영향을 보정할 수 있습니다. 또한 R-squared가 높아도 과적합일 수 있으므로 교차검증이 필요합니다.
체크리스트
다음 문서