데이터 분석/ADP 자격증 공부

(파이썬 한권으로 끝내기) 통계분석 – T검정(t-test), 분산분석(ANOVA), 교차분석(카이제곱 검정)

나르시스트 2026. 4. 5. 18:04

1. T검정(t-test)

1.1 일표본 T-검정(One Sample t-test)

※ 정규성 가정

– 귀무가설(H0): 모평균의 값은 **이다
– 대립가설(H1): 모평균의 값은 **가 아니다

import pandas as pd
import scipy.stats as stats
from scipy.stats import shapiro


cats = pd.read_csv('data/cats_gpt.csv')

mu = 2.6

# 1. 정규성 검정
print(shapiro(cats['Bwt']))

# 2. (정규성을 만족하면) stats.ttest_1samp
print(stats.ttest_1samp(cats.Bwt, popmean=mu))

# 2. (정규성을 만족하지 않으면) Wil Coxon 검정
#stats.wilcoxon(cats.Bwt - mu, alternative='two-sided')

→ 데이터가 정규분포를 따라서 stats.ttest_1samp()을 수행
→ p-value는 유의수준 0.05 보다 크므로, “고양이의 몸무게가 2.6kg이다”라는 귀무가설을 채택

1.2 대응표본 T-검정(Paired Sample t-test)

※ 정규성 가정

– 귀무가설(H0): 두 모평균 사이의 차이는 없다
– 대립가설(H1): 두 모평균 사이의 차이가 있다

import pandas as pd
import scipy.stats as stats
from scipy.stats import shapiro

data = {'before': [7,3,4,5,2,1,6,6,5,4], 'after': [8,4,5,6,2,3,6,8,6,5]}
data = pd.DataFrame(data)
print(data)

# 1. 정규성 검정
print(shapiro(data))  # 정규분포 따름

# 2. (정규성을 만족하면) Paired t-test 검정
print(stats.ttest_rel(data['after'], data['before'], alternative='greater'))
print(data.mean())

# 2. (정규성을 만족하지 않으면) Wil Coxon 검정
#stats.wilcoxon(before, after)

→ p-value가 유의수준보다 작으므로, “수면영양제를 복용하기 전/후의 평균 수면 시간 차이는 통계적으로 유의하다

1.3 독립표본 T-검정(Independent Sample t-test)

※ 정규성, 등분산성 가정
→ 등분산성 만족하면 equal_var=True로, 만족하지 않으면 equal_var=False로 t-test 검정

– 귀무가설(H0): ** 전/후의 차이가 없다
– 대립가설(H1): ** 전/후의 차이가 있다

import pandas as pd
import scipy.stats as stats

cats = pd.read_csv('data/cats2_gpt.csv')
print(cats)

female = cats.loc[cats.Sex == 'F', 'Bwt']
male = cats.loc[cats.Sex == 'M', 'Bwt']

#1.  (정규성을 만족하지 않으면) 만 휘트니 U 검정
#stats.mannwhitneyu(female, male)

# 2. (정규성을 만족하면)  등분산성 검정 - levene-test
print(stats.levene(female, male))

# 3. (등분산성을 만족하면, p-value가 0.05보다 크면)  equal_var = True 
print(stats.ttest_ind(female, male, equal_var=True))
print(female.mean())
print(male.mean())

2. 분산분석(ANOVA)

  • 일원배치 분산분석: 독립변수 1개, 종속변수 1개
  • 이원배치 분산분석: 독립변수 2개, 종속변수 1개
  • 다원배치 분산분석: 독립변수 3개 이상, 종속변수 1개
  • 다변량 분산분석(MANOVA): 독립변수 1개 이상, 종속변수 2개 이상

2.1 일원배치 분산분석(One-way ANOVA)

※ 정규성, 독립성, 등분산성 가정

– 귀무가설(H0): k개의 집단 간 모평균에는 차이가 없다.
– 대립가설(H1): k개의 집단 간 모평균이 모두 같다고 할 수 없다
→ p-value < 0.05인 경우, 사후검정을 통해 어떤 집단이 차이가 존재하는지 알아봐야 함

  • 정규성을 만족하지 않으면, Kruskal-Wallis
stats.kruskal(setosa, versicolor, virginica)
  • 등분산성을 만족하지 않으면, Welch ANOVA
import pingouin as pg
pg.welch_anova(data=df['value'], dv='sepal width', between='target')
혹은
from statsmodels.stats.oneway import anova_oneway
result = anova_oneway(df['value'], df['group'], use_var='unequal')

 

둘 다 만족하는 데이터로 ANOVA를 수행하자

import pandas as pd
import scipy.stats as stats
import seaborn as sns
import matplotlib.pyplot as plt

