부록 E09: 통계분석 코드
이 장에서 배울 것
이번 장에서는 통계분석을 코드로 실행하는 방법을 배웁니다. 수학 부록에서 평균, p-value, 회귀, 다중검정을 배웠다면, 여기서는 그 생각을 파이썬 코드로 옮깁니다.
핵심 용어를 먼저 정리하겠습니다.
- 사이파이(scipy): 과학 계산과 통계 검정을 위한 파이썬 라이브러리입니다.
- 통계량(statistic): 검정에서 계산되는 숫자입니다. 예를 들어 t-test의 t값이 있습니다.
- p값(p-value): 관찰한 차이가 우연으로도 나올 수 있는 정도를 나타내는 값입니다. 앞으로는
p-value라고도 쓰겠습니다. - t검정(t-test): 두 그룹 평균이 다른지 살펴보는 대표적인 검정입니다.
- 상관분석(correlation analysis): 두 숫자 변수가 함께 움직이는 경향을 보는 분석입니다.
- 회귀분석(regression analysis): 한 값이 다른 값과 어떻게 관련되는지 모델로 설명하는 분석입니다.
- 다중검정 보정(multiple testing correction): 검정을 많이 할 때 거짓 양성을 줄이기 위한 보정입니다.
가장 쉬운 비유: 계산기 대신 검정 도구 쓰기
암 그룹과 정상 그룹의 유전자 발현량이 다르게 보인다고 합시다. 눈으로만 보면 “다른 것 같다”고 느낄 수 있습니다. 하지만 연구에서는 “얼마나 그럴듯한 차이인지”를 숫자로 확인해야 합니다.
파이썬 통계 코드는 이 질문을 던지는 도구입니다.
두 그룹이 정말 다른가?
이 관계가 우연일 가능성은 어느 정도인가?
너무 많은 검정을 해서 우연한 발견을 진짜처럼 보고 있지는 않은가?
t검정 실행하기
두 그룹 평균을 비교하는 가장 단순한 예를 봅시다.
from scipy.stats import ttest_ind
cancer = [10.2, 11.5, 9.8, 12.1]
normal = [5.2, 6.1, 4.9, 5.8]
stat, p = ttest_ind(cancer, normal)
print(stat)
print(p)
ttest_ind는 서로 독립인 두 그룹의 평균을 비교합니다. 결과로 통계량과 p-value가 나옵니다.
중요한 것은 p-value가 “차이의 크기”가 아니라는 점입니다. p-value는 “차이가 없다는 가정 아래에서 이런 결과가 얼마나 이상한가”를 보는 숫자입니다.
평균도 함께 확인하기
검정을 하기 전후에는 평균도 같이 봐야 합니다.
import numpy as np
print(np.mean(cancer))
print(np.mean(normal))
예를 들어 암 그룹 평균이 10.9이고 정상 그룹 평균이 5.5라면, 차이의 방향과 크기를 대략 볼 수 있습니다. p-value만 보고 해석하면 위험합니다.
상관분석 코드
두 숫자 변수가 함께 커지는지 보고 싶을 때 상관분석을 씁니다.
from scipy.stats import pearsonr
gene_a = [1, 2, 3, 4]
gene_b = [2, 4, 6, 8]
r, p = pearsonr(gene_a, gene_b)
print(r)
print(p)
r은 상관계수입니다. r이 1에 가까우면 두 값이 함께 커지는 경향이 강합니다. 그러나 상관관계는 원인관계를 뜻하지 않습니다.
간단한 회귀분석
회귀분석은 입력값으로 출력값을 설명하거나 예측하는 방법입니다. 여기서는 가장 간단한 선형회귀 느낌만 봅니다.
from scipy.stats import linregress
x = [1, 2, 3, 4]
y = [2, 4, 6, 8]
result = linregress(x, y)
print(result.slope)
print(result.intercept)
slope는 기울기입니다. 위 예시에서는 x가 1 증가할 때 y가 2 증가하므로 기울기는 2에 가깝습니다.
여러 유전자를 검정할 때
유전자 하나만 검정하면 문제가 단순합니다. 하지만 RNA-seq에서는 유전자 수천 개를 한 번에 검정합니다. 그러면 우연히 작게 나온 p-value가 섞일 수 있습니다.
그래서 다중검정 보정을 합니다.
from statsmodels.stats.multitest import multipletests
p_values = [0.001, 0.02, 0.20, 0.04]
reject, q_values, _, _ = multipletests(p_values, method="fdr_bh")
print(reject)
print(q_values)
여기서 q_values는 FDR 방식으로 보정한 값이라고 생각하면 됩니다. FDR은 거짓 발견 비율을 조절하는 방법입니다.
표 데이터와 함께 쓰기
실제 분석에서는 리스트만 쓰지 않고 판다스 표와 함께 사용합니다.
import pandas as pd
from scipy.stats import ttest_ind
df = pd.read_csv("expression.csv")
cancer = df[df["group"] == "cancer"]["expression"]
normal = df[df["group"] == "normal"]["expression"]
stat, p = ttest_ind(cancer, normal)
print(p)
이 코드는 표에서 암 그룹과 정상 그룹을 나눈 뒤 t검정을 실행합니다.
생물정보학에서 왜 중요한가
차등발현 분석, 변이 연관성 분석, 임상 변수 비교, 모델 성능 평가에는 통계 코드가 계속 등장합니다. 통계 코드를 쓸 때는 함수 이름만 외우면 안 됩니다. 어떤 질문에 어떤 검정을 쓰는지, 결과 숫자가 무엇을 뜻하는지 함께 이해해야 합니다.
계산 감각: p-value와 효과 크기를 함께 보기
통계 코드는 숫자를 내지만, 숫자 하나만으로 결론을 내리면 위험합니다. 예를 들어 암 그룹 평균이 10.8이고 정상 그룹 평균이 10.1인데 p-value가 작을 수 있습니다. 샘플 수가 매우 크면 작은 차이도 통계적으로 유의하게 나올 수 있기 때문입니다.
따라서 최소한 세 가지를 함께 봐야 합니다.
- 평균 차이가 어느 방향인가
- 차이의 크기가 생물학적으로 의미 있는가
- 여러 유전자를 동시에 검정했다면 보정 후에도 남는가
RNA-seq에서는 유전자 수천 개를 동시에 검정하므로 우연히 작은 p-value가 많이 생깁니다. 그래서 FDR이나 q-value 같은 다중검정 보정 개념이 중요합니다.
초보자가 자주 하는 오해는 p-value를 “효과가 큰 정도”라고 생각하는 것입니다. p-value는 귀무가설 아래에서 현재처럼 극단적인 결과가 나올 가능성과 관련된 값이지, 유전자 발현 차이의 크기를 직접 말해주는 값이 아닙니다.
어려운 개념 보강: p-value, 효과크기, FDR을 한꺼번에 오해하지 않기
통계 코드에서 가장 위험한 습관은 p < 0.05만 보고 결과를 끝내는 것입니다. p-value는 중요하지만, p-value 하나가 생물학적 의미를 전부 말해 주지는 않습니다.
p-value는 다음 질문에 대한 답에 가깝습니다.
“두 그룹에 실제 차이가 없다고 가정했을 때,
지금 관찰한 정도의 차이 또는 그보다 더 극단적인 차이가 나올 가능성이 얼마나 되는가?”
여기서 중요한 기호와 말은 다음과 같습니다.
- “차이가 없다고 가정”: 귀무가설이라고 부릅니다.
- “관찰한 차이”: 실제 데이터에서 계산한 평균 차이, t값, 상관계수 같은 값입니다.
- “더 극단적인 차이”: 우연만으로 보기 어려운 방향의 결과입니다.
p-value는 “이 유전자가 진짜 원인일 확률”이 아닙니다. p-value가 작다는 것은 “차이가 없다는 가정과 현재 데이터가 잘 맞지 않는다”는 뜻입니다.
그래서 평균 차이 또는 효과크기(effect size)를 같이 봐야 합니다. 예를 들어 두 유전자가 모두 p-value가 작더라도 실제 차이의 크기는 다를 수 있습니다.
import numpy as np
normal = np.array([10, 11, 9, 10])
cancer = np.array([20, 21, 19, 20])
mean_diff = np.mean(cancer) - np.mean(normal)
print(mean_diff) # 10.0
위 예시에서 평균 차이는 10입니다. 이 값은 방향과 크기를 알려 줍니다. p-value는 우연 가능성의 신호이고, 평균 차이는 실제 변화량의 감각입니다. 둘을 같이 봐야 합니다.
유전자 수천 개를 한꺼번에 검정하면 더 큰 문제가 생깁니다. 검정을 많이 할수록 우연히 작게 나온 p-value가 섞입니다. 그래서 다중검정 보정이 필요합니다. FDR(false discovery rate)은 “발견했다고 부른 것들 중 거짓 발견의 비율을 조절하려는 기준”입니다.
Benjamini-Hochberg 방식의 아주 단순한 감각은 다음과 같습니다.
보정 p-value의 감각 ≈ 원래 p-value × 전체 검정 수 / 순위
예를 들어 1000개 유전자를 검정했고, 어떤 유전자의 p-value가 0.001이며 작은 순서로 10번째라면 다음처럼 생각할 수 있습니다.
0.001 × 1000 / 10 = 0.1
이 계산은 실제 구현의 모든 세부 규칙을 다 담지는 않지만, 왜 보정 후 p-value가 원래 p-value보다 커질 수 있는지 직관을 줍니다. 생물정보학에서는 “유전자 하나”보다 “유전자 수천 개”를 동시에 보는 일이 많으므로, 원래 p-value와 FDR을 구분해야 합니다.
흔한 오해는 세 가지입니다. 첫째, p-value는 효과크기가 아닙니다. 둘째, p-value가 작다고 생물학적으로 반드시 중요하다는 뜻은 아닙니다. 셋째, FDR 보정 없이 수천 개 결과에서 마음에 드는 유전자만 고르면 우연한 발견을 진짜처럼 해석할 위험이 큽니다.
미니 실습 블록: p-value와 효과크기를 함께 보기
이 실습은 p-value와 효과크기를 함께 보기를 직접 손으로 확인하는 연습입니다. 왜 필요한가 하면, 통계적으로 유의해도 변화가 너무 작거나, 변화가 커도 불확실하면 해석이 달라지기 때문입니다.
import pandas as pd
result = pd.DataFrame({
"gene": ["A", "B", "C"],
"log2FC": [2.0, 0.1, -1.5],
"padj": [0.001, 0.0001, 0.20]
})
selected = result[(result["padj"] < 0.05) & (result["log2FC"].abs() >= 1)]
print(selected)
각 코드 요소의 의미를 풀어보면 다음과 같습니다. &는 두 조건을 모두 만족한다는 뜻이고, abs()는 절댓값입니다. gene A만 통계 신호와 변화 크기를 함께 만족합니다.
생물정보학/계산생물학에서 쓰이는 장면은 분명합니다. RNA-seq 차등발현 결과에서 후보 유전자를 고를 때 씁니다.
흔한 오해 또는 주의점도 있습니다. p-value가 작다고 바로 원인 유전자라고 말하면 안 됩니다. 통계적 연관은 원인 증명과 다릅니다.
핵심 정리
통계분석 코드는 생물학적 질문을 숫자로 확인하는 도구입니다. ttest_ind는 두 그룹 평균 비교, pearsonr은 상관분석, linregress는 간단한 선형회귀, multipletests는 다중검정 보정에 사용됩니다. p-value는 차이의 크기가 아니므로 평균, 효과 크기, 생물학적 의미를 함께 봐야 합니다.
문제 풀이
통계분석 코드
주관식 답안은 Gemini API로 채점합니다. API 키는 이 브라우저에만 저장됩니다.
-
1. [쉬움] 객관식
scipy의 역할로 가장 적절한 것은?
-
2. [코드] 객관식
from scipy.stats import ttest_ind는 무엇을 가져오는 코드인가? -
3. [보통] 객관식
t검정(t-test)의 주된 용도는?
-
4. [보통] 객관식
p-value의 설명으로 적절한 것은?
-
5. [코드] 객관식
np.mean(cancer)는 무엇을 계산하는가? -
6. [코드] 객관식
pearsonr(gene_a, gene_b)는 무엇을 계산하는가? -
7. [보통] 객관식
상관계수 r이 1에 가까울 때 의미로 적절한 것은?
-
8. [보통] 객관식
linregress(x, y)에서slope는 무엇인가? -
9. [계산] 객관식
x=[1,2,3,4], y=[2,4,6,8]의 선형 관계에서 기울기는?
-
10. [코드] 객관식
multipletests(p_values, method="fdr_bh")의 역할로 적절한 것은? -
11. [보통] 객관식
다중검정 보정이 필요한 이유는?
-
12. [코드] 객관식
df[df["group"] == "cancer"]["expression"]의 의미는? -
13. [보통] 객관식
p-value만 보고 해석하면 위험한 이유는?
-
14. [계산] 객관식
두 그룹 평균이 cancer=10.9, normal=5.5라면 평균 차이는?
-
15. [코드] 객관식
stat, p = ttest_ind(cancer, normal)에서p에는 무엇이 들어가는가? -
16. [보통] 객관식
상관관계 해석에서 조심해야 할 점은?
-
17. [쉬움] 객관식
p_values=[0.001,0.02,0.20,0.04]는 무엇을 담은 리스트인가?
-
18. [보통] 객관식
통계분석 코드에서 그룹을 먼저 나누는 이유는?
-
19. [보통] 객관식
reject가 다중검정 결과에서 보통 뜻하는 것은? -
20. [보통] 객관식
통계 코드가 생물정보학에서 중요한 이유는?
-
21. [중간] 객관식
암 그룹 평균이 12, 정상 그룹 평균이 7이면 평균 차이(cancer-normal)는?
-
22. [중간] 객관식
p-value 해석으로 가장 적절한 것은?
-
23. [중간] 객관식
RNA-seq에서 수천 개 유전자를 동시에 검정할 때 필요한 절차는?
-
24. [중간] 객관식
상관계수 r이 1에 가까울 때 의미로 가장 적절한 것은?
-
25. [중간] 객관식
선형회귀에서 기울기 slope가 2라는 말의 의미로 가장 적절한 것은?
-
26. [중간] 객관식
p-values
[0.001, 0.02, 0.20, 0.04]에서 가장 작은 p-value는? -
27. [중간] 객관식
통계적으로 유의하지만 생물학적으로 중요하지 않을 수 있는 이유로 맞는 것은?
-
28. [중간] 객관식
두 그룹 평균 비교에 흔히 쓰이는 scipy 함수는?
-
29. [실전] 객관식
padj < 0.05와abs(log2FC) >= 1을 함께 쓰는 이유는? -
30. [실전] 객관식
log2FC = -1은 대략 어떤 의미인가? -
주관식 31. [실습] 주관식 · Gemini 채점
두 리스트
cancer,normal에 대해 t검정을 실행하고 p-value를 출력하는 코드를 작성하라. -
주관식 32. [실습] 주관식 · Gemini 채점
두 리스트의 평균을 각각 출력하는 코드를 작성하라.
-
주관식 33. [실습] 주관식 · Gemini 채점
pearsonr(gene_a, gene_b)결과의r과p가 각각 무엇을 뜻하는지 설명하라. -
주관식 34. [실습] 주관식 · Gemini 채점
다중검정 보정이 왜 필요한지 RNA-seq 예시로 설명하라.
-
주관식 35. [실습] 주관식 · Gemini 채점
판다스 데이터프레임에서 cancer 그룹과 normal 그룹의 expression을 나누는 코드를 작성하라.
-
주관식 36. [실습] 주관식 · Gemini 채점
p-value만으로 결론 내리면 안 되는 이유를 설명하라.
-
주관식 37. [실습] 주관식 · Gemini 채점
p-value, 평균 차이, 다중검정 보정을 함께 봐야 하는 이유를 RNA-seq 예시로 설명하라.
-
주관식 38. [실습] 주관식 · Gemini 채점
두 그룹 리스트
cancer=[10,12,14],normal=[4,5,6]의 평균 차이를 계산하는 간단한 파이썬 코드를 작성하라. -
주관식 39. [실습] 주관식 · Gemini 채점
resultDataFrame에서padj < 0.05인 행만 고르는 코드를 작성하라. -
주관식 40. [실습] 주관식 · Gemini 채점
log2FC = 2와log2FC = -2를 배수 변화로 해석하라.