Skip to content

회귀분석의 전제조건

회귀분석의 전제조건, 선형성, 독립성, 등분산성, 정규성


선형회귀는 분석 데이터가 선형성, 독립성, 등분산성, 정규성의 성질을 갖는다고 가정하기 때문에 좋은 선형 회귀분석 모델을 만들기 위해서는 네 개의 기본가정을 모두 만족하는지 확인해야 한다.

iris 데이터를 통해 네 가지 기본가정이 선형회귀 모델에 미치는 영향을 확인해보자.

0. 예제 데이터

import pydataset as pds
import pandas as pd

df = pds.data('iris')
df.reset_index(drop=True, inplace=True)
df.columns = [i.replace('.', '_') for i in df.columns]

df.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 150 entries, 0 to 149
Data columns (total 5 columns):
 #   Column        Non-Null Count  Dtype
---  ------        --------------  -----
 0   Sepal_Length  150 non-null    float64
 1   Sepal_Width   150 non-null    float64
 2   Petal_Length  150 non-null    float64
 3   Petal_Width   150 non-null    float64
 4   Species       150 non-null    object
dtypes: float64(4), object(1)
memory usage: 6.0+ KB

1. 선형성

iris 데이터의 분포는 다음과 같다.

import matplotlib.pyplot as plt
import seaborn as sns

sns.pairplot(
    data=df,
    hue='Species',
)

plt.show()

iris_pairplot

만약 Sepal_Length를 예측하려고 하는 종속변수라고 한다면, 위 그래프를 보았을 때 Sepal_Length와 대략적인 선형관계를 이루고 있는 변수는 Petal_Length와 Petal_Width이고, 선형성을 만족하지 않는 것은 Sepal_Width인 것으로 보인다.

이 상황에서 선형 회귀모델을 만들어 보자.

import statsmodels.formula.api as smf

formula = 'Sepal_Length ~ Sepal_Width + Petal_Length + Petal_Width'

model = smf.ols(formula=formula, data=df)
result = model.fit()

print(result.summary())
                            OLS Regression Results
==============================================================================
Dep. Variable:           Sepal_Length   R-squared:                       0.859
Model:                            OLS   Adj. R-squared:                  0.856
Method:                 Least Squares   F-statistic:                     295.5
Date:                Mon, 31 Jan 2022   Prob (F-statistic):           8.59e-62
Time:                        18:26:28   Log-Likelihood:                -37.321
No. Observations:                 150   AIC:                             82.64
Df Residuals:                     146   BIC:                             94.69
Df Model:                           3
Covariance Type:            nonrobust
================================================================================
                   coef    std err          t      P>|t|      [0.025      0.975]
--------------------------------------------------------------------------------
Intercept        1.8560      0.251      7.401      0.000       1.360       2.352
Sepal_Width      0.6508      0.067      9.765      0.000       0.519       0.783
Petal_Length     0.7091      0.057     12.502      0.000       0.597       0.821
Petal_Width     -0.5565      0.128     -4.363      0.000      -0.809      -0.304
==============================================================================
Omnibus:                        0.345   Durbin-Watson:                   2.060
Prob(Omnibus):                  0.842   Jarque-Bera (JB):                0.504
Skew:                           0.007   Prob(JB):                        0.777
Kurtosis:                       2.716   Cond. No.                         54.7
==============================================================================

Notes:
[1] Standard Errors assume that the covariance matrix of the errors is correctly specified.

모든 변수의 유의확률(p-value)이 0.05 미만으로 나와 유의하다고 나온다. 그 이유는 가장 선형성을 강하게 만족하는 Petal_Length와 Petal_Width의 영향도를 뺀 나머지 값들이 종속변수인 Sepal_Width와 선형성을 이루기 때문인데, 시각화로 확인하면 다음과 같다.

df['Rest_Sepal_Width'] = (
    df['Sepal_Length']
    - result.params.Petal_Length * df['Petal_Length']
    - result.params.Petal_Width * df['Petal_Width']
)

sns.pairplot(
    data=df,
    hue='Species',
)

plt.show()

iris_pairplot

Petal_Length와 Petal_Width의 영향도를 제거한 Rest_Sepal_Width를 Sepal_Width와 비교해보면 선형성이 아주 약간 생긴 것을 확인할 수 있다.

Petal_Length와 Petal_Width의 영향도를 뺀 나머지 값을 위와 같이 계산하는 이유는 Sepal_Length를 \(y\), Sepal_Width를 \(x_{0}\), Petal_Length를 \(x_{1}\), Petal_Width를 \(x_{2}\)라고 할 때 회귀식은 아래와 같이 정리되고,

