Python/실습

[Python 통계 분석 실습] 1. Z-TEST & One-Sample T-TEST

CocoJamjam 2023. 4. 27. 17:34
728x90
반응형

본 블로그의 [T-test] 독립표본과 대응표본의 평균 검정 글에서 T-test의 개념에 대해 다뤄보았다.
이론을 공부했다면 예제 실습을 해 보는 것은 당연한 수순이라 생각한다.
 
따라서 앞선 이론을 바탕으로
Z-test, One-Sample T-test(단일 표폰 t-test), Independent_Sample T-test(독립표본 t-test), Paired_Sample T-test(대응표본 t-test) 등에 대해 실습해 보자.

실습의 과정과 목표

  • Python (Google colab) 사용
  • 다시 한번 이론 정리
  • 간단한 예제 코드로 분석해 보기
  • 그래프 시각화 해보기

이 글의 내용과 예제코드의 출처는 연세 IT미래교육원의 수업 과정 중 정지훈 강사님의 교육을 바탕으로 작성되었습니다.


첫 실습 예제는 제목과 같이 Z-TEST & One-Sample T-TEST에 대한 예제이다.
 


목차

  • 평균의 비교
  • One-Sample z-test
  • One-Sample z-test 예제 실습
  • One-Sample T-test
  • One-Sample T-test 예제 실습
  • pingouin 라이브러리 소개

1. 평균의 비교

  • 일반적으로 t-test를 사용 ( 독립성, 정규성, 등분산성 확인 )
  • t-test의 종류
    • one sample t-test
    • student t-test
    • welch's test
    • paired sample t-test
  • t-test 효과의 표준 척도 : Cohen's D

2. One-Sample z-test

  • 가장 쓸모없는 test 중 하나
  • 이 test의 유일한 용도는 통계를 가르칠 때 사용
  • 전제 조건
    • 정규성(Normality) : 실제 모집단은 정규 분포를 따른다.
    • 모집단 표준편차 알고 있다 : 모집단의 실제 표준 편차를 알고 있다
    • 평균을 모르면 표준편차를 알 수 없다. (평균을 검정하는 건데 평균을 알고 있다???)
    • 현실적으로 모집단의 평균과 표준편차를 알 방법이 없기 때문에 쓸모없음.
  • 무엇을 검정하는가?
    • 추출된 표본이 동일 모집단에 속하는지 가설 검증
  • 가설검정
    • 귀무가설(H0) : 표본 평균이 모집단의 평균과 같음
    • 대립가설(H1) : 표본 평균이 모집단의 평균과 같지 않음

3. One-Sample z-test 예제 실습

  • 가정 : 모집단의 평균과 표준편차를 알고 있다.
    • 평균 : 67.5
    • 표준편차 : 9.5
  • 20명 심리학 학생 점수 추출
    • '20명의 평균은 67.5와 같은가? 다른가?'를 검정할 것이다.

3-1. 시각화

  • 평균이 67.5이고, 표준편차가 9.5인 그래프를 그려본다.
  • 이때 모집단의 수는 100개이다.
  • 히스토그램은 추출된 20개의 샘플의 분포를 나타낸다.

3-2. 데이터 불러오기 및 샘플의 최대/최소 설정

  • 추출된 각각의 데이터는 50~89 사이에서만 나오게 설정하였다.
import pandas as pd
df = pd.read_csv("https://raw.githubusercontent.com/ethanweed/pythonbook/main/Data/zeppo.csv")
df['grades'].min(), df['grades'].max()

# (50, 89)

3-3. 가설 세우기

  • 귀무가설(H0) : 추출된 20개의 샘플 데이터의 평균은 67.5이다.
  • 대립가설(H1) : H0가 아니다.
# 그래프로 시각화 하기.
import matplotlib.pyplot as plt
import numpy as np
import seaborn as sns
import scipy.stats as stats

