Compresión del Problema

La base de datos utilizada contiene información académica, familiar y social de estudiantes de secundaria, recopilada en dos instituciones portuguesas. Se trabajará exclusivamente con la asignatura de Matemáticas. Se analizan variables como el rendimiento académico en tres periodos (G1,G2,G3), características sociodemográficas (edad, género, nivel educativo de los padres), apoyo familiar, hábitos de estudio, intención de cursar estudios superiores, frecuencia con la que sale con amigos, entre otras.

Existe una creciente preocupación sobre los factores que determinan el bajo rendimiento académico en estudiantes de secundaria. Identificar qué aspectos sociodemográficos, familiares o de hábitos de vida están asociados a un mayor riesgo de bajo desempeño permite orientar estrategias educativas y de intervención más eficaces. Este análisis busca responder:

Objetivo:

Determinar cuáles son los principales factores que inciden en que un estudiante repruebe una asignatura de matemáticas en secundaria.

Comprensión de los Datos

Tablas:

library(readr)
library(knitr)
library(kableExtra)

# Leer base de datos
datos <- read.csv("D:/UNIVERSIDAD M/QUINTO SEMESTRE/Estadistica aplicada/student-mat.csv", sep = ";")

# Mostrar una tabla bonita con los primeros 10 registros
datos %>%
  head(10) %>%
  kable("html", caption = "Primeros 10 registros de la base de datos") %>%
  kable_styling(bootstrap_options = c("striped", "hover", "condensed", "responsive"), 
                full_width = F, 
                position = "center")
Primeros 10 registros de la base de datos
school sex age address famsize Pstatus Medu Fedu Mjob Fjob reason guardian traveltime studytime failures schoolsup famsup paid activities nursery higher internet romantic famrel freetime goout Dalc Walc health absences G1 G2 G3
GP F 18 U GT3 A 4 4 at_home teacher course mother 2 2 0 yes no no no yes yes no no 4 3 4 1 1 3 6 5 6 6
GP F 17 U GT3 T 1 1 at_home other course father 1 2 0 no yes no no no yes yes no 5 3 3 1 1 3 4 5 5 6
GP F 15 U LE3 T 1 1 at_home other other mother 1 2 3 yes no yes no yes yes yes no 4 3 2 2 3 3 10 7 8 10
GP F 15 U GT3 T 4 2 health services home mother 1 3 0 no yes yes yes yes yes yes yes 3 2 2 1 1 5 2 15 14 15
GP F 16 U GT3 T 3 3 other other home father 1 2 0 no yes yes no yes yes no no 4 3 2 1 2 5 4 6 10 10
GP M 16 U LE3 T 4 3 services other reputation mother 1 2 0 no yes yes yes yes yes yes no 5 4 2 1 2 5 10 15 15 15
GP M 16 U LE3 T 2 2 other other home mother 1 2 0 no no no no yes yes yes no 4 4 4 1 1 3 0 12 12 11
GP F 17 U GT3 A 4 4 other teacher home mother 2 2 0 yes yes no no yes yes no no 4 1 4 1 1 1 6 6 5 6
GP M 15 U LE3 A 3 2 services other home mother 1 2 0 no yes yes no yes yes yes no 4 2 2 1 1 1 0 16 18 19
GP M 15 U GT3 T 3 4 other other home mother 1 2 0 no yes yes yes yes yes yes no 5 5 1 1 1 5 0 14 15 15
# 📚 Cargar bibliotecas necesarias
library(dplyr)
library(readr)
library(e1071)
library(kableExtra)
library(tidyr)

# 📌 Cargar el dataset
datos <- read.csv("D:/UNIVERSIDAD M/QUINTO SEMESTRE/Estadistica aplicada/student-mat.csv", sep = ";")

# 📌 Seleccionar solo variables numéricas
datos_numericos <- datos %>%
  select(where(is.numeric))

