이번 주차는 머신러닝의 첫단계인 회귀 분석부터 시작한다.
회귀 분석을 처음 접했을 때는 쉬울 거 같지만, 막상 배우다보면 수학공식도 많이 나오고 초반에 배우다보니 후반에는 기억이 가물해진다.
수학공식이 싫으면 머신러닝을 배우면 안되지않나?
아무튼 단순선형회귀부터, 다중회귀, 로지스틱회귀 등 같은 종류는 아니지만 "회귀"라고 붙은 것들에 대해 개괄적으로 진도를 나갔다.
머신러닝부터는 이론 공부도 정리해서 올리고 싶은 마음이 크지만, 시간이 상당히 많이 소요될 것 같고 수학공식은 포스팅할 때 까다로워서 고민중이다.
그냥 기존처럼 실습 코드 위주로 포스팅하고 중간중간에 중요한 이론에 대해서는 따로 정리해서 끼워넣는게 나을 것 같다.
카페에서 공부중인데 옆자리에 아주 귀여운 강아지가 있어서 긴장(?)된다.
고개 돌릴때마다 쳐다봐서 너무 기분이 좋지만 티내지 않고 공부 열심히 하는 척 중이다.
1. Simple Linear Regression - (1) 상관계수
1. 간단예제
1. 라이브러리 준비
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import statsmodels.api as sm
import scipy.stats as stats
from scipy.stats import pearsonr, spearmanr
from scipy.stats import probplot
from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_squared_error, r2_score
우선 선형회귀, 로지스틱 회귀, 가설검정 등을 사용할 수 있는 통계분석용 import statsmodels.api as sm,
정규성 검정과 p-value 계산을 해주는 import scipy.stats as stats,
상관계수 계산용 함수를 불러올 수 있는 from scipy.stats import pearsonr, spearmanr,
Q-Q 플롯을 그릴 수 있는 시각화용 from scipy.stats import probplot,
선형회귀 모델을 만드는 Scikit-learn의 from sklearn.linear_model import LinearRegression,
회귀 모델 성능 평가 지표인 MSE, 결정계수를 계산할 수 있는 from sklearn.metrics import mean_squared_error, r2_score를 모두 준비해준다.
2. 예제데이터
x = np.arange(10)
y = 2 * x + 1
plt.scatter(x,
y)
plt.title('Scatter plot of x vs y')
plt.xlabel('x')
plt.ylabel('y')
plt.show()
## 피어슨 상관계수
pearson_corr, pearson_p_value = pearsonr(x, y)
## 스피어만 상관계수
spearman_corr, spearman_p_value = spearmanr(x, y)
print("피어슨 상관계수:", pearson_corr, "피어슨 pvalue:", pearson_p_value)
print("스피어만 상관계수:", spearman_corr, "스피어만 pvalue:", spearman_p_value)
피어슨 상관계수(Pearson correlation coefficient) 는 두 연속형 변수 간 선형관계를 측정하고,
스피어만 상관계수(Spearman correlation coefficient) 는 순위 기반으로 상관관계를 측정한다.
대체로 피어슨 상관계수를 많이 사용하지만, 비선형인 상관관계를 파악할 때 스피어만 상관계수로 잘 잡아낼 수 있다.
3. 4가지 경우 - (1)
3-1) 피어슨 ▲ 스피어만 ▼
np.random.seed(123)
x1 = np.random.rand(30)
y1 = np.random.rand(30)
# 배열의 첫 번째 위치에 2.5 추가
x1 = np.insert(x1, 0, 2.5)
y1 = np.insert(y1, 0, 2.5)
# 시각화
plt.scatter(x1, y1, color='red')
plt.xlabel('X')
plt.ylabel('Y')
plt.show()
# 상관계수 계산 및 출력
pearson_corr1, _ = pearsonr(x1, y1)
spearman_corr1, _ = spearmanr(x1, y1)
print('피어슨 상관계수:', pearson_corr1)
print('스피어만 상관계수:', spearman_corr1)
3-2) 피어슨 ▼ 스피어만 ▼
np.random.seed(1)
x2 = np.random.rand(30)
y2 = np.random.rand(30)
# 시각화
plt.scatter(x2, y2, color='red')
plt.xlabel('X')
plt.ylabel('Y')
plt.show()
# 상관계수 계산 및 출력
pearson_corr2, _ = pearsonr(x2, y2)
spearman_corr2, _ = spearmanr(x2, y2)
print('피어슨 상관계수:', pearson_corr2)
print('스피어만 상관계수:', spearman_corr2)
3-3) 피어슨 ▼ 스피어만 ▲
np.random.seed(12)
x3 = np.linspace(1, 200, 50)
y3 = np.log(x3)
# 시각화
plt.scatter(x3, y3, color='red')
plt.xlabel('X')
plt.ylabel('Y')
plt.show()
# 상관계수 계산 및 출력
pearson_corr3, _ = pearsonr(x3, y3)
spearman_corr3, _ = spearmanr(x3, y3)
print('피어슨 상관계수:', pearson_corr3)
print('스피어만 상관계수:', spearman_corr3)
3-4) 피어슨 ▲ 스피어만 ▲
np.random.seed(123)
x4 = np.linspace(0, 1, 30)
y4 = 2 * x4 + 1 + np.random.normal(0, 0.1, 30)
# 시각화
plt.scatter(x4, y4, color='red')
plt.xlabel('X')
plt.ylabel('Y')
plt.show()
# 상관계수 계산 및 출력
pearson_corr4, _ = pearsonr(x4, y4)
spearman_corr4, _ = spearmanr(x4, y4)
print('피어슨 상관계수:', pearson_corr4)
print('스피어만 상관계수:', spearman_corr4)
4. 4가지 경우 - (2)
4-1) 비선형 관계
x = np.arange(10)
y = 2**x
plt.scatter(x, y)
plt.title('Scatter plot of x vs y')
plt.xlabel('x')
plt.ylabel('y')
plt.show()
# 피어슨 상관계수
pearson_corr, _ = pearsonr(x, y) #
# 스피어만 상관계수
spearman_corr, _ = spearmanr(x, y)
print("피어슨 상관계수:", pearson_corr)
print("스피어만 상관계수:", spearman_corr)
4-2) 노이즈가 있는 데이터
np.random.seed(0)
x = np.arange(10)
y = 2 * x + 1 + np.random.normal(0, 3, 10)
plt.scatter(x, y)
plt.title('Scatter plot of x vs y')
plt.xlabel('x')
plt.ylabel('y')
plt.show()
# 피어슨 상관계수
pearson_corr, _ = pearsonr(x, y)
# 스피어만 상관계수
spearman_corr, _ = spearmanr(x, y)
print("피어슨 상관계수:", pearson_corr)
print("스피어만 상관계수:", spearman_corr)
4-3) 비선형 + 노이즈 데이터
np.random.seed(0)
x = np.arange(10)
y = x**2 + np.random.normal(0, 10, 10)
plt.scatter(x, y)
plt.title('Scatter plot of x vs y')
plt.xlabel('x')
plt.ylabel('y')
plt.show()
# 피어슨 상관계수
pearson_corr, _ = pearsonr(x, y)
# 스피어만 상관계수
spearman_corr, _ = spearmanr(x, y)
print("피어슨 상관계수:", pearson_corr)
print("스피어만 상관계수:", spearman_corr)
1. Simple Linear Regression - (2) 단순선형회귀
1. 단순선형회귀 예제
1-1) 데이터 로드
rd = pd.read_csv(ppath + '/1. data/refrigerator.csv')
data = rd
data.head()
1-2) 시각화
plt.figure(figsize = (8, 5))
sns.scatterplot(x = 'total_refrigerator', y = 'as_time', data = data)
plt.title('Scatter plot of Total Refrigerator vs AS Time')
plt.xlabel('Total Refrigerator')
plt.ylabel('AS Time')
plt.show()
1-3) 단순선형회귀모형 적합
## 독립 변수와 종속 변수 설정
# X = data['total_refrigerator'] # 1차원 배열 (31, )
# X = X.values.reshape(31, 1) # 2차원 배열(31, 1)로 변환
X = data[['total_refrigerator']] ## 2차원 배열 (31, 1)
y = data['as_time'] ## 1차원 배열 (31, )
X, y
1-4) 모델 생성 및 학습
model = LinearRegression()
model.fit(X, y)
1-5) 회귀계수 확인
print('회귀계수:', model.coef_[0]) # beta1
print('절편:', model.intercept_) # beta0
절편값은 신경쓰지 않아도 된다.
1-6) 예측값 계산
y_pred = model.predict(X)
y_pred
2. 시각화
plt.figure(figsize = (8, 5))
sns.scatterplot(x = 'total_refrigerator',
y = 'as_time',
data = data,
label = 'Actual')
plt.plot(data['total_refrigerator'],
y_pred,
color = 'red',
label = 'Predicted')
plt.title('Linear Regression: Total Refrigerator vs AS Time')
plt.legend()
plt.show()
3. 모델 성능 평가
# R2 기본 값 계산
sst = np.sum((data['as_time'] - np.mean(data['as_time']))**2)
ssr = np.sum((y_pred - np.mean(data['as_time']))**2)
sse = np.sum((data['as_time'] - y_pred)**2)
r_squared = ssr / sst
print(f'R-제곱: {r_squared}')
# 모델이용한 R2 값 계산값 확인
r_squared_ = model.score(X, y)
print('R-squared:', r_squared_)
결정계수(R²) 를 직접 계산하고, 모델 내장 평가 함수(score) 와 결과를 비교해서 모델의 설명력(예측력)을 평가한다.
4. 새로운 데이터가 있을 때
new = pd.DataFrame({'total_refrigerator': [235000]}) # X와 동일한 형태로 만들어주기
predicted_as_time = model.predict(new)
predicted_as_time[0]
new는 기존 X와 동일한 구조를 가져야 하며, 반드시 데이터프레임 형태여야만 한다.
plt.figure(figsize=(8, 5))
sns.scatterplot(x = 'total_refrigerator',
y = 'as_time',
data = data,
label = 'Actual')
plt.plot(data['total_refrigerator'], y_pred,
color='blue',
label='Predicted')
plt.scatter(new['total_refrigerator'],
predicted_as_time,
color='red',
label='Prediction',
zorder=5)
plt.title('Linear Regression: Total Refrigerator vs AS Time')
plt.xlabel('Total Refrigerator')
plt.ylabel('AS Time')
plt.legend()
plt.show()
5. 회귀모형 가정 확인
- Residuals vs Fitted : 잔차가 랜덤하게 분포하는지 확인, 패턴이 없다면 선형성 가정 만족
- QQ Plot (Normal Q-Q) : 잔차가 정규 분포를 따르는지 확인, 점들이 대각선에 가까우면 정규성 가정 만족
- Scale-Location (Spread-Location) : 잔차의 분산이 일정한지 확인, 빨간 선이 수평에 가까울수록 등분산 가정 만족
- Residuals vs Leverage : 레버리지 점수가 높은 이상치가 있는지 확인, 높은 레버리지와 큰 잔차를 가진 점들은 영향력이 큼
# 다중 플롯을 그릴 수 있는 함수
# from scipy.stats import probplot
# 통계 모듈을 이용한 R^2 계산 (statsmodels)
X_with_const = sm.add_constant(X) ## 독립 변수에 상수 추가
model_sm = sm.OLS(y, X_with_const).fit() ## 모델 적합
def plot_regression_diagnostics(model_sm):
fig, axes = plt.subplots(2, 2, figsize = (8, 6))
# Residuals vs Fitted
sns.residplot(x = model_sm.fittedvalues, y = model_sm.resid, lowess = True, line_kws = {'color': 'red'}, ax = axes[0, 0])
axes[0, 0].set_title('Residuals vs Fitted')
axes[0, 0].set_xlabel('Fitted values')
axes[0, 0].set_ylabel('Residuals')
# Normal Q-Q
probplot(model_sm.resid, dist = "norm", plot = axes[0, 1])
axes[0, 1].set_title('Normal Q-Q')
# Scale-Location
sns.scatterplot(x = model_sm.fittedvalues, y = np.sqrt(np.abs(model_sm.resid)), ax = axes[1, 0])
axes[1, 0].axhline(y = np.mean(np.sqrt(np.abs(model_sm.resid))), color = 'r', linestyle = '--')
axes[1, 0].set_title('Scale-Location')
axes[1, 0].set_xlabel('Fitted values')
axes[1, 0].set_ylabel('√|Residuals|')
# Residuals vs Leverage
sm.graphics.influence_plot(model_sm, criterion = "cooks", ax = axes[1, 1], size = 4)
axes[1, 1].set_title('Residuals vs Leverage')
plt.tight_layout()
plt.show()
# 회귀모형의 가정 확인
plot_regression_diagnostics(model_sm)
statsmodels 선형 회귀 모델을 기반으로, 잔차(Residual) 분석을 통해 회귀 모델의 기본 가정들이 잘 만족되는지 시각적으로 확인한다.
즉, 회귀모형이 만족해야 하는 4가지 주요 가정을 시각적으로 점검하는 것이다.
statsmodels.OLS는 상수항(절편)을 명시적으로 추가해야 하므로 sm.add_constant() 사용하고,
fit()으로 학습 후 model_sm 객체 생성한다.
1) Residuals vs Fitted(선형성) : 잔차 vs 예측값. 잔차는 평균이 0이고 등분산이어야 한다. (패턴X)
2) Normal Q-Q Plot(잔차정규성) : 잔차의 정규성 확인. 점들은 45도 직선 위에 근접해야하고 휘어진다면 잔차가 비정규분포일 수 있다.
3) Scale Location Plot(등분산성) : 잔차의 크기가 예측값에 따라 일정한지 확인한다. 점들이 수평선 주변에 고르게 퍼져야한다.
4) Residuals vs Leverage(이상치/영향점) : 이상치 및 영향력 큰 관측치 확인. 고립된 점 식별이 가능하다.
참고) statsmodels 활용
# 모델 생성 및 학습
# statsmodels를 사용한 회귀 모델 생성
X_with_const = sm.add_constant(X) # 독립 변수에 상수 추가 # model = LinearRegression(), model.fit(X, y) 과 달리...선형 회귀 모델을 만들 때 절편을 포함시켜줘야 함 ㅠ
model_sm = sm.OLS(y, X_with_const).fit() # 회귀 모델 적합
model_sm
# R2 값 계산
r_squared = model_sm.rsquared
print("R2:", r_squared)
# 회귀 계수 및 절편 출력
print("회귀계수:", model_sm.params)
# 새로운 데이터 예측
new_data = pd.DataFrame({'total_refrigerator': [240000]})
# 새로운 데이터에 상수 추가 (수동..)
new_data_with_const = pd.DataFrame({'const': 1, 'total_refrigerator': [240000]})
# 데이터 확인
print(new_data_with_const)
# 모델을 사용하여 예측
point_estimate = model_sm.predict(new_data_with_const)
print("예측값:", point_estimate)
statsmodels로 선형회귀 모델을 만들어서,
R² (결정계수)을 출력하고, 회귀 계수를 확인하고, 새로운 데이터 예측까지 수행한다.
2. Multiple Linear Regression
1. Sikit-learn VS statsmodel
1-1) scikit-learn의 LinearRegression
- 주로 예측 모델링에 사용
- 머신러닝 워크플로우(데이터 전처리, 모델 선택, 하이퍼파라미터 튜닝 등)와 통합하기 좋음
- 간결하고 사용하기 쉬운 API를 제공
- 통계적 검정이나 모델 요약을 제공하지 않음...
1-2) statsmodels의 OLS
- 주로 통계 분석 및 모델 해석에 사용
- 상세한 모델 요약과 통계적 검정(예: p-value, t-value)을 제공
- 회귀 진단 플롯과 같은 시각화 기능을 포함
- pandas의 DataFrame를 선호
1-3) 둘 다 사용
- 두 라이브러리를 함께 사용하는 것도 가능하며, 각 라이브러리의 장점을 활용할 수 있음
- scikit-learn을 사용하여 모델을 학습하고 평가한 후, statsmodels를 사용하여 모델의 통계적 검정을 수행할 수 있음
2. 다중회귀분석
0. 준비
0-1) 라이브러리 준비
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import statsmodels.api as sm
import scipy.stats as stats
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LinearRegression
from statsmodels.stats.outliers_influence import variance_inflation_factor
from statsmodels.tools.tools import add_constant
from sklearn.preprocessing import StandardScaler
from mlxtend.feature_selection import SequentialFeatureSelector as SFS
from sklearn.inspection import permutation_importance
from sklearn.metrics import mean_squared_error
from scipy.stats import probplot
다중공선성을 계산해서 변수 간 상관성을 확인할 수 있는
from statsmodels.stats.outliers_influence import variance_inflation_factor,
회귀 모델에 상수항을 추가해주는 from statsmodels.tools.tools import add_constant,
데이터표준화를 통해 모델 성능을 높여주는 from sklearn.preprocessing import StandardScaler,
순차적 변수선택을 할 수 있는 from mlxtend.feature_selection import SequentialFeatureSelector as SFS,
Permutation 방식으로 변수 중요도를 평가할 수 있는 from sklearn.inspection import permutation_importance 등을 불러온다.
0-2) 데이터 로드
rd = pd.read_csv(ppath + '/1. data/wine.csv')
rd = rd.drop(['Class'], axis = 1)
0-3) 종속변수 및 독립변수 설정
X = rd.drop(columns=['Alcohol']) # 독립변수
y = rd['Alcohol'] # 종속변수
## 회귀모델 적합(학습) 데이터 및 테스트 데이터 분할
X_train, X_new, y_train, y_new = train_test_split(X, y,
test_size = 0.1,# new 가 붙은 데이터의 비중이
random_state = 1234)
1. VIF
1-1) VIF 확인
# VIF 계산 함수
def calculate_vif(X):
X = add_constant(X)
vif = pd.DataFrame()
vif['Variable'] = X.columns
vif['VIF'] = [variance_inflation_factor(X.values, i) for i in range(X.shape[1])]
return vif
# VIF 계산
vif_df0 = calculate_vif(X_train)
vif_df0
VIF는 각 독립변수가 다른 독립변수들과 얼마나 강한 선형 상관관계를 갖는지 측정하고,
이 값이 높을수록 해당 변수는 다중공선성의 문제 있을 가능성이 크다.
VIF 계산에 필수적인 add_constant(X)로 상수항(절편)을 추가한다.
variance_inflation_factor(X.values, i)로 변수 i에 대해 VIF를 계산하고, 해당 변수를 타겟변수로 놓고 나머지로 회귀분석을 돌린다.
1-2) VIF 10 이상 제거
def remove_high_vif(X, threshold = 10):
while True:
vif = calculate_vif(X)
max_vif = vif['VIF'].max()
if max_vif > threshold:
max_feature = vif.loc[vif['VIF'] == max_vif, 'Variable'].values[0]
if max_feature != 'const':
print(f"Removing {max_feature} with VIF {max_vif}")
X = X.drop(max_feature, axis=1)
else:
break
else:
break
return X
# VIF 계산 및 높은 VIF 값을 가진 변수 제거
X_reduced = remove_high_vif(X_train, threshold = 10)
print("Final VIF values:")
다중공선성 문제를 해결하기 위해 VIF가 높은 변수를 자동으로 제거해주는 함수를 정의한다.
calculate_vif로 현재 변수들의 VIF를 계산하고, VIF가 가장 높은 변수를 찾은 뒤, threshold보다 크고 const가 아닌 변수를 제거한다.
그리고 이 과정을 VIF가 기준 이하가 될 때까지 반복해서 최종적으로 공선성이 줄어든 변수 집합을 반환한다.
2. 변수선택법
2-1. 기본모델
model_raw = LinearRegression() # 객체 선언
model_raw.fit(X = X_train, # 독립변수
y = y_train) # 종속 변수
print('R2:', model_raw.score(X_train, y_train)) ## R2값
# 적합 모형 수정 R^2 값 확인
def adjusted_r2(model, X, y):
r2 = model.score(X, y)
n = X.shape[0]
p = X.shape[1]
adj_r2 = 1 - (1 - r2) * (n - 1) / (n - p - 1)
return adj_r2
adj_r2_raw = adjusted_r2(model_raw, X_train, y_train) ## adj R2 값
print('adj R2:', adj_r2_raw)
LinearRegression 객체를 생성한 뒤, X_train, y_train 데이터를 이용해 모델을 학습시킨다.
score() 메서드를 통해 내부적으로 R² 값을 반환한다.(R²는 전체 변동 중 모델이 설명하는 비율로 해석가능)
R²는 독립변수를 많이 넣을수록 커지는 한계를 지닌다.
Adjusted R²는 변수 수에 따라 R²를 페널티 적용해 보정함으로써 모델 복잡도 증가에 따른 과적합을 방지한다.
2-2. 전진선택법
2-2-1) 전진선택법 적용
# from mlxtend.feature_selection import SequentialFeatureSelector as SFS
# 전진 선택법 적용
sfs = SFS(LinearRegression(),
k_features = 'best',
forward = True, # 방향
floating = False, # True 이면 각 단계에서 변수를 추가하거나 제거할 때, 이전에 선택/제거된 변수들도 재고려한다는 의미
scoring = 'r2', # 중간 과정 계산 기준
cv = 0, # cv > 1 이면, 여러번 반복해서 더 정확하게 추정 가능 보통 5, 10 사용
verbose = 2) # 1: 요약 출력, 2: 과정 전체 출력, 0: 출력x
sfs = sfs.fit(X_train, y_train)
selected_features_fs = list(sfs.k_feature_names_)
selected_features_fs ## 전체모델하고 동일
다중회귀 모델의 성능을 높이기 위해, 불필요한 변수를 제거하고 성능이 좋은 변수만 선택하는 전진 선택법을 적용한다.
*파라미터
- LinearRegression() : 사용할 모델
- k_features : 'best' 최적의 feature 개수 자동선택
- forward=True : 전진선택
- floating=False : 기본 전진선택법만 수행
- scoring='r2' : 평가기준 결정계수(R²)
- cv=0 : 교차 검증없이 전체 데이터 기준으로 평가
- verbose=2 : 선택 과정 전체를 자세히 출력
2-2-2) 결과 확인 및 수정 R^2값 확인
model_fs = LinearRegression()
model_fs.fit(X_train[selected_features_fs], y_train)
adj_r2_fs = adjusted_r2(model_fs, X_train[selected_features_fs], y_train)
print('adj R2:', adj_r2_fs)
2-2-3) statsmodels를 사용하여 회귀 분석 결과 요약
X_const = sm.add_constant(X_train[selected_features_fs]) # 상수항 추가!!
model_fs2 = sm.OLS(y_train, X_const).fit() # model_fs.fit(X_train[selected_features_fs], y_train) 와 동일
results = model_fs2.summary()
results
Omnibus: 0.300, Prob(Omnibus): 0.861
잔차가 정규분포를 따르는지 테스트하는 통계량. 높은 p-value는 정규성 의미
Durbin-Watson: 2.063
잔차의 자기 상관을 테스트하는 통계량. 2에 가까운 값은 자기 상관이 없음
Jarque-Bera (JB): 0.089, Prob(JB): 0.957
잔차의 정규성을 테스트하는 통계량. 높은 p-value는 정규성을 의미
Skew: 0.015
잔차의 왜도. 0에 가까운 값은 대칭적임을 나타냄
Kurtosis: 3.106
잔차의 첨도. 3에 가까운 값은 정규분포에 가까움을 의미
F-statistic: 16.75
Prob (F-statistic): 5.88e-22, 모델 전체가 통계적으로 유의미함.
R-squared: 0.581, 독립 변수들이 종속 변수의 변동성을 약 59.1% 설명함.
Adjusted R-squared: 0.546
유의미한 변수:
Malic acid: 0.1382, p-value = 0.004 (양의 영향)
Color intensity: 0.1665, p-value < 0.001 (양의 영향)
Proline: 0.0010, p-value < 0.001 (양의 영향)
2-3. 후진선택법
2-3-1) 후진선택법 적용
sfs = SFS(LinearRegression(),
k_features = 'best',
forward = False, #
floating = False,
scoring = adjusted_r2,
cv = 0,
verbose = 2)
sfs = sfs.fit(X_train, y_train)
selected_features_bs = list(sfs.k_feature_names_)
selected_features_bs
2-3-2) 결과 확인 및 수정 R^2값 확인
model_bs = LinearRegression()
model_bs.fit(X_train[selected_features_bs], y_train)
adj_r2_bs = adjusted_r2(model_bs, X_train[selected_features_bs], y_train)
print('adj R2:', adj_r2_bs)
2-3-3) statsmodels를 사용하여 회귀 분석 결과 요약
X_const = sm.add_constant(X_train[selected_features_bs]) # 상수항 추가
model_bs2 = sm.OLS(y_train, X_const).fit()
results = model_bs2.summary()
results
모델 적합도:
R-squared: 0.558
독립 변수들이 종속 변수의 변동성을 약 55.8% 설명함.
Adjusted R-squared: 0.558
유의미한 변수:
Malic acid: 0.1266, p-value = 0.004 (양의 영향)
Color intensity: 0.1593, p-value < 0.001 (양의 영향)
Proline: 0.0011, p-value < 0.001 (양의 영향)
2-3. 단계선택법
2-3-1) 단계선택법 적용
sfs = SFS(LinearRegression(),
k_features = 'best',
forward = False, # 시작방향이 Full model(backward)
floating = True, ## True 이면 각 단계에서 변수를 추가하거나 제거할 때, 이전에 선택/제거된 변수들도 재고려한다는 의미
scoring = adjusted_r2,
cv = 0,
verbose = 2)
sfs = sfs.fit(X_train, y_train)
selected_features_step = list(sfs.k_feature_names_)
selected_features_step
2-3-2) 결과 확인 및 수정 R^2값 확인
model_step = LinearRegression()
model_step.fit(X_train[selected_features_step], y_train)
adj_r2_step = adjusted_r2(model_step, X_train[selected_features_step], y_train)
print('adj R2:', adj_r2_step)
2-3-3) statsmodels를 사용하여 회귀 분석 결과 요약
X_const = sm.add_constant(X_train[selected_features_step]) # 상수항 추가
model_step2 = sm.OLS(y_train, X_const).fit()
results = model_step2.summary()
results
모델 적합도:
R-squared: 0.578
독립 변수들이 종속 변수(Alcohol)의 변동성을 약 57.8% 설명함.
Adjusted R-squared: 0.558
독립 변수의 수를 고려한 후에도 모델이 약 55.8%의 변동성을 설명함.
모델 통계량:
F-statistic: 29.35
Prob (F-statistic): 3.19e-25
모델 전체가 통계적으로 유의미함.
유의미한 변수:
const: 11.0683, p-value < 0.001
Malic acid: 0.1266, p-value = 0.004 (유의미한 양의 영향)
Color intensity: 0.1593, p-value < 0.001 (유의미한 양의 영향)
Proline: 0.0011, p-value < 0.001 (유의미한 양의 영향)
2-4. 비교
# 데이터프레임 생성
output = {
'Method': ['Forward Selection', 'Backward Elimination', 'Stepwise Selection'],
'Adjusted R2': [adj_r2_fs, adj_r2_bs, adj_r2_step],
'Selected Features': [selected_features_fs, selected_features_bs, selected_features_step]
}
# MSE 계산
y_raw = model_raw.predict(X_new)
y_fs = model_fs.predict(X_new[selected_features_fs])
y_bs = model_bs.predict(X_new[selected_features_bs])
y_step = model_step.predict(X_new[selected_features_step])
mse_raw = mean_squared_error(y_new, y_raw)
mse_fs = mean_squared_error(y_new, y_fs)
mse_bs = mean_squared_error(y_new, y_bs)
mse_step = mean_squared_error(y_new, y_step)
3. 변수 중요도 계산
## Permutation Importance 계산
# 변수의 중요도를 평가하는 방법 중 하나로
# 각 변수를 무작위로 섞어서 모델의 성능 변화량을 측정
# 변수를 섞어서 모델의 성능이 크게 떨어지면 그 변수는 중요한 변수임
# 데이터 스케일링: 데이터 단위 크기를 맞춰주기 위한 방법 (데이터 표준화, z-score)
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X_train[selected_features_step])
X_scaled_df = pd.DataFrame(X_scaled,
columns = selected_features_step) # 스케일링 후 데이터프레임으로 변환 및 컬럼 이름 부여
result = permutation_importance(model_step,
X_scaled_df,
y_train,
n_repeats = 10,
random_state = 123)
# 변수 중요도 출력
feature_importance = pd.DataFrame({'Feature': selected_features_step,
'Importance': result.importances_mean})
feature_importance = feature_importance.sort_values(by = 'Importance', # 선택 컬럼
ascending = False) # 내림차순
print(feature_importance)
변수 중요도 평가 방법인 Permutation Importance 를 계산하며, 정확하고 신뢰도 높은 결과를 위해 데이터 스케일링도 함께 적용해본다.
StandardScaler로 데이터 표준화
변수들의 단위(스케일)가 다르면 모델 성능에 영향을 줄 수 있으므로, 평균 0 표준편차 1로 표준화 (z-score normalization)해준다.
특히 선형 회귀 계수 비교나 변수 중요도 해석에서 스케일 정규화는 필수적이다.
Permutation Importance 계산
각 변수별로 값을 무작위로 섞은 뒤 모델 성능 변화를 관찰해서, 성능이 크게 떨어질수록 그 변수는 중요한 역할을 한다는 뜻이다.
n_repeats=10: 동일 변수를 여러 번 섞어 평균 내는 방식 (신뢰도 향상)
변수 중요도 정리 및 출력
변수 이름과 평균 중요도를 데이터프레임으로 정리하고, 중요도 기준으로 내림차순 정렬한다.
4. 가정 확인
# 다중 플롯을 그릴 수 있는 함수
# from scipy.stats import probplot
# 통계 모듈을 이용한 R^2 계산 (statsmodels)
X_with_const = sm.add_constant(X_train[selected_features_step]) ## 독립 변수에 상수 추가
model_sm = sm.OLS(y_train, X_with_const).fit() ## 모델 적합
def plot_regression_diagnostics(model_sm):
fig, axes = plt.subplots(2, 2, figsize = (8, 6))
# Residuals vs Fitted
sns.residplot(x = model_sm.fittedvalues, y = model_sm.resid, lowess = True, line_kws = {'color': 'red'}, ax = axes[0, 0])
axes[0, 0].set_title('Residuals vs Fitted')
axes[0, 0].set_xlabel('Fitted values')
axes[0, 0].set_ylabel('Residuals')
# Normal Q-Q
probplot(model_sm.resid, dist = "norm", plot = axes[0, 1])
axes[0, 1].set_title('Normal Q-Q')
# Scale-Location
sns.scatterplot(x = model_sm.fittedvalues, y = np.sqrt(np.abs(model_sm.resid)), ax = axes[1, 0])
axes[1, 0].axhline(y = np.mean(np.sqrt(np.abs(model_sm.resid))), color = 'r', linestyle = '--')
axes[1, 0].set_title('Scale-Location')
axes[1, 0].set_xlabel('Fitted values')
axes[1, 0].set_ylabel('√|Residuals|')
# Residuals vs Leverage
sm.graphics.influence_plot(model_sm, criterion = "cooks", ax = axes[1, 1], size = 4)
axes[1, 1].set_title('Residuals vs Leverage')
plt.tight_layout()
plt.show()
# 회귀모형의 가정 확인
plot_regression_diagnostics(model_sm)
3. Categorical Variables
1. 데이터 로드
rd = pd.read_csv(ppath + '/1. data/uber_lyft_preprocessed1.csv', index_col = 0)
X = rd.drop(columns=['price'])
y = rd['price']
# 데이터셋을 훈련 세트와 테스트 세트로 분할
X_train, X_test, y_train, y_test = train_test_split(X, y,
test_size = 0.3,
random_state = 2020)
2. 연속형 변수만 사용했을 때
2-1. 기본 범주형 변수 제거 코드
X_train_numeric = X_train.select_dtypes(exclude=['object'])
X_train_numeric
dtype이 'object'인 열 = 일반적으로 문자열이고 범주형이다.
자동으로 문자열 타입 열들을 제거하고 수치형만 남기는 법, 빠르고 간단하지만 일부 category, bool 타입이 빠질 수 있다.
2-2. 메뉴얼 범주형 변수 제거
X_train_numeric = X_train.drop(['source', 'cab_type', 'name', 'short_summary', 'destination'], axis = 1)
X_train_numeric
source, cab_type 등 명시적으로 제거할 컬럼을 지정한다.
데이터프레임 구조를 정확히 알고 있을 때 더 정밀하게 조절 가능하다.
model_num = LinearRegression()
model_num.fit(X_train_numeric,
y_train)
print('R2:', model_num.score(X_train_numeric, y_train)) ## R2값
## 적합 모형 수정 R^2 값 확인
def adjusted_r2(model, X, y):
r2 = model.score(X, y)
n = X.shape[0]
p = X.shape[1]
adj_r2 = 1 - (1 - r2) * (n - 1) / (n - p - 1)
return adj_r2
adj_r2_num = adjusted_r2(model_num, X_train_numeric, y_train) ## adj R2 값
print('adj R2:', adj_r2_num)
범주형 변수를 제거하고 수치형 데이터(X_train_numeric)만으로 학습시킨다.
score()는 R² 계산(모델이 전체 데이터 변동 중 얼마나 설명했는지)한다.
이 때 값이 1에 가까울수록 예측력이 좋다.
Adjusted R²는 변수 수가 많아질수록 R²가 과대평가되는 현상을 보정한다.
복잡한 모델이 실제로 유의미하게 나은지를 확인할 때 필수적이다.
R²와 보정 R²가 비슷하면 과적합 없이 설명력 유지하고, 차이가 크면 변수 수 많아서 R² 과대평가 가능성 있다.
3. 범주형 변수 처리
# 사용자가 더미 변수화를 하고 싶은 변수를 선택
sel_categorical_columns = ['source',
'cab_type',
'name',
'short_summary',
'destination']
# 선택된 범주형 변수 더미 변수화 (원-핫 인코딩 + 기준 범주 제거)
# 기준 범주를 제거해줘야 다중공선성 제거
X_train_w_cate = pd.get_dummies(X_train,
columns = sel_categorical_columns,
drop_first = True) # 다중공선성 제거(True)
X_train_w_cate.head().T
get_dummies()는 각 범주형 변수의 고유값을 0/1 더미 변수로 바꾼다.
drop_first=True로 기준 범주를 자동으로 제거하고, 다중공선성 문제를 방지할 수 있음 (즉, VIF 낮춤)
.T로 행/열 전치해서 컬럼 구조를 세로로 확인 가능하다.
4. 종속변수 및 독립변수 설정
4-1. 변수 설정
model_w_cate = LinearRegression()
model_w_cate.fit(X = X_train_w_cate,
y = y_train)
print('R2:', model_w_cate.score(X_train_w_cate, y_train)) ## R2값
adj_r2_w_cate = adjusted_r2(model_w_cate, X_train_w_cate, y_train) ## adj R2 값
print('adj R2:', adj_r2_w_cate)
4-2. statsmodels 이용 회귀분석 결과 확인
# 데이터 타입 확인 및 변환
X_train_w_cate = X_train_w_cate.astype(float)
y_train = y_train.astype(float)
# 상수항 추가
X_train_w_cate = add_constant(X_train_w_cate)
# 모델 생성 및 학습
model = sm.OLS(y_train, X_train_w_cate)
results = model.fit()
# 요약 결과 출력
results.summary()
4-3. 변수형 변주 해석 방법
기준 범주
범주형 변수의 범주 중 하나는 기준 범주로 사용됨
이 기준 범주와 나머지 범주 간 비교를 해야 함
예를 들어, source 변수가 있고, source_1이 기준 범주라면, 다른 source 변수들은 source_1과의 비교를 통해 이해될 수 있음
더미 변수 계수
더미 변수의 계수는 해당 범주가 기준 범주와 비교하여 종속 변수에 미치는 영향을 나타냄
양의 회의 계수는 해당범주가 기준 범주에 비해 종속변수의 값을 증가시키는 반면, 음의 계수는 감소시킴
p-value 해석
각 더미 변수의 p-value는 해당 범주가 기준 범주에 비해 유의미한 차이가 있는지를 나타냄
일반적으로 p-value가 0.05보다 작으면 유의미한 차이가 있다고 봄
short_summary 변수
기존에는 총4개의 범주를 가졌으나X_train['short_summary'].unique()로 확인
현재 모델에는 3개의 범주가 되었음(기준 변수를 제거했기 때문)
4-4. 추가적인 해석
기준변수(short_summary_0)가 유의한지 알고 싶은 경우 :
기준변수는 더미변수화 과정에서 제거되어 회귀 분석 결과에 직접적으로 나타나지 않음
다음과 같은 방법이 가능 1) drop_first - False 사용(다중공선성 가능성 무시)
2) 기준변수를 바꿔서 다시 모델 적합
같은 범주형 변수인데, 어떤 변수는 p값이 0.05보다 작고 어떤것은 아닌 경우:
같은 범주형 변수의 다른 범주들이 회귀 분석에서 p값이 0.05보다 작은 경우와 그렇지 않은 경우가 존재할 수 있음
만약 이런경우에는 각 범주가 기준 범주와 비교할 때 종속 변수에 미치는 영향이 다르다는 것을 의미함
🤯
원래는 주차의 전반부, 후반부로 나누어 포스팅할 생각이었는데,
생각보다 내용이 많고 실습코드가 빼곡해서 전반부도 다시 2개로 나눠서 포스팅해야겠다.
이번 포스팅에서는 회귀분석의 기본인 단순선형회귀, 다중선형회귀, 범주형 변수 제어에 대해 알아보았다.
처음에는 생소해서 조금 복잡해보이지만, 잘 정리해두니 그렇게까지 어려운 것 같지는 않다.
다음 포스팅에서는 전반부의 나머지인 벌점회귀와 로지스틱 회귀, 그리고 부록에 대해서 정리할 예정이다.
과제도 다시 풀어서 포스팅해야하는데,
짝수주차에는 할 일이 너무 많다.
처음 과제할 때 바로바로 포스팅할걸 ㅋ ㅜㅜ
'데이터 취업기 > 부트캠프 학습기' 카테고리의 다른 글
[4주차] 머신러닝 2. 회귀 모델 심화 - (3) (0) | 2025.05.27 |
---|---|
[4주차] 머신러닝 2. 회귀 모델 기초 - (2) (0) | 2025.05.01 |
[3주차] 머신러닝 1. 모델링 기초 - (2) (0) | 2025.04.28 |
[3주차] 머신러닝 1. 모델링 기초 - (1) (0) | 2025.04.23 |
[2주차] 데이터 시각화 기초 실습 - (2) (0) | 2025.04.22 |