Visualización de datos biológicos con Python
Al finalizar este tema
Con matplotlib, podrá crear tres tipos de gráficos que se utilizan habitualmente en la investigación biológica: curvas de supervivencia, gráficos de dispersión (volcano plots) y mapas de calor.
Introducción a matplotlib: primer gráfico
matplotlib es una de las bibliotecas de visualización más utilizadas en Python. plt.plot() Permite generar gráficos con una sola línea de código.
import matplotlibmatplotlib.use("Agg")import matplotlib.pyplot as pltimport numpy as np
# Curva de crecimiento celular (crecimiento exponencial)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) == 8assert cell_count[-1] == 500000print("growth_curve.png guardado correctamente")Gráfico de barras: Comparación del contenido de GC de varios genes
import matplotlibmatplotlib.use("Agg")import matplotlib.pyplot as pltimport 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) == 5assert all(0 <= gc <= 100 for gc in gc_values)print(f"GC values: {[f'{gc:.1f}%' for gc in gc_values]}")Curva de supervivencia de Kaplan-Meier
El análisis de supervivencia es una de las visualizaciones más importantes en los ensayos clínicos. Compara las tasas de supervivencia entre dos grupos: el grupo de tratamiento y el grupo de control.
import matplotlibmatplotlib.use("Agg")import matplotlib.pyplot as pltimport numpy as np
np.random.seed(42)
# Generar datos clínicos simuladosn_patients = 50treatment_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.0assert c_surv[0] == 1.0assert len(t_times) > 1print("kaplan_meier.png guardado correctamente")Gráfico de volcán: visualización de genes con expresión diferencial
Un gráfico de volcán permite visualizar de forma rápida los genes que presentan cambios significativos en su expresión a partir de datos de secuenciación de ARN (RNA-seq). El eje X representa la magnitud del cambio (log2 fold change) y el eje Y, la significancia estadística (-log10 del valor p).
import matplotlibmatplotlib.use("Agg")import matplotlib.pyplot as pltimport 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.0p_threshold = 0.05
colors = []for fc, p in zip(log2fc, pvalues): if p < p_threshold and fc > fc_threshold: colors.append("#E74C3C") # Regulación al alza elif p < p_threshold and fc < -fc_threshold: colors.append("#2E86AB") # Regulación a la baja else: colors.append("#CCCCCC") # No significativo
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 > 0assert n_down > 0print(f"Al alza: {n_up}, a la baja: {n_down}")Mapa de calor: patrones de expresión génica
El mapa de calor compara los niveles de expresión génica en varias muestras mediante el uso de colores.
import matplotlibmatplotlib.use("Agg")import matplotlib.pyplot as pltimport 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")Prueba tú mismo (Ejemplo desvanecido)
Completa los espacios en blanco para completar el diagrama de dispersión.
import matplotlibmatplotlib.use("Agg")import matplotlib.pyplot as pltx = [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)
Errores comunes y soluciones
P: El gráfico no se muestra y UserWarning: Matplotlib is currently using agg
En entornos de servidor (Colab, SSH, etc.), llama a matplotlib.use("Agg") antes de importar plt y guarda los archivos usando fig.savefig() en lugar de plt.show().
P: El título en coreano se muestra incorrectamente
Agrega plt.rcParams['font.family'] = 'NanumGothic'. En Colab, debes reiniciar el entorno de ejecución después de !apt-get install -y fonts-nanum.
P: El gráfico está recortado y las etiquetas no son visibles
Llama a fig.tight_layout() antes de savefig(). La mayoría de los problemas de recorte se resuelven.
En el siguiente texto, aprenderás a clasificar datos de expresión génica mediante aprendizaje automático.