R로 기초 통계 검정하기 — t-검정·ANOVA·상관·비모수 (R 실전)

TL;DR — 두 그룹 비교는 t.test(), 세 그룹 이상은 aov() + TukeyHSD(), 두 변수의 관계는 cor.test(), 정규성⁠(normality)을 못 믿겠으면 wilcox.test()·kruskal.test() 같은 비모수⁠(non-parametric) 검정으로 갑니다. 검정을 고르는 기준은 단 세 가지 — 그룹이 몇 개인가 (2 vs 3 이상), 짝지어졌는가 (대응 vs 독립), 정규분포를 따르는가 (모수 vs 비모수)입니다.

이 글은 더미 발현 데이터를 직접 만들어, 가정 점검 (shapiro.test·var.test) → 2그룹 t-검정 (독립·대응·Welch) → wilcox.test → 일원분산분석 (ANOVA) + 사후검정 → kruskal.test → cor.test 순서로 복붙하면 도는 코드를 따라갑니다. 효과크기⁠(effect size)와 다중비교 보정도 함께 짚습니다. 모두 base R 내장 함수라 따로 설치할 패키지가 없습니다. (코드는 R 4.5 기준, 출력은 실제 실행값입니다.)

🔗 관련 글  ·  검정 결과의 p-값을 어떻게 다룰지 (다중검정 보정)는 p-value와 FDR, 다중검정 보정  ·  결과를 그림으로 옮기려면 ggplot2 기초  ·  차등발현 같은 대규모 검정은 DESeq2로 차등발현 분석
그림 1. 그룹 수·짝지음·정규성 세 갈래로 내려가면 써야 할 검정이 정해진다.

이 글에서 할 것

실험 데이터를 손에 쥐면 가장 먼저 떠오르는 질문이 "이 차이가 통계적으로 의미가 있나"입니다. 처리군이 대조군보다 발현이 높아 보이는데, 그게 진짜 효과인지 우연인지 가르는 일이죠. R에는 이걸 위한 검정 함수가 base에 거의 다 들어 있습니다 — t-검정 (t-test), 분산분석 (ANOVA), 상관⁠(correlation), 비모수 검정까지 한 줄짜리 함수로 돌아갑니다.

문제는 함수를 모르는 게 아니라 어떤 검정을 언제 쓰는지를 헷갈리는 경우가 많다는 점입니다. 그래서 이 글은 검정을 고르는 흐름부터 잡고, 더미 데이터로 하나씩 실행해 결과를 읽습니다. 데모는 단순합니다. 대조군·처리군 발현값을 만들고 (재현 가능하도록 시드 고정), 가정을 점검한 뒤, 2그룹 비교 → 3그룹 분산분석 → 상관 분석을 차례로 돌립니다. 출력에 찍히는 t·F·r·p-값을 어떻게 해석하는지까지 정리합니다.

# base R만으로 — 설치할 패키지 없음
t.test(value ~ group, data = expr)      # 두 그룹 평균 비교
aov(expr ~ geno, data = df3)            # 세 그룹 이상 (분산분석)
cor.test(x, y, method = "pearson")      # 두 변수의 상관

0. 검정 고르기 — 1분 흐름

검정을 고르는 결정은 세 갈래 질문으로 끝납니다. 순서대로 따라가면 됩니다.

  • ① 그룹이 몇 개인가 — 두 그룹이면 t-검정 계열, 세 그룹 이상이면 분산분석 (ANOVA) 계열입니다. 세 그룹을 두 개씩 짝지어 t-검정을 반복하면 안 됩니다 (p-값이 부풀려지는 다중비교 문제 — 뒤에서 다룹니다).
  • ② 짝지어졌는가 — 같은 개체를 처리 전후로 잰 값처럼 대응⁠(paired) 표본이면 짝을 묶어 검정하고, 서로 다른 개체에서 잰 독립⁠(independent) 표본이면 그냥 두 집단을 비교합니다. 이 구분을 놓치면 검정력이 떨어지거나 가정이 깨집니다.
  • ③ 정규분포를 따르는가 — 데이터가 정규성⁠(normality)을 만족하면 평균을 비교하는 모수 (parametric) 검정 (t-검정·ANOVA), 그렇지 않거나 표본이 작고 순위 자료면 비모수⁠(non-parametric) 검정 (wilcox.test·kruskal.test)으로 갑니다. 비모수는 평균 대신 순위⁠(rank)를 비교합니다.