from statsmodels.stats.multicomp import MultiComparison
#from statsmodels.stats.multicomp import pairwise_tukeyhsd

Iris_data = pd.read_csv('data/iris_gpt.csv')

print(Iris_data['target'].unique())
print(Iris_data.target.value_counts())

target_list = Iris_data['target'].unique()
setosa = Iris_data[Iris_data['target'] == target_list[0]]['sepal width']
versicolor = Iris_data[Iris_data['target'] == target_list[1]]['sepal width']
virginica = Iris_data[Iris_data['target'] == target_list[2]]['sepal width']
print(target_list)

sns.scatterplot(x='target', y='sepal width', hue='target', style='target', s=100, data=Iris_data)
plt.show()

# 1. 정규성 검정 - 셋 다 만족해야 함
print(stats.shapiro(setosa))
print(stats.shapiro(versicolor))
print(stats.shapiro(virginica))

# 2. 등분산 검정
# 1을 만족하지 못했을 경우, kruskal 수행
stats.levene(setosa, versicolor, virginica)

# 3. One-way ANOVA
# 2를 만족하지 못했을 경우, pg.welch_anova(data=Iris_data, dv='sepal width', between='target')
stats.f_oneway(setosa, versicolor, virginica)

# 4. 사후검정
mc = MultiComparison(data=Iris_data['sepal width'], groups=Iris_data['target'])
tukeyhsd = mc.tukeyhsd(alpha=0.05)
#tukeyhsd = pairwise_tukeyhsd(endog=Iris_data['sepal width'], groups=Iris_data['target'], alpha=0.05)
fig = tukeyhsd.plot_simultaneous()
plt.show()

print(tukeyhsd.summary())

→ (Shapiro) p-value가 0.05보다 크므로 정규성 만족 – 하나라도 만족하지 않는다면 Kruskal-Wallis

→ (Levene) p-value가 0.05보다 크므로 등분산성 만족
→ (ANOVA) p-value가 유의수준 보다 작으므로 세 집단의 평균은 차이가 있음

→ (Tukeyhsd) 세 집단 모두 차이가 있음

2.2 이원배치 분산분석(Two-way ANOVA)

※ 정규성, 독립성, 등분산성 가정 + 교호작용 검증

– 귀무가설1(H0): a, b 변수의 상효작용 효과가 없다
– 귀무가설2(H0): a 변수에 따른 종속변수의 값에는 차이가 없다
– 귀무가설3(H0): b 변수에 따른 종속변수의 값에는 차이가 없다
– 대립가설1(H0): a, b 변수의 상효작용 효과가 있다
– 대립가설2(H0): a 변수에 따른 종속변수의 값에는 차이가 있다
– 대립가설3(H0): b 변수에 따른 종속변수의 값에는 차이가 있다

import pandas as pd
import matplotlib.pyplot as plt
from statsmodels.formula.api import ols
from statsmodels.stats.anova import anova_lm
from statsmodels.graphics.factorplots import interaction_plot

mtcars = pd.read_csv('data/mtcars.csv')
mtcars = mtcars[['mpg', 'am', 'cyl']]

# 1. Two-way ANOVA 검정 수행
formula = 'mpg ~ C(cyl) + C(am) + C(cyl):C(am)'
#formula = 'mpg ~ C(cyl) * C(am)'
model = ols(formula, mtcars).fit()
aov_table = anova_lm(model, typ=2)
print(aov_table)

cyl = mtcars['cyl']
am = mtcars['am']
mpg = mtcars['mpg']
fig, ax = plt.subplots(figsize=(6,6))
fig = interaction_plot(cyl, am, mpg, colors=['red', 'blue'], markers=['D', '^'], ms=10, ax=ax)  # x1, x2, y 순으로 작성
plt.show()

→ cyl 변수는 주효과 검정에서 귀무가설 기각
→ am 변수는 주효과 검정에서 귀무가설 채택
→ 교호작용 없음

from statsmodels.stats.multicomp import pairwise_tukeyhsd

# Tukey HSD 사후검정 
# (cyl 그룹별 mpg 평균 차이)
tukey_result = pairwise_tukeyhsd(endog=mtcars['mpg'], groups=mtcars['cyl'], alpha=0.05)
print(tukey_result)
tukey_result.plot_simultaneous()  # 시각화
plt.title('Tukey HSD: mpg ~ cyl')
plt.show()

# (am 그룹별 mpg 평균 차이)
tukey_result_am = pairwise_tukeyhsd(endog=mtcars['mpg'], groups=mtcars['am'], alpha=0.05)
print(tukey_result_am)
tukey_result_am.plot_simultaneous()
plt.title('Tukey HSD: mpg ~ am')
plt.show()

