BioPlayground

🧬
목록으로

머신러닝으로 유전자 발현 데이터 분석하기

scikit-learn으로 유전자 발현 데이터를 클러스터링하고 PCA로 시각화하고 종양/정상을 분류하는 법.

중급
|
120
|
검증 완료 (2026-06)
클러스터링PCA분류유전자 발현scikit-learnK-means
트랙 진행률0/5 (0%)

머신러닝으로 유전자 발현 데이터 분석하기

이 토픽을 마치면

scikit-learn으로 유전자 발현 데이터를 클러스터링(K-means)하고, PCA로 차원을 축소하고, 종양/정상 샘플을 분류할 수 있습니다.


머신러닝이란?

실험에서 경험이 쌓이면 겔 사진만 보고 "이 밴드 패턴은 mutation이다"라고 판단할 수 있게 되죠? 머신러닝은 컴퓨터에게 이런 "패턴 인식" 능력을 데이터로 학습시키는 것입니다.

바이오에서 자주 쓰는 ML:

  • 비지도 학습: 라벨 없이 데이터의 패턴을 찾음 (클러스터링, PCA)
  • 지도 학습: 정답이 있는 데이터로 분류 모델을 학습 (종양 vs 정상)

데이터 준비: 가상 유전자 발현 데이터

실제 RNA-seq 데이터를 모방한 가상 데이터를 만듭니다. 100개 샘플(50 정상 + 50 종양)에서 20개 유전자의 발현량을 측정한 상황입니다.

python
import numpy as np
import pandas as pd
np.random.seed(42)
n_samples = 100
n_genes = 20
gene_names = [f"Gene_{i+1:02d}" for i in range(n_genes)]
sample_labels = ["Normal"] * 50 + ["Tumor"] * 50
normal_expr = np.random.randn(50, n_genes) * 1.0 + 5.0
tumor_expr = np.random.randn(50, n_genes) * 1.5 + 5.0
tumor_expr[:, :5] += 3.0 # Gene_01~05 상향
tumor_expr[:, 15:] -= 2.0 # Gene_16~20 하향
expression = np.vstack([normal_expr, tumor_expr])
df = pd.DataFrame(expression, columns=gene_names)
df["Label"] = sample_labels
print(f"데이터 크기: {df.shape}")
print(f"샘플: Normal {sum(df['Label']=='Normal')}, Tumor {sum(df['Label']=='Tumor')}")
print(f"\n처음 5행:")
print(df.head().to_string())
assert df.shape == (100, 21)
assert sum(df["Label"] == "Normal") == 50
assert sum(df["Label"] == "Tumor") == 50

PCA: 고차원 데이터를 2D로 압축

유전자 20개의 발현량을 사람이 볼 수 있는 2차원으로 압축합니다. PCA(주성분 분석)는 데이터의 분산이 가장 큰 방향을 찾습니다.

