1. 공통 코드

from pathlib import Path
import random

import pandas as pd
import numpy as np

import matplotlib.pyplot as plt
from matplotlib import font_manager, rc
import platform
import matplotlib
import seaborn as sns

import scipy as sp
from scipy import stats
import statsmodels.api as sm
import statsmodels.formula.api as smf
from statsmodels.stats import power
%matplotlib inline

path = "c:/Windows/Fonts/malgun.ttf"
if platform.system() == 'Darwin':
    rc('font', family='AppleGothic')
elif platform.system() == 'Windows':
    font_name = font_manager.FontProperties(fname=path).get_name()
    rc('font', family=font_name)
    
matplotlib.rcParams['axes.unicode_minus'] = False

import warnings
warnings.filterwarnings('ignore')

 

 

2. 추론 통계 - 통계적 추론

- 기술통계: 주어진 데이터의 분포나 빈도, 평균 등의 통계량으로 데이터를 설명하기 위한 목적

  vs 추론통계: 주어진 데이터(sample)을 이용하여 모집단의 특성(모수)을 추론하는 것

  • 모집단은 전수조사가 어렵고, 정확한 범위를 알지 못하는 경우도 있어서 분산이나 평균을 구하기 어렵다. 
  • 그래서 실제 모집단에서 표본 몇십~몇백개를 추출하여 표본의 분산이나 표본의 평균을 사용하는 경우가 대부분이다.

- 추론: 제한된 데이터로 주어진 실험 결과를 더 큰 과정이나 모집단에 적용하려는 의도를 반영하는 것

- 추론통계: 가설 검정으로 모수를 판단하는 것

  1. 가설 세우기
  2. 실험 설계하기
  3. 데이터 수집하기
  4. 추론 및 결론 도출하기

 

 

3. 확률 분포의 추정

- 분석할 데이터는 어떤 확률 변수로부터 실현된 표본이다 가정

- 우리의 관심은 데이터가 아니라 데이터를 만들어내는 확률 변수의 분포

- 학률론적인 관점에서 데이터는 확률 변수의 분포를 알아내는 데 참고 자료일 뿐

 

3-1) 확률 분포의 결정 과정

- 확률 변수가 어떤 확률 분포를 따르는지 알아내는 과정

- 데이터로부터 해당 확률 분포의 모수의 값을 구함

-  데이터의 생성 원리나 특성을 알면 추측 가능. 히스토그램으로 확률 분포의 모양을 살펴보고 힌트를 얻을 수 있음.

  • 데이터가 0 또는 1: 베르누이 분포
  • 데이터가 카테고리 값 - 카테고리 분포
  • 데이터가 0과 1 사이의 실수 - 베타 분포
  • 데이터가 항상 0 또는 양수 - 로그 정규 분포, 감마 분포, F 분포, 카이 제곱 분포, 지수 분포, 하프코시분포 등
  • 데이터에 크기 제한이 없는 실수 - 정규 분포, t 분포, 코시 부포, 라플라스 분포 등
  • 균일 분포
  • 푸아송 분포 (=지수 분포)
  • 베이블 분포 (실제 사건은 패턴을 가지고 시간이 늘어나거나 줄어들게 되는데 그 때 지수분포 대신 사용)

 

 

4. 검정

- 데이터 뒤에 숨어있는 확률 변수의 분포와 모수에 대한 가설의 진위를 정량적으로 증명하는 작업

- 어떤 동전을 15번 던졌더니 앞면이 12번 나왔다.이 동전은 조작되지 않은 동전인가, 조작된 동전인가를 판단하는 것

- 주식의 수익률이 -2.5%, -5%, 4.3%, -3.7%, -5.6%일 때 이 주식은 장기적으로 수익일까 손실일까?

 

 

5. 추정

5-1) 점추정

  • 모집단의 특성을 하나의 값으로 추정하는 방식
  • 분산이나 표준편차를 구해서 모집단의 분산이나 표준편차라고 추정할 때   자유도: 데이터개수 -1
# 모집단의 특성을 하나의 값으로 추정
fish = pd.read_csv("./data/fish_length.csv")

#표본의 평균
mu = sp.mean(fish['length'])

print(mu)
4.187039324504523
  • 표본 평균이 4.187 이므로 모 평균도 4.187 이라고 추정 - 점 추정
  • 분산이나 표준 편차를 구해서 모집단의 분산이나 표준 편차라고 추정할 때는 자유도는 데이터개수 - 1 입니다.
sigma = sp.var(fish['length'], ddof=1)
print(sigma)
#분산을 추정할 때는 표본에서 계산량을 통계량을 이용할 수 있는데 
#이 경우에는 불편 분산을 이용
0.6803017080832622

 

 

5-2) 구간추정

- 모집단의 특성을 적절한 구간을 이용하여 추정하는 방식으로 하한값과 상한값으로 추정

- 용어

  • 신뢰계수: 구간 추정의 폭에 대한 신뢰 정보를 확률로 표현한 것. 90%, 95%, 99% 많이 이용
  • 신뢰수준: 계산된 구간이 모수를 포함한 확률을 의미.  90%, 95%, 99% 등으로 표현
  • 신뢰구간: 신뢰 수준 하에서 모수를 포함하는 구간으로 [하한값, 상한값]으로 표시
  • 표본오차: 모집단에서 추출한 표본이 모집단의 특성과 정확히 일치하지 않아서 발생하는 확률의 차이

- 대통령 후보의 지지율 여론 조사에서 후보의 지지율이 95% 신뢰 수준에서 표본 오차 ±3% 범위 내에서 32.4%로 조사되었다고 하면, 29.4% ~ 35.4% 이내에 존재할 확률이 95% 된다는 뜻. 5% 수준에서는 틀릴 수 있다는 뜻. 

 

 

5-3) 가설

- 데이터를 특정한 확률분포를 가진 확률 변수로 모형화하면 모수를 추정할 수 있음

- 확률 분포에 대한 어떤 주장을 가설(hypothesis)라고 하면 H를 이텔릭으로 표현

- 가설을 증명하는 행위를 통계적 가설 검정이라 함. 줄여서 검정

- 확률 분포의 모숫값이 특정한 값을 가진다는 가설을 검정하는 것을 모수 검정이라고 함

 

귀무가설(null hypothesis)

  • 검정 통계 분포를 결정하는 최초의 가설이 귀무 가설
  • 우연에 의해 발생한 것이라는 개념을 구체화하기 위해, 귀무가설을 먼저 세우고 대립 가설을 채택한다.
  • 실험에서 얻은 그룹 간의 차이가 무작위로 얻을 수 있는 합리적인 수준과는 극단적으로 다르다는 증거가 필요해서 그룹들이 보이는 결과는 서로 동일하면 그룹간의 차이는 우연히 발생한 결과임을 기본 가정으로 설정
  • 귀무가설은 기본적으로 같다, 차이가 없다에서 출발
  • 일반적으로 검정의 목적은 대다수 귀무 가설이 틀렸음을 확인하기 위한 작업

대립가설(alternative hypothesis)

  • 귀무 가설과 대립되는 가설
  • 귀무 가설이 그룹A와 B의 평균에는 차이가 없다는 것이라면 대립가설은 그룹 A와 B의 평균에는 차이가 있다는 가설

 

5-4) 검정

- 일원 검정(one-way test, single-tailed test - 단측 검정)과 이원검정(two-way test, 양측 검정)

 