\[ y = \beta_{0}x_{0} + \beta_{1}x_{1} + \beta_{2}x_{2} + \varepsilon \]

따라서 종속변수 \(y\)에서 Petal_Length와 Petal_Width의 영향도를 뺀 나머지 값인 Rest_Sepal_Width(\(\beta_{0}x_{0} + \varepsilon\))는 아래와 같이 정리되기 때문이다.

\[ \beta_{0}x_{0} + \varepsilon = y - \beta_{1}x_{1} - \beta_{2}x_{2} \]

Sepal_Width의 영향력(결정계수)을 확인하기 위해 Sepal_Width와 Sepal_Length를 단변량 회귀분석을 통해 확인해보자.

import statsmodels.formula.api as smf

formula = 'Sepal_Length ~ Sepal_Width'

model = smf.ols(formula=formula, data=df)
result = model.fit()

print(result.summary())
                            OLS Regression Results
==============================================================================
Dep. Variable:           Sepal_Length   R-squared:                       0.014
Model:                            OLS   Adj. R-squared:                  0.007
Method:                 Least Squares   F-statistic:                     2.074
Date:                Mon, 31 Jan 2022   Prob (F-statistic):              0.152
Time:                        19:05:50   Log-Likelihood:                -183.00
No. Observations:                 150   AIC:                             370.0
Df Residuals:                     148   BIC:                             376.0
Df Model:                           1
Covariance Type:            nonrobust
===============================================================================
                  coef    std err          t      P>|t|      [0.025      0.975]
-------------------------------------------------------------------------------
Intercept       6.5262      0.479     13.628      0.000       5.580       7.473
Sepal_Width    -0.2234      0.155     -1.440      0.152      -0.530       0.083
==============================================================================
Omnibus:                        4.389   Durbin-Watson:                   0.952
Prob(Omnibus):                  0.111   Jarque-Bera (JB):                4.237
Skew:                           0.360   Prob(JB):                        0.120
Kurtosis:                       2.600   Cond. No.                         24.2
==============================================================================

Notes:
[1] Standard Errors assume that the covariance matrix of the errors is correctly specified.

Sepal_Width의 유의확률(p-value)이 0.152로 0.05보다 크기 때문에 Sepal_Width 단독으로는 Sepal_Length에 영향력이 없다는 귀무가설을 기각할 수 없다. 즉, Sepal_Width가 Sepal_Length에 영향력이 없다고 해석된다.

2. 독립성(다중공선성)

독립성이란 독립변수 간에 상관관계가 없이 독립성을 만족하는 특성을 의미하며, 다중회귀분석에서 중요하게 다뤄지는 가정이다.

iris 데이터에서 변수 간 상관성을 확인해보자.

corr_result = df.corr(method="pearson")
print(corr_result)
              Sepal_Length  Sepal_Width  Petal_Length  Petal_Width
Sepal_Length      1.000000    -0.117570      0.871754     0.817941
Sepal_Width      -0.117570     1.000000     -0.428440    -0.366126
Petal_Length      0.871754    -0.428440      1.000000     0.962865
Petal_Width       0.817941    -0.366126      0.962865     1.000000

시각화해서 보기 좋게 표현하면 아래와 같다.

sns.set(font_scale=1)

fig, ax = plt.subplots()
ax = sns.heatmap(
    data=corr,
    cmap=plt.cm.coolwarm,
    annot=True,
    fmt='.4f',
)

plt.show()

iris_corr

Petal_Length와 Petal_Width의 상관성이 0.96으로 매우 높게 나오는데, 독립변수 간의 상관성이 있을 경우 다중공선성(Multicollinearity)이 있다고 표현되며, 분산팽창요인(VIF, Variance Inflation Factors)을 통해 다중공선성을 계산할 수 있다.
VIF를 계산하는 공식은 아래와 같고, \({R^{2}_{i}}\)\(i\) 번째 독립변수에 대해 다른 독립변수들로 회귀분석을 시행한 선형 모델의 \(R^{2}\)라는 뜻이다.

\[ VIF_{i} = \frac{1}{1-{R^{2}_{i}}} \]

Tip

VIF가 10이 넘으면 다중공선성이 있으며 5가 넘으면 주의할 필요가 있다고 보는데, 독립변수 a와 b가 서로 상관관계가 있다고 했을 때 두 변수 모두 VIF가 높고, 어느 하나만 VIF가 높은 경우는 없다. 서로 연관 있는 변수끼리 VIF가 높다.

Python에서는 statsmodels 패키지에서 제공하는 함수를 통해 직접 확인해보자.