📌 한 줄 요약 — 그룹 수 (2 vs 3+)로 t-검정인지 ANOVA인지 정하고, 짝지음 (대응 vs 독립)으로 검정 옵션을 정하고, 정규성 (모수 vs 비모수)으로 함수 계열을 정한다. 이 세 가지만 정하면 함수는 자동으로 결정된다.

1. 사전 준비 — 데이터 만들기

설치할 패키지가 없습니다. 검정 함수는 전부 base R (stats 패키지)에 들어 있으니 바로 시작합니다. 먼저 대조군·처리군 발현값을 만듭니다. 처리군 평균을 일부러 조금 높여, 검정이 차이를 잡아내는지 봅니다.

set.seed(2025)                                 # 재현성 고정
n <- 8                                         # 군당 표본 수
ctrl <- rnorm(n, mean = 10.0, sd = 1.2)        # 대조군 발현
trt  <- rnorm(n, mean = 12.2, sd = 1.2)        # 처리군 발현 (평균 높임)

expr <- data.frame(
  value = c(ctrl, trt),
  group = factor(rep(c("Ctrl", "Trt"), each = n))   # 그룹 라벨
)
head(expr, 4)                                  # 구조 확인
     value group
1 10.74491  Ctrl
2 10.04277  Ctrl
3 10.92779  Ctrl
4 11.52699  Ctrl

검정에 들어가는 데이터 형태는 보통 긴 형식⁠(long format)입니다. 측정값 한 열 (value)과 그룹 라벨 한 열 (group)로 두면, value ~ group 같은 공식⁠(formula)으로 함수에 그대로 넘길 수 있습니다. 데이터 정리가 필요하면 dplyr·tidyr 데이터 정리를 참고하세요.

2. 가정 점검 — 정규성·등분산

모수 검정 (t-검정·ANOVA)은 두 가지를 가정합니다 — 데이터가 정규분포를 따르고, 비교하는 집단의 분산이 비슷하다(등분산)는 것입니다. 검정을 돌리기 전에 이걸 확인합니다.

shapiro.test(ctrl)             # 정규성 검정 (귀무가설: 정규분포다)
shapiro.test(trt)
var.test(value ~ group, data = expr)   # 등분산 검정 (F-검정)
	Shapiro-Wilk normality test
data:  ctrl
W = 0.94679, p-value = 0.6789

	F test to compare two variances
data:  value by group
F = 0.28378, num df = 7, denom df = 7, p-value = 0.1185

shapiro.test() (Shapiro-Wilk 검정)는 귀무가설이 "데이터가 정규분포를 따른다"입니다. 그래서 p > 0.05면 정규성을 기각하지 못함 — 즉 정규분포로 봐도 무방하다는 뜻입니다 (여기선 p = 0.68이라 통과). var.test()는 두 집단의 분산이 같은지 보는 F-검정이고, 역시 p > 0.05면 등분산⁠(equal variance)으로 봅니다 (여기선 p = 0.12). 셋 이상의 등분산은 Levene 검정 (car::leveneTest)이나 Bartlett 검정 (bartlett.test)을 씁니다.

💡 정규성 검정에 너무 매달리지 말 것 — 표본이 크면 사소한 비정규성도 p < 0.05로 기각되고, 표본이 작으면 검정력이 약해 웬만하면 통과합니다. 그래서 shapiro.test만 맹신하기보다 히스토그램·Q-Q plot (qqnorm)으로 눈으로 함께 보는 편이 낫습니다. 애매하면 비모수 검정으로 가면 안전합니다.

3. 2그룹 비교 — t.test()

가정을 통과했으니 두 그룹 평균을 비교합니다. R의 t.test()는 기본이 Welch t-검정으로, 두 집단의 분산이 다를 수 있다고 가정합니다 (var.equal = FALSE가 기본값). 그래서 등분산이 의심스러워도 안전한 게 기본 동작입니다.

t.test(value ~ group, data = expr)         # Welch t-검정 (등분산 가정 안 함, 기본)
	Welch Two Sample t-test
data:  value by group
t = -3.7418, df = 10.677, p-value = 0.003426
alternative hypothesis: true difference in means between group Ctrl and group Trt is not equal to 0
95 percent confidence interval:
 -2.5912808 -0.6673877
sample estimates:
mean in group Ctrl  mean in group Trt
          10.48409           12.11343

출력을 읽는 법은 이렇습니다. t = -3.74는 검정통계량 t-값 (두 평균 차이를 표준오차로 나눈 값), df = 10.677은 자유도 (Welch라 정수가 아닙니다), p-value = 0.0034는 두 평균이 같다는 귀무가설 아래 이 차이가 나올 확률입니다. p < 0.05이므로 두 군의 평균이 다르다고 봅니다. 95% 신뢰구간⁠(confidence interval, CI) [-2.59, -0.67]이 0을 포함하지 않는 것도 같은 결론입니다 — 부호가 음수인 건 Ctrl − Trt라 대조군이 더 낮다는 뜻입니다.

