1 Introducción

En las clases anteriores construimos un flujo reproducible: creamos objetos, reconocimos tipos de variables, trabajamos con una base de hogares y utilizamos verbos de dplyr para seleccionar, filtrar, transformar, agrupar y resumir.

Ahora añadiremos una etapa esencial:

\[ \text{pregunta} \rightarrow \text{datos} \rightarrow \text{transformación} \rightarrow \text{visualización} \rightarrow \text{interpretación} \]

La visualización no es un paso decorativo. Un buen gráfico ayuda a descubrir patrones, evaluar la calidad de los datos y comunicar un argumento. Un mal gráfico puede ocultar información o inducir conclusiones equivocadas.

Al finalizar esta clase deberías poder:

  • reconocer qué gráfico conviene según la pregunta y los tipos de variables;
  • comprender la gramática de gráficos de ggplot2;
  • construir gráficos por capas con datos económicos y sociales;
  • transformar una base antes de visualizarla;
  • personalizar títulos, etiquetas, escalas, colores, leyendas, facetas y temas;
  • representar distribuciones, comparaciones, relaciones y evolución temporal;
  • convertir un gráfico exploratorio en una pieza breve de storytelling;
  • evitar errores frecuentes de interpretación y diseño.

Nota: todos los datos de esta clase son simulados. No corresponden a estadísticas reales de los municipios mencionados y no deben emplearse para caracterizarlos.

1.1 Ruta de aprendizaje

  1. Visualizar como parte del análisis de datos.
  2. Elegir el gráfico a partir de la pregunta y las variables.
  3. Construir una base simulada y prepararla con dplyr.
  4. Comprender la gramática y las capas de ggplot2.
  5. Representar una variable categórica o numérica.
  6. Comparar grupos y estudiar relaciones entre variables.
  7. Visualizar indicadores a lo largo del tiempo.
  8. Usar facetas, escalas, colores, etiquetas y temas.
  9. Pasar de exploración a storytelling.

1.2 Pregunta diagnóstica

Pregunta. Si quisieras estudiar ingresos de los hogares, ¿usarías el mismo gráfico para responder estas cuatro preguntas?
  1. ¿Cómo se distribuye el ingreso mensual?
  2. ¿Cuántos hogares hay en cada zona?
  3. ¿Cómo se relacionan ingreso y gasto?
  4. ¿Cómo cambia un indicador mes a mes?
Ver respuesta orientadora

No. Cada pregunta describe una estructura distinta:

  • una distribución numérica puede verse con histograma o densidad;
  • el conteo de categorías puede verse con barras;
  • la relación entre dos variables numéricas puede verse con dispersión;
  • la evolución temporal suele verse con líneas.

La elección comienza con la pregunta, no con el menú de gráficos disponible.

2 ¿Por qué visualizamos datos?

Una visualización puede cumplir distintos propósitos:

Propósito Pregunta Ejemplo
Explorar ¿Qué patrones o problemas aparecen? Detectar asimetría, valores extremos o faltantes.
Comparar ¿Qué grupos presentan valores diferentes? Comparar ingreso mediano entre municipios.
Relacionar ¿Cómo varían conjuntamente dos variables? Examinar ingreso y gasto mensual.
Mostrar cambio ¿Cómo evoluciona una variable? Observar un indicador a lo largo de los meses.
Comunicar ¿Cuál es el mensaje principal? Destacar el municipio con mayor mediana en la base simulada.
Idea clave. En la fase exploratoria podemos producir muchos gráficos para aprender sobre los datos. En la fase de comunicación seleccionamos y refinamos solamente aquellos que sostienen el mensaje relevante.

2.1 Antes de graficar: cuatro decisiones

  1. Pregunta: ¿qué quiero que el gráfico permita ver?
  2. Variables: ¿son numéricas, categóricas o temporales?
  3. Unidad de análisis: ¿cada fila representa un hogar, un municipio o un mes?
  4. Propósito: ¿estoy explorando, comparando o comunicando?
Error frecuente. Elegir un gráfico porque “se ve bonito” sin comprobar si representa correctamente la pregunta, la unidad de observación y el tipo de variable.

2.2 Guía rápida para elegir un gráfico

También podemos clasificar las visualizaciones por el número y la función de las variables:

  • univariadas: estudian una variable, como la distribución del ingreso o el conteo por zona;
  • bivariadas: relacionan dos variables, como ingreso y gasto, o ingreso según nivel educativo;
  • multivariadas: incorporan tres o más variables mediante color, tamaño, facetas u otros recursos;
  • temporales: reservan uno de los ejes para una fecha o período ordenado;
  • espaciales: vinculan los datos con coordenadas o polígonos geográficos.

Agregar variables no siempre mejora un gráfico. Cada elemento visual debe responder a una pregunta concreta.

Estructura de la pregunta Gráfico principal Alternativas o complementos Uso típico
Una variable categórica Barras Barras ordenadas, proporciones Frecuencias y composición.
Una variable numérica Histograma Densidad, caja y bigotes Forma, centro, dispersión y extremos.
Numérica según categoría Caja y bigotes Violín, puntos, barras de resumen Comparar distribuciones entre grupos.
Dos variables numéricas Dispersión Línea de tendencia, mapa de calor Relación o correlación.
Dos variables categóricas Barras agrupadas o apiladas Barras al 100 % Comparar frecuencias o composición.
Variable a través del tiempo Líneas Área, columnas Tendencia, ciclos y cambios.
Varias categorías o paneles Facetas Color o tipo de línea Comparaciones repetidas.
Varias correlaciones Mapa de calor Matriz de dispersión Resumen de relaciones entre variables.
Datos geográficos Mapa coroplético Puntos o símbolos proporcionales Patrones espaciales; requiere geometrías.

3 Preparación del entorno

3.1 Instalar no es cargar

ggplot2 forma parte de tidyverse. La instalación normalmente se hace una sola vez y no debe ejecutarse automáticamente al renderizar este documento.

install.packages("tidyverse")

En cada nueva sesión de R cargamos el paquete:

library(tidyverse)
library(readxl)

Al cargar tidyverse tendremos disponibles ggplot2, dplyr, tidyr, forcats, readr y otras herramientas relacionadas.

3.2 Paleta y tema de la clase

Guardaremos colores en objetos para reutilizarlos. Así evitamos escribir códigos diferentes en cada gráfico.

color_principal <- "#8F1D2C"
color_secundario <- "#355C7D"
color_acento <- "#D39B2A"
color_gris <- "#B9B9B9"

tema_clase <- theme_minimal(base_size = 14) +
  theme(
    plot.title = element_text(face = "bold", color = color_principal),
    plot.subtitle = element_text(color = "#444444"),
    plot.caption = element_text(color = "#666666", hjust = 0),
    panel.grid.minor = element_blank(),
    legend.position = "bottom"
  )

