[Python] 데이터 사이언스 스쿨 - 5.3 다중공선성과 변수선택

Updated:

데이터 사이언스 스쿨 자료를 토대로 공부한 내용입니다.

실습과정에서 필요에 따라 내용의 누락 및 추가, 수정사항이 있습니다.


기본 세팅

import numpy as np
import pandas as pd

import matplotlib as mpl
import matplotlib.pyplot as plt
import seaborn as sns

import warnings
%matplotlib inline
%config InlineBackend.figure_format = 'retina'

mpl.rc('font', family='NanumGothic') # 폰트 설정
mpl.rc('axes', unicode_minus=False) # 유니코드에서 음수 부호 설정

# 차트 스타일 설정
sns.set(font="NanumGothic", rc={"axes.unicode_minus":False}, style='darkgrid')
plt.rc("figure", figsize=(10,8))

warnings.filterwarnings("ignore")

5.3 다중공선성과 변수선택

다중공선성: 독립 변수의 일부가 다른 독립 변수의 조합으로 표현될 수 있는 경우로서 즉, 선형 독립이 아닌 경우이다.

이는 독립 변수의 공분산 행렬이 full rank 이어야 한다는 조건을 침해한다.

예제 데이터

from statsmodels.datasets.longley import load_pandas

dfy = load_pandas().endog # 종속변수 이름: TOTEMP
dfX = load_pandas().exog
df = pd.concat([dfy, dfX], axis=1)

corr_M = dfX.corr() # 독립변수 상관관계
mask = np.array(corr_M)
mask[np.tril_indices_from(mask)] = False

plt.figure(figsize= (10,8))

sns.heatmap(corr_M, 
            cmap = sns.light_palette("red", as_cmap=True),
            annot = True, 
            mask = mask)

plt.show()

png

  • 독립변수간의 상관관계가 강한 경우가 많다. 즉, 다중공선성이 존재한다.

5.3.1 다중공선성 문제점: 조건수 증가

from sklearn.model_selection import train_test_split
import statsmodels.api as sm

def get_model1(seed):
    df_train, df_test = train_test_split(df, test_size=0.5, random_state=seed)
    model = sm.OLS.from_formula("TOTEMP ~ GNPDEFL + POP + GNP + YEAR + ARMED + UNEMP", data=df_train)
    return df_train, df_test, model.fit()


df_train, df_test, result1 = get_model1(3)
print(result1.summary())
                            OLS Regression Results                            
==============================================================================
Dep. Variable:                 TOTEMP   R-squared:                       1.000
Model:                            OLS   Adj. R-squared:                  0.997
Method:                 Least Squares   F-statistic:                     437.5
Date:                Sat, 12 Jun 2021   Prob (F-statistic):             0.0366
Time:                        01:03:49   Log-Likelihood:                -44.199
No. Observations:                   8   AIC:                             102.4
Df Residuals:                       1   BIC:                             103.0
Df Model:                           6                                         
Covariance Type:            nonrobust                                         
==============================================================================
                 coef    std err          t      P>|t|      [0.025      0.975]
------------------------------------------------------------------------------
Intercept  -1.235e+07   2.97e+06     -4.165      0.150      -5e+07    2.53e+07
GNPDEFL      106.2620     75.709      1.404      0.394    -855.708    1068.232
POP            2.2959      0.725      3.167      0.195      -6.915      11.506
GNP           -0.3997      0.120     -3.339      0.185      -1.920       1.121
YEAR        6300.6231   1498.900      4.203      0.149   -1.27e+04    2.53e+04
ARMED         -0.2450      0.402     -0.609      0.652      -5.354       4.864
UNEMP         -6.3311      1.324     -4.782      0.131     -23.153      10.491
==============================================================================
Omnibus:                        0.258   Durbin-Watson:                   1.713
Prob(Omnibus):                  0.879   Jarque-Bera (JB):                0.304
Skew:                           0.300   Prob(JB):                        0.859
Kurtosis:                       2.258   Cond. No.                     2.01e+10
==============================================================================

Notes:
[1] Standard Errors assume that the covariance matrix of the errors is correctly specified.
[2] The condition number is large, 2.01e+10. This might indicate that there are
strong multicollinearity or other numerical problems.
  • 위 회귀분석 결과에서 알 수 있듯이 다중공선성이 존재시 조건수도 많아지게 된다.

  • 여기서 train으로 학습한 회귀모형의 결정계수가 1로서 100%로 나타났다.

