728x90
반응형

중심경향치(Center Tendency)

 이전 포스트에서는 최빈값, 중앙값을 구하는 방법에 대해 알아보았다. 이번 포스트에서는 가장 자주 사용되는 중심경향치인 산술 평균과 기하 평균, 조화 평균에 대해서 알아보도록 하자.

 

 

 

1. 산술 평균(Arithmetic mean)

  • 주어진 수의 합을 수의 개수로 나눈 값인 산술 평균은, 통계학에서 가장 많이 사용되는 중심경향치로, 단순하게 평균(Mean)이라고 부르기도 한다.
  • 중앙값처럼 산술 평균 역시, 양적 변수를 대상으로만 사용할 수 있다.

 

1.1. 산술평균의 정의

  • $N$ 개로 구성된 모집단의 관찰값 $X_1, X_2, X_3, ..., X_N$이 있다고 할 때, 모집단의 평균 $\mu$는 다음과 같이 구한다.

$$\mu = \frac{X_1 + X_2 + X_3 + \cdots X_N}{N} = \frac{1}{N}\sum_{i=1}^{N}X_i$$


  • 통계학에서 같은 값을 구한다고 할지라도 모집단을 대상(모수)으로 하는지, 표본집단을 대상(통계량)으로 하는지에 따라, 공식이 약간 달라지게 된다.
  • 평균은 모수와 통계량 모두 공식이 동일하지만, 사용하는 기호가 $\mu$에서 $\bar{x}$로 달라진다.

 

1.2 모평균과 표본평균이 동일한 이유.

 앞서 산술평균에서 단순하게 모수와 통계량의 공식은 동일하지만, 모수는 $\mu$로 통계량은 $\bar{x}$로 사용하는 기호가 다르다고 했다. 이는, 모집단의 평균과 표본 집단의 평균값이 같다는 소리인데, 어떻게 이 것이 가능한 것일까?

  • 먼저, 표본 집단을 완전 무작위로 추출한다고 가정해보자.
  • 이 무작위 추출은 복원 추출(뽑은 값을 또 뽑을 수 있음)을 가정한다.
  • 복원 추출을 가정한 완전 무작위 표본 추출이므로, 원소들이 뽑히는 사건은 서로 독립이다.

  • 위 증명을 보면, 아주 쉽게 표본평균의 기댓값이 모평균과 같다는 것을 찾아내었다.
  • 중간에 나온 원소가 1개인 표본 집합의 기댓값이 모평균과 같다는 이유는 무엇일까?

  • 위와 같은 이유로, 원소가 1개인 표본평균의 분산이 모집단과 동일하다.
  • 즉, 표본을 무수히 많이 뽑게 된다면, 표본 평균의 기댓값은 모평균과 동일하게 된다.

 

 

 

 

 

2. 파이썬으로 산술 평균 구하기.

2.1. 도수분포표로 평균 구하기

  • 중앙값과 마찬가지로, 도수분포표로 평균을 추정할 수는 있으나, 실제 평균과 일치하지는 않는다.
  • 원시 자료를 가지고 있는 상황이라면, 굳이 도수분포표를 구하고, 부정확한 평균을 추론할 필요는 없으나, 혹시나 구할 수 있는 데이터가 도수분포표밖에 없는 상황이 있을 수도 있으므로, 이를 가정하여, 도수분포표로 평균을 구하는 방법에 대해 알아보도록 하겠다.
  • 사용할 데이터와 양적 변수의 범주화된 도수분포표 변환 함수는 다음과 같다.

Data_for_study.csv
3.39MB