일원검정

  • 한 방향으로만 우연히 일어날 확률을 계산하는 가설 검정
  • 기존에 사용하던 옵션과 비교해서 새 옵션이 어떤지 검정을 하고자 하는 경우 새로운 옵션이 기존의 옵션보다 성능이 좋아야 변경을 할 것이므로 새 옵션이 기존의 옵션보다 성능이 우수하게 평가되는 것이 우연에 의해서 발생한 것이 아니라는 것을 검정해야 하는데, 이러한 경우를 단측검정이라고 한다.
  • (다르다가 아니라 좋다 나쁘다)

이원검정

  • (다르다)  
  • A와 B는 다르다는 것을 검정.
  • R과 파이썬의 scipy는 기본적으로 이원 검정(보수적)을 채택
  • 통계는 대부분 이원검정을 쓰지만, 사실 실무에서는 일원검정을 많이 사용한다. 

 

5-5) 통계적 유의성 

- 통계학자가 자신의 실험 결과가 우연히 일어난 것인지 아니면 우연히 일어날 수 없는 것인지 판단하는 방법

- 결과가 우연히 벌어질 수 있는 변동성의 바깥에 존재한다면 이 경우를 통계적으로 유의하다고 판단

 

- p-value(유의확률): 귀무가설을 구체화한 기회 모델이 주어졌을 때, 관측된 결과와 같이 특이하거나 극단적인 결과를 얻을 확률.

  • p-value가  낮으면 귀무 가설을 기각 - 대립 가설 채택
  • 유의 확률이 0.02 라면 귀무 가설을 기각 했을 때 이 기각 결정이 잘못될 확률이 2%라는 것

- alpha(유의수준): 우연에 의한 결과가 능가해야 하는 비정상적인 가능성의 임계 확률.

  • 유의 확률 < 유의 수준: 귀무 가설을 기각하고 대립 가설을 채택
  • 유의 확률 > 유의 수준: 대립 가설을 기각하고 귀무 가설을 채택
  • 이 값은 분석하는 사람이 결정

- 제1종 오류: 우연에 의한 결과를 실제 효과라고 잘못 결론내리는 것

  • 데이터가 부족하기 때문이다. 
  • 데이터 샘플을 늘리면 문제 해결됨

- 제2종 오류: 실제 효과를 우연에 의한 효과라고 잘못 결론내리는 것

  • 2종 오류를 찾아내는게 어렵다.

- 기각역(Critical Value): 유의 수준에 대해서 계산된 검정 통계량

 

- 동전을 15번 던졌을 때 한쪽 면이 12번 나온 경우 이 동전은 공정한 동전인가?

  • 동전 던지기는 이항분포(베르누이 분포)이고 확률은 0.5
  • 이항 분포의 누적밀도함수(cdf) 이용해서 12번 나올 확률을 찾음
  • 귀무가설: 앞면과 뒷면이 나올 확률은 일치한다. 이 동전은 공정하다.
    대립가설: 앞면과 뒷면이 나올 확률은 일치하지 않는다. 이 동전은 공정하지 않다.
  • 유의 수준을 설정해서 유의 수준보다 작은 확률이었다면 귀무 가설 기각, 유의 수준보다 크면 귀무가설 채택
  • 비교할 떄   단측 검정: (1-누적밀도함수의 결과)    양측 검정: (1-누적밀도함수의 결과) * 2
N = 15 # 시행 횟수
mu = 0.5 # 1이 나올 확률
rv = sp.stats.binom(N, mu)

#축 설정을 위한 데이터
xx = np.arange(N + 1)

#확률 질량 함수(pmf) - 이산 확률 분포에서 각 사건이 발생할 확률을 계산해주는 함수
#그래프를 2행 1열로 표시하고 첫번째 영역에 표시
plt.subplot(211)
#이산 확률 질량 함수 와 누적 밀도 함수는 stem 으로 시각화
plt.stem(xx, rv.pmf(xx))
plt.ylabel('pmf')
plt.title("확률 질량 함수")
plt.annotate("검정 통계량 t = 12", xy=(12, 0.02), xytext=(12, 0.1),
            arrowprops={"facecolor":"black"})
plt.show()

plt.subplot(212)
#이산 확률 질량 함수 와 누적 밀도 함수는 stem 으로 시각화
plt.stem(xx, rv.cdf(xx))
plt.ylabel('cdf')
plt.title("누적 확률 밀도 함수 함수")
plt.annotate("검정 통계량 t = 12", xy=(12, 0.02), xytext=(12, 0.1),
            arrowprops={"facecolor":"black"})
plt.show()

  • 이산확률질량함수와 누적밀도함수는 stem으로 시각화

 

# 양측 검정

  • 동전을 던졌을 때 15번 던졌을 때 12번 나온 경우에 대한 검정
  • 대립가설: μ ≠ 0.5
N = 15 # 시행 횟수
mu = 0.5 # 1이 나올 확률
rv = sp.stats.binom(N, mu)

#양측 검정에서 사용할 유의 확률: 11번까지 나올 수 있는 확률을 1에서 빼면 됩니다.
p = 2 * (1 - rv.cdf(12-1))
print(p)
#우연히 12번 이상 나올 확률은 0.035(대략 3.5% 정도)
#유의 수준을 5%로 설정하면 이 경우는 귀무 가설이 기각 - 이 동전은 공정하지 않다.
#유의 수준을 1%로 설정하면 이 경우는 귀무 가설을 기각할 수 없음 - 이 동전은 공정한 동전
0.03515625
  • 우연히 12번 이상 나올 확률은 0.035 (약 3.5%)
  • 유의 수준 5%에서는 귀무가설 기각, 이 동전은 공정하지 않다.
  • 유의 수준 1%에서는 귀무가설 채택, 이 동전은 공정하다.

# 단측 검정

  • 앞면이 12번 이상 나왔을 때 이 동전은 앞면이 더 많이 나오는 동전인가?
  • 특정 면에 대해서 더 많이, 적게 나온 것을 구하기에 단측 검정!
  • 대립 가설: μ > 0.5
  • 이항분포, 1이 나올 확률 0.5, 시행횟수 15번
#단측 검정 - 앞 면이 12번 이상 나왔을 때 이 동전은 앞 면이 더 많이 나오는 동전인가
#어떤 분포 - 이항 분포
#1이 나올 확률 - 0.5
N = 15
mu = 0.5
rv = sp.stats.binom(N, mu)

#앞면이 11번까지 나올 수 있는 확률을 구해서 1에서 빼기
p = (1 - rv.cdf(11))
print(p)

#이제 유의 확률을 계산했으므로 유의 수준을 설정해서 판정
if 0.01 < p:
    print("유의 확률이 더 높으므로 귀무 가설 채택")
else:
    print("유의 확률이 더 높으므로 대립 가설 채택")
0.017578125
유의 확률이 더 높으므로 귀무 가설 채택

 

 

 

6. 이항 검정

6-1) A/B 검정 (A/B Test)

- 두 처리 방법 또는 제품중 어느 쪽이 다른 쪽보다 더 우월한지를 입증하기 위해 실험군을 두 그룹으로 나누어 동시에 진행하는 실험

  • 대조군: 기준이 되는 기존 방법. 아무런 조치도 적용하지 않음.
  • 처리군: 반대되는 실험에 의해 만들어진 집단이나 처리법. 
  • 가설: 새로운 처리법을 적용한 처리군이 대조군보다 더 낫다.

- 디자인이나 마케팅에서 일반적으로 사용

