중심경향치(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. 도수분포표로 평균 구하기
- 중앙값과 마찬가지로, 도수분포표로 평균을 추정할 수는 있으나, 실제 평균과 일치하지는 않는다.
- 원시 자료를 가지고 있는 상황이라면, 굳이 도수분포표를 구하고, 부정확한 평균을 추론할 필요는 없으나, 혹시나 구할 수 있는 데이터가 도수분포표밖에 없는 상황이 있을 수도 있으므로, 이를 가정하여, 도수분포표로 평균을 구하는 방법에 대해 알아보도록 하겠다.
- 사용할 데이터와 양적 변수의 범주화된 도수분포표 변환 함수는 다음과 같다.
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가지가 있다.
- array의 모든 원소를 곱하고, 출력된 스칼라에 n 제곱근 씌우기
- 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의 역수는 무한대로 발산한다.
- 표본 값이 모두 양수여야 한다.
- 기하평균은 비율, 배수에 대한 것인데, 음수가 나올 수가 없다.
- 조화수열의 대상인 변화율의 음수는 벡터로써 방향이 반대를 의미한다.
- 변수가 비율 혹은 배수이지만, 각 표본 값이 독립적일 때 사용할 수 있다.
- 표본 값끼리 곱했을 때, 어떠한 의미도 갖지 않아야 서로 독립적이라 할 수 있다.
'Python으로 하는 기초통계학 > 기본 개념' 카테고리의 다른 글
산포도 - 편차: 분산 & 표준 편차 & 표준편차에 (n-1)을 나누는 이유 & 자유도 (0) | 2021.03.05 |
---|---|
산포도(Dispersion) - 범위, 사분위간 범위, 사분위수와 상자 수염 그림 (0) | 2021.03.04 |
중심경향치(1) - 최빈값, 중앙값 (0) | 2021.03.03 |
도수분포표와 시각화 (0) | 2021.03.02 |
도수분포표 (0) | 2021.03.02 |