본문 바로가기
내가 하는 데이터분석/내가 하는 통계분석

[다중 회귀분석, Multiple Regression] - 다중공선성 with Python

by Pymoon 2023. 1. 10.

다중 선형 회귀분석 중에서 필수적으로 해결해야 하는 것인 다중공선성에 대해 정리해보고자 한다.

 

 

 

 

이 전엔 선형 회귀분석에 대해서 정리해 보았다.

 

선형 회귀분석(Linear Regression)

하나 혹은 그 이상의 원인(독립변수)이 종속변수에 미치는 영향을 추적해서 식으로 표현하는 회귀분석을 복습해 보자. 회귀분석의 검토사항 모형이 데이터를 잘 적합하고 있는가? 회귀모형이

py-moon.tistory.com

 

 


다중공선성 : 다중 회귀분석에서 독립변수들 간에 강한 상관관계가 나타나는 문제



단순 선형 회귀분석과 다중 선형 회귀분석의 차이점은 독립변수의 수이다.

단순 선형 회귀분석 -> 독립변수 1개, 종속변수 1개

다중 선형 회귀분석 -> 독립변수 2개 이상, 종속변수 1개

 


 

다중 선형 회귀분석에서 다중공선성을 검사하고 진단한 후에 제거하는 과정을 정리하고자 한다.



1
2
3
import pandas as pd
 
Cars = pd.read_csv('data/Cars93.csv')
cs

 

> 다중 회귀분석에 필요한 데이터를 불러온다.

 

> Cars데이터에는 많은 변수가 있지만, 우리는 종속변수로 Price, 독립변수로 EngineSize, RPM, Weight, Length, MPG.city, MPG.highway를 가지고 분석을 진행하고자 한다.

 

 

 

 

1
2
3
4
5
6
7
import numpy as np
import statsmodels.api as sm
import statsmodels.formula.api as smf
 
Cars.columns = Cars.columns.str.replace('.','')
model = smf.ols(formula ='Price ~ EngineSize + RPM + Weight + Length + MPGcity + MPGhighway', data = Cars)
result = model.fit()
cs

 

> 5줄에서 변수에 온점이 있는 경우 제거해 주었다.

> 위 코드에 대한 summary를 통해서 회귀모형에 대한 정보를 얻고자 한다.



result.summary()

 

> summary테이블을 봤을 때 모델 자체는 유의하다는 결과를 보여주지만(Prob F-statistic) 이 모델이 전체 데이터를 설명하는 비율이 54.2%로 나온 것을 보았을 때 현재까지는 직관으론 그리 좋은 모델이라고 보기엔 어렵다는 것이다.

> 다음은 다중 공선성을 진단해 보자.



corr()

> 독립변수들 간의 상관계수를 확인함으로써 다중 공선성을 진단해본 결과,

> MPGcity변수와 MPGhighway변수가 약 0.94의 높은 양의 상관관계를 보이고 있다.

> 따라서 다중공선성 문제가 있다고 판단하지만, 둘 중 어느 변수를 제거해야 하는지를 모른다.

> 이번엔 VIF(Variance Inflation Factor) 값을 통해서 추려내보고자 한다. 일반적으로 VIF값이 10 이상이면 다중 공선성을 예상한다.




1
2
3
4
5
6
7
8
9
from patsy import dmatrices
from statsmodels.stats.outliers_influence import variance_inflation_factor
 
y, X = dmatrices('Price ~ EngineSize + RPM + Weight + Length + MPGcity + MPGhighway', data=Cars, return_type='dataframe')
 
vif_list=[]
for i in range(1len(X.columns)):
    vif_list.append([variance_inflation_factor(X.values, i), X.columns[i]])
pd.DataFrame(vif_list, columns=['vif''variable'])
cs

 

> VIF값을 알 수 있는 코드를 통해 확인해 보자.



 

> 결과를 보니 MPGcity변수가 VIF값이 약 13.6으로 MPGhighway변수보다 높으므로 MPGcity변수를 제거해야겠다는 생각이 든다.

> 해당 변수를 제거하고 다시 회귀모델을 살펴보자.



result.summary()

> 변수를 제거한 후 결과를 보니 모델 자체는 여전히 유의하지만 설명력도 여전히 낮다. 변화가 있긴 하지만 눈에 띌 정도는 아니라고 본다.

> 그렇다면, 이번에는 변수 선택법을 통해서 독립변수 중 유의한 변수를 고르고 모델의 성능을 최적화시켜보도록 하자.




1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
import time
import itertools
 