- 웹 전환율을 A/B 테스트로 검정하고자 하는 경우 부트스트래핑 방식을 추가적으로 도입해야 함

  • 실제 웹의 전환율(다음 행위를 할 확률)은 굉장히 낮기 때문에 하나의 실험 결과만으로 판정하기는 어렵다.
  • 다른 독립변인의 영향을 받아서 실험의 결과가 달라질 수 있기 때문에 실험군을 만드는게 굉장히 어렵다.
  • 샘플링을 여러개 놓고 굉장히 여러번 검증해봐야 한다. 

- 활용 분야

  • 종자 발아가 어디에서 더 잘되는지 알아보기 위해 두 가지 토양 처리를 검정
  • 암을 더 효과적으로 억제하는 두 가지 치료법을 검정
  • 두 가지 가격을 검정하여 더 많은 순이익을 산출하는 쪽을 결정
  • 두 개의 인터넷 뉴스 제목을 검정하여 더 많은 클릭을 생성하는 쪽을 결정
  • 두 개의 인터넷 광고를 검정하여 어느 것이 더 높은 전환율을 얻을 지 판단

 

6-2) 이항 검정 API

-scipy.stats.binom_test(x, n=None, p=0.5, alternative='two-sided')

  • x: 검정통계량. 1이 나온 횟수
  • n: 총 시도 횟수
  • p: 귀무 가설의 μ 값
  • alternative: 양측 검정인 경우에는 two-sided, 단측 검정인 경우에는 less 또는 'greater‘

* 이항 검정은 이산이라서 직접 연산을 수행하여 판정하는 것이 쉽기 때문에 누적확률분포함수나 확률질량함수로 직접 판정하는 경우도 많음.

 

# 게임에서 내가 이길 확률이 0.3인데 100번 했을 때 60번 이상 이기는 것이 가능한지 유의 수준 5%로 검정

#누적확률분포함수 이용해서 59번 이길 확률을 빼주면 60번 이상 확률 계산
r = 1-sp.stats.binom(100, 0.3).cdf(30 - 1)

print(p)

if r > 0.05:
    print("p-value가 0.05보다 크므로 정상적인 상황입니다.")
else:
    print("p-value가 0.05보다 작으므로 발생할 가능성이 낮은 상황입니다.")
5.12994979828818e-10
귀무 가설을 기각하고 대립 가설을 채택해서 이런 결과가 나올 확률은 극히 희박

 

# 여자 손님 중 비흡연자가 흡연자보다 많다고 할 수 있는가? (유의 수준 10%)

#데이터 가져오기
tips = sns.load_dataset("tips")
#print(tips.head())

#female이 Female 인 데이터만 추출
female = tips[tips['sex'] == 'Female']
#print(female['sex'].value_counts())

#흡연자의 인원 수 와 비흡연자의 인원 수를 계산
#전체 인원수
fem_cnt = female['sex'].count()
smoke_cnt = female[female['smoker'] == 'Yes'].count()
non_smoke_cnt = female[female['smoker'] == 'No'].count()
#print(fem_cnt)
#print(smoke_cnt)
#print(non_smoke_cnt)

#흡연자의 유의 확률
p = 1- (sp.stats.binom(fem_cnt, 0.5).cdf(smoke_cnt[0] - 1))
print(p)
if p > 0.1:
    print("귀무 가설을 채택 - 흡연자의 수가 비흡연자의 수보다 많다고 할 수 없다.")
else :
    print("대립 가설을 채택 - 흡연자의 수가 비흡연자의 수보다 많다고 할 수 있다.")
0.991086223535132
귀무 가설을 채택 - 흡연자의 수가 비흡연자의 수보다 많다고 할 수 없다.

 

 

 

7. t 검정

- 모집단의 분산을 모를 때 표본으로 추정하여 평균의 차이를 알아보는 검정

- 검정 통계량으로 스튜던트 t 분포를 가진 통계량 사용

- 표본 평군의 분포를 근사화하기 위해 개발

- 최대 2개의 집단까지 비교 가능

 

- T 검정은 다음 조건을 만족한다.

  • 연속형 데이터
  • 랜덤 표본 데이터
  • 모집단의 분포가 정규 분포에 유사
  • 분산에 동질성이 있음

- 종류

  • 1표본 T검정 - 평균 검정
  • 종속표본 T검정 - 한 집단의 두 개의 평균 비교 (데이터 개수가 같아야 함)
  • 독립표본 T검정 - 서로 다른 집단의 평균 비교

 

7-1) 단일 표본 T검정

- 정규 분포의 표본에 대해서 기대값(평균)을 조사하는 검정 방법

- scipy.stats.ttest_1samp(a, popmean, alternative)

  • a: 표본 데이터 배열
  • popmean: 귀무가설의 기댓값

- 귀무가설: 데이터의 평균이 popmean이다.

   p-value: 이 데이터의 평균이 popmean일 확률

- 데이터 개수가 많은 영향을 미친다. 

  • 데이터의 개수가 작으면 제1종 오류 발생 가능성이 높다.

- 양측검정

N = 10
mu_0 = 0
np.random.seed(0)

#정규 분포를 따르고 평균이 0인 샘플 데이터 10개 추출
x = sp.stats.norm(mu_0).rvs(N)

p = sp.stats.ttest_1samp(x, popmean=0)
print(p) #유의확률이 0.0478 
#유의수준이 5% 라면 귀무가설이 기각
#유형 1의 오류 - 귀무가설이 맞는데 귀무가설이 기각되는 경우 - 샘플 데이터가 적어서
TtestResult(statistic=2.28943967238967, pvalue=0.04781846490857058, df=9)
N = 100
mu_0 = 0
np.random.seed(0)

#정규 분포를 따르고 평균이 0인 샘플 데이터 100개 추출
x = sp.stats.norm(mu_0).rvs(N)

p = sp.stats.ttest_1samp(x, popmean=0)
print(p) #유의확률이 0.0478 
#유의수준이 5% 라면 귀무가설이 기각
#유형 1의 오류 - 귀무가설이 맞는데 귀무가설이 기각되는 경우 - 샘플 데이터가 적어서
TtestResult(statistic=0.5904283402851698, pvalue=0.5562489158694675, df=99)

 

# 평균 검정과 신뢰구간 구하기

#아래와 같은 데이터가 있을 때 펴균이 50이라고 할 수 있는지 유의수준 0.05에서 검정
data = np.array([49, 52, 45, 53, 49, 50, 55, 43, 44, 48])

stat,pv = stats.ttest_1samp(data,50)
print(f"양측 검정으로 p-value는 {pv:.3f}")

if pv>0.05:
  print("귀무 가설을 기각할 수 없다. 따라서 광고는 유의하다. ")
  print("과자 A에는 50개의 알사탕이 들어있다는 것은 타당하다.")
else:
  print("귀무 가설을 기각한다. 따라서 광고는 유의하지 않다.")
  print("과자 A에는 50개의 알사탕이 들어있다는 것은 타당하지 않다.")
 
#신뢰 구간을 구하기 위해서 t 분포를 생성
t_ = sp.stats.t(len(data) - 1)

#0.975 되는 지점의 좌표를 찾기 - 양쪽 검정이므로
p_05 = t_.ppf(0.975)

data_mean = data.mean()
data_std = data.std(ddof = 1)
#하한 과 상한 구하기
lp = data_mean - p_05 * (data_std / np.sqrt(len(data)))
hp = data_mean + p_05 * (data_std / np.sqrt(len(data)))
print(f'유의 수준 0.05 일 때 신뢰 구간: [{lp}, {hp}]')
유의 수준 0.05 일 때 신뢰 구간: [45.98262777665255, 51.617372223347445]

 

 

