Resumen ejecutivo
- La base reúne 1.048.575 registros de resultados de
la prueba Saber Pro, correspondientes a 271
instituciones de educación superior en 26
departamentos de Colombia. Casi la mitad de los registros (45%)
corresponde a instituciones ubicadas en Bogotá.
- La base tiene datos faltantes: entre el 1% y el 6%
de los registros según la variable (más adelante se detalla),
principalmente en las variables de condiciones familiares y de
matrícula.
- El 59% de los estudiantes son mujeres y el
78% cursó su programa de forma
presencial.
- Existe un gradiente socioeconómico claro: a mayor
estrato de vivienda, más alto el desempeño en las cinco pruebas,
especialmente en inglés, donde la brecha entre estrato
1 y estrato 6 supera los 55 puntos (sobre 300).
- Se observan también diferencias por género (los
hombres puntúan más alto en razonamiento cuantitativo e inglés) y por
método de estudio (los estudiantes presenciales
obtienen, en promedio, los puntajes más altos en los cinco
módulos).
- Los cinco módulos están correlacionados
positivamente entre sí, en particular lectura crítica con
competencias ciudadanas (r ≈ 0,69), lo que sugiere una habilidad
académica general subyacente.
Introducción
Saber Pro es la prueba estandarizada que aplica el
ICFES en Colombia a los estudiantes que están por culminar un programa
universitario, para evaluar competencias genéricas (razonamiento
cuantitativo, comunicación escrita, lectura crítica, inglés y
competencias ciudadanas). Este informe presenta un diagnóstico
descriptivo del perfil sociodemográfico y académico de los
estudiantes evaluados, así como de sus resultados, identificando
patrones que pueden orientar hipótesis para análisis posteriores.
Datos y
metodología
dim(sp)
colSums(is.na(sp))
sum(duplicated(sp$ESTU_CONSECUTIVO))
length(unique(sp$INST_NOMBRE_INSTITUCION))
length(unique(sp$ESTU_NUCLEO_PREGRADO))
length(unique(sp$ESTU_INST_DEPARTAMENTO))
La base contiene 1.048.575 filas y 23 columnas, que abarcan cinco
periodos principales de aplicación entre 2018 y 2022 (y una pequeña
fracción de registros en periodos residuales de menor tamaño). Cada fila
corresponde en principio a un estudiante distinto
(ESTU_CONSECUTIVO); se detectaron 3.170
identificadores repetidos (0,3% del total), un volumen bajo que
no afecta materialmente el diagnóstico.
La siguiente tabla resume las variables con valores ausentes:
na_counts <- colSums(is.na(sp))
na_counts <- na_counts[na_counts > 0]
tabla_na <- data.frame(
Variable = names(na_counts),
Faltantes = as.vector(na_counts),
Porcentaje = round(na_counts / nrow(sp) * 100, 2)
)
tabla_na <- tabla_na[order(-tabla_na$Faltantes), ]
knitr::kable(tabla_na, row.names = FALSE,
caption = "Tabla 1. Variables con datos faltantes")
Tabla 1. Variables con datos faltantes
| FAMI_TIENEAUTOMOVIL |
65994 |
6.29 |
| FAMI_TIENELAVADORA |
60091 |
5.73 |
| FAMI_TIENECOMPUTADOR |
57488 |
5.48 |
| FAMI_ESTRATOVIVIENDA |
48499 |
4.63 |
| ESTU_HORASSEMANATRABAJA |
47469 |
4.53 |
| FAMI_TIENEINTERNET |
40803 |
3.89 |
| ESTU_PAGOMATRICULABECA |
10438 |
1.00 |
| ESTU_PAGOMATRICULACREDITO |
10364 |
0.99 |
| ESTU_PAGOMATRICULAPADRES |
10248 |
0.98 |
| ESTU_VALORMATRICULAUNIVERSIDAD |
9988 |
0.95 |
| MOD_COMUNI_ESCRITA_PUNT |
7466 |
0.71 |
| ESTU_GENERO |
113 |
0.01 |
| MOD_INGLES_PUNT |
71 |
0.01 |
Los faltantes se concentran en las variables de condiciones
familiares (automóvil, lavadora, computador, internet, estrato)
y de matrícula, con una proporción máxima cercana al 6%
(FAMI_TIENEAUTOMOVIL). Los cinco puntajes de la prueba
prácticamente no tienen datos faltantes (0% a 0,7%). Los análisis
siguientes se calculan excluyendo los valores ausentes de cada variable
(no se imputan).
La tabla siguiente resume los grupos de variables de la base:
diccionario <- data.frame(
Grupo = c("Identificación",
"Institución y programa",
"Condiciones de matrícula y trabajo",
"Condiciones familiares",
"Resultados de la prueba"),
Variables = c(
"PERIODO, ESTU_CONSECUTIVO",
"INST_NOMBRE_INSTITUCION, ESTU_NUCLEO_PREGRADO, ESTU_INST_DEPARTAMENTO, ESTU_INST_MUNICIPIO",
"ESTU_METODO_PRGM, ESTU_VALORMATRICULAUNIVERSIDAD, ESTU_PAGOMATRICULABECA, ESTU_PAGOMATRICULACREDITO, ESTU_PAGOMATRICULAPADRES, ESTU_HORASSEMANATRABAJA",
"ESTU_GENERO, FAMI_ESTRATOVIVIENDA, FAMI_TIENEAUTOMOVIL, FAMI_TIENELAVADORA, FAMI_TIENECOMPUTADOR, FAMI_TIENEINTERNET",
"MOD_RAZONA_CUANTITAT_PUNT, MOD_COMUNI_ESCRITA_PUNT, MOD_LECTURA_CRITICA_PUNT, MOD_INGLES_PUNT, MOD_COMPETEN_CIUDADA_PUNT"
)
)
knitr::kable(diccionario, row.names = FALSE,
caption = "Tabla 2. Grupos de variables de la base")
Tabla 2. Grupos de variables de la base
| Identificación |
PERIODO, ESTU_CONSECUTIVO |
| Institución y programa |
INST_NOMBRE_INSTITUCION, ESTU_NUCLEO_PREGRADO,
ESTU_INST_DEPARTAMENTO, ESTU_INST_MUNICIPIO |
| Condiciones de matrícula y trabajo |
ESTU_METODO_PRGM, ESTU_VALORMATRICULAUNIVERSIDAD,
ESTU_PAGOMATRICULABECA, ESTU_PAGOMATRICULACREDITO,
ESTU_PAGOMATRICULAPADRES, ESTU_HORASSEMANATRABAJA |
| Condiciones familiares |
ESTU_GENERO, FAMI_ESTRATOVIVIENDA, FAMI_TIENEAUTOMOVIL,
FAMI_TIENELAVADORA, FAMI_TIENECOMPUTADOR, FAMI_TIENEINTERNET |
| Resultados de la prueba |
MOD_RAZONA_CUANTITAT_PUNT, MOD_COMUNI_ESCRITA_PUNT,
MOD_LECTURA_CRITICA_PUNT, MOD_INGLES_PUNT,
MOD_COMPETEN_CIUDADA_PUNT |
Perfil de los
estudiantes y sus programas
Género y método de
estudio
t_gen <- table(sp$ESTU_GENERO)
t_met <- sort(table(sp$ESTU_METODO_PRGM), decreasing = TRUE)
par(mfrow = c(1, 2), mar = c(6, 4, 3, 1))
barplot(as.vector(t_gen), names.arg = names(t_gen), col = "steelblue",
main = "Estudiantes por género", ylab = "Número de estudiantes")
barplot(as.vector(t_met), names.arg = names(t_met), las = 2, cex.names = 0.8,
col = "steelblue", main = "Estudiantes por método de estudio")

