Back to List

Visualizing Bio Data with Python

How to draw Kaplan-Meier survival curves, volcano plots, and gene expression heatmaps with Python matplotlib.

Intermediate
|
120min
|
Verified (2026-06)
matplotlibKaplan-Meiervolcano plotheatmapSurvival AnalysisDEG
Progress0/12 (0%)

Visualizing Bio Data with Python

After Completing This Topic

You'll be able to draw three graphs commonly used in bio research with matplotlib: survival curves, volcano plots, and heatmaps.


matplotlib Basics: Your First Graph

matplotlib is Python's go-to visualization library. You can draw a graph with a single plt.plot().

python
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
import numpy as np
# Cell growth curve (exponential growth)
hours = np.array([0, 2, 4, 6, 8, 10, 12, 24])
cell_count = np.array([1000, 1200, 1800, 3200, 6000, 11000, 22000, 500000])
fig, ax = plt.subplots(figsize=(8, 5))
ax.plot(hours, cell_count, "o-", color="#2E86AB", linewidth=2, markersize=6)
ax.set_xlabel("Time (hours)", fontsize=12)
ax.set_ylabel("Cell Count", fontsize=12)
ax.set_title("Cell Growth Curve", fontsize=14, fontweight="bold")
ax.set_yscale("log")
ax.grid(True, alpha=0.3)
fig.tight_layout()
fig.savefig("growth_curve.png", dpi=150)
plt.close(fig)
assert len(hours) == 8
assert cell_count[-1] == 500000
print("growth_curve.png saved")

Bar Chart: Comparing GC Content Across Genes

python
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
import numpy as np
def calculate_gc(seq: str) -> float:
seq = seq.upper()
return (seq.count("G") + seq.count("C")) / len(seq) * 100
genes = {
"BRCA1": "ATGGATTTATCTGCTCTTCGCGTTGAAGAAGTACAAAATGTC",
"TP53": "ATGGAGGAGCCGCAGTCAGATCCTAGCGTGAGTTTGCTGTGA",
"EGFR": "ATGCGACCCTCCGGGACGGCCGGGGCAGCGCTCCTGGCGCTG",
"MYC": "ATGCCCCTCAACGTTAGCTTCACCAACAGGAACTATGACCTCG",
"KRAS": "ATGACTGAATATAAACTTGTGGTAGTTGGAGCTGGTGGCGTAG",
}
names = list(genes.keys())
gc_values = [calculate_gc(seq) for seq in genes.values()]
colors = ["#E74C3C" if gc > 60 else "#2E86AB" for gc in gc_values]
fig, ax = plt.subplots(figsize=(8, 5))
bars = ax.bar(names, gc_values, color=colors, edgecolor="white", linewidth=0.5)
ax.axhline(y=60, color="#E74C3C", linestyle="--", alpha=0.5, label="GC > 60% threshold")
ax.set_ylabel("GC Content (%)", fontsize=12)
ax.set_title("GC Content by Gene", fontsize=14, fontweight="bold")
ax.legend()
ax.set_ylim(0, 100)
fig.tight_layout()
fig.savefig("gc_bar_chart.png", dpi=150)
plt.close(fig)
assert len(gc_values) == 5
assert all(0 <= gc <= 100 for gc in gc_values)
print(f"GC values: {[f'{gc:.1f}%' for gc in gc_values]}")

Kaplan-Meier Survival Curve

Survival analysis is the most important visualization in clinical trials. It compares survival rates between two groups (treatment vs control).

python
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
import numpy as np
np.random.seed(42)
# Generate simulated clinical data
n_patients = 50
treatment_times = np.sort(np.random.exponential(scale=24, size=n_patients))
control_times = np.sort(np.random.exponential(scale=16, size=n_patients))
def kaplan_meier(times, max_time=48):
times = times[times <= max_time]
n = len(times)
km_times = [0]
km_survival = [1.0]
at_risk = n
for t in sorted(set(times)):
events = np.sum(times == t)
survival = km_survival[-1] * (1 - events / at_risk)
km_times.append(t)
km_survival.append(survival)
at_risk -= events
return np.array(km_times), np.array(km_survival)
t_times, t_surv = kaplan_meier(treatment_times)
c_times, c_surv = kaplan_meier(control_times)
fig, ax = plt.subplots(figsize=(8, 5))
ax.step(t_times, t_surv, where="post", color="#2E86AB", linewidth=2, label="Treatment (n=50)")
ax.step(c_times, c_surv, where="post", color="#E74C3C", linewidth=2, label="Control (n=50)")
ax.fill_between(t_times, t_surv, step="post", alpha=0.1, color="#2E86AB")
ax.fill_between(c_times, c_surv, step="post", alpha=0.1, color="#E74C3C")
ax.set_xlabel("Time (months)", fontsize=12)
ax.set_ylabel("Survival Probability", fontsize=12)
ax.set_title("Kaplan-Meier Survival Curve", fontsize=14, fontweight="bold")
ax.legend(fontsize=11, loc="lower left")
ax.set_xlim(0, 48)
ax.set_ylim(0, 1.05)
ax.grid(True, alpha=0.3)
fig.tight_layout()
fig.savefig("kaplan_meier.png", dpi=150)
plt.close(fig)
assert t_surv[0] == 1.0
assert c_surv[0] == 1.0
assert len(t_times) > 1
print("kaplan_meier.png saved")

