Unidad 3 - Visualización y estadísticas descriptivas

Curso Nivelatorio de R · CIENFI · Universidad Icesi

Autor/a

PhD. Eduard F. Martínez-González

1 Objetivo de la unidad

En la Unidad 1 aprendimos a trabajar con objetos y scripts; en la Unidad 2, a importar y limpiar una base de datos. Esta unidad cierra ese ciclo: pasar de la base limpia al hallazgo comunicable. El objetivo NO es memorizar geometrías de ggplot2: es aprender a elegir el gráfico (o la tabla) que responde una pregunta concreta, y a presentarlo de forma que otra persona lo entienda sin que usted esté al lado explicando.

¿Por qué gráficos y descriptivas en una Maestría en Economía?

Porque en economía aplicada las estadísticas descriptivas y los gráficos no son un adorno: son la sección 2 de todo paper — la sección de “Datos y hechos estilizados”. Antes de estimar cualquier modelo, un buen trabajo empírico describe sus variables, documenta los patrones básicos (¿quién gana más? ¿cómo se distribuye el ingreso?) y convence al lector de que hay algo que vale la pena explicar. Si las descriptivas no se entienden, nadie va a creer las regresiones que vienen después.

1.1 El hilo conductor: la brecha de ingresos en la GEIH

Toda la unidad gira alrededor de una pregunta empírica real, con datos reales: ¿cuánto ganan los ocupados en Colombia, y cómo difiere ese ingreso por sexo y por educación? De esa pregunta grande se desprenden las que iremos respondiendo sección por sección: ¿cómo se distribuye el ingreso laboral (adelanto: de forma nada simétrica)? ¿Ganan distinto hombres y mujeres, y cambia la respuesta entre promedios y medianas? ¿Más educación se asocia con más ingreso? ¿La brecha por sexo aparece también dentro de cada nivel educativo?

1.2 Lo que aprenderemos en esta unidad

  • Construir gráficos por capas con ggplot2: la gramática de gráficos — datos + mapeos (aes) + geometrías + personalización — y el operador + para sumar capas.
  • Elegir el gráfico según la pregunta: distribuciones (histograma, densidad, boxplot), comparaciones entre categorías (barras), relaciones (dispersión) y evolución en el tiempo (líneas).
  • Resumir la base con estadísticas descriptivas: summary()/skim(), tablas de frecuencia, descriptivas por grupo y tablas listas para informe con modelsummary.
  • Comunicar y exportar: títulos, unidades, fuente de datos, temas, facetas y ggsave() — con la regla del curso: todo gráfico de un informe nace de un script.

Conexión con la Unidad 2

En la unidad anterior usamos group_by() + summarise() para construir tablas resumen; esta unidad convierte esas tablas en gráficos que se leen en segundos. La tabla con el ingreso mediano por sexo se vuelve un geom_col() que se lee en 3 segundos; la tabla de 10 filas por nivel educativo y sexo se vuelve un geom_col(position = "dodge") que muestra la brecha de un vistazo. El flujo siempre es el mismo: primero preparar los datos con dplyr, luego graficar con ggplot2.

2 La base de la unidad: un extracto de la GEIH

La GEIH (Gran Encuesta Integrada de Hogares) es la encuesta del DANE con la que se miden la ocupación, el desempleo y los ingresos laborales en Colombia. En esta unidad (y en la Unidad 4) trabajaremos con geih_nivelacion.csv, un extracto real de la GEIH preparado para el curso: 21.821 personas ocupadas de 18 a 65 años, con ingreso laboral positivo, residentes en cabeceras. Una fila por persona; la base ya está limpia. La descripción completa está en el diccionario de datos del curso.

Variable Descripción
id Identificador de la persona (1…21.821)
sexo hombre / mujer
edad Edad en años (18–65)
anios_educacion Años de educación aprobados (0–26)
nivel_educativo ninguno, primaria, secundaria, media, superior
departamento Departamento de residencia (24 cabeceras GEIH)
ingreso_laboral Ingreso laboral mensual (COP)
horas_semana Horas normalmente trabajadas por semana

