통계학 공부

선형회귀, 다항회귀, 스플라인회귀, 피어슨 / 비모수 / 상호정보 상관계수

myun0506 2026. 1. 10. 22:27

 

- 단순선형회귀

하나의 독립 변수(x)와 하나의 종속 변수(y) 간의 관계를 직선으로 모델링하는 방법

회귀식: Y = β0 + β1X, 여기서 β0는 절편, β1는 기울기

독립 변수의 변화에 따라 종속 변수가 어떻게 변화하는지 설명하고 예측

데이터가 직선적 경향을 따를 때 사용

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.linear_model import LinearRegression
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error, r2_score

# 예시 데이터 생성
np.random.seed(0)
X = 2 * np.random.rand(100, 1)
y = 4 + 3 * X + np.random.rand(100, 1)

# 데이터 분할
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

# 단순선형회귀 모델 생성 및 훈련
model = LinearRegression()
model.fit(X_train, y_train)

# 예측
y_pred = model.predict(X_test)

# 회귀 계수 및 절편 출력
print("회귀 계수: ", model.coef_)
print("절편: ", model.intercept_)

# 모델 평가
mse = mean_squared_error(y_test, y_pred) # 낮을 수록 좋은 모델
r2 = r2_score(y_test, y_pred) # 높을 수록 좋은 모델
print("평균 제곱 오차(MSE): ", mse)
print("결정 계수(R2): ", r2)

# 시각화
plt.scatter(X, y, color='blue')
plt.plot(X_test, y_pred, color='red', linewidth=2)
plt.title('linear regression')
plt.xlabel('X : cost')
plt.ylabel('Y: sales')
plt.show()
  • mse: 평균 제곱 오차, 낮을 수록 좋은 모델
  • r2: 결정 계수 R2, 높을 수록 좋은 모델
  • train data : test data = 8 : 2 
  • np.random.rand(100, 1): 0 이상 1 미만의 균등분포 난수 100개

 

- 다중선형회귀

두개 이상의 독립 변수(X1, X2, ..., Xn)와 하나의 종속 변수(Y) 간의 관계를 모델링

회귀식: Y = β0 + β1X1 + β2X2 + ... + βnXn

여러 독립 변수의 변화를 고려하여 종속 변수를 설명하고 예측

종속변수에 영향을 미치는 여러 독립변수가 있을 때 사용

여러 변수의 영향을 동시에 분석할 수 있음

변수들 간의 다중공선성 문제가 발생할 수 있음

  • 다중공선성(Multicollinearity)
    • 회귀 분석에서 독립 변수들 간에 높은 상관관계가 있는 경우
    • 문제점
      • 독립 변수들이 서로 강하게 상관되어 있으면, 각 변수의 개별적인 효과를 분리해내기 어려워져 회귀으 해석을 어렵게 만듦
      • 다중공선성으로 인해 실제로 중요한 변수가 통계쩍으로 유의하지 않게 나타날 수 있음
    • 진단방법
      • 상관계수를 계산하여 상관계수가 높은(약 0.7) 변수들이 있는지 확인
      • 분산 팽창 계수 (VIF)를 계산하여 VIF 값이 10보다 높은지 확인
    • 해결방법
      • 높은 계수를 가진 변수 중 하나를 제거하는 것
      • 주성분 분석(PCA)과 같은 변수들을 효과적으로 줄이는 차원 분석 방법 적용
# 예시 데이터 생성
data = {'TV': np.random.rand(100) * 100,
        'Radio': np.random.rand(100) * 50,
        'Newspaper': np.random.rand(100) * 30,
        'Sales': np.random.rand(100) * 100}
df = pd.DataFrame(data)

# 독립 변수(X)와 종속 변수(Y) 설정
X = df[['TV', 'Radio', 'Newspaper']]
y = df[['Sales']]

# 데이터 분할
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

# 다중선형회귀 모델 생성 및 훈련
model = LinearRegression()
model.fit(X_train, y_train)

# 예측
y_pred = model.predict(X_test)

# 회귀 계수 및 절편 출력
print("회귀 계수: ", model.coef_)
print("절편: ",model.intercept_)

