El presente informe tiene como propósito realizar un análisis de regresión y ANOVA sobre un conjunto de datos simulados que evalúan factores asociados al rendimiento académico de estudiantes. Teniendo en cuenta lo anterior, se implementarán Modelos de Regresión Lineal Múltiple y Modelos de Regresión Logística Simple y Múltiple.
En esta sección se realizará un análisis de regresión lineal múltiple. Se utilizarán variables cuantitativas relacionadas con hábitos de estudio y características personales. El objetivo es identificar cuáles de estos factores tienen un impacto significativo sobre la calificación final (Exam_Score).
Se cargan las librerías necesarias y se importa la base de datos
StudentPerformanceFactors.csv.
# Cargar librerías necesarias
library(readr) # Para leer archivos CSV
library(dplyr) # Para manipulación de datos
##
## Adjuntando el paquete: 'dplyr'
## The following objects are masked from 'package:stats':
##
## filter, lag
## The following objects are masked from 'package:base':
##
## intersect, setdiff, setequal, union
# Leer la base de datos
datos <- read_csv("StudentPerformanceFactors.csv")
## Rows: 6607 Columns: 20
## ── Column specification ────────────────────────────────────────────────────────
## Delimiter: ","
## chr (13): Parental_Involvement, Access_to_Resources, Extracurricular_Activit...
## dbl (7): Hours_Studied, Attendance, Sleep_Hours, Previous_Scores, Tutoring_...
##
## ℹ Use `spec()` to retrieve the full column specification for this data.
## ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
# Ver las primeras filas
head(datos)
## # A tibble: 6 × 20
## Hours_Studied Attendance Parental_Involvement Access_to_Resources
## <dbl> <dbl> <chr> <chr>
## 1 23 84 Low High
## 2 19 64 Low Medium
## 3 24 98 Medium Medium
## 4 29 89 Low Medium
## 5 19 92 Medium Medium
## 6 19 88 Medium Medium
## # ℹ 16 more variables: Extracurricular_Activities <chr>, Sleep_Hours <dbl>,
## # Previous_Scores <dbl>, Motivation_Level <chr>, Internet_Access <chr>,
## # Tutoring_Sessions <dbl>, Family_Income <chr>, Teacher_Quality <chr>,
## # School_Type <chr>, Peer_Influence <chr>, Physical_Activity <dbl>,
## # Learning_Disabilities <chr>, Parental_Education_Level <chr>,
## # Distance_from_Home <chr>, Gender <chr>, Exam_Score <dbl>
# Ver estructura de la base
str(datos)
## spc_tbl_ [6,607 × 20] (S3: spec_tbl_df/tbl_df/tbl/data.frame)
## $ Hours_Studied : num [1:6607] 23 19 24 29 19 19 29 25 17 23 ...
## $ Attendance : num [1:6607] 84 64 98 89 92 88 84 78 94 98 ...
## $ Parental_Involvement : chr [1:6607] "Low" "Low" "Medium" "Low" ...
## $ Access_to_Resources : chr [1:6607] "High" "Medium" "Medium" "Medium" ...
## $ Extracurricular_Activities: chr [1:6607] "No" "No" "Yes" "Yes" ...
## $ Sleep_Hours : num [1:6607] 7 8 7 8 6 8 7 6 6 8 ...
## $ Previous_Scores : num [1:6607] 73 59 91 98 65 89 68 50 80 71 ...
## $ Motivation_Level : chr [1:6607] "Low" "Low" "Medium" "Medium" ...
## $ Internet_Access : chr [1:6607] "Yes" "Yes" "Yes" "Yes" ...
## $ Tutoring_Sessions : num [1:6607] 0 2 2 1 3 3 1 1 0 0 ...
## $ Family_Income : chr [1:6607] "Low" "Medium" "Medium" "Medium" ...
## $ Teacher_Quality : chr [1:6607] "Medium" "Medium" "Medium" "Medium" ...
## $ School_Type : chr [1:6607] "Public" "Public" "Public" "Public" ...
## $ Peer_Influence : chr [1:6607] "Positive" "Negative" "Neutral" "Negative" ...
## $ Physical_Activity : num [1:6607] 3 4 4 4 4 3 2 2 1 5 ...
## $ Learning_Disabilities : chr [1:6607] "No" "No" "No" "No" ...
## $ Parental_Education_Level : chr [1:6607] "High School" "College" "Postgraduate" "High School" ...
## $ Distance_from_Home : chr [1:6607] "Near" "Moderate" "Near" "Moderate" ...
## $ Gender : chr [1:6607] "Male" "Female" "Male" "Male" ...
## $ Exam_Score : num [1:6607] 67 61 74 71 70 71 67 66 69 72 ...
## - attr(*, "spec")=
## .. cols(
## .. Hours_Studied = col_double(),
## .. Attendance = col_double(),
## .. Parental_Involvement = col_character(),
## .. Access_to_Resources = col_character(),
## .. Extracurricular_Activities = col_character(),
## .. Sleep_Hours = col_double(),
## .. Previous_Scores = col_double(),
## .. Motivation_Level = col_character(),
## .. Internet_Access = col_character(),
## .. Tutoring_Sessions = col_double(),
## .. Family_Income = col_character(),
## .. Teacher_Quality = col_character(),
## .. School_Type = col_character(),
## .. Peer_Influence = col_character(),
## .. Physical_Activity = col_double(),
## .. Learning_Disabilities = col_character(),
## .. Parental_Education_Level = col_character(),
## .. Distance_from_Home = col_character(),
## .. Gender = col_character(),
## .. Exam_Score = col_double()
## .. )
## - attr(*, "problems")=<externalptr>
Para este modelo se seleccionan las siguientes variables explicativas:
Hours_Studied: Horas de estudio semanales.Sleep_Hours: Horas de sueño promedio por noche.Tutoring_Sessions: Número de sesiones de tutoría por
mes.Previous_Scores: Puntajes anteriores.Attendance: Porcentaje de asistencia a clases.Physical_Activity: Horas de actividad física
semanal.La variable dependiente será Exam_Score.
# Seleccionar solo las variables necesarias
datos_modelo <- datos %>%
select(Hours_Studied, Attendance, Previous_Scores, Sleep_Hours, Tutoring_Sessions, Exam_Score, Physical_Activity)
# Eliminar filas con NA
datos_modelo <- na.omit(datos_modelo)
# Ver estructura y primeras filas
str(datos_modelo)
## tibble [6,607 × 7] (S3: tbl_df/tbl/data.frame)
## $ Hours_Studied : num [1:6607] 23 19 24 29 19 19 29 25 17 23 ...
## $ Attendance : num [1:6607] 84 64 98 89 92 88 84 78 94 98 ...
## $ Previous_Scores : num [1:6607] 73 59 91 98 65 89 68 50 80 71 ...
## $ Sleep_Hours : num [1:6607] 7 8 7 8 6 8 7 6 6 8 ...
## $ Tutoring_Sessions: num [1:6607] 0 2 2 1 3 3 1 1 0 0 ...
## $ Exam_Score : num [1:6607] 67 61 74 71 70 71 67 66 69 72 ...
## $ Physical_Activity: num [1:6607] 3 4 4 4 4 3 2 2 1 5 ...
head(datos_modelo)
## # A tibble: 6 × 7
## Hours_Studied Attendance Previous_Scores Sleep_Hours Tutoring_Sessions
## <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 23 84 73 7 0
## 2 19 64 59 8 2
## 3 24 98 91 7 2
## 4 29 89 98 8 1
## 5 19 92 65 6 3
## 6 19 88 89 8 3
## # ℹ 2 more variables: Exam_Score <dbl>, Physical_Activity <dbl>
Se ajusta el modelo lineal múltiple usando la función
lm():
# Ajustar el modelo
modelo <- lm(Exam_Score ~ Hours_Studied + Sleep_Hours + Tutoring_Sessions + Previous_Scores + Attendance + Physical_Activity, data = datos_modelo)
# Resumen del modelo
summary(modelo)
##
## Call:
## lm(formula = Exam_Score ~ Hours_Studied + Sleep_Hours + Tutoring_Sessions +
## Previous_Scores + Attendance + Physical_Activity, data = datos_modelo)
##
## Residuals:
## Min 1Q Median 3Q Max
## -5.4391 -1.1316 -0.1619 0.8435 30.9951
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 40.927132 0.338284 120.985 < 2e-16 ***
## Hours_Studied 0.291579 0.005069 57.517 < 2e-16 ***
## Sleep_Hours -0.018022 0.020686 -0.871 0.384
## Tutoring_Sessions 0.493505 0.024679 19.997 < 2e-16 ***
## Previous_Scores 0.048123 0.002110 22.809 < 2e-16 ***
## Attendance 0.197978 0.002631 75.262 < 2e-16 ***
## Physical_Activity 0.143997 0.029449 4.890 1.03e-06 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 2.467 on 6600 degrees of freedom
## Multiple R-squared: 0.5982, Adjusted R-squared: 0.5979
## F-statistic: 1638 on 6 and 6600 DF, p-value: < 2.2e-16
- Intercepto (40.93): Representa el valor esperado del puntaje del examen (Exam_Score) cuando todas las variables predictoras son cero. Aunque no tiene interpretación práctica directa (por ejemplo, estudiar 0 horas o tener 0 de asistencia no es realista), es necesario para construir la ecuación del modelo.
- Hours_Studied (0.29): Por cada hora adicional de estudio por semana, el puntaje del examen aumenta en promedio 0.29 unidades, manteniendo constantes las demás variables. Este coeficiente es altamente significativo (p < 0.001).
- Sleep_Hours (-0.018): Por cada hora adicional de sueño por noche, el puntaje del examen disminuye en promedio 0.018 unidades, aunque este efecto no es estadísticamente significativo (p = 0.384). No tendría un gran impacto si se excluyera del modelo.
- Tutoring_Sessions (0.49): Cada sesión adicional de tutoría al mes se asocia con un incremento promedio de 0.49 puntos en el examen, lo cual es estadísticamente significativo (p < 0.001).
- Previous_Scores (0.048): Por cada punto adicional en los exámenes anteriores, el puntaje actual aumenta en promedio 0.048 unidades, siendo significativo (p < 0.001).
- Attendance (0.198): Por cada punto porcentual adicional de asistencia, el puntaje del examen aumenta en promedio 0.198 puntos. Este es uno de los predictores más relevantes del modelo y muy significativo (p < 0.001).
- Physical_Activity (0.144): Por cada hora adicional de actividad física por semana, el puntaje del examen aumenta en promedio 0.144 unidades. Este efecto también es estadísticamente significativo (p < 0.001).
Hours_Studied,
Tutoring_Sessions, Previous_Scores,
Attendance y Physical_Activity resultaron
significativas (p < 0.05).Sleep_Hours no fue estadísticamente significativa.## Cargar paquetes necesarios
library(lmtest) # Para pruebas de Breusch-Pagan y Durbin-Watson
## Cargando paquete requerido: zoo
##
## Adjuntando el paquete: 'zoo'
## The following objects are masked from 'package:base':
##
## as.Date, as.Date.numeric
library(car) # Para multicolinealidad (VIF)
## Cargando paquete requerido: carData
##
## Adjuntando el paquete: 'car'
## The following object is masked from 'package:dplyr':
##
## recode
Se analiza mediante el gráfico de residuos vs valores ajustados:
# Gráfico de residuos vs valores ajustados
plot(modelo$fitted.values, modelo$residuals,
main = "Residuos vs Valores Ajustados",
xlab = "Valores Ajustados", ylab = "Residuos",
pch = 19, col = "darkgreen")
abline(h = 0, col = "blue", lty = 2)
Se utiliza la prueba de Durbin-Watson:
dwtest(modelo)
##
## Durbin-Watson test
##
## data: modelo
## DW = 2.0002, p-value = 0.504
## alternative hypothesis: true autocorrelation is greater than 0
# Si el estadístico está cerca de 2 y el p-valor > 0.05, se cumple la independencia.
Prueba de Breusch-Pagan:
bptest(modelo)
##
## studentized Breusch-Pagan test
##
## data: modelo
## BP = 5.9304, df = 6, p-value = 0.431
# p > 0.05 indica homocedasticidad (buen indicador).
Se evalúa el VIF (Variance Inflation Factor):
vif(modelo)
## Hours_Studied Sleep_Hours Tutoring_Sessions Previous_Scores
## 1.001048 1.001019 1.001032 1.001815
## Attendance Physical_Activity
## 1.001474 1.000988
# Valores VIF < 5 no indican problemas serios de colinealidad.
Gráficos e interpretación:
hist(modelo$residuals,
col = "lightblue", main = "Histograma de Residuos")
qqnorm(modelo$residuals)
qqline(modelo$residuals, col = "red")
# Los puntos deben seguir la línea roja si los residuos son normales.
ks.test(modelo$residuals, "pnorm", mean = mean(modelo$residuals), sd = sd(modelo$residuals))
## Warning in ks.test.default(modelo$residuals, "pnorm", mean =
## mean(modelo$residuals), : ties should not be present for the one-sample
## Kolmogorov-Smirnov test
##
## Asymptotic one-sample Kolmogorov-Smirnov test
##
## data: modelo$residuals
## D = 0.14262, p-value < 2.2e-16
## alternative hypothesis: two-sided
# p > 0.05 indica que no se rechaza la hipótesis de normalidad.
Aunque el test sugiere que los residuos no siguen distribución normal (p < 0.05), esto no invalida el modelo debido al gran tamaño de la muestra (n = 6607).
R² ajustado = 0.598).Hours_Studied,
Attendance y Previous_Scores.Este modelo permite una comprensión académica sólida del proceso de regresión lineal múltiple aplicada a datos educativos.
Se aplica el análisis de varianza al modelo de regresión lineal múltiple para determinar si cada una de las variables predictoras contribuye significativamente a explicar la variabilidad observada en el puntaje del examen (Exam_Score).
# Tabla ANOVA del modelo ajustado
anova(modelo)
## Analysis of Variance Table
##
## Response: Exam_Score
## Df Sum Sq Mean Sq F value Pr(>F)
## Hours_Studied 1 19840 19840 3259.8322 < 2.2e-16 ***
## Sleep_Hours 1 48 48 7.8881 0.004991 **
## Tutoring_Sessions 1 2645 2645 434.6138 < 2.2e-16 ***
## Previous_Scores 1 2747 2747 451.3550 < 2.2e-16 ***
## Attendance 1 34391 34391 5650.5130 < 2.2e-16 ***
## Physical_Activity 1 146 146 23.9098 1.033e-06 ***
## Residuals 6600 40169 6
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
El modelo muestra que todas las variables incluidas son estadísticamente significativas (p < 0.01), lo que indica que cada una de ellas explica una parte importante de la variación en los puntajes de examen.
Las variables con mayor capacidad explicativa son:
Attendance (F = 5650.5)
Hours_Studied (F = 3259.8)
Previous_Scores y Tutoring_Sessions también presentan valores F elevados, confirmando su relevancia.
Aunque Sleep_Hours tiene un F más bajo (7.89), también es significativa (p = 0.00499), por lo que su efecto, aunque menor, no debe ser descartado.
Df (Grados de libertad): Es igual a 1 para cada predictor porque se estima un coeficiente por variable. Para los residuos, es n - k - 1 = 6607 - 6 - 1 = 6600.
Sum Sq (Suma de cuadrados): Representa la cantidad total de variabilidad que cada variable explica del Exam_Score.
Mean Sq (Media cuadrática): Se obtiene al dividir la suma de cuadrados entre los grados de libertad (en este caso, son iguales a Sum Sq porque Df = 1).
F value: Compara la variabilidad explicada por el predictor con la variabilidad no explicada (residual). Valores altos indican fuerte efecto del predictor sobre la respuesta.
Pr(>F): p-valor asociado al estadístico F. Valores menores a 0.05 indican que el predictor es estadísticamente significativo.
| Variable | Sum Sq | ¿Qué significa? |
|---|---|---|
| Hours_Studied | 19840 | Esta variable explica 19.840 unidades de variabilidad
total del Exam_Score. Es una gran porción del
total. El modelo dice: “solo con saber cuántas horas estudia un
estudiante, ya puedo explicar 19840 unidades de la variabilidad de los
puntajes”. |
| Sleep_Hours | 48 | Esta variable explica muy poca variabilidad (solo 48 unidades). Aunque es estadísticamente significativa (p = 0.0049), su impacto práctico es bajo comparado con otras variables. |
| Tutoring_Sessions | 2645 | Las sesiones de tutoría explican 2645 unidades de variabilidad. Es una contribución moderada-alta. Es una variable importante en términos de predicción. |
| Previous_Scores | 2747 | Las notas anteriores explican 2747 unidades, lo que indica que tienen una fuerte relación con el rendimiento actual. Es un predictor muy relevante. |
| Attendance | 34391 | Es la variable más poderosa del modelo: explica
34391 unidades de variabilidad del Exam_Score. Es decir,
más de un tercio de toda la variación se debe a la
asistencia. |
| Physical_Activity | 146 | Explica 146 unidades de variación. Es estadísticamente significativa, pero su contribución es baja en comparación con las variables anteriores. Aun así, su efecto no debe descartarse. |
Estos valores nos permiten cuantificar y comparar la importancia relativa de cada predictor. Por ejemplo:
Aunque tanto Hours_Studied como Attendance son significativos, Attendance explica casi el doble de la variabilidad (34391 vs 19840).
En cambio, Sleep_Hours y Physical_Activity, aunque significativos, explican poca variación absoluta, por lo que pueden tener un efecto más complementario o moderador.
Teniendo en cuenta lo anterior:
Es un predictor altamente significativo. Explica una gran parte de la variabilidad del puntaje del examen. Cada hora de estudio adicional por semana tiene un fuerte impacto positivo.
Aunque su F es menor, es estadísticamente significativo. Su efecto es leve, pero consistente. Puede tener un impacto menor o interactuar con otras variables.
Predictor con alto poder explicativo. Aumentar las sesiones de tutoría mensuales está fuertemente asociado a un mejor desempeño.
Tiene un efecto claro y significativo. Las calificaciones anteriores predicen de forma robusta el rendimiento actual.
Es el predictor más relevante del modelo. La asistencia tiene la mayor suma de cuadrados y F más alto, lo que indica que explica gran parte de la varianza del examen.
Tiene un efecto moderado pero significativo. Sugiere que la actividad física también contribuye de forma positiva al rendimiento académico.
La suma de cuadrados residual (40169) representa la parte de la variabilidad que el modelo no logra explicar.
El análisis ANOVA indica que todas las variables predictoras seleccionadas son estadísticamente significativas y contribuyen a explicar la variación en los puntajes de examen (todas con p < 0.01).
Las variables Attendance, Hours_Studied, Previous_Scores y Tutoring_Sessions son las más relevantes en términos de magnitud de varianza explicada. Aunque Sleep_Hours y Physical_Activity explican una porción menor, su efecto también es significativo y complementario dentro del modelo.
Este análisis respalda la validez del modelo ajustado y confirma que el conjunto de variables incluidas permite explicar de forma significativa y coherente el rendimiento académico de los estudiantes.
Vamos a crear una variable llamada High_Score, que tome el valor 1 si el estudiante aprobó el examen, utilizando como punto de corte una nota ≥ 70, y 0 para indicar que el estudiante reprobó, es decir obtuvo una nora < 70.
datos_modelo <- datos_modelo %>%
mutate(High_Score = ifelse(Exam_Score >= 70, 1, 0))
Ahora, vamos a predecir la probabilidad de High_Score = 1 en función de una sola variable, como Hours_Studied:
modelo_logit_simple <- glm(High_Score ~ Hours_Studied, data = datos_modelo, family = binomial)
summary(modelo_logit_simple)
##
## Call:
## glm(formula = High_Score ~ Hours_Studied, family = binomial,
## data = datos_modelo)
##
## Coefficients:
## Estimate Std. Error z value Pr(>|z|)
## (Intercept) -4.728779 0.139129 -33.99 <2e-16 ***
## Hours_Studied 0.169378 0.006077 27.87 <2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## (Dispersion parameter for binomial family taken to be 1)
##
## Null deviance: 7371.3 on 6606 degrees of freedom
## Residual deviance: 6385.4 on 6605 degrees of freedom
## AIC: 6389.4
##
## Number of Fisher Scoring iterations: 4
\[ \log\left( \frac{p}{1-p} \right)= -4.7288 + 0.1694 \cdot \text{Hours_Studied} \]
(Intercept) = -4.7288
Este es el valor del logit cuando Hours_Studied = 0. No tiene una interpretación práctica directa porque estudiar 0 horas puede no ser realista.
Hours_Studied = 0.1694
Cada hora adicional de estudio aumenta el logit de obtener una nota alta en 0.1694 unidades.
La desviancia es una medida estadística utilizada para evaluar la calidad del ajuste de modelos de regresión generalizada, especialmente la regresión logística. Se interpreta como una medida de discrepancia entre los datos observados y el modelo ajustado.
Fundamento matemático:
La fórmula general es:
\[ \text{Desviancia} = -2 \cdot \log(\text{verosimilitud del modelo}) \]
Pero también puede expresarse como:
\[ \text{Desviancia} = 2 \cdot \left( \log L_{\text{saturado}} - \log L_{\text{modelo}} \right) \]
Donde:
\[ \ L_{\text{saturado}} \]
Es la verosimilitud del modelo saturado, un modelo hipotético que predice perfectamente cada valor observado.
\[ \ L_{\text{modelo}} \]
Es la verosimilitud del modelo ajustado.
Al restarlas, se obtiene una medida de “distancia” entre lo que predice nuestro modelo y lo que predeciría el modelo perfecto.
Interpretación general:
Desviancia baja → el modelo se ajusta bien a los datos.
Desviancia alta → el modelo se ajusta mal (hay mayor discrepancia entre lo observado y lo predicho).
Tipos de Desviancia:
Al usar summary obtenemos los siguientes tipos de Desviancia:
Es la desviancia de un modelo sin predictores, sólo con el intercepto.
Representa el peor escenario, donde se predice la misma probabilidad para todos.
Es la desviancia del modelo ajustado, incluyendo los predictores.
Mide qué tanto error comete el modelo al estimar las probabilidades.
Si el modelo ajustado es mejor que el modelo nulo, la residual deviance debe ser mucho menor que la null deviance. Por lo anterior, se puede interpretar la diferencia entre ambas como una medida de mejora del ajuste.
Teniendo en cuenta lo anterior obtuvimos:
Null deviance: 7371.3
Residual deviance: 6385.4
Esto significa que:
El modelo sin predictores (sólo intercepto) tiene una desviancia de 7371.3.
Al incluir Hours_Studied, la desviancia baja a 6385.4 → eso indica una mejora en el ajuste.
Por lo anterior, el valor obtenido (6385.4) representa qué tan lejos está nuestro modelo (con solo Hours_Studied) del modelo ideal que ajusta perfectamente los datos:
exp(coef(modelo_logit_simple))
## (Intercept) Hours_Studied
## 0.008837255 1.184567644
(Intercept): 0.0088 → Corresponde a los odds de tener nota alta cuando Hours_Studied = 0. Muy bajo, como es esperable.
Hours_Studied: 1.1846 → Por cada hora adicional de estudio, los odds de obtener nota ≥ 70 aumentan un 18.46%.
Esto equivale a un aumento de los odds de:
\[ e^{0.1694} \approx 1.1845 \]
Es decir, por cada hora extra de estudio, los odds de obtener una nota alta aumentan en un 18.45%.
predict(modelo_logit_simple, newdata = data.frame(Hours_Studied = 15), type = "response")
## 1
## 0.1008232
Un estudiante que estudia 15 horas tiene una probabilidad estimada del 10.08% de obtener una nota alta (según el umbral ≥ 70 definido).
predicciones <- ifelse(predict(modelo_logit_simple, type = "response") >= 0.5, 1, 0)
table(Predicho = predicciones, Real = datos_modelo$High_Score)
## Real
## Predicho 0 1
## 0 4708 1218
## 1 274 407
VP (407): Casos correctamente clasificados como nota alta.
VN (4708): Casos correctamente clasificados como nota baja.
FP (274): Casos que fueron clasificados como alta nota, pero no lo eran.
FN (1218): Casos con nota alta que fueron clasificados incorrectamente como nota baja.
library(dplyr)
library(ggplot2)
# Crear etiquetas legibles
datos_modelo$High_Score_Factor <- factor(datos_modelo$High_Score, levels = c(0, 1), labels = c("No Aprobado", "Aprobado"))
pred_factor <- factor(predicciones, levels = c(0, 1), labels = c("No Aprobado", "Aprobado"))
# Crear tabla de frecuencia
matriz <- table(`Clase real` = datos_modelo$High_Score_Factor, `Clase predicha` = pred_factor)
matriz
## Clase predicha
## Clase real No Aprobado Aprobado
## No Aprobado 4708 274
## Aprobado 1218 407
# Convertir a data frame largo
matriz_df <- as.data.frame(matriz)
# Opcional: verificar estructura
str(matriz_df)
## 'data.frame': 4 obs. of 3 variables:
## $ Clase.real : Factor w/ 2 levels "No Aprobado",..: 1 2 1 2
## $ Clase.predicha: Factor w/ 2 levels "No Aprobado",..: 1 1 2 2
## $ Freq : int 4708 1218 274 407
ggplot(matriz_df, aes(x = `Clase.real`, y = `Clase.predicha`, fill = Freq)) +
geom_tile(color = "white") +
geom_text(aes(label = Freq), size = 8) +
scale_fill_gradient(low = "#deebf7", high = "#3182bd") +
labs(title = "Matriz de confusion",
x = "Real",
y = "Predicho",
fill = "Frecuencia") +
theme_minimal(base_size = 14)
Esto nos permite saber qué tan bien clasifica el modelo más allá del conteo
VP <- sum(predicciones == 1 & datos_modelo$High_Score == 1)
VN <- sum(predicciones == 0 & datos_modelo$High_Score == 0)
FP <- sum(predicciones == 1 & datos_modelo$High_Score == 0)
FN <- sum(predicciones == 0 & datos_modelo$High_Score == 1)
precision <- (VP + VN) / (VP + VN + FP + FN)
sensitivity <- VP / (VP + FN)
specificity <- VN / (VN + FP)
f1_score <- 2 * (precision * sensitivity) / (precision + sensitivity)
cat("Precision:", round(precision, 3), "\nSensibilidad:", round(sensitivity, 3), "\nEspecificidad:", round(specificity, 3), "\nF1-Score:", round(f1_score, 3))
## Precision: 0.774
## Sensibilidad: 0.25
## Especificidad: 0.945
## F1-Score: 0.378
| Métrica | Valor | Interpretación |
|---|---|---|
| Precisión | 0.774 | El 77.4% de todas las predicciones del modelo fueron correctas. |
| Sensibilidad | 0.25 | El modelo detectó correctamente solo el 25% de los estudiantes que aprueban. |
| Especificidad | 0.945 | El modelo identificó correctamente el 94.5% de los que reprueban. |
| F1-Score | 0.378 | Promedio armónico de precisión y sensibilidad: indica bajo balance general. |
El modelo es muy bueno para detectar quiénes van a perder (especificidad).
Pero es muy malo para detectar quiénes van a ganar (sensibilidad baja).
Esto indica un problema de desbalance en las clases: probablemente hay muchos más estudiantes con nota < 70 que con nota ≥ 70.
Además, usar un umbral de 0.5 puede no ser el mejor criterio cuando las clases están desbalanceadas.
Cambiamos el umbral a 0.3
# Cambiar el umbral a 0.3
predicciones_03 <- ifelse(predict(modelo_logit_simple, type = "response") >= 0.3, 1, 0)
VP <- sum(predicciones_03 == 1 & datos_modelo$High_Score == 1)
VN <- sum(predicciones_03 == 0 & datos_modelo$High_Score == 0)
FP <- sum(predicciones_03 == 1 & datos_modelo$High_Score == 0)
FN <- sum(predicciones_03 == 0 & datos_modelo$High_Score == 1)
precision <- (VP + VN) / (VP + VN + FP + FN)
sensitivity <- VP / (VP + FN)
specificity <- VN / (VN + FP)
f1_score_2 <- 2 * (precision * sensitivity) / (precision + sensitivity)
cat("Umbral 0.3\nPrecision:", round(precision, 3),
"\nSensibilidad:", round(sensitivity, 3),
"\nEspecificidad:", round(specificity, 3),
"\nF1-Score:", round(f1_score_2, 3))
## Umbral 0.3
## Precision: 0.713
## Sensibilidad: 0.603
## Especificidad: 0.749
## F1-Score: 0.654
| Métrica | Valor | Interpretación |
|---|---|---|
| Precisión | 0.713 | El 71.3% de todas las predicciones fueron correctas. |
| Sensibilidad | 0.603 | El modelo detectó correctamente el 60.3% de los estudiantes que aprobaron. |
| Especificidad | 0.749 | El modelo identificó correctamente el 74.9% de los estudiantes que reprobaron. |
| F1-Score | 0.654 | Buen equilibrio entre precisión y sensibilidad: el modelo clasifica bien y detecta muchos casos positivos. |
| Métrica | Umbral 0.5 | Umbral 0.3 | Interpretación |
|---|---|---|---|
| Precisión | 0.774 | 0.713 | Disminuye un poco al permitir más positivos |
| Sensibilidad | 0.250 | 0.603 | Sube mucho → detectas más estudiantes que aprueban |
| Especificidad | 0.945 | 0.749 | Baja → se aceptan más falsos positivos |
| F1-Score | 0.378 | 0.654 | Aumenta notablemente → mejor balance global |
🧠 Interpretación:
Sensibilidad subió de 25% a 60.3% → ahora detectas muchos más estudiantes con nota ≥ 70.
Especificidad bajó (era esperable) → ahora tienes más falsos positivos.
Precisión general bajó un poco, pero sigue siendo razonable.
library(ggplot2)
ggplot(datos_modelo, aes(x = Hours_Studied, y = High_Score)) +
geom_jitter(height = 0.05, alpha = 0.2) +
stat_smooth(method = "glm", method.args = list(family = "binomial"),
se = FALSE, color = "blue") +
labs(title = "Curva logística: Probabilidad de nota ≥ 70 según horas de estudio",
y = "Probabilidad estimada", x = "Horas de estudio")
## `geom_smooth()` using formula = 'y ~ x'
✅ ¿Qué muestra este gráfico?
Eje X (Horas de estudio): Variable independiente continua.
Eje Y (Probabilidad estimada): Valor que predice el modelo, es decir, la probabilidad de que un estudiante saque una nota ≥ 70.
Puntos dispersos (geom_jitter):
Cada punto representa a un estudiante.
Está codificado como 0 (nota < 70) o 1 (nota ≥ 70), pero con jitter para evitar sobreposición.
Representa la curva logística ajustada por el modelo.
Muestra cómo cambia la probabilidad de éxito (High_Score = 1) al aumentar las horas de estudio.
📌 ¿Qué interpretaciones puedes hacer?
La curva no es lineal, sino sigmoidea (forma de S), lo cual es ideal para modelar probabilidades.
A medida que aumentan las horas de estudio, también aumenta la probabilidad de tener una nota mayor o igual a 70.
Hay una zona de cambio rápido (aproximadamente entre 15 y 30 horas) donde pequeñas diferencias en horas de estudio provocan grandes cambios en probabilidad.
Para estudiantes que estudian menos de 10 horas, la probabilidad de éxito es muy baja (cerca de 0).
Para quienes estudian más de 35 horas, la probabilidad se aproxima a 1.
library(pROC)
## Type 'citation("pROC")' for a citation.
##
## Adjuntando el paquete: 'pROC'
## The following objects are masked from 'package:stats':
##
## cov, smooth, var
roc_obj <- roc(datos_modelo$High_Score, predict(modelo_logit_simple, type = "response"))
## Setting levels: control = 0, case = 1
## Setting direction: controls < cases
plot(roc_obj, col = "darkblue", main = "Curva ROC")
auc(roc_obj)
## Area under the curve: 0.7469
✅ ¿Qué es la curva ROC?
La curva ROC (Receiver Operating Characteristic) evalúa el rendimiento de un modelo de clasificación binaria para todos los posibles umbrales de decisión.
Eje X: 1 − Especificidad (falsos positivos).
Eje Y: Sensibilidad (verdaderos positivos).
Cada punto en la curva representa un umbral de probabilidad distinto utilizado para clasificar.
La línea diagonal gris representa el rendimiento de un clasificador aleatorio (sin capacidad predictiva). Cuanto más lejos esté la curva del área diagonal y más se acerque a la esquina superior izquierda, mejor el modelo.
✅ ¿Qué significa el AUC?
Esto quiere decir:
El modelo tiene capacidad discriminativa moderada.
Si tomas una observación positiva y una negativa al azar, el modelo tiene aproximadamente un 74.7% de probabilidad de asignar una mayor probabilidad al caso positivo.
Interpretación general del AUC:
0.90–1.00: Excelente
0.80–0.90: Buena
0.70–0.80: Aceptable/moderada
0.60–0.70: Débil
0.50–0.60: Muy débil (cercano al azar)
Es una extensión de la regresión logística simple, donde en lugar de tener una sola variable independiente, incluyes varias variables explicativas. El modelo tiene esta forma:
\[ \log\left( \frac{p}{1-p} \right) = \beta_0 + \beta_1 X_1 + \beta_2 X_2 + \cdots + \beta_k X_k \]
modelo_1 <- glm(High_Score ~ Hours_Studied + Attendance, data = datos_modelo, family = binomial)
summary(modelo_1)
##
## Call:
## glm(formula = High_Score ~ Hours_Studied + Attendance, family = binomial,
## data = datos_modelo)
##
## Coefficients:
## Estimate Std. Error z value Pr(>|z|)
## (Intercept) -23.053091 0.607793 -37.93 <2e-16 ***
## Hours_Studied 0.286737 0.009446 30.36 <2e-16 ***
## Attendance 0.187612 0.005376 34.90 <2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## (Dispersion parameter for binomial family taken to be 1)
##
## Null deviance: 7371.3 on 6606 degrees of freedom
## Residual deviance: 4007.7 on 6604 degrees of freedom
## AIC: 4013.7
##
## Number of Fisher Scoring iterations: 6
modelo_2 <- glm(High_Score ~ Hours_Studied + Attendance + Previous_Scores, data = datos_modelo, family = binomial)
summary(modelo_2)
##
## Call:
## glm(formula = High_Score ~ Hours_Studied + Attendance + Previous_Scores,
## family = binomial, data = datos_modelo)
##
## Coefficients:
## Estimate Std. Error z value Pr(>|z|)
## (Intercept) -28.276954 0.765359 -36.95 <2e-16 ***
## Hours_Studied 0.302602 0.010019 30.20 <2e-16 ***
## Attendance 0.201948 0.005819 34.70 <2e-16 ***
## Previous_Scores 0.048019 0.003091 15.53 <2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## (Dispersion parameter for binomial family taken to be 1)
##
## Null deviance: 7371.3 on 6606 degrees of freedom
## Residual deviance: 3740.5 on 6603 degrees of freedom
## AIC: 3748.5
##
## Number of Fisher Scoring iterations: 6
modelo_3 <- glm(High_Score ~ Hours_Studied + Attendance + Previous_Scores + Tutoring_Sessions, data = datos_modelo, family = binomial)
summary(modelo_3)
##
## Call:
## glm(formula = High_Score ~ Hours_Studied + Attendance + Previous_Scores +
## Tutoring_Sessions, family = binomial, data = datos_modelo)
##
## Coefficients:
## Estimate Std. Error z value Pr(>|z|)
## (Intercept) -31.543543 0.871041 -36.21 <2e-16 ***
## Hours_Studied 0.329739 0.010884 30.30 <2e-16 ***
## Attendance 0.218521 0.006396 34.16 <2e-16 ***
## Previous_Scores 0.053212 0.003245 16.40 <2e-16 ***
## Tutoring_Sessions 0.546184 0.036649 14.90 <2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## (Dispersion parameter for binomial family taken to be 1)
##
## Null deviance: 7371.3 on 6606 degrees of freedom
## Residual deviance: 3498.6 on 6602 degrees of freedom
## AIC: 3508.6
##
## Number of Fisher Scoring iterations: 7
modelo_4 <- glm(High_Score ~ Hours_Studied + Sleep_Hours + Tutoring_Sessions + Previous_Scores + Attendance + Physical_Activity, data = datos_modelo, family = binomial)
summary(modelo_4)
##
## Call:
## glm(formula = High_Score ~ Hours_Studied + Sleep_Hours + Tutoring_Sessions +
## Previous_Scores + Attendance + Physical_Activity, family = binomial,
## data = datos_modelo)
##
## Coefficients:
## Estimate Std. Error z value Pr(>|z|)
## (Intercept) -32.440246 0.925379 -35.056 < 2e-16 ***
## Hours_Studied 0.331895 0.010966 30.266 < 2e-16 ***
## Sleep_Hours 0.009559 0.029601 0.323 0.747
## Tutoring_Sessions 0.549444 0.036844 14.913 < 2e-16 ***
## Previous_Scores 0.053847 0.003260 16.518 < 2e-16 ***
## Attendance 0.220439 0.006452 34.167 < 2e-16 ***
## Physical_Activity 0.191484 0.042114 4.547 5.45e-06 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## (Dispersion parameter for binomial family taken to be 1)
##
## Null deviance: 7371.3 on 6606 degrees of freedom
## Residual deviance: 3477.6 on 6600 degrees of freedom
## AIC: 3491.6
##
## Number of Fisher Scoring iterations: 7
| Modelo | Variables incluidas | Desviancia |
|---|---|---|
| modelo_1 | Hours_Studied, Attendance | 4007.7 |
| modelo_2 | modelo_1 + Previous_Scores | 3740.5 |
| modelo_3 | modelo_2 + Tutoring_Sessions | 3498.6 |
| modelo_4 | modelo_3 + Sleep_Hours + Physical_Activity | 3477.6 |
Desviancia: 4007.7, la más alta.
Esto indica que el modelo tiene un ajuste pobre y no captura bien toda la información relevante.
Desviancia baja a 3740.5, lo que representa una mejora considerable.
Confirma que el rendimiento previo es un predictor importante.
Desviancia: 3498.6.
El modelo mejora aún más al considerar el acceso a tutorías, que es otra variable predictiva significativa.
Desviancia final: 3477.6, la más baja de todos los modelos.
Aunque Sleep_Hours no es significativa, el modelo completo mejora el ajuste general.
AIC(modelo_1, modelo_2, modelo_3, modelo_4)
## df AIC
## modelo_1 3 4013.688
## modelo_2 4 3748.475
## modelo_3 5 3508.571
## modelo_4 7 3491.574
| Modelo | Variables incluidas | AIC |
|---|---|---|
| modelo_1 | Hours_Studied, Attendance | 4013.7 |
| modelo_2 | modelo_1 + Previous_Scores | 3748.5 |
| modelo_3 | modelo_2 + Tutoring_Sessions | 3508.6 |
| modelo_4 | modelo_3 + Sleep_Hours + Physical_Activity | 3491.6 |
El modelo_4 tiene el AIC más bajo (3491.6), lo que indica que es el modelo con mejor balance entre ajuste y complejidad.
Aunque Sleep_Hours no fue significativa (p = 0.747), su inclusión no aumentó el AIC, y Physical_Activity sí fue significativa.
# Asegúrate de tener las librerías necesarias
library(ggplot2)
library(patchwork)
## Warning: package 'patchwork' was built under R version 4.4.3
# Obtener coeficientes del modelo
coefs <- coef(modelo_4)
# Media de cada variable (para mantenerlas fijas)
medias <- colMeans(datos_modelo[, c("Hours_Studied", "Sleep_Hours", "Tutoring_Sessions",
"Previous_Scores", "Attendance", "Physical_Activity")])
# Función para crear una curva logística univariada manteniendo las otras variables fijas
curva_logistica <- function(var, var_label) {
# Secuencia de valores para la variable
secuencia <- seq(min(datos_modelo[[var]]), max(datos_modelo[[var]]), length.out = 100)
# Crear data frame con la variable de interés variando y las otras fijas en su media
datos_curva <- as.data.frame(t(sapply(secuencia, function(x) {
vec <- medias
vec[var] <- x
return(vec)
})))
# Agregar nombre de columnas
colnames(datos_curva) <- names(medias)
# Predecir probabilidades
datos_curva$Probabilidad <- predict(modelo_4, newdata = datos_curva, type = "response")
datos_curva$Variable <- secuencia
# Graficar
ggplot(datos_curva, aes(x = Variable, y = Probabilidad)) +
geom_line(color = "blue", size = 1) +
labs(x = "Valor de la variable",
y = "Probabilidad estimada de aprobar",
title = var_label) +
theme_minimal()
}
# Crear todas las curvas
g1 <- curva_logistica("Attendance", "Attendance")
## Warning: Using `size` aesthetic for lines was deprecated in ggplot2 3.4.0.
## ℹ Please use `linewidth` instead.
## This warning is displayed once every 8 hours.
## Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
## generated.
g2 <- curva_logistica("Hours_Studied", "Hours_Studied")
g3 <- curva_logistica("Previous_Scores", "Previous_Scores")
g4 <- curva_logistica("Tutoring_Sessions", "Tutoring_Sessions")
g5 <- curva_logistica("Sleep_Hours", "Sleep_Hours")
g6 <- curva_logistica("Physical_Activity", "Physical_Activity")
# Juntar los gráficos en una sola figura con 2 filas y 3 columnas
(g1 | g2 | g3) / (g4 | g5 | g6) +
plot_annotation(title = "Curvas logísticas por variable (modelo_4)")
Interpretación variable por variable:
1. Attendance
Tiene una forma sigmoidea muy clara.
A medida que la asistencia sube del 80% al 100%, la probabilidad de aprobar pasa de cerca de 0 a más de 0.9.
✔️ Es uno de los predictores más potentes.
2. Hours_Studied
Similar a Attendance: curva logística muy pronunciada.
El salto más importante en probabilidad ocurre entre 15 y 30 horas semanales de estudio.
3. Previous_Scores
Curva creciente pero más suave.
El efecto es importante, pero más gradual. Mejores puntajes anteriores → mayor probabilidad de éxito actual.
4. Tutoring_Sessions
Relación positiva clara: más tutorías → mayor probabilidad de aprobar.
El cambio es bastante progresivo (no tan abrupto como con Hours_Studied).
5. Sleep_Hours
Curva muy plana: indica muy poca variación en la probabilidad.
Ya lo habías identificado como no significativo en el modelo → esta gráfica lo confirma visualmente.
6. Physical_Activity
Curva creciente leve → contribuye de forma moderada.
La probabilidad de aprobar aumenta, pero en un rango muy estrecho (de 0.04 a 0.11 aprox).
Conclusión visual:
Las variables Attendance y Hours_Studied son las más influyentes.
Sleep_Hours tiene un efecto muy bajo → podrías considerar excluirla si buscas un modelo más parsimonioso.
El resto (Previous_Scores, Tutoring_Sessions, Physical_Activity) también contribuyen, con diferentes magnitudes.
# Paso 1: Generar predicciones de probabilidad
probabilidades <- predict(modelo_4, type = "response")
# Paso 2: Clasificar usando umbral 0.3
predicciones_03 <- ifelse(probabilidades >= 0.3, 1, 0)
# Paso 3: Calcular matriz de confusión
VP <- sum(predicciones_03 == 1 & datos_modelo$High_Score == 1)
VN <- sum(predicciones_03 == 0 & datos_modelo$High_Score == 0)
FP <- sum(predicciones_03 == 1 & datos_modelo$High_Score == 0)
FN <- sum(predicciones_03 == 0 & datos_modelo$High_Score == 1)
library(ggplot2)
library(dplyr)
# Convertir las predicciones y valores reales a factores con etiquetas
datos_modelo$Pred_Logit_03 <- factor(predicciones_03, levels = c(0, 1), labels = c("No Aprobado", "Aprobado"))
datos_modelo$Real_Logit <- factor(datos_modelo$High_Score, levels = c(0, 1), labels = c("No Aprobado", "Aprobado"))
# Crear la tabla de frecuencia
matriz_confusion <- table(Real = datos_modelo$Real_Logit, Predicho = datos_modelo$Pred_Logit_03)
matriz_df <- as.data.frame(matriz_confusion)
# Graficar heatmap
ggplot(matriz_df, aes(x = Real, y = Predicho, fill = Freq)) +
geom_tile(color = "white") +
geom_text(aes(label = Freq), size = 6) +
scale_fill_gradient(low = "#deebf7", high = "#08519c") +
labs(title = "Matriz de Confusion - Modelo Logistico Multiple",
x = "Clase real",
y = "Clase predicha",
fill = "Frecuencia") +
theme_minimal(base_size = 14)
Interpretación:
Verdaderos Positivos (VP = 1395): Estudiantes que aprobaron y fueron correctamente clasificados.
Falsos Negativos (FN = 230): Estudiantes que aprobaron, pero el modelo los clasificó como no aprobados.
Verdaderos Negativos (VN = 4355): Estudiantes que no aprobaron y fueron correctamente identificados.
Falsos Positivos (FP = 627): Estudiantes que no aprobaron, pero el modelo los clasificó como aprobados.
# Paso 4: Calcular métricas
precision <- (VP + VN) / (VP + VN + FP + FN)
sensibilidad <- VP / (VP + FN)
especificidad <- VN / (VN + FP)
f1_score <- 2 * (precision * sensibilidad) / (precision + sensibilidad)
# Mostrar resultados
cat("Evaluacion modelo_4 (Umbral 0.3):\n")
## Evaluacion modelo_4 (Umbral 0.3):
cat("Precision:", round(precision, 3), "\n")
## Precision: 0.87
cat("Sensibilidad:", round(sensibilidad, 3), "\n")
## Sensibilidad: 0.858
cat("Especificidad:", round(especificidad, 3), "\n")
## Especificidad: 0.874
cat("F1-Score:", round(f1_score, 3), "\n")
## F1-Score: 0.864
| Métrica | Valor | Interpretación |
|---|---|---|
| Precisión | 0.870 | El 87.0% de todas las predicciones fueron correctas. |
| Sensibilidad | 0.858 | El modelo identificó correctamente el 85.8% de los estudiantes que aprobaron. |
| Especificidad | 0.874 | Identificó correctamente el 87.4% de los estudiantes que no aprobaron. |
| F1-Score | 0.864 | Excelente equilibrio entre sensibilidad y precisión. |
🧠 Interpretación general:
El modelo clasifica de forma muy precisa tanto los aprobados como los no aprobados.
La sensibilidad alta indica que rara vez deja de identificar a quienes sí aprobaron.
El F1-Score de 0.864 confirma que el modelo tiene un rendimiento equilibrado.
Esto lo convierte en una herramienta útil para predecir con anticipación el rendimiento académico, ideal para intervenir antes de que ocurra el fracaso escolar.
library(pROC)
roc_obj <- roc(datos_modelo$High_Score, probabilidades)
## Setting levels: control = 0, case = 1
## Setting direction: controls < cases
plot(roc_obj, col = "darkblue", main = "Curva ROC - modelo_4")
auc(roc_obj)
## Area under the curve: 0.94
La Curva ROC (Receiver Operating Characteristic) permite visualizar el rendimiento del modelo en todos los posibles umbrales de clasificación. Representa la relación entre:
Sensibilidad (eje Y): Tasa de verdaderos positivos.
1 - Especificidad (eje X): Tasa de falsos positivos.
En el gráfico, la línea azul representa la curva del modelo, mientras que la línea diagonal gris representa un clasificador aleatorio.
✅ Área Bajo la Curva (AUC): 0.94
El valor de AUC = 0.94 indica que el modelo tiene una capacidad de discriminación excelente.
En términos prácticos: si se selecciona un estudiante que aprobó y uno que no al azar, hay un 94% de probabilidad de que el modelo asigne una probabilidad mayor al estudiante que aprobó.
Cuanto más cerca de 1 esté el AUC, mejor es el modelo clasificando correctamente.
El presente análisis empleó modelos de regresión logística para predecir la probabilidad de que un estudiante obtenga una nota igual o superior a 70 puntos (variable binaria High_Score), utilizando como predictores variables académicas y de hábitos personales.
Se ajustó un primer modelo con un único predictor: las horas de estudio semanales. Este mostró una relación positiva y estadísticamente significativa con la probabilidad de éxito (OR ≈ 1.18 por hora adicional), pero con desempeño predictivo limitado:
Desviancia residual: 6385.4
AUC: 0.7469 (discriminación aceptable)
F1-Score (umbral 0.5): 0.378 → sensibilidad baja (25%)
Clasifica bien a quienes no aprobaron (alta especificidad), pero falla al identificar adecuadamente a quienes sí aprobaron (baja sensibilidad).
Se desarrollaron distintos modelos incluyendo nuevas variables predictoras: Attendance, Previous Scores, Tutoring Sessions, Sleep Hours y Physical Activity.
El modelo más completo y con mejor desempeño fue el que incluyó todas las variables anteriores (modelo_4):
AUC: 0.94 → excelente discriminación.
Precisión: 0.87
Sensibilidad: 0.858
Especificidad: 0.874
F1-Score: 0.864
AIC: 3491.6 (el más bajo de todos los modelos probados)
Desviancia residual: 3477.6 → la más baja de todos los modelos
Este modelo mostró una gran capacidad de clasificación, tanto en detectar a los estudiantes con alto desempeño como a quienes no alcanzan la nota mínima.
El cambio de umbral de clasificación de 0.5 a 0.3 fue clave para mejorar la sensibilidad del modelo sin sacrificar demasiado la especificidad.
La curva ROC y el AUC confirmaron que el modelo múltiple completo presenta un mejor balance entre verdaderos positivos y falsos positivos que el modelo simple.
Las curvas logísticas individuales mostraron la influencia positiva de cada predictor sobre la probabilidad de éxito académico.
El análisis confirma que variables como horas de estudio, asistencia, desempeño previo y tutorías influyen significativamente en la probabilidad de aprobar un examen. La regresión logística múltiple, junto con una elección adecuada del umbral, se consolida como una herramienta eficaz para:
Evaluar el rendimiento académico.
Identificar estudiantes en riesgo.
Diseñar intervenciones educativas basadas en datos.