En RStudio, dentro del proyecto del curso, la importamos y le damos el primer vistazo (Unidad 2):

## llamar/instalar librerias
require(pacman)
p_load(tidyverse, rio, janitor, skimr)

## importar el extracto de la GEIH
geih <- import("datos/originales/geih_nivelacion.csv")

glimpse(geih)
skim(geih)

Qué buscar en el output: glimpse() debe mostrar 21.821 filas, 8 columnas y tipos correctos (el ingreso numérico, no texto). skim() agrega mínimos, máximos, percentiles y un mini-histograma por variable. Fíjese en el ingreso laboral: la media queda muy por encima del percentil 50. Esa distancia es la primera pista de toda la unidad.

Advertencia

El ingreso laboral tiene outliers reales

En toda encuesta de hogares, unas pocas personas reportan ingresos decenas de veces mayores que la mediana. No son errores de digitación: son outliers reales. La consecuencia práctica es que los gráficos y promedios en niveles engañan: el histograma queda “aplastado” contra la izquierda y el promedio se infla. La solución habitual en economía es trabajar con log(ingreso_laboral); lo veremos de frente en la sección Niveles o logaritmos.

Cómo leer esta unidad: dos tipos de código

Los bloques interactivos (con botón Run code) corren en su navegador con webR; como el navegador no puede leer archivos de su disco, usan geih_mini, una mini base de 16 personas que imita la estructura de la GEIH — incluido un outlier de ingreso. geih_mini ya viene cargada en el documento: se crea automáticamente al abrir la página (igual que la mini serie desempleo que usaremos más adelante), así que ninguna celda visible la construye — usted solo la usa. Los bloques estáticos (sin botón) se corren en RStudio, sobre la base completa: son los que van en sus scripts. Lo único que debe ejecutar una vez, antes del primer gráfico, es la celda de librerías de esta sección: webR comparte el environment en toda la página.

Cargue las librerías de la unidad y conozca la mini base — no hay que construirla: ya está en memoria, solo la imprimimos:

Nota importante: la persona 16 gana 25 millones — nuestro outlier de juguete. Reproduce, a escala, el mismo fenómeno que encontrará en la base completa. Las conclusiones económicas serias, eso sí, se verifican siempre con la base real en la práctica de la unidad.

3 La gramática de gráficos

En Excel seleccionamos datos y elegimos “gráfico de barras” de un menú: rápido, pero rígido. ggplot2 sigue otra lógica, la gramática de gráficos (grammar of graphics, de ahí las dos “g”): todo gráfico se arma con la misma fórmula — datos + mapeos estéticos (aes) + geometrías + personalización — y las piezas se conectan por capas con el operador +.

La construcción es acumulativa (por capas)

Piense en amoblar un cuarto: (1) Datosggplot(data = mi_tabla) → entra al cuarto vacío: R reserva el lienzo para esa tabla, pero no pinta nada. (2) Mapeosaes(x = ..., y = ...) → decide qué va en qué pared: qué variable define cada eje (y el color, el tamaño…). Aparecen los ejes, el interior sigue vacío. (3) Geometríageom_col(), geom_point(), geom_line()… → por fin llegan los muebles: las barras, los puntos, las líneas. Cada capa adicional (títulos, temas, facetas) se suma con +.

3.1 Construcción paso a paso

Construyamos el primer gráfico de la brecha: el ingreso mediano por sexo. Paso 0 es preparar la tabla con dplyr (lo que ya sabemos de la Unidad 2):

Paso 1 — Solo los datos: el lienzo en blanco. R muestra un rectángulo gris vacío: ya sabe qué tabla vamos a graficar, pero no qué variables ni cómo representarlas.

Paso 2 — Agregar el mapeo: aparecen los ejes. R ya sabe qué mostrar, pero no cómo dibujarlo. (mapping = aes(...) es la forma completa; en la práctica se abrevia aes(...).)

Paso 3 — Agregar la geometría: aparecen las barras.

Lea el código como una frase: la primera línea dice qué tabla grafico; aes(), qué variable va en cada eje; geom_col(), cómo se dibujan los datos. El operador + se lee “y además agrega esta capa” — en las siguientes secciones seguiremos sumando capas: colores, títulos, temas y facetas.