mu = 67.5			# 평균 67.5 설정
sigma = 9.5			# 표준편차 9.5 설정
x = np.linspace(mu - 3*sigma, mu + 3*sigma, 100)# 정규분포 그래프 생성, 첨도설정(범위 및 개수)
y = 100* stats.norm.pdf(x, mu, sigma)		# Y축 확률밀도함수 설정
# stats.norm.pdf(x, mu, sigma)는 함수는 주어진 값 x에서 정규(가우스) 분포의
# 확률 밀도 함수(PDF)를 계산하며, 평균은 mu이고 표준 편차는 sigma이다.
# 100을 곱한 이유 : x의 범위가 작아서 y값이 작게 나오기 때문


fig, ax = plt.subplots()		# 그래프 fig 생성
ax1 = sns.histplot(df['grades'])# 1번 그래프 : grade에 대한 히스토그램
ax2 = sns.lineplot(x=x,y=y, color='black')	# 2번 그래프 : x, y 정규분포 선그래프

plt.ylim(bottom=-1)			# y축 0 위치 설정 (최소값 설정)

ax1.set_frame_on(False)			# 바깥 테두리 삭제
ax1.axes.get_yaxis().set_visible(False)		# y축 눈금/레이블 삭제

plt.show()

표본 분포 히스토그램

3-4. 샘플의 평균 구하기

# 샘플의 평균 구하기
import statistics
statistics.mean(df['grades'])

# 72.3
  • 모집단의 평균은 67.5이고, 추출된 샘플의 평균은 72.3이다.
    • 모집단의 평균과 20개의 추출된 평균은 다르다고 볼 수 있는가?
    • 우연히 샘플링 에러로 72.3이 나온 것인가?
    • 결정을 어떻게 내릴 수 있는가?

3-5. 가설 검정

3-5-1. 가설 검정의 전제조건

  • 샘플은 정규분포를 이루고 있다. (정규성(Normality))
  • 샘플의 관측값은 서로 독립적으로 생성됨 (독립성(Independence))
  • 모집단의 표준편차는 9.5이다.
  • stats.norm.pdf(x, mu, sigma)는 함수 : 주어진 값 x에서 정규 분포의 확률 밀도 함수(PDF)를 계산하며, 평균은 mu이고 표준 편차는 sigma이다.
    • pdf 함수는 평균과 표준편차가 주어졌을 때 무작위 변수가 특정 값 x를 취할 확률을 반환한다.
    • 표준 편차 시그마는 곡선의 확산 또는 폭을 결정한다.
    • 요약하면, stats.norm.pdf(x, mu, sigma)는 평균 mu와 표준 편차 시그마가 주어질 때 특정 값 x에서 정규 분포의 확률 밀도를 계산한다.
import numpy as np
import seaborn as sns
from scipy import stats
from matplotlib import pyplot as plt

mu = 0
sigma = 1
x = np.linspace(mu - 3*sigma, mu + 3*sigma, 100)
y = 100* stats.norm.pdf(x, mu, sigma)

fig, axes = plt.subplots(1, 2, figsize=(15, 5)) # subplot 생성 row=1, cols=2

sns.lineplot(x=x,y=y, color='black', ax=axes[0]) # [0] 인덱스에 선그래프
sns.lineplot(x=x,y=y, color='black', ax=axes[1]) # [1] 인덱스에 선그래프

axes[0].set_frame_on(False) # 그래프의 배경테두리 삭제
axes[1].set_frame_on(False)
axes[0].get_yaxis().set_visible(False)	# 그래프의 y축 삭제
axes[1].get_yaxis().set_visible(False)
axes[0].get_xaxis().set_visible(False)	# 그래프의 x축 삭제	
axes[1].get_xaxis().set_visible(False)

axes[0].axhline(y=0, color='black') # y축 수평선 (y=0)
axes[0].axvline(x=mu, color='black', linestyle='--') # x축 수평선 (x=mu)

axes[1].axhline(y=0, color='black') # y축 수평선 (y=0)
axes[1].axvline(x=mu + sigma, color='black', linestyle='--') # x축 수평선 (x=mu + sigma)

axes[0].hlines(y=23.6, xmin = mu-sigma, xmax = mu, color='black') 
axes[1].hlines(y=23.6, xmin = mu-sigma, xmax = mu, color='black')


