귀퉁이 서재
DATA - 12. 부트스트랩(Bootstrap) 본문
부트스트랩(Bootstrap)
모수의 분포를 추정하는 파워풀한 방법은 현재 있는 표본에서 추가적으로 표본을 복원 추출하고 각 표본에 대한 통계량을 다시 계산하는 것입니다. 이러한 절차를 부트스트랩이라고 합니다. 부트스트랩은 데이터가 정규분포를 따라야 한다는 가정이 꼭 필요하지 않습니다. 1억 개의 모집단에서 뽑은 200개의 표본이 있다고 합시다. 200개로만 통계량을 구하는 것이 아니라 200개를 기준으로 복원 추출하여 새로운 통계량을 구하는 것입니다.
다음은 부트스트랩으로 신뢰구간을 구하는 절차입니다.
1. 200개의 표본 중 하나를 뽑아 기록하고 다시 제자리에 둡니다.
2. 이를 n번 반복합니다.
3. n번 재표본추출한 값의 평균을 구합니다.
4. 1~3 단계를 R번 반복합니다. (R: 부트스트랩 반복 횟수)
5. 평균에 대한 결과 R개를 사용하여 신뢰구간을 구합니다.
이 방법을 사용하면 표본이 200개밖에 없을지라도 부트스트랩을 통해 200개 보다 더 많은 통계량을 구할 수 있습니다. 따라서 부트스트랩을 활용하면 모수를 더 정확히 추정할 수 있습니다. 특히, R이 클수록 신뢰구간에 대한 추정은 더 정확해집니다. (Reference1: 데이터 과학을 위한 통계, 피터 브루스 외 저)
어떻게 200개의 표본에서 여러 번 표본을 뽑는 것이 모수의 분포를 더 정확하게 추정할 수 있을까요? 200개의 표본은 모수로부터 균등하게 뽑은 것입니다. 모수를 대표하는 표본인 것입니다. 따라서 부트스트랩은 이빨 빠진 표본 분포 사이사이에 이빨을 심어주는 것과 같은 기능을 합니다. 부트스트랩이 왜 효과가 있는지는 본 링크를 참고하시기 바랍니다. (Reference2)
신뢰구간
신뢰구간이란 모수가 어느 범위 안에 있는지를 확률적으로 보여주는 방법입니다. (Reference3) 어떤 제조회사의 부품에 대한 95% 신뢰구간이 [100mm, 120mm]라고 한다면, 부품이 100mm와 120mm 사이에 있을 것이라고 95% 확신할 수 있습니다. 다른 말로 95% 신뢰구간이란, 확률표본을 100번 뽑아 구간 100개를 얻으면 이 중 모수를 포함하는 것은 대략 95개 정도가 될 것이라는 뜻입니다. (표본통계량의 부트스트랩 표본분포의 95%를 포함하는 구간을 말합니다.) (Reference4: 통계학 이론과 응용, 배도선 저)
표본의 수가 많을 수록 (즉, n이 클수록) 신뢰구간은 작아지고, 신뢰수준(Confidence Level)이 클수록 (ex. 95% -> 99%) 신뢰구간은 커집니다. 또한, 신뢰구간과 가설검정을 통해 모수의 총량 (평균, 표준편차 등)에 대해서 알 수 있으며, 모수 개별에 대해서는 모릅니다. 모수 개별 데이터에 대한 접근은 머신러닝 분야에서 이루어집니다.
Pandas, Numpy 활용 부트스트랩 실습
부트스트랩을 활용하여 커피를 마시는 사람과 마시지 않는 사람의 키를 비교해보겠습니다. Data set은 제 깃헙에서 다운받으실 수 있습니다. Data set에는 21살 미만/이상 여부, 커피를 마시는지 마시지 않는지 여부, 그리고 키에 대한 데이터가 있습니다. 부트스트랩 연습을 위해 모수를 모른다고 가정하고 200개의 샘플에서 부트스트래핑을 할 것입니다. 커피가 키에 영향을 미치는지 분석하기 위해 아래 4개의 분석을 하겠습니다. 처음에 200개의 샘플을 뽑고, 그것을 활용하여 모두 부트스트래핑을 합니다.
- 커피를 마시지 않는 사람과 커피를 마시는 사람의 평균 키 차이
- 21살 이상과 21살 미만인 사람들의 평균 키 차이
- 21살 미만인 사람들 중 커피를 마시지 않는 사람과 커피를 마시는 사람의 평균 키 차이
- 21살 이상인 사람들 중 커피를 마시지 않는 사람과 커피를 마시는 사람의 평균 키 차이
import pandas as pd
import numpy as np
np.random.seed(104)
df = pd.read_csv('coffee_dataset.csv')
df.head()
user_id | age | drinks_coffee | height | |
---|---|---|---|---|
0 | 4509 | <21 | False | 64.538179 |
1 | 1864 | >=21 | True | 65.824249 |
2 | 2060 | <21 | False | 71.319854 |
3 | 7875 | >=21 | True | 68.569404 |
4 | 6254 | <21 | True | 64.020226 |
모집단에서 200개의 표본을 랜덤샘플링합니다.
df_sample = df.sample(200)
df_sample.head()
user_id | age | drinks_coffee | height | |
---|---|---|---|---|
486 | 4951 | <21 | False | 68.539632 |
2297 | 5383 | >=21 | True | 70.594166 |
911 | 3904 | <21 | False | 61.114035 |
146 | 4351 | >=21 | True | 68.952444 |
2255 | 3998 | <21 | False | 65.493818 |
부트스트랩(Bootstrap)을 활용한 신뢰구간
부트스트랩을 10,000번 반복해서 커피를 마시지 않는 사람과 마시는 사람의 키 차이의 99% 신뢰구간을 구해보겠습니다.
- 커피를 마시지 않는 사람과 커피를 마시는 사람의 평균 키 차이
iterationNum = 10000
diffHeightList = []
for _ in range(iterationNum):
bootSample = df_sample.sample(200, replace=True) # 복원 추출
nonCoffeeHeightMean = bootSample[bootSample['drinks_coffee'] == False].height.mean() # 커피를 마시지 않는 사람 평균 키
coffeeHeightMean = bootSample[bootSample['drinks_coffee'] == True].height.mean() # 커피를 마시는 사람 평균 키
diff = nonCoffeeHeightMean - coffeeHeightMean
diffHeightList.append(diff)
# 신뢰수준 99%인 평균 키 차이에 대한 신뢰구간
np.percentile(diffHeightList, 0.5), np.percentile(diffHeightList, 99.5)
(-3.1319975931075725, -0.7811253958264867)
일반적으로 커피를 마시는 사람이 커피를 마시지 않는 사람에 비해 키가 작은 것이라 생각할 수 있습니다. 그런데 신뢰수준 99%로 커피를 마시지 않는 사람이 커피를 마시는 사람에 비해 더 작습니다. 좀 더 심층적으로 분석하기 위해 나이에 따라 21살 미만, 이상의 키 차이를 구해보겠습니다.
- 21살 이상과 21살 미만인 사람들의 평균 키 차이
diffHeightListByAge = []
for _ in range(iterationNum):
bootSample = df_sample.sample(200, replace=True) # 복원 추출
over21HeightMean = bootSample[bootSample['age'] == '>=21'].height.mean() # 21살 이상 평균 키
under21HeightMean = bootSample[bootSample['age'] == '<21'].height.mean() # 21살 미만 평균 키
diff = over21HeightMean - under21HeightMean
diffHeightListByAge.append(diff)
np.percentile(diffHeightListByAge, 0.5), np.percentile(diffHeightListByAge, 99.5)
(3.094910696764073, 4.97970620735041)
신뢰수준 99%로 21살 이상이 21살 미만에 비해 키가 큽니다. 당연한 결과입니다. 21살 미만인 사람들 중 커피를 마시지 않는 사람과 마시는 사람의 평균 키를 비교해보겠습니다.
- 21살 미만인 사람들 중 커피를 마시지 않는 사람과 커피를 마시는 사람의 평균 키 차이
diffHeightListUnder21 = []
for _ in range(iterationNum):
bootSample = df_sample.sample(200, replace=True) # 복원 추출
nonCoffeeHeightMeanUnder21 = bootSample.query("age == '<21' and drinks_coffee == False").height.mean() # 21살 미만이며 커피를 마시지 않는 사람 평균 키
coffeeHeightMeanUnder21 = bootSample.query("age == '<21' and drinks_coffee == True").height.mean() # 21살 미만이며 커피를 마시는 사람 평균 키
diff = nonCoffeeHeightMeanUnder21 - coffeeHeightMeanUnder21
diffHeightListUnder21.append(diff)
np.percentile(diffHeightListUnder21, 0.5), np.percentile(diffHeightListUnder21, 99.5)
(0.265445115542466, 2.473977523788757)
- 21살 이상인 사람들 중 커피를 마시지 않는 사람과 커피를 마시는 사람의 평균 키 차이
diffHeightListOver21 = []
for _ in range(iterationNum):
bootSample = df_sample.sample(200, replace=True) # 복원 추출
nonCoffeeHeightMeanOver21 = bootSample.query("age != '<21' and drinks_coffee == False").height.mean() # 21살 이상이며 커피를 마시지 않는 사람 평균 키
coffeeHeightMeanOver21 = bootSample.query("age != '<21' and drinks_coffee == True").height.mean() # 21살 이상이며 커피를 마시는 사람 평균 키
diff = nonCoffeeHeightMeanOver21 - coffeeHeightMeanOver21
diffHeightListOver21.append(diff)
np.percentile(diffHeightListOver21, 0.5), np.percentile(diffHeightListOver21, 99.5)
(0.38466991879827656, 3.290024158015461)
전체 데이터에서는 커피를 마시는 사람이 커피를 마시지 않는 사람에 비해 평균 키가 컸지만 21살 미만, 이상으로 나누어 계산해보니 모두 커피를 마시지 않는 사람의 평균 키가 컸습니다. 이는 앞서 살펴본 심슨의 역설입니다.
모수와 부트스트랩 신뢰구간의 비교
다음 챕터에서 가설검정에 대해 알아보겠지만, 신뢰구간을 통해서도 가설검정을 할 수 있습니다. 부트스트랩을 통해 신뢰구간을 정하고 관측된 값이 신뢰구간 안에 있으면 해당 가설은 참인 것으로 판정하는 것입니다. 본 예제에서는 원본 데이터를 알고 있기 때문에 실제 평균의 차이를 구해서 모수가 신뢰구간 안에 있는지 보겠습니다.
# 1. 커피를 마시지 않는 사람과 커피를 마시는 사람의 평균 키 차이
df[df['drinks_coffee'] == False].height.mean() - df[df['drinks_coffee'] == True].height.mean()
-1.9568024933369657
# 2. 21살 이상과 21살 미만인 사람들의 평균 키 차이
df[df['age'] == '>=21'].height.mean() - df[df['age'] == '<21'].height.mean()
3.88229124992111
# 3. 21살 미만인 사람들 중 커피를 마시지 않는 사람과 커피를 마시는 사람의 평균 키 차이
df.query("age == '<21' and drinks_coffee == False").height.mean() - df.query("age == '<21' and drinks_coffee == True").height.mean()
1.6993900935511732
# 4. 21살 이상인 사람들 중 커피를 마시지 않는 사람과 커피를 마시는 사람의 평균 키 차이
df.query("age != '<21' and drinks_coffee == False").height.mean() - df.query("age != '<21' and drinks_coffee == True").height.mean()
1.9509354889786579
각 모수인
-1.9568024933369657
3.88229124992111
1.6993900935511732
1.9509354889786579
가 각각 (아까 구했던) 신뢰구간
(-3.1319975931075725, -0.7811253958264867)
(3.094910696764073, 4.97970620735041)
(0.265445115542466, 2.473977523788757)
(0.38466991879827656, 3.290024158015461)
안에 있음을 알 수 있습니다.
따라서 모수가 부트스트랩 신뢰구간 안에 있다는 사실을 알게 되었습니다.
References
Reference1: 데이터 과학을 위한 통계, 피터 브루스 외 저
Reference2: Explaining to laypeople why bootstrapping works
Reference4: 통계학 이론과 응용, 배도선 저
'데이터 분석' 카테고리의 다른 글
DATA - 14. 통계적 유의성의 함정 (0) | 2019.04.23 |
---|---|
DATA - 13. 가설검정과 p-value, 본페로니 교정 (6) | 2019.04.19 |
DATA - 11. 표본 분포, 대수의 법칙, 중심극한정리 (0) | 2019.04.14 |
DATA - 10. 베이즈 추정(Bayesian Estimation) (8) | 2019.04.13 |
DATA - 9. 베르누이 시행과 이항 분포 (0) | 2019.04.12 |