matplotlib·seaborn으로 생물 데이터 시각화 — 발현 데이터 플롯 실전

TL;DR — 차등발현(differential expression) 결과나 발현 행렬을 받았을 때 가장 많이 그리는 그림 네 가지를
matplotlib(파이썬 저수준 플로팅 라이브러리)와seaborn(그 위에 얹은 고수준 통계 시각화 라이브러리)로 처음부터 끝까지 그립니다. 볼케이노 플롯(volcano plot), 클러스터 히트맵(clustermap), 박스·바이올린 플롯(box/violin plot), 그리고 facet 다중 패널까지입니다.역할 분담이 분명합니다.
matplotlib은 점·선·축을 직접 그리는 바탕이고,seaborn은 "통계 그래프 한 장"을 짧은 명령으로 그려 줍니다. 볼케이노처럼 세밀한 라벨링이 필요한 그림은matplotlib으로 손보고, 분포 비교나 히트맵은seaborn한 줄로 끝냅니다.이 글의 코드는 모두 무작위 시드를 고정한 작은 발현 데이터로 돌아가, 그대로 복사하면 같은 그림이 나옵니다. seaborn 0.13에서 바뀐
hue·팔레트 규칙처럼 자주 막히는 지점도 함께 짚습니다.
🔗 관련 글 · 그릴 데이터를 먼저 정제하려면 pandas로 발현 행렬 정제하기에서 이어집니다 · 같은 그래프를 R로 그리는 법은 ggplot2로 바이오 데이터 그래프 그리기와 1:1로 비교됩니다 · 단일세포 데이터의 UMAP·클러스터 시각화는 scanpy로 단일세포 RNA-seq 분석하기에서 다룹니다
이 글에서 만들 것
발현 분석을 끝내고 나면 결국 그림으로 보여 줘야 합니다. "어떤 유전자가 처리군에서 확 올라갔나"는 볼케이노 플롯으로, "샘플들이 발현 패턴으로 어떻게 묶이나"는 클러스터 히트맵으로, "관심 유전자가 그룹마다 어떻게 분포하나"는 박스·바이올린 플롯으로 봅니다. 이 세 장에 다중 패널(facet) 한 장을 더하면 RNA-seq 결과 슬라이드의 대부분이 채워집니다.
matplotlib (John Hunter, 2003~)은 파이썬 그래프의 바탕입니다. 점 하나, 선 하나, 글자 하나를 좌표에 직접 찍는 저수준 도구라 못 그리는 게 거의 없지만, 그만큼 손이 많이 갑니다. seaborn (Michael Waskom, 2012~)은 그 위에 얹혀, 데이터프레임과 그릴 열 이름만 주면 통계 그래프 한 장을 만들어 줍니다. 둘은 경쟁 관계가 아니라 층위가 다릅니다 — seaborn으로 큰 그림을 그리고, matplotlib으로 축·라벨·주석을 마무리하는 식으로 같이 씁니다.
이 글은 작은 차등발현 결과와 발현 행렬을 직접 만들어, 네 가지 그림을 순서대로 그립니다. ① 볼케이노 플롯(산점도와 유의 영역 색칠, 상위 유전자 라벨), ② 클러스터 히트맵(z-score 정규화와 계층 군집), ③ 박스·바이올린 플롯(그룹별 발현 분포), ④ facet 다중 패널(catplot으로 여러 유전자를 한 번에)입니다. 마지막에 한글 폰트·저장 해상도(dpi)·SVG 같은 실무 설정을 정리합니다. 코드는 matplotlib 3.11·seaborn 0.13.2 기준이고, 시드를 고정해 재현됩니다.
# 핵심 흐름 한눈에
import matplotlib.pyplot as plt
import seaborn as sns
sns.set_theme(style="whitegrid") # ① 전역 스타일 한 번에
sns.scatterplot(data=deg, x="log2FC", y="neglog10p", hue="sig") # ② 볼케이노
sns.clustermap(expr_z, cmap="vlag", center=0) # ③ z-score 히트맵 + 군집
sns.violinplot(data=long, x="group", y="expr") # ④ 그룹별 분포
sns.catplot(data=long, x="group", y="expr", col="gene", kind="box") # ⑤ facet
plt.savefig("figure.png", dpi=300, bbox_inches="tight") # ⑥ 저장
1. 사전 준비 — 설치와 샘플 데이터
matplotlib·seaborn·pandas를 한 번에 설치합니다. seaborn을 깔면 matplotlib·numpy·pandas는 의존성으로 따라오지만, 명시해 두는 편이 분명합니다. 클러스터 히트맵의 계층 군집은 SciPy (사이파이)가 필요한데, 이 역시 보통 같이 깔려 있습니다.
# pip로 설치 (가상환경 권장)
pip install matplotlib seaborn pandas
# 또는 conda
conda install -c conda-forge matplotlib seaborn pandas
import matplotlib, seaborn as sns, pandas as pd, numpy as np
print("matplotlib", matplotlib.__version__)
print("seaborn", sns.__version__)
matplotlib 3.11.0
seaborn 0.13.2
이제 설명용으로 작은 데이터 두 개를 만듭니다. 하나는 차등발현 결과 표(유전자별 log2 배수변화와 p-value), 다른 하나는 발현 행렬(유전자 × 샘플)입니다. 실제 분석이라면 앞쪽은 DESeq2·edgeR의 결과 테이블이고, 뒤쪽은 정규화한 카운트 행렬이 됩니다.
import numpy as np
import pandas as pd
rng = np.random.default_rng(7) # 재현성 고정 (시드 7)
n = 300 # 유전자 300개
# ── 차등발현 결과 표: 대부분은 변화 없고, 일부만 크게 오르내림 ──
log2fc = rng.normal(0, 1.0, n) # log2 배수변화 (대칭 분포)
log2fc[:15] += rng.normal(3, 0.4, 15) # 앞 15개 = 강하게 상향
log2fc[15:30] -= rng.normal(3, 0.4, 15) # 다음 15개 = 강하게 하향
pval = 10 ** (-np.abs(log2fc) * rng.uniform(0.8, 1.6, n)) # |FC| 클수록 작은 p
pval = np.clip(pval, 1e-12, 1.0) # p-value 범위 보정
deg = pd.DataFrame({
"gene": [f"G{i:03d}" for i in range(n)],
"log2FC": log2fc,
"pvalue": pval,
})
deg["neglog10p"] = -np.log10(deg["pvalue"]) # y축에 쓸 -log10(p)
deg.head(3)
gene log2FC pvalue neglog10p
0 G000 3.412847 1.073229e-04 3.969310
1 G001 3.001210 3.018501e-04 3.520282
2 G002 3.880644 9.142122e-06 5.039459
차등발현 그림에서 "유의(significant)" 여부는 보통 두 기준을 동시에 봅니다 — 배수변화가 충분히 크고(|log2FC| ≥ 1), p-value가 충분히 작은(p < 0.05) 유전자입니다. 이 둘을 합쳐 색칠용 라벨 열을 미리 만들어 둡니다.
# 유의 기준: |log2FC| >= 1 그리고 p < 0.05 → 상향/하향/비유의 3분류
fc_thr, p_thr = 1.0, 0.05
cond_up = (deg["log2FC"] >= fc_thr) & (deg["pvalue"] < p_thr)
cond_down = (deg["log2FC"] <= -fc_thr) & (deg["pvalue"] < p_thr)
deg["sig"] = np.select([cond_up, cond_down],
["up", "down"], default="ns") # ns = not significant
deg["sig"].value_counts()
sig
ns 268
up 17
down 15
Name: count, dtype: int64
다음은 발현 행렬입니다. 히트맵·분포 그림에 쓸 작은 행렬을 따로 만듭니다. 유전자 12개 × 샘플 10개(대조군 5와 처리군 5), 처리군에서 일부 유전자가 올라가도록 신호를 넣습니다.
# ── 발현 행렬: 유전자 12 × 샘플 10 (control 5 + treated 5) ──
genes = [f"GENE{i:02d}" for i in range(1, 13)]
samples = [f"C{i}" for i in range(1, 6)] + [f"T{i}" for i in range(1, 6)]
base = rng.normal(8, 1.2, size=(12, 10)) # 로그 발현값 바탕 (log2 스케일)
base[:4, 5:] += rng.normal(2.5, 0.4, (4, 5)) # 앞 4개 유전자 = 처리군서 상향
base[4:7, 5:] -= rng.normal(2.0, 0.4, (3, 5)) # 다음 3개 = 처리군서 하향
expr = pd.DataFrame(base, index=genes, columns=samples).round(2)
expr.iloc[:4, :6] # 일부만 미리보기
C1 C2 C3 C4 C5 T1
GENE01 7.42 6.88 9.10 8.05 7.61 10.33
GENE02 8.21 9.04 7.55 8.66 8.12 10.78
GENE03 6.95 7.80 8.43 7.21 8.90 9.52
GENE04 9.13 8.27 7.92 8.48 7.74 11.06

