Do it! 쉽게 배우는 파이썬 데이터 분석

14 통계 분석 기법을 이용한 가설 검정




목차

14-1 가설 검정이란?(link)

14-2 t 검정 - 두 집단의 평균 비교하기(link)

14-3 상관분석 - 두 변수의 관계 분석하기(link)

14-1 가설 검정이란?

기술 통계와 추론 통계

  • 기술 통계(descriptive statistics): 데이터를 요약해 설명하는 통계 분석 기법
  • 추론 통계(inferential statistics): 어떤 값이 발생할 확률을 계산하는 통계 분석 기법

기술 통계와 추론 통계

ex) 성별에 따른 월급 차이가 우연히 발생할 확률을 계산

  • 이런 차이가 우연히 나타날 확률이 작다면
    • 성별에 따른 월급 차이가 통계적으로 유의하다(statistically significant)고 결론

  • 이런 차이가 우연히 나타날 확률이 크다면
    • 성별에 따른 월급 차이가 통계적으로 유의하지 않다(not statistically significant)고 결론

기술 통계와 추론 통계

  • 기술 통계 분석에서 집단 간 차이가 있는 것으로 나타났더라도 이는 우연에 의한 차이일 수 있음
  • 신뢰할 수 있는 결론을 내리려면 유의확률을 계산하는 통계적 가설 검정 절차를 거쳐야 함

통계적 가설 검정(statistical hypothesis test)

  • 유의확률을 이용해 가설을 검정하는 방법
  • 유의확률(significance probability, p-value)
    실제로는 집단 간 차이가 없는데 우연히 차이가 있는 데이터가 추출될 확률


유의확률이 크면

  • ‘집단 간 차이가 통계적으로 유의하지 않다’ 고 해석
  • 실제로 차이가 없더라도 우연에 의해 이런 정도의 차이가 관찰될 가능성이 크다는 의미

유의확률이 작으면

  • ‘집단 간 차이가 통계적으로 유의하다’ 고 해석
  • 실제로 차이가 없는데 우연히 이런 정도의 차이가 관찰될 가능성이 작다, 우연이라고 보기 힘들다는 의미

14-2 t 검정 - 두 집단의 평균 비교하기

t 검정(t-test)

  • 두 집단의 평균에 통계적으로 유의한 차이가 있는지 알아볼 때 사용하는 통계 분석 기법


compact 자동차와 suv 자동차의 도시 연비 t 검정

기술 통계 분석
import pandas as pd
mpg = pd.read_csv('mpg.csv')
## 기술 통계 분석

# compact, suv 추출하기
# category별 분리
# 빈도 구하기
# cty 평균 구하기
mpg.query('category in ["compact", "suv"]') \
   .groupby('category', as_index = False) \
   .agg(n    = ('category', 'count'),
        mean = ('cty', 'mean'))
  category   n      mean
0  compact  47  20.12766
1      suv  62  13.50000
t 검정
  • 비교하는 집단의 분산(값이 퍼져 있는 정도)이 같은지 여부에 따라 적용하는 공식이 다름
  • equal_var = True: 집단 간 분산이 같다고 가정
compact = mpg.query('category == "compact"')['cty']
suv = mpg.query('category == "suv"')['cty']

# t-test
from scipy import stats
stats.ttest_ind(compact, suv, equal_var = True)
TtestResult(statistic=11.917282584324107, pvalue=2.3909550904711282e-21, df=107.0)

일반적으로 유의확률 5%를 판단 기준으로 삼음

  • p-value가 0.05 미만이면 ‘집단 간 차이가 통계적으로 유의하다’ 고 해석
  • 실제로는 차이가 없는데 이런 정도의 차이가 우연히 관찰될 확률이 5%보다 작다면,
    이 차이를 우연이라고 보기 어렵다고 결론내리는 것
  • 'pvalue=2.3909550904711282e-21': 2.3909550904711282 × 10의 -21승
  • p-value가 0.05보다 작기 때문에 ‘compact와 suv 간 평균 도시 연비 차이가 통계적으로 유의하다’ 고 결론

일반 휘발유와 고급 휘발유의 도시 연비 t 검정

기술 통계 분석
## 기술 통계 분석

# r, p 추출하기
# fl별 분리
# 빈도 구하기
# cty 평균 구하기
mpg.query('fl in ["r", "p"]') \
   .groupby('fl', as_index = False) \
   .agg(n    = ('category', 'count'),
        mean = ('cty', 'mean'))
  fl    n       mean
0  p   52  17.365385
1  r  168  16.738095
t 검정
regular = mpg.query('fl == "r"')['cty']
premium = mpg.query('fl == "p"')['cty']