- 단측 검정

  • 데이터를 측정한 결과: 74, 73, 75, 73, 75, 74, 73, 73, 74, 72
  • 신뢰구간 95%(유의수준 0.05)에서 홍길동의 몸무게가 73보다 작다고 할 수 있는지 검정
  • alternative 인자를 ‘less’ 혹은 ‘greater’로 전달하면 단측 검정을 할 수 있음
#데이터를 측정한 결과: 
data = np.array([74, 73, 75, 73, 75, 74, 73, 73, 74, 72])
#신뢰구간 95%(유의수준 - 5%)에서 평균이 73보다 작다고 할 수 있는지 검정?
mean = 73
_, p = sp.stats.ttest_1samp(data, mean, alternative='less')
print(p)
if p > 0.05:
    print("귀무가설을 기각할 수 없다. 평균은 73보다 크거나 같다.")
else:
    print("귀무가설을 기각할 수 있다. 평균은 73보다 작다.")
0.040563094422921574
귀무가설을 기각할 수 있다. 평균은 73보다 작다.

 

 

 

7-2) 대응(종속) 표본 t 검정

- 동일한 표본에 대해서 관측된 결과의 기댓값이 같은지 검정

  • 실제로는 기대값이 다르다는 것을 확인하기 위해서 수행

- 예시: 

  • 어떤 학생들이 특강을 수강하기 전과 수강한 이후에 대해서 시험을 치렀을 때 학생들의 점수 변화가 있었는지 
  • 어떤 약을 복용하기 전과 후가 달라졌는지 확인
#귀무 가설: 2집단의 평균이 같다
#대립 가설: 2집단의 평균이 다르다

x1 = np.array([0.7, -1.6, 0.2])
x2 = np.array([1.2, -1.1, 0.2])

_, pvalue = sp.stats.ttest_rel(x1, x2)

if pvalue > 0.05:
    print("귀무 가설을 기각할 수 없다. 두 집단의 평균은 같다")
else:
    print("귀무 가설을 기각할 수 있다. 두 집단의 평균은 다르다")
귀무 가설을 기각할 수 없다. 두 집단의 평균은 같다

 

#당뇨 약 복용 전과 복용 후 검정

  • 동일한 환자에 대한 측정치라고 가정: 대응표본t검정
  • 변화가 있는지 알아보는 검정 - 양측 검정
  • 유의 수준 5%
#데이터
data_a = np.array([440, 90, 120, 220, 230, 320, 450, 180])
data_b = np.array([220, 80, 100, 110, 180, 250, 350, 170])

#양측 검정 수행
_, p = sp.stats.ttest_rel(data_a, data_b, alternative = "two-sided")
print(p)
if p > 0.05:
    print("귀무 가설을 기각할 수 없다, 2개 데이터 집단의 평균은 같지 않다라고 할 수 없다.")
else:
    print("귀무 가설을 기각할 수 있다, 2개 데이터 집단의 평균은 같지 않다라고 할 수 있다.")
0.02139085535260364
귀무 가설을 기각할 수 있다, 2개 데이터 집단의 평균은 같지 않다라고 할 수 있다.
#단측 검정 수행
#단측 검정을 수행할 때는 앞의 데이터를 기준으로 판정
#앞의 데이터가 더 커야 하면 greater 더 작아야 하면 less
_, p = sp.stats.ttest_rel(data_a, data_b, alternative = "greater")
#단측 검정의 p-value 값이 양측 검정의 p-value/2 입니다.
print(p)
if p > 0.05:
    print("귀무 가설을 기각할 수 없다, 첫번째 데이터가 더 크지 않다.");
else:
    print("귀무 가설을 기각할 수 있다, 첫번째 데이터의 평균이 더 크다라고 할 수 있다.")
nan
귀무 가설을 기각할 수 있다, 첫번째 데이터의 평균이 더 크다라고 할 수 있다.

 

 

7-3) 독립 표본 t검정

- 두 개의 독립적인 정규 분포에서 나온 두 개의 데이터 셋을 사용해서 두 정규 분포의 기대값(평균)이 동일한지를 검사

- scipy.stats.ttest_ind(a, b, axis=0, equal_var=True, alternative='two-sided'..)

  • equal_var 옵션: 2개 데이터의 분산이 같은지 설정

- 이 경우에도 샘플 데이터의 개수가 너무 적으면 제2종 오류 (실제 효과를 우연에 의한 효과라고 잘못 결론 내리는 것 - 대립 가설을 채택해야 하는데 귀무가설을 채택해야 하는 경우) 발생

np.random.seed(0)

#첫번째 데이터
N_1 = 10
mu_1 = 0
sigma_1 = 1

#두번째 데이터
N_2 = 10
mu_2 = 0.5
sigma_2 = 1

#2개 데이터 집단의 평균은 0 과 0.5 로 다름
x1 = sp.stats.norm(mu_1, sigma_1).rvs(N_1)
x2 = sp.stats.norm(mu_2, sigma_2).rvs(N_2)

#그래프로 출력
ax = sns.distplot(x1, kde=False, fit=sp.stats.norm, label="1번 데이터 집합")
ax = sns.distplot(x2, kde=False, fit=sp.stats.norm, label="2번 데이터 집합")

ax.lines[0].set_linestyle(":")
plt.legend()
plt.show()

print(sp.stats.ttest_ind(x1, x2, equal_var=False))

 

- 샘플의 개수가 적으면 제2종 오류 발생 가능성이 높아짐

 

 

7-4) 등분산 검정

- 독립표본t검정에서는 기대값이 같은지 확인을 하고자 하는 경우 분산의 값이 다른지 같은지를 알아야 한다.

- 등분산 검정에는 sp.stats 패키지  bartlett, fligner, levene 검정 함수 제공

  • 2개 데이터의 집단만 대입하면 p-value 값을 얻어낼 수 있다.
#첫번째 데이터 - 분산이 1
N_1 = 100
mu_1 = 0
sigma_1 = 1

#두번째 데이터 - 분산이 1.5
N_2 = 100
mu_2 = 0
sigma_2 = 1.5

np.random.seed(0)

#평균은 같은데 분산이 다른 데이터를 생성
x1 = sp.stats.norm(mu_1, sigma_1).rvs(N_1)
x2 = sp.stats.norm(mu_2, sigma_2).rvs(N_2)

_, p = sp.stats.bartlett(x1, x2)
#유의 확률이 낮게 나오기 때문에 2개 데이터의 분산이 다르다
print(p)
2.4364708986190797e-05

 

 

 

8. 윌콕슨의 분호 순위 검정

- 대응 표본(종속표본 - 동일한표본) 차이에 정규분포를 적용할 수 없는 경우 사용하는 중앙값의 차이에 대한 검정

- 차이의 절대값을 이용해서 순위 부여

  • 부여된 순위에 평균의 차이를 +와 -부호를 부여해서 곱한 후 얻어진 2개의 값의 차이를 가지고 판정
  • 차이가 있는 경우에는 순위의 편차가 심할 것이고 그렇지 않은 경우는 순위의 편차가 별로 없음

- 차이에 편향이 있을수록 순위에도 편향이 생기고, 검정 통계량은 작은 값이 돼서 중앙값에 차이가 있다는 주장을 할 수 있음

- 알고리즘을 직접 구현해도 되고  scipy.stats.wilcoxon 함수 제공

  • 함수를 이용하게 되면 중간에 표준화를 수행하고 정규분포로 검정을 하기 때문에 직접 구현하는 경우와 값은 다르게 나옴

 

8-1) 알고리즘 직접 구현

