반응형

원본 링크



How to Not Misunderstand Correlation

0의 계수(coefficient)가 '관계 없음'을 의미하지 않는 이유


Photo by Thirdman on Pexels

데이터로 하나되기

아이스크림 판매가 날씨와 관련이 있을까? 더 큰소리의 노래가 더 유명한 경향이 있는가? 나이가 들어감에 따라 몸무게 늘어나는가? 마을내 황새의 수가 더 많은 신생아에 들어맞는가?

여러분이 새로운 데이터셋을 탐색할때 자주 나타날 수 있는 질문의 형태들이다. 사람의 직감과 실험에 의해 이들 질문에 대답하려 할 수 있지만 여러분은 여러분의 추정을 검증하기 위해 사랑스러운 통계의 팔에 의지해야만 한다.

보통 하나의 변수가 다른 것과 얼마나 관련있는지에 대한 더 명확한 그림을 얻을 수 있게 하는 상관계수(correlation coefficient)이라는 지표(metric)가 있다. 여러분은 선형관계인지를 알기 위해 이변량 분석(bivariate analysis, 두 변수간)을 진행할 때 자주 사용할 것이다.

이 글에서는 이들 계소를 해석하고 계산하는 것에 대한 모든 것과 어떻게 이에 따르는 일반적인 위험을 피하는지를 알아본다.



Setup


import numpy as np
import pandas as pd

import matplotlib.pyplot as plt
import seaborn as sns

sns.set_context('talk')



Scatterplots Masterclass

상관계수(correlation coefficients)에 대해 학습하기 전에 어떻게 완벽한 산점도(scatterplot)를 그리는지 아는 것이 중요하다. 연관성(correlation)과 산전도(scatterplot)은 종종 서로의 결과를 확인하는데 도움이 되기 때문에 함께 제공된다.

도표(plot)을 가장 일반적이고 쉽게 이해한 것중 하나가 산점도이다. 산점도는 쉽지만 변수간 관계를 완벽하게 포함하는 것을 그리기가 얼마나 어려운지 놀랄 것이다. 한가지 예로 1896년에서 2016년까지 선수의 데이터를 포함하는 올림픽 데이터셋을 사용할 것이다.



olymp = pd.read_csv('data/athlete_events.csv', 
                    usecols=['Sex', 'Age', 'Height', 'Weight', 
                    'Year', 'Sport', 'NOC']).dropna()

>>> olymp.head()



>>> olymp.info()

<class 'pandas.core.frame.DataFrame'>
Int64Index: 206165 entries, 0 to 271115
Data columns (total 7 columns):
 #   Column  Non-Null Count   Dtype  
---  ------  --------------   -----  
 0   Sex     206165 non-null  object 
 1   Age     206165 non-null  float64
 2   Height  206165 non-null  float64
 3   Weight  206165 non-null  float64
 4   NOC     206165 non-null  object 
 5   Year    206165 non-null  int64  
 6   Sport   206165 non-null  object 
dtypes: float64(3), int64(1), object(3)
memory usage: 12.6+ MB
olymp.shape
(206165, 7)

산점도는 두 변수간, 특히 수치인 관계를 잘 보여준다. 보통은 산점도를 그리기 위해 pyplotscatter 또는 seabornscatterplot 함수를 사용한다. 그라나 pyplot은 올림픽데이터셋 같은 대규모 데이터셋에 대한 산점도를 그리는 훨씬 더 바른 방법을 제공한다.

점(dot)으로 데이터 포인터를 그리는 o로 marker string을 설정한 pyplot.plot을 사용하여 키 대비 몸무게를 그린다.


# Create figure and axis objects
fig, ax = plt.subplots(figsize=(10,5))

# Create the scatterplot
ax.plot(olymp['Height'], 
        olymp['Weight'], 
        marker='o', # plot as dots
        linestyle='') # remove line

# Labeling
ax.set(title='Height vs. Weight of Olympic Athletes',
      xlabel='Height (cm)', ylabel='Weight (kg)')

plt.show();


도표에서 키가 큰 선수가 몸무게가 더 나가는 경향이 있는 것을 볼 수 있다. 그러나 200,000개 이상의 데이터가 있기 때문에 산점도는 겹쳐 그려졌다. 더 많은 데이터포인트는 서로의 겹쳐 동일 공간에 많이 또는 적은 데이터포인트가 있는지 말하기 힘들다. 이를 개선하기 위해 plt.plotalpha 파라미터를 사용하는 투명도(transparency)를 사용한다.