# t-test
stats.ttest_ind(regular, premium, equal_var = True)
TtestResult(statistic=-1.066182514588919, pvalue=0.28752051088667036, df=218.0)
  • p-value가 0.05보다 큼
  • 실제로는 차이가 없는데 우연에 의해 이런 정도의 차이가 관찰될 확률이 28.75%
  • ‘일반 휘발유와 고급 휘발유를 사용하는 자동차의 도시 연비 차이가 통계적으로 유의하지 않다’ 고 결론
  • 고급 휘발유 자동차의 연비 평균이 0.6 정도 높지만 이런 차이는 우연히 발생했을 가능성이 크다고 해석

14-3 상관분석 - 두 변수의 관계 분석하기

상관분석(correlation analysis)
  • 두 연속 변수가 서로 관련이 있는지 검정하는 통계 분석 기법
  • 상관계수(correlation coefficient)
    • 두 변수가 얼마나 관련되어 있는지, 관련성의 정도를 파악할 수 있음
    • 0~1 사이의 값, 1에 가까울수록 관련성이 크다는 의미
    • 양수면 정비례, 음수면 반비례 관계

실업자 수와 개인 소비 지출의 상관관계

1. 상관계수 구하기

# economics 데이터 불러오기
economics = pd.read_csv('economics.csv')

# 상관행렬 만들기
economics[['unemploy', 'pce']].corr()
          unemploy       pce
unemploy  1.000000  0.614518
pce       0.614518  1.000000
  • 출력 결과는 unemploypce의 상관계수를 나타낸 행렬
  • 행과 열에 똑같은 변수가 나열되어 대칭이므로 왼쪽 아래와 오른쪽 위에 표현된 상관계수가 같음
  • 상관계수가 양수 0.61: 한 변수가 증가하면 다른 변수가 증가하는 정비례 관계

2. 유의확률 구하기

# 상관분석
stats.pearsonr(economics['unemploy'], economics['pce'])
PearsonRResult(statistic=0.6145176141932079, pvalue=6.773527303289964e-61)
  • 출력 결과의 첫 번째 값이 상관계수, 두 번째 값이 유의확률
  • 유의확률이 0.05 미만이므로 실업자 수와 개인 소비 지출의 상관관계가 통계적으로 유의하다고 결론

상관행렬 히트맵 만들기

상관행렬(correlation matrix)

  • 모든 변수의 상관관계를 나타낸 행렬
  • 여러 변수의 관련성을 한꺼번에 알아보고 싶을 때 사용
  • 어떤 변수끼리 관련이 크고 적은지 한 눈에 파악할 수 있음

1. 상관행렬 만들기

mtcars = pd.read_csv('mtcars.csv')
mtcars.head()
    mpg  cyl   disp   hp  drat     wt   qsec  vs  am  gear  carb
0  21.0    6  160.0  110  3.90  2.620  16.46   0   1     4     4
1  21.0    6  160.0  110  3.90  2.875  17.02   0   1     4     4
2  22.8    4  108.0   93  3.85  2.320  18.61   1   1     4     1
3  21.4    6  258.0  110  3.08  3.215  19.44   1   0     3     1
4  18.7    8  360.0  175  3.15  3.440  17.02   0   0     3     2
car_cor = mtcars.corr()      # 상관행렬 만들기
car_cor = round(car_cor, 2)  # 소수점 둘째 자리까지 반올림
car_cor
       mpg   cyl  disp    hp  drat    wt  qsec    vs    am  gear  carb
mpg   1.00 -0.85 -0.85 -0.78  0.68 -0.87  0.42  0.66  0.60  0.48 -0.55
cyl  -0.85  1.00  0.90  0.83 -0.70  0.78 -0.59 -0.81 -0.52 -0.49  0.53
disp -0.85  0.90  1.00  0.79 -0.71  0.89 -0.43 -0.71 -0.59 -0.56  0.39
hp   -0.78  0.83  0.79  1.00 -0.45  0.66 -0.71 -0.72 -0.24 -0.13  0.75
drat  0.68 -0.70 -0.71 -0.45  1.00 -0.71  0.09  0.44  0.71  0.70 -0.09
wt   -0.87  0.78  0.89  0.66 -0.71  1.00 -0.17 -0.55 -0.69 -0.58  0.43
qsec  0.42 -0.59 -0.43 -0.71  0.09 -0.17  1.00  0.74 -0.23 -0.21 -0.66
vs    0.66 -0.81 -0.71 -0.72  0.44 -0.55  0.74  1.00  0.17  0.21 -0.57
am    0.60 -0.52 -0.59 -0.24  0.71 -0.69 -0.23  0.17  1.00  0.79  0.06
gear  0.48 -0.49 -0.56 -0.13  0.70 -0.58 -0.21  0.21  0.79  1.00  0.27
carb -0.55  0.53  0.39  0.75 -0.09  0.43 -0.66 -0.57  0.06  0.27  1.00
  • mpg(연비)와 cyl(실린더 수)의 상관계수 -0.85: 연비가 높을수록 실린더 수가 적은 경향이 있음
  • cyl(실린더 수)과 wt(무게)의 상관계수 0.78: 실린더 수가 많을수록 자동차가 무거운 경향

