Análisis de datos de expresión génica mediante aprendizaje automático
Al finalizar este tema
Con scikit-learn, podrá realizar el agrupamiento (K-means) de datos de expresión génica, reducir la dimensionalidad mediante PCA y clasificar muestras tumorales frente a normales.
¿Qué es el aprendizaje automático?
A medida que acumula experiencia en el laboratorio, podrá determinar que "este patrón de bandas es una mutación" simplemente observando las fotografías del gel. El aprendizaje automático consiste en enseñar a las computadoras esta capacidad de "reconocimiento de patrones" mediante datos.
ML común en bioinformática:
- Aprendizaje no supervisado: encuentra patrones en los datos sin etiquetas (agrupamiento, PCA)
- Aprendizaje supervisado: entrena modelos de clasificación con datos etiquetados (tumor vs. normal)
Preparación de datos: Datos de expresión génica simulados
Se generan datos sintéticos que imitan datos reales de RNA-seq. En esta situación, se mide la expresión de 20 genes en 100 muestras (50 normales + 50 tumorales).
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 # Genes_01~05 hacia arribatumor_expr[:, 15:] -= 2.0 # Genes_16~20 hacia abajo
expression = np.vstack([normal_expr, tumor_expr])df = pd.DataFrame(expression, columns=gene_names)df["Label"] = sample_labels
print(f"Tamaño de datos: {df.shape}")print(f"Muestras: Normal {sum(df['Label']=='Normal')}, Tumor {sum(df['Label']=='Tumor')}")print(f"\nPrimeras 5 filas:")print(df.head().to_string())
assert df.shape == (100, 21)assert sum(df["Label"] == "Normal") == 50assert sum(df["Label"] == "Tumor") == 50PCA: Reducción de la dimensionalidad de datos a 2D
Se reduce la dimensionalidad de los datos de expresión de 20 genes a una representación bidimensional para facilitar su visualización. El análisis de componentes principales (PCA) identifica las direcciones con la mayor varianza en los datos.
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. Estandarización (media 0, varianza 1)scaler = StandardScaler()X_scaled = scaler.fit_transform(expression)
# 2. Aplicar PCApca = PCA(n_components=2)X_pca = pca.fit_transform(X_scaled)
print(f"Dimensiones originales: {expression.shape[1]} genes")print(f"Dimensiones reducidas: {X_pca.shape[1]} componentes principales")print(f"Varianza explicada: PC1={pca.explained_variance_ratio_[0]:.1%}, PC2={pca.explained_variance_ratio_[1]:.1%}")print(f"Total: {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("Guardado completo de pca_plot.png")assert X_pca.shape == (100, 2)Clustering K-medias: Aprendizaje no supervisado
Identifica grupos de datos sin necesidad de etiquetas. K-medias es uno de los algoritmos de clustering más intuitivos.
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: clasificación en 2 clústereskmeans = KMeans(n_clusters=2, random_state=42, n_init=10)clusters = kmeans.fit_predict(X_scaled)
# Verificar qué tan bien coinciden los clústeres con las etiquetas realesfrom sklearn.metrics import adjusted_rand_scoreari = adjusted_rand_score(true_labels, clusters)print(f"Clúster 0: {sum(clusters == 0)} muestras")print(f"Clúster 1: {sum(clusters == 1)} muestras")print(f"Índice de Rand ajustado: {ari:.3f} (1.0 = coincidencia perfecta)")
assert len(set(clusters)) == 2assert ari > 0.5print("El agrupamiento K-means tuvo éxito en la distinción normal/tumoral")Clasificación: Aprendizaje supervisado
En esta ocasión, se entrena el modelo con un conjunto de datos en el que se conoce la etiqueta correcta (Normal/Tumor) y se utilizan los datos para clasificar nuevas muestras.
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. División de datos (80% entrenamiento, 20% prueba)X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42, stratify=y)
# 2. Estandarizaciónscaler = StandardScaler()X_train_scaled = scaler.fit_transform(X_train)X_test_scaled = scaler.transform(X_test)
# 3. Entrenamiento de Random Forestclf = RandomForestClassifier(n_estimators=100, random_state=42)clf.fit(X_train_scaled, y_train)
# 4. Predicción y Evaluacióny_pred = clf.predict(X_test_scaled)accuracy = accuracy_score(y_test, y_pred)
print(f"Precisión: {accuracy:.1%}")print(f"\nDatos de entrenamiento: {len(X_train)} muestras, datos de prueba: {len(X_test)} muestras")print(f"\nInforme de clasificación:")print(classification_report(y_test, y_pred, target_names=["Normal", "Tumor"]))
assert accuracy > 0.8assert len(X_train) == 80assert len(X_test) == 20Importancia de las características: ¿Qué genes son importantes?
El modelo de Bosque Aleatorio identifica los genes (características) que más contribuyen a la clasificación.
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:] # Top 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"Gen más importante: {top_gene}")print(f"Top 5 genes: {[gene_names[i] for i in sorted_idx[-5:]]}")
assert len(importances) == 20assert sum(importances) > 0.99Prueba tú mismo (Ejemplo desvanecido)
Completa los espacios en blanco para completar el patrón de ajuste y predicción de scikit-learn.
from sklearn.cluster import KMeansfrom sklearn.preprocessing import StandardScalerscaler = StandardScaler()X_scaled = scaler.fit_transform(data)kmeans = KMeans(n_clusters=)clusters = kmeans.(X_scaled)
Errores comunes y soluciones
P: ValueError: could not convert string to float
Hay columnas de texto (por ejemplo, nombres de genes, etiquetas) en los datos. Selecciona solo las columnas numéricas con df.select_dtypes(include=[np.number]).
P: La precisión es solo del 50%
Esto significa que los datos no son diferentes de los datos aleatorios. Verifica si se realizó la estandarización (StandardScaler) y si las características (genes) tienen patrones suficientemente distintos.
P: ConvergenceWarning: Number of distinct clusters found
El algoritmo K-means no ha convergido. Aumenta los valores de n_init=10 y max_iter=300, o verifica el escalado de los datos.
¡Felicidades! Has completado los 5 temas del MVP de la ruta común de DevBench y la ruta A-1.