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:
ggplot2;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.
dplyr.ggplot2.No. Cada pregunta describe una estructura distinta:
La elección comienza con la pregunta, no con el menú de gráficos disponible.
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. |
También podemos clasificar las visualizaciones por el número y la función de las variables:
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. |
ggplot2 forma parte de tidyverse. La
instalación normalmente se hace una sola vez y no debe
ejecutarse automáticamente al renderizar este documento.
En cada nueva sesión de R cargamos el paquete:
Al cargar tidyverse tendremos disponibles
ggplot2, dplyr, tidyr,
forcats, readr y otras herramientas
relacionadas.
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.
Nunca debemos entregar automáticamente una base a
ggplot2. Primero comprobamos estructura, variables y
faltantes.
## 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
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>
ggplot2 no
reemplaza a dplyr. Con frecuencia transformamos y resumimos
con dplyr, y después enviamos el resultado a
ggplot2.
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:
x, y, color, fill,
size, etc.;El resultado todavía está vacío porque definimos los datos y el eje
x, pero no indicamos cómo dibujar las
observaciones.
El signo + agrega capas. En ggplot2, debe
quedar al final de la línea que continúa.
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.
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_claseaes(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”).
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_claseLa recta resume una tendencia lineal descriptiva. No demuestra que el ingreso cause el gasto ni sustituye un análisis econométrico.
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_clasegeom_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_clasereorder() ordena las categorías por la variable numérica
y coord_flip() intercambia los ejes. Esto facilita leer
etiquetas largas.
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_claseposition = "fill" transforma cada barra en proporciones.
Si usáramos position = "stack", veríamos conteos
apilados.
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()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().
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_claseExplicació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.
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_clasebins?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_claseNo existe un número universal de intervalos. Conviene probar alternativas y explicar la decisión cuando sea relevante.
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_claseLa densidad resulta útil para comparar formas, pero puede ser menos intuitiva para una audiencia no técnica.
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_claseLos puntos más allá de los bigotes no son automáticamente errores. Son observaciones que merecen revisión sustantiva y técnica.
| 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. |
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().
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_claseExplicació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.
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.
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.
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_clasehogares_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.
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_claseAl leer un gráfico de dispersión observamos:
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_clasealpha introduce transparencia y ayuda cuando los puntos
se superponen. geom_smooth(method = "lm") agrega una recta
estimada para describir el patrón lineal.
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:
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.
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_claseExplicació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.
Una serie temporal organiza observaciones según una fecha. El orden es sustantivo: los meses no son categorías intercambiables.
Cargamos la base.
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_claseUna 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.
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_claseEl á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.
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_claseUsar scales = "free" permite que cada panel tenga su
propia escala, pero puede dificultar la comparación directa. Debe ser
una decisión consciente.
labs():
explicar el gráficoggplot(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_claseUn 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.
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_clasecoord_cartesian() frente a eliminar datoscoord_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_claseDefinir 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.
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.
El storytelling con datos no significa inventar una historia. Significa organizar evidencia visual alrededor de una pregunta y un mensaje verificable.
Primero podemos crear un borrador rápido.
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:
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_claseLas anotaciones deben explicar, no saturar. Conviene destacar solamente la información necesaria para comprender el mensaje.
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_zonaEl 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.
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.
hogares_analisis.municipio.Pista. La expresión
mean(ahorro_mensual < 0, na.rm = TRUE) calcula una
proporción. Usa slice_max() para identificar el municipio
destacado.
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_claseExplicació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.
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:
ingreso_per_capita como variable numérica;zona;subsidio_grafico mediante facetas;Pista. Puedes usar
facet_wrap(vars(subsidio_grafico)).
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.
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.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.
ggplot2+ al inicio de una nueva instrucción
independiente;aes();geom_bar() cuando la tabla ya contiene el valor de
y;geom_col() sin proporcionar una variable numérica
en y;