# 모델 평가
mse = mean_squared_error(y_test, y_pred)
r2 = r2_score(y_test, y_pred)
print("평균 제곱 오차(MSE): ",mse)
print("결정 계수(R2): ", r2)

# 상관계수 확인
corr = df[['TV', 'Radio', 'Newspaper']].corr()
print(corr)

'''
# 분산팽창계수(VIF) 계산
from statsmodels.stats.outliers_influence import variance_inflation_factor
import pandas as pd

X = df[['TV', 'Radio', 'Newspaper']]
vif = pd.DataFrame()
vif['변수'] = X.columns
vif['VIF'] = [variance_inflation_factor(X.values, i) for i in range(X.shape[1])]
print(vif) 
'''
  • data: 모든 변수의 개수는 100개로 동일하고, tv, radio, newspaper의 값의 범위는 각각 100, 50, 30임
  • r2: 이 모델이 '아무것도 안하고 평균만 예측하는 모델'보다 나은가를 나타냄
    • r2 = 1 → 완벽
    • r2 = 0 → 평균 예측과 동일
    • r2 < 0 → 평균보다도 못함
    • 지금 데이터에서 Sales가 Tv, Radio, Newspaper와 아무 관계가 없기 때문에 r2가 음수가 나옴(매우 정상)
  • corr: 상관계수
    • 0.3 ~ 0.7: 어느 정도 관계
    • 0.7 이상: 다중공선성 의심
    • 이때, 예를들어 corr(Tv, Radio) = 0.85라고 할때 다중 공선성 위험이 있는데 이 두 변수 중에 종속변수와의 관계까 더 약한 변수를 제거해야함 
    • df[['TV', 'Radio', 'Sales']].corr()['Sales'] 코드 실행해서 상관계수 값이 더 작게 나오는 변수를 제거
  • VIF: 분산팽창계수
    • < 5: 문제 없음
    • 5 ~ 10: 주의
    • > 10: 심각한 다중공선성

- 범주형 변수

  • 순서가 있는 범주형 변수 (옷의 사이즈 L, M,... 수능 등급 1등급, 2등급, ...)
    • 각 문자를 임의의 숫자로 변환해도 문제가 없다 (순서가 잘 반영될 수 있게 숫자로 변환)
    • ex) XL → 3, L → 2, M → 1, S → 0
  • 순서가 없는 범주형 변수 (성별 - 남,여 지역 - 부산, 대구, 대전,...) 
    • 2개 밖에 없는 경우 임의의 숫자로 바로 변환해도 문제가 없음
    • 3개 이상인 경우 무조건 원-핫 인코딩(하나만 1이고 나머지는 0인 벡터) 변환을 해주어야함 → pandas의 get_dummies 활용하여 쉽게 구현 가능
    • ex) 부산 = [1,0,0,0], 대전 = [0,1,0,0], 대구 = [0,0,1,0], 광주 = [0,0,0,1]
# 예시 데이터 생성
data = {'Gender': ['Male', 'Female', 'Female', 'Male', 'Male'],
        'Experience': [5, 7, 10, 3, 8],
        'Salary': [50, 60, 65, 40, 55]}
df = pd.DataFrame(data)

# 범주형 변수 더미 변수로 변환
df = pd.get_dummies(df, drop_first = True)

# 독립 변수(X)와 종속 변수(Y) 설정
X = df[['Experience', 'Gender_Male']]
y = df['Salary']

# 단순선형회귀 모델 생성 및 훈련
model = LinearRegression()
model.fit(X,y)

# 예측
y_pred = model.predict(X)

# 회귀 계수 및 절편 출력
print("회귀 계수: ",model.coef_)
print("절편: ",model.intercept_)

# 모델 평가
mse = mean_squared_error(y, y_pred)
r2 = r2_score(y, y_pred)
print("평균 제곱 오차(MSE): ",mse)
print("결정 계수(R2): ",r2)

 

  • drop_first: 범주형 변수 중 1개를 빼는 것 (어차피 마지막 1개 변수는 있으나 마나한 존재이기 때문에 & 다중공선성 문제를 방지하기 위해서라도 하나는 삭제) 

 

- 다항회귀, 스플라인회귀