# 📊 Calcular medidas estadísticas para todas las variables numéricas
tabla_resumen <- datos_numericos %>%
  summarise(across(
    everything(),
    list(
      Mínimo = ~min(., na.rm = TRUE),
      Máximo = ~max(., na.rm = TRUE),
      Mediana = ~median(., na.rm = TRUE),
      Media = ~round(mean(., na.rm = TRUE), 2),
      `1er Cuartil` = ~quantile(., 0.25, na.rm = TRUE),
      `3er Cuartil` = ~quantile(., 0.75, na.rm = TRUE),
      `Coef. de Variación` = ~round(sd(., na.rm = TRUE) / mean(., na.rm = TRUE) * 100, 1),
      Asimetría = ~round(skewness(., na.rm = TRUE), 2),
      Curtosis = ~round(kurtosis(., na.rm = TRUE), 2)
    ),
    .names = "{.col}_{.fn}"
  )) %>%
  pivot_longer(everything(),
               names_to = c("Variable", "Medida"),
               names_sep = "_") %>%
  pivot_wider(names_from = Medida, values_from = value)

# 📋 Crear tabla formateada
kable(tabla_resumen,
      col.names = c("Variable", "Mínimo", "Máximo", "Mediana", "Media",
                    "1er Cuartil", "3er Cuartil", "Coef. de Variación",
                    "Asimetría", "Curtosis"),
      caption = "Resumen estadístico de variables numéricas del rendimiento estudiantil") %>%
  kable_styling(bootstrap_options = c("striped", "hover", "condensed", "responsive"),
                full_width = F, position = "center") %>%
  add_header_above(c(" " = 1, "Estadísticas Descriptivas" = 9))
Resumen estadístico de variables numéricas del rendimiento estudiantil
Estadísticas Descriptivas
Variable Mínimo Máximo Mediana Media 1er Cuartil 3er Cuartil Coef. de Variación Asimetría Curtosis
age 15 22 17 16.70 16 18 7.6 0.46 -0.03
Medu 0 4 3 2.75 2 4 39.8 -0.32 -1.10
Fedu 0 4 2 2.52 2 3 43.2 -0.03 -1.21
traveltime 1 4 1 1.45 1 2 48.2 1.59 2.27
studytime 1 4 2 2.04 1 2 41.2 0.63 -0.04
failures 0 3 0 0.33 0 0 222.5 2.37 4.89
famrel 1 5 4 3.94 4 5 22.7 -0.94 1.09
freetime 1 5 3 3.24 3 4 30.9 -0.16 -0.33
goout 1 5 3 3.11 2 4 35.8 0.12 -0.79
Dalc 1 5 1 1.48 1 2 60.1 2.17 4.65
Walc 1 5 2 2.29 1 3 56.2 0.61 -0.81
health 1 5 4 3.55 3 5 39.1 -0.49 -1.03
absences 0 75 4 5.71 0 8 140.2 3.64 21.31
G1 3 19 11 10.91 8 13 30.4 0.24 -0.71
G2 0 19 11 10.71 9 13 35.1 -0.43 0.59
G3 0 20 11 10.42 8 14 44.0 -0.73 0.37
# Crear el dataframe con la información de las variables
variables_df <- data.frame(
  Variable = c("age", "failures", "schoolsup", "famsup", "goout", 
               "internet", "romantic", "studytime", "traveltime", "paid"),
  
  Descripción = c("Edad del estudiante (15 a 22)",
                 "Número de fallos en clases anteriores (0 a 4)",
                 "Apoyo educativo adicional (sí o no)",
                 "Apoyo educativo familiar (sí o no)",
                 "Frecuencia con la que sale con amigos (1 - muy bajo a 5 - muy alto)",
                 "Acceso a Internet en casa (Sí o no)",
                 "En una relación romántica (Sí o no)",
                 "Tiempo de estudio semanal (en horas)",
                 "Tiempo de viaje de casa a la escuela (en horas)",
                 "Clases extra pagadas dentro de la materia del curso (Sí o no)"),
  
  Tipo = c("Cuantitativa discreta",
           "Cuantitativa discreta",
           "Cualitativa nominal",
           "Cualitativa nominal",
           "Cualitativa ordinal",
           "Cualitativa nominal",
           "Cualitativa nominal",
           "Cuantitativa discreta",
           "Cuantitativa continua",
           "Cualitativa nominal")
)

# Mostrar la tabla con formato profesional usando kable
library(knitr)
library(kableExtra)

variables_df %>%
  kable(
    format = "html",
    col.names = c("Variable", "Descripción", "Tipo"),
    align = c("l", "l", "l"),
    caption = "Descripción de Variables del Estudio"
  ) %>%
  kable_styling(
    bootstrap_options = c("striped", "hover"),
    full_width = FALSE,
    font_size = 14
  ) %>%
  column_spec(1, bold = TRUE) %>%
  row_spec(0, bold = TRUE, color = "white", background = "#3498db")