2. 히트맵 만들기

  • 여러 변수로 상관행렬을 만들면 출력된 값이 너무 많아서 관심있는 변수들의 관계를 파악하기 어려움
  • 값의 크기를 색깔로 표현한 히트맵(heatmap)을 만들면 변수들의 관계를 쉽게 파악할 수 있음
import matplotlib.pyplot as plt
plt.rcParams.update({'figure.dpi' : '120',           # 해상도 설정
                     'figure.figsize': [7.5, 5.5]})  # 가로 세로 크기 설정

# 히트맵 만들기
import seaborn as sns
sns.heatmap(car_cor,
            annot = True,   # 상관계수 표시
            cmap = 'RdBu')  # 컬러맵

  • 상관계수가 클수록 상자 색깔을 진하게 표현
  • 상관계수가 양수면 파란색, 음수면 빨간색 계열로 표현
  • 상자 색깔을 보면 상관관계의 정도와 방향을 쉽게 파악할 수 있음

3. 대각 행렬 제거하기

  • 히트맵은 대각선 기준으로 왼쪽 아래와 오른쪽 위의 값이 대칭하여 중복됨
  • sns.heatmap()mask를 이용해 중복된 부분 제거

(1) mask 만들기

  • 상관행렬의 행과 열의 수 만큼 0으로 채운 배열(array)을 만듦
# mask 만들기
import numpy as np
mask = np.zeros_like(car_cor)
mask
array([[0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.]])

(1) mask 만들기

  • mask의 오른쪽 위 대각 행렬을 1로 바꿈
# 오른쪽 위 대각 행렬을 1로 바꾸기
mask[np.triu_indices_from(mask)] = 1
mask
array([[1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.],
       [0., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.],
       [0., 0., 1., 1., 1., 1., 1., 1., 1., 1., 1.],
       [0., 0., 0., 1., 1., 1., 1., 1., 1., 1., 1.],
       [0., 0., 0., 0., 1., 1., 1., 1., 1., 1., 1.],
       [0., 0., 0., 0., 0., 1., 1., 1., 1., 1., 1.],
       [0., 0., 0., 0., 0., 0., 1., 1., 1., 1., 1.],
       [0., 0., 0., 0., 0., 0., 0., 1., 1., 1., 1.],
       [0., 0., 0., 0., 0., 0., 0., 0., 1., 1., 1.],
       [0., 0., 0., 0., 0., 0., 0., 0., 0., 1., 1.],
       [0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 1.]])

(2) 히트맵에 mask 적용하기

  • sns.heatmap()mask 적용
  • mask의 1에 해당하는 위치의 값이 제거되어 왼쪽 아래의 상관계수만 표현됨
# 히트맵 만들기
sns.heatmap(data = car_cor,
            annot = True,   # 상관계수 표시
            cmap = 'RdBu',  # 컬러맵
            mask = mask)    # mask 적용

(3) 빈 행과 열 제거하기

  • 앞에서 만든 히트맵의 왼쪽 위 mpg행, 오른쪽 아래 carb열에 아무 값도 표현되어 있지 않음
  • 행과 열의 변수가 같아서 상관계수가 항상 1이 되는 위치이므로 값을 표현하지 않은 것
  • mask와 상관행렬의 첫 번째 행과 마지막 열을 제거해서 빈 행과 열 제거

mask_new = mask[1:, :-1]         # mask 첫 번째 행, 마지막 열 제거
cor_new = car_cor.iloc[1:, :-1]  # 상관행렬 첫 번째 행, 마지막 열 제거

sns.heatmap(data = cor_new,
            annot = True,       # 상관계수 표시
            cmap = 'RdBu',      # 컬러맵
            mask = mask_new)    # mask 적용

보기 좋게 수정하기

sns.heatmap(data = cor_new,
            annot = True,               # 상관계수 표시
            cmap = 'RdBu',              # 컬러맵
            mask = mask_new,            # mask 적용
            linewidths = .5,            # 경계 구분선 추가
            vmax = 1,                   # 가장 진한 파란색으로 표현할 최대값
            vmin = -1,                  # 가장 진한 빨간색으로 표현할 최소값
            cbar_kws = {'shrink': .5})  # 범례 크기 줄이기

히트맵 모양 바꾸기

  • sns.heatmap()의 파라미터를 이용하면 히트맵의 모양을 다양하게 바꿀 수 있음
  • seaborn 공식 문서 참고: bit.ly/easypy_142