par(mfrow = c(1, 1))
El 59% de los estudiantes son mujeres y el
41% hombres (moda: F; 113 registros sin dato).
En cuanto al método de estudio, el 78% cursó su
programa de forma presencial, el 16% a
distancia y el 6% en distancia
virtual (moda: PRESENCIAL).
Procedencia
geográfica de las instituciones
t_dep <- sort(table(sp$ESTU_INST_DEPARTAMENTO), decreasing = TRUE)
tabla_dep <- data.frame(
Departamento = names(t_dep),
Frecuencia = as.vector(t_dep),
Porcentaje = pct(t_dep)
)
knitr::kable(tabla_dep, row.names = FALSE,
caption = "Tabla 3. Estudiantes por departamento de la institución (26 departamentos)")
Tabla 3. Estudiantes por departamento de la institución (26
departamentos)
| BOGOTÁ |
472719 |
BOGOTÁ |
45.08 |
| ANTIOQUIA |
123230 |
ANTIOQUIA |
11.75 |
| ATLANTICO |
65847 |
ATLANTICO |
6.28 |
| VALLE |
61373 |
VALLE |
5.85 |
| SANTANDER |
46175 |
SANTANDER |
4.40 |
| BOLIVAR |
29871 |
BOLIVAR |
2.85 |
| NORTE SANTANDER |
27854 |
NORTE SANTANDER |
2.66 |
| BOYACA |
20251 |
BOYACA |
1.93 |
| NARIÑO |
18589 |
NARIÑO |
1.77 |
| TOLIMA |
17837 |
TOLIMA |
1.70 |
| CUNDINAMARCA |
16571 |
CUNDINAMARCA |
1.58 |
| RISARALDA |
16401 |
RISARALDA |
1.56 |
| CALDAS |
15750 |
CALDAS |
1.50 |
| CORDOBA |
15240 |
CORDOBA |
1.45 |
| MAGDALENA |
15021 |
MAGDALENA |
1.43 |
| CAUCA |
14770 |
CAUCA |
1.41 |
| SUCRE |
13808 |
SUCRE |
1.32 |
| QUINDIO |
13319 |
QUINDIO |
1.27 |
| HUILA |
10253 |
HUILA |
0.98 |
| CESAR |
8912 |
CESAR |
0.85 |
| LA GUAJIRA |
7390 |
LA GUAJIRA |
0.70 |
| CHOCO |
7293 |
CHOCO |
0.70 |
| META |
4348 |
META |
0.41 |
| CAQUETA |
3943 |
CAQUETA |
0.38 |
| PUTUMAYO |
933 |
PUTUMAYO |
0.09 |
| CASANARE |
877 |
CASANARE |
0.08 |
La distribución geográfica está muy concentrada:
Bogotá reúne el 45% de los registros,
seguida de lejos por Antioquia (12%) y
Atlántico (6%). Los cinco primeros departamentos
concentran más del 70% de las reseñas, mientras que departamentos como
Casanare o Putumayo apenas superan el 0,1%.
Matrícula y trabajo
durante el estudio
t_mat <- table(sp$ESTU_VALORMATRICULAUNIVERSIDAD)
tabla_mat <- data.frame(
Valor_matricula = names(t_mat),
Frecuencia = as.vector(t_mat),
Porcentaje = round(as.vector(t_mat) / nrow(sp) * 100, 2)
)
knitr::kable(tabla_mat, row.names = FALSE,
caption = "Tabla 6. Valor de la matrícula universitaria")
Tabla 6. Valor de la matrícula universitaria
| No pagó matrícula |
34045 |
3.25 |
| Menos de 500 mil |
120897 |
11.53 |
| Entre 500 mil y menos de 1 millón |
116150 |
11.08 |
| Entre 1 millón y menos de 2.5 millones |
303978 |
28.99 |
| Entre 2.5 millones y menos de 4 millones |
192296 |
18.34 |
| Entre 4 millones y menos de 5.5 millones |
106692 |
10.17 |
| Entre 5.5 millones y menos de 7 millones |
59041 |
5.63 |
| Más de 7 millones |
105488 |
10.06 |
t_hor <- table(sp$ESTU_HORASSEMANATRABAJA)
tabla_hor <- data.frame(
Horas_trabajadas = names(t_hor),
Frecuencia = as.vector(t_hor),
Porcentaje = round(as.vector(t_hor) / nrow(sp) * 100, 2)
)
knitr::kable(tabla_hor, row.names = FALSE,
caption = "Tabla 7. Horas semanales trabajadas mientras se estudiaba")
Tabla 7. Horas semanales trabajadas mientras se
estudiaba
| No trabaja |
176326 |
16.82 |
| Menos de 10 horas |
133213 |
12.70 |
| Entre 11 y 20 horas |
175440 |
16.73 |
| Entre 21 y 30 horas |
140203 |
13.37 |
| Más de 30 horas |
375924 |
35.85 |
El valor de matrícula más frecuente está entre 1 y 2,5
millones de pesos (29% de los registros), y solo un 3% de los
estudiantes reporta no haber pagado matrícula. Respecto al trabajo
durante el estudio, la categoría más frecuente es más de 30
horas semanales (36%), lo que indica que una parte importante
de los estudiantes combina el estudio con una carga laboral
considerable; un 17% declara no trabajar.
Condiciones
socioeconómicas familiares
t_estr <- table(sp$FAMI_ESTRATOVIVIENDA)
tabla_estr <- data.frame(
Estrato = names(t_estr),
Frecuencia = as.vector(t_estr),
Porcentaje = round(as.vector(t_estr) / nrow(sp) * 100, 2)
)
knitr::kable(tabla_estr, row.names = FALSE,
caption = "Tabla 8. Estrato socioeconómico de la vivienda")
Tabla 8. Estrato socioeconómico de la vivienda
| Estrato 1 |
172016 |
16.40 |
| Estrato 2 |
351280 |
33.50 |
| Estrato 3 |
317965 |
30.32 |
| Estrato 4 |
98738 |
9.42 |
| Estrato 5 |
35869 |
3.42 |
| Estrato 6 |
19245 |
1.84 |
| Sin Estrato |
4963 |
0.47 |
bin_vars <- c("ESTU_PAGOMATRICULABECA", "ESTU_PAGOMATRICULACREDITO",
"ESTU_PAGOMATRICULAPADRES", "FAMI_TIENEAUTOMOVIL",
"FAMI_TIENELAVADORA", "FAMI_TIENECOMPUTADOR", "FAMI_TIENEINTERNET")
bin_etiquetas <- c("Matrícula pagada con beca", "Matrícula pagada con crédito",
"Matrícula pagada por los padres", "Familia tiene automóvil",
"Familia tiene lavadora", "Familia tiene computador",
"Familia tiene internet")
tabla_bin <- data.frame(
Indicador = bin_etiquetas,
Pct_Si = sapply(bin_vars, function(v) round(mean(sp[[v]] == "Si", na.rm = TRUE) * 100, 1)),
Pct_No = sapply(bin_vars, function(v) round(mean(sp[[v]] == "No", na.rm = TRUE) * 100, 1)),
Pct_SinDato = sapply(bin_vars, function(v) round(mean(is.na(sp[[v]])) * 100, 1))
)
knitr::kable(tabla_bin, row.names = FALSE,
caption = "Tabla 9. Indicadores socioeconómicos familiares (% sobre el total de registros)")
Tabla 9. Indicadores socioeconómicos familiares (% sobre el
total de registros)
| Matrícula pagada con beca |
20.1 |
79.9 |
1.0 |
| Matrícula pagada con crédito |
32.2 |
67.8 |
1.0 |
| Matrícula pagada por los padres |
50.6 |
49.4 |
1.0 |
| Familia tiene automóvil |
36.4 |
63.6 |
6.3 |
| Familia tiene lavadora |
86.3 |
13.7 |
5.7 |
| Familia tiene computador |
91.1 |
8.9 |
5.5 |
| Familia tiene internet |
89.0 |
11.0 |
3.9 |
El estrato 2 es el más frecuente (34%), seguido del
estrato 3 (30%) y el estrato 1 (16%); los estratos 5 y 6 son
minoritarios (5% en conjunto), un patrón esperable dado el perfil
socioeconómico de acceso a la educación superior en Colombia. En cuanto
a los bienes y servicios del hogar, el acceso a
computador e internet es alto (86% y
86% respectivamente), mientras que el automóvil es el
bien menos frecuente (34%). La mitad de los estudiantes (50%) reporta
que sus padres pagaron la matrícula, y un 32% la pagó con crédito.
Resultados en las
pruebas Saber Pro
resumen <- data.frame(
Modulo = nombres_mod,
Media = sapply(cuant, function(v) mean(sp[[v]], na.rm = TRUE)),
Mediana = sapply(cuant, function(v) median(sp[[v]], na.rm = TRUE)),
Q1 = sapply(cuant, function(v) quantile(sp[[v]], .25, na.rm = TRUE)),
Q3 = sapply(cuant, function(v) quantile(sp[[v]], .75, na.rm = TRUE)),
IQR = sapply(cuant, function(v) IQR(sp[[v]], na.rm = TRUE)),
DesvEst = sapply(cuant, function(v) sd(sp[[v]], na.rm = TRUE)),
CoefVar_pct = sapply(cuant, function(v) sd(sp[[v]], na.rm = TRUE) / mean(sp[[v]], na.rm = TRUE) * 100),
Asimetria = sapply(cuant, function(v) asimetria(sp[[v]])),
Curtosis = sapply(cuant, function(v) curtosis(sp[[v]]))
)
rownames(resumen) <- NULL
knitr::kable(resumen, digits = 2,
caption = "Tabla 10. Estadísticos descriptivos de los cinco módulos (escala 0-300)")
Tabla 10. Estadísticos descriptivos de los cinco módulos
(escala 0-300)
| Razonamiento cuantitativo |
147.0 |
147 |
124 |
169 |
45 |
32.29 |
21.97 |
0.18 |
0.17 |
| Comunicación escrita |
141.2 |
141 |
125 |
165 |
40 |
39.38 |
27.89 |
-0.67 |
4.55 |
| Lectura crítica |
149.2 |
149 |
127 |
172 |
45 |
31.08 |
20.83 |
-0.03 |
0.13 |
| Inglés |
153.4 |
150 |
131 |
175 |
44 |
33.49 |
21.84 |
0.07 |
2.86 |
| Competencias ciudadanas |
145.0 |
146 |
121 |
169 |
48 |
33.79 |
23.31 |
-0.13 |
0.32 |
par(mfrow = c(2, 3))
for (i in seq_along(cuant)) {
hist(sp[[cuant[i]]], main = nombres_mod[i], xlab = "Puntaje",
col = "steelblue", border = "white")
}
par(mfrow = c(1, 1))