Ejercicio mental: leer código antes de ejecutarlo

Prediga qué muestra el paso 3 si reemplaza geom_col() por geom_point(). Misma tabla, mismos ejes… solo cambia la pieza de geometría. Respuesta: el mismo gráfico, pero con dos puntos flotantes en lugar de barras. La lógica de construcción es siempre la misma; lo único que cambia es cómo se dibujan los datos.

4 Geometrías según la pregunta

La geometría no se elige por gusto estético: se elige según la pregunta que el gráfico debe responder. Esta tabla es el mapa de la sección (y una buena candidata para imprimir):

Pregunta Geometría en ggplot2
¿Cómo se distribuye una variable numérica? geom_histogram(), geom_density(), geom_boxplot()
¿Cómo comparo un valor entre categorías? geom_col()
¿Qué relación hay entre dos variables numéricas? geom_point() (+ geom_smooth())
¿Cómo evoluciona una variable en el tiempo? geom_line()

4.1 Distribución de una variable: histograma, densidad y boxplot

Pregunta típica: “¿el ingreso se concentra en valores bajos o altos? ¿hay valores extremos?” El histograma parte el rango de la variable en intervalos (bins) y cuenta cuántas observaciones caen en cada uno; la densidad es su versión suavizada — muestra la forma sin depender del número de bins:

Cómo interpretar: casi todas las personas quedan apiladas a la izquierda y hay una barra solitaria cerca de 25 millones: el outlier. El histograma “en niveles” queda aplastado — la advertencia de la sección anterior, ahora en imagen. Repita el ejercicio en RStudio con la base completa (ggplot(geih, aes(x = ingreso_laboral)) + geom_histogram(bins = 60)): se ve igual de mal, y esa es la lección. La retomaremos en Niveles o logaritmos.

El boxplot (diagrama de caja) resume la distribución en cinco números: la caja va del cuartil 1 al cuartil 3, la línea central es la mediana, los “bigotes” cubren el rango típico y los puntos sueltos marcan candidatos a atípicos. Su gran ventaja: permite comparar distribuciones entre grupos.

Cómo interpretar: el punto aislado en el grupo de los hombres es el outlier de 25 millones. Quitándolo, las cajas son comparables — y la mediana de las mujeres queda incluso un poco más arriba en esta mini base. Anote esa observación: será central en la sección de estadísticas descriptivas.

¿Histograma, densidad o boxplot?

Histograma: conteos por intervalo, la primera radiografía de UNA variable. Densidad: la forma suave, ideal con muchas observaciones o para superponer grupos. Boxplot: mediana, cuartiles y atípicos de un vistazo — imbatible para comparar una distribución entre grupos (por sexo, por nivel educativo, por departamento).

4.1.1 Mini-ejercicio: la distribución de la edad

Resultado esperado: la edad se reparte de forma mucho más pareja entre los 18 y los 65 años. No todas las variables tienen colas largas: el ingreso sí, la edad no — la decisión de escala se toma variable por variable.

4.2 Comparar categorías: geom_col() (y su diferencia con geom_bar())

Pregunta típica: “¿qué grupo gana más?” — y su prima: “¿cuántas personas hay en cada grupo?” Hay dos funciones de barras que confunden a todo el mundo: geom_bar() recibe solo x y cuenta filas; geom_col() recibe x e y y dibuja un valor que usted ya calculó. Compárelas:

¿Qué pasó? reorder(nivel_educativo, mediana) ordena las categorías de menor a mayor mediana — el orden alfabético que trae por defecto casi nunca es el orden útil. Y note el resultado: primaria < secundaria < media < superior. La educación paga: nuestro primer hecho estilizado, en un gráfico.

geom_bar() vs. geom_col()

Regla práctica: si el paso anterior fue un group_by() + summarise(), lo que sigue es geom_col(). Si quiere contar filas directamente desde la base, geom_bar().

En la base real hay categorías de sobra (24 departamentos); la receta estándar es ordenar, filtrar y voltear — con nombres largos, poner la categoría en el eje Y evita etiquetas ilegibles en diagonal:

## top 10 departamentos por ingreso mediano: barras horizontales y ordenadas
geih %>%
  group_by(departamento) %>%
  summarise(mediana = median(ingreso_laboral), .groups = "drop") %>%
  slice_max(mediana, n = 10) %>%
  ggplot(aes(x = mediana, y = reorder(departamento, mediana))) +
  geom_col(fill = "steelblue")

4.2.1 Mini-ejercicio: la brecha en barras

Calcule el ingreso promedio por sexo en geih_mini y grafíquelo con barras ordenadas con reorder().

Pregunta de análisis: según su gráfico, ¿quién gana más y por cuánto? Guarde la respuesta: en la sección de estadísticas descriptivas verá que la mediana cuenta otra historia — y entenderá por qué.

4.3 Relación entre dos variables: geom_point()

Pregunta típica: “¿más años de educación se asocian con más ingreso?” Cada punto del gráfico de dispersión es una observación (aquí, una persona). Y con una capa más, geom_smooth(method = "lm"), superponemos la recta de mínimos cuadrados que mejor resume la nube (se = FALSE oculta la banda de confianza):

Cómo interpretar: la nube sube hacia la derecha — a más educación, más ingreso —, pero el outlier estira el eje Y y “jala” la recta hacia arriba: los valores extremos no solo dañan gráficos, también sesgan rectas.

Anticipo de la Unidad 4

Esa recta ES una regresión: la versión visual de lm(ingreso_laboral ~ anios_educacion). En la Unidad 4 la estimaremos formalmente — con el ingreso en logaritmos, en la ecuación de Mincer — e interpretaremos su pendiente y su significancia. Si entiende geom_smooth(), ya tiene la intuición de lo que viene.

4.3.1 Mini-ejercicio: edad e ingreso

4.4 Evolución en el tiempo: geom_line()

Pregunta típica: “¿cómo ha cambiado el desempleo en la última década?” Nuestro extracto de la GEIH es un corte transversal (un solo mes), así que usamos desempleo, una mini serie que ya viene cargada en el documento: la tasa de desempleo anual de Colombia (cifras aproximadas, con fines ilustrativos):

Cómo interpretar: geom_line() conecta los puntos en el orden del eje X y geom_point() marca cada año; el pico de 2020 (pandemia) se lee de inmediato — eso es lo que una línea hace mejor que cualquier tabla. Nota importante: con un eje X numérico la línea se conecta sola; si X fuera texto ("T1", "T2", …) habría que agregar group = 1 dentro de aes().

4.4.1 Mini-ejercicio: título y contexto

Decisión de geometría. ¿Qué geometría usaría para cada pregunta?

  1. ¿Cuántos ocupados hay en cada nivel educativo? 2. ¿El ingreso se concentra en valores bajos o altos? 3. ¿Cómo comparo la distribución del ingreso entre hombres y mujeres? 4. ¿Más horas de trabajo se asocian con más ingreso? 5. ¿Cómo ha evolucionado el desempleo desde 2015?

Respuestas: 1. geom_bar(). 2. geom_histogram() o geom_density(). 3. geom_boxplot() con x = sexo. 4. geom_point() + geom_smooth(). 5. geom_line().

5 Estéticas: dentro y fuera de aes()

Las estéticas son los atributos visuales con los que un gráfico codifica información: color (puntos, líneas y contornos), fill (relleno de barras y cajas), size (tamaño), alpha (transparencia) y shape (forma del punto). La confusión más común de todo ggplot2 es cuándo va algo dentro de aes y cuándo fuera.

La regla fundamental

Dentro de aes() cuando el atributo depende de una variable de la base (“que el color cambie según el sexo de cada persona”): R asigna los colores y crea la leyenda. Fuera de aes() cuando es un valor fijo para todo el gráfico (“que todos los puntos sean azules”).

¿Qué pasó? En el caso 2, R asignó un color a cada categoría de sexo y creó la leyenda automáticamente: el gráfico ahora responde dos preguntas a la vez (la relación educación–ingreso y si el patrón difiere por sexo). En la base completa hay otro uso clave: con 21.821 puntos el gráfico de dispersión es una mancha, y la transparencia deja ver dónde se concentra la masa:

## alpha revela la densidad de la nube (RStudio, base completa)
ggplot(geih, aes(x = anios_educacion, y = log(ingreso_laboral))) +
  geom_point(alpha = 0.1)
Advertencia

El error clásico: aes(color = "blue")

Los puntos NO salen azules: salen rojizos, con una leyenda absurda que dice "blue". Al escribirlo dentro de aes(), R interpreta "blue" como una variable constante ficticia y le asigna el primer color de su paleta. Regla práctica: un valor literal — texto entre comillas o un número ("blue", 3, 0.5) — casi siempre va fuera de aes().

5.0.1 Mini-ejercicio: corrija el gráfico

6 Personalización para comunicar

Un gráfico sin título, sin unidades y sin fuente es como un informe sin conclusiones: técnicamente “completo”, pero mudo. Esta sección convierte gráficos correctos en gráficos presentables.

6.1 Etiquetas, temas y ejes legibles

labs() controla todos los textos del gráfico; los temas (theme_*()) controlan fondo, grillas y tipografía; y scale_y_continuous() reescribe las etiquetas del eje — porque nadie lee “2500000” de corrido:

Buenas prácticas de cada elemento: el título dice la idea principal (“La educación paga”, no “Gráfico de barras del ingreso”); el subtítulo da contexto (población, período, filtros); los ejes siempre llevan unidades; el caption lleva la fuente de datos — en este curso, un gráfico sin fuente está incompleto. Para las etiquetas del eje usamos aquí una función propia (function(x) paste0(x / 1e6, " M")); en RStudio, el paquete scales trae etiquetas listas — label_number(big.mark = ".", decimal.mark = ",") para separador de miles, o label_number(scale = 1e-6, suffix = " M"), que usaremos en la sección de exportar.

¿Qué tema usar?

theme_minimal(): limpio y moderno, nuestra opción por defecto para informes. theme_bw(): fondo blanco con borde, el estándar en papers académicos. theme_classic(): sin grillas, para publicaciones muy sobrias. theme_gray() (el default): evítelo en entregas — delata el gráfico “sin terminar”.

6.2 Facetas: el mismo gráfico por subgrupos

Una faceta repite el mismo gráfico para cada subgrupo, en paneles separados que comparten escala — comparar se vuelve inmediato:

facet_wrap(~ variable) parte el gráfico por una variable (con scales = "free_y" si los grupos difieren mucho en tamaño); facet_grid(filas ~ columnas) cruza dos. Sobre la base completa, un solo gráfico responde la pregunta del hilo conductor:

## distribucion del ingreso (en logs) por sexo y nivel educativo: 10 paneles
ggplot(geih, aes(x = log(ingreso_laboral))) +
  geom_histogram(bins = 40, fill = "steelblue", color = "white") +
  facet_grid(sexo ~ nivel_educativo) +
  theme_bw()

Cómo interpretar: cada columna es un nivel educativo, cada fila un sexo. ¿La distribución de los hombres está desplazada a la derecha de la de las mujeres dentro de cada nivel educativo? Eso es exactamente lo que este gráfico permite ver.

¿Color o facetas?

Color en el mismo panel: pocas categorías (2–4) y comparación directa — por ejemplo, dos densidades superpuestas con fill = sexo y alpha. Facetas: más categorías, o cuando quiere comparar distribuciones completas sin que las capas se tapen entre sí. Si el gráfico con colores parece un espagueti, la respuesta son facetas.

6.2.1 Mini-ejercicio integrador

Construya un boxplot del ingreso por sexo en geih_mini con: título que comunique el hallazgo, ejes con unidades, fuente en el caption y theme_minimal().

7 Exportar gráficos

En el panel Plots de RStudio hay un botón Export para guardar el gráfico con clic. No lo use para trabajo serio: el tamaño depende de cómo tenga la ventana ese día, la resolución es baja y no queda rastro en el script — no es reproducible. La herramienta correcta es ggsave(): asignamos el gráfico a un objeto y lo exportamos a output/figuras/ del proyecto (Unidad 1):