#데이터 가져오기 - 전 후의 차이가 별로 없는 경우
training_rel = pd.read_csv('./data/training_rel.csv')
#print(training_rel.head())

#알고리즘을 이해하기 위해서 데이터의 개수를 줄임
toy_df = training_rel[:6].copy()
#print(toy_df)

#데이터의 차이를 계산
diff = toy_df['후'] - toy_df['전']
toy_df['차'] = diff
#print(toy_df)

#차이의 절대값 순위를 계산
#차이가 적은 데이터의 순위가 1 차이가 많은 데이터의 순위가 뒤로 갑니다.
rank = sp.stats.rankdata(abs(diff)).astype(int)
toy_df['순위'] = rank
print(toy_df)

#차이가 음수인 데이터의 순위 합 과 차이가 양수인 데이터의 순위합을 구함
r_minus = np.sum((diff < 0) * rank)
r_plus = np.sum((diff > 0) * rank)
#낮은 쪽이 검정 통계량
print(r_minus, r_plus)
#이 값이 임계값보다 작은 경우에 귀무 가설이 기각되는 단측 검정
print("검정 통계량:", r_minus)
    전   후   차  순위
0  59  41 -18   5
1  52  63  11   3
2  55  68  13   4
3  61  59  -2   1
4  59  84  25   6
5  45  37  -8   2
8 13
검정 통계량: 8
#데이터 가져오기 - 전 후의 차이가 있는 경우
toy_df['후'] = toy_df['전'] +  np.arange(1, 7)

#데이터의 차이를 계산
diff = toy_df['후'] - toy_df['전']
toy_df['차'] = diff
#print(toy_df)

#차이의 절대값 순위를 계산
#차이가 적은 데이터의 순위가 1 차이가 많은 데이터의 순위가 뒤로 갑니다.
rank = sp.stats.rankdata(abs(diff)).astype(int)
toy_df['순위'] = rank
print(toy_df)

#차이가 음수인 데이터의 순위 합 과 차이가 양수인 데이터의 순위합을 구함
r_minus = np.sum((diff < 0) * rank)
r_plus = np.sum((diff > 0) * rank)
#이번에는 고의적으로 데이터 값의 차이를 1, 2, 3, 4, 5, 6 으로 만들어서 순위를 구함
#이렇게 차이가 많이 나면서 증가하거나 감소하면 t 검정을 사용하기가 어려움
#t 검정은 데이터의 분포가 정규분포라는 가정이 있어합니다.
#이 데이터는 각 데이터들의 차이가 서로 완전히 다르기 때문에 정규 분포라고 가정하기가 어려움
#이런 경우 차이의 순위를 이용해서 일정한 순위 형태로 늘어났는지 확인할 수 있는 검정이
#윌콕슨의 부호 순위 검정
#낮은 쪽이 검정 통계량
print(r_minus, r_plus)
#이 값이 임계값보다 작은 경우에 귀무 가설이 기각되는 단측 검정
print("검정 통계량:", r_minus)
    전   후  차  순위
0  59  60  1   1
1  52  54  2   2
2  55  58  3   3
3  61  65  4   4
4  59  64  5   5
5  45  51  6   6
0 21
검정 통계량: 0
#데이터 가져오기 - 전 후의 차이가 랜덤한 경우
#순위가 낮아진 것도 높아진 것도 있기 때문에 줄어든 데이터의 순위 와 늘어난 데이터의 순위
#합의 차이가 얼마 안남
#순위의 변화 합을 계산하면 전체적으로 늘어나거나 줄어들었는지 아니면
#랜덤한 변화를 가져왔는지 알 수 있습니다.
toy_df['후'] = toy_df['전'] +  [1, -2, -3, 4, 5, -6]

#데이터의 차이를 계산
diff = toy_df['후'] - toy_df['전']
toy_df['차'] = diff
#print(toy_df)

#차이의 절대값 순위를 계산
#차이가 적은 데이터의 순위가 1 차이가 많은 데이터의 순위가 뒤로 갑니다.
rank = sp.stats.rankdata(abs(diff)).astype(int)
toy_df['순위'] = rank
print(toy_df)

#차이가 음수인 데이터의 순위 합 과 차이가 양수인 데이터의 순위합을 구함
r_minus = np.sum((diff < 0) * rank)
r_plus = np.sum((diff > 0) * rank)
#이번에는 고의적으로 데이터 값의 차이를 1, 2, 3, 4, 5, 6 으로 만들어서 순위를 구함
#이렇게 차이가 많이 나면서 증가하거나 감소하면 t 검정을 사용하기가 어려움
#t 검정은 데이터의 분포가 정규분포라는 가정이 있어합니다.
#이 데이터는 각 데이터들의 차이가 서로 완전히 다르기 때문에 정규 분포라고 가정하기가 어려움
#이런 경우 차이의 순위를 이용해서 일정한 순위 형태로 늘어났는지 확인할 수 있는 검정이
#윌콕슨의 부호 순위 검정
#낮은 쪽이 검정 통계량
print(r_minus, r_plus)
#이 값이 임계값보다 작은 경우에 귀무 가설이 기각되는 단측 검정
print("검정 통계량:", r_minus)
    전   후  차  순위
0  59  60  1   1
1  52  50 -2   2
2  55  52 -3   3
3  61  65  4   4
4  59  64  5   5
5  45  39 -6   6
11 10
검정 통계량: 11

 

 

8-2) 제공되는 API 활용

_, p = sp.stats.wilcoxon(training_rel['전'], training_rel['후'])
print(p)
0.03623390197753906
  • 변화가 없다가 귀무 가설
  • 이 경우는 유의 수준을 0.05 로 잡으면 귀무가설을 기각할 수 있고 0.01로 잡으면 귀무 가설을 기각할 수 없음

 

 

9. Mann-Whitney Rank Test

- 대응되는 표본이 아닌 독립 표본인 경우 정규 분포를 가정할 수 없는 경우 중앙값의 차이에 대한 검정

- 정규 분포가 아니라면 평균이나 분산을 가정할 수 없어서 평균을 사용하지 못하고 중앙값을 사용

- 윌콕슨의 부호 순위 합 검정은 대응되는 표본이라서 각 열의 차이를 가지고 순위를 구하고 순위의 변화량을 측정하는데 전체 데이터를 가지고 순위를 구한 후 각 열의 순위 합을 가지고 검정

 

9-1) 실습

#열의 각 데이터가 동일한 표본이 아닌 경우
training_rel = pd.read_csv('./data/training_ind.csv')
toy_df = training_rel[:6].copy()
#print(toy_df)
#두 열의 데이터를 합산해서 순위를 구하는 구조
rank = sp.stats.rankdata(np.concatenate([toy_df["A"], toy_df["B"]]))
rank_df = pd.DataFrame({"A":rank[:5], "B":rank[5:10]}).astype(int)
print(rank_df)
    A   B
0   4   3
1   7   6
2   1   9
3  12  11
4   2   5
  • 데이터가 일정한 변화를 가져온다면 A 나 B에 좋은 순위가 모여있을 가능성이 높음
  • 데이터의 편차가 아주 크지 않다면 이 이론은 성립
  • 한쪽에 좋은 순위나 나쁜 순위가 몰려있으면 데이터에 편향이 발생한 것이므로 유의미한
  • 차이가 있다라고 판정하는 방식

 

 

 

10. 분산 분석

- 여러 글룹의 수치 데이터를 비교해서 여러 그룹 간의 통계적으로 유의미한 차이를 검정하는 통계적 절차가 분산 분석 또는 ANOVA