axes[0].text(mu,42, r'$\mu = \mu_0$', size=20, ha="center") # 위치 지정하여 text 삽입
axes[1].text(mu + sigma, 42, r'$\mu \neq \mu_0$', size=20, ha="center")

axes[0].text(mu-sigma - 0.2, 23.6, r'$\sigma = \sigma_0$', size=20, ha="right")
axes[1].text(mu-sigma - 0.2, 23.6, r'$\sigma = \sigma_0$', size=20, ha="right")

plt.show()

 

  • 위 그림이 의미하는 것이 바로 두 가지 전제조건을 말한다.
  • ① 양쪽 모두 분포는 동일하다. ② 표준편차도 동일하다.
  • 다만, 모집단의 평균과 추출된 샘플링된 평균은 같으냐? 다르냐?

3-5-2. z-score

  • 각 데이터 값이 평균으로부터 얼마나 떨어져 있는지를 나타내는 통계적인 예측값
    • 표준화된 수치(Standardized Score)
grades = df['grades']
sample_mean = statistics.mean(grades)
sample_mean
sd_true = 9.5
mu_null = 67.5
N = len(grades)

# 평균의 표준오차 구하기
import math
sem_true = sd_true / math.sqrt(N)
sem_true

# 2.1242645786248002

## z_score 구하기
z_score = (sample_mean - mu_null) / sem_true
z_score

# 2.259605535157681
  • 위 통계량이 의미하는 것은 무엇인가? 를 생각해 보자.
  • 이때 아래 그래프가 필요하다.
mu = 0
sigma = 1

x = np.arange(-3,3,0.001)
y = stats.norm.pdf(x, mu, sigma)


fig, (ax0, ax1) = plt.subplots(1, 2, sharey = True, figsize=(15, 5))


# Two-sided test
crit = 1.96
p_lower = x[x<crit*-1]
p_upper = x[x>crit]

ax0.plot(x, y)
ax0.fill_between(p_lower, 0, stats.norm.pdf(p_lower, mu, sigma),color="none",hatch="///",edgecolor="b")
ax0.fill_between(p_upper, 0, stats.norm.pdf(p_upper, mu, sigma), color="none",hatch="///",edgecolor="b")
ax0.set_title("Two sided test", size = 20)
ax0.text(-1.96,-.03, '-1.96', size=18, ha="right")
ax0.text(1.96,-.03, '1.96', size=18, ha="left")
ax0.text(z_score, 0, 'O', size=18, ha="left", color = 'red')

# One-sided test
crit = 1.64
p_upper = x[x>crit]

ax1.plot(x, y)
ax1.set_title("One sided test", size = 20)
ax1.text(1.64,-.03, '1.64', size=18, ha="left")
ax1.fill_between(p_upper, 0, stats.norm.pdf(p_upper, mu, sigma), color="none",hatch="///",edgecolor="b")

ax0.set_frame_on(False)
ax1.set_frame_on(False)

ax0.get_yaxis().set_visible(False)
ax1.get_yaxis().set_visible(False)
ax0.get_xaxis().set_visible(False)
ax1.get_xaxis().set_visible(False)

plt.show()

  • z-score(여기서는 2.259)가 의미하는 것은 위 분포에서 검정하려는 값이 어디에 속하는지 표현해 주는 것이다.
  • 그런데, p-value를 구하려면 어떻게 해야 할까?
# P-value 구하기

from statistics import NormalDist

lower_area = NormalDist().cdf(-z_score)
upper_area = lower_area
p_value = lower_area + upper_area
p_value

# 0.023845743764939864
  • NormalDist(). cdf(-z_score)
    • NormalDist() : 기본적으로 평균이 0이고 표준편차가 1인 정규 분포 개체를 생성
    • .cdf() : 확률 변수 X가 주어진 값보다 작거나 같을 확률을 알려준다.
    • .cdf() 메서드는 주어진 값에서 정규 분포의 누적 분포 함수(CDF)를 계산하는 데 사용. 이 경우 입력 값은 -z_score
    • z_score가 음수이면 x가 평균보다 낮고, z_score가 양수이면 x가 평균보다 높다고 볼 수 있다.