mat_cor <- round(cor(sp[cuant], use = "pairwise.complete.obs"), 2)
colnames(mat_cor) <- rownames(mat_cor) <- nombres_mod
knitr::kable(mat_cor, caption = "Tabla 11. Correlación entre los cinco módulos")
Tabla 11. Correlación entre los cinco módulos
| Razonamiento cuantitativo |
1.00 |
0.26 |
0.59 |
0.48 |
0.52 |
| Comunicación escrita |
0.26 |
1.00 |
0.30 |
0.24 |
0.27 |
| Lectura crítica |
0.59 |
0.30 |
1.00 |
0.53 |
0.69 |
| Inglés |
0.48 |
0.24 |
0.53 |
1.00 |
0.53 |
| Competencias ciudadanas |
0.52 |
0.27 |
0.69 |
0.53 |
1.00 |
Los cinco módulos se califican sobre 300 puntos y sus medias son
similares entre sí (entre 141 y 153). La mayoría de los módulos presenta
una distribución aproximadamente simétrica (asimetría
cercana a 0), salvo comunicación escrita, que muestra
asimetría negativa (-0,67) y una curtosis alta (4,5) —una mayor
concentración de puntajes centrales con colas más pesadas que una
distribución normal—, e inglés, con curtosis igualmente
alta (2,9) pese a tener asimetría casi nula; esto último es consistente
con el hecho de que el puntaje de inglés en Saber Pro suele derivarse de
niveles de dominio (MCER), lo que genera cierta concentración de
valores. Para todos los módulos, media y mediana son razonablemente
cercanas, por lo que la media y la desviación estándar siguen siendo
medidas resumen razonables, complementadas con la mediana para
comunicación escrita e inglés. La matriz de correlación muestra que los
cinco módulos están positivamente correlacionados,
especialmente lectura crítica con competencias ciudadanas (r ≈ 0,69) y
con razonamiento cuantitativo (r ≈ 0,59), lo que sugiere una habilidad
académica general que atraviesa las distintas competencias
evaluadas.
Comparación de
resultados según características de los estudiantes
Por género
media_gen <- aggregate(sp[cuant], by = list(Genero = sp$ESTU_GENERO), FUN = mean, na.rm = TRUE)
names(media_gen)[-1] <- nombres_mod
knitr::kable(media_gen, digits = 1,
caption = "Tabla 12. Puntaje promedio por género")
Tabla 12. Puntaje promedio por género
| F |
140.9 |
141.9 |
148.1 |
150.6 |
143.0 |
| M |
155.8 |
140.1 |
151.0 |
157.4 |
147.8 |
Los hombres obtienen en promedio puntajes más altos
en razonamiento cuantitativo (156 frente a 141 en
mujeres, una brecha de 15 puntos) y en inglés (157
frente a 151); las mujeres puntúan levemente más alto
en comunicación escrita (142 frente a 140). Estas
diferencias son coherentes con patrones reportados en otras pruebas
estandarizadas y no implican una diferencia de capacidad inherente entre
géneros: pueden reflejar factores educativos y sociales previos a la
universidad.
Por método de
estudio
media_met <- aggregate(sp[cuant], by = list(Metodo = sp$ESTU_METODO_PRGM), FUN = mean, na.rm = TRUE)
names(media_met)[-1] <- nombres_mod
knitr::kable(media_met, digits = 1,
caption = "Tabla 13. Puntaje promedio por método de estudio")
Tabla 13. Puntaje promedio por método de estudio
| DISTANCIA |
131.9 |
132.7 |
134.4 |
133.6 |
131.5 |
| DISTANCIA VITUAL |
136.0 |
133.8 |
139.2 |
142.7 |
137.2 |
| PRESENCIAL |
150.9 |
143.5 |
153.0 |
158.2 |
148.3 |
boxplot(MOD_INGLES_PUNT ~ ESTU_METODO_PRGM, data = sp, col = "lightgoldenrod",
main = "Puntaje de inglés según método de estudio", ylab = "Puntaje")