tema_clase es un objeto de ggplot2. Más adelante podremos agregarlo a distintos gráficos mediante + tema_clase.

3.3 Carga de la base de datos

Nunca debemos entregar automáticamente una base a ggplot2. Primero comprobamos estructura, variables y faltantes.

hogares <- read_excel("Hogares.xlsx")


glimpse(hogares)
## Rows: 240
## Columns: 8
## $ id_hogar        <dbl> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,…
## $ municipio       <chr> "Cali", "Yumbo", "Palmira", "Cali", "Cali", "Cali", "C…
## $ zona            <chr> "Urbana", "Urbana", "Urbana", "Urbana", "Urbana", "Rur…
## $ numero_personas <dbl> 3, 5, 5, 4, 2, 6, 2, 3, 4, 2, 3, 4, 4, 5, 5, 5, 5, 2, …
## $ nivel_educativo <chr> "Secundaria", "Universitaria", "Técnica o tecnológica"…
## $ recibe_subsidio <chr> "Sí", "No", "No", "No", "No", "No", "Sí", "Sí", "Sí", …
## $ ingreso_mensual <dbl> 3926000, 2222000, 2754000, 1881000, 4977000, 4937000, …
## $ gasto_mensual   <dbl> 3073000, 1774000, 2132000, 1211000, 4907000, 4663000, …
hogares %>% 
  summarise(
    numero_filas = n(),
    faltantes_ingreso = sum(is.na(ingreso_mensual)),
    faltantes_gasto = sum(is.na(gasto_mensual)),
    faltantes_subsidio = sum(is.na(recibe_subsidio))
  )
## # A tibble: 1 × 4
##   numero_filas faltantes_ingreso faltantes_gasto faltantes_subsidio
##          <int>             <int>           <int>              <int>
## 1          240                 6               5                 10

3.4 Transformar antes de visualizar

Crearemos ahorro mensual, ingreso per cápita y una versión explícita de los faltantes de subsidio.

hogares_analisis <- hogares %>%
  mutate(
    ahorro_mensual = ingreso_mensual - gasto_mensual,
    ingreso_per_capita = ingreso_mensual / numero_personas,
    zona = factor(zona, levels = c("Urbana", "Rural")),
    nivel_educativo = factor(
      nivel_educativo,
      levels = c(
        "Primaria", "Secundaria",
        "Técnica o tecnológica", "Universitaria"
      ),
      ordered = TRUE
    ),
    subsidio_grafico = if_else(
      is.na(recibe_subsidio),
      "Sin información",
      as.character(recibe_subsidio)
    )
  )

hogares_analisis %>%
  select(
    id_hogar, municipio, ingreso_mensual, gasto_mensual,
    ahorro_mensual, ingreso_per_capita
  ) %>%
  slice_head(n = 6)
## # A tibble: 6 × 6
##   id_hogar municipio ingreso_mensual gasto_mensual ahorro_mensual
##      <dbl> <chr>               <dbl>         <dbl>          <dbl>
## 1        1 Cali              3926000       3073000         853000
## 2        2 Yumbo             2222000       1774000         448000
## 3        3 Palmira           2754000       2132000         622000
## 4        4 Cali              1881000       1211000         670000
## 5        5 Cali              4977000       4907000          70000
## 6        6 Cali              4937000       4663000         274000
## # ℹ 1 more variable: ingreso_per_capita <dbl>
Conexión con la clase anterior. ggplot2 no reemplaza a dplyr. Con frecuencia transformamos y resumimos con dplyr, y después enviamos el resultado a ggplot2.

4 ¿Qué es ggplot2?

ggplot2 es un paquete para construir gráficos mediante una gramática de gráficos. La idea central es que una visualización puede descomponerse en elementos coherentes:

  • datos: la tabla que contiene las variables;
  • mapeos estéticos: qué variable se representa en x, y, color, fill, size, etc.;
  • geometrías: puntos, barras, líneas, cajas, áreas;
  • transformaciones estadísticas: conteos, suavizados o resúmenes;
  • escalas: cómo se traducen valores a posiciones, colores y etiquetas;
  • coordenadas: el sistema y el rango visible;
  • facetas: paneles definidos por categorías;
  • tema: elementos visuales que no representan datos.

4.1 La estructura mínima

ggplot(
  data = hogares_analisis,
  mapping = aes(x = ingreso_mensual)
)

El resultado todavía está vacío porque definimos los datos y el eje x, pero no indicamos cómo dibujar las observaciones.

ggplot(
  data = hogares_analisis,
  mapping = aes(x = ingreso_mensual)
) +
  geom_histogram()

El signo + agrega capas. En ggplot2, debe quedar al final de la línea que continúa.

4.2 Plantilla general

ggplot(datos, aes(x = variable_x, y = variable_y)) +
  geom_tipo() +
  labs(
    title = "Título",
    subtitle = "Subtítulo",
    x = "Etiqueta horizontal",
    y = "Etiqueta vertical",
    caption = "Fuente o nota"
  ) +
  escala_necesaria() +
  tema_elegido()

No todos los gráficos necesitan y; por ejemplo, un histograma calcula los conteos internamente.

4.3 Mapear una variable o fijar una propiedad

Dentro de aes() vinculamos una propiedad con una variable. Fuera de aes() fijamos una propiedad para todas las observaciones.

# Color fijo: todos los puntos tienen el mismo color
ggplot(
  hogares_analisis,
  aes(x = ingreso_mensual, y = gasto_mensual)
) +
  geom_point(color = color_principal, alpha = 0.65) +
  tema_clase

# Color mapeado: el color representa la zona
ggplot(
  hogares_analisis,
  aes(x = ingreso_mensual, y = gasto_mensual, color = zona)
) +
  geom_point(alpha = 0.65) +
  tema_clase

Error frecuente. Escribir aes(color = “rojo”) cuando se desea un color fijo. Eso crea una categoría llamada “rojo”. Para fijar el color se usa geom_point(color = “red”).

4.4 Herencia de estéticas

Los mapeos definidos en ggplot(aes(...)) son heredados por las capas posteriores. Una capa también puede tener sus propios datos o mapeos.

ggplot(
  hogares_analisis,
  aes(x = ingreso_mensual, y = gasto_mensual)
) +
  geom_point(color = color_secundario, alpha = 0.55) +
  geom_smooth(method = "lm", se = FALSE, color = color_principal) +
  tema_clase

La recta resume una tendencia lineal descriptiva. No demuestra que el ingreso cause el gasto ni sustituye un análisis econométrico.

5 Una variable categórica: gráfico de barras

Un gráfico de barras representa frecuencias o cantidades por categoría. Con una sola variable en x, geom_bar() cuenta automáticamente las filas.