2. 볼케이노 플롯 — 산점도로 차등발현 한눈에
볼케이노 플롯은 x축에 log2 배수변화, y축에 −log10 p를 찍는 산점도입니다. 화산이 분출하듯 좌우 위쪽으로 점이 솟아오른 모양이라 이런 이름이 붙었습니다. 오른쪽 위는 "크게 올라가고 유의한" 유전자, 왼쪽 위는 "크게 내려가고 유의한" 유전자입니다.
seaborn의 scatterplot에 색 기준(hue)으로 아까 만든 sig 열을 넘기면, 상향·하향·비유의가 자동으로 다른 색이 됩니다.
import matplotlib.pyplot as plt
import seaborn as sns
sns.set_theme(style="whitegrid", font_scale=1.0) # 전역 테마
fig, ax = plt.subplots(figsize=(7, 6))
palette = {"up": "#E76F51", "down": "#2E7D6B", "ns": "#C9C4BA"} # 색 직접 지정
sns.scatterplot(data=deg, x="log2FC", y="neglog10p",
hue="sig", hue_order=["up", "down", "ns"],
palette=palette, s=28, edgecolor="none", ax=ax)
# 기준선: 세로 |log2FC|=1, 가로 p=0.05 (−log10 0.05 ≈ 1.30)
ax.axvline(-1, color="#999", ls="--", lw=1)
ax.axvline( 1, color="#999", ls="--", lw=1)
ax.axhline(-np.log10(0.05), color="#999", ls="--", lw=1)
ax.set_xlabel("log$_2$ fold change") # 아래첨자: $_2$
ax.set_ylabel("$-$log$_{10}$ $p$") # 통계기호 p는 수식($)으로 이탤릭
ax.set_title("Volcano plot — control vs treated", weight="bold")
ax.legend(title="유의성", frameon=True)
plt.tight_layout()
plt.savefig("volcano.png", dpi=300, bbox_inches="tight")
여기서 핵심은 세 가지입니다. hue에 범주 열을 주면 seaborn이 범례까지 자동으로 달아 줍니다. palette를 딕셔너리로 주면 "up은 코랄, down은 그린"처럼 색을 못 박을 수 있습니다(브랜드색 통일에 유용합니다). 축 라벨의 $-$log$_{10}$ $p$처럼 $...$로 감싼 부분은 LaTeX 수식으로 렌더되는데, 통계기호 p는 이렇게 수식 안에 넣어야 이탤릭으로 나옵니다.
이제 상위 유전자에 이름표를 답니다. seaborn에는 점 라벨링 기능이 따로 없어서, 이 부분은 matplotlib의 annotate로 직접 그립니다. −log10 p가 가장 큰 상·하향 유전자 몇 개만 골라 텍스트를 얹습니다.
# 상·하향에서 각각 가장 유의한 유전자 3개씩 라벨링
top = pd.concat([
deg[deg.sig == "up"].nlargest(3, "neglog10p"),
deg[deg.sig == "down"].nlargest(3, "neglog10p"),
])
for _, r in top.iterrows():
ax.annotate(r["gene"], (r["log2FC"], r["neglog10p"]),
xytext=(5, 4), textcoords="offset points", # 점에서 살짝 띄움
fontsize=9, fontweight="bold", color="#333")
fig.savefig("volcano_labeled.png", dpi=300, bbox_inches="tight")
annotate의 xytext=(5, 4)와 textcoords="offset points"는 "점에서 오른쪽 5pt·위 4pt 떨어진 곳에 글자를 놓아라"는 뜻입니다. 라벨이 점과 겹쳐 안 보이는 일을 막는 흔한 방법입니다. 라벨이 많아 서로 겹친다면 adjustText 같은 보조 패키지를 쓰지만, 보통은 상위 몇 개만 다는 걸로 충분합니다.
3. 클러스터 히트맵 — clustermap으로 발현 패턴 군집
히트맵은 발현 행렬을 색의 농담으로 보여 줍니다. 그냥 값을 칠하면 발현이 높은 유전자 행만 진하게 나와 패턴이 안 보이므로, 유전자별로 z-score 정규화(각 행을 평균 0·표준편차 1로)한 뒤 칠하는 게 표준입니다. seaborn의 clustermap은 여기에 더해 비슷한 행·열끼리 계층 군집(hierarchical clustering)으로 묶어 덴드로그램(dendrogram)까지 그려 줍니다.
# 행(유전자) 단위 z-score: (값 − 행평균) / 행표준편차
expr_z = expr.sub(expr.mean(axis=1), axis=0).div(expr.std(axis=1), axis=0)
g = sns.clustermap(
expr_z, cmap="vlag", center=0, # vlag = 파랑–흰–빨강 발산 팔레트
vmin=-2, vmax=2, # 색 범위 고정 (이상치에 안 휘둘림)
col_cluster=True, row_cluster=True, # 행·열 모두 군집
figsize=(8, 7), linewidths=.4,
cbar_kws={"label": "z-score"},
dendrogram_ratio=0.14,
)
g.ax_heatmap.set_xlabel("샘플")
g.ax_heatmap.set_ylabel("유전자")
g.figure.suptitle("발현 z-score 클러스터 히트맵", y=1.02, fontweight="bold")
g.savefig("clustermap.png", dpi=300, bbox_inches="tight")
center=0은 0을 색의 중앙(흰색)에 두라는 뜻이라, 상향(빨강)·하향(파랑)이 직관적으로 갈립니다. vmin·vmax로 색 범위를 ±2에 고정하면 극단값 한두 개가 전체 색을 잡아먹는 일을 막습니다. clustermap은 일반 heatmap과 달리 Figure 전체를 새로 만들어 반환하므로(g.figure로 접근), plt.subplots로 만든 축에 끼워 넣을 수 없다는 점만 기억하면 됩니다. 군집을 끄고 단순 히트맵만 원하면 sns.heatmap(expr_z, ...)을 씁니다.
덴드로그램을 보면 처리군 샘플(T1~T5)과 대조군(C1~C5)이 좌우로 갈려 묶이는 게 보입니다. 우리가 신호를 넣은 그대로, 발현 패턴만으로 두 조건이 분리된 셈입니다.