- 3가지 조건이 필요

  • 정규성: 각각의 그룹에서 변인은 정규 분포
  • 분산의 동질성: 모집단의 분산은 각각의 모집단에서 동일
  • 관찰의 독립성: 각각의 모집단에서 크기가 각각인 표본들이 독립적으로 표집

- 용어

  • 쌍별 비교: 여러 그룹 중에서 두 그룹 간의 가설 검정
  • 총괄 검정: 여러 그룹 평균들의 전체 분산에 대한 단일 가설 검정
  • 분산 분해: 구성 요소를 분리하는 것으로 전체 평균, 처리 평균, 잔차 오차로부터 개별 값들에 대한 기여
  • F 통계량: 그룹 평균 간의 차이가 랜덤 모델에서 예상하는 것보다 벗어나는 정도를 측정하는 표준적인 통계량
  • SS(Sum of Squares): 어떤 평균으로부터의 편차들의 제곱합

 

10-1) 분산의 중요성

- 어떤 그룹 사이에 평균이 같다고 해서 2 그룹 사이에 연관성이 있다고 말하기는 어려움

  • 분산이 다르면 분포는 완전히 달라지기 때문
  • 그룹 과 그룹을 비교할 때는 평균 과 분산을 모두 확인해야 함
#중앙점의 좌표 - 평균
centers = [5, 5.3, 4.7]

#표준 편차 - 분산의 제곱근
#0.1 과 2.0으로 변경했을 때 분포가 아예 달라집니다.
#0.1 인 경우 2개의 분포가 상단에 배치되고 1개의 분포가 하단에 배치됩니다.
#2.0 인 경우 3개의 분포가 동일선상에 배치가 됩니다.

#std = 0.1
std = 2.0

colors = 'brg'

data_1 = []
for i in range(3):
    data_1.append(sp.stats.norm(centers[i], std).rvs(100))
    plt.plot(np.arange(len(data_1[i])) + i * len(data_1[0]), data_1[i], '.', color=colors[i])
  • 분산이 클수록 집단의 평균 값의 차이는 무의미
  • 집단 평균 값의 분산이 클수록 집단 내 분산이 작아질수록 평균의 차이가 분명해짐

- 집단 간 분산 과 집단 내 분산 이 두 가지를 이용해서 분석을 하는 것이 분산 분석 - 가장 기본적인 클러스터링(그룹화 - 집단 내의 분산은 작게 집단 과 집단의 분산은 크게)을 할 때 원리

- 분산 분석에서는 일원 분산 분석(종속 변인이 1개이고 독립 변인의 수도 1개) 과 이원 분산 분석(독립 변인의 수가 2개 이상)으로 나눔

 

 

10-2) 일원 분산 분석

- 종속 변인이 1개이고 독립 변인의 집단도 1개인 경우

- 한 가지 변수의 변화(독립 변인)가 결과 변수(종속 변인)에 미치는 영향을 알아보기 위해서 수행

- python에서는 One-Way ANOVA 분석은 scipy.stats 나 statsmodel 라이브러리를 이용

  • statsmodel를 이용하는 것이 더 많은 분석 정보를 얻어낼 수 있음

- Altman 910 데이터

  • 22명의 심장 우회 수술을 받은 환자를 3개의 그룹으로 분류해서 시술한 결과
    • 그룹1 - 50% 아산화 질소 와 50%의 산소 혼합물을 24시간 동안 흡입
    • 그룹2 - 50% 아산화 질소 와 50%의 산소 혼합물을 수술받는 동안만 흡입
    • 그룹3 - 35% ~ 50%의 산소만 24시간 동안 흡입
  • 적혈구의 엽산 수치를 24시간 이후에 측정
  • 적혈구의 엽산 수치가 종속 변인이 되고 3개의 그룹으로 나눈 변인이 독립 변인
  • 독립 변인에 따른 종속 변인의 평균에 차이가 있는 지 검정
    • p-value 가 유의 수준보다 낮으면 평균에 차이가 있는 것이고 그렇지 않으면 평균에 차이가 없다라고 판정

- scipy.stats.f_oneway(그룹별 데이터 설정) 와 statsmodels.formula.api 를 이용(R 의 형태를 갖는 API 

  • 여러 개의 컬럼을 설정할 때 python 은 컬럼 이름의 list를 이용하지만 R은 종속 변인 과 독립 변인을 설정할 때 ~ 로 구분해서 설정
#Altman 910 데이터 가져오기

#텍스트 파일의 내용을 가지고 numpy 의 ndarray 생성
#대부분의 경우 pandas를 이용해서 데이터를 읽어오고 ndarray 로 변환하지만
#텍스트 데이터(txt, tsv, csv )의 경우는 데이터를 가지고 직접 ndarray 생성 가능

import urllib.request
url = 'https://raw.githubusercontent.com/thomas-haslwanter/statsintro_python/master/ipynb/Data/data_altman/altman_910.txt'
data = np.genfromtxt(urllib.request.urlopen(url), delimiter=',')
#print(data)

#첫번째 열이 측정치이고 두번째 열이 그룹

#데이터를 그룹 별로 분할
group1 = data[data[:, 1] == 1, 0]
group2 = data[data[:, 1] == 2, 0]
group3 = data[data[:, 1] == 3, 0]
#print(group1)

#시각화를 통해서 데이터의 분포를 확인
#중앙값의 위치나 박스 와 수염의 크기가 거의 비슷하다면 데이터에 차이가 없다라고 판단
plot_data = [group1, group2, group3]
plt.boxplot(plot_data)
plt.show()

 

# sp.stats.f_oneway API

_, p_value = sp.stats.f_oneway(group1, group2, group3)
print("유의 확률:", p_value)

#유의 수준을 5%
if p_value > 0.05:
    print("유의 수준이 유의 확률보다 크기때문에 이 데이터들은 평균 값이 유의미하게 차이가 나지 않음")
else:
    print("유의 수준이 유의 확률보다 작거나 같기때문에 이 데이터들은 평균 값이 유의미하게 차이가 납니다.")
    
print(sp.stats.f_oneway(group1, group2, group3))
유의 확률: 0.043589334959178244
유의 수준이 유의 확률보다 작거나 같기때문에 이 데이터들은 평균 값이 유의미하게 차이가 납니다.
F_onewayResult(statistic=3.7113359882669763, pvalue=0.043589334959178244)

 

# statsmodels API 활용

from statsmodels.formula.api import ols

#기존 배열을 가지고 DataFrame을 생성
df = pd.DataFrame(data, columns=['value', 'treatment'])

#모델을 생성
model = ols('value ~ C(treatment)', df).fit()
#print(model)

#훈련을 수행하고 결과를 확인
import statsmodels.api as sm
print(sm.stats.anova_lm(model)) #가장 마지막 열의 첫번째 데이터가 p-value
print(type(sm.stats.anova_lm(model)))
                df        sum_sq      mean_sq         F    PR(>F)
C(treatment)   2.0  15515.766414  7757.883207  3.711336  0.043589
Residual      19.0  39716.097222  2090.320906       NaN       NaN
<class 'pandas.core.frame.DataFrame'>

 

 

10-3) 이원 분산 분석

- 독립 변인의 수가 2개 이상일 때 집단 간 차이가 유의한지를 검증

- Interaction Effect(상호 작용 효과)를 확인하기 위해 사용

  • 한 변인의 변화가 결과에 미치는 영향이 다른 변인의 수준에 따라 달라지는가 하는 것을 확인

- altman_12_6

  • 태아의 머리 둘레 측정 데이터
  • 4명의 관측자가 3명의 태아를 대상으로 측정