3-6. 결론 및 정리

  • p-value가 0.02라는 값은, 귀무가설을 기각하고, 대립가설을 채택해야 한다는 것을 의미한다.
  • 즉, 표본평균이 모집단의 평균과 같지 않음을 의미.

4.  One-Sample T-test

  • z-test와 t-test의 가장 큰 차이점
    • z-test: 모수의 표준편차를 안다
    • t-test : 모수의 표준편차를 모른다.
  • 이것이 의미하는 것은 샘플의 표준편차를 여러 번 구해서 모수의 표준편차를 추정(estimate)하는 것이다.

4-1. 샘플 표준편차 구하기

# 데이터 불러오기
import pandas as pd
df = pd.read_csv("https://raw.githubusercontent.com/ethanweed/pythonbook/main/Data/zeppo.csv")

# 샘플의 표준편차 구하기
import statistics
statistics.stdev(df['grades'])

# 9.520614752375915
  • 샘플의 표준편차(σ^)는 9.52라고 말할 수 있다.
  • 그러나, 모집단의 표준편차(σ)는 9.5라고 말할 수 없다.
  • 앞서 했던 Z-TEST에서는 모수의 표준편차를 아는 조건이 있었지만, T-TEST는 모수의 표준편자를 모르는 상태로 추정하는 것이다.

4-2. 가설 검정

4-2-1. 가설의 전제조건

  • 기본적으로 z-test와 동일
    • 샘플은 정규분포를 이루고 있다. (정규성(Normality))
    • 샘플의 관측값은 서로 독립적으로 생성됨 (독립성(Independence))
  • 모수의 표준편차를 알지 못한다.

4-2-2. 가설 설정

  • 귀무가설(H0) : 모집단의 평균은 100이다.
  • 대립가설(H1) : 모집단의 평균은 100이 아니다
# 모수의 표준편차를 모를 때, 모수와 샘플의 평균 비교 판단

import matplotlib.pyplot as plt
import numpy as np
import seaborn as sns
import scipy.stats as stats

mu = 0		 # 평균
sigma = 1		 # 표준편차
x = np.linspace(mu - 3*sigma, mu + 3*sigma, 100)
y = 100 * stats.norm.pdf(x, mu, sigma)