import pandas as pd
import numpy as np
def make_freq_table(data, column):
    """
    -------------------------------------------------------------------------------
    지정된 변수의 관찰값이 20개보다 많은 경우, 10개의 등급을 가진 데이터를 반환한다.
    10개의 등급은 동일한 간격으로 자른 것이다.
    -------------------------------------------------------------------------------
    Input: DataFrame, str
    Output: DataFrame
    """
    # array 생성
    target_array = data[column].to_numpy()

    # class의 수가 20개보다 많은 경우 10개로 줄인다.
    class_array = np.unique(target_array)

    if len(class_array) > 20:

        min_key = class_array.min()
        max_key = class_array.max()
        split_key = np.linspace(min_key, max_key, 10)

        a0 = str(round(split_key[0], 2)) + " 이하"
        a1 = str(round(split_key[0], 2)) + " ~ " + str(round(split_key[1], 2))
        a2 = str(round(split_key[1], 2)) + " ~ " + str(round(split_key[2], 2))
        a3 = str(round(split_key[2], 2)) + " ~ " + str(round(split_key[3], 2))
        a4 = str(round(split_key[3], 2)) + " ~ " + str(round(split_key[4], 2))
        a5 = str(round(split_key[4], 2)) + " ~ " + str(round(split_key[5], 2))
        a6 = str(round(split_key[5], 2)) + " ~ " + str(round(split_key[6], 2))
        a7 = str(round(split_key[6], 2)) + " ~ " + str(round(split_key[7], 2))
        a8 = str(round(split_key[7], 2)) + " ~ " + str(round(split_key[8], 2))
        a9 = str(round(split_key[8], 2)) + " 이상"
        new_index = [a0, a1, a2, a3, a4, a5, a6, a7, a8, a9]


        target_array= np.where(target_array <= split_key[0], 0,
                               np.where((target_array > split_key[0]) & (target_array <= split_key[1]), 1,
                                        np.where((target_array > split_key[1]) & (target_array <= split_key[2]), 2,
                                                 np.where((target_array > split_key[2]) & (target_array <= split_key[3]), 3,
                                                          np.where((target_array > split_key[3]) & (target_array <= split_key[4]), 4,
                                                                   np.where((target_array > split_key[4]) & (target_array <= split_key[5]), 5,
                                                                            np.where((target_array > split_key[5]) & (target_array <= split_key[6]), 6,
                                                                                     np.where((target_array > split_key[6]) & (target_array <= split_key[7]), 7,
                                                                                              np.where((target_array > split_key[7]) & (target_array <= split_key[8]), 8, 9)))))))))


    # 도수분포표 생성
    freq_table = pd.DataFrame(pd.Series(target_array).value_counts(), columns = ["freq"])
    freq_table.index.name = column

    freq_table.sort_index(inplace = True)
    freq_table["ratio"] = freq_table.freq / sum(freq_table.freq)
    freq_table["cum_freq"] = np.cumsum(freq_table.freq)
    freq_table["cum_ratio"] = np.round(np.cumsum(freq_table.ratio), 2)
    freq_table["ratio"] = np.round(freq_table["ratio"], 2)

    if "new_index" in locals():
        freq_table.index = new_index
        freq_table.index.name = column

    return freq_table
  • 키에 대하여 범주화된 도수분포표를 뽑아보자.
Rawdata = pd.read_csv("Data_for_study.csv")
make_freq_table(Rawdata, "키")

  • 범주화된 도수분포표를 이용한 평균 계산은 각 등급(Class)의 중앙값에 각 빈도를 곱해 평균을 계산한다.
  • 위와 같이 136 이하, 189.33 이상이 존재하는 경우, 두 등급에 속하는 빈도가 전체에서 차지하는 비중이 매우 작으므로, 단순하게 우리가 알고 있는 136, 189.33에 대하여 빈도를 곱하거나, 이상치로 보고 제거해도 된다.
  • 애초에 도수분포표를 이용해 중심경향치를 계산하는 것은 정확한 값을 얻기 위해서가 아닌, 대략적인 값을 얻기 위해서이므로, 이번에는 저 두 값에 그냥 빈도를 곱하도록 하겠다.
