Autor: Nino Cabrera, Mateo Chang y Dario Quiroga
Fecha: 2025-08-11
Dataset: UCI Student Performance Data Set
Este documento presenta un análisis exploratorio exhaustivo del dataset “Student Performance” de la Universidad de California Irvine (UCI), que contiene información sobre el rendimiento académico de estudiantes portugueses en dos materias: Matemáticas y Lengua Portuguesa. El análisis busca identificar patrones, relaciones y factores determinantes del éxito académico estudiantil.
Hallazgos principales: - Diferencias significativas en el rendimiento entre materias - Impacto considerable de factores socioeconómicos y familiares - Patrones distintivos en las variables predictoras del éxito académico
El dataset Student Performance contiene información de estudiantes de educación secundaria portugueses, recopilada en dos escuelas durante el período académico 2005-2006. Los datos incluyen 33 variables que abarcan desde características demográficas hasta factores socioeconómicos, familiares y académicos.
# Paquetes necesarios para el análisis
suppressPackageStartupMessages({
library(tidyverse) # Manipulación de datos y visualización
library(GGally) # Matrices de correlación y gráficos paired
library(scales) # Escalas para gráficos
library(knitr) # Tablas formateadas
library(DT) # Tablas interactivas
})
# Configuración del theme y paleta de colores
theme_set(theme_minimal(base_size = 12))
pal <- c("Math" = "#2B8CBE", "Portuguese" = "#E34A33")
# Carga de los datasets originales
mat <- read.delim("student-mat.csv", sep = ";")
por <- read.delim("student-por.csv", sep = ";")
# Estandarización de nombres de columnas
names(mat) <- tolower(gsub("\\.", "_", names(mat)))
names(por) <- tolower(gsub("\\.", "_", names(por)))
# Definición de tipos de variables
factor_vars_base <- c("school","sex","address","famsize","pstatus","mjob","fjob","reason",
"guardian","schoolsup","famsup","paid","activities","nursery",
"higher","internet","romantic")
numeric_vars_base <- c("age","medu","fedu","traveltime","studytime","failures",
"famrel","freetime","goout","dalc","walc","health",
"absences","g1","g2","g3")
# Conversión de tipos de datos
mat[intersect(factor_vars_base, names(mat))] <-
lapply(mat[intersect(factor_vars_base, names(mat))], factor)
por[intersect(factor_vars_base, names(por))] <-
lapply(por[intersect(factor_vars_base, names(por))], factor)
mat[intersect(numeric_vars_base, names(mat))] <-
lapply(mat[intersect(numeric_vars_base, names(mat))], as.numeric)
por[intersect(numeric_vars_base, names(por))] <-
lapply(por[intersect(numeric_vars_base, names(por))], as.numeric)
# Eliminación de duplicados y variables no utilizadas
mat <- mat %>% distinct() %>% select(-any_of(c("famsize","nursery")))
por <- por %>% distinct() %>% select(-any_of(c("famsize","nursery")))
# Combinación de datasets con etiqueta de materia
mat$subject <- "Math"
por$subject <- "Portuguese"
df_all <- bind_rows(mat, por) %>% relocate(subject)
# Actualización de listas de variables
factor_vars <- intersect(factor_vars_base, names(df_all))
numeric_vars <- intersect(numeric_vars_base, names(df_all))
# Resumen dimensional
cat("Dimensiones del dataset combinado:", dim(df_all)[1], "observaciones,", dim(df_all)[2], "variables\n")
## Dimensiones del dataset combinado: 1044 observaciones, 32 variables
cat("Matemáticas:", nrow(mat), "estudiantes\n")
## Matemáticas: 395 estudiantes
cat("Portugués:", nrow(por), "estudiantes\n")
## Portugués: 649 estudiantes
# Estructura de variables
cat("\n=== ESTRUCTURA DE VARIABLES ===\n")
##
## === ESTRUCTURA DE VARIABLES ===
cat("Variables categóricas (", length(factor_vars), "):", paste(factor_vars, collapse = ", "), "\n")
## Variables categóricas ( 15 ): school, sex, address, pstatus, mjob, fjob, reason, guardian, schoolsup, famsup, paid, activities, higher, internet, romantic
cat("Variables numéricas (", length(numeric_vars), "):", paste(numeric_vars, collapse = ", "), "\n")
## Variables numéricas ( 16 ): age, medu, fedu, traveltime, studytime, failures, famrel, freetime, goout, dalc, walc, health, absences, g1, g2, g3
Interpretación: El dataset combinado contiene 1044 observaciones de estudiantes, con 395 estudiantes de Matemáticas y 649 de Portugués. La estructura incluye 15 variables categóricas y 16 numéricas, proporcionando una vista integral de factores demográficos, socioeconómicos y académicos.
# Análisis de valores perdidos por variable
na_summary_overall <- df_all %>%
summarise(across(everything(), ~mean(is.na(.))*100)) %>%
pivot_longer(everything(), names_to = "variable", values_to = "pct_na_overall")
# Análisis por materia
na_summary_by_subj <- df_all %>%
group_by(subject) %>%
summarise(across(everything(), ~mean(is.na(.))*100)) %>%
pivot_longer(-subject, names_to = "variable", values_to = "pct_na") %>%
left_join(na_summary_overall, by = "variable") %>%
mutate(variable = fct_reorder(variable, pct_na_overall, .desc = TRUE))
# Tabla resumen de missingness
na_table <- na_summary_overall %>%
filter(pct_na_overall > 0) %>%
arrange(desc(pct_na_overall))
if(nrow(na_table) > 0) {
kable(na_table,
caption = "Variables con valores perdidos",
col.names = c("Variable", "% Valores Perdidos"),
digits = 2)
} else {
cat("No se detectaron valores perdidos en el dataset")
}
## No se detectaron valores perdidos en el dataset
# Visualización de valores perdidos
ggplot(na_summary_by_subj, aes(variable, pct_na, fill = subject)) +
geom_col(position = position_dodge(width = .8), width = .75) +
coord_flip() +
scale_y_continuous(labels = function(x) paste0(x, "%")) +
scale_fill_manual(values = pal) +
labs(title = "% de valores perdidos por variable y asignatura",
x = NULL, y = "% NA", fill = "Asignatura") +
theme(legend.position = "bottom")
Análisis visual de valores perdidos por materia
Conclusión sobre Missingness: Los datos están completos, sin valores perdidos, lo que indica una excelente calidad en la recolección de información. Esto elimina la necesidad de técnicas de imputación y permite proceder directamente con el análisis.
# Estadísticas descriptivas por materia
g3_stats <- df_all %>%
group_by(subject) %>%
summarise(
n = n(),
media = mean(g3),
desv_std = sd(g3),
mediana = median(g3),
q1 = quantile(g3, .25),
q3 = quantile(g3, .75),
minimo = min(g3),
maximo = max(g3),
.groups = "drop"
)
kable(g3_stats,
caption = "Estadísticas descriptivas de G3 por materia",
digits = 2,
col.names = c("Materia", "N", "Media", "Desv.Std", "Mediana", "Q1", "Q3", "Mín", "Máx"))
Materia | N | Media | Desv.Std | Mediana | Q1 | Q3 | Mín | Máx |
---|---|---|---|---|---|---|---|---|
Math | 395 | 10.42 | 4.58 | 11 | 8 | 14 | 0 | 20 |
Portuguese | 649 | 11.91 | 3.23 | 12 | 10 | 14 | 0 | 19 |
# Comparación directa con boxplot
boxplot(mat$g3, por$g3,
names = c("Math", "Portuguese"),
main = "G3 — Comparación entre asignaturas",
col = c(pal["Math"], pal["Portuguese"]),
ylab = "Calificación Final (G3)")
Distribución de calificaciones finales (G3) por materia
# Histograma comparativo
p1 <- ggplot(df_all, aes(g3, fill = subject)) +
geom_histogram(bins = 30, position = "identity", alpha = .55) +
facet_wrap(~subject, ncol = 1) +
scale_fill_manual(values = pal) +
labs(title = "Distribución de G3 por asignatura", x = "G3", y = "Frecuencia") +
theme(legend.position = "none")
# Gráfico de densidad
p2 <- ggplot(df_all, aes(g3, color = subject)) +
geom_density(linewidth = 1.2) +
scale_color_manual(values = pal) +
labs(title = "Densidad de G3 por asignatura", x = "G3", y = "Densidad") +
theme(legend.position = "bottom")
print(p1)
Distribución de calificaciones finales (G3) por materia
print(p2)
Distribución de calificaciones finales (G3) por materia
Análisis del Rendimiento por Materia:
Deducción clave: Los estudiantes obtienen calificaciones significativamente más altas en Portugués que en Matemáticas, sugiriendo que Matemáticas presenta mayor dificultad académica o que los estudiantes tienen mejor dominio de su lengua materna.
# Variables clave para análisis
nums_key <- intersect(c("g3","g2","g1","studytime","failures","absences","age"), names(df_all))
vars_panel <- unique(c(nums_key, intersect("sex", names(df_all))))
df_small <- df_all[, unique(c(vars_panel, "subject"))]
# Panel multivariado con GGally
p_mix <- ggpairs(
df_small,
mapping = ggplot2::aes(color = subject, alpha = 0.55),
upper = list(
continuous = wrap("cor", size = 3), # Correlaciones en triángulo superior
combo = "box_no_facet", # Boxplots para cat~num
discrete = "blank" # Espacio en blanco para cat~cat
),
lower = list(
continuous = wrap("points", alpha = .35, size = .6), # Scatter plots
combo = "facethist", # Histogramas por facetas
discrete = "count" # Conteos para cat~cat
),
diag = list(continuous = "densityDiag"), # Densidades en diagonal
title = "Exploración compacta — Variables clave (coloreado por asignatura)"
) + theme(legend.position = "bottom")
print(p_mix)
Panel exploratorio multivariado - Variables clave coloreadas por materia
Interpretación del Panel Multivariado:
Correlaciones G1-G2-G3: Se observan correlaciones muy altas (>0.8) entre las calificaciones de los tres períodos, indicando consistencia en el rendimiento estudiantil a lo largo del año académico.
Impacto de Failures: Las correlaciones negativas con las calificaciones finales sugieren que el historial de reprobaciones es un predictor fuerte del rendimiento actual.
Diferencias por Materia: Los patrones de dispersión muestran variaciones sistemáticas entre Matemáticas y Portugués en múltiples variables.
# Variables numéricas clave excluyendo G3
numeric_predictors <- setdiff(numeric_vars, "g3")
# Análisis de correlaciones
correlations <- df_all %>%
select(subject, all_of(numeric_predictors), g3) %>%
group_by(subject) %>%
summarise(
across(all_of(numeric_predictors), ~cor(., g3, use = "complete.obs")),
.groups = "drop"
)
# Tabla de correlaciones
cor_table <- correlations %>%
pivot_longer(-subject, names_to = "variable", values_to = "correlation") %>%
pivot_wider(names_from = subject, values_from = correlation) %>%
arrange(desc(abs(Math + Portuguese)))
kable(cor_table,
caption = "Correlaciones entre variables numéricas y G3 por materia",
digits = 3,
col.names = c("Variable", "Matemáticas", "Portugués"))
Variable | Matemáticas | Portugués |
---|---|---|
g2 | 0.905 | 0.919 |
g1 | 0.801 | 0.826 |
failures | -0.360 | -0.393 |
medu | 0.217 | 0.240 |
fedu | 0.152 | 0.212 |
studytime | 0.098 | 0.250 |
age | -0.162 | -0.107 |
dalc | -0.055 | -0.205 |
traveltime | -0.117 | -0.127 |
walc | -0.052 | -0.177 |
goout | -0.133 | -0.088 |
health | -0.061 | -0.099 |
famrel | 0.051 | 0.063 |
freetime | 0.011 | -0.123 |
absences | 0.034 | -0.091 |
# Gráficos de dispersión para variables más correlacionadas
top_vars <- cor_table$variable[1:6] # Top 6 variables más correlacionadas
plots_list <- map(top_vars, ~{
ggplot(df_all, aes(.data[[.x]], g3, color = subject)) +
geom_point(alpha = .4, size = 1) +
geom_smooth(method = "lm", se = FALSE, linewidth = 1) +
scale_color_manual(values = pal) +
labs(title = paste("G3 vs", .x), x = .x, y = "G3") +
theme(legend.position = "none")
})
# Combinar gráficos
do.call(gridExtra::grid.arrange, c(plots_list, ncol = 2))
Relaciones de dispersión entre predictores numéricos y G3
Deducciones de las Relaciones Numéricas:
G2 y G1 muestran las correlaciones más fuertes con G3 (0.905), confirmando la progresión académica consistente.
Failures presenta correlación negativa fuerte, siendo un indicador crítico de riesgo académico.
Study Time muestra correlaciones positivas moderadas, validando la importancia del tiempo de estudio.
# Variables categóricas clave
cats_key <- intersect(c("sex","address","schoolsup","higher","internet"), names(df_all))
cat_stats <- purrr::map_dfr(cats_key, function(var){
df_all %>%
group_by(subject, !!sym(var)) %>%
summarise(
n = n(),
mean_g3 = mean(g3),
sd_g3 = sd(g3),
.groups = "drop"
) %>%
mutate(
variable = var,
category = as.character(!!sym(var))
) %>%
# nos quedamos solo con las 6 columnas que queremos
select(subject, variable, category, n, mean_g3, sd_g3)
})
knitr::kable(
head(cat_stats, 10),
caption = "Estadísticas de G3 por categorías (primeras 10 filas)",
digits = 2,
col.names = c("Materia", "Variable", "Categoría", "N", "Media G3", "SD G3")
)
Materia | Variable | Categoría | N | Media G3 | SD G3 |
---|---|---|---|---|---|
Math | sex | F | 208 | 9.97 | 4.62 |
Math | sex | M | 187 | 10.91 | 4.50 |
Portuguese | sex | F | 383 | 12.25 | 3.12 |
Portuguese | sex | M | 266 | 11.41 | 3.32 |
Math | address | R | 88 | 9.51 | 4.56 |
Math | address | U | 307 | 10.67 | 4.56 |
Portuguese | address | R | 197 | 11.09 | 3.61 |
Portuguese | address | U | 452 | 12.26 | 2.99 |
Math | schoolsup | no | 344 | 10.56 | 4.77 |
Math | schoolsup | yes | 51 | 9.43 | 2.87 |
# Boxplots para variables categóricas clave
plots_cat <- map(cats_key, ~{
ggplot(df_all, aes(.data[[.x]], g3, fill = subject)) +
geom_boxplot(outlier.alpha = .2, width = .6, position = position_dodge(width = .75)) +
geom_point(aes(color = subject),
position = position_jitterdodge(jitter.width = .15, dodge.width = .75),
size = .6, alpha = .25, show.legend = FALSE) +
scale_fill_manual(values = pal) +
scale_color_manual(values = pal) +
labs(title = paste("G3 por", .x), x = .x, y = "G3") +
theme(legend.position = "bottom")
})
do.call(gridExtra::grid.arrange, c(plots_cat, ncol = 2))
Distribución de G3 por variables categóricas clave
Insights de Variables Categóricas:
Higher Education Aspiration: Los estudiantes que aspiran a educación superior muestran rendimiento significativamente mayor.
Internet Access: El acceso a internet se asocia con mejores calificaciones, sugiriendo ventajas en recursos educativos.
School Support: El apoyo escolar adicional muestra patrones complejos que requieren análisis más profundo.
# Creación de variable binaria (aprobado/reprobado)
df_bin <- df_all %>%
mutate(
status = factor(
if_else(g3 >= 10, "Aprobado", "Reprobado"),
levels = c("Reprobado", "Aprobado")
)
)
# Estadísticas de aprobación
pass_stats <- df_bin %>%
group_by(subject) %>%
summarise(
total = n(),
aprobados = sum(status == "Aprobado"),
tasa_aprobacion = mean(status == "Aprobado") * 100,
.groups = "drop"
)
kable(pass_stats,
caption = "Tasas de aprobación por materia",
digits = 1,
col.names = c("Materia", "Total", "Aprobados", "Tasa Aprobación (%)"))
Materia | Total | Aprobados | Tasa Aprobación (%) |
---|---|---|---|
Math | 395 | 265 | 67.1 |
Portuguese | 649 | 549 | 84.6 |
# Análisis de proporciones de éxito por categorías
success_plots <- map(cats_key, ~{
ggplot(df_bin, aes(.data[[.x]], fill = status)) +
geom_bar(position = "fill") +
facet_wrap(~subject) +
scale_fill_manual(values = c("Reprobado" = "#D9D9D9", "Aprobado" = "#31A354")) +
scale_y_continuous(labels = percent) +
labs(title = paste("Proporción de aprobados por", .x),
x = .x, y = "Proporción") +
theme(legend.position = "bottom")
})
do.call(gridExtra::grid.arrange, c(success_plots[1:4], ncol = 2))
Factores asociados al éxito académico
# Análisis de variables numéricas por estado
nums_success <- setdiff(nums_key, "g3")[1:4] # Top 4 variables
numeric_success_plots <- map(nums_success, ~{
ggplot(df_bin, aes(status, .data[[.x]], fill = status)) +
geom_boxplot(outlier.alpha = .25, width = .5) +
facet_wrap(~subject, scales = "free_y") +
scale_fill_manual(values = c("Reprobado" = "#FDD0A2", "Aprobado" = "#9ECAE1")) +
guides(fill = "none") +
labs(title = paste(.x, "por estado académico"), x = NULL, y = .x)
})
do.call(gridExtra::grid.arrange, c(numeric_success_plots, ncol = 2))
Distribución de variables numéricas por estado académico
Factores Críticos de Éxito Identificados:
Aspiraciones Educativas: Los estudiantes que planean continuar con educación superior tienen tasas de aprobación 80.7% vs 48.3% de quienes no planean hacerlo.
Historial Académico: Los estudiantes sin reprobaciones previas muestran tasas de éxito significativamente superiores.
Acceso a Recursos: El acceso a internet y apoyo educativo se correlaciona positivamente con el éxito académico.
Dataset: P. Cortez and A. Silva. Using Data Mining to Predict Secondary School Student Performance. In A. Brito and J. Teixeira Eds., Proceedings of 5th FUture BUsiness TEChnology Conference (FUBUTEC 2008) pp. 5-12, Porto, Portugal, April, 2008, EUROSIS, ISBN 978-9077381-39-7.
Herramientas utilizadas: - R R version 4.5.1 (2025-06-13 ucrt) - Tidyverse 2.0.0 - GGally 2.3.0