글의 목차
- Python생태계의 통계 Library
- SciPy, Statsmodels의 차이점
- SciPy에서의 평균치 검정(t-test) 사용법 (feat 이론편의 예시 그대로 구현)
- One sample t-test
- Two samples t-test (Student's & Welch's t-test)
- Paired samples t-test
이번 포스팅에서는 Python으로 직접 평균치 검정을 수행해볼까 합니다. 만약 평균치 검정 이론에 대한 이해가 필요하시다면, 앞선 포스팅을 보고오시길 바랍니다.
평균치 검정 : t검정 [One, Two(Student's & Welch's), Paired Samples t-test]
글의 목차평균치 검정의 당위성 (왜 필요한가? feat 불확실성/변동성)평균치 검정에 대한 정의 및 이해 평균치 검정에 대한 가정 (정규성, 등분산성:Two Samples t-test)평균치 검정의 종류 및 실제 계
taehyuklee.tistory.com
1. Python 생태계의 통계 라이브러리
Python 생태계에서 통계 분석을 위한 대표적인 라이브러리로는 SciPy와 statsmodels가 있으며, 머신러닝 분야에서는 scikit-learn이 널리 사용된다. 이번에는 통계 분석 라이브러리로 SciPy를 주로 활용할 계획이지만, statsmodels의 특징도 함께 언급하고 넘어가고자 한다.
2. SciPy와 Statsmodels 라이브러리의 차이점
SciPy - Library
# Python Package Manger
pip install scipy
ttest_ind — SciPy v1.14.1 Manual
>>> import numpy as np >>> from scipy import stats >>> rng = np.random.default_rng() Test with sample with identical means: >>> rvs1 = stats.norm.rvs(loc=5, scale=10, size=500, random_state=rng) >>> rvs2 = stats.norm.rvs(loc=5, scale=10, size=500, random_s
docs.scipy.org
statsmodels - Library
# Python Package Manger
pip install statsmodels
statsmodels.stats.weightstats.ttest_ind - statsmodels 0.14.4
statsmodels.stats.weightstats.ttest_ind statsmodels.stats.weightstats.ttest_ind(x1, x2, alternative='two-sided', usevar='pooled', weights=(None, None), value=0)[source] ttest independent sample Convenience function that uses the classes and throws away the
www.statsmodels.org
두 Library모두 사용해본 결과 다음과 같이 요약할 수 있을 것 같다.
- SciPy : p-value와 기본 통계량을 빠르게 산출하는 데 최적화되어 있으며, 대용량 데이터를 처리할때나, 빠른 판단이 필요한 경우 간단한 통계 계산을 수행하는 데 많이 사용된다.
t-test는 결과 값에 있어 큰 차이 없어서, 선형 회귀 분석을 예시로 출력 차이를 보고자 한다.
# Scipy 출력
Slope: -1.7
Intercept: 86.16666666666667
R-squared: 0.12690281030444972
P-value: 0.19248793653349897
- Statsmodels : 통계 모델의 진단과 평가에 필요한 다양한 기능을 제공한다. 예를 들어 선형 회귀분석 후 각 변수의 검정통계량 (t-값), 모델의 검정통계량 (F-값), 모델 성능 지표 (결정계수 R-squared) 등을 포함한 상세한 통계량을 제공한다. 다양한 지표들을 통해 통계 모델을 해석하고 검증하는 데 활용할 수 있다.
Statsmodels로 선형회귀로 할 경우는 다음과 같은 출력이 나온다
# Statsmodels 기준의 출력
OLS Regression Results
==============================================================================
Dep. Variable: scores R-squared: 0.127
Model: OLS Adj. R-squared: 0.060
Method: Least Squares F-statistic: 1.890
Date: Mon, 04 Nov 2024 Prob (F-statistic): 0.192
Time: 00:20:44 Log-Likelihood: -40.667
No. Observations: 15 AIC: 85.33
Df Residuals: 13 BIC: 86.75
Df Model: 1
Covariance Type: nonrobust
==============================================================================
coef std err t P>|t| [0.025 0.975]
------------------------------------------------------------------------------
const 86.1667 1.597 53.969 0.000 82.717 89.616
group_code -1.7000 1.237 -1.375 0.192 -4.372 0.972
==============================================================================
Omnibus: 1.370 Durbin-Watson: 0.739
Prob(Omnibus): 0.504 Jarque-Bera (JB): 0.911
Skew: -0.272 Prob(JB): 0.634
Kurtosis: 1.922 Cond. No. 2.92
==============================================================================
정리하자면, 빠른 판단이 필요하고 p-value 및 검정 통계량(statistics)을 신속하게 산출하고자 한다면 SciPy를 이용하는 것이 적합해 보인다. 반면, 보다 정밀한 검증과 모델 진단이 필요하다면 Statsmodels를 사용하는 것이 좋을 거라 생각한다.
3. SciPy를 이용한 평균치 검정 (t-test)
예시에 대한 일관성을 가져가기 위해 앞서 포스팅했던 t-test이론 편에서의 예제를 그대로 가져오도록 하겠습니다. 앞에서 손으로 직접 계산한 검정통계량 (t-value)와 p-value가 일치하는지 확인해 보시길 바랍니다.
3.1 One sample t-test (일 표본 t-test)
[Table1. 환자 번호 & 약물 투여 후 측정된 혈압]
환자 리스트 | 투약 후 혈압 (mm Hg) |
환자1 | 118 |
환자2 | 121 |
환자3 | 119 |
환자4 | 117 |
환자5 | 120 |
이 집단의 평균 혈압이 기준 혈압(120mm Hg)과 유의미하게 다른지 검정한다.
[Python SciPy One sample t-test 사용법]
# scipy.stats에서 ttest_1samp method 사용
from scipy import stats
import numpy as np
x1 = np.array([118, 121, 119, 117, 120])
mu0 = 120.0 # 비교할 평균값
result = stats.ttest_1samp(x1, popmean=mu0, alternative=alternative, axis=0)
# 결과 Result (t 검정통계량, p-value)
TtestResult(statistic=np.float64(-1.414213562373095), pvalue=np.float64(0.23019964108049873), df=np.int64(4))
일표본 t-test의 경우 scipy.stats 안에 존재한다. method명은 ttest_1samp 이다. (공식 문서 기준)
위치 인자
첫 번째 인자 (x1): 내가 검정할 표본을 넣게 된다. Python List를 넣어도 상관없고, Pandas에서 Slice해서 넣어도 상관 없다. 내부적으로 numpy로 변환하는 듯 해보인다. (공식문서 기준) 되도록이면, numpy로 넣어주도록 하자.
기본인자 (공식문서 순서만 지키면 위치 인자로도 사용 가능)
popmean = {popmean 인자}
임의로 'mu0'라고 넣었지만, 해당 인자에는 내가 기대하는 평균값을 넣게 된다. 위의 예시에서는 '120'을 넣었다.
alternative = {alternative-인자 : str type}
- "two-sided" : 양측 검정 : 귀무가설 : 표본 평균 != ref_avg (기준 평균) (default)
- "less" 단측 검정 (좌측) : 귀무가설 : 표본 평균 < ref_avg (기준 평균)
- "greater" 단측 검정 (우측) : 귀무가설 : 표본 평균 > ref_avg (기준 평균)
axis = {axis - 인자 : int type}
- 0 : 0으로 세팅하게 되면 열을 기준으로 읽게 된다 (default)
- 1 : 1로 세팅하게 되면 행을 기준으로 읽게 된다 (2D Matrix일때 사용)
- None : ravled (평탄화) 2차원을 1차원으로 만들어서 검정에 들어갑니다.
from scipy import stats
import numpy as np
# 인자 axis에 따른 결과 예시
arr = np.array([[1.5, 2.3, 3.7],
[4.1, 5.2, 6.8],
[7.4, 8.5, 9.1],
[10.2, 11.4, 12.9],
[13.3, 14.1, 15.6]])
mu0 = 5
'''
참고로 위의 예제에서는 result 객체로 그냥 print했지만,
unpack으로 검정통계량, p-value로 분할하여 return이 가능하다.
'''
t_stat, p_value = stats.ttest_1samp(arr, popmean=mu0, axis=0)
t_stat, p_value = stats.ttest_1samp(arr, popmean=mu0, axis=1)
t_stat_axis1, p_value_axis1 = stats.ttest_1samp(arr, popmean=mu, axis=None)
# 각 각에 대한 결과
# axis = 0 일때의 결과 (열 기준으로 3개의 값이 나오는 것을 확인 가능)
T-statistic: [1.09461774 1.56522962 2.1804585 ]
P-value: [0.33517446 0.19258282 0.09469596]
# axis = 1 일때의 결과 (행 기준으로 5개의 값이 나오는 것을 확인 가능)
T-statistic: [-3.88856886 0.46776758 6.6964953 8.3223972 13.84510894]
P-value: [0.06022121 0.68597053 0.02158075 0.01413253 0.00517637]
# axis = None 일때의 결과 (모두 1차원으로 만들어 1개의 값이 나오는 것을 확인 가능)
T-statistic: 2.947475125290377
P-value: 0.010598724800782978
위의 코드 조각에서 axis옵션에 따른 결과가 어떻게 달라지는지 확인해 보았다.
그 외에 nan_policy 등과 같이 NaN값을 어떻게 처리할지에 대한 것이 있지만, 이 부분은 통계와는 큰 관계없는 Option이므로 넘어가도록 한다.
3.2 Two samples t-test (독립 표본 t-test)
[Table2. 환자 번호, 집단 분류 & 약물 복용 여부측정된 혈압]
환자 리스트 | 집단 분류 | 투약 후 혈압 (mm Hg) |
환자1 | 복용 | 115 |
환자2 | 복용 | 118 |
환자3 | 복용 | 116 |
환자4 | 미복용 | 122 |
환자5 | 미복용 | 124 |
약물을 복용한 집단과 미복용 집단의 평균 혈압이 유의미하게 다른지 검정한다. (두 집단의 표본수가 꼭 같을 필요는 없다)
3.2.1 Student's t-test
[Python SciPy Student's t-test 사용법]
from scipy import stats
import numpy as np
x1 = np.array([115, 118, 116]) # 복용 집단
x2 = np.array([122, 124]) # 미복용 집단
t_stat, p_val = stats.ttest_ind(x1, x2, equal_var=True, alternative=alternative)
# Result - Student's t-test 결과 equal_var = True 일때
T-statistic: -4.898979485566359
P-value: 0.016276603459428517
독립 표본 t-test의 경우 scipy.stats 안에 존재하며, method명은 ttest_ind이다. (공식 문서)
위치 인자
첫 번째 인자(x1) / 두 번째 인자(x2) : 첫 번째 표본, 두 번째 표본에 해당되며, 검정 통계량의 분자 항 E[X1] - E[X2] 에 대응된다고 생각하면 된다.
기본인자 (공식문서 순서만 지키면 위치 인자로도 사용 가능)
eqaul_var = {equal_var-인자 : bool type}
- "True" : X1, X2 표본 집단에 대한 등분산성 가정이 충족 된다.
- "False" : X1, X2 표본 집단에 대한 등분산성 가정이 충족 되지 않는다. (자동으로 Welch's t-test로 넘어감)
alternative = {alternative-인자 : str type}
- "two-sided" : 양측 검정 : 귀무가설 : 표본1 평균 != 표본2 평균 (해당 인자가 default 로 되어 있다)
- "less" 단측 검정 (좌측) : 귀무가설 : 표본1 평균 < 표본2 평균
- "greater" 단측 검정 (우측) : 귀무가설 : 표본1 평균 > 표본2 평균
axis = {axis - 인자 : int type}
- 0 : 0으로 세팅하게 되면 열을 기준으로 읽게 된다 (default)
- 1 : 1로 세팅하게 되면 행을 기준으로 읽게 된다 (2D Matrix일때 사용)
- None : 2차원을 1차원으로 변환하여 검정에 들어간다. (위에서 했기 때문에 생략한다)
data = np.array([[115, 118, 116],
[122, 124, 123]])
data1 = np.array([[231, 123, 132],
[223, 321, 421]])
# 1. axis=0 (열 기준)
t_stat_axis0, p_val_axis0 = stats.ttest_ind(data, data1, axis=0, equal_var=True)
# 2. axis=1 (행 기준)
t_stat_axis1, p_val_axis1 = stats.ttest_ind(data, data1, axis=1, equal_var=True)
# axis에 따른 결과
# axis=0
T-statistic: [-20.41364284 -1.01973393 -1.08618662]
P-value: [0.00239111 0.41512875 0.39087777]
# axis=1
T-statistic: [-1.31950545 -3.47552886]
P-value: [0.25745718 0.02545524]
3.2.2 Welch's t-test
[Python SciPy Welch's t-test 사용법]
from scipy import stats
import numpy as np
x1 = np.array([115, 118, 116]) # 복용 집단
x2 = np.array([122, 124]) # 미복용 집단
t_stat, p_val = stats.ttest_ind(x1, x2, equal_var=False, alternative=alternative)
# Result - Welch's t-test 결과 equal_var = False 일때
T-statistic: -5.0000000000000036
P-value: 0.025054956171761823
Student's t-test랑 mehotd는 같지만, equal_var 옵션을 False로 함으로써 사용할 수 있는 검정 기법이다. (등분산성 가정을 충족하지 못할때)
나머지 인자들은 Student's t-test 인자랑 똑같으므로, 위의
3.3 Paired Samples t-test (대응 표본 t-test)
[Table3. 환자 번호 & 약물 투여 전/후 혈압]
환자 리스트 | 투약 전 혈압 (mm Hg) | 투약 후 혈압 (mm Hg) |
환자1 | 130 | 120 |
환자2 | 128 | 119 |
환자3 | 135 | 125 |
환자4 | 132 | 123 |
환자5 | 129 | 121 |
약물 투여 전후의 평균 혈압 차이가 유의미하게 감소했는지 검정한다.
이론편에서는 유의미하게 같은지를 확인했지만, 이번에는 유의미하게 감소했는지를 검정해보도록 한다.
[Python SciPy Paired Samples t-test 사용법]
# 투약 전 혈압 데이터 (data)
data = np.array([130, 128, 135, 132, 129])
# 투약 후 혈압 데이터 (data1)
data1 = np.array([120, 119, 125, 123, 121])
# 대응 표본 t-검정 수행
t_stat, p_val = stats.ttest_rel(data, data1, alternative='less') # 'less'는 감소했는지를 검정
# Result
T-statistic: 24.58803425594304
P-value: 0.9999918819424217
대응 표본 t-test의 경우 scipy.stats 안에 존재하며, method명은 ttest_rel이다. (공식 문서)
유의수준을 0.05로 한다고 했을때 이미, 0.9999이므로 귀무가설을 채택한다. 즉, 약효과에 의해 혈압이 유의미하게 감소하였음을 알 수 있다. 나머지 axis, alternative 등의 인자들은 위에서 설명했으므로, 생략하도록 한다.
*단, Paired Samples t-test의 경우는 양쪽 데이터 쌍의 개수가 일치해야 한다. 일치하지 않을 경우 ValueError가 발생한다.
ValueError: unequal length arrays
결론 맺음말
이번에는 앞서 이론으로 알아보았던, 평균치 검정에 대해서 SciPy Library를 이용해서 구현하는 방법을 알아보았습니다. 앞서 이론 파트에서 직접 손으로 계산했던 t-통계량, p-value과 Python으로 구현한 값과 비교했을때 일치했음을 알 수 있었습니다. 이 부분들을 직접 손으로 계산도 해보고 구현해보길 바랍니다.
참고 자료
- Scipy 공식문서
- Statsmodels 공식 문서
- 자료분석개론 학교 수업 (전공 수업)