본문 바로가기
통계, AI

t-test에 대한 모든 것

by 데분노트 2025. 4. 6.

이 글에서는 t-test이란 무엇인지, 언제 사용되는지, 그리고 이와 관련된 중요한 통계 개념들을 예시와 함께 아주 쉽게 설명해보겠습니다.

1. t-test란?

t-검정은 두 그룹의 평균이 서로 다른지 비교하는 통계적인 방법입니다. 예를들어 남녀 간의 연봉차이, 지역별 소득 차이 등을 비교할 때 쓰이죠. 두 그룹의 평균은 그냥 평균을 계산해서 비교하면 끝나는 작업인데, 왜 t-test라는 통계적 기법이 필요한 걸까요? 그 이유는 두 그룹으로 나눈 데이터가 편향된 데이터일 수 있기 때문입니다. 예를 들어 남녀 간의 연봉차이를 비교할 건데, 수집한 연봉 데이터의 80%가 부산 사람들이었다고 하면, 편향된 데이터라고 할 수 있죠.

 

대한민국 전체 5,000만명의 데이터는 '모집단'이라고 하고, 그 중 일부 데이터를 '표본'이라고 합니다. '표본'의 데이터가 정확하려면 무작위 추출을 해야하는데, 이렇게 무작이 추출을 한다고 한들 완전한 편향에서는 벗어나기가 현실적으로 힘들 수 있죠. 그래서 두 그룹의 평균을 비교할 땐, t-test를 하는 것입니다. t-test는 두 집단의 데이터가 표본일 때, 표본에서의 평균 차이가 모집단에서도 유의할지 검증하는 작업이기 때문입니다. 그럼 예시로 이해를 해보겠습니다. 예시를 간단하게 하기 위해 아래와 같이 임의의 수치를 넣어서 비교해보겠습니다.

  • 모집단 A(=대한민국 전체 여성)의 평균 연봉: 7,000만원
  • 표본 A(=그 중 일부 여성)의 평균 연봉: 6,000만원
  • 모집단 B(=대한민국 전체 남성의 평균 연봉: 7,000만원
  • 표본 B(=그 중 일부 남성의 평균 연봉): 6,500만원

대부분의 경우 모집단의 데이터는 현실적으로 알기 어렵습니다. 예를 들어 대한민국 전체 남녀의 오른손 엄지 손가락의 길이를 비교해야한다고 하면, 어떻게 비교를 해야할까요? 정부에서 강제로 전국에 모든 사람들이 며칠안에 손가락 길이를 제지 않으면 벌금을 부과하는식으로 데이터를 수집해야겠죠. 근데 그걸 현실로 실현하긴 어렵습니다. 따라서, 우리는 가정을 해야합니다. 이 가정을 통계학에서는 귀무가설이라고 하며, 이 때의 귀무가설은 "모집단의 평균 차이는 없다."는 가정 하에 분석을 시작하게 됩니다. 

 

그래서 예시에서는 모집단의 평균 연봉 차이는 0으로 설정했습니다. 이게 귀무가설이죠. 하지만 표본끼리 비교를 한 결과 500만원이 나왔습니다. 모집단에서 표본을 무한대로 여러번 무작위로 추출했을 때, 표본 평균의 차이의 분포가 어떻게 나올까요? 우리는 "모집단의 평균 차이는 0"이라고 가정을 했기 때문에, 아래와 같이 가운데가 0인 정규 분포 형태가 나올겁니다. 표본 평균의 차이가 0인 경우가 가장 많고, 0과 멀어질 수록 점점 분포가 줄어들게 되겠죠. 

 

 

이 분포에서 남성의 평균 연봉이 500만원 더 높게 나올 확률이 어떻게 될까요? 위 그래프에 따르면 4%가 될 것입니다. 통계학에서는 위 정규 분포 그래프를 귀무가설 하의 t분포라고 합니다. 귀무가설은 "모집단에서의 평균은 0이다."라는 가정이었죠. 우리는 표본 평균의 차이가 500만원이 나온 데이터를 본 입장으로써 대립가설로 "모집단에서의 평균은 0과 다르다."를 주장하고 싶습니다. 이 주장을 '대립가설'이라고 하며, 대립가설을 채택하기 위해서는 위와 같은 정규 분포에서 표본 평균 차이가 발견될 확률이 통상적으로 5% 이하여야합니다. 우연으로 발생되기 어려운 확률이기 때문이죠. 이 때의 확률을 통계학에서는 'p-value'라고합니다. t-test를 진행해본 결과, p-value가 4%가 나왔으니 '표본 평균 차이 500만원'은 우연으로 발생하기 어렵죠. 그래서 귀무가설을 기각하고, "모집단의 평균 차이는 0이 아니다."를 주장할 수 있습니다.

 

2. t-test를 사용할 수 있는 조건

t-검정은 다음과 같은 경우에 사용할 수 있습니다.

  • 두 그룹이 독립적이고(서로 다른 사람들),
  • 각 그룹의 데이터가 정규분포를 따른다고 가정할 수 있을 때,
  • 표본의 크기가 크지 않을 때 (30명 이하).

1) 두 그룹이 독립적일 때(서로 다른 사람들)