inFile = 'altman_12_6.txt'
url_base = 'https://raw.githubusercontent.com/thomas-haslwanter/statsintro_python/master/ipynb/Data/data_altman/'
url = url_base + inFile

#첫번째 열이 머리 둘레 크기 - 종속 변인
#두번째 열이 태아를 구별하기 위한 번호 - 독립 변인
#세번째 열이 관측자를 구별하기 위한 번호 - 독립 변인
data = np.genfromtxt(urllib.request.urlopen(url), delimiter=",")
#print(data)

#이 경우는 이원 분산 분석을 수행
df = pd.DataFrame(data, columns=['head_size', 'fetus', 'observer'])
model = ols('head_size ~ C(fetus) + C(observer) + C(fetus):C(observer)', df).fit()
#print(model)

#훈련을 수행하고 결과를 확인
import statsmodels.api as sm
print(sm.stats.anova_lm(model)) #가장 마지막 열의 첫번째 데이터가 p-value
print(type(sm.stats.anova_lm(model)))
                        df      sum_sq     mean_sq            F        PR(>F)
C(fetus)               2.0  324.008889  162.004444  2113.101449  1.051039e-27
C(observer)            3.0    1.198611    0.399537     5.211353  6.497055e-03
C(fetus):C(observer)   6.0    0.562222    0.093704     1.222222  3.295509e-01
Residual              24.0    1.840000    0.076667          NaN           NaN
<class 'pandas.core.frame.DataFrame'>

 

 

 

10-4) 웹 점착성

- 웹 전환율

  • 현재 페이지에서 다른 페이지로 이동하는 비율
  • 상품 목록 페이지에서 상품 상세 보기 페이지로 이동하는 것 또는 상품 상세 보기 페이지에서 상품 결재 페이지로 이동하는 것 등의 비율
  • 포탈의 광고 또는 SNS 광고를 통해서 들어오는 고객의 비율

- 웹 점착성

  • 방문자가 페이지에서 보낸 시간
  • 여러 명의 유저가 여러 페이지에서 보낸 시간을 가진 데이터를 가지고 분산 분석을 수행
  • 이 데이터가 앞의 데이터 와 다른 점은 페이지를 선택하는 권리가 유저에게 있다는 것
    •  모든 유저가 모든 페이지에 방문하는 것은 아니며 그 확률은 매우 낮음
  • 부트스트래핑 이나 순열 검정(재표본 추출) 같은 방식을 이용
    • 모든 데이터를 한 상자에 모아서 페이지 개수 별로 각 페이지의 개수만큼 추출
    • 각 그룹의 평균을 기록한 후 각 그룹의 평균 사이의 분산을 기록
    • 이 작업을 여러 번 반복한 후 이 분산이 관찰된 변화가 p 값
#웹 페이지에 머무르는 시간을 웹 점착성이라고 합니다.
#4개의 페이지에 머무르는 시간이 기록된 데이터를 가져오기
four_sessions = pd.read_csv('./data/four_sessions.csv')
#print(four_sessions.head())

#그룹 별 Time 데이터의 평균의 분산을 구합니다.
observed_variance = four_sessions.groupby('Page').mean().var()[0]
print("그룹 별 Time 의 평균에 대한 분산:", observed_variance)

#랜덤하게 섞어서 그룹 별로 TIme에 대한 평균의 분산을 구해주는 함수
def perm_test(df):
    df = df.copy()
    df['Time'] = np.random.permutation(df['Time'].values)
    return df.groupby('Page').mean().var()[0]

#print(perm_test(four_sessions))

np.random.seed(1)
#3000번을 수행해서 observed_variance 보다 분산이 높은 비율을 계산
perm_variance = [perm_test(four_sessions) for _ in range(3000)]
#이 비율의 값이 p-value
#유의 수준을 정해서 분산이 의미가 있는지 판정
#유의 수준 보다 p-value 값이 작다면 분산의 차이가 의미가 있습니다.
#점착성에 차이가 있는 것
#2개의 동일한 콘텐츠를 가진 페이지로 이 작업을 수행하게 되면 A/B 테스트가 됩니다.
print("p-value:", np.mean([var > observed_variance for var in perm_variance]))
그룹 별 Time 의 평균에 대한 분산: 55.426666666666655
p-value: 0.08433333333333333

 

 

 

11. 카이 제곱 검정

- 범주 별로 관측빈도와 기대빈도의 차이를 통해서 확률 모형이 데이터를 얼마나 잘 설명하는지 검정하는 통계 방법

- 관측된 데이터를 대상으로 유의 확률을 적용하여 적합도 검정이나 변수 간의 독립성 여부를 검정하거나 동질성 검정을 수행할 수 있다.

  • 적합도 검정: 표본이 기대에 적합한지 검정
  • 독립성 검정: 두 항목이 서로 독립인지 검정
  • 동질성 검정: 두 집단의 데이터 분포가 같은지 검정

 - 웹 테스트 시 종종 단순한 A/B 검정을 넘어서 동시에 여러가지 처리를 한 번에 테스트할 필요가 있을 때 카이제곱검정 사용

  • 리스트 페이지에 3개의 페이지에 대한 링크가 존재할 때 1000명의 유저가 방문하면
  A 페이지        B페이지  C페이지
클릭 14 8 12
클릭하지 않은 경우 986 992 998
  • 전환율이 실제로 굉장히 낮기 때문에 단순 비교는 어렵다. => 재표본 추출(순열검정, 부트스트랩)을 이용해서 클릭률이 우연히 발생할 수 있는 것보다 유의미한 정도로 큰 차이인지 검정
  • 귀무가설: 모두가 동일한 클릭률을 갖는다.
  • 전체 클릭률은 34/3000, 계산해서 1.133%이며 이렇게 되는 경우 아래와 같이 통계 가정
  A 페이지        B페이지  C페이지
클릭 11.3 11.33 11.33
클릭하지 않은 경우 988.67 988.67 988.67
  • 피어슨 잔차 계산: (실제 관측값 - 기대값) / 기대값의 제곱근
  • 카이제곱 통계량 계산: 피어슨 잔차들의 제곱합

r=행, c=열

  • 카이제곱 통계량이 귀무가설로부터 얻을 수 있는 값보다 클까?
    => 재표본 알고리즘을 통해 검정
    • 34개의 1과 2966개의 0이 들어있는 배열 생성
    • 상자의 내용물을 랜덤하게 1000개씩 추출한 후 1의 개수 계산
    • 이렇게 얻은 횟수와 기대한 횟수의 차이를 제곱해서 합산 (=카이제곱통계량계산)
    • 이 과정을 1000번 반복
    • 처음 계산한 카이제곱통계량보다 몇번이나 초과되었는지 확인 (p-value)

- 카이제곱 검정을 수행하는 함수나 메소드를 만들어야 한다면 수식을 기억해야하고, 그렇지 않으면 API 함수를 사용

- scipy.stats.chi2_contingency()

  • 리턴 값은 statistic(카이제곱통계량), pvalue(제일 중요) 등

# 웹 전환율 차이에 대한 카이 제곱 검정

  • 유의 수준 5%로 결정
