728x90
반응형

대푯값(Representative value)과 비대칭도(Skewness)

 앞서 학습였던, 중앙값(Median), 최빈값(Mode), 평균(Mean) 등과 같은 대푯값과 표준편차(Standard Deviation)를 사용하면, 데이터가 대략 어떻게 생겼는지를 파악할 수 있다.

 이번 포스트에서는 대푯값이 무엇인지, 대푯값을 사용하여 어떻게 데이터의 형태를 파악할 수 있는지에 대해 알아보도록 하자.

 

 

 

 

1. 대푯값(Representative value)

  • 대푯값은 말 그대로 어떠한 데이터를 단 하나의 값으로 표현할 수 있는 값을 말한다.
  • 데이터 어디에 모여있는지(중심경향치): 평균(Mean), 중앙값(Median), 최빈값(Mode), 절사 평균(Trimmed mean)
  • 데이터를 정렬하고 일정 크기로 나눴을 때의 간격 점: 사분위수(Quartile), 백분위수(Percentile)
  • 정상 범위에서 벗어난 값: 이상값(Outlier)
  • 확률상 기대되는 대푯값: 기댓값(Expected value)

 

1.1. 대푯값을 이용하여, 데이터의 대략적인 분포를 알 수 있다.

  • 산포도는 대푯값이 아니지만, 대푯값을 이용해서 산포도를 알 수도 있다.
  • 사분위수를 이용하면, 사분위 범위를 알 수 있고, 사분위 범위를 알면, 상자 수염 그림(Box-and-Whisker plot)을 그릴 수 있다.
  • 평균과 중앙값, 최빈값을 이용하여, 데이터의 대략적인 개형을 알 수 있다.

 

 

 

 

 

2. 대푯값을 이용한 데이터의 대략적인 개형 알아보기

  • 집중화 경향치인 평균, 중앙값, 최빈값은 데이터 분포에 따라 아래와 같은 경향을 갖는다.

  • 위 그림은 조금 과장을 한 것이긴 하지만, 데이터는 위와 같은 경향을 띄며, 그 이유는 다음과 같다.
  1. 최빈값: 가장 많이 등장한 관찰 값이 최빈값이 된다.
  2. 중앙값: 관찰 값의 빈도에 상관없이 무조건 가운데에 위치한다.
  3. 평균: 이상치의 영향을 크게 받는다.
  • 간단한 도수분포표를 만들어서, 실제로는 어떻게 돌아가는지 확인해보자.
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

# 부드러운 그래프를 그린다.
from scipy.interpolate import make_interp_spline

plt.rc('font', family='NanumGothic')
# 최빈값, 중앙값, 평균 계산
def Rep_V(data, column):

    raw_list = []

    for i in range(len(data)):

        X_list = [data.loc[i, "X"]] * data.loc[i, column]
        raw_list = raw_list + X_list

    median = np.median(raw_list)
    mean = np.mean(raw_list)
    std = np.std(raw_list)
    mode = np.bincount(raw_list).argmax()

    return mode, median, mean, std