데이터가 훨씬 복잡할 때 사용하는 회귀

  • 다항회귀
    • 독립 변수와 종속 변수 간의 관계가 선형이 아닐 떄 사용. 독립 변수의 다항식을 사용하여 종속 변수를 예측
    • 데이터가 곡선적 경향을 따를 때 사용
    • 비선형 관계를 모델링할 수 있음
    • 고차 다항식의 경우 과적합(overfitting) 위험이 있음

from sklearn.preprocessing import PolynomialFeatures

# 예시 데이터 생성
np.random.seed(0)
X = 2 - 3 * np.random.normal(0, 1, 100)
y = X - 2 * (X ** 2) + np.random.normal(-3, 3, 100)
X = X[:, np.newaxis]

# 다항 회귀 (2차)
polynomial_features = PolynomialFeatures(degree=2)
X_poly = polynomial_features.fit_transform(X)

model = LinearRegression()
model.fit(X_poly, y)
y_poly_pred = model.predict(X_poly)

# 모델 평가
mse = mean_squared_error(y, y_poly_pred)
r2 = r2_score(y, y_poly_pred)
print("평균 제곱 오차(MSE):", mse)
print("결정 계수(R2):", r2)

# 시각화
plt.scatter(X, y, s=10)
# 정렬된 X 값에 따른 y 값 예측
sorted_zip = sorted(zip(X, y_poly_pred))
X, y_poly_pred = zip(*sorted_zip)
plt.plot(X, y_poly_pred, color='m')
plt.title('polynomial regerssion')
plt.xlabel('area')
plt.ylabel('price')
plt.show()
  • PolynomialFeatures(degree=2): 전처리도구를 통해 독립변수 X를 2차까지 확장
    • degree=2 변환 후 X_poly = [1, x, x^2]
  • X: 원래 독립변수, 현실 세계의 의미를 가진 값 (x축에 올라가야 하는 값)
  • X_poly: 모델 학습을 위한 수학적 변형, 차원이 3개, 의미없는 중간 계산용 데이터 (축으로 쓰일 수 없음)

 

  • 스플라인 회귀
    • 독립 변수의 구간별로 다른 회귀식을 적용하여 복잡한 관계를 모델링
    • 구간마다 다른 다항식을 사용하여 전체적으로 매끄러운 곡선을 생성
    • 데이터가 국부적으로 다른 패턴을 보일 때 사용
    • 복잡한 비선형 관계를 유연하게 모델링할 수 있음
    • 적절한 매듭점(knots)의 선택이 중요함

 

 

- 피어슨 상관계수

가장 왼쪽 그래프가 피어슨 상관계수 그래프, X와 Y의 선형 관계를 보여줌

그래프에서 점들이 직선적으로 퍼져있으며, 상관계수는 0.99로 매우 강한 양의 선형 관계를 나타냄

데이터가 정규분포를 따를 때, 두 연속형 변수 간의 선형 관계를 측정하는 지표 (-1~1 사이의 값)

  • 1: 완전한 양의 선형 관계
  • -1: 완전한 음의 선형 관계
  • 0: 선형 관계가 없음

 

세번째 줄의 경우에는 관계가 있어보이는 그래프들도 있지만, 선형관계가 아니라는 점에서 피어슨 상관계수는 0으로 나옴

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from scipy.stats import pearsonr

# 예시 데이터 생성
np.random.seed(0)
study_hours = np.random.rand(100) * 10
exam_scores = 3 * study_hours + np.random.randn(100) * 5

# 데이터프레임 생성
df = pd.DataFrame({'Study Hours': study_hours, 'Exam Scores': exam_scores})

# 피어슨 상관계수 계산
pearson_corr, _ = pearsonr(df['Study Hours'], df['Exam Scores'])
print(f"피어슨 상관계수: {pearson_corr}")

# 상관관계 히트맵 시각화
sns.heatmap(df.corr(), annot=True, cmap='coolwarm', vmin=-1, vmax=1)
plt.title('pearson coefficient heatmap')
plt.show()

 

- 비모수 상관계수

데이터가 정규분포를 따르지 않거나 변수들이 순서형 데이터일 때 사용하는 상관계수

데이터의 분포에 대한 가정 없이 두 변수 간의 상관관계를 측정할 때 사용