import statsmodels.formula.api as smf
from statsmodels.stats.outliers_influence import variance_inflation_factor

formula = 'Sepal_Length ~ Sepal_Width + Petal_Length + Petal_Width'

model = smf.ols(formula=formula, data=df)

vif = pd.DataFrame(
    {'VIF': variance_inflation_factor(model.exog, i), 'columns': column}
    for i, column in enumerate(model.exog_names)
)

vif.sort_values(by='VIF', ascending=False, inplace=True)
vif.reset_index(drop=True, inplace=True)

print(vif)
         VIF       columns
0  95.343302     Intercept
1  15.097572  Petal_Length
2  14.234335   Petal_Width
3   1.270815   Sepal_Width

Petal_Length와 Petal_Width가 모두 10 이상이 나와 다중공선성이 있는 것으로 나타났다. 이러면 회귀분석 결과에 왜곡을 줘서 변수들의 결정계수가 틀리게 나오게 된다. 좀 더 정확한 회귀분석 결과를 위해 둘 중 하나를 제외하고 회귀분석을 시행해보자.

우선 Petal_Width를 제외하고 회귀분석을 시행한 결과와 VIF는 아래와 같다.

import statsmodels.formula.api as smf
from statsmodels.stats.outliers_influence import variance_inflation_factor

formula = 'Sepal_Length ~ Sepal_Width + Petal_Length'

model = smf.ols(formula=formula, data=df)
result = model.fit()

print(result.summary(), end="\n\n")

vif = pd.DataFrame(
    {'VIF': variance_inflation_factor(model.exog, i), 'columns': column}
    for i, column in enumerate(model.exog_names)
)

vif.sort_values(by='VIF', ascending=False, inplace=True)
vif.reset_index(drop=True, inplace=True)

print(vif)
                            OLS Regression Results
==============================================================================
Dep. Variable:           Sepal_Length   R-squared:                       0.840
Model:                            OLS   Adj. R-squared:                  0.838
Method:                 Least Squares   F-statistic:                     386.4
Date:                Sun, 13 Feb 2022   Prob (F-statistic):           2.93e-59
Time:                        18:09:59   Log-Likelihood:                -46.513
No. Observations:                 150   AIC:                             99.03
Df Residuals:                     147   BIC:                             108.1
Df Model:                           2
Covariance Type:            nonrobust
================================================================================
                   coef    std err          t      P>|t|      [0.025      0.975]
--------------------------------------------------------------------------------
Intercept        2.2491      0.248      9.070      0.000       1.759       2.739
Sepal_Width      0.5955      0.069      8.590      0.000       0.459       0.733
Petal_Length     0.4719      0.017     27.569      0.000       0.438       0.506
==============================================================================
Omnibus:                        0.164   Durbin-Watson:                   2.021
Prob(Omnibus):                  0.921   Jarque-Bera (JB):                0.319
Skew:                          -0.044   Prob(JB):                        0.853
Kurtosis:                       2.792   Cond. No.                         48.3
==============================================================================

Notes:
[1] Standard Errors assume that the covariance matrix of the errors is correctly specified.

         VIF       columns
0  83.033291     Intercept
1   1.224831   Sepal_Width
2   1.224831  Petal_Length

다음으로 Petal_Length를 제외하고 회귀분석을 시행한 결과는 아래와 같다.

import statsmodels.formula.api as smf
from statsmodels.stats.outliers_influence import variance_inflation_factor

formula = 'Sepal_Length ~ Sepal_Width + Petal_Width'

model = smf.ols(formula=formula, data=df)
result = model.fit()

print(result.summary(), end="\n\n")

vif = pd.DataFrame(
    {'VIF': variance_inflation_factor(model.exog, i), 'columns': column}
    for i, column in enumerate(model.exog_names)
)

vif.sort_values(by='VIF', ascending=False, inplace=True)
vif.reset_index(drop=True, inplace=True)

print(vif)
                            OLS Regression Results
==============================================================================
Dep. Variable:           Sepal_Length   R-squared:                       0.707
Model:                            OLS   Adj. R-squared:                  0.703
Method:                 Least Squares   F-statistic:                     177.6
Date:                Sun, 13 Feb 2022   Prob (F-statistic):           6.15e-40
Time:                        18:11:02   Log-Likelihood:                -91.910
No. Observations:                 150   AIC:                             189.8
Df Residuals:                     147   BIC:                             198.9
Df Model:                           2
Covariance Type:            nonrobust
===============================================================================
                  coef    std err          t      P>|t|      [0.025      0.975]