Descripción de Variables del Estudio
Variable Descripción Tipo
age Edad del estudiante (15 a 22) Cuantitativa discreta
failures Número de fallos en clases anteriores (0 a 4) Cuantitativa discreta
schoolsup Apoyo educativo adicional (sí o no) Cualitativa nominal
famsup Apoyo educativo familiar (sí o no) Cualitativa nominal
goout Frecuencia con la que sale con amigos (1 - muy bajo a 5 - muy alto) Cualitativa ordinal
internet Acceso a Internet en casa (Sí o no) Cualitativa nominal
romantic En una relación romántica (Sí o no) Cualitativa nominal
studytime Tiempo de estudio semanal (en horas) Cuantitativa discreta
traveltime Tiempo de viaje de casa a la escuela (en horas) Cuantitativa continua
paid Clases extra pagadas dentro de la materia del curso (Sí o no) Cualitativa nominal
library(ggplot2)
library(dplyr)

# Cargar los datos
df <- read.csv("student-mat.csv", sep = ";")

# Crear categorías para failures (0, 1, 2, 3+)
df <- df %>%
  mutate(
    failures_cat = case_when(
      failures == 0 ~ "0 cursos",
      failures == 1 ~ "1 curso",
      failures == 2 ~ "2 cursos",
      failures >= 3 ~ "3+ cursos"
    ),
    failures_cat = factor(failures_cat, levels = c("0 cursos", "1 curso", "2 cursos", "3+ cursos")),
    studytime_cat = factor(studytime, levels = 1:4, 
                         labels = c("1 (bajo)", "2", "3", "4 (alto)"))
  )

# Crear tabla cruzada
tabla_cruzada <- df %>%
  count(failures_cat, studytime_cat) %>%
  group_by(failures_cat) %>%
  mutate(porcentaje = n / sum(n) * 100) %>%
  ungroup()

# Diagrama de barras corregido
ggplot(tabla_cruzada, aes(x = failures_cat, y = porcentaje, fill = studytime_cat)) +
  geom_bar(stat = "identity", position = position_dodge(width = 0.8)) +
  geom_text(
    aes(label = paste0(round(porcentaje, 1), "%")), 
    position = position_dodge(width = 0.8),
    vjust = -0.3,
    size = 3.5
  ) +
  scale_fill_brewer(
    palette = "Set2",
    name = "Tiempo de estudio"
  ) +
  labs(
    x = "Número de cursos reprobados",
    y = "Porcentaje",
    title = "Distribución del Tiempo de Estudio por Cursos Reprobados"
  ) +
  theme_minimal() +
  theme(
    plot.title = element_text(hjust = 0.5, size = 14, face = "bold"),
    axis.text.x = element_text(size = 11),
    axis.text.y = element_text(size = 11),
    axis.title = element_text(size = 12),
    legend.position = "right"
  ) +
  scale_y_continuous(labels = function(x) paste0(x, "%"))

El gráfico muestra que a medida que aumenta el número de cursos reprobados, también lo hace el porcentaje de estudiantes con menos tiempo de estudio. Por ejemplo, entre quienes reprobaron 3 o más cursos, el 56.2% tenía un nivel de estudio 1 (bajo), mientras que en los que no reprobaron cursos solo el 23.7% tenía ese mismo nivel. Además, el nivel 2 (estudio moderado) es el más común en todos los grupos, incluso entre quienes no reprobaron (50.6%) y quienes reprobaron un curso (52%). El estudio alto (nivel 4) es muy poco frecuente en general, con solo 8.3% en quienes no reprobaron y apenas 2% en quienes reprobaron un curso, lo que sugiere que dedicar más tiempo al estudio no es habitual, pero podría estar relacionado con mejores resultados académicos.

library(ggplot2)
library(dplyr)

# Cargar los datos
df <- read.csv("student-mat.csv", sep = ";")