실무에서는 두 그룹이 독립적으로 존재하지 않고, 섞여 있는 경우도 많을 겁니다. 예를 들어 서비스 개편 전/후를 비교해야할 때 그렇죠. 이런 경우에 두 경우를 분리해서 분석을 해야하는데요. 예를 들어 서비스 개편 전 X 버튼 클릭 수 와 개편 후 X 버튼 클릭 수를 비교해야한다면, 개편 전/후에 모두 노출된 사용자와 한쪽에만 노출된 사용자를 따로 분리해서 분석을 해야합니다.

 

  • 개편 전/후 모두 노출된 사용자만 추려서 → 쌍체표본 t-test(파이썬에서는 ttest_rel()) 수행
  • 한쪽에만 있는 유저들끼리 독립 비교 → 독립표본 t-test(파이썬에서는 ttest_ind()) 수행

또는 혼합모델 or 회귀분석을 통해 비교하는 방법도 있습니다.

import statsmodels.formula.api as smf
import pandas as pd

# 예시 데이터프레임
df = pd.DataFrame({
    'user_id': [1,1,2,2,3,3,4,5],
    'group': ['before','after','before','after','before','after','before','after'],
    'clicks': [12, 15, 8, 10, 14, 13, 9, 17]
})

# 선형모델
model = smf.ols('clicks ~ C(group) + C(user_id)', data=df).fit()
print(model.summary())

 

 

결과 해석

 

  • C(group)[T.after]: after 그룹의 효과, 즉 개편의 효과
    이 계수가 유의미하게 크면 → 개편 이후 클릭 수가 증가한 것
    • ex) 이 값이 +2.3이고, 유의확률(p-value) < 0.05라면?
      👉 after 그룹은 평균적으로 2.3 클릭 더 했다, 이건 통계적으로 유의미하다는 뜻
  • C(user_id): 사용자별 고정효과 (각 사용자 고유 성향을 보정)
    • ex) user 1: 헤비 유저 → 원래 클릭 많이 함
    • user 2: 라이트 유저 → 원래 클릭 적게 함
      👉  "기본 클릭 성향"을 모델에 반영해주는 것

C(user_id)가 왜 중요한가?

t-test만 쓰면?

  • 클릭 차이가 "개편 때문인지", "유저가 헤비유저라 그런지" 구분 못함

회귀에서 C(user_id)까지 넣으면?

  • "유저 차이는 통제하고, 개편 효과만 본다" → 더 정교한 비교 가능!

 

2) 각 그룹의 데이터가 정규분포를 따른다고 가정할 수 있을 때