등분산이 확실하면 고전적인 Student t-검정도 쓸 수 있습니다 (df가 정수로 바뀜).

t.test(value ~ group, data = expr, var.equal = TRUE)   # Student t-검정 (등분산 가정)
# t = -3.7418, df = 14, p-value = 0.002189

같은 개체를 처리 전후로 잰 대응⁠(paired) 표본이면 paired = TRUE를 켭니다. 짝을 묶어 차이를 검정하므로 개체 간 변동을 제거해 검정력이 올라갑니다.

# before/after는 같은 샘플을 두 번 잰 값 (순서·길이가 짝으로 맞아야 함)
t.test(after, before, paired = TRUE)       # 대응 t-검정
# t = 7.1658, df = 9, p-value = 5.275e-05
⚠️ paired를 헷갈리지 말 것 — 서로 다른 개체에서 잰 두 집단 (독립)에 paired = TRUE를 켜면 통계가 완전히 틀립니다. 대응은 "같은 대상의 전/후", 독립은 "다른 대상의 그룹 A/B"입니다. 또 대응이면 두 벡터의 순서가 짝으로 정확히 맞아야 합니다 (1번 샘플의 before와 after가 같은 위치).

효과크기⁠(effect size)도 함께 봅니다. p-값은 "차이가 있는가"만 말하지 "얼마나 큰가"는 말하지 않기 때문입니다. 두 그룹 비교의 표준 효과크기는 Cohen's d — 평균 차이를 합동 표준편차 (pooled SD)로 나눈 값입니다.

# Cohen's d = (평균 차) / 합동 표준편차
m1 <- mean(trt); m0 <- mean(ctrl)
s_p <- sqrt(((n-1)*var(trt) + (n-1)*var(ctrl)) / (2*n - 2))
(m1 - m0) / s_p                            # 1.87 → 큰 효과 (관행: 0.2 작음·0.5 중간·0.8 큼)

4. 비모수 2그룹 — wilcox.test()

정규성을 만족하지 못하거나 표본이 작고 이상치가 있으면 비모수 검정으로 갑니다. 두 그룹용은 wilcox.test() — 독립 표본이면 Mann-Whitney U 검정, 대응 표본 (paired = TRUE)이면 Wilcoxon 부호순위 검정입니다. 평균 대신 순위⁠(rank)를 비교하므로 분포 가정이 느슨합니다.

wilcox.test(value ~ group, data = expr)    # Mann-Whitney U (독립·비모수)
# paired = TRUE 면 Wilcoxon 부호순위 검정
	Wilcoxon rank sum exact test
data:  value by group
W = 5, p-value = 0.002953

출력의 W는 순위합 기반 검정통계량이고, 해석은 p-값으로 같습니다 (p < 0.05면 두 군의 분포 위치가 다름). 모수 t-검정과 결론이 대체로 일치하지만, 비모수는 평균이 아니라 분포의 위치 이동⁠(location shift)을 봅니다. 동점⁠(tie)이 있으면 정확 p-값 대신 정규근사를 쓴다는 경고가 뜰 수 있는데, 해석에는 지장이 없습니다.

5. 3그룹 이상 — aov() + TukeyHSD()

그룹이 셋 이상이면 t-검정을 짝마다 반복하는 대신 일원분산분석 (one-way ANOVA)으로 "적어도 한 군은 다른가"를 한 번에 검정합니다. R에서는 aov()로 모형을 적합하고 summary()로 표를 봅니다.

# 세 유전형: WT(야생형)·KO(녹아웃)·OE(과발현)
fit <- aov(expr ~ geno, data = df3)        # 일원분산분석
summary(fit)
            Df Sum Sq Mean Sq F value   Pr(>F)
geno         2  29.52  14.760   19.51 1.63e-05 ***
Residuals   21  15.89   0.757

분산분석표에서 핵심은 F value = 19.51Pr(>F) = 1.63e-05입니다. F-값은 군 간 분산을 군 내 분산으로 나눈 비로, 클수록 군 차이가 두드러집니다. p < 0.05이므로 "세 군의 평균이 모두 같다"는 귀무가설을 기각합니다. 다만 ANOVA는 어느 군이 다른지는 말해 주지 않습니다 — 그래서 사후검정⁠(post-hoc test)이 필요합니다.