ggplot(hogares_analisis, aes(x = zona)) +
  geom_bar(fill = color_principal, width = 0.7) +
  labs(
    title = "Hogares simulados por zona",
    x = NULL,
    y = "Número de hogares",
    caption = "Fuente: datos simulados con fines pedagógicos."
  ) +
  tema_clase

5.1 geom_bar() frente a geom_col()

  • geom_bar() recibe observaciones y las cuenta.
  • geom_col() recibe una tabla que ya contiene una cantidad en y.
conteo_municipio <- hogares_analisis %>%
  count(municipio, name = "numero_hogares")

ggplot(
  conteo_municipio,
  aes(x = reorder(municipio, numero_hogares), y = numero_hogares)
) +
  geom_col(fill = color_secundario) +
  coord_flip() +
  labs(
    title = "Número de hogares por municipio",
    x = NULL,
    y = "Número de hogares",
    caption = "Fuente: datos simulados con fines pedagógicos."
  ) +
  tema_clase

reorder() ordena las categorías por la variable numérica y coord_flip() intercambia los ejes. Esto facilita leer etiquetas largas.

5.2 Dos variables categóricas: barras apiladas y proporciones

ggplot(
  hogares_analisis,
  aes(x = zona, fill = subsidio_grafico)
) +
  geom_bar(position = "fill") +
  scale_y_continuous(labels = scales::label_percent(decimal.mark = ",")) +
  scale_fill_manual(
    values = c(
      "No" = color_gris,
      "Sí" = color_principal,
      "Sin información" = color_acento
    )
  ) +
  labs(
    title = "Composición de la respuesta sobre subsidios por zona",
    subtitle = "Cada barra representa el 100 % de los hogares de la zona",
    x = NULL,
    y = "Proporción",
    fill = "Respuesta",
    caption = "Fuente: datos simulados con fines pedagógicos."
  ) +
  tema_clase

position = "fill" transforma cada barra en proporciones. Si usáramos position = "stack", veríamos conteos apilados.

Decisión gráfica. Usa barras agrupadas cuando quieras comparar cantidades de subgrupos. Usa barras al 100 % cuando el interés principal sea comparar composición. No confundas proporciones con tamaños absolutos.

5.3 ¿Y el gráfico circular?

Un gráfico circular puede representar composición, pero suele dificultar la comparación precisa entre segmentos. Las barras ordenadas normalmente permiten comparar mejor longitudes. Si el objetivo exige un circular, puede construirse con coord_polar(), pero no debería ser la opción automática.

hogares_analisis %>%
  count(subsidio_grafico) %>%
  ggplot(aes(x = "", y = n, fill = subsidio_grafico)) +
  geom_col(width = 1) +
  coord_polar(theta = "y") +
  theme_void()

5.3.1 Inténtalo tú: categorías

Enunciado. Construye un gráfico de barras que cuente hogares por nivel_educativo. Ordénalo de mayor a menor frecuencia y usa barras horizontales.

Pista. Primero crea una tabla con count(). Después usa geom_col(), reorder() y coord_flip().

# Escribe aquí tu solución.
Ver solución
conteo_educacion <- hogares_analisis %>%
  count(nivel_educativo, name = "numero_hogares")

ggplot(
  conteo_educacion,
  aes(
    x = reorder(nivel_educativo, numero_hogares),
    y = numero_hogares
  )
) +
  geom_col(fill = color_principal, width = 0.7) +
  coord_flip() +
  labs(
    title = "Hogares simulados según nivel educativo",
    x = NULL,
    y = "Número de hogares",
    caption = "Fuente: datos simulados con fines pedagógicos."
  ) +
  tema_clase

Explicación. count() produce una fila por categoría y geom_col() utiliza el conteo ya calculado. reorder() organiza la lectura y coord_flip() deja las categorías en el eje vertical.

Interpretación. El gráfico describe la composición de esta base simulada. No representa la distribución educativa real de los municipios.

6 Una variable numérica: distribución

6.1 Histograma

El histograma divide el rango de una variable numérica en intervalos y cuenta cuántas observaciones caen en cada uno.

ggplot(hogares_analisis, aes(x = ingreso_mensual)) +
  geom_histogram(
    bins = 18,
    fill = color_principal,
    color = "white"
  ) +
  scale_x_continuous(
    labels = scales::label_number(
      scale = 0.000001,
      suffix = " M",
      decimal.mark = ","
    )
  ) +
  labs(
    title = "Distribución del ingreso mensual",
    subtitle = "El ingreso presenta asimetría hacia valores altos",
    x = "Ingreso mensual (millones de unidades monetarias)",
    y = "Número de hogares",
    caption = "Fuente: datos simulados con fines pedagógicos."
  ) +
  tema_clase

6.1.1 ¿Qué hace bins?

bins indica cuántos intervalos se utilizarán. Pocos intervalos pueden ocultar estructura; demasiados pueden exagerar ruido.

ggplot(hogares_analisis, aes(x = ingreso_mensual)) +
  geom_histogram(
    bins = 8,
    fill = color_secundario,
    color = "white"
  ) +
  labs(
    title = "La forma percibida depende del número de intervalos",
    x = "Ingreso mensual",
    y = "Número de hogares"
  ) +
  tema_clase

No existe un número universal de intervalos. Conviene probar alternativas y explicar la decisión cuando sea relevante.

6.2 Densidad

La curva de densidad suaviza la distribución. Su eje vertical no representa conteos directos y el área total bajo la curva es uno.

ggplot(
  hogares_analisis,
  aes(x = ingreso_mensual, fill = zona, color = zona)
) +
  geom_density(alpha = 0.25, linewidth = 0.9, na.rm = TRUE) +
  scale_fill_manual(
    values = c("Urbana" = color_principal, "Rural" = color_secundario)
  ) +
  scale_color_manual(
    values = c("Urbana" = color_principal, "Rural" = color_secundario)
  ) +
  scale_x_continuous(
    labels = scales::label_number(
      scale = 0.000001,
      suffix = " M",
      decimal.mark = ","
    )
  ) +
  labs(
    title = "Distribución del ingreso según zona",
    x = "Ingreso mensual (millones)",
    y = "Densidad",
    fill = "Zona",
    color = "Zona",
    caption = "Fuente: datos simulados con fines pedagógicos."
  ) +
  tema_clase

La densidad resulta útil para comparar formas, pero puede ser menos intuitiva para una audiencia no técnica.

6.3 Caja y bigotes

Un boxplot resume mediana, cuartiles, dispersión y observaciones potencialmente atípicas.

ggplot(
  hogares_analisis,
  aes(x = "Ingreso", y = ingreso_mensual)
) +
  geom_boxplot(
    fill = color_secundario,
    alpha = 0.75,
    width = 0.45,
    na.rm = TRUE
  ) +
  scale_y_continuous(
    labels = scales::label_number(
      scale = 0.000001,
      suffix = " M",
      decimal.mark = ","
    )
  ) +
  labs(
    title = "Resumen de la distribución del ingreso",
    x = NULL,
    y = "Ingreso mensual (millones)",
    caption = "Fuente: datos simulados con fines pedagógicos."
  ) +
  tema_clase