먼저 각 그룹의 데이터가 정규분포를 따른다고 가정은 어떻게 할 수 있을까요? 그 방법은 Shapiro-Wilk 검정을 사용하거나 Q-Q플롯을 통해 확인할 수 있습니다. Shapiro-Wilk 검정은 "내가 가진 데이터가 정규분포처럼 생겼는가?"를 수치적으로 판단해주는 검정이고, Q-Q 플롯은 시각적으로 정규성 판단하는 방법입니다. 두 방법 모두 비교할 A그룹과 B그룹에 각각 적용을 해서 두 그룹 모두 정규성이 확인 되어야만, t-test를 사용할 수 있죠.

 

 Shapiro-Wilk 검정

데이터가 정렬되었을 때, 정규분포에서 나올 것 같은 이론적인 분포와 얼마나 비슷한가? 를 비교.

  1. 데이터를 오름차순 정렬
  2. 정규분포를 따를 경우 기댓값(이론값) 계산
  3. 실제 값과 이론 값의 상관관계 측정 → W 통계량
  4. W 값이 1에 가까울수록 정규분포, 작을수록 정규성 깨짐
from scipy.stats import shapiro

data = [5.1, 5.2, 5.3, 5.0, 5.5]
stat, p = shapiro(data)
print(p)  # p < 0.05 → 정규성 X

 

Q-Q플롯

  • Quantile-Quantile Plot (분위수-분위수 그래프)
  • 데이터의 분위수와 정규분포 분위수를 비교해서,
    → **대각선(45도 선)**을 기준으로 얼마나 일치하는지 확인
import scipy.stats as stats
import matplotlib.pyplot as plt

stats.probplot(data, dist="norm", plot=plt)
plt.title("Q-Q plot")
plt.show()

 

 

만약 한 쪽 그룹이라도 정규성이 깨진 경우, 비모수 검정을 사용해야합니다. 

mannwhitneyu() (독립) or wilcoxon() (쌍체)

 

3) 표본의 크기가 크지 않을 때 (30명 이하)

표본 크기 n≥30n \geq 30 이상이면, 통계적으로 "표본 평균은 정규분포를 따른다"는 중심극한정리(Central Limit Theorem)를 쓸 수 있습니다. 그래서 분석 기법 선택이 좀 더 자유로워집니다.

 

1. t-test를 그대로 써도 된다!

  • 표본 수가 작을 때 쓰는 기법이지만, n이 커도 t-test은 여전히 잘 작동함.
  • 실제로 n이 크면 t분포는 정규분포와 거의 같아지기 때문에, 결과 차이도 거의 없음.
  • 그래서 t-ttest은 n 크든 작든 무난하게 쓸 수 있는 안전한 선택지.

💡 실제로는 n ≥ 30이면 t-ttest과 z-ttest 결과는 거의 똑같이 나옴.


2. z-검정(Z-test)도 이론적으로 가능

  • 표본 크기가 크고, 모집단의 표준편차(σ)를 알고 있는 경우에 쓰는 방법.
  • 하지만 현실에서 모집단의 σ를 아는 경우는 거의 없음.
  • 그래서 이론상은 가능하지만, 실무에서는 거의 안 씀.

📌 정리:

  • 모집단의 표준편차를 모르면 → t-검정
  • 모집단의 표준편차를 알면 → z-검정 (하지만 현실에선 드묾)

3. 비모수 검정도 가능 (Mann-Whitney U test 등)

  • 표본 수가 클 때는 정규성 가정을 만족한다고 보고 t-test을 써도 되지만,
  • 데이터가 한쪽으로 치우쳐 있다거나 이상치가 많을 때는 비모수 검정도 고려할 수 있음.
  • 특히 순위 기반 비교가 필요할 때 유리함.

 

요약 정리

조건 기법 비고
n < 30 t-ttest 정규성 가정 확인 필요
n ≥ 30, 모집단 표준편차 모름 t-ttest 일반적인 상황, 안전한 선택
n ≥ 30, 모집단 표준편차 앎 z-ttest 거의 안 쓰임
정규성 크게 벗어남 비모수 검정 Mann-Whitney, Wilcoxon 등

 

3. 단측검정 vs 양측검정