def processSubset(X, y, feature_set):
    model = sm.OLS(y, X[list(feature_set)])
    regr = model.fit()
    AIC = regr.aic
    return {'model':regr, 'AIC':AIC}
 
# 전진선택법
def forward(X, y, predictors):
    remaining_predictors = [p for p in X.columns.difference(['Intercept']) if p not in predictors]
    results = []
    for p in remaining_predictors:
        results.append(processSubset(X=X, y=y, feature_set = predictors + [p] + ['Intercept']))
        
    models = pd.DataFrame(results)
    best_model = models.loc[models['AIC'].argmin()]
    print('Processed ', models.shape[0], 'models on 'len(predictors)+1'predictors in')
    print('Selected predictors: ', best_model['model'].model.exog_names, 'AIC:', best_model[0])
    return best_model
 
 
# 후진소거법
def backward(X, y, predictors):
    tic = time.time()
    results = []
    
    for combo in itertools.combinations(predictors, len(predictors) -1):
        results.append(processSubset(X=X, y=y, feature_set = list(combo) + ['Intercept']))
    
    models = pd.DataFrame(results)
    best_model = models.loc[models['AIC'].argmin()]
    toc = time.time()
    print('Processed ', models.shape[0], 'models on 'len(predictors)-1'predictors in', (toc - tic))
    print('Selected predictors: ', best_model['model'].model.exog_names, 'AIC:', best_model[0])
    return best_model
 
# 단계적 선택법
def Stepwise_model(X, y):
    Stepmodels = pd.DataFrame(columns = ['AIC''model'])
    tic = time.time()
    predictors = []
    Smodel_before = processSubset(X, y, predictors + ['Intercept'])['AIC']
    
    for i in range(1len(X.columns.difference(['Intercept'])) +1):
        Forward_result = forward(X=X, y=y, predictors=predictors)
        print('Forward')
        Stepmodels.loc[i] = Forward_result
        predictors = Stepmodels.loc[i]['model'].model.exog_names
        predictors = [k for k in predictors if k != 'Intercept']
        Backward_result = backward(X=X, y=y, predictors=predictors)
        
        if Backward_result['AIC'< Forward_result['AIC']:
            Stepmodels.loc[i] = Backward_result
            predictors = Stepmodels.loc[i]['model'].model.exog_names
            Smodel_before = Stepmodels.loc[i]['AIC']
            predictors = [k for k in predictors if k != 'Intercept']
            print('Backward')
            
        if Stepmodels.loc[i]['AIC'> Smodel_before:
            break
        else:
            Smodel_before = Stepmodels.loc[i]['AIC']
    
    toc = time.time()
    print('Total elapsed time:', (toc - tic), 'seconds.')
    
    return (Stepmodels['model'][len(Stepmodels['model'])])
cs

> 위 코드는 [파이썬 한 권으로 끝내기]를 참고하여 작성한 코드이다.

> 위에서 진행하는 변수 선택법은 AIC값을 기준으로 변수를 선택하거나 제거하는 방식을 사용했다.

> 각 시행마다 AIC값을 계산하여 높은 AIC값을 유발하는 변수는 제거하였다.

 

> 유의한 변수를 찾거나 방해가 되는 변수를 제거하는 전진선택법, 후진소거법, 단계적선택법을 사용을 하는 것도 좋지만, 그 알고리즘을 이해하는 것도 역량을 성장시킬 수 있는 방법 중 하나라고 생각한다.

> 위 코드를 실행시킨 모델의 summary를 확인해보자.


Stepwise_best_model.summary()

> 결과적으로 마지막에 남은 변수는 Weight, RPM, EngineSize 세 변수이다.

> 모델이 데이터를 약 54.7%를 설명하고 있지만, 높은 설명력이 아니므로 다른 추가적인 과정을 거쳐야할 것으로 보인다.

> 이 모델이 의미하는 회귀식은 y = 0.0073Weight + 0.0071RPM + 4.3054EngineSize - 51.7933 이다.



OUTTRO.


다중회귀분석을 많이 사용하지만 다중공선성 문제를 필수적으로 해결하려하지 않았던 지난 분석들을 돌아본다.

물론 최근에는 없지만 "필수적"이라는 수식어 때문에 괜히 찔리는 듯 하다.

다중회귀분석을 사용할 때 다중공선성을 상관계수, VIF, 변수선택법 등을 통해서 해결한다면 더 좋은 성능과 설명력을 가질 수 있다.

물론, 다중공선성이 모델 성능의 전부는 아니지만 꼭 해결해야하는 문제임을 상기시킨다.