# Create the scatterplot
ax.plot(olymp['Height'], 
        olymp['Weight'], 
        marker='o', 
        linestyle='', 
        alpha=0.02) # Lower transparency


비록 투명도를 증가시켰지만 산점도는 여전히 겹쳐보인다. 더 개산하기 위해 markersize 파리미터로 마터 크기를 줄인다.


# Create a scatterplot
ax.plot(olymp['Height'], 
        olymp['Weight'], 
        marker='o', 
        linestyle='', 
        alpha=0.02,
        markersize=1) # Used to control the markers


산점도가 훨씬 더 나아 보인고 키가 열(column)로 뭉쳐져 있다는 것을 볼 수 있다. 이는 키가 인치(inch)로 측정되고 정수 센티미터로 반올린되어 변환되었기 때문일 것이다.

이 문제를 해결하기 위해 지터링(jittering)이라 불리는 기술이 있다. 지터링은 위와 같은 도표를 개선하기 위해 무작위 노이즈로써 가짜 데이터를 생성하는 방법이다.

여러분이 seaborn을 사용한다면 x_jitter 또는 y_jitter 파라미터를 사용할 수 있디만 matplotlib는 이런 파리미터가 없다. 이를 해결하기 위해 평균과 표준편가가 주어지면 정규분포를 생성하는 np.random.normal함수를 사용한다. 시살상, 반올림된 값을 채운다.

실제 데이터에 영향을 미치지 않기 위해 평균 0, 표준편차 2(무작위 선택이지만 작아야 한다.)인 정규분포를 생성하고 선수의 키게 이 분포를 더한다.


# Jitter the height
height_jitter = olymp['Height'] + np.random.normal(0, 2, size=len(olymp))

# Create figure and axis objects
fig, ax = plt.subplots(figsize=(10,5))

# Create a scatterplot
ax.plot(height_jitter, # Jittered heights
        olymp['Weight'], 
        marker='o', 
        linestyle='', 
        alpha=0.01,
        markersize=1)

# Labeling
ax.set(title='Height vs. Weight of Olympic Athletes',
      xlabel='Height (cm)', ylabel='Weight (kg)')

plt.show();


열을 제거했고 행(row)에 뭉쳐켜 있는 몸무게를 볼 수 있다. 더 낮은 표준편차로 위의 연산을 수행한다.(표준편차 1)



# Jitter the weight
weight_jitter = olymp['Weight'] + np.random.normal(0, 1, size=len(olymp))

# Create figure and axis objects
fig, ax = plt.subplots(figsize=(10,5))

# Create a scatterplot
ax.plot(height_jitter, # Jittered heights
        weight_jitter, # Jittered weights
        marker='o', 
        linestyle='', 
        alpha=0.01,
        markersize=1)

# Labeling
ax.set(title='Height vs. Weight of Olympic Athletes',
      xlabel='Height (cm)', ylabel='Weight (kg)')

plt.show();


마지막으로 완벽에 가까운 도표를 얻었다. 마지막 단계는 포인트에 대한 주요 군집이 확대되는 것이다.


# Create a scatterplot
ax.plot(height_jitter,
        weight_jitter,
        marker='o', 
        linestyle='', 
        alpha=0.01,
        markersize=1)

# Zoom in
ax.axis([150, 210, 30, 125])


axis 파리미터는 각각 최소 x, 최대 x, 최소 y, 최대 y인 4가지 값의 list를 취한다.

최종 연산의 효과를 보기 위해 최종 버전과 초기 산점도를 비교할 수 있다.


올바른 산점도를 얻는 것은 매우 중요하다. 회귀와 그 이상의 단계로 진행하기 전에 관심변수(variable of interest)간 관계를 최대한 이해해야만 한다. 특히 산점도로 관계가 선형인지 아닌지를 아는 것은 상관계수를 이해하는데 중요하다.



피어슨의 상관계수(Pearson’s Correlation Coefficient)

앞 단락에서 두 변수간 관계를 어떻게 시각화하는지 알아보았다. 이제는 이 관계의 강도를 수량화하는 지표를 알아본다. 통계적으로 이 지표를 피어슨의 상관계수라고 부르며 -1에서 1사이값을 갖는다.

계수의 절대값이 높을 수록 관계가 더 강해진다. 계수의 부호는 방향을 나타낸다.


Image by Wikipedia