양측검정 평균이 크거나 작거나, 어느 쪽이든 차이가 있으면 귀무가설 기각
단측검정 평균이 특정 방향(예: 더 크다)로 차이가 있는지만 관심

 

📌 단측검정(one-tailed test)

한쪽 방향으로만 차이가 있는지를 검정합니다.

  • 예시: A반이 B반보다 평균 점수가 더 높은지만 알고 싶을 때

📌 양측검정(two-tailed test)

차이가 있는지만 보고, 어느 쪽이 더 높은지는 상관하지 않습니다.

  • 예시: A반과 B반의 평균이 다른지만 알고 싶을 때

만약 실무에서 서비스 개편 후, 클릭 수가 증가했는지, 감소했는지를 알려면 양측검정을 사용해야합니다.

 

파이썬 예시

from scipy.stats import ttest_ind

# A: 개편 전, B: 개편 후
stat, p = ttest_ind(A, B, alternative='two-sided')

if p < 0.05:
    if B.mean() > A.mean():
        print("개편 후 클릭이 유의미하게 증가함")
    else:
        print("개편 후 클릭이 유의미하게 감소함")
else:
    print("개편 후 클릭 수 변화는 통계적으로 유의하지 않음")

 

4. 쌍체(대응) 샘플을 비교할 때, 주의할 점

위에서 예시로 들었던 개편 전/후에 모두 서비스를 사용했던 사람들의 X버튼 클릭 수를 비교한다고 했을 때, 귀무가설을 기각했다고 가정해봅시다. 그러면 이 결과의 원인이 서비스의 개편 때문인지, 다른 영향 때문인지 알 수 있을까요? 거기까진 알 수 없습니다. 그러면 어떻게 개편의 영향만 분리해서 볼 수 있을까요?

 

1) A/B 테스트

첫 번째 방법은 동일 기간의 통제 그룹을 통제하는 A/B테스트를 진행하는 것입니다. 이 때는 독립 표본 t-test를 사용할 수 있습니다. 

 

2) 인터럽트 시계열 분석 (Interrupted Time Series)

  • 시간 흐름을 고려한 회귀모델을 만들고,
  • 특정 시점(개편 시점)에서 level shift, slope change가 생겼는지 확인

예시: statsmodels의 회귀 분석

import statsmodels.formula.api as smf

df['intervention'] = (df['date'] >= '2025-03-01').astype(int)
model = smf.ols('clicks ~ date + intervention', data=df).fit()

 

만약 통제 A/B테스트와 같은 실험을 진행하지 않아 통제 그룹이 없는 경우엔 두 번째 방법인 인터럽스 시계열 분석 방법을 사용해야합니다.

 

외부 변수(ex. 마케팅 캠페인)가 없는 상황일 때의 ITS 분석 파이썬 예시

import pandas as pd
import statsmodels.formula.api as smf

# 예시 데이터
# date, page_view, x_click, intervention_date 포함되어 있다고 가정
df = pd.read_csv("your_data.csv")
df['date'] = pd.to_datetime(df['date'])

# CTR 계산
df['ctr'] = df['x_btn_click_count'] / df['page_view_count']

# 시간 변수 만들기
df = df.sort_values('date')
df['time'] = range(1, len(df) + 1)

# 개편 여부 (intervention)
intervention_date = pd.to_datetime("2024-12-01")  # 예시
df['intervention'] = (df['date'] >= intervention_date).astype(int)

# 개편 이후 시간 흐름 변수
df['time_after'] = df['time'] - df['time'][df['intervention'] == 1].min() + 1
df['time_after'] = df['time_after'].apply(lambda x: x if x > 0 else 0)

# ITS 회귀 모델
model = smf.ols('ctr ~ time + intervention + time_after', data=df).fit()
print(model.summary())

 

구글 시트 예시

1. 데이터 준비

날짜 | 페이지뷰 | X버튼 클릭수 | CTR | 개편 여부 | 시간 | 개편 이후 시간
2024-11-28 | 1000 | 50 | 0.05 | 0 | 1 | 0
2024-11-29 | 1200 | 60 | 0.05 | 0 | 2 | 0
...
2024-12-01 | 1300 | 100 | 0.077 | 1 | 4 | 1
...

 