Los estudiantes de programas presenciales obtienen
los promedios más altos en los cinco módulos, con una diferencia
particularmente marcada en inglés (158 frente a 134 en
programas a distancia, ≈24 puntos). Esta diferencia debe interpretarse
con cautela: los estudiantes de programas a distancia suelen tener
perfiles distintos (mayor edad, más horas de trabajo, menor exposición
previa a segunda lengua), por lo que la diferencia observada
probablemente combina efecto del método de estudio con factores
de selección de quién accede a cada modalidad.
Por estrato
socioeconómico
media_estr <- aggregate(sp[cuant], by = list(Estrato = sp$FAMI_ESTRATOVIVIENDA), FUN = mean, na.rm = TRUE)
names(media_estr)[-1] <- nombres_mod
knitr::kable(media_estr, digits = 1,
caption = "Tabla 14. Puntaje promedio por estrato socioeconómico")
Tabla 14. Puntaje promedio por estrato socioeconómico
| Estrato 1 |
137.3 |
135.0 |
140.1 |
139.5 |
135.7 |
| Estrato 2 |
144.6 |
139.2 |
146.4 |
148.2 |
143.0 |
| Estrato 3 |
149.3 |
142.4 |
151.7 |
157.6 |
148.4 |
| Estrato 4 |
156.7 |
148.0 |
159.8 |
173.8 |
156.2 |
| Estrato 5 |
160.8 |
152.1 |
163.2 |
184.7 |
159.2 |
| Estrato 6 |
164.4 |
157.2 |
165.3 |
196.1 |
161.6 |
| Sin Estrato |
135.9 |
131.8 |
138.4 |
141.4 |
130.2 |
boxplot(MOD_INGLES_PUNT ~ FAMI_ESTRATOVIVIENDA, data = sp, col = "lightgreen",
main = "Puntaje de inglés según estrato", ylab = "Puntaje", las = 2, cex.axis = 0.8)