4. 박스·바이올린 플롯 — 그룹별 발현 분포
관심 유전자 하나의 발현이 그룹마다 어떻게 다른지는 박스플롯이나 바이올린 플롯으로 봅니다. 이 그림들은 데이터가 long 형식(한 행에 샘플·그룹·발현값 하나씩)일 때 자연스럽습니다. 발현 행렬을 melt로 녹이고, 샘플 이름에서 그룹을 뽑아냅니다.
# wide(유전자×샘플) → long, 그리고 샘플명 첫 글자로 그룹 추출
long = (expr.reset_index(names="gene")
.melt(id_vars="gene", var_name="sample", value_name="expr"))
long["group"] = long["sample"].str[0].map({"C": "control", "T": "treated"})
long.head(3)
gene sample expr group
0 GENE01 C1 7.42 control
1 GENE02 C1 8.21 control
2 GENE03 C1 6.95 control
먼저 바이올린 플롯입니다. 박스플롯이 사분위수(quartile)만 보여 준다면, 바이올린은 분포의 모양(밀도)까지 보여 줍니다. 상향 유전자 하나(GENE01)를 골라 두 그룹을 비교합니다.
sub = long[long["gene"] == "GENE01"] # 유전자 하나만
fig, ax = plt.subplots(figsize=(6, 5))
sns.violinplot(data=sub, x="group", y="expr",
hue="group", palette={"control": "#6BAB97", "treated": "#E76F51"},
legend=False, inner="box", ax=ax) # inner="box": 안에 박스도
sns.stripplot(data=sub, x="group", y="expr", # 실제 점도 겹쳐 찍기
color="#333", size=5, alpha=.7, ax=ax)
ax.set_xlabel(""); ax.set_ylabel("정규화 발현값 (log$_2$)")
ax.set_title("GENE01 — 그룹별 발현 분포", fontweight="bold")
plt.tight_layout()
plt.savefig("violin.png", dpi=300, bbox_inches="tight")
여기서 seaborn 0.13의 바뀐 규칙이 처음 등장합니다 — x축과 같은 변수를 hue에도 넘기고(hue="group"), legend=False로 중복 범례를 끕니다. 예전(0.12 이전)에는 palette만 줘도 각 박스가 다른 색이었지만, 0.13부터는 hue 없이 팔레트만 주면 경고가 뜹니다(아래 두 번째 에러 참고). stripplot을 겹쳐 실제 데이터 점을 같이 찍으면, 샘플 수가 적을 때(여기선 그룹당 5개) 분포 모양이 과장돼 보이는 걸 정직하게 보완할 수 있습니다.
박스플롯도 호출만 바꾸면 됩니다. 여러 유전자를 한 축에 늘어놓을 때 특히 박스플롯이 깔끔합니다.
# 유전자 4개를 한 박스플롯에 (x=유전자, 색=그룹)
sel = long[long["gene"].isin(["GENE01", "GENE02", "GENE05", "GENE06"])]
fig, ax = plt.subplots(figsize=(8, 5))
sns.boxplot(data=sel, x="gene", y="expr", hue="group",
palette={"control": "#6BAB97", "treated": "#E76F51"}, ax=ax)
ax.set_xlabel(""); ax.set_ylabel("정규화 발현값 (log$_2$)")
ax.set_title("유전자별·그룹별 발현 비교", fontweight="bold")
ax.legend(title="그룹")
plt.tight_layout()
plt.savefig("boxplot.png", dpi=300, bbox_inches="tight")
hue="group"을 주면 유전자마다 control·treated 박스가 나란히 두 개씩 그려집니다. 상향 유전자(GENE01·GENE02)는 treated 박스가 위로, 하향 유전자(GENE05·GENE06)는 아래로 내려간 게 한눈에 들어옵니다.
5. facet 다중 패널 — catplot으로 여러 유전자 한 번에
유전자가 여러 개면 한 그림에 욱여넣기보다 작은 패널을 격자로 나열하는 게 깔끔합니다. 이걸 facet (또는 small multiples)이라 부릅니다. seaborn의 catplot(범주형)·relplot(관계형)은 col·row에 열 이름만 주면 패널을 자동으로 쪼개 줍니다. catplot은 그림(figure) 수준 함수라 plt.subplots 없이 자체적으로 격자를 만듭니다.
# 유전자 6개를 패널 6개로 (가로 3 × 세로 2), 각 패널은 박스플롯
sel6 = long[long["gene"].isin(
["GENE01", "GENE02", "GENE03", "GENE05", "GENE06", "GENE08"])]
g = sns.catplot(
data=sel6, x="group", y="expr",
hue="group", palette={"control": "#6BAB97", "treated": "#E76F51"},
col="gene", col_wrap=3, kind="box", # col=패널 기준, col_wrap=한 줄에 3개
height=2.8, aspect=0.9, legend=False,
)
g.set_titles("{col_name}") # 각 패널 제목 = 유전자명
g.set_axis_labels("", "발현값 (log$_2$)")
g.figure.suptitle("유전자별 발현 분포 (facet)", y=1.03, fontweight="bold")
g.savefig("facet.png", dpi=300, bbox_inches="tight")
col="gene"이 패널을 유전자별로 쪼개고, col_wrap=3이 세 개마다 줄을 바꿉니다. kind="box"를 "violin"·"bar"·"strip"으로 바꾸면 같은 격자에 다른 그림이 그려집니다. catplot이 반환하는 건 축이 아니라 FacetGrid 객체라, 제목·축 라벨은 g.set_titles·g.set_axis_labels처럼 격자 메서드로 한꺼번에 손봅니다. 패널이 너무 많아지면 height·aspect로 한 패널 크기를 줄여 전체가 화면에 들어오게 조절합니다.