양(positive)의 계수는 x변수가 증가하면 y변수가 증가하다는 의미지만 음(negative)의 계수는 x가 증가하면 y가 감소하는 패턴을 나타낸다.

시각적으로 피어슨의 상관계수는 데어터 포인트가 서로 얼마나 가까이 있는지를 나타낸다. 위 도표의 첫번째 줄에서 다양한 강도의 계수를 볼 수 있다.

그러나, 경사가 있는 계수를 혼동하지 않는 것이 중요하다. 두번째 줄에서 이에 대한 까다로운 예를 본다. 여기서 관계는 같지만 경사가 다르다.

게다가 피어슨 계수는 오직 선형(linear) 관계만을 포착한다. 만약 선을 닮지 않았다면 데이터 포인트가 얼마나 가까운지 중요하지 않다. 마지막 줄에서 이에 대한 예를 볼 수 있다.

일반적으로 3가지 계수에 대한 범주가 있다.


상관계수는 소문자 r로 보통 표시된다. 다음은 피어슨 계수를 계산하기 위해 소프트웨어 패지지에서 내부적으로 사용되는 수식이다.


여러분이 통계학 숙제를 하는 중이라면 위 수식이 x와 y의 공분산을 x와 y의 표준편차의 곱으로 나눈 것을 알 것이다.(앞선 이미지에서 수평적 산점조인 중앙의 도표가 왜 관계가 없는지를 추론할 수 있다. 완전한 수평 또는 수직을 갖는 것은 변수중 하나가 상수인 것을 의미한다. 따라서 표준편차기 0이다. 즉, 0으로 나눌 수 없다.)

코드를 게속 보면, 상관계수는 데이터프레임에서 corr 메서드로 계산될 수 있다. 우리는 올림픽데이터셋의 나이, 몸무게, 키 컬럼에 대한 계수만을 본다.


>>> olymp[['Weight', 'Height', 'Age']].corr()


결과는 3가지 변수의 개별 쌍에 대한 상관계수를 보여주는 관계 매트릭스이다. 이 결과를 해석하면 키와 몸무게가 0.8의 계수로 높은 관계가 있다는 것을 볼 수 있다. 그러나 나이와 몸무게간 관계는 나이와 키맞ㄴ큼이나 약하다.(각각 0.21, 0.14)

그러나 여러분은 결코 단지 상관계수를 보는 것만으로 변수간 관계를 결론지어서는 안된다. 산점도 또는 몇몇의 경우 box plot 또는 violin plot으로 시각적으로 확인하여 여러분이 추정이 올바른지 확실히 해야 한다.

예를 들면, 산점도를 사용하여 나이와 모뭄게간 계수를 더블클릭할 수 있다. 열(row) 도표는 많은 통찰(insight)을 주지 못하기 때문에 이전 단락의 기술을 사용하여 몇가지 변화를 주었다.


# Create figure and axis objects
fig, ax = plt.subplots(figsize=(10, 5))

# Jitter the ages
age_jitter = olymp['Age'] + np.random.normal(0, 0.3, size=len(olymp))
# Jitter the weights
weight_jitter = olymp['Weight'] + np.random.normal(0, 2, size=len(olymp))

# Create the scatterplot
ax.plot(age_jitter, weight_jitter, 'o', alpha=0.03, markersize=1.2)

# Zoom in
ax.axis([10, 40, 20, 110])

# Label
ax.set(title='Age vs. Weight of Olympic athletes',
       xlabel='Age (years)', ylabel='Weight (kg)')

plt.show();


데이터 포인트가 더 많이 퍼지고 낮은 계수(0.21)를 보이는 약간 양의 기울기를 갖는다.



Coefficient close to 0 does not mean ‘no relationship’

관계 매트릭스를 계산한 후 계속 진행하기 전에 계수에 대한 여러분의 추론을 시각적으로 점검한다. 관계선에 대한 일반적인 실패중 하나는 선형관계가 아닌 두 변수를 비교하면 계수가 0에 매우 가깝다는 것이다.

변수들이 관계없다는 결론대신 일반적인 경향을 보여주는 산점도 또는 유사한 다이어그램을 만든다. 관계성이 0에 가까운 것은 강력한 비선형 관계를 숨길 수 있기 때문이다.

관계성이 0에 가깝지만 시각화가 굴곡이 많은 경향을 나타내면 이는 비선형 관계를 나타낸다.

반응형

+ Recent posts