# Crear categorías para failures (0, 1, 2, 3+)
df <- df %>%
  mutate(
    failures_cat = case_when(
      failures == 0 ~ "0 cursos",
      failures == 1 ~ "1 curso",
      failures == 2 ~ "2 cursos",
      failures >= 3 ~ "3+ cursos"
    ),
    failures_cat = factor(failures_cat, levels = c("0 cursos", "1 curso", "2 cursos", "3+ cursos")),
    schoolsup_cat = factor(schoolsup, levels = c("yes", "no"), labels = c("Con apoyo escolar", "Sin apoyo escolar"))
  )

# Crear tabla cruzada
tabla_cruzada <- df %>%
  count(failures_cat, schoolsup_cat) %>%
  group_by(failures_cat) %>%
  mutate(porcentaje = n / sum(n) * 100) %>%
  ungroup()

# Diagrama de barras estilizado
ggplot(tabla_cruzada, aes(x = failures_cat, y = porcentaje, fill = schoolsup_cat)) +
  geom_bar(stat = "identity", position = position_dodge(width = 0.8)) +
  geom_text(
    aes(label = paste0(round(porcentaje, 1), "%")),
    position = position_dodge(width = 0.8),
    vjust = -0.3,
    size = 3.5
  ) +
  scale_fill_manual(
    values = c("Con apoyo escolar" = "#1f77b4", "Sin apoyo escolar" = "#ff7f0e"),
    name = "Apoyo Escolar"
  ) +
  labs(
    x = "Número de cursos reprobados",
    y = "Porcentaje",
    title = "Distribución del Apoyo Escolar según Fracasos Académicos"
  ) +
  theme_minimal() +
  theme(
    plot.title = element_text(hjust = 0.5, size = 14, face = "bold"),
    axis.text.x = element_text(size = 11),
    axis.text.y = element_text(size = 11),
    axis.title = element_text(size = 12),
    legend.position = "right"
  ) +
  scale_y_continuous(labels = function(x) paste0(x, "%"))

El gráfico muestra que, sin importar el número de cursos reprobados, la mayoría de los estudiantes no recibe apoyo escolar. Por ejemplo, incluso entre quienes han reprobado 3 o más cursos, el 87.5% no tiene apoyo, y solo el 12.5% sí lo recibe. Esta proporción es muy similar en todos los grupos, lo que indica que el apoyo escolar es poco frecuente y no varía según el desempeño académico.

# 📚 Cargar librerías necesarias
library(dplyr)
library(knitr)
library(kableExtra)

# 📁 Leer los datos
df <- read.csv("student-mat.csv", sep = ";")

# 🔄 Normalizar nombres de columnas
colnames(df) <- make.names(colnames(df), unique = TRUE)

# ✅ Convertir a UTF-8 y limpiar caracteres raros
df <- df %>%
  mutate(across(everything(), ~ iconv(., from = "", to = "UTF-8", sub = " "))) %>%
  mutate(across(everything(), ~ gsub("[^[:alnum:] [:space:]]", "", .))) %>%
  mutate(across(everything(), ~ trimws(as.character(.))))

# 🔄 Función para calcular la moda
moda <- function(x) {
  x <- na.omit(x)
  x <- x[x != ""]
  if (length(x) == 0) return("Sin Datos")
  
  tab <- table(x)
  max_freq <- max(tab)
  modas <- names(tab[tab == max_freq])
  return(paste(modas, collapse = ", "))
}

# 📊 Calcular la moda de las variables de interés
moda_failures <- moda(df$failures)
moda_goout <- moda(df$goout)

# 📋 Crear tabla de resultados
tabla_moda <- data.frame(
  Variable = c("Número de Fracasos (failures)", "Frecuencia de Salidas Sociales (goout)"),
  Moda = c(moda_failures, moda_goout)
)

# 🖥 Mostrar tabla formateada
kable(tabla_moda, format = "html") %>%
  kable_styling(bootstrap_options = c("striped", "hover", "responsive"), full_width = FALSE) %>%
  column_spec(1, bold = TRUE)
Variable Moda
Número de Fracasos (failures) 0
Frecuencia de Salidas Sociales (goout) 3

Esta tabla muestra las modas de dos variables: el número de fracasos académicos (failures) y la frecuencia de salidas sociales (goout). La moda de failures es 0, lo que indica que la mayoría de los estudiantes no ha reprobado ningún curso, lo cual es un signo positivo respecto al rendimiento académico general. Por otro lado, la moda de goout es 3, lo que sugiere que los estudiantes suelen tener una frecuencia social media, es decir, salen con sus amigos varias veces por semana, pero no en exceso. En conjunto, esto podría reflejar un equilibrio entre vida social y rendimiento académico en la mayoría de los estudiantes.