TukeyHSD()는 모든 군 쌍을 비교하되, 다중비교에 따른 p-값 부풀림을 자체적으로 보정합니다 (Tukey의 HSD — honestly significant difference).

TukeyHSD(fit)                              # 모든 쌍 비교 + 다중비교 보정
$geno
            diff       lwr      upr     p adj
KO-WT 0.06274305 -1.033493 1.158979 0.9885979
OE-WT 2.38340203  1.287166 3.479638 0.0000558
OE-KO 2.32065899  1.224423 3.416895 0.0000779

표의 각 행이 한 쌍의 비교입니다. diff는 평균 차, lwr·upr는 그 차이의 95% 신뢰구간, p adj는 보정된 p-값입니다. 여기서는 OE-WTOE-KOp < 0.001로 유의하지만 KO-WTp = 0.99로 차이가 없습니다 — 즉 OE만 도드라지고 KO와 WT는 비슷하다는 뜻입니다. ANOVA의 전체 p-값만 봤다면 놓쳤을 구조를, 사후검정이 짚어 줍니다.

💡 왜 t-검정을 반복하면 안 되나 — 세 군을 두 개씩 짝지으면 비교가 3번, 다섯 군이면 10번입니다. 각 검정에서 위양성⁠(false positive) 확률이 5%라면, 비교를 반복할수록 "어딘가 하나는 우연히 유의"할 확률이 급격히 커집니다. ANOVA + 사후검정은 이 문제를 구조적으로 막습니다. 같은 원리의 대규모 버전이 다중검정 보정 (p-value와 FDR)입니다.

6. 비모수 3그룹 — kruskal.test()

세 군 이상인데 정규성을 못 믿겠으면 ANOVA의 비모수 대응인 kruskal.test() (Kruskal-Wallis 검정)를 씁니다. 역시 순위 기반으로 "적어도 한 군은 다른가"를 검정합니다.

kruskal.test(expr ~ geno, data = df3)      # Kruskal-Wallis (비모수 ANOVA)
	Kruskal-Wallis rank sum test
data:  expr by geno
Kruskal-Wallis chi-squared = 15.365, df = 2, p-value = 0.0004608

p < 0.05이므로 군 간 차이가 있습니다. 어느 쌍이 다른지 보려면 사후검정으로 Dunn 검정(dunn.test::dunn.test 또는 FSA::dunnTest)을 쓰고, 거기서 다중비교 보정을 적용합니다 — TukeyHSD가 ANOVA의 짝이라면, Dunn 검정이 Kruskal-Wallis의 짝입니다.

그림 2. 모수 검정은 정규분포의 평균을, 비모수 검정은 값을 순위로 바꿔 위치를 비교한다.

7. 상관 — cor.test()

연속 변수가 함께 움직이는지 (예: 유전자 A 발현 ↔ 유전자 B 발현)는 상관⁠(correlation)으로 봅니다. cor.test()는 상관계수 r과 그 p-값을 함께 줍니다.

cor.test(x, y, method = "pearson")         # 피어슨: 선형 관계 (정규성 가정)
	Pearson's product-moment correlation
data:  x and y
t = 9.3529, df = 38, p-value = 2.119e-11
cor
0.8349581

cor = 0.83이 피어슨 상관계수 r입니다 (−1~+1, 0이면 무관, 부호는 방향). p < 0.05이므로 두 변수의 상관이 0이 아니라고 봅니다. 피어슨 (Pearson)은 선형⁠(linear) 관계를 재고 정규성을 가정하므로, 관계가 곡선이거나 이상치가 있으면 순위 기반의 스피어만 (Spearman)으로 바꿉니다.

cor.test(x, y, method = "spearman")        # 스피어만: 단조⁠(순위) 관계, 비모수
# rho = 0.737, p-value = 3.419e-07

스피어만은 값 대신 순위로 상관을 재므로 (r 대신 ρ, rho), 단조⁠(monotonic)이기만 하면 곡선 관계도 잡고 이상치에 덜 휘둘립니다. 관계의 모양을 모르겠으면 둘 다 보고 비교하면 됩니다. 관계를 식으로 모형화하려면 회귀⁠(regression)로 넘어갑니다 — 한 줄이면 됩니다.

lm(y ~ x)                                  # 선형회귀 (기울기·절편 추정)
summary(lm(y ~ x))$coefficients            # 계수·표준오차·t·p
그림 3. 피어슨은 값의 선형 관계를, 스피어만은 순위의 단조 관계를 재 곡선·이상치에 강하다.

8. 검정 선택 표