>>> ((136.0*1)+((142.67+136.0)/2*70)+((149.33+142.67)/2*869)+((156.0+149.33)/2*7079)+
    ((162.67+156.0)/2*14131)+((169.33+162.67)/2*14640)+((176.0+169.33)/2*12492)+
    ((182.67+176.0)/2*5118)+((189.33+182.67)/2*1241)+(189.33*107))/55748
    
165.47919010547466
  • 위 코드처럼 값을 하나하나 쓰기 싫은 사람을 위해, pandas의 str 모듈을 사용해 계산하는 방법도 보도록 하겠다.
>>> 키_도수분포표 = make_freq_table(Rawdata, "키")
>>> 키_도수분포표.reset_index(drop=False, inplace=True)

# 1 부터 8번 행까지 처리
>>> 키1_8_array = 키_도수분포표[1:9].키.str.split(" ~ ", expand=True).values.astype("float")
>>> 키1_8_sum = np.sum(((키1_8_array[:,0] + 키1_8_array[:,1])/2) * 키_도수분포표[1:9].freq.to_numpy())

# 0, 9 행 처리
>>> 키0_9_array = 키_도수분포표.loc[[0,9]].키.str.partition(" ")[0].to_numpy().astype("float")
>>> 키0_9_sum = np.sum(키0_9_array * 키_도수분포표.loc[[0,9]].freq.to_numpy())

>>> 평균키 = (키1_8_sum + 키0_9_sum)/키_도수분포표.freq.sum()
>>> 평균키
165.4791901054746
  • 0번행과 9번행은 1~8번행까지와 구간의 패턴이 다르므로, 이를 분리하여, 원하는 값만 추출하여 평균을 구했다.
  • 위에서 숫자를 일일이 복사 붙여넣기한 것과 같은 결과가 나왔다.

 

2.2. 파이썬으로 산술 평균 구하기

  • 원시자료를 가지고 있는 경우, 위 같이 번거로운 과정 없이 아주 편하게 평균을 구할 수 있다.
>>> Rawdata.키.mean()
165.5272655521264

>>> np.mean(Rawdata.키.to_numpy())
165.52726555212743
  • Series.mean()은 pandas 함수로 평균을 구하는 것이다.
  • np.mean(array)는 numpy 함수로 평균을 구하는 것이다.
  • 실제 평균과 위에서 도수분포표를 이용해서 추론한 평균이 꽤 유사한 것을 알 수 있다.

 

 

 

 

 

3. 기타 산술 평균

3.1. 가중 평균(Weighted mean)

  • 서로 다른 집단에 대하여, 다른 가중치를 부여하여 평균을 내는 방법
  • 집단의 크기가 서로 다르거나, 다른 점수 배점을 가질 때, 사용하는 방법이다.
  • 예를 들어, A반은 10명, B반은 40명일 때, A반과 B반의 평균을 동일하게 생각하고 $\bar{X_A}$와 $\bar{X_B}$를 더하고 평균을 내면 안 된다.
  • 이 경우, A반은 전체에서 차지하는 비중인 $\frac{10}{50}$만큼 평균 $\bar{X_A}$ 곱해주고, B반은 전체에서 차지하는 비중인 $\frac{40}{50}$만큼 평균 $\bar{X_B}$에 곱해주고 이 둘을 더해줘야 한다.
  • 즉, A반의 평균이 $\bar{X_A} = 60$이고, B반의 평균이 $\bar{X_B} = 70$이라면, 두 반의 평균은 $\frac{60+70}{2}=65$이 아닌, $0.2\bar{X_A} + 0.8\bar{X_B}= 0.2 * 60 + 0.8 * 70 = 68$점이 된다.

3.2. 절삭 평균(Trimmed mean)

  • 이상치인 극단적으로 크거나 작은 값을 제거하고 평균을 내는 방법이다.
  • 예를 들어, A라는 마을의 실제 평균 월급은 250만 원인데, 그 동네에 빌 게이츠가 살아서 평균 월급이 1,000만 원이 나와버린다면, 그 평균은 실제 데이터가 모여 있는 곳을 보여준다고 할 수 없다.
  • 물론, 이상치를 제거한다면, 이상치가 제거된 것을 집단의 크기에도 반영해야 한다.

 

 

 

 