대표적으로 스피어만 상관계수와 켄달의 타우 상관계수가 있음

  • 스피어만 상관계수
    • 두 변수의 순위 간의 일관성을 측정 (순서 관계만 중요할 때)
    • 켄달의 타우 상관계수보다 데이터 내 편차와 에러에 민감
  • 켄달의 타우 상관계수
    • 순위 간의 일치 쌍 및 불일치 쌍의 비율을 바탕으로 계산 (표본이 작거나, 순위 일치/불일치 자체가 중요할 때)
    • ex) 예를 들어 사람의 키와 몸무게에 대해 상관계수를 알고자 할 때 키가 크고 몸무게도 더 나가면 일치 쌍에 해당, 키가 크지만 몸무게가 더 적으면 불일치 쌍에 해당하고 이들의 개수 비율로 상관계수를 결정
from scipy.stats import spearmanr, kendalltau
import seaborn as sns

# 예시 데이터 생성
np.random.seed(0)
customer_satisfaction = np.random.rand(100)
repurchase_intent = 3 * customer_satisfaction + np.random.randn(100) * 0.5

# 데이터프레임 생성
df = pd.DataFrame({'Customer Satisfaction': customer_satisfaction, 'Repurchase Intent': repurchase_intent})

# 스피어만 상관계수 계산
spearman_corr, ps = spearmanr(df['Customer Satisfaction'], df['Repurchase Intent'])
print(f"스피어만 상관계수: {spearman_corr}, p값: {ps}")

# 켄달의 타우 상관계수 계산
kendall_corr, pk = kendalltau(df['Customer Satisfaction'], df['Repurchase Intent'])
print(f"켄달의 타우 상관계수: {kendall_corr}, p값: {pk}")

# 상관관계 히트맵 시각화
sns.heatmap(df.corr(method='spearman'), annot=True, cmap='coolwarm', vmin=-1, vmax=1)
plt.title('spearman coefficient heatmap')
plt.show()

 

 

- 상호정보 상관계수

참고) 원칙적으로 상호정보 상관관계는 상관계수라는 표현을 굳이 사용하지는 않습니다. 쉽게 이해를 돕기 위해서 다른 상관관계랑 같이 '상관계수'라는 표현을 사용하였지만, 일반적인 상관계수는 -1~1 사이로 정규화 되어 있는데 상호정보 상관관계는 0이상 (무한까지)의 범위를 가지고 있기 때문입니다.

두 변수 간의 상호 정보를 측정

변수 간의 정보 의존성을 바탕으로 비선형 관계를 탐지

서로의 정보에 대한 불확실성을 줄이는 정도를 바탕으로 계산

범주형 데이터에 대해서도 적용 가능

import numpy as np
from sklearn.metrics import mutual_info_score
import pandas as pd
import matplotlib.pyplot as plt

# 범주형 예제 데이터
X = np.array(['cat', 'dog', 'cat', 'cat', 'dog', 'dog', 'cat', 'dog', 'dog', 'cat', 'cat'])
Y = np.array(['high', 'high', 'low', 'high', 'low', 'high', 'high', 'low', 'low', 'high', 'low'])

# 상호 정보량 계산
mi = mutual_info_score(X, Y)
print(f"Mutual Information (categorical): {mi}")

# 교차표 만들기
df = pd.DataFrame({'X':X, 'Y':Y})
contingency = pd.crosstab(df['X'],df['Y'])
print(contingency)

# 히트맵으로 시각화
plt.imshow(contingency, interpolation='nearest')
plt.colorbar(label='count')
plt.xticks(range(len(contingency.columns)), contingency.columns)
plt.yticks(range(len(contingency.index)), contingency.index)
plt.xlabel('Y')
plt.ylabel('X')
plt.title('Contingency Table (Basis of Mutual Information)')
plt.show()

 

- 재현 가능성

동일한 연구나 실험을 반복했을 때 일관된 결과가 나오는지 여부. 연구의 신뢰성을 높이는 중요한 요소

ex) 신약을 개발할 때 실험실에서만 효과가 있는 것이 아니라 실제 상황에서도 일관된 결과가 나온다고 믿을 수 있기 때문에 개발 가능한 것

최근 p값에 대한 논쟁이 두드러지고 있음

