El punto de partida

Todo lo que usamos procede de TCGA (The Cancer Genome Atlas), el consorcio genómico del National Cancer Institute y el National Human Genome Research Institute estadounidenses, en su categoría de acceso abierto.

01 · oncoseq_expresion.parquet

Matriz de expresión RNA-Seq. 1.344 muestras tumorales y 336 de tejido sano, de cinco órganos.

La etiqueta de cada muestra. 320 pacientes aportaron tumor y sano; tejido de 121 centros distintos.

03 · gene_names.csv

Traduce los identificadores anónimos a genes reales. 20.502 con símbolo oficial HGNC.

Los tres ficheros fueron renombrados y reempaquetados por el equipo: los enlaces llevan a la fuente pública, no al archivo con ese nombre.

Lo que hay dentro

1.680 muestras de cinco tejidos distintos. De cada uno hay tejido tumoral y tejido sano del mismo paciente — y esa pareja es lo que permite entrenar el primer modelo.

Todos los tumores son primarios (código 01 del barcode TCGA) y las muestras sanas son tejido adyacente del mismo paciente (código 11). El modelo no ha visto metástasis. La proporción tumor/sano es exactamente 80/20 en los cinco tejidos, por diseño de la extracción.

Tumoral Sano (adyacente)
560
360
295
260
205
MamaBRCA
RiñónKIRC
PulmónLUAD
PróstataPRAD
ColonCOAD
Muestras
1.680
Tumorales
1.344
Sanas
336
Genes
20.531
Pacientes con ambas
320

El examen con las respuestas

320 pacientes aportaron dos muestras: una tumoral y una sana. Si caen en lados distintos de la partición, el modelo reconoce al paciente, no al cáncer. Saca buena nota sin haber aprendido nada.

División aleatoria

Las muestras se reparten al azar. El mismo paciente acaba a ambos lados y las métricas salen infladas.

Agrupación por paciente

GroupShuffleSplit para el hold-out y StratifiedGroupKFold para la validación cruzada: todas las muestras de una persona van juntas.

Pacientes con doble muestra
320
Compartidos · al azar
107
Compartidos · agrupado
0

Cómo está construido

01 · Fuentes de datos

TCGA · GDC · perfiles de expresión génica

1.680 muestras RNA-Seq · 20.531 genes · acceso público

02 · Pipeline de datos y modelado

Limpieza, EDA y entrenamiento

Selección de genes por ANOVA · partición por paciente · modelos versionados en joblib

03 · Base de datos

Supabase · PostgreSQL

Dataset y predicciones almacenadas

04 · Aplicación

Streamlit

Predicción y explicabilidad en directo. Desplegada en Render.

Usted está aquí
05 · Web del proyecto

Presentación, hallazgos y límites

Publicada en GitHub Pages.

Flujo de datos Lectura y escritura Despliegue

Dos cuadernos, dos preguntas

El primero explora qué hay en los datos. El segundo decide qué genes importan y qué modelo usar.

01_exploring_eda.ipynb
  • [1]Carga y análisis exploratorio. Los datos ya venían limpios de origen.
  • [2]Matriz de expresión cruzando expresion y metadatos: tumor o sano, y de qué cohorte.
  • [3]PCA para ver si tumor y tejido sano formaban grupos separados.
  • [4]Ranking de los 50 genes más variables entre muestras.
  • [5]Probamos a eliminar los genes que valen cero. Mismo resultado — decidimos conservarlos.

→ dataset_clean · gráficos y rutas listos para el notebook 02

02_feature_selection_&_modeling.ipynb
  • [1]Selección de genes por ANOVA F-test, con el número de genes optimizado entre 500 y 3.000.
  • [2]Escalado y selección dentro del pipeline, para que se recalculen en cada partición.
  • [3]Evaluación de 7 clasificadores: HistGradientBoosting, KNN, regresión logística (L2, L1 y PCA), SVM lineal y Random Forest.
  • [4]Validación con partición 80/20 agrupada por paciente y 5-Fold Cross-Validation.

→ modelos entrenados y versionados en joblib

Dos decisiones, en código

Todo el proyecto está en el repositorio. Estos dos fragmentos son los que sostienen la validez de los resultados.

La partición que evita la trampa

Ningún paciente puede estar a los dos lados. Y lo comprobamos con un assert: si falla, el cuaderno se para.

02_feature_selection_modeling.ipynb
# Validación cruzada agrupada por paciente
cv = StratifiedGroupKFold(n_splits=5, shuffle=True,
                           random_state=RANDOM_STATE)
 
# Conjunto apartado, también por paciente
gss = GroupShuffleSplit(n_splits=1, test_size=0.2,
                        random_state=RANDOM_STATE)
train_idx, test_idx = next(gss.split(X, binary_target,
                                     groups=groups))
 
assert set(groups[train_idx]).isdisjoint(
       set(groups[test_idx]))
print("OK: sin pacientes compartidos")

El assert no es decorativo: es una comprobación que se ejecuta cada vez. Si algún paciente apareciera en ambos conjuntos, el análisis se detiene.

Todo dentro del pipeline

Escalado y selección de genes van dentro, no antes. Así se recalculan en cada partición y no se filtra información del test.

02_feature_selection_modeling.ipynb
pipe = Pipeline([
    ("var",    VarianceThreshold(threshold=0.0)),
    ("scaler", StandardScaler()),
    ("select", SelectKBest(f_classif)),
    ("clf",    LogisticRegression(max_iter=5000,
                   class_weight="balanced")),
])
 
param_dist = {
    "select__k": [500, 1000, 1500, 2000, 3000],
    "clf__C":    [0.01, 0.05, 0.1, 0.5, 1.0, 2.0],
}

El class_weight balanced compensa el desbalance 80/20 sin tocar los datos, y los hiperparámetros se buscan con la misma validación agrupada.

El código completo, los cuadernos y los modelos entrenados están en GitHub

OncoLens · Proyecto académico · Bootcamp Data Analyst II · 2026