1 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.

2 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.

3 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
Variable Faltantes Porcentaje
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
Grupo Variables
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

4 Perfil de los estudiantes y sus programas

4.1 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).

4.2 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)
Departamento Frecuencia Porcentaje.Var1 Porcentaje.Freq
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%.

4.3 Áreas de formación e instituciones

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)")
Tabla 4. Top 10 áreas de formación (de 56 en total)
Area Frecuencia Porcentaje.Var1 Porcentaje.Freq
ADMINISTRACIÓN 216840 ADMINISTRACIÓN 20.68
EDUCACIÓN 97635 EDUCACIÓN 9.31
DERECHO Y AFINES 83495 DERECHO Y AFINES 7.96
CONTADURÍA PUBLICA 78364 CONTADURÍA PUBLICA 7.47
PSICOLOGÍA 75019 PSICOLOGÍA 7.15
INGENIERÍA INDUSTRIAL Y AFINES 56566 INGENIERÍA INDUSTRIAL Y AFINES 5.39
INGENIERÍA DE SISTEMAS, TELEMÁTICA Y AFINES 38214 INGENIERÍA DE SISTEMAS, TELEMÁTICA Y AFINES 3.64
INGENIERÍA CIVIL Y AFINES 34896 INGENIERÍA CIVIL Y AFINES 3.33
MEDICINA 28994 MEDICINA 2.77
SOCIOLOGÍA, TRABAJO SOCIAL Y AFINES 27535 SOCIOLOGÍA, TRABAJO SOCIAL Y AFINES 2.63
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)")
Tabla 5. Top 10 instituciones (de 271 en total)
Institucion Frecuencia Porcentaje.Var1 Porcentaje.Freq
CORPORACION UNIVERSITARIA MINUTO DE DIOS -UNIMINUTO-BOGOTÁ D.C. 78618 CORPORACION UNIVERSITARIA MINUTO DE DIOS -UNIMINUTO-BOGOTÁ D.C. 7.50
POLITECNICO GRANCOLOMBIANO-BOGOTÁ D.C. 42686 POLITECNICO GRANCOLOMBIANO-BOGOTÁ D.C. 4.07
UNIVERSIDAD NACIONAL ABIERTA Y A DISTANCIA UNAD-BOGOTÁ D.C. 29183 UNIVERSIDAD NACIONAL ABIERTA Y A DISTANCIA UNAD-BOGOTÁ D.C. 2.78
FUNDACION UNIVERSITARIA DEL AREA ANDINA-BOGOTÁ D.C. 24114 FUNDACION UNIVERSITARIA DEL AREA ANDINA-BOGOTÁ D.C. 2.30
UNIVERSIDAD COOPERATIVA DE COLOMBIA-BOGOTÁ D.C. 19385 UNIVERSIDAD COOPERATIVA DE COLOMBIA-BOGOTÁ D.C. 1.85
UNIVERSIDAD DE PAMPLONA-PAMPLONA 15093 UNIVERSIDAD DE PAMPLONA-PAMPLONA 1.44
UNIVERSIDAD DE ANTIOQUIA-MEDELLIN 14976 UNIVERSIDAD DE ANTIOQUIA-MEDELLIN 1.43
CORPORACION UNIFICADA NACIONAL DE EDUCACION SUPERIOR-CUN-BOGOTÁ D.C. 14046 CORPORACION UNIFICADA NACIONAL DE EDUCACION SUPERIOR-CUN-BOGOTÁ D.C. 1.34
UNIVERSIDAD NACIONAL DE COLOMBIA-BOGOTÁ D.C. 13685 UNIVERSIDAD NACIONAL DE COLOMBIA-BOGOTÁ D.C. 1.31
PONTIFICIA UNIVERSIDAD JAVERIANA-BOGOTÁ D.C. 13338 PONTIFICIA UNIVERSIDAD JAVERIANA-BOGOTÁ D.C. 1.27

Administración es, con amplio margen, el área de formación más reseñada (21% de los registros), seguida de educación, derecho y contaduría pública. A nivel de instituciones, ninguna concentra más del 8% del total; la más frecuente es la Corporación Universitaria Minuto de Dios (UNIMINUTO), coherente con su amplia cobertura de programas a distancia en el país.

4.4 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
Valor_matricula Frecuencia Porcentaje
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
Horas_trabajadas Frecuencia Porcentaje
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.

5 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 Frecuencia Porcentaje
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)
Indicador Pct_Si Pct_No Pct_SinDato
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.

6 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)
Modulo Media Mediana Q1 Q3 IQR DesvEst CoefVar_pct Asimetria Curtosis
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 Comunicación escrita Lectura crítica Inglés Competencias ciudadanas
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.

7 Comparación de resultados según características de los estudiantes

7.1 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
Genero Razonamiento cuantitativo Comunicación escrita Lectura crítica Inglés Competencias ciudadanas
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.

7.2 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
Metodo Razonamiento cuantitativo Comunicación escrita Lectura crítica Inglés Competencias ciudadanas
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.

7.3 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 Razonamiento cuantitativo Comunicación escrita Lectura crítica Inglés Competencias ciudadanas
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.

7.4 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
Computador Razonamiento cuantitativo Comunicación escrita Lectura crítica Inglés Competencias ciudadanas
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
Internet Razonamiento cuantitativo Comunicación escrita Lectura crítica Inglés Competencias ciudadanas
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.

8 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.

9 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")