5.3.2 다중공선성 문제점: 과최적화

from sklearn.metrics import r2_score

test1 = []

for i in range(10):
    df_train, df_test, result = get_model1(i)
    
    pred_test = result.predict(df_test)
    rsquared = r2_score(df_test.TOTEMP, pred_test)
    test1.append(round(rsquared,3))

test1
[0.982, 0.974, 0.988, 0.759, 0.981, 0.894, 0.88, 0.931, 0.861, 0.968]
  • train에선 결정계수가 1이 었으나 test에선 모두 train보다 결정계수가 작게 나왔다.

  • 원래 test에서의 예측 성능이 보통 train보다 낮긴 하지만 과최적화 문제가 있다면 성능이 더 크게 저하된다.

5.3.3 다중공선성 해결법

  • 변수 선택법으로 의존적인 변수 삭제

  • PCA(principal component analysis) 방법으로 의존적인 성분 삭제

  • 정규화(regularized) 방법 사용 (여기선 정규화라고 칭했지만 보통 규제라고 표현한다.)

5.3.4 분산팽창계수(VIF)

다중 공선성을 없애는 가장 기본적인 방법은 다른 독립변수에 의존하는 변수를 없애는 것이다.

가장 의존적인 독립변수를 선택하는 방법으로는 VIF(Variance Inflation Factor)를 사용할 수 있다.

  • VIF는 특정 독립변수를 나머지 독립변수로 적합했을 때 성능을 나타낸 것이다.

  • 다른 변수에 의존할수록 VIF 값이 높다.

statsmodels.stats.outliers_influencevariance_inflation_factor()를 이용하여 VIF를 계산할 수 있다.

from statsmodels.stats.outliers_influence import variance_inflation_factor

vif = pd.DataFrame()

lst = []

# 독립변수의 갯수 만큼 반복
for i in range(dfX.shape[1]):
    v = variance_inflation_factor(dfX.values, i) # 컬럼을 정수로 지정한다.
    lst.append(v)
    
vif["VIF Factor"] = lst
vif["features"] = dfX.columns
vif
VIF Factor features
0 12425.514335 GNPDEFL
1 10290.435437 GNP
2 136.224354 UNEMP
3 39.983386 ARMED
4 101193.161993 POP
5 84709.950443 YEAR
  • 각 독립변수의 VIF 계수를 확인하였다.

  • 이 중에서 VIF 계수가 작은 순으로 3가지 독립변수 GNP, ARMED, UNEMP만 사용해보자.

def get_model2(seed):
    df_train, df_test = train_test_split(df, test_size=0.5, random_state=seed)
    model = sm.OLS.from_formula("TOTEMP ~ GNP + ARMED + UNEMP", data=df_train)
    
    return df_train, df_test, model.fit()


df_train, df_test, result2 = get_model2(3)
print(result2.summary())
                            OLS Regression Results                            
==============================================================================
Dep. Variable:                 TOTEMP   R-squared:                       0.989
Model:                            OLS   Adj. R-squared:                  0.981
Method:                 Least Squares   F-statistic:                     118.6
Date:                Sat, 12 Jun 2021   Prob (F-statistic):           0.000231
Time:                        01:03:49   Log-Likelihood:                -57.695
No. Observations:                   8   AIC:                             123.4
Df Residuals:                       4   BIC:                             123.7
Df Model:                           3                                         
Covariance Type:            nonrobust                                         
==============================================================================
                 coef    std err          t      P>|t|      [0.025      0.975]
------------------------------------------------------------------------------
Intercept   5.399e+04   1013.271     53.281      0.000    5.12e+04    5.68e+04
GNP            0.0500      0.005     10.669      0.000       0.037       0.063
ARMED         -1.2804      0.497     -2.574      0.062      -2.662       0.101
UNEMP         -1.4265      0.363     -3.931      0.017      -2.434      -0.419
==============================================================================
Omnibus:                        0.628   Durbin-Watson:                   2.032
Prob(Omnibus):                  0.731   Jarque-Bera (JB):                0.565
Skew:                           0.390   Prob(JB):                        0.754
Kurtosis:                       1.958   Cond. No.                     2.44e+06
==============================================================================