# 그래프 그리기
def draw_Ske(data, column, title):
    """
    도수분포다각형을 부드럽게 그리고, 최빈값, 중앙값, 평균을 점으로 찍고, 선을 그린다.
    """
    x = data["X"].to_numpy()
    y = data[column].to_numpy()

    fig = plt.figure(figsize=(10, 6))

    # 부드러운 그래프를 그린다.
    model=make_interp_spline(x,y)
    xs=np.linspace(np.min(index_list),np.max(index_list),100)
    ys=model(xs)

    plt.plot(xs, ys)

    # 최빈값, 중앙값, 평균 그리기
    mode, median, mean, std = Rep_V(data, column)
    plt.scatter(mode, 1, c = "r", s = 100)
    plt.axvline(x=mode, c = "r", linewidth = 1, alpha = 0.5)
    plt.annotate('최빈값', xy=(mode+1, 1), xytext=(mode + 10, 0.5), fontsize = 15,
                 arrowprops=dict(facecolor='black', width=1, headwidth=8))

    plt.scatter(median, 3, c = "g", s = 100)
    plt.axvline(x=median, c = "g", linewidth = 1, alpha = 0.5)
    plt.annotate('중앙값', xy=(median+1, 3), xytext=(median + 10, 3.2), fontsize = 15,
                 arrowprops=dict(facecolor='black', width=1, headwidth=8))

    plt.scatter(mean, 5, c = "y", s = 100)
    plt.axvline(x=mean, c = "y", linewidth = 1, alpha = 0.5)
    plt.annotate('평균', xy=(mean+1, 5), xytext=(mean + 10, 6), fontsize = 15,
                 arrowprops=dict(facecolor='black', width=1, headwidth=8))



    plt.title(title, fontsize = 20, pad = 15)
    plt.xlabel("$X$", fontsize = 15, labelpad = 10)
    plt.ylabel("$f$", rotation = 0, fontsize = 15, labelpad = 10)
    plt.show()
  • 함수 Rep_V(data, column)는 도수분포표를 본래 데이터 셋으로 되돌리고, 최빈값, 중앙값, 평균과 같은 대푯값을 계산하는 함수다.
  • 데이터의 양이 지나치게 많아, 컴퓨터가 이를 감당하지 못하는 경우에는 의도적으로 도수분포표를 만들어 대푯값을 계산할 수도 있으나, 위와 같이 데이터의 양이 적은 경우, 도수분포표를 본래 데이터 셋으로 되돌리는 것이 더 쉽다.
index_list = [0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100]
freq1_list = [1, 3, 6, 11, 17, 24, 17, 11, 6, 3, 1]
freq2_list = [1, 2, 3, 6, 10, 14, 20, 25, 15, 3, 1]
freq3_list = [1, 3, 15, 25, 20, 14, 10, 6, 3, 2, 1]

DF = pd.DataFrame({"X":index_list, "f1":freq1_list, "f2":freq2_list, "f3":freq3_list})
DF

2.1. 대칭 분포

draw_Ske(DF, "f1", "1. 대칭분포")

  • 대칭 분포인 경우, "평균 = 중앙값 = 최빈값"인 것을 볼 수 있다.

2.2. 왼쪽 꼬리 분포

draw_Ske(DF, "f2", "2. 왼쪽꼬리분포")

  • 왼쪽 꼬리 분포인 경우, "평균 < 중앙값 < 최빈값"이 되는 것을 볼 수 있다.

2.3. 오른쪽 꼬리 분포

  • 오른 꼬리 분포인 경우, "평균 > 중앙값 > 최빈값"이 되는 것을 볼 수 있다.

 

 

 

 

 