## figura central de la unidad: brecha por sexo dentro de cada nivel educativo
tab_brecha <- geih %>%
  group_by(nivel_educativo, sexo) %>%
  summarise(mediana = median(ingreso_laboral), .groups = "drop")

g_brecha <- ggplot(tab_brecha,
                   aes(x = reorder(nivel_educativo, mediana), y = mediana, fill = sexo)) +
  geom_col(position = "dodge") +
  scale_y_continuous(labels = label_number(scale = 1e-6, suffix = " M")) +
  labs(title    = "La brecha por sexo aparece en todos los niveles educativos",
       subtitle = "Ingreso laboral mediano de los ocupados, cabeceras",
       x = "Nivel educativo", y = "Ingreso mediano (millones de COP)",
       fill = NULL, caption = "Fuente: GEIH (DANE), extracto docente del curso.") +
  theme_minimal()

## exportar: reproducible y con dimensiones controladas
ggsave(filename = "output/figuras/fig_brecha_sexo_educacion.png",
       plot     = g_brecha,
       width    = 8, height = 5, dpi = 300)

¿Qué hace cada argumento? filename es una ruta relativa dentro del proyecto, y su extensión define el formato (.png para Word/HTML, .pdf para LaTeX). plot es el objeto a guardar: si se omite, ggsave() guarda el último gráfico mostrado — funciona, pero sea explícito. width y height van en pulgadas, y dpi = 300 es resolución de imprenta, suficiente para cualquier informe. De paso: position = "dodge" (en geom_col()) pone las barras de hombres y mujeres lado a lado en lugar de apiladas.

Regla del curso: todo gráfico de un informe nace de un script

Nada de capturas de pantalla ni del botón Export. En la estructura del curso, el script 02_descriptivas.R genera todas las figuras del proyecto y las exporta a output/figuras/ con nombres descriptivos (fig_brecha_sexo_educacion.png, no Rplot01.png). ¿La ganancia? Si mañana cambia la base — un mes más de GEIH, un filtro distinto —, corre el script y todas las figuras se actualizan solas. Es la reproducibilidad de la Unidad 1, aplicada a los resultados.

8 Estadísticas descriptivas

Los gráficos muestran la forma; las tablas dan los números exactos. La sección de “Datos” de un paper (o de su proyecto final) necesita ambos. Vamos de lo general a lo específico: resumen global → tablas de frecuencia → descriptivas por grupo → tabla lista para informe.

8.1 Resumen global: summary() y skim()

Cómo interpretar: para cada variable numérica, summary() reporta mínimo, cuartiles, mediana, media y máximo. Mire el ingreso: la media (≈ 3,2 millones) duplica con creces la mediana (≈ 1,5 millones). Cuando media y mediana se separan tanto, la distribución es asimétrica — y el promedio deja de ser “el valor típico”. En RStudio, skim() (paquete skimr) agrega faltantes, percentiles y mini-histogramas:

skim(geih)   ## resumen enriquecido de la base completa

8.2 Tablas de frecuencia: table() y tabyl()

En RStudio, tabyl() (paquete janitor) devuelve conteos y porcentajes en un solo paso, y permite tablas cruzadas con formato de informe:

## frecuencias con janitor
geih %>% tabyl(nivel_educativo)

## tabla cruzada: composicion educativa por sexo (porcentajes por columna)
geih %>%
  tabyl(nivel_educativo, sexo) %>%
  adorn_percentages("col") %>%
  adorn_pct_formatting(digits = 1)

8.3 Descriptivas por grupo: group_by() + summarise()

El corazón de la sección — y el reencuentro con la Unidad 2. La pregunta del hilo conductor, en números:

Cómo interpretar (mire con calma, esto es lo importante): en promedio, los hombres de la mini base ganan ≈ 4,5 millones y las mujeres 1,9 — la brecha parece enorme. En mediana, los hombres ganan ≈ 1,5 millones y las mujeres 1,6 — ¡la comparación se invierte! La diferencia es una sola persona: el outlier de 25 millones, que infla el promedio (y la desviación estándar) del grupo de los hombres.

¿Promedio o mediana? Reporte ambos y explique la diferencia