Los puntos más allá de los bigotes no son automáticamente errores. Son observaciones que merecen revisión sustantiva y técnica.

6.4 Histograma, densidad o boxplot

Gráfico Fortalezas Limitaciones
Histograma Muestra forma y conteos aproximados. Depende de los intervalos.
Densidad Facilita comparar formas suavizadas. El eje de densidad puede ser menos intuitivo.
Boxplot Resume y compara grupos de forma compacta. Oculta detalles de la forma de la distribución.

6.4.1 Inténtalo tú: distribución

Enunciado. Construye un histograma de ingreso_per_capita con 20 intervalos. Agrega una línea vertical en la mediana, etiquetas claras y una nota sobre el origen simulado de los datos.

Pista. Calcula primero la mediana con median(..., na.rm = TRUE) y utiliza geom_vline().

# Escribe aquí tu solución.
Ver solución
mediana_ingreso_pc <- median(
  hogares_analisis$ingreso_per_capita,
  na.rm = TRUE
)

ggplot(hogares_analisis, aes(x = ingreso_per_capita)) +
  geom_histogram(
    bins = 20,
    fill = color_secundario,
    color = "white"
  ) +
  geom_vline(
    xintercept = mediana_ingreso_pc,
    color = color_principal,
    linewidth = 1,
    linetype = "dashed"
  ) +
  scale_x_continuous(
    labels = scales::label_number(
      scale = 0.000001,
      suffix = " M",
      decimal.mark = ","
    )
  ) +
  labs(
    title = "Distribución del ingreso per cápita",
    subtitle = "La línea punteada indica la mediana",
    x = "Ingreso per cápita (millones)",
    y = "Número de hogares",
    caption = "Fuente: datos simulados con fines pedagógicos."
  ) +
  tema_clase

Explicación. El histograma muestra la forma de la distribución y geom_vline() agrega una referencia calculada a partir de los datos.

Interpretación. La mediana divide las observaciones válidas en dos grupos de igual tamaño. No debe interpretarse como un umbral normativo de bienestar.

7 Una variable numérica según grupos

7.1 Boxplot por categoría

ggplot(
  hogares_analisis,
  aes(x = nivel_educativo, y = ingreso_mensual)
) +
  geom_boxplot(fill = color_acento, alpha = 0.70, na.rm = TRUE) +
  scale_y_continuous(
    labels = scales::label_number(
      scale = 0.000001,
      suffix = " M",
      decimal.mark = ","
    )
  ) +
  labs(
    title = "Distribución del ingreso por nivel educativo",
    subtitle = "Comparación descriptiva en una base simulada",
    x = NULL,
    y = "Ingreso mensual (millones)",
    caption = "Fuente: datos simulados con fines pedagógicos."
  ) +
  tema_clase +
  theme(axis.text.x = element_text(angle = 20, hjust = 1))

Este gráfico permite comparar medianas, dispersión y posibles valores atípicos entre grupos.

Precaución. Ver diferencias entre cajas no permite concluir que el nivel educativo cause el ingreso observado. Además, esta base fue simulada sin propósito inferencial.

7.2 Gráfico de violín

El violín combina una forma de densidad con comparación por categorías. Su ancho representa dónde se concentran más observaciones.

ggplot(
  hogares_analisis,
  aes(x = zona, y = ingreso_mensual, fill = zona)
) +
  geom_violin(alpha = 0.55, trim = FALSE, na.rm = TRUE) +
  geom_boxplot(width = 0.16, fill = "white", na.rm = TRUE) +
  scale_fill_manual(
    values = c("Urbana" = color_principal, "Rural" = color_secundario)
  ) +
  scale_y_continuous(
    labels = scales::label_number(
      scale = 0.000001,
      suffix = " M",
      decimal.mark = ","
    )
  ) +
  labs(
    title = "Forma y resumen del ingreso por zona",
    x = NULL,
    y = "Ingreso mensual (millones)",
    fill = "Zona",
    caption = "Fuente: datos simulados con fines pedagógicos."
  ) +
  tema_clase +
  guides(fill = "none")

El violín requiere suficientes observaciones por grupo. Con grupos pequeños, su suavizado puede sugerir una estructura que los datos no sostienen.

7.3 Resumir y luego graficar

Cuando queremos comparar una estadística, primero construimos una tabla agregada.

resumen_municipio <- hogares_analisis %>%
  group_by(municipio) %>%
  summarise(
    numero_hogares = n(),
    ingreso_mediano = median(ingreso_mensual, na.rm = TRUE),
    ingreso_promedio = mean(ingreso_mensual, na.rm = TRUE),
    .groups = "drop"
  ) %>%
  arrange(ingreso_mediano)

resumen_municipio
## # A tibble: 5 × 4
##   municipio    numero_hogares ingreso_mediano ingreso_promedio
##   <chr>                 <int>           <dbl>            <dbl>
## 1 Yumbo                    19         2293500         2583444.
## 2 Buenaventura             49         2577000         2875667.
## 3 Cali                    101         2767500         3013650 
## 4 Jamundí                  24         2925000         3177292.
## 5 Palmira                  47         2964000         3089191.
ggplot(
  resumen_municipio,
  aes(x = reorder(municipio, ingreso_mediano), y = ingreso_mediano)
) +
  geom_col(fill = color_secundario, width = 0.68) +
  coord_flip() +
  scale_y_continuous(
    labels = scales::label_number(
      scale = 0.000001,
      suffix = " M",
      decimal.mark = ","
    )
  ) +
  labs(
    title = "Ingreso mediano por municipio",
    subtitle = "La tabla se resume antes de construir el gráfico",
    x = NULL,
    y = "Ingreso mediano (millones)",
    caption = "Fuente: datos simulados con fines pedagógicos."
  ) +
  tema_clase

Unidad de análisis. En hogares_analisis, cada fila es un hogar. En resumen_municipio, cada fila es un municipio. El gráfico debe interpretarse de acuerdo con la tabla que recibe.

8 Dos variables numéricas: relación y correlación

8.1 Gráfico de dispersión

Cada punto representa una observación. El eje horizontal muestra una variable y el vertical otra.

ggplot(
  hogares_analisis,
  aes(x = ingreso_mensual, y = gasto_mensual)
) +
  geom_point(
    color = color_secundario,
    alpha = 0.60,
    size = 2
  ) +
  scale_x_continuous(
    labels = scales::label_number(
      scale = 0.000001, suffix = " M", decimal.mark = ","
    )
  ) +
  scale_y_continuous(
    labels = scales::label_number(
      scale = 0.000001, suffix = " M", decimal.mark = ","
    )
  ) +
  labs(
    title = "Relación entre ingreso y gasto mensual",
    subtitle = "Cada punto representa un hogar simulado",
    x = "Ingreso mensual (millones)",
    y = "Gasto mensual (millones)",
    caption = "Fuente: datos simulados con fines pedagógicos."
  ) +
  tema_clase