fig axes = plt.subplots(1, 2, figsize = (15, 5))
sns.lineplot(x = x, y = y, color = 'black', ax = axes[0]
sns.lineplot(x = x, y = y, color = 'black', ax = axes[1]

axes[0].set_frame_on(False)
axes[1].set_frame_on(False)
axes[0].get_yaxis().set_visible(False)
axes[1].get_yaxis().set_visible(False)
axes[0].get_xaxis().set_visible(False)
axes[1].get_xaxis().set_visible(False)

axes[0].axhline(y = 0, color = 'black')
axes[0].axvline(x = mu, color = 'black', linestyle = '--')

axes[1].axhline(y = 0, color = 'black')
axes[1].axvline(x = mu + sigma, color = 'black', linestyle = '--')

axes[0].hlines(y = 23.6, xmin = mu - sigma, xmax = mu, color = 'black')
axes[1].hlines(y = 23.6, xmin = mu - sigma, xmax = mu, color = 'black')

axes[0].text(mu, 42, r'$\mu = \mu_0$', size = 20, ha = 'center')
axes[1].text(mu + sigma, 42, r'$\mu \neq \mu_0$', size = 20, ha = 'center')

axes[0].text(mu - sigma - 0.2, 23.6, r'$\sigma = ??$', size = 20, ha = 'right')
axes[1].text(mu - sigma - 0.2, 23.6, r'$\sigma = ??$', size = 20, ha = 'right')

 

4-2-3. 추정

  • 모수에서 추출한 Sample의 평균은 구했지만, 전체 모수를 추정할 수 있을까?
  • 샘플 수를 늘려보면 좀 더 정확해진다.
# Sample 수에 따른 분포 비교

mu = 0			# 평균
variance = 1		# 분산
sigma = np.sqrt(variance)		# 표준편차

x = np.linspace(-4, 4, 100)		# x축 설정 (min, max, divide)
y_norm = stats.norm.pdf(x, mu, sigma)		# 분포 생성

fig, axes = plt.subplots(1, 2, figsize=(15, 5))

# 자유도가 2일 때의 T-분포 (점선)
y_t = stats.t.pdf(x, 2)
sns.lineplot(x = x, y = y_norm, color = 'black', linestyle='--', ax = axes[0])
sns.lineplot(x = x, y = y_t, color = 'black', ax = axes[0])

# 자유도가 10일 때의 T-분포 (점선)
y_t = stats.t.pdf(x, 10)
sns.lineplot(x = x, y = y_norm, color = 'black', linestyle='--', ax = axes[1])
sns.lineplot(x = x, y = y_t, color = 'black', ax = axes[1])

axes[0].text(0, 0.42, r'$df = 2$', size=20, ha="center")
axes[1].text(0, 0.42, r'$df = 10$', size=20, ha="center")

# sns.despine()
axes[0].get_yaxis().set_visible(False)
axes[1].get_yaxis().set_visible(False)
axes[0].set_frame_on(False)
axes[1].set_frame_on(False)

plt.show()

  • 그림 해석
    • df는 자유도(Degrees of Freedom)를 의미한다. 자유도란 Sample의 개수 N에서 -1을 한 값이다. 
    • df가 커질수록 이상적인 분포(실선)와 가까워진다. >> 중심극한정리 참고
    • 즉, 모수 대비 Sample의 개수가 커질수록 모수를 추정하는 정확도 또한 올라간다.

5. One-Sample t-test 예제 실습

5-1. 전제 조건

  • 전국 대학생의 평균 점수는 67.5이다.
  • 우리 대학생 20명의 평균은 00이다.

5-2. 가설 설정

  • 귀무가설(H0) : 전국 대학생의 평균 점수와 우리 대학생의 평균점수는 통계적으로 유의하게 같다.
  • 대립가설(H1) : 전국 대학생의 평균 점수와 우리 대학생의 평균점수는 통계적으로 유의하게 다르다.

5-3. 가설 검정

5-3-1. t 통계량, p-value 값 구하기

from scipy.stats import ttest_1samp
t, p = ttest_1samp(a = df['grades'], popmean = 67.5)
t, p

# (2.25471286700693, 0.03614521878144544)

5-3-2. 표본의 평균, 자유도 구하기

N = len(df['grades'])
degfree = N-1
sample_mean = statistics.mean(df['grades'])
print('Sample의 평균 : ', sample_mean)
print('자유도 : ', degfree)

# Sample의 평균 : 72.3
# 자유도 : 19

5-3-3. 신뢰구간 구하기

from scipy import stats

confidence_level = 0.95
degrees_freedom = len(df['grades'])-1
sample_mean = statistics.mean(df['grades'])
sample_standard_error = stats.sem(df['grades'])

confidence_interval = stats.t.interval(confidence_level, degrees_freedom,
                                       sample_mean, sample_standard_error)
confidence_interval

# (67.84421513791415, 76.75578486208585)

5-4. 결론 및 정리

  • 평균 72.3을 기록한 우리 학생들의 평균 점수는 전체 대학생의 평균점수 67.5보다 약간 높다.
  • t(19)=2.25, p<0.05, 신뢰도 95% 신뢰구간=[67.8,76.8]

6. pingouin 라이브러리 소개

!pip install --upgrade pingouin

from pingouin import ttest
ttest(df['grades'], 67.5)

pingouin 출력 내용

7. 마치며

이것으로 One-Sample에 대한 Z-TEST & T-TEST에 대해 알아보았다.

Z-TEST는 실생활에서는 거의 쓰이지 않지만 이번에 배운 내용을 바탕으로 한 단계씩 더 나아가 실무에서도 T-TEST를 사용할 수 있기를 바란다.

다음에는  Independent_Sample T-test(독립표본 t-test), Paired_Sample T-test(대응표본 t-test)에 대해서 다뤄 보겠다.

 

다음 글

[Python 통계 분석 실습] 2. Independent-Sample(독립표본) T-TEST

[Python 통계 분석 실습] 3. Independent-Sample(독립표본) T-TEST의 비모수적 검정

728x90
반응형