2. 시트에서 회귀분석

  • Google Sheets에서는 LINEST() 함수로 선형 회귀 가능
  • CTR을 종속 변수로, time, intervention, time_after를 독립 변수로 넣어 LINEST() 적용
  • 예시:
=LINEST(E2:E31, F2:H31, TRUE, TRUE)
  • 여기서 E열: CTR / F열: time / G열: intervention / H열: time_after

 

외부 변수가 있는 상황일 때의 ITS 분석 파이썬 예시

# 외부 변수 컬럼 추가
# 예시: 특정 날짜 범위에 캠페인 진행
df['marketing_campaign'] = ((df['date'] >= '2024-12-01') & (df['date'] <= '2024-12-05')).astype(int)

# 외부 변수 포함한 ITS 회귀
model = smf.ols('ctr ~ time + intervention + time_after + marketing_campaign', data=df).fit()
print(model.summary())

 

구글 시트 예시

 

1. 데이터에 마케팅 캠페인 변수 추가:

날짜 | CTR | time | intervention | time_after | 마케팅 캠페인

 

2. LINEST() 함수 확장:

=LINEST(B2:B31, C2:F31, TRUE, TRUE)
    • B: CTR, C~F: time, intervention, time_after, marketing_campaign

 

정리

상황 파이썬 구글 시트
외부 변수 없음 ctr ~ time + intervention + time_after LINEST(CTR, [time, intervention, time_after])
외부 변수 있음 + marketing_campaign 포함 LINEST(CTR, [time, intervention, time_after, campaign])

 

3) Difference-in-Differences (DID) 분석

컨트롤 그룹과 실험 그룹 모두 개편 전후 데이터를 갖고 있을 때
"단순 시간 흐름"과 "개편 효과"를 분리해주는 기법

 

  개편 전 개편 후
통제 그룹 A1 A2
실험 그룹 B1 B2

DID 효과 = (B2 - B1) - (A2 - A1)
→ 단순 시간 변화량을 빼고, 개편 효과만 순수하게 추정 가능.

 

 

5. 등분산 vs 이분산

📌 등분산 (Equal variance)

두 그룹의 데이터가 비슷한 퍼짐 정도(분산)를 갖는 경우입니다.

 

✅ Levene’s Test (레빈 검정)

  • 가장 널리 쓰이는 방법
  • 정규분포를 가정하지 않아도 됨
from scipy.stats import levene

# 예시: 두 그룹의 리스트
group1 = df[df['group'] == 'A']['value']
group2 = df[df['group'] == 'B']['value']

stat, p = levene(group1, group2)
print(f"Levene test p-value: {p}")

 

  • p < 0.05: 이분산 (분산이 다르다)
  • p ≥ 0.05: 등분산 (분산이 같다)

✅ Bartlett’s Test (바틀렛 검정)

  • 정규성을 가정함
from scipy.stats import bartlett

stat, p = bartlett(group1, group2)
print(f"Bartlett test p-value: {p}")

 

데이터가 정규분포를 안 따르는 것 같으면 Levene을 쓰는 게 더 안전합니다.

 

 구글 시트로 하는 법

  • 시트 자체에서 등분산 검정 함수는 없지만,
  • [두 그룹의 분산을 VAR.S() 함수로 비교]해서 크게 차이나는지 볼 수 있다.
=VAR.S(A2:A20)  // 그룹 A 분산
=VAR.S(B2:B20)  // 그룹 B 분산

 

 

 

📌 이분산 (Unequal variance)

두 그룹의 분산이 서로 다를 때 사용해야 하는 검정입니다. 이때는 Welch's t-test를 사용해야합니다.

 

정리

상황 사용 검점
두 그룹의 분산이 같음 scipy.stats.ttest_ind(a, b, equal_var=True)
두 그룹의 분산이 다름 scipy.stats.ttest_ind(a, b, equal_var=False) ← Welch’s t-test

댓글