머신러닝으로 유전자 발현 데이터 분석하기
이 토픽을 마치면
scikit-learn으로 유전자 발현 데이터를 클러스터링(K-means)하고, PCA로 차원을 축소하고, 종양/정상 샘플을 분류할 수 있습니다.
머신러닝이란?
실험에서 경험이 쌓이면 겔 사진만 보고 "이 밴드 패턴은 mutation이다"라고 판단할 수 있게 되죠? 머신러닝은 컴퓨터에게 이런 "패턴 인식" 능력을 데이터로 학습시키는 것입니다.
바이오에서 자주 쓰는 ML:
- 비지도 학습: 라벨 없이 데이터의 패턴을 찾음 (클러스터링, PCA)
- 지도 학습: 정답이 있는 데이터로 분류 모델을 학습 (종양 vs 정상)
데이터 준비: 가상 유전자 발현 데이터
실제 RNA-seq 데이터를 모방한 가상 데이터를 만듭니다. 100개 샘플(50 정상 + 50 종양)에서 20개 유전자의 발현량을 측정한 상황입니다.
import numpy as npimport pandas as pd
np.random.seed(42)
n_samples = 100n_genes = 20gene_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.0tumor_expr = np.random.randn(50, n_genes) * 1.5 + 5.0tumor_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") == 50assert sum(df["Label"] == "Tumor") == 50PCA: 고차원 데이터를 2D로 압축
유전자 20개의 발현량을 사람이 볼 수 있는 2차원으로 압축합니다. PCA(주성분 분석)는 데이터의 분산이 가장 큰 방향을 찾습니다.
import numpy as npimport pandas as pdfrom sklearn.preprocessing import StandardScalerfrom sklearn.decomposition import PCA
np.random.seed(42)n_genes = 20gene_names = [f"Gene_{i+1:02d}" for i in range(n_genes)]
normal_expr = np.random.randn(50, n_genes) * 1.0 + 5.0tumor_expr = np.random.randn(50, n_genes) * 1.5 + 5.0tumor_expr[:, :5] += 3.0tumor_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]import matplotlibmatplotlib.use("Agg")import matplotlib.pyplot as pltimport numpy as npfrom sklearn.preprocessing import StandardScalerfrom sklearn.decomposition import PCA
np.random.seed(42)n_genes = 20
normal_expr = np.random.randn(50, n_genes) * 1.0 + 5.0tumor_expr = np.random.randn(50, n_genes) * 1.5 + 5.0tumor_expr[:, :5] += 3.0tumor_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는 가장 직관적인 클러스터링 알고리즘입니다.
import numpy as npfrom sklearn.preprocessing import StandardScalerfrom sklearn.cluster import KMeans
np.random.seed(42)n_genes = 20
normal_expr = np.random.randn(50, n_genes) * 1.0 + 5.0tumor_expr = np.random.randn(50, n_genes) * 1.5 + 5.0tumor_expr[:, :5] += 3.0tumor_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_scoreari = 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)) == 2assert ari > 0.5print("K-means 클러스터링이 정상/종양 구분에 성공")분류: 지도 학습
이번에는 정답(Normal/Tumor)을 알고 있는 데이터로 모델을 학습하고, 새로운 샘플을 분류합니다.
import numpy as npfrom sklearn.preprocessing import StandardScalerfrom sklearn.model_selection import train_test_splitfrom sklearn.ensemble import RandomForestClassifierfrom 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.0tumor_expr = np.random.randn(50, n_genes) * 1.5 + 5.0tumor_expr[:, :5] += 3.0tumor_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.8assert len(X_train) == 80assert len(X_test) == 20Feature Importance: 어떤 유전자가 중요한가?
Random Forest는 분류에 가장 많이 기여한 유전자(feature)를 알려줍니다.
import matplotlibmatplotlib.use("Agg")import matplotlib.pyplot as pltimport numpy as npfrom sklearn.preprocessing import StandardScalerfrom sklearn.ensemble import RandomForestClassifier
np.random.seed(42)n_genes = 20gene_names = [f"Gene_{i+1:02d}" for i in range(n_genes)]
normal_expr = np.random.randn(50, n_genes) * 1.0 + 5.0tumor_expr = np.random.randn(50, n_genes) * 1.5 + 5.0tumor_expr[:, :5] += 3.0tumor_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) == 20assert sum(importances) > 0.99직접 해보기 (Faded Example)
아래 빈칸을 채워 scikit-learn의 fit-predict 패턴을 완성하세요.
from sklearn.cluster import KMeansfrom sklearn.preprocessing import StandardScalerscaler = 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토픽을 모두 완료했습니다.