Volcano Plot: Visualizing Differentially Expressed Genes

A volcano plot shows genes with statistically significant expression changes in RNA-seq data at a glance. The X-axis is the magnitude of change (log2 fold change), and the Y-axis is statistical significance (-log10 p-value).

python
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
import numpy as np
np.random.seed(42)
n_genes = 500
log2fc = np.random.normal(0, 1.5, n_genes)
pvalues = 10 ** (-np.abs(log2fc) * np.random.uniform(0.5, 3, n_genes))
fc_threshold = 1.0
p_threshold = 0.05
colors = []
for fc, p in zip(log2fc, pvalues):
if p < p_threshold and fc > fc_threshold:
colors.append("#E74C3C") # Upregulated
elif p < p_threshold and fc < -fc_threshold:
colors.append("#2E86AB") # Downregulated
else:
colors.append("#CCCCCC") # Not significant
neg_log_p = -np.log10(pvalues)
fig, ax = plt.subplots(figsize=(8, 6))
ax.scatter(log2fc, neg_log_p, c=colors, s=10, alpha=0.7, edgecolors="none")
ax.axhline(-np.log10(p_threshold), color="gray", linestyle="--", alpha=0.5)
ax.axvline(fc_threshold, color="gray", linestyle="--", alpha=0.5)
ax.axvline(-fc_threshold, color="gray", linestyle="--", alpha=0.5)
n_up = sum(1 for c in colors if c == "#E74C3C")
n_down = sum(1 for c in colors if c == "#2E86AB")
ax.set_xlabel("logโ‚‚ Fold Change", fontsize=12)
ax.set_ylabel("-logโ‚โ‚€ p-value", fontsize=12)
ax.set_title(f"Volcano Plot (โ†‘{n_up} โ†“{n_down} DEGs)", fontsize=14, fontweight="bold")
ax.grid(True, alpha=0.2)
fig.tight_layout()
fig.savefig("volcano_plot.png", dpi=150)
plt.close(fig)
assert n_up > 0
assert n_down > 0
print(f"Upregulated: {n_up}, Downregulated: {n_down}")

Heatmap: Gene Expression Patterns

A heatmap compares gene expression levels across multiple samples using colors.

python
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
import numpy as np
np.random.seed(42)
gene_names = ["BRCA1", "TP53", "EGFR", "MYC", "KRAS", "PTEN", "RB1", "APC"]
sample_names = ["Normal_1", "Normal_2", "Tumor_1", "Tumor_2", "Tumor_3"]
expression = np.random.randn(len(gene_names), len(sample_names))
expression[:, 2:] += np.random.uniform(0.5, 2.0, (len(gene_names), 3))
fig, ax = plt.subplots(figsize=(8, 6))
im = ax.imshow(expression, cmap="RdBu_r", aspect="auto", vmin=-3, vmax=3)
ax.set_xticks(range(len(sample_names)))
ax.set_xticklabels(sample_names, rotation=45, ha="right", fontsize=10)
ax.set_yticks(range(len(gene_names)))
ax.set_yticklabels(gene_names, fontsize=10)
ax.set_title("Gene Expression Heatmap", fontsize=14, fontweight="bold")
fig.colorbar(im, ax=ax, label="Z-score", shrink=0.8)
fig.tight_layout()
fig.savefig("heatmap.png", dpi=150)
plt.close(fig)
assert expression.shape == (8, 5)
print(f"Heatmap: {expression.shape[0]} genes ร— {expression.shape[1]} samples")

Try It Yourself (Faded Example)

Fill in the blanks to complete a scatter plot.

Fill in the Blankspython
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
x = [1, 2, 3, 4, 5]
y = [2.1, 3.9, 6.2, 7.8, 10.1]
fig, ax = plt.subplots()
ax.(x, y, color="blue")
ax.set_xlabel("Concentration")
ax.set_ylabel("")
fig.savefig("scatter.png")
plt.close(fig)

Common Errors & Solutions

Q: Graph doesn't show, UserWarning: Matplotlib is currently using agg

In server environments (Colab, SSH, etc.), call matplotlib.use("Agg") before importing plt, and use fig.savefig() to save to file instead of plt.show().

Q: Non-ASCII title characters are garbled

Add plt.rcParams['font.family'] = 'NanumGothic'. In Colab, install fonts first with !apt-get install -y fonts-nanum and restart the runtime.

Q: Labels are cut off at the edges

Call fig.tight_layout() before savefig(). This solves most clipping issues.


In the next article, we'll learn to classify gene expression data with machine learning.

๐Ÿ’ฌ Questions & Comments

0 comments

You can post without signing in. Guest comments cannot be edited or deleted by their author.

0/2000

Loading...