ggplot2

La Gramática de Gráficos

Manuel Toral

Ciencia de Datos I · Tec de Monterrey

¿Por qué ggplot2?

La mayoría de los sistemas de gráficos son listas de instrucciones:
“dibuja una barra aquí, un punto allá”.

ggplot2 es diferente. Se basa en la Gramática de Gráficos (Wilkinson, 2005): cada visualización es una declaración sobre cómo los datos se traducen en propiedades visuales.

📖 Un gráfico = datos + mapeo estético + geometría
Todo lo demás es opcional pero poderoso.

Las 7 capas de la gramática:

Capa Función
data El dataframe
aes() Mapeo de variables
geom_*() Representación visual
stat_*() Transformaciones
scale_*() Traducción de aesthetics
coord_*() Sistema de coordenadas
facet_*() Paneles múltiples

El dataset: mpg

Usaremos mpg, incluido en ggplot2. Contiene datos de 234 vehículos.

library(ggplot2)
library(dplyr)

glimpse(mpg)
Rows: 234
Columns: 11
$ manufacturer <chr> "audi", "audi", "audi", "audi", "audi", "audi", "audi", "…
$ model        <chr> "a4", "a4", "a4", "a4", "a4", "a4", "a4", "a4 quattro", "…
$ displ        <dbl> 1.8, 1.8, 2.0, 2.0, 2.8, 2.8, 3.1, 1.8, 1.8, 2.0, 2.0, 2.…
$ year         <int> 1999, 1999, 2008, 2008, 1999, 1999, 2008, 1999, 1999, 200…
$ cyl          <int> 4, 4, 4, 4, 6, 6, 6, 4, 4, 4, 4, 6, 6, 6, 6, 6, 6, 8, 8, …
$ trans        <chr> "auto(l5)", "manual(m5)", "manual(m6)", "auto(av)", "auto…
$ drv          <chr> "f", "f", "f", "f", "f", "f", "f", "4", "4", "4", "4", "4…
$ cty          <int> 18, 21, 20, 21, 16, 18, 18, 18, 16, 20, 19, 15, 17, 17, 1…
$ hwy          <int> 29, 29, 31, 30, 26, 26, 27, 26, 25, 28, 27, 25, 25, 25, 2…
$ fl           <chr> "p", "p", "p", "p", "p", "p", "p", "p", "p", "p", "p", "p…
$ class        <chr> "compact", "compact", "compact", "compact", "compact", "c…

Las variables clave: displ (desplazamiento del motor, L), hwy (MPG en autopista), class (tipo de vehículo), cyl (cilindros), drv (tracción).

01
Data + aes()
El canvas: dónde viven los datos y cómo se mapean a propiedades visuales

Paso 1 · Data + aes() — El canvas

1
Data + aes
2
Geoms
3
Scales
4
Faceting
5
Themes

ggplot() crea el canvas. Solo con data y aes() obtienes un lienzo vacío — sin geometría no hay nada que dibujar.

ggplot(data = mpg,
       aes(x = displ, y = hwy))

¿Qué hace aes()?

Mapea columnas del dataframe a propiedades visuales:

Argumento Propiedad visual
x, y Posición en ejes
color Color del borde/línea
fill Color de relleno
size Tamaño del elemento
shape Forma del punto
alpha Transparencia
linetype Tipo de línea

Paso 1 · aes() global vs. local

1
Data + aes
2
Geoms
3
Scales
4
Faceting
5
Themes

El aes() en ggplot() aplica a todas las capas. El aes() dentro de un geom_*() aplica solo a esa capa.

Global (se hereda):

ggplot(mpg, aes(x = displ,
                y = hwy,
                color = class)) +
  geom_point() +
  geom_smooth() # ← también usa color

Local (solo ese geom):

ggplot(mpg, aes(x = displ, y = hwy)) +
  geom_point(aes(color = class)) + # ← solo puntos
  geom_smooth()                    # ← sin color

⚠️ Regla importante: si pones un valor fijo (e.g. color = “red”) va fuera del aes(). Si mapeas una variable (e.g. color = class), va dentro.

02
Geoms
La representación física: cómo se dibujan los datos

Paso 2 · Geoms — Los tipos de gráfico

1
Data + aes
2
Geoms
3
Scales
4
Faceting
5
Themes

Los geoms son las formas físicas que representan los datos. Cada uno tiene aesthetics requeridos y opcionales.

Geom Uso Requiere
geom_point() Dispersión x, y
geom_line() Series de tiempo x, y
geom_bar() Conteos x
geom_col() Valores exactos x, y
geom_histogram() Distribución x
geom_boxplot() Resumen estadístico x, y
geom_violin() Distribución completa x, y
geom_smooth() Tendencia x, y
geom_text() Etiquetas x, y, label
ggplot(mpg, aes(x = displ, y = hwy)) +
  geom_point(aes(color = class),
             size = 3,
             alpha = 0.7)