Preparación de los Datos

# Cargar los datos
datos <- read.csv("student-mat.csv", sep = ";")

# Convertir variables categóricas a factores
datos <- datos %>%
  mutate(
    # Variables binarias/nominales
    sex = factor(sex, levels = c("F", "M"), labels = c("Femenino", "Masculino")),
    address = factor(address, levels = c("U", "R"), labels = c("Urbano", "Rural")),
    famsize = factor(famsize, levels = c("LE3", "GT3"), labels = c("≤3 miembros", ">3 miembros")),
    Pstatus = factor(Pstatus, levels = c("A", "T"), labels = c("Separados", "Juntos")),
    
    # Variables ordinales
    Medu = factor(Medu, levels = 0:4, 
                 labels = c("Ninguno", "Primaria", "5-9 año", "Secundaria", "Superior")),
    Fedu = factor(Fedu, levels = 0:4,
                 labels = c("Ninguno", "Primaria", "5-9 año", "Secundaria", "Superior")),
    
    # Variables binarias de apoyo
    schoolsup = factor(schoolsup, levels = c("no", "yes"), labels = c("No", "Sí")),
    famsup = factor(famsup, levels = c("no", "yes"), labels = c("No", "Sí")),
    paid = factor(paid, levels = c("no", "yes"), labels = c("No", "Sí")),
    
    # Otras variables categóricas
    internet = factor(internet, levels = c("no", "yes"), labels = c("No", "Sí")),
    romantic = factor(romantic, levels = c("no", "yes"), labels = c("No", "Sí"))
  )

# Ver estructura básica
str(datos[, c(1:10, (ncol(datos)-5):ncol(datos))]) # Muestra primeras y últimas columnas
## 'data.frame':    395 obs. of  16 variables:
##  $ school  : chr  "GP" "GP" "GP" "GP" ...
##  $ sex     : Factor w/ 2 levels "Femenino","Masculino": 1 1 1 1 1 2 2 1 2 2 ...
##  $ age     : int  18 17 15 15 16 16 16 17 15 15 ...
##  $ address : Factor w/ 2 levels "Urbano","Rural": 1 1 1 1 1 1 1 1 1 1 ...
##  $ famsize : Factor w/ 2 levels "≤3 miembros",..: 2 2 1 2 2 1 1 2 1 2 ...
##  $ Pstatus : Factor w/ 2 levels "Separados","Juntos": 1 2 2 2 2 2 2 1 1 2 ...
##  $ Medu    : Factor w/ 5 levels "Ninguno","Primaria",..: 5 2 2 5 4 5 3 5 4 4 ...
##  $ Fedu    : Factor w/ 5 levels "Ninguno","Primaria",..: 5 2 2 3 4 4 3 5 3 5 ...
##  $ Mjob    : chr  "at_home" "at_home" "at_home" "health" ...
##  $ Fjob    : chr  "teacher" "other" "other" "services" ...
##  $ Walc    : int  1 1 3 1 2 2 1 1 1 1 ...
##  $ health  : int  3 3 3 5 5 5 3 1 1 5 ...
##  $ absences: int  6 4 10 2 4 10 0 6 0 0 ...
##  $ G1      : int  5 5 7 15 6 15 12 6 16 14 ...
##  $ G2      : int  6 5 8 14 10 15 12 5 18 15 ...
##  $ G3      : int  6 6 10 15 10 15 11 6 19 15 ...

Modelado de Regresión Logística

# Cargar librerias necesarias
library(dplyr)
library(readr)
library(knitr)
library(kableExtra)

# Cargar los datos
datos <- read.csv("student-mat.csv", sep = ";")

# Crear la variable binaria: 1 si G3 >= 10, 0 si no
datos <- datos %>%
  mutate(G3_binaria = ifelse(G3 >= 10, 1, 0))

# Convertir variables categoricas a factores (solo las necesarias)
variables_categoricas <- c("schoolsup", "famsup", "internet", "romantic", "paid", "G3_binaria")
datos <- datos %>%
  mutate(across(all_of(variables_categoricas), as.factor))