Al leer un gráfico de dispersión observamos:

  • dirección de la relación;
  • intensidad visual;
  • forma lineal o no lineal;
  • grupos;
  • valores extremos;
  • cambios en la dispersión.

8.2 Color, transparencia y tendencia

ggplot(
  hogares_analisis,
  aes(
    x = ingreso_mensual,
    y = gasto_mensual,
    color = zona
  )
) +
  geom_point(alpha = 0.60, size = 2) +
  geom_smooth(
    method = "lm",
    se = FALSE,
    linewidth = 0.9
  ) +
  scale_color_manual(
    values = c("Urbana" = color_principal, "Rural" = color_secundario)
  ) +
  scale_x_continuous(
    labels = scales::label_number(
      scale = 0.000001, suffix = " M", decimal.mark = ","
    )
  ) +
  scale_y_continuous(
    labels = scales::label_number(
      scale = 0.000001, suffix = " M", decimal.mark = ","
    )
  ) +
  labs(
    title = "Ingreso y gasto mensual según zona",
    subtitle = "Las líneas resumen tendencias lineales dentro de cada grupo",
    x = "Ingreso mensual (millones)",
    y = "Gasto mensual (millones)",
    color = "Zona",
    caption = "Fuente: datos simulados. Asociación descriptiva, no causal."
  ) +
  tema_clase

alpha introduce transparencia y ayuda cuando los puntos se superponen. geom_smooth(method = "lm") agrega una recta estimada para describir el patrón lineal.

8.3 Coeficiente de correlación

cor_ingreso_gasto <- cor(
  hogares_analisis$ingreso_mensual,
  hogares_analisis$gasto_mensual,
  use = "complete.obs"
)

round(cor_ingreso_gasto, 2)
## [1] 0.95

La correlación lineal toma valores entre -1 y 1. Resume dirección e intensidad lineal, pero:

  • no prueba causalidad;
  • puede verse afectada por valores extremos;
  • puede ocultar relaciones no lineales;
  • no reemplaza la inspección del gráfico.

8.3.1 Inténtalo tú: relación entre variables

Enunciado. Construye un gráfico de dispersión entre numero_personas e ingreso_per_capita. Usa color para distinguir si el hogar recibe subsidio, trata explícitamente la categoría sin información y añade títulos.

Pista. Usa subsidio_grafico en color. Como numero_personas toma pocos valores enteros, agrega position_jitter(width = 0.10) para reducir superposición.

# Escribe aquí tu solución.
Ver solución
ggplot(
  hogares_analisis,
  aes(
    x = numero_personas,
    y = ingreso_per_capita,
    color = subsidio_grafico
  )
) +
  geom_point(
    alpha = 0.65,
    size = 2,
    position = position_jitter(width = 0.10, height = 0)
  ) +
  scale_color_manual(
    values = c(
      "No" = color_gris,
      "Sí" = color_principal,
      "Sin información" = color_acento
    )
  ) +
  scale_x_continuous(breaks = 1:6) +
  scale_y_continuous(
    labels = scales::label_number(
      scale = 0.000001, suffix = " M", decimal.mark = ","
    )
  ) +
  labs(
    title = "Tamaño del hogar e ingreso per cápita",
    x = "Número de personas",
    y = "Ingreso per cápita (millones)",
    color = "Recibe subsidio",
    caption = "Fuente: datos simulados con fines pedagógicos."
  ) +
  tema_clase

Explicación. position_jitter() desplaza ligeramente puntos que compartirían la misma posición horizontal. El desplazamiento facilita ver la cantidad de observaciones, pero no cambia el valor sustantivo de numero_personas.

Interpretación. El gráfico permite explorar patrones y heterogeneidad. No identifica el efecto del tamaño del hogar ni del subsidio sobre el ingreso per cápita.

9 Datos a través del tiempo

Una serie temporal organiza observaciones según una fecha. El orden es sustantivo: los meses no son categorías intercambiables.

Cargamos la base.

indicadores_mensuales <- read_excel("indicadores_mensuales.xlsx")

9.1 Gráfico de líneas

ggplot(
  indicadores_mensuales,
  aes(x = fecha, y = desempleo)
) +
  geom_line(color = color_principal, linewidth = 1) +
  geom_point(color = color_principal, size = 1.3) +
  scale_x_date(
    date_breaks = "6 months",
    date_labels = "%b\n%Y"
  ) +
  scale_y_continuous(
    labels = scales::label_number(
      decimal.mark = ",",
      suffix = " %"
    )
  ) +
  labs(
    title = "Evolución mensual de un indicador de desempleo",
    subtitle = "Serie simulada: tendencia y variación estacional",
    x = NULL,
    y = "Tasa simulada",
    caption = "Fuente: datos simulados con fines pedagógicos."
  ) +
  tema_clase

Una línea conecta observaciones ordenadas y resalta continuidad. Para años o categorías discretas, las columnas pueden ser apropiadas; para muchos períodos consecutivos, la línea suele ser más clara.

9.2 Varias series: formato largo y facetas

pivot_longer() convierte varias columnas de indicadores en una estructura con una columna para el nombre del indicador y otra para su valor.

indicadores_largos <- indicadores_mensuales %>%
  pivot_longer(
    cols = c(desempleo, inflacion_anual),
    names_to = "indicador",
    values_to = "valor"
  ) %>%
  mutate(
    indicador = recode(
      indicador,
      desempleo = "Desempleo",
      inflacion_anual = "Inflación anual"
    )
  )

indicadores_largos %>%
  slice_head(n = 8)
## # A tibble: 8 × 3
##   fecha               indicador       valor
##   <dttm>              <chr>           <dbl>
## 1 2021-01-01 00:00:00 Desempleo       12.7 
## 2 2021-01-01 00:00:00 Inflación anual  4.51
## 3 2021-02-01 00:00:00 Desempleo       13.2 
## 4 2021-02-01 00:00:00 Inflación anual  4.53
## 5 2021-03-01 00:00:00 Desempleo       12.9 
## 6 2021-03-01 00:00:00 Inflación anual  4.59
## 7 2021-04-01 00:00:00 Desempleo       12.5 
## 8 2021-04-01 00:00:00 Inflación anual  4.76
ggplot(
  indicadores_largos,
  aes(x = fecha, y = valor, color = indicador)
) +
  geom_line(linewidth = 0.95, show.legend = FALSE) +
  facet_wrap(
    vars(indicador),
    ncol = 1,
    scales = "free_y"
  ) +
  scale_color_manual(
    values = c(
      "Desempleo" = color_principal,
      "Inflación anual" = color_secundario
    )
  ) +
  scale_x_date(
    date_breaks = "1 year",
    date_labels = "%Y"
  ) +
  scale_y_continuous(
    labels = scales::label_number(
      decimal.mark = ",",
      suffix = " %"
    )
  ) +
  labs(
    title = "Dos indicadores simulados a través del tiempo",
    subtitle = "Las facetas evitan recurrir a un doble eje vertical",
    x = NULL,
    y = "Valor simulado",
    caption = "Fuente: datos simulados con fines pedagógicos."
  ) +
  tema_clase