Con distribuciones asimétricas como el ingreso, la media es sensible a los valores extremos y la mediana no. Que la brecha cambie de tamaño (o de signo) según el estadístico ya es un hallazgo, no un problema técnico: dice que la parte alta de la distribución difiere entre grupos. Compruébelo en la base real: la dirección de la brecha por sexo se mantiene con ambos estadísticos, pero su magnitud cambia — por eso los papers de distribución del ingreso reportan medianas, percentiles o logaritmos, no solo promedios.

Sobre la base completa, el mismo patrón responde las tres preguntas del hilo conductor:

## brecha por sexo
geih %>%
  group_by(sexo) %>%
  summarise(promedio = mean(ingreso_laboral), mediana = median(ingreso_laboral),
            desv_est = sd(ingreso_laboral), n = n())

## retorno a la educacion
geih %>%
  group_by(nivel_educativo) %>%
  summarise(mediana = median(ingreso_laboral), n = n())

## la brecha por sexo DENTRO de cada nivel educativo
geih %>%
  group_by(nivel_educativo, sexo) %>%
  summarise(mediana = median(ingreso_laboral), n = n(), .groups = "drop")

Nota importante: la tercera tabla es exactamente la que graficamos con geom_col(position = "dodge") en la sección de exportar. Tabla y gráfico son dos vistas del mismo summarise().

8.3.1 Mini-ejercicio: descriptivas por nivel educativo

8.4 Tablas listas para informe: modelsummary

Copiar números de la consola a Word, uno por uno, es la fuente de errores más vieja del trabajo empírico. El paquete modelsummary produce tablas de descriptivas exportables directamente a output/tablas/:

## tablas de descriptivas para el informe
p_load(modelsummary)

## opcion 1: resumen automatico de toda la base (tipo skim, pero exportable)
datasummary_skim(geih, output = "output/tablas/tab_resumen_geih.html")

## opcion 2: tabla a la medida — la formula se lee "filas ~ columnas"
datasummary(ingreso_laboral + edad + anios_educacion + horas_semana ~ sexo * (Mean + Median + SD),
            data   = geih,
            output = "output/tablas/tab_descriptivas_sexo.docx")

Cómo leer la fórmula de datasummary(): a la izquierda del ~ van las variables (las filas); a la derecha, los estadísticos (las columnas: Mean, Median, SD, N, Min, Max…); sexo * (...) parte cada estadístico en una columna por sexo — la tabla de la brecha, lista para pegar en el informe. La extensión del output decide el formato: .docx para Word, .html, .tex para LaTeX.

Las tablas también nacen de scripts

La misma regla de las figuras aplica a las tablas: el script 02_descriptivas.R las genera y las exporta a output/tablas/. En la Unidad 4, modelsummary() (la función hermana de datasummary()) hará lo mismo con las tablas de regresión. Su informe final no debe tener un solo número transcrito a mano.

9 Niveles o logaritmos

Ya lo vimos tres veces: el histograma aplastado, el boxplot lleno de atípicos, el promedio que duplica la mediana. Los tres son síntomas de lo mismo: la distribución del ingreso es asimétrica a la derecha, con una cola larga de valores altos. La transformación estándar en economía es el logaritmo. Compare los dos histogramas:

¿Qué hizo el logaritmo? Comprimió la cola derecha: el outlier de 25 millones sigue ahí, pero ya no domina el eje — en logs, la distancia entre 2,5 y 25 millones es la misma que entre 250 mil y 2,5 millones. Además, las comparaciones se vuelven multiplicativas: pasar de 1 a 2 millones y de 10 a 20 son el mismo salto (una duplicación), que es como solemos pensar los cambios en variables económicas. Compruebe el contraste con los 21.821 ocupados — en logs, el ingreso toma la clásica forma de campana:

ggplot(geih, aes(x = ingreso_laboral)) + geom_histogram(bins = 60)
ggplot(geih, aes(x = log(ingreso_laboral))) + geom_histogram(bins = 60)

Nota importante: log(0) es -Inf y el logaritmo de un negativo es NaN (Unidad 1). Por eso el extracto del curso se restringe a ocupados con ingreso laboral positivo: el log siempre está definido. Con otras bases, ese chequeo es suyo.