# Ajustar modelo de regresion logistica con variables seleccionadas
modelo_logistico <- glm(G3_binaria ~ age + failures + schoolsup + famsup + 
                        goout + internet + romantic + studytime + traveltime + paid,
                      data = datos,
                      family = binomial())

# Extraer y formatear resultados
resumen <- summary(modelo_logistico)$coefficients

tabla_resultados <- data.frame(
  Variable = rownames(resumen),
  Coeficiente = round(resumen[, 1], 4),
  Error_Estándar = round(resumen[, 2], 4),
  Estadistico_z = round(resumen[, 3], 2),
  Valor_p = round(resumen[, 4], 4),
  Significancia = ifelse(resumen[, 4] < 0.001, "***",
                       ifelse(resumen[, 4] < 0.01, "**",
                            ifelse(resumen[, 4] < 0.05, "*", "NS")))
)

# Mostrar tabla con formato
kable(tabla_resultados,
      align = c("l", "c", "c", "c", "c", "c"),
      caption = "Resultados del Modelo de Regresion Logistica (Variables Seleccionadas)") %>%
  kable_styling(bootstrap_options = c("striped", "hover"),
              full_width = FALSE) %>%
  column_spec(1, bold = TRUE) %>%
  footnote(general = "NS: No significativo (p > 0.05), * p < 0.05, ** p < 0.01, *** p < 0.001")
Resultados del Modelo de Regresion Logistica (Variables Seleccionadas)
Variable Coeficiente Error_Estándar Estadistico_z Valor_p Significancia
(Intercept) (Intercept) 6.3699 1.8408 3.46 0.0005 ***
age age -0.2590 0.1044 -2.48 0.0131
failures failures -0.8374 0.1781 -4.70 0.0000 ***
schoolsupyes schoolsupyes -0.9319 0.3492 -2.67 0.0076 **
famsupyes famsupyes -0.5515 0.2652 -2.08 0.0376
goout goout -0.3235 0.1085 -2.98 0.0029 **
internetyes internetyes 0.3319 0.3176 1.04 0.2961 NS
romanticyes romanticyes -0.3466 0.2519 -1.38 0.1688 NS
studytime studytime 0.0863 0.1501 0.57 0.5655 NS
traveltime traveltime 0.0268 0.1716 0.16 0.8757 NS
paidyes paidyes 0.2681 0.2553 1.05 0.2938 NS
Note:
NS: No significativo (p > 0.05), * p < 0.05, ** p < 0.01, *** p < 0.001

Evaluación

Matriz de Confusión:

# Cargar librerías necesarias
library(caret)
library(ggplot2)
library(dplyr)
library(kableExtra)

# Cargar datos
datos <- read.csv("student-mat.csv", sep = ";")

# Crear variable objetivo binaria (sin usar G1/G2)
datos_modelo <- datos %>%
  mutate(
    G3_binaria = ifelse(G3 >= 10, 1, 0),
    G3_binaria = factor(G3_binaria, levels = c(1, 0), labels = c("Aprobado", "Reprobado"))
  ) %>%
  select(-G1, -G2, -G3) # Eliminamos las variables de notas

# Convertir variables categóricas a factores
datos_modelo <- datos_modelo %>%
  mutate(across(c(schoolsup, famsup), ~factor(ifelse(. == "yes", 1, 0))))

# Modelo de regresión logística con variables significativas
modelo_logit <- train(
  G3_binaria ~ age + failures + schoolsup + famsup + goout + 
              studytime + traveltime + paid + romantic + internet,
  data = datos_modelo,
  method = "glm",
  family = "binomial",
  trControl = trainControl(method = "cv", number = 10)
)

# Predicciones numéricas (probabilidades)
predicciones_prob <- predict(modelo_logit, newdata = datos_modelo, type = "prob")[, "Aprobado"]

# Clasificación con umbral de 0.5
predicciones_clase <- ifelse(predicciones_prob > 0.5, "Aprobado", "Reprobado")

# Conversión a factor para evaluar
real <- datos_modelo$G3_binaria
pred <- factor(predicciones_clase, levels = c("Aprobado", "Reprobado"))

# Matriz de confusión con caret
conf_mat <- confusionMatrix(pred, real)

# Presentación de resultados
conf_table <- as.table(conf_mat$table)
conf_df <- as.data.frame.matrix(conf_table)