click_rate = pd.read_csv('./python_statistics-main/data/click_rates.csv')
#피봇
clicks = click_rate.pivot(index='Click', columns='Headline', values='Rate')
print(clicks)
Headline  Headline A  Headline B  Headline C
Click                                       
Click             14           8          12
No-click         986         992         988
row_average = clicks.mean(axis=1)
pd.DataFrame({
    'Headline A': row_average,
    'Headline B': row_average,
    'Headline C': row_average,
})
result = sp.stats.chi2_contingency(clicks)
print(result)
Chi2ContingencyResult(
statistic=1.6659394708658917,
pvalue=0.4347562562343731,
dof=2,
expected_freq=array([[ 11.33333333,  11.33333333,  11.33333333], [988.66666667, 988.66666667, 988.66666667]]))
print(dir(result))
['__add__', '__class__', '__class_getitem__', '__contains__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__getnewargs_ex__', '__getstate__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__module__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '_asdict', '_extra_fields', '_fields', 'count', 'dof', 'expected_freq', 'index', 'pvalue', 'statistic']
  • p-value가 있는데, 속성인지 메소드인지 알아보려면
print(help(result.pvalue))
class float64(floating, builtins.float)
...
  • 값이군
if result.pvalue > 0.05:
    print("귀무가실 기각 불가")
else:
    print("귀무가실 기각")
귀무가실 기각 불가
  • 전환율의 차이가 없다.
그래서 UI에서 유의미한 결과 개선을 만드는게 굉장히 어려운 작업이다.

 

 

11-1) 일원 카이 제곱 검정

- 한개의 변인을 대상으로 검정을 수행하는 것으로 적합도 검정이나 선호도 분석에 이용 가능

- scipy.stats.chisquare(f_obs, f_exp=None)

  • f_obs : 데이터 행렬 (거짓에 관련된 데이터)
  • f_exp : 기댓값 행렬

# 예시: 5가지 음료에 대한 선호도 조사: 응답 -> [41,30,51,71,61]

  • 음료 선택에 선호도가 같은지 다른지 확인
  • 유의수준 1%
data = np.array([41,30,51,71,61])
result = sp.stats.chisquare(data)

if result.pvalue > 0.01:
    print("귀무가설 기각 불가")
else:
    print("귀무가설 기각")
귀무가설 기각
  • 선호도에 차이가 있다.
  • 모든 사람이 5가지 음료 중 하나를 선택한 경우에 대한 검정이었고, 음료를 선택하지 않은 사람도 있는 경우에는 두번째 매개변수로 선택하지 않은 사람의 인원수를 대입해야 함

 

11-2) 이원 카이 제곱 검정

- 카이 제곱 독립 검정

  • 2개의 변인에 대해서 서로 독립적인지 여부 판정
  • 귀무가설: 2개의 변인은 독립적이다.
  • p-value를 반대로 판정하면 동질성 검정이 됨

- scipy.stats.chi2_contingency(2차원 배열의 행렬이나 각 배열의 list) 

objs = np.array([np.array([2, 4]), np.array([3, 6])])
result = sp.stats.chi2_contingency(objs)
print(result)
Chi2ContingencyResult(
statistic=0.0,
pvalue=1.0,
dof=1,
expected_freq=array([[2., 4.], [3., 6.]]))
  • 귀무가설 기각 불가

 

3) 피셔의 정확 검정

- 카이제곱분포는 재표본 검정의 좋은 근사치 제공

- 대부분 통계 소프트웨어는 발생할 수 있는 모든 조합을 열거하고 빈도를 집계하고 관찰된 결과가 얼마나 극단적으로 발생할 수 있는지를 나타내는 p값을 구해서 리턴한다. (=피셔의 정확검정)

- 웹 전환율 테스트나 출판을 위해 통계적으로 유의미한 p값을 찾는 논문 연구에서 널리 사용

 

 

12. Z 검정

- 모집단의 평균과 표준편차가 얼마인지 알려졌을 때, 새롭게 조사된 표본의 평균이 모집단의 평균과 같은지 추정

- 표본의 크기는 30보다 커야 하고 모집단에서 균일한 확률로 선택되어야 함

  • 표본의 크기가 작다면 t검정 수행

- 제약조건

  • 종속변수가 양적 변수
  • 모집단의 평균과 표준편차를 알아야 함
  • 모집단의 분포가 정규분포여야 함: 정규성 검정 수행
  • 두집단을 비교할 경우 두집단의 분산이 같아야 함: 등분산 검정 먼저 수행

- 귀무 가설:모집단의 평균 = 표본의 평균

 

 

 

12-1) Z 검정 통계량

(표본평균 - 모평균) / (모표준편차/표본의개수의 제곱근)

- 표본의 평균이 모평균과 모표준편차에 의해 그려지는 정규분포에서 위치 값

 

12-2) 단일 표본 z 검정

 - 분산의 값을 정확히 알고 있는 정규 분포의 표본에 대해서 기댓값을 조사하는 방법

- 이 API는 존재하지 않아서 직접 구현해서 사용

# 평균이 0이고 표준편차가 1인 정규분포에서 10개의 데이터 추출
N = 10
mu_0 = 0
np.random.seed(0)
x = sp.stats.norm(mu_0).rvs(N)
print(x)
[ 1.76405235  0.40015721  0.97873798  2.2408932   1.86755799 -0.97727788
  0.95008842 -0.15135721 -0.10321885  0.4105985 ]
# z 통계량과 유의확률 리턴해주는 함수
ef ztest_1samp(x, sigma2=1, mu=0):
    z = (x.mean() - mu) / np.sqrt(sigma2/len(x))
    return z, 2 * sp.stats.norm().sf(np.abs(z))

ztest_1samp(x)
(2.3338341854824276, 0.019604406021683538)
  • pvalue가 0.019
  • N의 크기를 100 이상으로 늘려보면 p-value가 0.1 이상으로 나옴

 

12-3) 비율 검정

- 두 집단을 대상으로 비율 차이 검정을 수행해서 두 집단의 비율이 같은지  다른지 검정

- statsmodels.stats.proportion 패키지 proportions_ztest 함수 이용

from statsmodels.stats.proportion import proportions_ztest

r= proportions_ztest(count=135, nobs=150, value=110/150)
print(r)

if(r[1] >= 0.05):
    print("유의 확룰이 0.5보다 크므로 효과가 없다.")
else:
    print("유의 확룰이 0.5보다 작으므로 효과가 있다.")
(6.80413817439772, 1.0165593635824276e-11)
유의 확룰이 0.5보다 작으므로 효과가 있다.
  • scipy는 항상 앞에 이름이 있었는데, 지금은 이름이 없다.이건 완전한 튜플이다.
  • 유의확률을 꺼내는 방법은 r[1]
  • 혹은 처음부터 r 대신 staticvalue와 pvalue 값을 나눠서 받는다.

 

 

 

13. 정규성 검정

- 데이터의 분포가 가우시안 정규 분포를 따르는지 확인하는 검정

- 매우 중요한 작업이라서 다양한 API 제공

  • 골모고로프-스미르노프 검정: scipy.stats.ke_2samp
  • 샤피로 윌크 검정: scipy.stats.shapiro
  • 앤더스 달링 검정: scipy.stats.anderson
  • 다고스티노 K-제곱 검정: scipy.stats.mstats.normaltest

- statsmodel 에서도 제공

  • statsmodels.stats.diagnostic.kstest_normal
  • 기타 등
np.random.seed(0)

N = 100
x1 = sp.stats.norm(0, 1).rvs(N)
print(sp.stats.shapiro(x1))

x2 = sp.stats.norm(0, 1).rvs(N)
print(sp.stats.ks_2samp(x1, x2))
ShapiroResult(statistic=0.9926937818527222, pvalue=0.8689165711402893)
KstestResult(statistic=0.1, pvalue=0.7020569828664881, statistic_location=-0.41361898075974735, statistic_sign=-1)