4. 기하 평균(Geometric mean)

  • 기하 평균은 $n$개의 양수 값을 모두 곱하고, $n$ 제곱근을 해준 것이다.
  • 기하 평균은 지금까지 우리가 다뤘던 변수들과 달리 곱셈으로 계산하는 값에서 사용된다.
  • 변수가 비율로 구성된 경우, 기하 평균을 사용하면 된다.
  • $n$ 개로 구성된 모집단의 관찰 값 $a_1, a_2, a_3, ..., a_n$이 있다고 할 때, 기하 평균은 다음과 같이 구한다.

$$(\prod_{i=1}^{n}a_i)^{\frac{1}{n}} = (a_1\cdot a_2 \cdot a_3\cdots a_n)^{\frac{1}{n}}=\sqrt[n]{a_1\cdot a_2 \cdot a_3\cdots a_n}$$


  • 앞서 보았던 산술평균은 합의 평균이고, 기하 평균은 곱의 평균이다.
  • 기하평균의 대상인 변수의 모든 관찰 값은 양수여야 한다(제곱근을 사용하므로).
  • 로그 항등식을 사용하여 기하평균 공식을 변환시키면, $f(n) = ln x$일 때의 일반화된 f-평균이 만들어진다.

$$ln(a_1\cdot a_2 \cdot a_3\cdots a_n)^{\frac{1}{n}} = \frac{lna_1 + lna_2 + lna3 + \cdots + lna_n}{n}$$


 

4.1 파이썬으로 기하평균 구하기

  • 아래와 같은 데이터가 있다고 가정해보자.
salary = [0.85, 1.1, 1.5, 0.95, 2.0, 1.05]
  • 해당 데이터는 월급 200만 원을 1로 놓고, 6명의 월급의 비율을 본 것이다.
  • 즉, $a_1$은 $200*0.85=170$만원을 월급으로 받고, $a_2$는 $200*1.1=220$만원을 월급으로 받는다는 의미다.
  • 위 데이터의 기하 평균은 다음과 같다.
>>> (0.85*1.1*1.5*0.95*2.0*1.05)**(1/6)
1.187064439031504

 

4.2. Numpy로 구하기

  • 반복문을 사용하는 방법 말고 기하평균을 Numpy로 구하는 방법은 크게 2가지가 있다.
  1. array의 모든 원소를 곱하고, 출력된 스칼라에 n 제곱근 씌우기
  2. array를 모두 로그 치환하고, 그에 대한 산술 평균을 구한 후, 지수로 원 상태로 만들어주기

4.2.1. 방법 1.

# 방법 1.
>>> salary_A = np.array(salary)
>>> np.prod(salary_A)**(1/len(salary_A))
1.187064439031504
  • np.prod(array): 배열의 모든 인자들을 곱한다.

4.2.2. 방법 2.

  • 자연로그의 평균을 구하고, 아래 로그의 성질을 사용하여, 기하 평균을 구하는 방법도 있다.

$$\log_xy = A \rightarrow x^A = y$$

# 방법 2
>>> np.exp(1) ** (np.mean(np.log(salary_A)))
1.1870644390315037
  • np.exp(1): 자연로그의 밑인 2.718281...이다.
  • np.log(array): array의 모든 원소를 자연로그로 변환시킨다.

 

4.3. statistics 라이브러리 사용하기

>>> import statistics as stat
>>> stat.geometric_mean(salary_A)
1.187064439031504
  • statistics 라이브러리는 굉장히 많은 수학적 통계 계산 함수를 제공한다.
  • 개인적으로는 statistics 함수를 사용하기보다는 numpy가 성능이 더 좋으므로, numpy를 사용하길 추천한다.

 

 

 

 

 