가설검정 원리상의 문제나 가설검정의 잘못된 사용이 낮은 재현성으로 이어진다는 문제 발생, 재현성 위기 문제 발생(논문)

  • 재현성 위기의 원인
    • 실험 조건을 동일하게 조성하기 어려움
    • 가설검정 사용방법에 있어서 잘못됨
      • p해킹 (p값이 0.05가 유도되게끔 조작하는 것이 가능)
      • 실제로는 통계적으로 아무 의미가 없음에도 의미가 있다고 해버리는 1종 오류를 저지를 수 있음
      • 100번 중에 5번(0.05)은 귀무가설이 옳음에도 불구하고 기각될 수 있음
      • 유의수준으로 통제하는 것이 중요
      • 잘못된 가설을 세우더라도 우연히 0.05보다 낮아서 가설이 맞는 것처럼 보일 수도 있음. 따라서 가능한 좋은 가설을 세우는 것도 중요

- p-해킹

데이터 분석을 반복하여 p 값을 인위적으로 낮추는 행위

유의미한 결과를 얻기 위해 다양한 변수를 시도하거나, 데이터를 계속해서 분석하는 등의 방법 포함

p-해킹은 데이터 분석 결과의 신뢰성을 저하시킴

  • p-해킹을 조심해야하는 경우
    • 여러 가설 검정을 시도할 때
      • 여러 가설 검정을 시도하여 유의미한 p값을 얻을 때까지 반복 분석하는 것을 조심
      • 유의한 결과를 얻기 위해 p값이 0.05 이하인 결과만 선택적으로 보고하는 행위를 조심
      • 데이터의 수를 늘리다보니 특정 데이터 수를 기록할 때 잠깐 p값이 0.05 이하를 기록함으로 이를 바탕으로 대립가설을 채택하는 것을 조심
      • 즉, 결과를 보며 데이터 개수를 늘려서는 안됨
      • 다양한 변수를 건드리며 유리한 결과가 나올 때 다시 처음부터 가설을 그 결과에 맞게 세우는 것
      • 가능한 가설을 미리 세우고 검증하는 가설검증형 방식으로 분석을 해야 하며 만약 탐색적으로 분석한 경우 가능한 모든 변수를 보고하고 본페로니 보정과 같은 방법을 사용해야 함

- 선택적 보고

유의미한 결과만을 보고하고, 유의미하지 않은 결과는 보고하지 않는 행위

이는 데이터 분석의 결과를 왜곡하고, 신뢰성을 저하시킴

결과를 보면서 가설을 다시 설정했는데 마치 처음부터 설정한 가설이라고 얘기하는 행위

 

- 자료수집 중단 시점 결정

원하는 결과가 나올 때까지 자료를 수집하는 것을 조심

데이터 수집을 시작하기 전에 언제 수집을 중단할지 명확하게 결정하지 않으면, 원하는 결과가 나올 때까지 데이터를 계속 수집할 수 있음. 이는 결과의 신뢰성을 떨어뜨림

 

- 데이터 탐색과 검증 분리

검증하기 위한 데이터는 반드시 따로 분리해놓아야 함

데이터셋을 탐색용(training)과 검증용(test)으로 분리하여 사용

from sklearn.model_selection import train_test_split

# 데이터 생성
np.random.seed(42)
X = 2 * np.random.rand(100, 1)
y = 4 + 3 * X + np.random.randn(100, 1)

# 데이터 분할 (학습용 80%, 검증용 20%)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

# 모델 학습
model = LinearRegression()
model.fit(X_train, y_train)

# 학습용 데이터로 예측
y_train_pred = model.predict(X_train)

# 검증용 데이터로 예측
y_test_pred = model.predict(X_test)

# 학습용 데이터 평가
train_mse = mean_squared_error(y_train, y_train_pred)
train_r2 = r2_score(y_train, y_train_pred)
print(f"학습용 데이터 - MSE: {train_mse}, R2: {train_r2}")

# 검증용 데이터 평가
test_mse = mean_squared_error(y_test, y_test_pred)
test_r2 = r2_score(y_test, y_test_pred)
print(f"검증용 데이터 - MSE: {test_mse}, R2: {test_r2}")