# Mostrar tabla bonita
conf_df %>%
  kable(caption = "Matriz de Confusión - Rendimiento en Matemáticas", 
        align = "c") %>%
  kable_styling(bootstrap_options = c("striped", "hover"), full_width = FALSE)
Matriz de Confusión - Rendimiento en Matemáticas
Aprobado Reprobado
Aprobado 244 83
Reprobado 21 47
# Gráfico de importancia de variables
var_imp <- varImp(modelo_logit)
plot(var_imp, main = "Importancia de Variables en el Modelo")

Esta matriz de confusión muestra el desempeño del modelo al predecir el rendimiento en matemáticas:

  • 244 estudiantes fueron correctamente clasificados como aprobados (verdaderos positivos).

  • 47 fueron correctamente clasificados como reprobados (verdaderos negativos).

  • 83 fueron mal clasificados como aprobados cuando en realidad reprobaron (falsos positivos).

  • 21 fueron mal clasificados como reprobados cuando en realidad aprobaron (falsos negativos).

# 📋 Extraer métricas clave desde la matriz de confusión
metricas <- tibble(
  Métrica = c("Precisión (Accuracy)", 
              "Índice Kappa", 
              "Sensibilidad (Recall para Aprobado)", 
              "Especificidad (para Reprobado)", 
              "Valor Predictivo Positivo (VPP)", 
              "Valor Predictivo Negativo (VPN)", 
              "Prevalencia de Aprobado", 
              "Exactitud Balanceada"),
  Valor = round(c(
    conf_mat$overall["Accuracy"],
    conf_mat$overall["Kappa"],
    conf_mat$byClass["Sensitivity"],
    conf_mat$byClass["Specificity"],
    conf_mat$byClass["Pos Pred Value"],
    conf_mat$byClass["Neg Pred Value"],
    conf_mat$byClass["Prevalence"],
    conf_mat$byClass["Balanced Accuracy"]
  ), 3)
)

# 📊 Mostrar tabla de métricas
metricas %>%
  kable(caption = "Estadísticas del Modelo de Regresión Logística - Rendimiento Estudiantil") %>%
  kable_styling(bootstrap_options = c("striped", "hover"), full_width = FALSE)
Estadísticas del Modelo de Regresión Logística - Rendimiento Estudiantil
Métrica Valor
Precisión (Accuracy) 0.737
Índice Kappa 0.321
Sensibilidad (Recall para Aprobado) 0.921
Especificidad (para Reprobado) 0.362
Valor Predictivo Positivo (VPP) 0.746
Valor Predictivo Negativo (VPN) 0.691
Prevalencia de Aprobado 0.671
Exactitud Balanceada 0.641
  • Acierta el 73.7% de las veces (Precisión decente).

  • Detecta muy bien a los que aprueban (92.1% de sensibilidad).

  • Falla en identificar reprobados (solo acierta el 36.2% de estos).

  • Kappa bajo (0.32): El modelo es solo un poco mejor que adivinar al azar.

En resumen:
Útil para predecir aprobados, pero poco confiable para reprobados. Necesita ajustes si el objetivo es detectar a los estudiantes en riesgo.

Despliegue

El modelo se utilizo para la realizacion de una aplicacion web que determina la probabilidad de que un estudiante apruebe o no la materia de matemáticas.
Link app: https://7pwlfw.csb.app/ (se debe abrir en otra pestaña)

Conclusiones

  • El número de fracasos académicos (failures) es el predictor más fuerte del bajo rendimiento, con un coeficiente negativo significativo (p < 0.001). Además, las gráficas mostraron que la mayoría de estudiantes no recibe apoyo escolar, incluso con altos niveles de reprobación, lo cual podría estar contribuyendo a estos fracasos.

  • La frecuencia de salidas sociales (goout) también se asocia negativamente con el rendimiento (p < 0.01), lo que indica que un mayor número de salidas sociales puede estar afectando negativamente el desempeño estudiantil.

  • Los estudiantes que reciben apoyo escolar (schoolsup) y familiar (famsup) tienden a tener un menor rendimiento, pero esto no significa que el apoyo cause bajo rendimiento, sino que probablemente estos apoyos se brindan a estudiantes que ya presentan dificultades académicas, lo cual explica la relación negativa observada.