차원 축소는 특성(변수)이 많은 고차원 데이터를 더 적은 차원으로 줄이는 기법입니다.
시각화, 노이즈 제거, 연산량 감소에 사용되며,
생물정보학에서는 유전자 발현 데이터나 단일 세포 데이터처럼 특성 수가 매우 많은 데이터를 다룰 때 자주 사용됩니다.
pip install scikit-learn umap-learn
왜 차원 축소가 필요한지
RNA-seq 발현량 데이터는 보통 수천~수만 개의 유전자(특성)를 가집니다.
이런 고차원 데이터를 그대로 시각화하거나 분석하기는 어렵습니다.
- 사람은 2~3차원까지만 직관적으로 시각화할 수 있음
- 특성 수가 많을수록 차원의 저주(curse of dimensionality)로 거리 기반 분석이 왜곡됨
- 많은 특성이 서로 상관되어 있어 정보가 중복됨
차원 축소는 데이터의 핵심 구조와 패턴을 최대한 보존하면서 차원 수를 줄여 이런 문제를 완화합니다.
PCA (Principal Component Analysis)
PCA는 데이터의 분산을 가장 잘 설명하는 방향(주성분)을 찾아 데이터를 투영하는 선형 차원 축소 기법입니다.
가장 기본적이고 널리 쓰이는 차원 축소 방법입니다.
기본 개념
- 첫 번째 주성분(PC1)은 데이터의 분산이 가장 큰 방향
- 두 번째 주성분(PC2)은 PC1과 직교하면서 남은 분산을 가장 잘 설명하는 방향
- 이런 식으로 서로 직교하는 주성분을 순서대로 찾아냄
기본 사용법
import numpy as np
from sklearn.decomposition import PCA
from sklearn.preprocessing import StandardScaler
# 예시 데이터: 20개 샘플, 100개 유전자 발현량
np.random.seed(0)
X = np.random.rand(20, 100)
# PCA 전 표준화
X_scaled = StandardScaler().fit_transform(X)
# PCA 적용
pca = PCA(n_components=5)
X_pca = pca.fit_transform(X_scaled)
print(X_pca.shape) # (20, 5)
PCA 적용 전에는 특성 스케일링(표준화)을 거치는 것이 일반적입니다.
스케일이 큰 특성이 분산을 과대하게 좌우해서 주성분이 왜곡될 수 있기 때문입니다.
설명된 분산 (Explained Variance)
각 주성분이 전체 분산 중 얼마를 설명하는지 확인합니다.
explained_var = pca.explained_variance_ratio_
print(explained_var)
# 예: [0.08 0.07 0.06 0.06 0.05]
cumulative_var = np.cumsum(explained_var)
print(cumulative_var) # 누적 설명 분산
Scree plot
주성분 개수에 따라 설명되는 분산이 어떻게 줄어드는지 확인하는 그래프입니다.
몇 개의 주성분까지 사용할지 정할 때 참고합니다.
import matplotlib.pyplot as plt
pca_full = PCA().fit(X_scaled)
explained_var_full = pca_full.explained_variance_ratio_
fig, ax = plt.subplots(figsize=(7, 4))
ax.plot(range(1, len(explained_var_full) + 1), explained_var_full, "bo-")
ax.set_xlabel("Principal Component")
ax.set_ylabel("Explained Variance Ratio")
ax.set_title("Scree Plot")
plt.tight_layout()
plt.show()
PCA 결과 시각화
fig, ax = plt.subplots(figsize=(6, 5))
ax.scatter(X_pca[:, 0], X_pca[:, 1])
ax.set_xlabel(f"PC1 ({explained_var[0]:.1%})")
ax.set_ylabel(f"PC2 ({explained_var[1]:.1%})")
ax.set_title("PCA")
plt.tight_layout()
plt.show()
라벨(그룹)이 있는 데이터라면 색상으로 구분해서 그릴 수 있습니다.
import pandas as pd
import seaborn as sns
labels = ["group1"] * 10 + ["group2"] * 10 # 예시 라벨
df_pca = pd.DataFrame({
"PC1": X_pca[:, 0],
"PC2": X_pca[:, 1],
"group": labels
})
fig, ax = plt.subplots(figsize=(6, 5))
sns.scatterplot(data=df_pca, x="PC1", y="PC2", hue="group", ax=ax)
plt.tight_layout()
plt.show()
주성분에 기여하는 특성 확인
각 주성분에 가장 크게 기여하는 특성(유전자)을 확인할 수 있습니다.
loadings = pca.components_ # (주성분 수, 원본 특성 수)
pc1_loadings = pd.Series(loadings[0])
top_features = pc1_loadings.abs().sort_values(ascending=False).head(5)
print(top_features)
새 데이터 투영
학습된 PCA로 새로운 샘플을 같은 공간에 투영할 수 있습니다.
X_new_scaled = scaler.transform(X_new) # 기존 scaler로 transform
X_new_pca = pca.transform(X_new_scaled) # fit 없이 transform만
UMAP (Uniform Manifold Approximation and Projection)
UMAP은 비선형 차원 축소 기법입니다.
고차원 데이터의 구조를 저차원에서도 비교적 잘 보존하며, 단일 세포 RNA-seq 분석에서 세포를 시각화하는 데 널리 사용됩니다.
pip install umap-learn
기본 사용법
import umap
reducer = umap.UMAP(n_neighbors=15, min_dist=0.1, random_state=42)
X_umap = reducer.fit_transform(X_scaled)
print(X_umap.shape) # (20, 2)
주요 하이퍼파라미터
| 파라미터 | 의미 | 영향 |
| n_neighbors | 국소 구조 파악에 사용할 이웃 수 | 작으면 국소 구조 강조, 크면 전역 구조 강조 |
| min_dist | 저차원에서 점들 간 최소 거리 | 작으면 조밀한 클러스터, 크면 흩어진 분포 |
| n_components | 출력 차원 수 | 시각화 목적이면 보통 2 |
fig, ax = plt.subplots(figsize=(6, 5))
ax.scatter(X_umap[:, 0], X_umap[:, 1])
ax.set_xlabel("UMAP1")
ax.set_ylabel("UMAP2")
ax.set_title("UMAP")
plt.tight_layout()
plt.show()
n_neighbors 값을 바꿔보면서 결과가 어떻게 달라지는지 비교해볼 수 있습니다.
fig, axes = plt.subplots(1, 3, figsize=(13, 4))
for ax, n in zip(axes, [5, 15, 50]):
reducer = umap.UMAP(n_neighbors=n, random_state=42)
emb = reducer.fit_transform(X_scaled)
ax.scatter(emb[:, 0], emb[:, 1])
ax.set_title(f"n_neighbors={n}")
plt.tight_layout()
plt.show()
PCA 후 UMAP 적용
원본 고차원 데이터에 바로 UMAP을 적용하기보다, PCA로 먼저 차원을 줄인 뒤 UMAP을 적용하는 방식이 실무에서 자주 쓰입니다. 노이즈를 줄이고 계산 속도도 빨라집니다.
# 1단계: PCA로 50차원까지 축소
pca = PCA(n_components=50, random_state=42)
X_pca50 = pca.fit_transform(X_scaled)
# 2단계: UMAP으로 2차원까지 축소
reducer = umap.UMAP(n_neighbors=15, min_dist=0.1, random_state=42)
X_umap = reducer.fit_transform(X_pca50)
Scanpy 같은 단일 세포 분석 도구도 내부적으로 이 흐름(PCA → UMAP)을 기본 단계로 사용합니다.
PCA vs UMAP vs t-SNE
| 항목 | PCA | t-SNE | UMAP |
| 방식 | 선형 | 비선형 | 비선형 |
| 전역 구조 보존 | 우수 | 약함 | 비교적 우수 |
| 국소 구조 보존 | 약함 | 우수 | 우수 |
| 계산 속도 | 빠름 | 느림 | 비교적 빠름 |
| 새 데이터 투영 | 가능 (transform) | 어려움 | 가능 (transform) |
| 주요 용도 | 전처리, 분산 분석 | 시각화 전용 | 시각화, 전처리 모두 |
t-SNE와 UMAP은 군집 내부의 상대적 위치는 의미가 있지만, 군집 간 거리나 군집 크기를 정량적으로 해석하는 것은 권장되지 않습니다. 두 기법 모두 시각화를 위해 거리 정보를 어느 정도 왜곡하기 때문입니다.
t-SNE 기본 사용법
from sklearn.manifold import TSNE
tsne = TSNE(n_components=2, perplexity=30, random_state=42)
X_tsne = tsne.fit_transform(X_scaled)
perplexity는 UMAP의 n_neighbors와 비슷한 역할을 하며, 보통 5~50 사이의 값을 사용합니다.
t-SNE는 transform() 메서드가 없어서, 새로운 데이터에 기존 임베딩을 그대로 적용하기는 어렵습니다.
생물정보학에서의 활용
RNA-seq 샘플 QC
PCA는 RNA-seq 분석에서 샘플 간 그룹화가 잘 되어 있는지, 이상치 샘플이 있는지 확인하는 용도로 자주 사용됩니다.
정규화된 발현량 행렬(샘플 × 유전자)에 PCA를 적용해서 시각화하고, 조건별로 샘플이 잘 분리되는지 확인합니다.
단일 세포 RNA-seq 클러스터링
scRNA-seq 데이터는 보통 수천~수만 개의 세포와 수천 개의 유전자로 구성됩니다.
PCA로 차원을 줄인 뒤 UMAP으로 시각화하고, 클러스터링으로 세포 유형을 구분하는 것이 일반적인 흐름입니다.
# Scanpy의 일반적인 흐름 (개념 참고용)
import scanpy as sc
# adata: AnnData 객체 (세포 × 유전자)
sc.pp.normalize_total(adata)
sc.pp.log1p(adata)
sc.tl.pca(adata, n_comps=50)
sc.pp.neighbors(adata, n_neighbors=15)
sc.tl.umap(adata)
sc.tl.leiden(adata) # 클러스터링
sc.pl.umap(adata, color="leiden")
정리표
| 항목 | 내용 |
| PCA 전 권장 작업 | 특성 스케일링 (StandardScaler) |
| PCA 핵심 지표 | explained_variance_ratio_ |
| PCA → UMAP 흐름 | PCA로 먼저 축소한 뒤 UMAP 적용이 일반적 |
| UMAP 핵심 파라미터 | n_neighbors, min_dist |
| 거리 해석 주의 | UMAP/t-SNE의 군집 간 거리는 정량적 해석 지양 |
'BI&Programming-Tools > Python' 카테고리의 다른 글
| TDD 개념 — Test Driven Development (0) | 2026.06.24 |
|---|---|
| Python 커맨드 라인 인자 받기 — sys.argv & argparse (0) | 2026.06.23 |
| [파이썬 기초] 데이터 전처리 심화 (결측값, 이상값, 정규화, 인코딩) (0) | 2026.06.20 |
| [파이썬 기초] scikit-learn 기초 (분류, 회귀, 클러스터링) (0) | 2026.06.20 |
| [파이썬 기초] argparse로 커맨드라인 도구 만들기 (0) | 2026.06.20 |