상황 모수 검정 비모수 검정 R 함수
2그룹·독립Student/Welch t-검정Mann-Whitney Ut.test() · wilcox.test()
2그룹·대응대응 t-검정Wilcoxon 부호순위t.test(paired=TRUE)
3그룹 이상일원분산분석 (ANOVA)Kruskal-Wallisaov() · kruskal.test()
ANOVA 사후검정TukeyHSDDunn 검정TukeyHSD() · dunn.test()
두 변수 상관피어슨 r스피어만 ρcor.test(method=...)
정규성 점검shapiro.test() · qqnorm()
등분산 점검var.test() · bartlett.test()

9. 자주 발생하는 에러 & 오해

  • 세 그룹을 t-검정으로 짝마다 비교 — 가장 흔한 실수입니다. 비교를 반복하면 위양성이 쌓이니, 3군 이상은 aov() + TukeyHSD() (또는 kruskal.test() + Dunn)로 한 번에 검정하고 사후 보정을 받으세요.
  • 독립 표본에 paired = TRUE — 서로 다른 개체인데 대응 검정을 켜면 결과가 무의미합니다. 같은 대상의 전/후일 때만 대응입니다. 반대로 진짜 대응 자료를 독립으로 검정하면 검정력을 버립니다.
  • 정규성·등분산을 점검하지 않고 t-검정 — 작은 표본에서 분포가 치우치면 t-검정의 p-값을 믿기 어렵습니다. shapiro.test·qqnorm으로 보고, 애매하면 wilcox.test로 가세요. R의 t.test는 기본이 Welch라 등분산을 가정하지 않는 점은 다행입니다.
  • 다중비교 보정 누락 — 여러 유전자·여러 조건을 한꺼번에 검정하면 p-값을 그대로 쓰면 안 됩니다. p.adjust(pvals, method = "BH")로 보정하세요 (BH = Benjamini-Hochberg, FDR 제어). 예: p.adjust(c(0.001, 0.008, 0.02, 0.04, 0.31), "BH")0.005, 0.020, 0.033, 0.050, 0.310. 자세한 건 p-value와 FDR에서 다룹니다.
  • 상관을 인과로 해석r이 크고 p가 작아도 "A가 B를 일으킨다"는 뜻은 아닙니다. 상관은 동반 변화일 뿐, 인과는 실험 설계로 따로 보여야 합니다.
  • cor.test가 NA를 반환 — 입력에 NA가 있으면 결과가 NA로 나옵니다. use = "complete.obs"를 주거나 결측을 먼저 제거하세요.
🎓 다음 학습
(검정 다음 단계) p-value와 FDR, 다중검정 보정 — 여러 검정의 p-값을 어떻게 보정하나
(결과 시각화) ggplot2 기초 · Volcano plot 시각화 — 검정 결과를 그림으로
(대규모 검정) DESeq2로 차등발현 분석 — 수만 유전자를 한 번에 검정하는 모형
(샘플 구조 먼저) PCA로 샘플 QC · dplyr·tidyr 데이터 정리

References

  1. R Core Team. (2024). R: A language and environment for statistical computing. (t.test·aov·cor.test·wilcox.test·kruskal.test·p.adjuststats 패키지 기본 함수)
  2. Student. (1908). The probable error of a mean. Biometrika, 6(1), 1–25. (t-검정의 원형)
  3. Welch, B. L. (1947). The generalization of "Student's" problem when several different population variances are involved. Biometrika, 34(1–2), 28–35. (Welch t-검정 — t.test의 기본)
  4. Benjamini, Y., Hochberg, Y. (1995). Controlling the false discovery rate. Journal of the Royal Statistical Society B, 57(1), 289–300. (p.adjust(method = "BH") — 다중검정 보정)
  5. Cohen, J. (1988). Statistical Power Analysis for the Behavioral Sciences (2nd ed.). Erlbaum. (Cohen's d — 효과크기 관행)

Pipette & Pipeline · A bio portfolio journal

이 글을 쓴 사람 Yumingming

생명융합공학과 박사과정.
Microbiome · Cosmetics · RNA Therapeutics · Bioinformatics를 공부하며,
실험(Wet Lab)과 데이터(Dry Lab)를 잇는 글을 논문(article) 기반으로 씁니다.

About · 더 알아보기 →

⚕️ 이 글은 학습·정보 제공 목적이며, 의학적 진단·치료·조언을 대체하지 않습니다. 건강·질병·치료에 관한 결정은 반드시 의사 등 전문가와 상의하세요. 자세한 내용은 면책조항을 참고해 주세요.