Decisión gráfica. Los dobles ejes pueden sugerir relaciones distintas según las escalas elegidas. Para indicadores con unidades o rangos diferentes, las facetas suelen ofrecer una comparación más transparente.

9.3 Gráfico de área

El área enfatiza magnitud acumulada respecto de una línea base. Es útil en algunas series no negativas, pero puede ocultar detalle y no siempre es la mejor opción para tasas.

ggplot(indicadores_mensuales, aes(x = fecha, y = inflacion_anual)) +
  geom_area(fill = color_secundario, alpha = 0.45) +
  geom_line(color = color_secundario, linewidth = 0.8)

10 Facetas: pequeños múltiples

Las facetas dividen un gráfico en paneles usando una variable categórica. Permiten mantener la misma gramática y comparar patrones.

ggplot(hogares_analisis, aes(x = ingreso_mensual)) +
  geom_histogram(
    bins = 12,
    fill = color_secundario,
    color = "white"
  ) +
  facet_wrap(vars(municipio), ncol = 2) +
  scale_x_continuous(
    labels = scales::label_number(
      scale = 0.000001,
      suffix = " M",
      decimal.mark = ","
    )
  ) +
  labs(
    title = "Distribución del ingreso por municipio",
    subtitle = "Misma escala para facilitar comparaciones",
    x = "Ingreso mensual (millones)",
    y = "Número de hogares",
    caption = "Fuente: datos simulados con fines pedagógicos."
  ) +
  tema_clase

Usar scales = "free" permite que cada panel tenga su propia escala, pero puede dificultar la comparación directa. Debe ser una decisión consciente.

11 Etiquetas, escalas, coordenadas y temas

11.1 labs(): explicar el gráfico

ggplot(hogares_analisis, aes(x = zona, y = ahorro_mensual)) +
  geom_boxplot(fill = color_principal, alpha = 0.65, na.rm = TRUE) +
  labs(
    title = "Distribución del ahorro mensual por zona",
    subtitle = "La línea central de cada caja representa la mediana",
    x = NULL,
    y = "Ahorro mensual",
    caption = paste(
      "Fuente: datos simulados con fines pedagógicos.",
      "Valores negativos indican gasto superior al ingreso."
    )
  ) +
  tema_clase

Un título informativo dice qué se muestra. Un título orientado a storytelling puede expresar el hallazgo principal, siempre que sea fiel a los datos.

11.2 Escalas

Las escalas controlan cómo se presentan los valores:

  • scale_x_continuous() y scale_y_continuous() para variables numéricas;
  • scale_x_date() para fechas;
  • scale_color_manual() para colores de líneas o puntos;
  • scale_fill_manual() para rellenos;
  • limits, breaks y labels para rangos, marcas y formato.
ggplot(
  resumen_municipio,
  aes(x = municipio, y = ingreso_mediano)
) +
  geom_point(color = color_principal, size = 4) +
  scale_y_continuous(
    breaks = seq(0, 4000000, by = 1000000),
    labels = scales::label_number(
      scale = 0.000001,
      suffix = " M",
      decimal.mark = ","
    ),
    expand = expansion(mult = c(0, 0.08))
  ) +
  labs(
    title = "Ingreso mediano por municipio",
    x = NULL,
    y = "Ingreso mediano (millones)"
  ) +
  tema_clase

11.3 coord_cartesian() frente a eliminar datos

coord_cartesian() acerca la vista sin descartar observaciones del cálculo estadístico.

ggplot(hogares_analisis, aes(x = ingreso_mensual)) +
  geom_histogram(
    bins = 20,
    fill = color_secundario,
    color = "white"
  ) +
  coord_cartesian(xlim = c(0, 7000000)) +
  labs(
    title = "Acercamiento visual a un rango del ingreso",
    subtitle = "El zoom no modifica la base utilizada por el histograma",
    x = "Ingreso mensual",
    y = "Número de hogares"
  ) +
  tema_clase

Definir límites directamente en una escala puede eliminar datos antes de algunos cálculos. Para un zoom visual, coord_cartesian() suele ser más seguro.

11.4 Temas completos y ajustes puntuales

ggplot(hogares_analisis, aes(x = zona)) +
  geom_bar(fill = color_principal) +
  labs(
    title = "Un tema modifica elementos no asociados a los datos",
    x = NULL,
    y = "Número de hogares"
  ) +
  theme_classic(base_size = 14) +
  theme(
    plot.title = element_text(face = "bold"),
    axis.line = element_line(color = "#555555")
  )

Algunos temas completos son theme_minimal(), theme_classic(), theme_light() y theme_bw(). theme() permite modificar componentes específicos.

11.5 Colores y accesibilidad

  • usa color para representar información, no solo para decorar;
  • evita demasiadas categorías cromáticas;
  • conserva buen contraste entre fondo, texto y datos;
  • no dependas exclusivamente de rojo y verde;
  • mantén una categoría destacada y las demás en tonos neutros cuando exista un mensaje focal;
  • conserva la misma asociación entre categoría y color a lo largo del documento.

12 Del gráfico exploratorio al storytelling

El storytelling con datos no significa inventar una historia. Significa organizar evidencia visual alrededor de una pregunta y un mensaje verificable.

12.1 Una estructura sencilla

  1. Contexto: ¿qué problema o indicador observamos?
  2. Tensión o contraste: ¿qué patrón, cambio o diferencia merece atención?
  3. Evidencia: ¿qué muestra exactamente el gráfico?
  4. Interpretación prudente: ¿qué podemos afirmar y qué no?
  5. Implicación o siguiente pregunta: ¿qué conviene investigar o decidir?

12.2 De borrador exploratorio a gráfico comunicativo

Primero podemos crear un borrador rápido.

ggplot(
  resumen_municipio,
  aes(x = municipio, y = ingreso_mediano)
) +
  geom_col()

Ahora identificamos programáticamente el municipio con mayor mediana dentro de la base simulada y lo destacamos.

municipio_destacado <- resumen_municipio %>%
  slice_max(
    order_by = ingreso_mediano,
    n = 1,
    with_ties = FALSE
  ) %>%
  pull(municipio)

