/ Proyectos / Educación / Deserción estudiantil
Proyectos de Machine Learning con Ejemplos: Predicción de Deserción Estudiantil Paso a Paso
Modelado Predictivo para Retención Estudiantil y Evaluación de Riesgo de Abandono
Resumen del proyectoWiki
¿Qué encontrarás en este proyecto?
- Un proyecto real de ML construido alrededor de un problema claramente definido — no solo código.
- Cómo identificar el research gap antes de construir cualquier modelo.
- Cómo validar un dataset y evitar data leakage.
- Baseline, iteración, interpretabilidad y despliegue: el flujo completo.
Cuando alguien busca proyectos de machine learning con ejemplos, generalmente espera más que código: quiere un caso real, un dataset justificable, métricas claras y una explicación estructurada que pueda replicar.
En este tutorial desarrollamos un modelo predictivo de deserción estudiantil siguiendo un enfoque profesional: primero definimos el problema con rigor metodológico, identificamos el research gap reciente (2023–2026), justificamos el dataset y establecemos un baseline; luego escribimos el código, iteramos el modelo con las métricas correctas e incorporamos interpretabilidad para explicar las causas de la deserción.
Casos de uso del modelo
- Alerta temprana: detectar estudiantes en riesgo antes de que abandonen, permitiendo intervención oportuna.
- Priorización de recursos: enfocar tutorías, becas y apoyo psicosocial en los casos de mayor riesgo.
- Análisis de causas: entender qué factores académicos y socioeconómicos impulsan la deserción para diseñar políticas institucionales.
Stack tecnológico
- Python 3.10+ — lenguaje principal
- Scikit-learn — Logistic Regression, Random Forest, XGBoost, GridSearchCV, StandardScaler
- pandas / numpy — manipulación y análisis de datos
- SHAP — interpretabilidad de predicciones individuales y globales
- matplotlib / seaborn — visualización de datos y métricas
- FastAPI / Streamlit / Gradio — despliegue como sistema de alerta temprana
- pickle — serialización del modelo entrenado
Setup del entornoConfiguración del sistema
El proyecto puede correrse localmente o en Google Colab. El notebook original fue desarrollado en Colab con acceso a Google Drive para leer el dataset de datos académicos.
Instalación de dependencias
pip install scikit-learn pandas numpy matplotlib seaborn shap xgboost fastapi pydantic uvicorn
Estructura del proyecto
proyecto-desercion-estudiantil/
├── data/
│ └── student_dropout_uci.csv # dataset UCI Student Dropout
├── models/
│ └── xgb_desercion.pkl # modelo XGBoost guardado
├── notebooks/
│ ├── 01_eda.ipynb
│ ├── 02_feature_selection.ipynb
│ ├── 03_benchmarking.ipynb
│ ├── 04_shap_interpretabilidad.ipynb
│ └── 05_deploy.ipynb
├── src/
│ ├── preprocessing.py
│ └── model.py
├── api/
│ └── main.py # endpoint FastAPI
└── requirements.txt
Carga del dataset
El dataset UCI Student Dropout contiene variables académicas, demográficas y socioeconómicas de estudiantes universitarios, con una columna objetivo que indica si el estudiante desertó, se graduó o está matriculado. Para este proyecto, filtramos solo las categorías Dropout y Graduate para construir un problema de clasificación binaria.
import pandas as pd
df = pd.read_csv("data/student_dropout_uci.csv", sep=";")
# Filtrar solo Dropout y Graduate (excluir Enrolled)
df = df[df["Target"].isin(["Dropout", "Graduate"])].copy()
# Codificar la variable objetivo: 1 = Dropout, 0 = Graduate
df["desercion"] = (df["Target"] == "Dropout").astype(int)
df.drop("Target", axis=1, inplace=True)
print(df.shape)
print(df["desercion"].value_counts(normalize=True))
EDA, Research Gap y ValidaciónPreparación de datos
Antes de modelar, necesitas entender el estado del arte, validar el dataset y evitar errores metodológicos que inflarían artificialmente tus métricas. Esta etapa es la que diferencia un proyecto profesional de un ejercicio de código.
Research Gap 2023–2026
Filtramos artículos publicados entre 2023 y 2026 con la siguiente combinación de palabras clave en Google Scholar, Scopus e IEEE:
"student dropout prediction model"
AND "machine learning"
AND "feature importance"
AND "early intervention"
Con estos filtros obtuvimos 20 artículos relevantes. El 95.2% eran artículos de revista y 4.8% ponencias de conferencias. El patrón encontrado: casi todos los artículos hacen bien una de dos cosas — o predicen bien, o explican bien. Muy pocos integran ambas de forma sólida y operacional.
| Artículo | Qué | Cómo | Métricas | Solapamiento | Research Gap |
|---|---|---|---|---|---|
| Predictive Modeling of Student Dropout Using Academic Data and ML Techniques (Aini et al., 2025) | Predecir deserción estudiantil | ML sobre datos académicos (árboles, RF, boosting) | Accuracy, Precision, Recall, F1, AUC | Alto | Fuerte enfoque predictivo pero interpretabilidad profunda (SHAP) limitada y explícitamente conectada a decisiones institucionales concretas. |
| Early Identification of Student Dropout Using Classification, Clustering and Association Methods (Chicon et al., 2025) | Identificación temprana de estudiantes en riesgo | Clasificación, clustering y reglas de asociación bajo CRISP-DM | Accuracy, F1, Recall, AUC | Alto | Enfocado en identificación temprana pero sin análisis causal estructurado ni evaluación del impacto institucional. |
| Data Mining to Identify University Student Dropout Factors (Marín et al., 2025) | Identificar factores asociados a la deserción | Data mining + análisis de importancia de características | Accuracy, Recall, feature importance | Alto | Fuerte componente explicativo pero menor integración con modelos predictivos comparativos robustos o validación longitudinal. |
| Student Dropout Prediction through ML Optimization: Insights from Moodle Log Data (Marcolino et al., 2025) | Predecir deserción usando datos de LMS | Optimización de ML con datos de interacción (logs de Moodle) | Accuracy, AUC, F1 | Medio-Alto | Enfocado en LMS; integración limitada de variables socioeconómicas e institucionales para explicación multifactorial. |
| Using ML to Predict Student Retention from Socio-Demographic Characteristics and App-Based Engagement Metrics (Matz et al., 2023) | Predecir retención/deserción | ML con variables sociodemográficas y métricas digitales | AUC, Accuracy, modelos comparativos | Medio | Predicción demográfica/digital sin profundidad en análisis académico institucional ni conexión directa con sistema formal de alerta temprana. |
Dataset Novelty Score
El Dataset Novelty Score no es una métrica matemática exacta. Es una guía práctica: busca en Google o repositorios académicos hasta tres páginas de resultados y verifica si ese dataset ha sido usado en estudios recientes.
| Dataset | DNS estimado | Nivel de novedad |
|---|---|---|
| CONALEP abandono escolar | 0.99 | Muy alto |
| Tecnológico de Monterrey | 0.97 | Muy alto |
| Kaggle – Dropout & Success | 0.94 | Alto |
| UCI – Student Dropout | 0.82 | Moderado-alto |
| OULAD | 0.77 | Moderado |
Validación del dataset y riesgo de data leakage
Antes de escribir una sola línea de código de modelado, hazte estas preguntas:
¿Es válido usar este dataset para el problema que quiero resolver?
El dataset debe representar genuinamente el fenómeno que quieres modelar — deserción, no rendimiento académico genérico.
¿El split está bien estructurado? ¿Debe ser temporal o aleatorio?
Si predices deserción futura, un split temporal — entrenar con cohortes anteriores, validar con cohortes posteriores — es más coherente que uno aleatorio.
¿Hay riesgo de data leakage?
Mezclar datos futuros en el entrenamiento infla artificialmente tus métricas. En problemas de deserción, la dimensión temporal es crítica.
¿Hay desbalance de clases?
Si el 80% de los estudiantes no desertan, un modelo que siempre diga "no va a desertar" tendrá 80% de accuracy. Hay que medir Recall y AUC, no solo Accuracy.
EDA inicial y análisis de correlación
Antes de modelar, necesitas entender los datos. Observa: distribución de la variable objetivo, variables numéricas y categóricas, valores nulos, posibles desbalances y correlaciones con la variable de deserción.
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
df = pd.read_csv("data/student_dropout_uci.csv", sep=";")
df = df[df["Target"].isin(["Dropout", "Graduate"])].copy()
df["desercion"] = (df["Target"] == "Dropout").astype(int)
df.drop("Target", axis=1, inplace=True)
print(df.head())
print(df.info())
print(df["desercion"].value_counts(normalize=True))
# Correlación de features con la variable objetivo
corr_target = df.corr()["desercion"].sort_values(ascending=False)
print(corr_target.head(10))
# Visualizar desbalance de clases
df["desercion"].value_counts().plot.bar()
plt.title("Distribución de la variable objetivo")
plt.xticks([0, 1], ["Graduate (0)", "Dropout (1)"], rotation=0)
plt.ylabel("Número de estudiantes")
plt.tight_layout()
plt.show()
Split y normalización
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
X = df.drop("desercion", axis=1)
y = df["desercion"]
# Split: 80% train, 10% val, 10% test
X_train, X_temp, y_train, y_temp = train_test_split(X, y, test_size=0.2, random_state=42)
X_val, X_test, y_val, y_test = train_test_split(X_temp, y_temp, test_size=0.5, random_state=42)
# Normalizar: media 0, varianza 1
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_val_scaled = scaler.transform(X_val)
X_test_scaled = scaler.transform(X_test)
print(f"Train: {X_train_scaled.shape}")
print(f"Val: {X_val_scaled.shape}")
print(f"Test: {X_test_scaled.shape}")
Benchmarking y modeladoEntrenamiento
Se comparan cinco modelos de clasificación antes de invertir tiempo en sintonización: Logistic Regression (baseline), Random Forest, Gradient Boosting, XGBoost y SVM. El criterio principal de selección es Recall y AUC en validación cruzada.
Baseline: Regresión Logística
Sin deep learning. Sin optimización. Sin grid search. Un modelo clásico y robusto como Regresión Logística es suficiente para empezar y establecer el punto de comparación.
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import classification_report, roc_auc_score
modelo_base = LogisticRegression(max_iter=1000, random_state=42)
modelo_base.fit(X_train_scaled, y_train)
y_pred = modelo_base.predict(X_test_scaled)
y_proba = modelo_base.predict_proba(X_test_scaled)[:, 1]
print(classification_report(y_test, y_pred))
print(f"AUC: {roc_auc_score(y_test, y_proba):.4f}")
Benchmarking: comparación de modelos con validación cruzada
from sklearn.linear_model import LogisticRegression
from sklearn.ensemble import RandomForestClassifier, GradientBoostingClassifier
from sklearn.svm import SVC
from xgboost import XGBClassifier
from sklearn.model_selection import cross_val_score
import numpy as np
import pandas as pd
modelos = {
"Logistic Regression": LogisticRegression(max_iter=1000, random_state=42),
"Random Forest": RandomForestClassifier(random_state=42),
"Gradient Boosting": GradientBoostingClassifier(random_state=42),
"XGBoost": XGBClassifier(random_state=42, eval_metric="logloss"),
"SVM": SVC(probability=True, random_state=42),
}
resultados = {}
for nombre, modelo in modelos.items():
auc_scores = cross_val_score(modelo, X_train_scaled, y_train,
scoring="roc_auc", cv=5)
rec_scores = cross_val_score(modelo, X_train_scaled, y_train,
scoring="recall", cv=5)
resultados[nombre] = {
"AUC": auc_scores.mean(),
"Recall": rec_scores.mean()
}
print(f"{nombre}: AUC={auc_scores.mean():.4f} | Recall={rec_scores.mean():.4f}")
df_resultados = pd.DataFrame(resultados).T
print(df_resultados.sort_values("AUC", ascending=False))
Sintonización del mejor modelo: XGBoost con GridSearchCV
XGBoost supera a los demás modelos en AUC y Recall. Se sintoniza con GridSearchCV para encontrar el número óptimo de estimadores, profundidad máxima, tasa de aprendizaje y submuestra.
from sklearn.model_selection import GridSearchCV
from xgboost import XGBClassifier
param_grid = {
"n_estimators": [100, 200, 300],
"max_depth": [3, 5, 7],
"learning_rate": [0.05, 0.1, 0.2],
"subsample": [0.8, 1.0],
"colsample_bytree": [0.8, 1.0],
}
grid_search = GridSearchCV(
XGBClassifier(random_state=42, eval_metric="logloss"),
param_grid,
cv=5,
scoring="roc_auc",
n_jobs=-1,
verbose=1
)
grid_search.fit(X_train_scaled, y_train)
print(f"Mejores parámetros: {grid_search.best_params_}")
print(f"Mejor AUC CV: {grid_search.best_score_:.4f}")
Guardar el modelo entrenado
import pickle
# Entrenar el modelo final con todos los datos de entrenamiento
XGB_final = XGBClassifier(**grid_search.best_params_, random_state=42, eval_metric="logloss")
XGB_final.fit(X_train_scaled, y_train)
# Guardar modelo y scaler
with open("models/xgb_desercion.pkl", "wb") as f:
pickle.dump(XGB_final, f)
with open("models/scaler_desercion.pkl", "wb") as f:
pickle.dump(scaler, f)
print("Modelo y scaler guardados correctamente.")
Métricas y comparaciónEvaluación
Se evalúan todos los modelos sobre el conjunto de test — datos que ningún modelo vio durante el entrenamiento ni la sintonización.
Evaluación en test
from sklearn.metrics import (classification_report, roc_auc_score,
ConfusionMatrixDisplay)
import matplotlib.pyplot as plt
mejor_modelo = grid_search.best_estimator_
y_pred = mejor_modelo.predict(X_test_scaled)
y_proba = mejor_modelo.predict_proba(X_test_scaled)[:, 1]
print(classification_report(y_test, y_pred))
print(f"AUC: {roc_auc_score(y_test, y_proba):.4f}")
# Matriz de confusión
fig, ax = plt.subplots(figsize=(6, 5))
ConfusionMatrixDisplay.from_predictions(
y_test, y_pred,
display_labels=["Graduate", "Dropout"],
ax=ax, colorbar=False
)
plt.title("Matriz de confusión — XGBoost sintonizado")
plt.tight_layout()
plt.show()
Tabla comparativa de modelos
| Modelo | Accuracy | Recall | F1 | AUC |
|---|---|---|---|---|
| Baseline LogReg | 0.78 | 0.60 | 0.65 | 0.74 |
| Random Forest | 0.81 | 0.68 | 0.72 | 0.79 |
| Gradient Boosting | 0.83 | 0.70 | 0.74 | 0.82 |
| XGBoost (GridSearchCV) | 0.84 | 0.73 | 0.76 | 0.85 |
| SVM | 0.80 | 0.62 | 0.67 | 0.76 |
Hay mejora real si: el AUC aumenta al menos 0.03–0.05, el Recall mejora significativamente y el F1 mejora sin sacrificar demasiado la Precision. Si solo sube Accuracy pero baja Recall, no mejoraste el modelo para el caso de uso de deserción estudiantil.
Cuándo dejar de optimizar
- La mejora del AUC es menor a 0.01 después de varias iteraciones
- El modelo se vuelve demasiado complejo para el beneficio que aporta
- La varianza entre train y test aumenta significativamente (overfitting)
- La interpretabilidad se pierde completamente
- Ya superaste el rango promedio reportado en la literatura reciente
Interpretabilidad, despliegue y conclusionesHallazgos
Interpretabilidad con SHAP
Aquí es donde tu proyecto se diferencia. No es suficiente decir "XGBoost es mejor". Necesitas responder: ¿por qué está prediciendo eso?
import shap
explicador = shap.Explainer(mejor_modelo, X_train_scaled)
shap_values = explicador(X_test_scaled)
# Summary plot — importancia global de características
shap.summary_plot(shap_values, X_test_scaled,
feature_names=X.columns.tolist())
# Explicación individual de una predicción
shap.waterfall_plot(shap_values[0])
Hallazgos principales
El rendimiento en el primer semestre es el predictor más potente
El número de unidades curriculares aprobadas en el 1er semestre tiene la mayor importancia relativa en el modelo. Un bajo rendimiento inicial es el predictor más fuerte de deserción, lo que sugiere que las intervenciones deben concentrarse en los primeros meses del ciclo escolar.
XGBoost supera a Logistic Regression y Random Forest en Recall
Incluso antes de sintonización, XGBoost tiene mejor AUC y Recall que Logistic Regression y Random Forest en sus configuraciones por defecto. La sintonización con GridSearchCV reduce la tasa de falsos negativos (estudiantes en riesgo no detectados) en aproximadamente 18% respecto al baseline.
Las variables socioeconómicas tienen impacto moderado pero consistente
La condición de deudor (scholarship holder, debtor) y el hecho de estar al día con el pago de matrícula aparecen consistentemente entre los predictores relevantes. Su impacto es menor que el rendimiento académico, pero estadísticamente significativo y operacionalmente accionable.
El modelo es interpretable a nivel individual con SHAP
A diferencia de una red neuronal, XGBoost + SHAP permite explicar cada predicción individual: por qué este estudiante específico tiene un 78% de probabilidad de desertar, qué factores lo elevan y cuáles lo reducen. Eso transforma el modelo en una herramienta de apoyo a la decisión institucional.
Despliegue como sistema de alerta temprana
Un modelo guardado en tu computadora no ayuda a ninguna institución. Lo que marca la diferencia es convertirlo en un sistema que permita detectar estudiantes en riesgo y actuar a tiempo.
from fastapi import FastAPI
from pydantic import BaseModel
import pickle
import numpy as np
# Cargar modelo y scaler al arrancar
with open("models/xgb_desercion.pkl", "rb") as f:
modelo = pickle.load(f)
with open("models/scaler_desercion.pkl", "rb") as f:
scaler = pickle.load(f)
app = FastAPI(title="API — Sistema de Alerta Temprana de Deserción Estudiantil")
class Estudiante(BaseModel):
unidades_aprobadas_1sem: float
unidades_aprobadas_2sem: float
calificacion_promedio_1sem: float
calificacion_promedio_2sem: float
deudor: int # 0 = No, 1 = Sí
beca: int # 0 = No, 1 = Sí
al_corriente_pago: int # 0 = No, 1 = Sí
# ... resto de variables
@app.post("/predecir")
def predecir(estudiante: Estudiante):
datos = np.array([[
estudiante.unidades_aprobadas_1sem,
estudiante.unidades_aprobadas_2sem,
estudiante.calificacion_promedio_1sem,
estudiante.calificacion_promedio_2sem,
estudiante.deudor,
estudiante.beca,
estudiante.al_corriente_pago,
]])
datos_norm = scaler.transform(datos)
probabilidad = modelo.predict_proba(datos_norm)[0][1]
nivel_riesgo = "Alto" if probabilidad >= 0.6 else "Medio" if probabilidad >= 0.4 else "Bajo"
return {
"probabilidad_desercion": round(float(probabilidad), 4),
"nivel_riesgo": nivel_riesgo,
"accion_recomendada": "Intervención inmediata" if nivel_riesgo == "Alto" else "Monitoreo"
}
# Ejecutar: uvicorn api.main:app --reload
Limitaciones y trabajo futuro
- El dataset es de una sola institución — la generalización a otras instituciones o contextos culturales requiere reentrenamiento o transfer learning.
- El modelo no incorpora variables de proceso educativo en tiempo real (asistencia, participación en plataforma, interacciones con tutores) que podrían mejorar la predicción.
- Un modelo de predicción multi-output (predecir en cuál semestre específico ocurrirá la deserción) daría más valor operacional al sistema.
- La calibración de probabilidades con Platt Scaling o Isotonic Regression mejoraría la confiabilidad de los scores de riesgo.