Se observa un gradiente socioeconómico consistente:
a medida que aumenta el estrato de vivienda, aumentan los puntajes
promedio en los cinco módulos. La brecha es más pronunciada en
inglés, donde el estrato 6 promedia 196 puntos frente a
140 en el estrato 1 (≈56 puntos de diferencia), mientras que en los
demás módulos las brechas rondan entre 20 y 30 puntos. Esto es
consistente con el mayor acceso a educación en lengua extranjera
(colegios bilingües, cursos privados) entre los hogares de mayor
estrato. La categoría “Sin Estrato” (viviendas rurales o sin
clasificación, 0,5% de los registros) presenta puntajes similares a los
estratos 1-2.
Acceso a computador e
internet en el hogar
media_comp <- aggregate(sp[cuant], by = list(Computador = sp$FAMI_TIENECOMPUTADOR), FUN = mean, na.rm = TRUE)
media_int <- aggregate(sp[cuant], by = list(Internet = sp$FAMI_TIENEINTERNET), FUN = mean, na.rm = TRUE)
names(media_comp)[-1] <- nombres_mod
names(media_int)[-1] <- nombres_mod
knitr::kable(media_comp, digits = 1,
caption = "Tabla 15. Puntaje promedio según si la familia tiene computador")
Tabla 15. Puntaje promedio según si la familia tiene
computador
| No |
134.9 |
135.5 |
137.8 |
137.8 |
132.3 |
| Si |
148.3 |
141.8 |
150.4 |
156.2 |
147.0 |
knitr::kable(media_int, digits = 1,
caption = "Tabla 16. Puntaje promedio según si la familia tiene internet")
Tabla 16. Puntaje promedio según si la familia tiene
internet
| No |
136.6 |
136.3 |
138.1 |
137.1 |
133.1 |
| Si |
148.2 |
141.8 |
150.6 |
156.5 |
147.1 |
Contar con computador e internet en
el hogar se asocia con puntajes más altos en los cinco módulos
(diferencias de 9 a 20 puntos frente a quienes no cuentan con ellos),
reforzando el patrón socioeconómico observado por estrato. De nuevo,
esta asociación es descriptiva: probablemente refleja el mismo trasfondo
socioeconómico que el estrato de vivienda, más que un efecto aislado del
acceso tecnológico.
Conclusiones y
limitaciones
- El perfil de estudiantes evaluados está concentrado geográficamente
en Bogotá (45%) y en programas
presenciales (78%), con una mayoría de
mujeres (59%).
- Esta base sí tiene datos faltantes (hasta 6% en
algunas variables de condiciones familiares), que fueron excluidos del
cálculo de cada indicador en lugar de imputados.
- Existe un gradiente socioeconómico claro en los
resultados: a mayor estrato y mayor acceso a computador/internet,
mejores puntajes en los cinco módulos, con la brecha más marcada en
inglés.
- Se observan diferencias de género (hombres más
altos en razonamiento cuantitativo e inglés) y de método de
estudio (presencial > distancia > distancia virtual en
todos los módulos).
- Los cinco módulos están correlacionados
positivamente entre sí, sugiriendo una habilidad académica
general subyacente además de las competencias específicas de cada
uno.
- Limitación central: este es un análisis
descriptivo y observacional. Las diferencias por
género, método de estudio o condición socioeconómica no deben
interpretarse como relaciones causales: factores de selección
(quién estudia qué, cómo y dónde) y variables no observadas pueden
explicar parte de los patrones encontrados.
Anexo: código fuente
completo
knitr::opts_chunk$set(echo = TRUE, warning = FALSE, message = FALSE)
options(scipen = 100, digits = 4)
sp <- read.csv("data/saberpro.csv", stringsAsFactors = FALSE,
encoding = "UTF-8", na.strings = c("NA", ""))
moda <- function(x) {
x <- x[!is.na(x)]
ux <- unique(x)
ux[which.max(tabulate(match(x, ux)))]
}
asimetria <- function(x) {
x <- x[!is.na(x)]; n <- length(x); m <- mean(x); s <- sd(x)
(sum((x - m)^3) / n) / s^3
}
curtosis <- function(x) {
x <- x[!is.na(x)]; n <- length(x); m <- mean(x); s <- sd(x)
(sum((x - m)^4) / n) / s^4 - 3
}
pct <- function(t) round(prop.table(t) * 100, 2)
cuant <- c("MOD_RAZONA_CUANTITAT_PUNT", "MOD_COMUNI_ESCRITA_PUNT",
"MOD_LECTURA_CRITICA_PUNT", "MOD_INGLES_PUNT",
"MOD_COMPETEN_CIUDADA_PUNT")
nombres_mod <- c("Razonamiento cuantitativo", "Comunicación escrita",
"Lectura crítica", "Inglés", "Competencias ciudadanas")
# Reordenar niveles ordinales para tablas y gráficos
sp$ESTU_HORASSEMANATRABAJA[sp$ESTU_HORASSEMANATRABAJA == "0"] <- "No trabaja"
niveles_horas <- c("No trabaja", "Menos de 10 horas", "Entre 11 y 20 horas",
"Entre 21 y 30 horas", "Más de 30 horas")
sp$ESTU_HORASSEMANATRABAJA <- factor(sp$ESTU_HORASSEMANATRABAJA, levels = niveles_horas)
niveles_matricula <- c("No pagó matrícula", "Menos de 500 mil",
"Entre 500 mil y menos de 1 millón",
"Entre 1 millón y menos de 2.5 millones",
"Entre 2.5 millones y menos de 4 millones",
"Entre 4 millones y menos de 5.5 millones",
"Entre 5.5 millones y menos de 7 millones",
"Más de 7 millones")
sp$ESTU_VALORMATRICULAUNIVERSIDAD <- factor(sp$ESTU_VALORMATRICULAUNIVERSIDAD,
levels = niveles_matricula)
niveles_estrato <- c("Estrato 1", "Estrato 2", "Estrato 3", "Estrato 4",
"Estrato 5", "Estrato 6", "Sin Estrato")
sp$FAMI_ESTRATOVIVIENDA <- factor(sp$FAMI_ESTRATOVIVIENDA, levels = niveles_estrato)
dim(sp)
colSums(is.na(sp))
sum(duplicated(sp$ESTU_CONSECUTIVO))
length(unique(sp$INST_NOMBRE_INSTITUCION))
length(unique(sp$ESTU_NUCLEO_PREGRADO))
length(unique(sp$ESTU_INST_DEPARTAMENTO))
na_counts <- colSums(is.na(sp))
na_counts <- na_counts[na_counts > 0]
tabla_na <- data.frame(
Variable = names(na_counts),
Faltantes = as.vector(na_counts),
Porcentaje = round(na_counts / nrow(sp) * 100, 2)
)
tabla_na <- tabla_na[order(-tabla_na$Faltantes), ]
knitr::kable(tabla_na, row.names = FALSE,
caption = "Tabla 1. Variables con datos faltantes")
diccionario <- data.frame(
Grupo = c("Identificación",
"Institución y programa",
"Condiciones de matrícula y trabajo",
"Condiciones familiares",
"Resultados de la prueba"),
Variables = c(
"PERIODO, ESTU_CONSECUTIVO",
"INST_NOMBRE_INSTITUCION, ESTU_NUCLEO_PREGRADO, ESTU_INST_DEPARTAMENTO, ESTU_INST_MUNICIPIO",
"ESTU_METODO_PRGM, ESTU_VALORMATRICULAUNIVERSIDAD, ESTU_PAGOMATRICULABECA, ESTU_PAGOMATRICULACREDITO, ESTU_PAGOMATRICULAPADRES, ESTU_HORASSEMANATRABAJA",
"ESTU_GENERO, FAMI_ESTRATOVIVIENDA, FAMI_TIENEAUTOMOVIL, FAMI_TIENELAVADORA, FAMI_TIENECOMPUTADOR, FAMI_TIENEINTERNET",
"MOD_RAZONA_CUANTITAT_PUNT, MOD_COMUNI_ESCRITA_PUNT, MOD_LECTURA_CRITICA_PUNT, MOD_INGLES_PUNT, MOD_COMPETEN_CIUDADA_PUNT"
)
)
knitr::kable(diccionario, row.names = FALSE,
caption = "Tabla 2. Grupos de variables de la base")
t_gen <- table(sp$ESTU_GENERO)
t_met <- sort(table(sp$ESTU_METODO_PRGM), decreasing = TRUE)
par(mfrow = c(1, 2), mar = c(6, 4, 3, 1))
barplot(as.vector(t_gen), names.arg = names(t_gen), col = "steelblue",
main = "Estudiantes por género", ylab = "Número de estudiantes")
barplot(as.vector(t_met), names.arg = names(t_met), las = 2, cex.names = 0.8,
col = "steelblue", main = "Estudiantes por método de estudio")
par(mfrow = c(1, 1))
t_dep <- sort(table(sp$ESTU_INST_DEPARTAMENTO), decreasing = TRUE)
tabla_dep <- data.frame(
Departamento = names(t_dep),
Frecuencia = as.vector(t_dep),
Porcentaje = pct(t_dep)
)
knitr::kable(tabla_dep, row.names = FALSE,
caption = "Tabla 3. Estudiantes por departamento de la institución (26 departamentos)")
t_nuc <- sort(table(sp$ESTU_NUCLEO_PREGRADO), decreasing = TRUE)
tabla_nuc <- data.frame(
Area = names(head(t_nuc, 10)),
Frecuencia = as.vector(head(t_nuc, 10)),
Porcentaje = pct(t_nuc)[1:10]
)
knitr::kable(tabla_nuc, row.names = FALSE,
caption = "Tabla 4. Top 10 áreas de formación (de 56 en total)")
t_inst <- sort(table(sp$INST_NOMBRE_INSTITUCION), decreasing = TRUE)
tabla_inst <- data.frame(
Institucion = names(head(t_inst, 10)),
Frecuencia = as.vector(head(t_inst, 10)),
Porcentaje = pct(t_inst)[1:10]
)
knitr::kable(tabla_inst, row.names = FALSE,
caption = "Tabla 5. Top 10 instituciones (de 271 en total)")
t_mat <- table(sp$ESTU_VALORMATRICULAUNIVERSIDAD)
tabla_mat <- data.frame(
Valor_matricula = names(t_mat),
Frecuencia = as.vector(t_mat),
Porcentaje = round(as.vector(t_mat) / nrow(sp) * 100, 2)
)
knitr::kable(tabla_mat, row.names = FALSE,
caption = "Tabla 6. Valor de la matrícula universitaria")
t_hor <- table(sp$ESTU_HORASSEMANATRABAJA)
tabla_hor <- data.frame(
Horas_trabajadas = names(t_hor),
Frecuencia = as.vector(t_hor),
Porcentaje = round(as.vector(t_hor) / nrow(sp) * 100, 2)
)
knitr::kable(tabla_hor, row.names = FALSE,
caption = "Tabla 7. Horas semanales trabajadas mientras se estudiaba")
t_estr <- table(sp$FAMI_ESTRATOVIVIENDA)
tabla_estr <- data.frame(
Estrato = names(t_estr),
Frecuencia = as.vector(t_estr),
Porcentaje = round(as.vector(t_estr) / nrow(sp) * 100, 2)
)
knitr::kable(tabla_estr, row.names = FALSE,
caption = "Tabla 8. Estrato socioeconómico de la vivienda")
bin_vars <- c("ESTU_PAGOMATRICULABECA", "ESTU_PAGOMATRICULACREDITO",
"ESTU_PAGOMATRICULAPADRES", "FAMI_TIENEAUTOMOVIL",
"FAMI_TIENELAVADORA", "FAMI_TIENECOMPUTADOR", "FAMI_TIENEINTERNET")
bin_etiquetas <- c("Matrícula pagada con beca", "Matrícula pagada con crédito",
"Matrícula pagada por los padres", "Familia tiene automóvil",
"Familia tiene lavadora", "Familia tiene computador",
"Familia tiene internet")
tabla_bin <- data.frame(
Indicador = bin_etiquetas,
Pct_Si = sapply(bin_vars, function(v) round(mean(sp[[v]] == "Si", na.rm = TRUE) * 100, 1)),
Pct_No = sapply(bin_vars, function(v) round(mean(sp[[v]] == "No", na.rm = TRUE) * 100, 1)),
Pct_SinDato = sapply(bin_vars, function(v) round(mean(is.na(sp[[v]])) * 100, 1))
)
knitr::kable(tabla_bin, row.names = FALSE,
caption = "Tabla 9. Indicadores socioeconómicos familiares (% sobre el total de registros)")
resumen <- data.frame(
Modulo = nombres_mod,
Media = sapply(cuant, function(v) mean(sp[[v]], na.rm = TRUE)),
Mediana = sapply(cuant, function(v) median(sp[[v]], na.rm = TRUE)),
Q1 = sapply(cuant, function(v) quantile(sp[[v]], .25, na.rm = TRUE)),
Q3 = sapply(cuant, function(v) quantile(sp[[v]], .75, na.rm = TRUE)),
IQR = sapply(cuant, function(v) IQR(sp[[v]], na.rm = TRUE)),
DesvEst = sapply(cuant, function(v) sd(sp[[v]], na.rm = TRUE)),
CoefVar_pct = sapply(cuant, function(v) sd(sp[[v]], na.rm = TRUE) / mean(sp[[v]], na.rm = TRUE) * 100),
Asimetria = sapply(cuant, function(v) asimetria(sp[[v]])),
Curtosis = sapply(cuant, function(v) curtosis(sp[[v]]))
)
rownames(resumen) <- NULL
knitr::kable(resumen, digits = 2,
caption = "Tabla 10. Estadísticos descriptivos de los cinco módulos (escala 0-300)")
par(mfrow = c(2, 3))
for (i in seq_along(cuant)) {
hist(sp[[cuant[i]]], main = nombres_mod[i], xlab = "Puntaje",
col = "steelblue", border = "white")
}
par(mfrow = c(1, 1))
mat_cor <- round(cor(sp[cuant], use = "pairwise.complete.obs"), 2)
colnames(mat_cor) <- rownames(mat_cor) <- nombres_mod
knitr::kable(mat_cor, caption = "Tabla 11. Correlación entre los cinco módulos")
media_gen <- aggregate(sp[cuant], by = list(Genero = sp$ESTU_GENERO), FUN = mean, na.rm = TRUE)
names(media_gen)[-1] <- nombres_mod
knitr::kable(media_gen, digits = 1,
caption = "Tabla 12. Puntaje promedio por género")
media_met <- aggregate(sp[cuant], by = list(Metodo = sp$ESTU_METODO_PRGM), FUN = mean, na.rm = TRUE)
names(media_met)[-1] <- nombres_mod
knitr::kable(media_met, digits = 1,
caption = "Tabla 13. Puntaje promedio por método de estudio")
boxplot(MOD_INGLES_PUNT ~ ESTU_METODO_PRGM, data = sp, col = "lightgoldenrod",
main = "Puntaje de inglés según método de estudio", ylab = "Puntaje")
media_estr <- aggregate(sp[cuant], by = list(Estrato = sp$FAMI_ESTRATOVIVIENDA), FUN = mean, na.rm = TRUE)
names(media_estr)[-1] <- nombres_mod
knitr::kable(media_estr, digits = 1,
caption = "Tabla 14. Puntaje promedio por estrato socioeconómico")
boxplot(MOD_INGLES_PUNT ~ FAMI_ESTRATOVIVIENDA, data = sp, col = "lightgreen",
main = "Puntaje de inglés según estrato", ylab = "Puntaje", las = 2, cex.axis = 0.8)
media_comp <- aggregate(sp[cuant], by = list(Computador = sp$FAMI_TIENECOMPUTADOR), FUN = mean, na.rm = TRUE)
media_int <- aggregate(sp[cuant], by = list(Internet = sp$FAMI_TIENEINTERNET), FUN = mean, na.rm = TRUE)
names(media_comp)[-1] <- nombres_mod
names(media_int)[-1] <- nombres_mod
knitr::kable(media_comp, digits = 1,
caption = "Tabla 15. Puntaje promedio según si la familia tiene computador")
knitr::kable(media_int, digits = 1,
caption = "Tabla 16. Puntaje promedio según si la familia tiene internet")