resumen_story <- resumen_municipio %>%
  mutate(
    destacado = if_else(
      municipio == municipio_destacado,
      "Sí",
      "No"
    ),
    etiqueta_ingreso = scales::label_number(
      scale = 0.000001,
      suffix = " M",
      decimal.mark = ",",
      accuracy = 0.1
    )(ingreso_mediano)
  )
ggplot(
  resumen_story,
  aes(
    x = reorder(municipio, ingreso_mediano),
    y = ingreso_mediano,
    fill = destacado
  )
) +
  geom_col(width = 0.68) +
  geom_text(
    aes(label = etiqueta_ingreso),
    hjust = -0.12,
    size = 4
  ) +
  coord_flip() +
  scale_fill_manual(
    values = c("No" = color_gris, "Sí" = color_principal),
    guide = "none"
  ) +
  scale_y_continuous(
    labels = scales::label_number(
      scale = 0.000001,
      suffix = " M",
      decimal.mark = ","
    )) +
  labs(
    title = paste0(
      municipio_destacado,
      " presenta la mayor mediana en la base simulada"
    ),
    subtitle = paste(
      "El color dirige la atención sin ocultar",
      "la comparación con los demás municipios"
    ),
    x = NULL,
    y = "Ingreso mediano (millones)",
    caption = paste(
      "Fuente: datos simulados con fines pedagógicos.",
      "Comparación descriptiva; no representa estadísticas oficiales."
    )
  ) +
  tema_clase +
  theme(plot.margin = margin(5.5, 45, 5.5, 5.5))

El segundo gráfico mejora la comunicación porque:

  • ordena las categorías;
  • destaca un elemento con intención;
  • utiliza un título que comunica el hallazgo;
  • muestra valores directos;
  • aclara fuente, carácter simulado y alcance descriptivo;
  • elimina una leyenda innecesaria.

12.3 Anotar un momento relevante

pico_inflacion <- indicadores_mensuales %>%
  slice_max(
    order_by = inflacion_anual,
    n = 1,
    with_ties = FALSE
  )
ggplot(
  indicadores_mensuales,
  aes(x = fecha, y = inflacion_anual)
) +
  geom_line(color = color_secundario, linewidth = 1) +
  geom_point(
    data = pico_inflacion,
    color = color_principal,
    size = 3
  ) +
  geom_text(
    data = pico_inflacion,
    aes(
      label = paste0(
        "Máximo simulado: ",
        round(inflacion_anual, 1),
        " %"
      )
    ),
    vjust = -1,
    color = color_principal,
    fontface = "bold"
  ) +
  scale_x_date(
    date_breaks = "1 year",
    date_labels = "%Y"
  ) +
  scale_y_continuous(
    labels = scales::label_number(
      decimal.mark = ",",
      suffix = " %"
    ),
    expand = expansion(mult = c(0.05, 0.16))
  ) +
  labs(
    title = "La serie simulada alcanza un máximo en el tramo central",
    subtitle = "La anotación dirige la atención al punto relevante",
    x = NULL,
    y = "Inflación anual simulada",
    caption = "Fuente: datos simulados con fines pedagógicos."
  ) +
  tema_clase

Las anotaciones deben explicar, no saturar. Conviene destacar solamente la información necesaria para comprender el mensaje.

12.4 Preguntas para auditar un gráfico

  • ¿La pregunta y la unidad de análisis son claras?
  • ¿El tipo de gráfico corresponde a las variables?
  • ¿Los ejes, unidades y categorías están etiquetados?
  • ¿El título comunica el contenido o el hallazgo?
  • ¿La escala distorsiona diferencias?
  • ¿Los faltantes fueron tratados de forma explícita?
  • ¿El color representa información útil?
  • ¿La fuente y las notas están visibles?
  • ¿La interpretación distingue descripción, asociación y causalidad?

13 Guardar un gráfico

Los gráficos pueden guardarse con ggsave(). Por defecto, guarda el último gráfico mostrado, pero es más reproducible asignar el gráfico a un objeto.

grafico_ingreso_zona <- ggplot(
  hogares_analisis,
  aes(x = zona, y = ingreso_mensual, fill = zona)
) +
  geom_boxplot(na.rm = TRUE) +
  scale_fill_manual(
    values = c("Urbana" = color_principal, "Rural" = color_secundario)
  ) +
  labs(
    title = "Ingreso mensual por zona",
    x = NULL,
    y = "Ingreso mensual",
    caption = "Fuente: datos simulados."
  ) +
  tema_clase +
  guides(fill = "none")

grafico_ingreso_zona

El siguiente código se muestra, pero no se ejecuta al renderizar para evitar crear archivos automáticamente.

ggsave(
  filename = "resultados/grafico_ingreso_zona.png",
  plot = grafico_ingreso_zona,
  width = 9,
  height = 6,
  units = "in",
  dpi = 300,
  bg = "white"
)
  • filename: ruta relativa y nombre del archivo;
  • plot: objeto gráfico que se guardará;
  • width y height: dimensiones;
  • dpi: resolución para formatos de imagen;
  • bg: color de fondo.

También puede guardarse como .pdf o .svg si el flujo de trabajo requiere un formato vectorial.

14 Práctica integradora

14.1 Situación

Un equipo necesita una pieza visual breve sobre el ahorro mensual de los hogares simulados. Quiere comparar municipios, identificar cuál presenta la mayor mediana y comunicar el resultado sin sugerir causalidad.

14.2 Enunciado

  1. Parte de hogares_analisis.
  2. Agrupa por municipio.
  3. Calcula número de hogares, mediana del ahorro y proporción de ahorro negativo entre observaciones válidas.
  4. Ordena los municipios por mediana del ahorro.
  5. Construye un gráfico horizontal de puntos o barras.
  6. Destaca el municipio con mayor mediana.
  7. Agrega título narrativo, unidades, fuente y nota de alcance.

Pista. La expresión mean(ahorro_mensual < 0, na.rm = TRUE) calcula una proporción. Usa slice_max() para identificar el municipio destacado.

# Escribe aquí tu transformación y tu gráfico.
Ver solución
resumen_ahorro <- hogares_analisis %>%
  group_by(municipio) %>%
  summarise(
    numero_hogares = n(),
    ahorro_mediano = median(ahorro_mensual, na.rm = TRUE),
    proporcion_ahorro_negativo = mean(
      ahorro_mensual < 0,
      na.rm = TRUE
    ),
    .groups = "drop"
  ) %>%
  arrange(ahorro_mediano)

municipio_mayor_ahorro <- resumen_ahorro %>%
  slice_max(
    order_by = ahorro_mediano,
    n = 1,
    with_ties = FALSE
  ) %>%
  pull(municipio)

resumen_ahorro <- resumen_ahorro %>%
  mutate(
    destacado = if_else(
      municipio == municipio_mayor_ahorro,
      "Sí",
      "No"
    )
  )