# 결합 그룹 생성
mtcars['group'] = mtcars['cyl'].astype(str) + '_' + mtcars['am'].astype(str)

# 결합 그룹 사후검정
tukey_result_inter = pairwise_tukeyhsd(endog=mtcars['mpg'], groups=mtcars['group'], alpha=0.05)
print(tukey_result_inter)
tukey_result_inter.plot_simultaneous()
plt.title('Tukey HSD: mpg ~ cyl:am interaction')
plt.show()

→ 모든 cyl 조합 간 연비 차이가 통계적으로 유의하다. (4 > 6 > 8) 기통 수가 많을수록 연비는 나빠지는 경향

3. 교차분석(카이제곱 검정)

→ 각 범주에 따른 결과 변수의 분포를 설명하거나, 두 변수가 상관이 있는지를 검정
→ 종속변수가 범주형
→ 각 범주의 기대빈도는 5 이상이어야 함

 

*적합성 검정

– 귀무가설: 타이타닉호의 생존자 중 남자 비율이 50%, 여자 비율이 50%이다
– 대립가설: 타이타닉호의 생존자 중 남자 비율이 50%, 여자 비율이 50%라고 할 수 없다

import pandas as pd
from scipy.stats import chisquare

df = pd.read_csv('data/titanic.csv')

df_t = df[df['Survived'] == 1]
table = df_t[['Sex']].value_counts()
print(table)

chi = chisquare(table, f_exp=[171,171])  # 비율 입력, np.array([342/2, 342/2]), np.array([100/3, 100/3, 100/3])
print('결과: ', chi)
observed = [40, 30, 30]
expected = [sum(observed) / 3] * 3

→ p-value가 유의수준 보다 작으므로, 귀무가설을 기각하여 대립가설을 채택한다

 

*독립성/동질성 검정

– 귀무가설: 두 변수는 독립이다
– 대립가설: 두 변수는 독립이 아니다

import pandas as pd
from scipy.stats import chi2_contingency

df = pd.read_csv('data/titanic.csv')
table = pd.crosstab(df['Pclass'], df['Survived'])
print(table)

chi, p, df, expect = chi2_contingency(table)
print('Statistic: ', chi)
print('p-value: ', p)
print('df: ', df)
print('expect: ', expect)

→ p-value가 유의수준 보다 작으므로, 귀무가설을 기각하여 독립이 아니라고 할 수 있음

동질성 검정의 귀무가설(H0): 변수1의 분포는 변수2에 관계없이 동일하다

 

*사후검정(혹시나 시험에 나올까봐) – 각 그룹 쌍마다 별도 교차표 작성 후 카이제곱 검정

import pandas as pd
import numpy as np
from itertools import combinations
from scipy.stats import chi2_contingency
from statsmodels.stats.multitest import multipletests

table = pd.DataFrame({
    '성공': [30, 45, 25],
    '실패': [20, 5, 25]
}, index=['A', 'B', 'C'])

chi2, p, dof, expected = chi2_contingency(table)
residuals = (table - expected) / np.sqrt(expected)
print("표준화 잔차 (z값):")
print(residuals)

group_labels = table.index.tolist()
results = []

for g1, g2 in combinations(group_labels, 2):
    sub_table = table.loc[[g1, g2]]
    chi2, p, *_ = chi2_contingency(sub_table)
    results.append((f"{g1} vs {g2}", p))

# 다중 비교 문제(multiple comparisons) 발생 → p-value 보정
comparisons, raw_p = zip(*results)
adjusted = multipletests(raw_p, method='bonferroni')

for comp, pval, adj_p in zip(comparisons, raw_p, adjusted[1]):
    print(f"{comp}: p = {pval:.4f}, Bonferroni 보정 p = {adj_p:.4f}")

→ A-B, B-C 그룹 간 비율 차이 있음

*피셔 검정 (Fisher’s Exact Test)

2×2 교차표, 5보다 작은 셀이 20% 넘는 경우
작은 표본에서 특히 유용하며, 카이제곱 검정을 사용할 수 없는 경우에 사용

import scipy.stats as stats

# 2x2 교차표 생성 (약물 A와 B에 대한 데이터)
# 행: 약물 A, B / 열: 치료 성공, 치료 실패
data = [[8, 2],  # 약물 A
        [1, 5]]  # 약물 B

# Fisher's Exact Test 수행
oddsratio, p_value = stats.fisher_exact(data)

# 결과 출력
print(f"오즈비(odds ratio): {oddsratio:.4f}")
print(f"p-값: {p_value:.4f}")

※ 2×3 교차표에서도 적용 가능하지만, 계산 복잡성 때문에 통계 소프트웨어를 사용하여 수행하는 것이 일반적