Hacia la Unidad 4: los logs no son cosmética

En la próxima unidad estimaremos la ecuación de Mincer: log(ingreso_laboral) ~ anios_educacion + edad. Con el ingreso en logs, el coeficiente de educación se interpreta (aproximadamente) como el cambio porcentual del ingreso por un año adicional de educación — el “retorno a la educación”. La decisión de escala que tomamos hoy al graficar define cómo se leerán los coeficientes mañana.

10 Buenas prácticas de comunicación

Un buen gráfico responde UNA pregunta

Antes de escribir una línea de código, escriba la pregunta que el gráfico debe responder. Si necesita comunicar tres ideas, haga tres gráficos: uno por pregunta. El gráfico que intenta decirlo todo termina no diciendo nada.

Advertencia

Los cinco errores que más se repiten en informes

1. Eje Y truncado en gráficos de barras. En una barra, la altura ES el dato: si el eje arranca en 1.400.000 en vez de 0, una diferencia del 5% se ve como del 200%. ggplot2 inicia las barras en cero por defecto: no lo “arregle” recortando el eje. La excepción son las líneas de tendencia, donde acercar el eje al rango de los datos puede ser legítimo — siempre declarándolo.

2. Demasiadas categorías sin ordenar. 24 departamentos en orden alfabético = un gráfico que nadie lee. La receta de esta unidad: reorder() para ordenar por valor, top 10 con slice_max() (o agrupar el resto en “otros”) y barras horizontales para nombres largos.

3. Colores sin significado. El color debe codificar una variable (sexo, nivel educativo) o no estar. Cinco barras de cinco colores “porque se ve bonito” agregan ruido, no información; y evite el contraste rojo–verde como única distinción (es invisible para buena parte de los lectores daltónicos). En caso de duda: un solo color sobrio.

4. Gráfico sin título, unidades ni fuente. ¿Ingreso en COP corrientes o en millones? ¿Todo el país o solo cabeceras? ¿Datos de quién? Un gráfico que no responde esas preguntas por sí solo obliga al lector a adivinar. labs() completo — título con la idea, ejes con unidades, caption con la fuente — siempre.

5. La torta (pie) casi nunca es la respuesta. El ojo humano es malo comparando ángulos y áreas: con más de tres categorías, o con valores parecidos, la torta es ilegible. Unas barras ordenadas comunican lo mismo, mejor, en todos los casos. Dato revelador: ggplot2 ni siquiera trae un geom_pie().

Antes de compartir cualquier gráfico, pregúntese: ¿el título dice el hallazgo? ¿los ejes tienen unidades? ¿está la fuente? ¿cada color significa algo? ¿alguien sin contexto entiende la idea en 30 segundos? Si alguna respuesta es “no”, el gráfico no está listo.

11 Checklist de salida

Antes de pasar a la Unidad 4, verifique que puede:

Si algún punto le genera dudas, vuelva a la sección correspondiente y ejecute los mini-ejercicios de nuevo.

12 Preguntas de comprensión

  1. Quiere comparar la distribución del ingreso laboral entre los cinco niveles educativos en un solo gráfico. ¿Usaría histogramas o boxplots? ¿Por qué?
  2. En la GEIH, la brecha de ingresos por sexo medida con el promedio resulta mayor que la medida con la mediana. ¿Qué propiedad de la distribución del ingreso explica esa diferencia, y cuál de los dos estadísticos reportaría en un informe?
  3. Tiene el ingreso mediano por nivel educativo, separado por sexo. ¿Cuándo preferiría facetas (facet_wrap()) y cuándo color (fill = sexo) dentro del mismo panel?
  4. ¿Qué produce cada uno de estos tres códigos, y cuál es casi siempre un error?: aes(color = sexo), geom_point(color = "blue"), aes(color = "blue").
  5. Para graficar cuántos ocupados hay por departamento directamente desde la base, ¿usaría geom_bar() o geom_col()? ¿Y para graficar una tabla que ya construyó con group_by() + summarise()?

13 Recursos de aprendizaje de la unidad