En este informe se presenta un análisis del fenómeno del abandono escolar en el nivel medio superior a través de técnicas estadísticas categóricas, como árboles de decisión y regresión logística. Se parte de una base de datos simulada que integra factores personales, académicos y contextuales. El objetivo es ilustrar cómo se pueden identificar los factores más influyentes en la probabilidad de abandono escolar utilizando herramientas de análisis en RMarkdown.
# Crear base simulada
set.seed(123)
base_simulada <- tibble(
genero = sample(c("Masculino", "Femenino"), 300, replace = TRUE),
edad = sample(15:19, 300, replace = TRUE),
apoyo_familiar = sample(c("Bajo", "Medio", "Alto"), 300, replace = TRUE, prob = c(0.4, 0.4, 0.2)),
calificaciones = sample(c("Bajo", "Medio", "Alto"), 300, replace = TRUE, prob = c(0.4, 0.4, 0.2)),
tecnologia_acceso = sample(c("Sí", "No"), 300, replace = TRUE, prob = c(0.7, 0.3)),
situacion_economica = sample(c("Baja", "Media", "Alta"), 300, replace = TRUE, prob = c(0.5, 0.4, 0.1)),
nivel_socioeconomico = sample(c("Bajo", "Medio", "Alto"), 300, replace = TRUE, prob = c(0.6, 0.3, 0.1)),
horas_estudio = sample(c("2 horas", "3 horas", "5 horas"), 300, replace = TRUE, prob = c(0.4, 0.4, 0.2)),
estado_civil = sample(c("Soltero/a", "Casado/a", "Unión libre"), 300, replace = TRUE, prob = c(0.6, 0.25, 0.15)),
asignaturas_reprobadas = sample(c("Ninguna", "Una", "Dos o más"), 300, replace = TRUE, prob = c(0.3, 0.4, 0.3)),
cantidad_reprobadas = sample(0:5, 300, replace = TRUE, prob = c(0.3, 0.2, 0.2, 0.15, 0.1, 0.05)),
abandono = sample(c("Sí", "No"), 300, replace = TRUE, prob = c(0.35, 0.65))
)
# Asegurar variables categóricas como factores
base_simulada <- base_simulada %>% mutate(across(-cantidad_reprobadas, as.factor))
# Crear árbol de decisión
arbol_modelo <- rpart(abandono ~ ., data = base_simulada, method = "class")
rpart.plot(arbol_modelo, type = 4, extra = 104, under = TRUE, cex = 0.9)
# Preparar base para regresión (convertir abandono a binaria y asegurar variables numéricas)
base_logit <- base_simulada %>%
mutate(
abandono_bin = ifelse(abandono == "Sí", 1, 0),
edad = as.numeric(as.character(edad)),
cantidad_reprobadas = as.numeric(as.character(cantidad_reprobadas))
)
# Verificar nombres de variables y valores faltantes
print(names(base_logit))
## [1] "genero" "edad" "apoyo_familiar"
## [4] "calificaciones" "tecnologia_acceso" "situacion_economica"
## [7] "nivel_socioeconomico" "horas_estudio" "estado_civil"
## [10] "asignaturas_reprobadas" "cantidad_reprobadas" "abandono"
## [13] "abandono_bin"
print(colSums(is.na(base_logit)))
## genero edad apoyo_familiar
## 0 0 0
## calificaciones tecnologia_acceso situacion_economica
## 0 0 0
## nivel_socioeconomico horas_estudio estado_civil
## 0 0 0
## asignaturas_reprobadas cantidad_reprobadas abandono
## 0 0 0
## abandono_bin
## 0
# Eliminar filas con valores faltantes
base_logit <- na.omit(base_logit)
# Ajustar modelo
modelo_logit <- glm(abandono_bin ~ genero + edad + apoyo_familiar + calificaciones +
tecnologia_acceso + situacion_economica + nivel_socioeconomico +
horas_estudio + estado_civil + asignaturas_reprobadas + cantidad_reprobadas,
data = base_logit, family = binomial)
summary(modelo_logit)
##
## Call:
## glm(formula = abandono_bin ~ genero + edad + apoyo_familiar +
## calificaciones + tecnologia_acceso + situacion_economica +
## nivel_socioeconomico + horas_estudio + estado_civil + asignaturas_reprobadas +
## cantidad_reprobadas, family = binomial, data = base_logit)
##
## Coefficients:
## Estimate Std. Error z value Pr(>|z|)
## (Intercept) -1.08755 1.68097 -0.647 0.5176
## generoMasculino 0.46181 0.25355 1.821 0.0685 .
## edad -0.01989 0.09211 -0.216 0.8290
## apoyo_familiarBajo 0.05560 0.37076 0.150 0.8808
## apoyo_familiarMedio 0.56626 0.37078 1.527 0.1267
## calificacionesBajo -0.39538 0.35644 -1.109 0.2673
## calificacionesMedio -0.32048 0.35087 -0.913 0.3610
## tecnologia_accesoSí 0.21568 0.27497 0.784 0.4328
## situacion_economicaBaja 0.25557 0.43307 0.590 0.5551
## situacion_economicaMedia -0.13664 0.42786 -0.319 0.7494
## nivel_socioeconomicoBajo 0.30376 0.40843 0.744 0.4570
## nivel_socioeconomicoMedio 0.01479 0.44532 0.033 0.9735
## horas_estudio3 horas 0.18221 0.28044 0.650 0.5159
## horas_estudio5 horas -0.30113 0.37171 -0.810 0.4179
## estado_civilSoltero/a -0.43738 0.29577 -1.479 0.1392
## estado_civilUnión libre 0.21405 0.39409 0.543 0.5870
## asignaturas_reprobadasNinguna 0.23210 0.33727 0.688 0.4913
## asignaturas_reprobadasUna 0.31550 0.31768 0.993 0.3207
## cantidad_reprobadas 0.17406 0.08859 1.965 0.0494 *
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## (Dispersion parameter for binomial family taken to be 1)
##
## Null deviance: 396.42 on 299 degrees of freedom
## Residual deviance: 373.11 on 281 degrees of freedom
## AIC: 411.11
##
## Number of Fisher Scoring iterations: 4
# Gráfico de caja: cantidad de reprobadas por apoyo familiar
ggplot(base_logit, aes(x = apoyo_familiar, y = cantidad_reprobadas, fill = abandono)) +
geom_boxplot() +
theme_minimal() +
labs(title = "Asignaturas reprobadas por apoyo familiar", y = "Cantidad reprobadas")
# Gráfico de caja: edad por nivel socioeconómico
ggplot(base_logit, aes(x = nivel_socioeconomico, y = edad, fill = abandono)) +
geom_boxplot() +
theme_minimal() +
labs(title = "Edad por nivel socioeconómico", y = "Edad")
# Gráfico de caja: edad por calificaciones
ggplot(base_logit, aes(x = calificaciones, y = edad, fill = abandono)) +
geom_boxplot() +
theme_minimal() +
labs(title = "Edad por nivel de calificaciones", y = "Edad")
# Gráfico de caja: edad por apoyo familiar y género
ggplot(base_logit, aes(x = apoyo_familiar, y = edad, fill = abandono)) +
geom_boxplot() +
facet_wrap(~genero) +
theme_minimal() +
labs(title = "Edad por apoyo familiar según género", y = "Edad")
# Gráfico de caja: cantidad de reprobadas por estado civil y género
ggplot(base_logit, aes(x = estado_civil, y = cantidad_reprobadas, fill = abandono)) +
geom_boxplot() +
facet_wrap(~genero) +
theme_minimal() +
labs(title = "Asignaturas reprobadas por estado civil según género", y = "Cantidad reprobadas")
# Gráfico de caja: edad por horas de estudio y abandono
ggplot(base_logit, aes(x = horas_estudio, y = edad, fill = abandono)) +
geom_boxplot() +
facet_wrap(~abandono) +
theme_minimal() +
labs(title = "Edad según horas de estudio y abandono", y = "Edad")
# Gráfico de caja: edad vs abandono
ggplot(base_logit, aes(x = abandono, y = edad, fill = abandono)) +
geom_boxplot() +
theme_minimal() +
labs(title = "Distribución de edad según abandono")
# Gráfico de dispersión: edad vs cantidad reprobadas
ggplot(base_logit, aes(x = edad, y = cantidad_reprobadas, color = abandono)) +
geom_jitter(width = 0.2, height = 0.2, alpha = 0.6) +
theme_minimal() +
labs(title = "Dispersión entre edad y cantidad de asignaturas reprobadas")
ggplot(base_logit, aes(x = edad, fill = abandono)) +
geom_histogram(position = "dodge", bins = 5) +
theme_minimal() +
labs(title = "Histograma de edad según abandono")
# Agregar columna de probabilidades
base_logit$probabilidad_abandono <- predict(modelo_logit, type = "response")
# Mostrar primeras filas
head(base_logit[, c("abandono", "probabilidad_abandono")])
## # A tibble: 6 × 2
## abandono probabilidad_abandono
## <fct> <dbl>
## 1 No 0.309
## 2 Sí 0.610
## 3 No 0.497
## 4 No 0.200
## 5 No 0.605
## 6 No 0.246
# Gráfico de densidad de probabilidades por grupo
ggplot(base_logit, aes(x = probabilidad_abandono, fill = abandono)) +
geom_density(alpha = 0.6) +
theme_minimal() +
labs(title = "Distribución de probabilidades predichas de abandono",
x = "Probabilidad predicha", y = "Densidad")
# Clasificar abandono predicho según umbral 0.5
base_logit$prediccion_abandono <- ifelse(base_logit$probabilidad_abandono >= 0.5, "Sí", "No")
# Matriz de confusión
matriz_confusion <- table(Predicho = base_logit$prediccion_abandono, Real = base_logit$abandono)
matriz_confusion
## Real
## Predicho No Sí
## No 166 78
## Sí 22 34
# Precisión del modelo
precision <- mean(base_logit$prediccion_abandono == base_logit$abandono)
paste("Precisión del modelo: ", round(precision * 100, 2), "%")
## [1] "Precisión del modelo: 66.67 %"
# Función para generar matriz de confusión y precisión por umbral
analizar_umbral <- function(umbral) {
pred <- ifelse(base_logit$probabilidad_abandono >= umbral, "Sí", "No")
matriz <- table(Predicho = pred, Real = base_logit$abandono)
precision <- mean(pred == base_logit$abandono)
list(umbral = umbral, matriz = matriz, precision = round(precision * 100, 2))
}
# Resultados para umbrales 0.4, 0.5 y 0.6
res_04 <- analizar_umbral(0.4)
res_05 <- analizar_umbral(0.5)
res_06 <- analizar_umbral(0.6)
# Mostrar resultados
res_04$matriz
## Real
## Predicho No Sí
## No 132 55
## Sí 56 57
paste("Precisión con umbral 0.4:", res_04$precision, "%")
## [1] "Precisión con umbral 0.4: 63 %"
res_05$matriz
## Real
## Predicho No Sí
## No 166 78
## Sí 22 34
paste("Precisión con umbral 0.5:", res_05$precision, "%")
## [1] "Precisión con umbral 0.5: 66.67 %"
res_06$matriz
## Real
## Predicho No Sí
## No 177 102
## Sí 11 10
paste("Precisión con umbral 0.6:", res_06$precision, "%")
## [1] "Precisión con umbral 0.6: 62.33 %"
# Crear secuencia de umbrales
umbrales <- seq(0.1, 0.9, by = 0.05)
# Calcular precisión para cada umbral
resultados <- tibble(
umbral = umbrales,
precision = map_dbl(umbrales, ~ mean(ifelse(base_logit$probabilidad_abandono >= .x, "Sí", "No") == base_logit$abandono))
)
# Graficar curva
ggplot(resultados, aes(x = umbral, y = precision)) +
geom_line(color = "blue", size = 1.2) +
geom_point(color = "darkred") +
theme_minimal() +
labs(title = "Curva de precisión según el umbral de clasificación",
x = "Umbral de probabilidad",
y = "Precisión del modelo")
# Seleccionar variables numéricas
vars_cor <- base_logit %>%
select(edad, cantidad_reprobadas, abandono_bin, probabilidad_abandono)
# Calcular matriz de correlación
cor_matrix <- round(cor(vars_cor), 2)
# Convertir a formato largo para ggplot
cor_data <- as.data.frame(as.table(cor_matrix)) %>%
rename(Var1 = Var1, Var2 = Var2, Correlación = Freq)
# Gráfico heatmap
ggplot(cor_data, aes(Var1, Var2, fill = Correlación)) +
geom_tile(color = "white") +
geom_text(aes(label = Correlación), color = "black", size = 4) +
scale_fill_gradient2(low = "blue", high = "red", mid = "white", midpoint = 0) +
theme_minimal() +
labs(title = "Matriz de correlación entre variables numéricas")
A partir del análisis realizado, se identificaron diversas variables que pueden incidir significativamente en la decisión de un estudiante de abandonar sus estudios, tales como el nivel de apoyo familiar, el número de asignaturas reprobadas, el nivel socioeconómico, el estado civil y las horas de estudio semanales. El árbol de decisión permitió visualizar las rutas más frecuentes hacia el abandono escolar, mientras que la regresión logística ofreció una interpretación cuantitativa de la influencia de cada factor. La validación del modelo mostró que es posible predecir con razonable precisión el riesgo de abandono en función de los factores analizados.
Este ejercicio demuestra el valor del análisis categórico y las herramientas estadísticas en la toma de decisiones educativas.