python
import numpy as np
import pandas as pd
from sklearn.preprocessing import StandardScaler
from sklearn.decomposition import PCA
np.random.seed(42)
n_genes = 20
gene_names = [f"Gene_{i+1:02d}" for i in range(n_genes)]
normal_expr = np.random.randn(50, n_genes) * 1.0 + 5.0
tumor_expr = np.random.randn(50, n_genes) * 1.5 + 5.0
tumor_expr[:, :5] += 3.0
tumor_expr[:, 15:] -= 2.0
expression = np.vstack([normal_expr, tumor_expr])
labels = ["Normal"] * 50 + ["Tumor"] * 50
# 1. 표준화 (평균 0, 분산 1)
scaler = StandardScaler()
X_scaled = scaler.fit_transform(expression)
# 2. PCA 적용
pca = PCA(n_components=2)
X_pca = pca.fit_transform(X_scaled)
print(f"원본 차원: {expression.shape[1]}개 유전자")
print(f"축소 차원: {X_pca.shape[1]}개 주성분")
print(f"설명 분산: PC1={pca.explained_variance_ratio_[0]:.1%}, PC2={pca.explained_variance_ratio_[1]:.1%}")
print(f"합계: {sum(pca.explained_variance_ratio_):.1%}")
assert X_pca.shape == (100, 2)
assert pca.explained_variance_ratio_[0] > pca.explained_variance_ratio_[1]
python
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
import numpy as np
from sklearn.preprocessing import StandardScaler
from sklearn.decomposition import PCA
np.random.seed(42)
n_genes = 20
normal_expr = np.random.randn(50, n_genes) * 1.0 + 5.0
tumor_expr = np.random.randn(50, n_genes) * 1.5 + 5.0
tumor_expr[:, :5] += 3.0
tumor_expr[:, 15:] -= 2.0
expression = np.vstack([normal_expr, tumor_expr])
labels = np.array(["Normal"] * 50 + ["Tumor"] * 50)
scaler = StandardScaler()
X_scaled = scaler.fit_transform(expression)
pca = PCA(n_components=2)
X_pca = pca.fit_transform(X_scaled)
fig, ax = plt.subplots(figsize=(8, 6))
for label, color in [("Normal", "#2E86AB"), ("Tumor", "#E74C3C")]:
mask = labels == label
ax.scatter(X_pca[mask, 0], X_pca[mask, 1], c=color, s=40, alpha=0.7, label=label, edgecolors="white", linewidth=0.5)
ax.set_xlabel(f"PC1 ({pca.explained_variance_ratio_[0]:.1%})", fontsize=12)
ax.set_ylabel(f"PC2 ({pca.explained_variance_ratio_[1]:.1%})", fontsize=12)
ax.set_title("PCA — Normal vs Tumor", fontsize=14, fontweight="bold")
ax.legend(fontsize=11)
ax.grid(True, alpha=0.2)
fig.tight_layout()
fig.savefig("pca_plot.png", dpi=150)
plt.close(fig)
print("pca_plot.png 저장 완료")
assert X_pca.shape == (100, 2)

K-means 클러스터링: 비지도 학습

라벨 없이 데이터만으로 그룹을 찾습니다. K-means는 가장 직관적인 클러스터링 알고리즘입니다.

python
import numpy as np
from sklearn.preprocessing import StandardScaler
from sklearn.cluster import KMeans
np.random.seed(42)
n_genes = 20
normal_expr = np.random.randn(50, n_genes) * 1.0 + 5.0
tumor_expr = np.random.randn(50, n_genes) * 1.5 + 5.0
tumor_expr[:, :5] += 3.0
tumor_expr[:, 15:] -= 2.0
expression = np.vstack([normal_expr, tumor_expr])
true_labels = [0] * 50 + [1] * 50
scaler = StandardScaler()
X_scaled = scaler.fit_transform(expression)
# K-means: 2개 클러스터로 분류
kmeans = KMeans(n_clusters=2, random_state=42, n_init=10)
clusters = kmeans.fit_predict(X_scaled)
# 클러스터가 실제 라벨과 얼마나 일치하는지 확인
from sklearn.metrics import adjusted_rand_score
ari = adjusted_rand_score(true_labels, clusters)
print(f"클러스터 0: {sum(clusters == 0)}개 샘플")
print(f"클러스터 1: {sum(clusters == 1)}개 샘플")
print(f"Adjusted Rand Index: {ari:.3f} (1.0 = 완벽 일치)")
assert len(set(clusters)) == 2
assert ari > 0.5
print("K-means 클러스터링이 정상/종양 구분에 성공")

분류: 지도 학습

이번에는 정답(Normal/Tumor)을 알고 있는 데이터로 모델을 학습하고, 새로운 샘플을 분류합니다.

