Skip to main content

이변량/다변량 분석

이변량 분석(Bivariate Analysis)은 두 변수 간의 관계를, 다변량 분석(Multivariate Analysis)은 세 변수 이상의 복합 관계를 탐색합니다. 피처와 타겟의 관계를 파악하고, 다중공선성을 탐지하며, 데이터의 숨겨진 패턴을 발견합니다.

학습 목표

  • 수치형-수치형 변수의 상관관계를 분석할 수 있다
  • 수치형-범주형 변수의 그룹별 차이를 시각화할 수 있다
  • 범주형-범주형 변수의 교차 분석을 수행할 수 있다
  • PCA로 고차원 데이터를 2D로 시각화할 수 있다

왜 중요한가

단변량 분석으로는 변수 간의 상호작용을 파악할 수 없습니다. 피처 선택, 다중공선성 제거, 교호작용 피처 생성 등 ML 모델의 성능을 좌우하는 의사결정이 이변량/다변량 분석에서 이루어집니다.

수치형-수치형: 상관관계

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns

np.random.seed(42)
n = 200
df = pd.DataFrame({
    'experience': np.random.uniform(1, 20, n),
    'salary': np.random.normal(4500, 800, n),
    'performance': np.random.normal(75, 10, n),
    'hours_worked': np.random.normal(45, 5, n),
    'department': np.random.choice(['개발', '영업', '마케팅'], n),
    'turnover': np.random.choice([0, 1], n, p=[0.8, 0.2])
})
df['salary'] = df['salary'] + df['experience'] * 200

# 상관 행렬
numeric_cols = ['experience', 'salary', 'performance', 'hours_worked']
corr = df[numeric_cols].corr()