3. 피어슨의 비대칭 계수(Pearson's skewness coefficients)

 칼 피어슨(Karl Pearson)이 만든, 피어슨의 비대칭 계수는, 위에서 본 분포가 좌, 우로 치우치는 경우 평균, 중앙값, 최빈값이 일정한 크기로 순서를 갖는 성질을 이용해서 만든 것이다.

  • 피어슨의 비대칭 계수는 평균에 대하여, 최빈값이나 중앙값을 뺀 값을 표준 편차로 나눠서 구한다.
  • 피어슨의 비대칭 계수를 구하는 방법은 크게 3가지가 있다.

◎ 피어슨의 제1 비대칭도 계수: $SK_1 = \frac{\bar{X} - mode}{S}$

◎ 피어슨의 제2 비대칭도 계수: $SK_2 = \frac{3(\bar{X} - mode)}{S}$

◎ 피어슨의 제3 비대칭도 계수: $SK_3 = \frac{3(\bar{X} - median)}{S}$


  • 위 비대칭도 계수들은 평균과 다른 대푯값의 거리를 얼마나 민감하게 볼 것인가에 대하여 다르게 만들어진 것임을 볼 수 있다.
  • 일반적으로 사용하는 것은 피어슨의 제3 비대칭도 계수다.
  • 단순한 해석 방법은 3가지 모두 동일하다.

 

3.1. 피어슨의 비대칭 계수 보는 방법

  • 비대칭도 계수가 0이 나오는 경우, 평균과 중앙값(최빈값)이 같으므로, 분포가 치우쳐지지 않음을 알 수 있다.
  • 비대칭도 계수가 양수인 경우, 평균보다 중앙값이 작으므로, 오른쪽 꼬리 분포이다.
  • 비대칭도 계수가 음수인 경우, 평균보다 중앙값이 크므로, 왼쪽 꼬리 분포이다.
  • 비대칭도 계수가 클수록, 한쪽으로 치우친 정도가 크다는 소리다.
  • 비대칭도 계수가 0~1 사이인 경우, 1 표준편차보다 분자(평균과 대푯값의 거리에 3을 곱한 값)가 작다는 의미이므로, 치우친 정도가 작다는 소리다.
  • 이를 이용하여, 서로 다른 분포에 대하여, 치우친 정도를 비교할 수도 있다(표준편차로 나누었으므로, 단위도 사라진다.).

 

3.2. 파이썬으로 피어슨의 비대칭 계수를 구현해보자.

  • 기존 도수분포표에 보다 치우친 새로운 도수를 추가하여 비교해보자.
index_list = [0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100]
freq1_list = [1, 3, 6, 11, 17, 24, 17, 11, 6, 3, 1]
freq2_list = [1, 2, 3, 6, 10, 14, 20, 25, 15, 3, 1]
freq3_list = [1, 3, 15, 25, 20, 14, 10, 6, 3, 2, 1]
freq4_list = [1, 1, 2, 3, 5, 7, 10, 16, 23, 26, 6]

DF = pd.DataFrame({"X":index_list, "f1":freq1_list, "f2":freq2_list, "f3":freq3_list, "f4":freq4_list})
DF

  • 피어슨의 비대칭도 계수를 구하는 함수를 만들고, 각 변수의 도수들을 비교해보자.
  • 각 칼럼의 최빈값, 중앙값, 평균, 표준편차를 출력해보자.
>>> for column in ["f1", "f2", "f3", "f4"]:

>>>     mode, median, mean, std = Rep_V(DF, column)
>>>     print(f"column: {column} \n 최빈값:{mode}, 중앙값: {median}, 평균: {mean}, 표준편차: {np.round(std,2)}")
>>>     print("----"*20)
    
    
column: f1 
 최빈값:50, 중앙값: 50.0, 평균: 50.0, 표준편차: 19.39
--------------------------------------------------------------------------------
column: f2 
 최빈값:70, 중앙값: 60.0, 평균: 58.8, 표준편차: 19.2
--------------------------------------------------------------------------------
column: f3 
 최빈값:30, 중앙값: 40.0, 평균: 41.2, 표준편차: 19.2
--------------------------------------------------------------------------------
column: f4 
 최빈값:90, 중앙값: 80.0, 평균: 71.9, 표준편차: 21.06
--------------------------------------------------------------------------------

 

  • 1번부터 3번까지 피어슨의 비대칭 계수를 계산하는 함수를 만들고, 계수를 계산해보자.
def P_SK(data, column, key):
    
    mode, median, mean, std = Rep_V(DF, column)
    
    if key == 1:
        result = (mean - mode)/std
        
    elif key == 2:
        result = 3*(mean - mode)/std
        
    elif key == 3:
        result = 3*(mean - median)/std
        
    return result
>>> for column in ["f1", "f2", "f3", "f4"]:
    
>>>     print(f"column: {column}")
>>>     for key in range(1,4):
        
>>>         P_SK_value = P_SK(DF, column, key)
>>>         print(f"피어슨의 제 {key} 비대칭도 계수 = {np.round(P_SK_value, 4)}")
        
>>>     print("----"*20)


column: f1
피어슨의 제 1 비대칭도 계수 = 0.0
피어슨의 제 2 비대칭도 계수 = 0.0
피어슨의 제 3 비대칭도 계수 = 0.0
--------------------------------------------------------------------------------
column: f2
피어슨의 제 1 비대칭도 계수 = -0.5834
피어슨의 제 2 비대칭도 계수 = -1.7502
피어슨의 제 3 비대칭도 계수 = -0.1875
--------------------------------------------------------------------------------
column: f3
피어슨의 제 1 비대칭도 계수 = 0.5834
피어슨의 제 2 비대칭도 계수 = 1.7502
피어슨의 제 3 비대칭도 계수 = 0.1875
--------------------------------------------------------------------------------
column: f4
피어슨의 제 1 비대칭도 계수 = -0.8596
피어슨의 제 2 비대칭도 계수 = -2.5787
피어슨의 제 3 비대칭도 계수 = -1.154
--------------------------------------------------------------------------------
  • 대칭 분포인 f1은 비대칭도 계수가 0이 나왔다.
  • 왼쪽 꼬리 분포인 f2는 비대칭도 계수가 모두 음수가 나왔다.
  • 오른쪽 꼬리 분포인 f3는 비대칭도 계수가 모두 양수가 나왔다.
  • 새로 만든 f2의 비대칭도 계수는 모두 음수가 나왔다.
  • 수치가 제법 다르므로, f2와 f4를 시각화하여, 직접 비교해보자.

 

3.3. 시각화하여 비대칭도 비교

  • 위에서 만든 도수분포와 최빈값, 중앙값, 평균을 그려주는 함수인 draw_Ske를 약간 변형하여, 4개 칼럼에 대해 subplot으로 그려보도록 하자.
data = DF
x = data["X"].to_numpy()
fig, axes = plt.subplots(nrows=2, ncols=2, figsize=(16,12))
ax = axes.ravel()
column_list = ["f1", "f2", "f3", "f4"]

for i, column in enumerate(column_list):
    

    y = data[column].to_numpy()
    
    # 부드러운 그래프를 그린다.
    model=make_interp_spline(x,y)
    xs=np.linspace(np.min(index_list),np.max(index_list),100)
    ys=model(xs)
    
    ax[i].plot(xs, ys)
    title = column_list[i] + "의 분포"
    ax[i].set_title(title, fontsize=20, fontweight="bold")
    
    
    # 최빈값, 중앙값, 평균 그리기
    mode, median, mean, std = Rep_V(data, column)
    ax[i].scatter(mode, 1, c = "r", s = 100)
    ax[i].axvline(x=mode, c = "r", linewidth = 1, alpha = 0.5)
    ax[i].annotate('최빈값', xy=(mode+1, 1), xytext=(mode + 10, 0.5), fontsize = 15,
                 arrowprops=dict(facecolor='black', width=1, headwidth=8))

    ax[i].scatter(median, 3, c = "g", s = 100)
    ax[i].axvline(x=median, c = "g", linewidth = 1, alpha = 0.5)
    ax[i].annotate('중앙값', xy=(median+1, 3), xytext=(median + 10, 3.2), fontsize = 15,
                 arrowprops=dict(facecolor='black', width=1, headwidth=8))

    ax[i].scatter(mean, 5, c = "y", s = 100)
    ax[i].axvline(x=mean, c = "y", linewidth = 1, alpha = 0.5)
    ax[i].annotate('평균', xy=(mean+1, 5), xytext=(mean + 10, 6), fontsize = 15,
                 arrowprops=dict(facecolor='black', width=1, headwidth=8))
    
plt.tight_layout()

  • 위에서 만들었던, 피어슨의 3 비대칭도 계수로 f2와 f4를 비교해보자.
# 치우침이 비교적 작은 f2의 피어슨 비대칭도 계수
>>> P_SK(DF, "f2", 3)
-0.18752034836405185

# 치우침이 비교적 큰 f4의 피어슨 비대칭도 계수
>>> P_SK(DF, "f4", 3)
-1.154019976254971
  • f2와 f4의 피어슨 비대칭도 계수를 절댓값으로 비교해보면, f4가 더 큰 것을 알 수 있다.
  • 위 결과는 평균과 중앙값의 거리가 f2보다 f4가 더 멀다는 것을 의미하며, 이는 f4가 f2보다 한쪽에 더 치우쳐져 있음을 보여준다.
  • 위 그래프를 보면 알 수 있듯, f2의 분포보다 f4의 분포가 오른쪽으로 더 데이터가 몰려있음을 알 수 있다.

 

 

 

 

 

4. 왜도(Skewness)

  • 왜도는 위에서 본 피어슨의 비대칭 계수(Pearson's skewness coefficients)와 같은 분포가 어디에 치우쳐져 있는 지를 보는 보다 더 대표적인 방법이다.
  • 사실 왜도를 보다 간략화시킨 것이 피어슨의 비대칭 계수로, 왜도를 보는 방법은 피어슨의 비대칭 계수와 동일하다.
  • 음의 왜도(Negative skewness): 왜도에서는 왼쪽에 긴 꼬리를 갖는 왼쪽 꼬리 분포를, 음의 왜도나 좌비대칭형(Skewed left)이라 부른다.
  • 양의 왜도(positive skewness): 왜도에서는 오른쪽에 긴 꼬리를 갖는 오른쪽 꼬리 분포를, 양의 왜도나 우비대칭형(Skewed right)이라 부른다.
  • 왜도는 각 관찰 값의 편차에 표준편차를 나눈 값에 3 제곱을 하여 합친 값이다.
  • 왜도는 모집단의 왜도와 표본 왜도를 구하는 공식이 약간 다르다.

◎ 표본 왜도

$$b_1 = \frac{m_3}{S^3} = \frac{\frac{1}{n}\sum_{i=1}^{n}(x_i - \bar{x})^3}{[\frac{1}{n-1}\sum_{i=1}^{n}(x_i - \bar{x})^2]^{\frac{3}{2}}} = \frac{\sum_{i=1}^{n}(x_i - \bar{x})^3}{nS^3}$$

◎ 모집단의 왜도

$$G_1 = \frac{n^2}{(n-1)(n-2)}b_1 = \frac{n}{(n-1)(n-2)}\sum_{i=1}^{n}(\frac{x_i - \bar{x}}{S})^3$$


  • 모집단의 평균, 표준편차 등을 알 수 없기 때문에 표본집단을 이용하여, 모집단의 왜도를 추정한다.

 

4.1. 파이썬을 사용하여 왜도를 구해보자.

  • 위에서 만든 DF에 대하여 왜도를 구해보도록 하자.
  • 모집단의 왜도를 구하도록 해보자.
# 도수분포표의 도수를 list로 변환한다.
def freqT2list(data, column):
    
    result = []

    for i in range(len(data)):

        X_list = [data.loc[i, "X"]] * data.loc[i, column]
        result = result + X_list
        
    return result
    
    
# 왜도 계산
def skewness(data, column):
    
    target = np.array(freqT2list(data, column))

    n = len(target)
    mean = np.mean(target)
    std = np.std(target, ddof=1)

    return n/((n-1)*(n-2))*np.sum(((target - mean)/std)**3)
>>> for column in ["f1", "f2", "f3", "f4"]:
    
>>>     skew = skewness(DF, column)
>>>     print(f"column:{column}, 왜도: {skew}")


column:f1, 왜도: 1.0985509210885902e-16
column:f2, 왜도: -0.704757133477093
column:f3, 왜도: 0.7047571334770931
column:f4, 왜도: -1.1553356711140326
  • 대칭 분포인 f1의 왜도는 1.0985509210885902e-16로 나왔다.
  • 이는 0.0000000000000001098으로, 소수점 아래 16번째 자리에서 처음으로 e 앞에있는 숫자가 나온다는 의미로, 매우 작은 수로, 파이썬의 계산 방식 등으로 인해 이런 문제가 발생할 수 있으니, 깔끔하게 소숫점 아래 6자리에서 반올림을 해서 다시 보자.
>>> for column in ["f1", "f2", "f3", "f4"]:
    
>>>     skew = np.round(skewness(DF, column), 6)
>>>     print(f"column:{column}, 왜도: {skew}")


column:f1, 왜도: 0.0
column:f2, 왜도: -0.704757
column:f3, 왜도: 0.704757
column:f4, 왜도: -1.155336
  • 왜도 역시, 피어슨의 비대칭 계수처럼 음과 양으로 데이터가 어디에 모여있는지 알 수 있다. 간단하게 음수면 긴 꼬리가 음수의 방향, 양수면 긴 꼬리가 양수의 방향에 있다고 생각하자.
  • 왜도가 커질수록 분포가 크게 치우친 것이다.

 

4.2. Scipy로 왜도를 구해보자

  • scipy의 stats 모듈에 있는 skew 함수를 사용해서 더 쉽게 왜도를 구할 수 있다.
from scipy import stats
>>> for column in ["f1", "f2", "f3", "f4"]:
    
>>>     target = np.array(freqT2list(DF, column))
>>>     skew = stats.skew(target)
>>>     print(f"column:{column}, 왜도: {skew}")

column:f1, 왜도: 0.0
column:f2, 왜도: -0.6941414183237987
column:f3, 왜도: 0.694141418323799
column:f4, 왜도: -1.137932918011734
  • scipy.stats.skew(array): 왜도를 구한다.
  • scipy로 구한 왜도와 직접 만든 공식과 그 값이 꽤 다른 것을 알 수 있다.
  • 이는, scipy의 왜도는 표본 왜도를 구하며, 사용하는 공식이 약간 다르므로, 표준편차를 모 표준편차로 구했기 때문이다.
  • 이에 맞게 공식을 고쳐서 출력해보자.
# 왜도 계산
def skewness(data, column):
    
    target = np.array(freqT2list(data, column))

    n = len(target)
    mean = np.mean(target)
    std = np.std(target, ddof=0)

    return 1/n*np.sum(((target - mean)/std)**3)
>>> for column in ["f1", "f2", "f3", "f4"]:
    
>>>     skew = np.round(skewness(DF, column), 6)
>>>     print(f"column:{column}, 왜도: {skew}")


column:f1, 왜도: 0.0
column:f2, 왜도: -0.694141
column:f3, 왜도: 0.694141
column:f4, 왜도: -1.137933
  • 왜도가 돌아가는 방식에 대해 알 필요는 있으나, 개인적으로는 이 부분을 심화시키는 것은 시간낭비라고 판단된다.
  • 왜도는 분포의 형태를 대략적으로 아는 도구이므로, 보다 정확한 도구를 사용하는 것보단, 동일한 도구를 이용해서 평가하는 것이 중요하다고 판단되므로, scipy.stats의 skew함수를 사용하도록 하자.

 

 

 

 

 

5. 첨도(Kurtosis)

  • 왜도가 데이터가 어느 쪽에 쏠려 있는지를 보는 용도라면, 첨도는 데이터가 얼마나 모여있는지를 가리킨다.
  • 첨도가 클수록 데이터는 한 곳에 매우 많이 모여 있고, 첨도가 작을수록 데이터가 흩어져 있다.
  • 첨도 = 0: 표준 정규분포다.
  • 첨도 > 0: 표준 정규분포보다 평균에 데이터가 더 많이 모여 있는 뾰족한 형태다.
  • 첨도 < 0: 표준 정규분포보다 평균에서 데이터가 더 흩어진 완만한 형태다.
  • 첨도가 크다할지라도, 분산이 더 큰 경우, 완만한 그래프가 나타날 수 있으므로, 분산이 동일한 분포끼리 비교해야 한다.
  • 첨도는 각 관찰 값과 표본 평균의 편차에 표본 표준편차를 나눈 값에 4 제곱을 해서 구한다.
  • 첨도의 기준은 정규분포가 되며, 정규분포의 첨도는 3이지만, 이를 보기 쉽도록, 3을 빼서, 0으로 보정해준다.

◎ 표본 첨도

$$g_2 = \frac{m_4}{m_{2}^2} - 3 = \frac{\frac{1}{n}\sum_{i=1}^{n}(x_i - \bar{x})^4}{[\frac{1}{n}\sum_{i=1}^{n}(x_i - \bar{x})^2]^2} - 3 = \frac{n}{(n-1)^2}\sum_{i=1}^{n}(\frac{x_i-\bar{x}}{S})^4 - 3$$

◎ 모집단의 첨도(불편추정량)

$$G_2 = \frac{n-1}{(n-2)(n-3)}[(n+1)g_2 + 6]) = \frac{n(n+1)}{(n-1)(n-2)(n-3)}\sum_{i=1}^{n}(\frac{x_i - \bar{x}}{S})^4 - 3\frac{(n-1)^2}{(n-2)(n-3)}$$


 

5.1. 파이썬으로 첨도를 구현해보자.

  • 넘파이를 이용하여, 평균은 0이고, 표준편차 1인 표준 정규분포를 만들어보자.
np.random.seed(1234)
Normal_Dist = np.random.normal(loc = 0, scale = 1, size = 10000)
plt.rc('font', family='monospace')
  • 위에서 한글을 사용하기 위해 폰트를 나눔 고딕으로 설정하는 경우 음수의 "-" 부호가 깨지게 되므로, 폰트를 바꿔주었다.
fig = plt.figure(figsize=(10, 6))

plt.hist(Normal_Dist, bins = 60, density=True)
plt.show()

 

5.2. 첨도 함수를 만들어보자.

5.2.1. Numpy 라이브러리의 함수를 사용해서 만들어보자.

# 첨도
def Kurt(array):
    
    mean = np.mean(array)
    std = np.std(array, ddof = 1)
    n = len(array)

    g2 = n/(n-1)**2*np.sum(((array - mean)/std)**4) - 3

    G2 = ((n-1)/((n-2)*(n-3)))*((n+1)*g2 + 6)
    
    return G2
>>> Kurt(Normal_Dist)
-0.06547331601746852

 

5.2.2. scipy.stats 모듈의 kurtosis() 함수를 사용해서 만들어보자.

from scipy import stats
>>> stats.kurtosis(Normal_Dist)
-0.06604052394826398
  • scipy와 numpy로 직접 만든 첨도의 결과가 약간 다른 것을 알 수 있다.
  • 이는, scipy에서는 Kurt함수의 G2가 아닌 g2로 계산되기 때문으로, 즉 모집단의 첨도가 아닌 표본 첨도가 계산된다는 소리다.
def Kurt_g2(array):
    
    mean = np.mean(array)
    std = np.std(array, ddof = 1)
    n = len(array)

    g2 = n/(n-1)**2*np.sum(((array - mean)/std)**4) - 3
    
    return g2
>>> Kurt_g2(Normal_Dist)
-0.06604052394826398

>>> stats.kurtosis(Normal_Dist) == Kurt_g2(Normal_Dist)
True

 

5.3. 무작위로 생성한 표준정규분포의 난수를 더 표준정규분포에 가깝게 해 보자.

  • 위에서 만든 표준정규분포의 난수는 크기가 10,000으로, 실제 표준정규분포와 거리가 있다.
  • 이번에는 난수의 크기를 1,000,000으로 해서 실제 표준정규분포에 더 가깝게 만들어보자.
    (데이터의 양이 엄청나기 때문에 컴퓨터에 부담이 가는 경우 수를 조금 줄이자)
np.random.seed(1234)
Normal_Dist = np.random.normal(loc = 0, scale = 1, size = 1000000)
fig = plt.figure(figsize=(10, 6))

plt.hist(Normal_Dist, bins = 60, density=True)
plt.show()

>>> Kurt_g2(Normal_Dist)
0.0006377812832605301
  • 난수의 크기가 10,000일 때보다 표준정규분포에 근사하게 되니 첨도가 0에 더 가까워지는 것을 볼 수 있다.
  • 그러나, 난수를 이용해서인지 크기를 늘린다고 해서 선형으로 0에 더 가깝게 가지는 않는다.

 

5.4. 표준 편차가 1로 동일하지만, 크기가 매우 작아 표준 정규분포와 거리가 먼 난수를 생성해보자.

np.random.seed(1234)
Normal_Dist = np.random.normal(loc = 0, scale = 1, size = 10)
fig = plt.figure(figsize=(10, 6))

plt.hist(Normal_Dist, density=True)
plt.show()

>>> Kurt_g2(Normal_Dist)
-0.6112182077094723
  • 이번에 생성한 데이터는 평균이 1, 표준편차가 1로 이전의 난수와 동일하지만, 크기가 10으로 매우 작아, 분산만 동일하지 정규분포로 보기 힘든 데이터를 생성해보았다.
  • 이 경우, 첨도는 -0.611로 기존에 생성한 분포보다 더 완만하다는 것을 알 수 있다.

 

5.5. 표준편차가 다른 분포들을 만들고, 첨도를 비교해보자.

np.random.seed(1234)
Normal_Dist0 = np.random.normal(loc = 0, scale = 0.4, size = 100000)
Normal_Dist1 = np.random.normal(loc = 0, scale = 1, size = 100000)
Normal_Dist2 = np.random.normal(loc = 0, scale = 2, size = 100000)
fig = plt.figure(figsize=(10, 6))

plt.hist(Normal_Dist0, density=True, bins=60, alpha=0.5, label="N(0, 0.5)")
plt.hist(Normal_Dist1, density=True, bins=60, alpha=0.5, label="N(0, 1)")
plt.hist(Normal_Dist2, density=True, bins=60, alpha=0.5, label="N(0, 2)")
plt.legend(loc="upper right")
plt.show()

>>> Ku0 = Kurt_g2(Normal_Dist0)
>>> Ku1 = Kurt_g2(Normal_Dist1)
>>> Ku2 = Kurt_g2(Normal_Dist2)

>>> print(f"표준편차가 0.5인 분포의 첨도:{Ku0}")
>>> print(f"표준편차가 1인 분포의 첨도:{Ku1}")
>>> print(f"표준편차가 2인 분포의 첨도:{Ku2}")

표준편차가 0.5인 분포의 첨도:-0.030292593794574607
표준편차가 1인 분포의 첨도:-0.00581814290437066
표준편차가 2인 분포의 첨도:0.01995196078021788
  • 위 분포만 본다면, 표준 정규분포에 가장 가까운 노란색보다 더 뾰족하게 솟은 파란색 분포가 값이 더 커야 할 것으로 보이지만, 보다 음수에 가까운 것을 알 수 있다.
  • 이는 첨도와 표준편차는 별개로 움직이기 때문으로, 위와 같이 표준편차가 다른 경우, 그래프의 개형이 보다 더 뾰족하거나 완만하다 할지라도, 첨도가 별개로 작동하여, 잘못된 판단을 할 위험이 있다.
  • 즉, 첨도는 분산이 동일한 상태에서 사용해야, 데이터가 평균에 모인 정도에 대해 제대로 된 비교를 할 수 있다.
728x90
반응형

+ Recent posts