-------------------------------------------------------------------------------
Intercept       3.4573      0.309     11.182      0.000       2.846       4.068
Sepal_Width     0.3991      0.091      4.380      0.000       0.219       0.579
Petal_Width     0.9721      0.052     18.659      0.000       0.869       1.075
==============================================================================
Omnibus:                        2.095   Durbin-Watson:                   1.877
Prob(Omnibus):                  0.351   Jarque-Bera (JB):                1.677
Skew:                           0.239   Prob(JB):                        0.432
Kurtosis:                       3.198   Cond. No.                         30.3
==============================================================================

Notes:
[1] Standard Errors assume that the covariance matrix of the errors is correctly specified.

         VIF      columns
0  70.472677    Intercept
1   1.154799  Sepal_Width
2   1.154799  Petal_Width

아래와 같이 R-squared는 크게 변하지 않으면서 VIF가 크게 개선된 것을 확인할 수 있다.

지표 모든 변수 Petal_Width 제외 Petal_Length 제외
R-squared 0.859 0.840 0.707
Adj. R-squared 0.856 0.838 0.703
coef Sepal_Width 0.6508 0.5955 0.3991
coef Petal_Length 0.7091 0.4719
coef Petal_Width -0.5565 0.9721
VIF Sepal_Width 1.270815 1.224831 1.154799
VIF Petal_Length 15.097572 1.224831
VIF Petal_Width 14.234335 1.154799

Tip

다중공선성을 해결하는 방법은 위에서 진행한 것과 같이 다중공선성이 높은 변수를 제외하는 방법과, 다중공선성이 높은 변수들을 합쳐서 하나로 치환해주는 방법이 있다.

2-1. 💡다중공선성 계산용 모듈

statsmodels의 model을 거치지 않고 계산 하는 함수는 아래와 같다.

import statsmodels.api as sm
from statsmodels.stats.outliers_influence import variance_inflation_factor

def vif_check(dataset, target=False):

    dataset = dataset.select_dtypes(exclude=['object'])
    dataset = sm.add_constant(dataset)

    if target: dataset.drop([target], axis=1, inplace=True)
    else: pass

    vif = pd.DataFrame()
    vif['VIF'] = [variance_inflation_factor(exog=dataset.values, exog_idx=i) for i in range(dataset.shape[1])]
    vif['features'] = dataset.columns
    vif.sort_values(by='VIF', ascending=False, inplace=True)
    vif.reset_index(drop=True, inplace=True)

    return vif
import pydataset as pds
import pandas as pd

df = pds.data('iris')

vif = vif_check(dataset=df)

print(vif)
          VIF      features
0  131.113086         const
1   31.261498  Petal.Length
2   16.090175   Petal.Width
3    7.072722  Sepal.Length
4    2.100872   Sepal.Width

3. 등분산성

Incomplete

이 글은 미완성입니다.

등분산검정(Equal-variance test)은 두 정규분포로부터 생성된 두 개의 데이터 집합으로부터 두 정규분포의 분산 모수가 같은지 확인하기 위한 검정이다. SciPy 패키지를 통해서 검정할 수 있다.

  • scipy
    • scipy.stats.bartlett: 바틀렛 검정
    • scipy.stats.fligner: 플리그너 검정
    • scipy.stats.levene: 레빈 검정

4. 정규성

Incomplete

이 글은 미완성입니다.

마지막 정규성은 확률분포가 가우시안 정규분포를 따르는지의 여부를 의미한다. 모델 요약의 Omnibus, Prob(Omnibus), Durbin-Watson, Jarque-Bera (JB), Prob(JB) 등이 정규성을 확인하는 지표이며, SciPy, statsmodels 패키지를 통해서 별도로 확인할 수 있다.

  • scipy

    • scipy.stats.ks_2samp: 콜모고로프-스미르노프 검정(Kolmogorov-Smirnov test)
    • scipy.stats.shapiro: 샤피로-윌크 검정(Shapiro–Wilk test)
    • scipy.stats.anderson: 앤더스-달링 검정(Anderson–Darling test)
    • scipy.stats.mstats.normaltest: 다고스티노 K-제곱 검정(D’Agostino’s K-squared test)
  • StatsModels

    • statsmodels.stats.diagnostic.kstest_normal: 콜모고로프-스미르노프 검정(Kolmogorov-Smirnov test)
    • statsmodels.stats.stattools.omni_normtest: 옴니버스 검정(Omnibus Normality test)
    • statsmodels.stats.stattools.jarque_bera: 자크-베라 검정(Jarque–Bera test)
    • statsmodels.stats.diagnostic.lillifors: 릴리포스 검정(Lilliefors test)

Reference