# 히트맵
fig, ax = plt.subplots(figsize=(8, 6))
mask = np.triu(np.ones_like(corr, dtype=bool))  # 상삼각 마스킹
sns.heatmap(corr, mask=mask, 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()

# 강한 상관 쌍 추출
strong_corr = []
for i in range(len(corr.columns)):
    for j in range(i+1, len(corr.columns)):
        r = corr.iloc[i, j]
        if abs(r) > 0.5:
            strong_corr.append({
                'var1': corr.columns[i],
                'var2': corr.columns[j],
                'correlation': r
            })
print("강한 상관 관계 (|r| > 0.5):")
print(pd.DataFrame(strong_corr))

pairplot으로 전체 관계 탐색

# 핵심 변수만 선택하여 pairplot
sns.pairplot(df[['experience', 'salary', 'performance', 'department']],
             hue='department', palette='Set2', diag_kind='kde',
             plot_kws={'alpha': 0.5})
plt.suptitle('변수 쌍 관계 탐색', y=1.02)
plt.show()

수치형-범주형: 그룹 비교

fig, axes = plt.subplots(1, 3, figsize=(15, 5))

# 박스플롯: 부서별 연봉
sns.boxplot(data=df, x='department', y='salary', ax=axes[0], palette='Set2')
axes[0].set_title('부서별 연봉 분포')

# 바이올린플롯: 부서별 성과
sns.violinplot(data=df, x='department', y='performance', ax=axes[1],
               palette='Set2', inner='quartile')
axes[1].set_title('부서별 성과 분포')

# 바플롯: 부서별 평균 근무시간
sns.barplot(data=df, x='department', y='hours_worked', ax=axes[2],
            palette='Set2', errorbar='ci')
axes[2].set_title('부서별 평균 근무시간')

plt.tight_layout()
plt.show()

# 통계적 유의성 검정
from scipy.stats import f_oneway

groups = [df[df['department'] == dept]['salary'] for dept in df['department'].unique()]
f_stat, p_value = f_oneway(*groups)
print(f"부서별 연봉 ANOVA: F={f_stat:.2f}, p={p_value:.4f}")

타겟 변수와의 관계

# 이진 타겟(turnover)과 수치형 변수의 관계
fig, axes = plt.subplots(1, 3, figsize=(15, 5))

for ax, col in zip(axes, ['salary', 'experience', 'performance']):
    sns.boxplot(data=df, x='turnover', y=col, ax=ax, palette=['#4a9eca', '#e6a23c'])
    ax.set_xticklabels(['잔류', '이직'])
    ax.set_title(f'{col} vs 이직 여부')

plt.tight_layout()
plt.show()

범주형-범주형: 교차 분석

# 교차표 (Cross Tabulation)
cross_tab = pd.crosstab(df['department'], df['turnover'],
                        margins=True, normalize='index')
cross_tab.columns = ['잔류', '이직', '합계']
print("부서별 이직률:")
print(cross_tab.round(3))

# 히트맵으로 시각화
cross_counts = pd.crosstab(df['department'], df['turnover'])
cross_counts.columns = ['잔류', '이직']

fig, ax = plt.subplots(figsize=(6, 4))
sns.heatmap(cross_counts, annot=True, fmt='d', cmap='YlOrRd', ax=ax)
ax.set_title('부서별 이직 현황')
plt.tight_layout()
plt.show()

# 카이제곱 검정
from scipy.stats import chi2_contingency
chi2, p_value, dof, expected = chi2_contingency(cross_counts)
print(f"\n카이제곱 검정: chi2={chi2:.2f}, p={p_value:.4f}")

다변량 분석: 조건부 시각화

# 3변수 산점도: 색상 + 크기
fig, ax = plt.subplots(figsize=(10, 6))
scatter = ax.scatter(df['experience'], df['salary'],
                     c=df['performance'], s=df['hours_worked']*2,
                     cmap='RdYlBu_r', alpha=0.6, edgecolors='white')
plt.colorbar(scatter, label='성과')
ax.set_xlabel('경력 (년)')
ax.set_ylabel('연봉 (만원)')
ax.set_title('경력-연봉-성과-근무시간 관계')
plt.tight_layout()
plt.show()

# FacetGrid: 부서별 경력-연봉 관계
g = sns.FacetGrid(df, col='department', height=4, aspect=1.2)
g.map_dataframe(sns.scatterplot, x='experience', y='salary',
                hue='turnover', palette=['#4a9eca', '#e6a23c'], alpha=0.6)
g.add_legend(title='이직')
g.set_titles('{col_name}')
plt.suptitle('부서별 경력-연봉 관계', y=1.02)
plt.show()

PCA — 차원 축소 시각화

from sklearn.preprocessing import StandardScaler
from sklearn.decomposition import PCA

# 수치형 변수 표준화
X = df[numeric_cols]
X_scaled = StandardScaler().fit_transform(X)

# PCA (2차원으로 축소)
pca = PCA(n_components=2)
X_pca = pca.fit_transform(X_scaled)

# 설명된 분산 비율
print(f"PC1 설명 분산: {pca.explained_variance_ratio_[0]:.1%}")
print(f"PC2 설명 분산: {pca.explained_variance_ratio_[1]:.1%}")
print(f"누적 설명 분산: {pca.explained_variance_ratio_.sum():.1%}")

# 시각화
fig, ax = plt.subplots(figsize=(8, 6))
for dept in df['department'].unique():
    mask = df['department'] == dept
    ax.scatter(X_pca[mask, 0], X_pca[mask, 1], label=dept, alpha=0.6)
ax.set_xlabel(f'PC1 ({pca.explained_variance_ratio_[0]:.1%})')
ax.set_ylabel(f'PC2 ({pca.explained_variance_ratio_[1]:.1%})')
ax.set_title('PCA 2D 시각화')
ax.legend()
plt.tight_layout()
plt.show()

AI/ML에서의 활용

  • 피처 선택: 타겟과 높은 상관을 가진 피처를 우선 선택합니다
  • 다중공선성 제거: 피처 간 상관이 0.9 이상이면 하나를 제거합니다
  • 교호작용 피처: 그룹 비교에서 차이가 큰 변수 조합으로 교호작용 피처를 생성합니다
  • 차원 축소: PCA로 고차원 데이터의 구조를 파악하고 시각화합니다
변수가 많으면 핵심 변수 5개 이하로 제한하세요. 데이터가 많으면 sample(n=1000)으로 서브샘플링합니다. 빠른 대안으로 상관 행렬 히트맵을 먼저 확인하는 것도 좋습니다.

체크리스트

  • 상관 행렬을 계산하고 히트맵으로 시각화할 수 있다
  • 그룹별 분포 차이를 박스플롯/바이올린플롯으로 비교할 수 있다
  • 교차표를 생성하고 카이제곱 검정을 수행할 수 있다
  • PCA로 고차원 데이터를 2D로 시각화할 수 있다

다음 문서