Notes:
[1] Standard Errors assume that the covariance matrix of the errors is correctly specified.
[2] The condition number is large, 2.44e+06. This might indicate that there are
strong multicollinearity or other numerical problems.
  • VIF계수가 작은 GNP, ARMED, UNEMP만을 사용하였을 때 여전히 조건수가 많지만 이전보다 줄어든 것을 알 수 있다.
df_train.std()
TOTEMP      3325.272051
GNPDEFL       10.975224
GNP        92714.595875
UNEMP       1028.961751
ARMED        678.062523
POP         6499.246792
YEAR           4.566962
dtype: float64
  • 변수별 단위차가 있어 스케일링이 필요하다.
def get_model3(seed):
    df_train, df_test = train_test_split(df, test_size=0.5, random_state=seed)
    model = sm.OLS.from_formula("TOTEMP ~ scale(GNP) + scale(ARMED) + scale(UNEMP)", data=df_train)
    return df_train, df_test, model.fit()


df_train, df_test, result3 = get_model3(3)
print(result3.summary())
                            OLS Regression Results                            
==============================================================================
Dep. Variable:                 TOTEMP   R-squared:                       0.989
Model:                            OLS   Adj. R-squared:                  0.981
Method:                 Least Squares   F-statistic:                     118.6
Date:                Sat, 12 Jun 2021   Prob (F-statistic):           0.000231
Time:                        01:03:49   Log-Likelihood:                -57.695
No. Observations:                   8   AIC:                             123.4
Df Residuals:                       4   BIC:                             123.7
Df Model:                           3                                         
Covariance Type:            nonrobust                                         
================================================================================
                   coef    std err          t      P>|t|      [0.025      0.975]
--------------------------------------------------------------------------------
Intercept     6.538e+04    163.988    398.686      0.000    6.49e+04    6.58e+04
scale(GNP)    4338.7051    406.683     10.669      0.000    3209.571    5467.839
scale(ARMED)  -812.1407    315.538     -2.574      0.062   -1688.215      63.933
scale(UNEMP) -1373.0426    349.316     -3.931      0.017   -2342.898    -403.187
==============================================================================
Omnibus:                        0.628   Durbin-Watson:                   2.032
Prob(Omnibus):                  0.731   Jarque-Bera (JB):                0.565
Skew:                           0.390   Prob(JB):                        0.754
Kurtosis:                       1.958   Cond. No.                         4.77
==============================================================================

Notes:
[1] Standard Errors assume that the covariance matrix of the errors is correctly specified.
  • 스케일링으로 인해 조건수가 4.77로 크게 감소하였다.

  • 여기서는 train으로 학습한 회귀모형의 결정계수가 0.989로 나타났다.

from sklearn.metrics import r2_score

test2 = []

for i in range(10):
    df_train, df_test, result = get_model3(i)
    
    pred_test = result.predict(df_test)
    rsquared = r2_score(df_test.TOTEMP, pred_test)
    test2.append(round(rsquared,3))

test2
[0.976, 0.984, 0.969, 0.94, 0.977, 0.956, 0.98, 0.992, 0.984, 0.979]
  • train에서의 결정계수 0.989와 test 결정계수가 어느정도 비슷하게 나타나 이전보다 차이가 줄었다.
plt.subplot(121)
plt.plot(test1, 'ro', label="검증 성능")
plt.axhline(result1.rsquared, label="학습 성능")
plt.legend()
plt.xlabel("시드값")
plt.ylabel("성능(결정계수)")
plt.title("다중공선성 제거 전")
plt.ylim(0.7, 1.1)

plt.subplot(122)
plt.plot(test2, 'ro', label="검증 성능")
plt.axhline(result2.rsquared, label="학습 성능")
plt.legend()
plt.xlabel("시드값")
plt.ylabel("성능(결정계수)")
plt.title("다중공선성 제거 후")
plt.ylim(0.7, 1.1)

plt.suptitle("다중공선성 제거 전과 제거 후의 성능 비교", y=1.04)
plt.tight_layout()
plt.show()

png

  • 그래프로 확인하면 다중공선성 제거 전에는 검증/학습 성능의 차이가 크지만 제거 후 성능 차이가 감소한 것을 확인 할 수 있다.

Leave a comment