Paso 2 · Capas múltiples

1
Data + aes
2
Geoms
3
Scales
4
Faceting
5
Themes

Puedes apilar geoms con +. Se dibujan en orden — el último queda encima.

ggplot(mpg, aes(x = displ, y = hwy)) +
  geom_point(aes(color = class), size = 2.5, alpha = 0.65) +        # capa 1: puntos
  geom_smooth(method = "lm", se = TRUE,                             # capa 2: regresión
              color = "#1a1a2e", fill = "#cbd5e1", linewidth = 1.2)

Paso 2 · Distribuciones con geoms combinados

1
Data + aes
2
Geoms
3
Scales
4
Faceting
5
Themes

El violín + jitter es una combinación habitual para mostrar la distribución completa y los datos individuales.

mpg |>
  filter(cyl %in% c(4, 6, 8)) |>
  ggplot(aes(x = factor(cyl), y = hwy, fill = factor(cyl))) +
  geom_violin(alpha = 0.35, color = NA) +                     # distribución
  geom_boxplot(width = 0.12, outlier.shape = NA, alpha = 0.7) + # resumen
  geom_jitter(width = 0.08, alpha = 0.3, size = 1.5)            # datos crudos

03
Scales y colores
Cómo se traduce cada aesthetic en valores concretos

Paso 3 · Scales — La traducción

1
Data + aes
2
Geoms
3
Scales
4
Faceting
5
Themes

Cada aesthetic tiene una scale. La convención es scale_{aesthetic}_{tipo}().

Función Cuándo usarla
scale_color_manual() Colores exactos propios
scale_color_brewer() Paletas categóricas de ColorBrewer
scale_color_viridis_c() Variable continua, accesible
scale_fill_gradient2() Divergente (neg/pos)
scale_x_log10() Eje X logarítmico
scale_y_percent() Eje Y en porcentaje (scales)
scale_size_area() Tamaño proporcional al área
ggplot(mpg, aes(displ, hwy, color = class)) +
  geom_point(size = 3, alpha = 0.8) +
  scale_color_brewer(palette = "Set2") +
  scale_x_continuous(breaks = 2:7) +
  scale_y_continuous(limits = c(10, 45))

Paso 3 · Colores manuales y labs()

1
Data + aes
2
Geoms
3
Scales
4
Faceting
5
Themes

labs() controla todos los textos: título, subtítulo, ejes, leyendas, caption.

mi_paleta <- c("4" = "#3b82f6", "6" = "#f97316", "8" = "#ef4444")

mpg |>
  filter(cyl %in% c(4, 6, 8)) |>
  ggplot(aes(displ, hwy, color = factor(cyl))) +
  geom_point(size = 3, alpha = 0.7) +
  scale_color_manual(values = mi_paleta) +
  labs(
    title    = "Mayor desplazamiento, menor eficiencia",
    subtitle = "Relación entre cilindrada y MPG en autopista",
    x        = "Desplazamiento del motor (litros)",
    y        = "MPG en autopista",
    color    = "Cilindros",
    caption  = "Fuente: EPA · dataset mpg (ggplot2)"
  )

04
Faceting
Multiplicar el gráfico por categorías para comparar de un vistazo

Paso 4 · facet_wrap()

1
Data + aes
2
Geoms
3
Scales
4
Faceting
5
Themes

facet_wrap(~variable) crea un panel por cada valor único de la variable. Ideal para una sola variable categórica.

ggplot(mpg, aes(displ, hwy, color = drv)) +
  geom_point(alpha = 0.6, size = 2) +
  geom_smooth(method = "lm", se = FALSE, linewidth = 0.8) +
  facet_wrap(~class, nrow = 2) +
  labs(title = "Eficiencia por clase de vehículo", color = "Tracción",
       x = "Desplazamiento (L)", y = "MPG autopista")

Paso 4 · facet_grid()

1
Data + aes
2
Geoms
3
Scales
4
Faceting
5
Themes

facet_grid(filas ~ columnas) cruza dos variables. Más estructurado y comparativo.

mpg |>
  filter(cyl %in% c(4, 6, 8), drv != "4") |>
  ggplot(aes(displ, hwy)) +
  geom_point(alpha = 0.5, size = 2, color = "#3b82f6") +
  facet_grid(drv ~ factor(cyl),
             labeller = label_both) + # ← pone "cyl: 4" en lugar de solo "4"
  labs(title = "Eficiencia: tracción × cilindros",
       x = "Desplazamiento (L)", y = "MPG autopista")

Paso 4 · Scales libres en facets

1
Data + aes
2
Geoms
3
Scales
4
Faceting
5
Themes