python
import numpy as np
from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import accuracy_score, classification_report
np.random.seed(42)
n_genes = 20
normal_expr = np.random.randn(50, n_genes) * 1.0 + 5.0
tumor_expr = np.random.randn(50, n_genes) * 1.5 + 5.0
tumor_expr[:, :5] += 3.0
tumor_expr[:, 15:] -= 2.0
X = np.vstack([normal_expr, tumor_expr])
y = np.array([0] * 50 + [1] * 50) # 0=Normal, 1=Tumor
# 1. 데이터 분할 (80% 학습, 20% 테스트)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42, stratify=y)
# 2. 표준화
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)
# 3. Random Forest 학습
clf = RandomForestClassifier(n_estimators=100, random_state=42)
clf.fit(X_train_scaled, y_train)
# 4. 예측 & 평가
y_pred = clf.predict(X_test_scaled)
accuracy = accuracy_score(y_test, y_pred)
print(f"정확도: {accuracy:.1%}")
print(f"\n학습 데이터: {len(X_train)}개, 테스트 데이터: {len(X_test)}개")
print(f"\n분류 보고서:")
print(classification_report(y_test, y_pred, target_names=["Normal", "Tumor"]))
assert accuracy > 0.8
assert len(X_train) == 80
assert len(X_test) == 20

Feature Importance: 어떤 유전자가 중요한가?

Random Forest는 분류에 가장 많이 기여한 유전자(feature)를 알려줍니다.

python
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
import numpy as np
from sklearn.preprocessing import StandardScaler
from sklearn.ensemble import RandomForestClassifier
np.random.seed(42)
n_genes = 20
gene_names = [f"Gene_{i+1:02d}" for i in range(n_genes)]
normal_expr = np.random.randn(50, n_genes) * 1.0 + 5.0
tumor_expr = np.random.randn(50, n_genes) * 1.5 + 5.0
tumor_expr[:, :5] += 3.0
tumor_expr[:, 15:] -= 2.0
X = np.vstack([normal_expr, tumor_expr])
y = np.array([0] * 50 + [1] * 50)
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
clf = RandomForestClassifier(n_estimators=100, random_state=42)
clf.fit(X_scaled, y)
importances = clf.feature_importances_
sorted_idx = np.argsort(importances)[-10:] # 상위 10개
fig, ax = plt.subplots(figsize=(8, 5))
ax.barh(range(len(sorted_idx)), importances[sorted_idx], color="#2E86AB")
ax.set_yticks(range(len(sorted_idx)))
ax.set_yticklabels([gene_names[i] for i in sorted_idx], fontsize=10)
ax.set_xlabel("Feature Importance", fontsize=12)
ax.set_title("Top 10 Important Genes", fontsize=14, fontweight="bold")
fig.tight_layout()
fig.savefig("feature_importance.png", dpi=150)
plt.close(fig)
top_gene = gene_names[sorted_idx[-1]]
print(f"가장 중요한 유전자: {top_gene}")
print(f"상위 5 유전자: {[gene_names[i] for i in sorted_idx[-5:]]}")
assert len(importances) == 20
assert sum(importances) > 0.99

직접 해보기 (Faded Example)

아래 빈칸을 채워 scikit-learn의 fit-predict 패턴을 완성하세요.

빈칸 채우기python
from sklearn.cluster import KMeans
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
X_scaled = scaler.fit_transform(data)
kmeans = KMeans(n_clusters=)
clusters = kmeans.(X_scaled)

흔한 에러 & 해결법

Q: ValueError: could not convert string to float

문자열 열(예: 유전자 이름, 라벨)이 데이터에 포함되어 있습니다. df.select_dtypes(include=[np.number])로 숫자 열만 선택하세요.

Q: 정확도가 50%밖에 안 됩니다

데이터가 무작위와 다를 바 없다는 뜻입니다. 표준화(StandardScaler)를 했는지, 피처(유전자)가 충분히 다른 패턴을 가지는지 확인하세요.

Q: ConvergenceWarning: Number of distinct clusters found

K-means가 수렴하지 못했습니다. n_init=10, max_iter=300을 늘리거나, 데이터 스케일링을 확인하세요.


축하합니다! DevBench 공통 + A-1 트랙의 MVP 5토픽을 모두 완료했습니다.