resumen_ahorro
## # A tibble: 5 × 5
##   municipio    numero_hogares ahorro_mediano proporcion_ahorro_negat…¹ destacado
##   <chr>                 <int>          <dbl>                     <dbl> <chr>    
## 1 Yumbo                    19         325500                    0.0556 No       
## 2 Palmira                  47         372500                    0.217  No       
## 3 Cali                    101         410000                    0.153  No       
## 4 Buenaventura             49         448000                    0.0930 No       
## 5 Jamundí                  24         560000                    0.25   Sí       
## # ℹ abbreviated name: ¹​proporcion_ahorro_negativo
ggplot(
  resumen_ahorro,
  aes(
    x = reorder(municipio, ahorro_mediano),
    y = ahorro_mediano,
    color = destacado
  )
) +
  geom_hline(
    yintercept = 0,
    color = "#777777",
    linetype = "dashed"
  ) +
  geom_point(size = 5) +
  coord_flip() +
  scale_color_manual(
    values = c("No" = color_gris, "Sí" = color_principal),
    guide = "none"
  ) +
  scale_y_continuous(
    labels = scales::label_number(
      scale = 0.000001,
      suffix = " M",
      decimal.mark = ","
    )
  ) +
  labs(
    title = paste0(
      municipio_mayor_ahorro,
      " registra la mayor mediana de ahorro simulado"
    ),
    subtitle = paste(
      "La línea punteada separa medianas positivas",
      "y negativas"
    ),
    x = NULL,
    y = "Ahorro mensual mediano (millones)",
    caption = paste(
      "Fuente: datos simulados con fines pedagógicos.",
      "Comparación descriptiva; no implica un efecto del municipio."
    )
  ) +
  tema_clase

Explicación. La transformación cambia la unidad de análisis de hogar a municipio. El gráfico destaca un solo resultado, mantiene el resto como contexto y utiliza una línea de referencia en cero.

Interpretación. La mayor mediana identifica una posición dentro de esta simulación. No permite afirmar que residir en ese municipio cause mayor ahorro ni que el resultado represente a su población.

15 Ejercicio individual

Enunciado. Construye una visualización para responder:

¿Cómo cambia la distribución del ingreso per cápita entre zonas y según condición de subsidio?

Requisitos:

  • utiliza ingreso_per_capita como variable numérica;
  • compara zona;
  • incorpora subsidio_grafico mediante facetas;
  • elige boxplot o violín y justifica la elección;
  • formatea el eje monetario;
  • incluye un título, subtítulo y nota de fuente;
  • redacta dos frases de interpretación prudente.

Pista. Puedes usar facet_wrap(vars(subsidio_grafico)).

# Escribe aquí tu solución.
Ver una solución posible
ggplot(
  hogares_analisis,
  aes(x = zona, y = ingreso_per_capita, fill = zona)
) +
  geom_boxplot(alpha = 0.75, na.rm = TRUE) +
  facet_wrap(vars(subsidio_grafico)) +
  scale_fill_manual(
    values = c("Urbana" = color_principal, "Rural" = color_secundario)
  ) +
  scale_y_continuous(
    labels = scales::label_number(
      scale = 0.000001,
      suffix = " M",
      decimal.mark = ","
    )
  ) +
  labs(
    title = "Ingreso per cápita por zona y condición de subsidio",
    subtitle = "Las cajas resumen mediana, cuartiles y dispersión",
    x = NULL,
    y = "Ingreso per cápita (millones)",
    fill = "Zona",
    caption = "Fuente: datos simulados con fines pedagógicos."
  ) +
  tema_clase +
  guides(fill = "none")

Justificación. Se eligió el boxplot porque permite comparar de manera compacta el centro y la dispersión entre varios subgrupos.

Interpretación posible. Las cajas permiten observar diferencias descriptivas y heterogeneidad dentro de cada condición. Como los datos son simulados y no existe un diseño de identificación causal, el gráfico no muestra el efecto de la zona ni del subsidio sobre el ingreso per cápita.

16 Reto opcional: mapa de calor de correlaciones

Un mapa de calor resume varias correlaciones mediante color. Antes de construirlo, debemos convertir la matriz en una tabla larga.

# Variables sugeridas:
# ingreso_mensual, gasto_mensual, ahorro_mensual,
# ingreso_per_capita y numero_personas.
Ver solución
matriz_correlacion <- hogares_analisis %>%
  select(
    ingreso_mensual,
    gasto_mensual,
    ahorro_mensual,
    ingreso_per_capita,
    numero_personas
  ) %>%
  cor(use = "pairwise.complete.obs")

correlaciones_largas <- as.data.frame(matriz_correlacion) %>%
  rownames_to_column(var = "variable_1") %>%
  pivot_longer(
    cols = -variable_1,
    names_to = "variable_2",
    values_to = "correlacion"
  )
ggplot(
  correlaciones_largas,
  aes(x = variable_1, y = variable_2, fill = correlacion)
) +
  geom_tile(color = "white") +
  geom_text(
    aes(label = round(correlacion, 2)),
    size = 3.7
  ) +
  scale_fill_gradient2(
    low = color_secundario,
    mid = "white",
    high = color_principal,
    midpoint = 0,
    limits = c(-1, 1)
  ) +
  coord_equal() +
  labs(
    title = "Correlaciones entre variables de los hogares",
    subtitle = "Valores cercanos a 1 o -1 indican mayor asociación lineal",
    x = NULL,
    y = NULL,
    fill = "Correlación",
    caption = "Fuente: datos simulados. Correlación no implica causalidad."
  ) +
  tema_clase +
  theme(
    axis.text.x = element_text(angle = 35, hjust = 1)
  )

Explicación. cor() produce una matriz; pivot_longer() la convierte a formato largo; geom_tile() dibuja una celda por combinación y el color representa el coeficiente.

Precaución. Una matriz de correlaciones resume asociaciones lineales bivariadas. No controla por otras variables, no identifica causalidad y puede ocultar patrones no lineales.

17 Errores frecuentes en ggplot2

  • olvidar agregar una geometría y obtener un panel vacío;
  • usar + al inicio de una nueva instrucción independiente;
  • escribir el nombre de una variable entre comillas dentro de aes();
  • usar geom_bar() cuando la tabla ya contiene el valor de y;
  • usar geom_col() sin proporcionar una variable numérica en y;
  • ignorar los valores faltantes sin revisar por qué existen;
  • confundir conteos, porcentajes y densidades;
  • comparar grupos con escalas inconsistentes sin advertirlo;
  • recortar un eje de barras y exagerar diferencias;
  • usar demasiados colores o categorías;
  • sobrecargar el gráfico con etiquetas y anotaciones;
  • conectar con líneas categorías que no tienen orden temporal;
  • interpretar correlación o comparación descriptiva como causalidad;