Por default todos los paneles comparten ejes. Con scales = "free" cada panel tiene sus propios rangos.

Ejes fijos (default) — para comparar:

ggplot(mpg, aes(hwy)) +
  geom_histogram(fill = "#3b82f6",
                 bins = 15) +
  facet_wrap(~drv)

Ejes libres — para ver forma:

ggplot(mpg, aes(hwy)) +
  geom_histogram(fill = "#f97316",
                 bins = 15) +
  facet_wrap(~drv,
             scales = "free_y") # ← solo Y libre

05
Themes y labs()
El estilo visual y los textos del gráfico

Paso 5 · Themes incorporados

1
Data + aes
2
Geoms
3
Scales
4
Faceting
5
Themes

Los themes controlan todo lo que no es dato: fondo, grillas, tipografía, leyendas.

Función Estilo
theme_minimal() Limpio, sin fondo
theme_classic() Ejes L, para publicación
theme_bw() Fondo blanco, grid gris
theme_light() Suave, panel gris claro
theme_dark() Fondo oscuro
theme_void() Sin ejes ni fondo

Paquetes externos:

  • {ggthemes} → FiveThirtyEight, Economist, WSJ
  • {hrbrthemes} → tipografía limpia
  • {ggdark} → dark mode elegante
base <- ggplot(mpg, aes(displ, hwy,
                        color = factor(cyl))) +
  geom_point(size = 2.5, alpha = 0.7) +
  labs(color = "cyl")

base + theme_classic(base_size = 14)

Paso 5 · theme() — Control total

1
Data + aes
2
Geoms
3
Scales
4
Faceting
5
Themes

theme() modifica elementos específicos con funciones element_*().

ggplot(mpg, aes(displ, hwy, color = class)) +
  geom_point(size = 2.5, alpha = 0.7) +
  scale_color_brewer(palette = "Set2") +
  labs(title = "Eficiencia vs. Desplazamiento",
       subtitle = "Dataset mpg · ggplot2",
       x = "Desplazamiento (L)", y = "MPG autopista") +
  theme_minimal(base_size = 13) +
  theme(
    plot.title       = element_text(face = "bold", size = 16),
    plot.subtitle    = element_text(color = "gray50", margin = margin(b = 10)),
    legend.position  = "bottom",
    legend.title     = element_text(face = "bold"),
    panel.grid.minor = element_blank(),
    axis.line        = element_line(color = "gray80")
  )

Gráfico completo: todo junto

El flujo completo con las 5 capas integradas.

mpg |>
  filter(cyl %in% c(4, 6, 8)) |>
  ggplot(aes(x = displ, y = hwy,
             color = factor(cyl),
             size  = cyl)) +                                   # 1. data + aes
  geom_point(alpha = 0.6) +                                   # 2. geom
  geom_smooth(aes(group = factor(cyl)),                       # 2. geom adicional
              method = "lm", se = FALSE, linewidth = 0.8) +
  scale_color_manual(values = c("4"="#3b82f6","6"="#f97316","8"="#ef4444")) + # 3. scale
  scale_size(range = c(1.5, 4), guide = "none") +
  facet_wrap(~drv, labeller = as_labeller(                    # 4. facet
    c(f="Tracción delantera", r="Tracción trasera", `4`="4x4"))) +
  labs(title = "Eficiencia por cilindrada, tracción y cilindros",
       x = "Desplazamiento (L)", y = "MPG autopista",
       color = "Cilindros", caption = "Fuente: EPA · mpg") +
  theme_minimal(base_size = 12) +                            # 5. theme
  theme(legend.position = "bottom",
        strip.text = element_text(face = "bold"),
        panel.grid.minor = element_blank())

Referencia rápida

ggplot(data, aes(x, y, color, fill, size, shape, alpha, linetype))  # canvas

# Geoms frecuentes
+ geom_point()   + geom_line()    + geom_bar()      + geom_col()
+ geom_histogram() + geom_boxplot() + geom_violin()  + geom_smooth()
+ geom_text()    + geom_label()   + geom_area()     + geom_tile()

# Scales
+ scale_color_brewer(palette)   + scale_color_manual(values)
+ scale_fill_gradient2()        + scale_x_log10()

# Faceting
+ facet_wrap(~var, nrow, scales)  + facet_grid(fila ~ col, scales)

# Textos
+ labs(title, subtitle, x, y, color, fill, caption)

# Tema base + ajustes finos
+ theme_minimal() + theme_classic() + theme_bw()
+ theme(plot.title = element_text(...), legend.position, ...)

💡 Regla de oro: empieza con lo mínimo que funcione, luego añade capas una por una. Cada + es una decisión visual consciente.

¿Preguntas?

Ciencia de Datos II · Tec de Monterrey

ggplot2 · dplyr · tidyr · forcats · scales