5. 조화 평균(Harmonic mean)

  • 조화 평균은 주어진 값들의 역수의 산술 평균의 역수다.
  • 평균 변화율을 구할 때 주로 사용한다.
  • $n$ 개로 구성된 모집단의 관찰 값 $a_1, a_2, a_3, ..., a_n$이 있다고 할 때, 조화 평균은 다음과 같이 구한다.

$$H = \frac{n}{\frac{1}{a_1} + \frac{1}{a_2} + \frac{1}{a_3} + \cdots + \frac{1}{a_n}}$$


  • 조화평균은 속도와 같은 변화율의 평균을 구할 때, 매우 유용하다.
  • 예를 들어, 전체 거리의 절반은 40km/h로 이동하고, 나머지 절반을 60km/h로 이동하였다면, 평균 속력은 산술 평균인 50km/h가 아닌 조화 평균인 48km/h가 된다.
  • 동일한 거리에 대하여 소모된 시간이 다르므로, 단순하게 산술 평균을 구한다면, 평균 속력을 구할 수 없다.
  • 위와 같은 경우엔, 시간의 차원에서 평균을 구해야하므로, 시간에 대하여 값을 바꿔주고, 평균을 구한 후, 다시 속력으로 바꿔보자.
  • 거리를 $S$라 가정하고, 속도를 $V$라고 가정 하자.

 

5.1. 변화율(Rate of change)

  • 두 변수의 변화 정도를 비율로 나타낸 것이다.
  • 미분에서 $\frac{dy}{dx}$라는 단어가 나오는데, 이는 변수 $x$에 대한 변수 $y$의 변화율을 의미한다.
  • 평균변화율(Average rate of change):
    어떤 함수 $f(x)$가 있다고 할 때, 두 점 $A(a, f(a)), B(b, f(b))$에 대하여(단, $a<b$이다.), 두 점을 이어 만든 직선의 기울기를 말한다.
  • 함수 $y=f(x)$에서 $x$의 값이 $a$부터 $a + \bigtriangleup x $까지 변한다고 하면, 아래 식을 $[a, a+\bigtriangleup x]$에서의 $y$의 평균변화율이라 한다.

$$\frac{\bigtriangleup y}{\bigtriangleup x} = \frac{f(a+\bigtriangleup x) - f(a)}{\bigtriangleup x}$$

 

5.2. 파이썬으로 조화 평균을 구해보자

V_array = np.array([20, 30, 50, 30, 60, 40, 80]) 
  • 다음과 같은 속도 데이터가 있다고 가정해보자.
  • 조화 평균을 사용하여, 평균 속력을 구해보자.

5.2.1.  Numpy로 구해보자.

  • 조화 평균은 역수 평균의 역수이므로, numpy의 Broadcast를 사용하면 쉽게 구할 수 있다.
>>> 1/(np.mean(1/V_array))
36.68122270742358

5.2.2. statistics 라이브러리 사용하기

>>> stat.harmonic_mean(V_array)
36.68122270742358

 

 

 

 

 

6. 기하 평균과 조화 평균의 주의사항

  • 0인 표본 값이 존재하는 경우, 사용할 수가 없다.
    • 기하평균은 곱의 평균이므로, 원소에 0이 있는 경우 곱하여 0이 된다.
    • 조화 평균은 역수의 평균의 역수인데, 0의 역수는 무한대로 발산한다.
  • 표본 값이 모두 양수여야 한다.
    • 기하평균은 비율, 배수에 대한 것인데, 음수가 나올 수가 없다.
    • 조화수열의 대상인 변화율의 음수는 벡터로써 방향이 반대를 의미한다.
  • 변수가 비율 혹은 배수이지만, 각 표본 값이 독립적일 때 사용할 수 있다.
    • 표본 값끼리 곱했을 때, 어떠한 의미도 갖지 않아야 서로 독립적이라 할 수 있다.

 

728x90
반응형

+ Recent posts