6. 스타일·한글 폰트·저장 — 발표용 마무리
마지막은 실무 설정입니다. 발표 슬라이드나 논문 그림으로 쓰려면 한글이 깨지지 않아야 하고, 해상도와 형식을 챙겨야 합니다.
matplotlib은 기본 폰트에 한글 글리프가 없어 한글 라벨이 네모(□□□)로 깨집니다. rcParams로 한글 폰트를 한 번 지정하면 이후 모든 그림에 적용됩니다. 윈도우는 Malgun Gothic, macOS는 AppleGothic, 리눅스는 나눔고딕 등을 씁니다. 음수 부호가 깨지는 것도 함께 막아 줍니다.
import matplotlib.pyplot as plt
# 운영체제별 한글 폰트 (있는 것 하나 고름)
plt.rcParams["font.family"] = ["Malgun Gothic", "AppleGothic", "NanumGothic"]
plt.rcParams["axes.unicode_minus"] = False # 음수 부호(−) 깨짐 방지
plt.rcParams["figure.dpi"] = 110 # 화면 미리보기 해상도
plt.rcParams["savefig.bbox"] = "tight" # 저장 시 여백 자동 정리
seaborn의 전역 테마도 그림 전체 인상을 좌우합니다. set_theme의 style(배경·격자)과 context(글자·선 두께)를 조합합니다. 발표용은 글자를 키운 "talk"가, 논문용은 기본 "notebook"이 무난합니다.
# style: white / whitegrid / dark / darkgrid / ticks
# context: paper / notebook / talk / poster (오른쪽으로 갈수록 요소 큼)
sns.set_theme(style="ticks", context="talk", palette="muted")
저장은 savefig로 합니다. 비트맵인 PNG는 dpi로 해상도를 정하고(인쇄·발표는 300 이상), 벡터인 SVG·PDF는 dpi와 무관하게 무한 확대해도 안 깨져 논문 그림에 적합합니다.
fig.savefig("figure.png", dpi=300, bbox_inches="tight") # 발표·웹: 고해상도 PNG
fig.savefig("figure.svg", bbox_inches="tight") # 논문: 벡터 SVG
fig.savefig("figure.pdf", bbox_inches="tight") # 논문: 벡터 PDF
bbox_inches="tight"는 그림 둘레의 불필요한 여백을 잘라 줍니다. 라벨이 그림 밖으로 잘려 나간다면 이 옵션이 거의 해결합니다. 색약(color vision deficiency)을 배려하려면 palette="colorblind"나 발산 팔레트 "vlag"·"icefire"를 쓰는 게 좋습니다.
자주 발생하는 에러 & 해결
- 한글 라벨이 네모(□□□)로 깨짐 — matplotlib 기본 폰트에 한글 글리프가 없어서입니다. 6절처럼
plt.rcParams["font.family"]에 설치된 한글 폰트를 지정하세요. 폰트 캐시가 옛 정보를 들고 있으면 한 번 지운 뒤(matplotlib.font_manager._load_fontmanager(try_read_cache=False)) 재시작하면 잡힙니다. 음수 부호까지 깨지면axes.unicode_minus를False로 둡니다. - seaborn 0.13:
palettewithouthue경고 —sns.boxplot(data=df, x="group", y="v", palette="Set2")처럼hue없이 팔레트만 주면FutureWarning이 뜨고 0.14에서 막힙니다. 0.13부터 범주형 함수는hue가 없으면 단색으로 그리는 게 기본입니다. 해결은 x축 변수를hue에도 넘기고(hue="group")legend=False로 중복 범례를 끄는 것입니다. 이 글의 모든 박스·바이올린 코드가 이 패턴을 씁니다. clustermap을subplots축에 못 넣음 —fig, ax = plt.subplots(); sns.clustermap(..., ax=ax)는 에러입니다.clustermap은 자체적으로 Figure 전체(히트맵·덴드로그램·컬러바)를 만들어ClusterGrid를 반환하기 때문입니다. 기존 축에 넣고 싶으면 군집 없는sns.heatmap(..., ax=ax)을 쓰고, 군집이 꼭 필요하면clustermap을 독립 그림으로 두세요. 저장은g.savefig(...)로 합니다.- 그림이 안 뜨거나 빈 창만 나옴 — 스크립트(.py)에서는
plt.show()를 마지막에 호출해야 창이 뜹니다. 주피터에서는 셀의 마지막 객체가 자동 표시되지만, 같은 셀에서 그림을 그리고 또 다른 걸 그리면 앞 그림이 먹힐 수 있으니 셀을 나누거나fig를 명시적으로 다루세요. 저장만 하고 화면 표시가 필요 없으면matplotlib.use("Agg")로 백엔드를 비대화형으로 바꾸면 됩니다. - 라벨·제목이 그림 밖으로 잘림 —
bbox_inches="tight"를savefig에 주거나, 그리기 전에plt.tight_layout()을 호출하세요. facet (catplot)에서suptitle이 패널과 겹치면y=1.03처럼 위로 올리고, 그래도 잘리면g.figure.subplots_adjust(top=0.9)로 위 여백을 줍니다. - 점이 너무 많아 산점도가 뭉개짐(overplotting) — 볼케이노에 유전자가 수만 개면 점이 겹쳐 새까매집니다.
s로 점을 작게(s=8),alpha로 반투명하게(alpha=0.4) 주거나, 비유의 점만rasterized=True로 래스터화해 SVG 용량을 줄입니다. 유의 유전자만 색을 진하게, 나머지는 회색으로 깔면 가독성이 올라갑니다.
확장 — 더 해 볼 것
seaborn.objects인터페이스 — 0.12부터 들어온 새 문법(so.Plot(df, x=..., y=...).add(so.Dot()))으로, ggplot2처럼 레이어를+가 아닌.add()로 쌓습니다. 아직 실험적(experimental)이지만 복잡한 그림을 선언적으로 조립하기에 좋습니다.- 상호작용 그림 — 발표용 정적 그림 너머로,
plotly·bokeh를 쓰면 마우스를 올려 유전자 이름을 보는 인터랙티브 볼케이노를 만들 수 있습니다. 탐색 단계에서 유용합니다. - 전용 패키지 — 차등발현 시각화에 특화된
pydeseq2·sanbomics의 볼케이노 헬퍼, 라벨 겹침을 자동으로 푸는adjustText, 출판용 조판을 돕는matplotlib의constrained_layout등을 함께 쓰면 손이 줄어듭니다. - PCA·UMAP 산점도 — 샘플 전체 구조를 보는 차원축소 그림은 단일세포 분석에서 특히 중요합니다. scanpy 글에서
sc.pl.umap으로 이어집니다. - 색·테마 다듬기 — 저널마다 요구하는 그림 규격(폰트 크기·선 두께·색)이 다릅니다.
rcParams를 묶은 스타일 시트(plt.style.use("my.mplstyle"))를 하나 만들어 두면 모든 그림을 일관되게 찍을 수 있습니다.
• (데이터 정제) pandas로 발현 행렬 정제하기 — 그릴 데이터를 분석에 넣기 직전까지 다듬는 전 단계
• (R로 같은 그래프) ggplot2로 바이오 데이터 그래프 그리기 — 산점도·막대·박스를 R로, 이 글과 1:1 비교
• (다음 단계) scanpy로 단일세포 RNA-seq 분석하기 — UMAP·클러스터 등 더 큰 데이터의 시각화
• (파이썬 입문) Biopython 입문 — FASTA·FASTQ 파싱 — 바이오 데이터를 파이썬으로 다루는 첫걸음
References
- Hunter, J. D. (2007). Matplotlib: A 2D Graphics Environment. Computing in Science & Engineering, 9(3), 90–95. (matplotlib 원논문)
- Waskom, M. L. (2021). seaborn: statistical data visualization. Journal of Open Source Software, 6(60), 3021. (seaborn 원논문)
- The Matplotlib development team. (2026). Matplotlib documentation — v3.11. matplotlib.org/stable
- Waskom, M. seaborn 0.13 release notes — 범주형 함수 재작성·
hue/팔레트 규칙 변경·native_scale. whatsnew/v0.13.0 - seaborn. seaborn.clustermap — 계층 군집 히트맵 API. clustermap docs
Pipette & Pipeline · A bio portfolio journal
'Code Bench > Python for Bio' 카테고리의 다른 글
| numpy·scipy로 생물 데이터 수치·통계 — 배열 연산·가설검정 (0) | 2026.07.01 |
|---|---|
| scikit-learn으로 생물 데이터 머신러닝 — 분류·교차검증·ROC (0) | 2026.06.29 |
| scanpy로 단일세포 RNA-seq 분석하기 — QC부터 UMAP·클러스터까지 (Python for Bio) (0) | 2026.06.23 |
| pandas로 생물 데이터 정제하기 — 발현 행렬·메타데이터 실전 패턴 (Python for Bio) (1) | 2026.06.23 |
| Biopython 입문 — FASTA·FASTQ 파싱부터 서열 통계까지 (Python for Bio) (0) | 2026.06.23 |