Apunte 2 — ggplot2 y la lógica de los geom

Analítica de Personas · Semestre otoño 2026 · Semana 1 · Prof. René Gempp

1. La idea central: gráficos por capas

ggplot2 construye gráficos capa por capa, como si fueras apilando transparencias. Cada capa agrega un elemento visual. Las capas se conectan con el signo + (no con el pipe |>).

1
ggplot(datos, aes(x = ..., y = ...)) ← El lienzo: qué datos y qué variables
2
+ geom_*() ← La geometría: cómo se dibujan los datos
3
+ labs(title = ..., x = ..., y = ...) ← Las etiquetas: títulos y nombres de ejes
4
+ theme_minimal() ← El estilo visual general
¡Atención! +, no |> Dentro de ggplot2, las capas se conectan con +. El pipe |> se usa para pasar datos antes de ggplot, pero una vez que escribes ggplot(), todo lo que sigue usa +. Es el error más común de la semana 1.

2. La función aes(): mapeo de variables a propiedades visuales

aes() (de aesthetics) le dice a ggplot qué variable del dataset controla qué propiedad visual del gráfico:

PropiedadQué controlaEjemplo
xPosición en el eje horizontalaes(x = edad)
yPosición en el eje verticalaes(y = ingreso_mensual)
fillColor de relleno (barras, áreas)aes(fill = genero)
colorColor de borde o líneaaes(color = departamento)
sizeTamaño del punto o líneaaes(size = antiguedad)
Dentro vs. fuera de aes() Si una propiedad visual depende de una variable del dataset, va dentro de aes(). Si es un valor fijo para todos los datos, va fuera:
# El COLOR depende de la variable "genero" → dentro de aes()
ggplot(innovaco, aes(x = departamento, fill = genero)) +
  geom_bar()

# TODAS las barras son verdes (valor fijo) → fuera de aes()
ggplot(innovaco, aes(x = departamento)) +
  geom_bar(fill = "#2C5F2D")

3. Los geom: ¿qué tipo de gráfico necesito?

Cada geom_*() dibuja los datos de una manera distinta. La elección depende de qué tipo de variables tienes y qué pregunta quieres responder:

Guía rápida: ¿qué geom uso?

Quiero ver...Variable(s)geom
Distribución de una variable numérica1 numéricageom_histogram()
Conteo por categorías1 categóricageom_bar()
Valores ya calculados en barras1 categórica + 1 numéricageom_col()
Comparar distribuciones entre grupos1 categórica + 1 numéricageom_boxplot()
Relación entre dos numéricas2 numéricasgeom_point()
Distribución suavizada1 numéricageom_density()

geom_histogram() — Distribución de una variable numérica

Divide una variable numérica en intervalos (bins) y cuenta cuántas observaciones caen en cada uno. Ideal para responder: ¿cómo se distribuye la edad/ingreso/antigüedad?

ggplot(innovaco, aes(x = edad)) +
  geom_histogram(
    binwidth = 3,              # Ancho de cada barra: cada 3 años
    fill     = "#2C5F2D",      # Color de relleno
    color    = "white"         # Color del borde
  ) +
  labs(
    title = "Distribución de Edad en InnovaCo",
    x = "Edad (años)",
    y = "Número de empleados"
  ) +
  theme_minimal()
ArgumentoQué haceValores típicos
binwidthAncho de cada barra (en unidades de la variable)3 para edad, 100000 para ingreso
binsNúmero total de barras (alternativa a binwidth)20, 30
fillColor de relleno de las barras"#2C5F2D", "steelblue"
colorColor del borde de las barras"white", "black"
¿binwidth o bins? Usa binwidth cuando la unidad tiene significado (3 años de edad, $100.000 de ingreso). Usa bins cuando solo quieres controlar cuántas barras hay. Si no pones ninguno, R usa 30 bins por defecto y te avisa con un mensaje.

geom_bar() — Contar automáticamente por categoría

Cuenta cuántas filas hay por cada valor de la variable categórica. No necesitas calcular nada antes: ggplot cuenta por ti.

# Conteo simple por departamento
ggplot(innovaco, aes(x = departamento)) +
  geom_bar(fill = "#065A82") +
  labs(title = "Empleados por departamento", x = NULL, y = "Cantidad") +
  theme_minimal()

# Barras agrupadas por género (fill dentro de aes)
ggplot(innovaco, aes(x = departamento, fill = genero)) +
  geom_bar(position = "dodge") +
  labs(title = "Composición por departamento y género") +
  theme_minimal()
ArgumentoQué haceOpciones
positionCómo se organizan las barras cuando hay grupos"stack" (apiladas, default), "dodge" (lado a lado), "fill" (proporcional)
fillColor de relleno (fijo, fuera de aes)Cualquier color
position = "dodge" vs "fill" "dodge" muestra barras lado a lado (compara cantidades absolutas). "fill" normaliza cada barra al 100% (compara proporciones). Para comparar tasas de rotación entre departamentos, "fill" es más útil.

geom_col() — Barras con valores ya calculados

A diferencia de geom_bar() que cuenta por ti, geom_col() necesita que le des tanto el eje x como el eje y. Úsalo cuando ya calculaste los valores con summarise():

# Primero calculo la tasa de rotación por departamento
rotacion_depto <- innovaco |>
  group_by(departamento) |>
  summarise(tasa_rotacion = mean(rotacion == "Sí"))

# Luego grafico con geom_col (necesito x e y)
ggplot(rotacion_depto, aes(x = departamento, y = tasa_rotacion)) +
  geom_col(fill = "#B85042") +
  scale_y_continuous(labels = scales::percent) +
  labs(title = "Tasa de rotación por departamento") +
  theme_minimal()

¿geom_bar() o geom_col()?

geom_bar(): ggplot cuenta por ti → solo necesitas aes(x = variable)
geom_col(): tú ya calculaste los valores → necesitas aes(x = categoría, y = valor)

geom_boxplot() — Comparar distribuciones entre grupos

Muestra la mediana, cuartiles y valores atípicos de una variable numérica, separada por grupos. Ideal para preguntas como: ¿varía el ingreso entre niveles jerárquicos?

ggplot(innovaco, aes(x = nivel_jerarquico, y = ingreso_mensual)) +
  geom_boxplot(fill = "#A7BEAE", color = "#2C5F2D") +
  labs(
    title = "Distribución de ingreso por nivel jerárquico",
    x = "Nivel", y = "Ingreso mensual (CLP)"
  ) +
  theme_minimal()

Lectura de un boxplot: La línea gruesa del centro es la mediana. La caja va del primer cuartil (25%) al tercer cuartil (75%). Los puntos fuera de los "bigotes" son valores atípicos.

4. Personalización: labs(), scale_*() y theme_*()

Etiquetas con labs()

labs(
  title    = "Título principal del gráfico",
  subtitle = "Subtítulo que aporta contexto",
  x        = "Nombre del eje X",
  y        = "Nombre del eje Y",
  fill     = "Nombre de la leyenda",       # si usas fill en aes
  caption  = "Fuente: HRIS InnovaCo"       # nota al pie
)
Tip para reportes ejecutivos: Un buen subtítulo resume el hallazgo: "Soporte Técnico lidera con un 35% de rotación". Así el lector entiende el gráfico sin tener que interpretar las barras.

Formateo de ejes con scale_*()

# Eje Y como porcentaje
+ scale_y_continuous(labels = scales::percent)

# Eje Y como pesos chilenos
+ scale_y_continuous(labels = scales::comma)

# Colores personalizados para categorías
+ scale_fill_manual(values = c("Hombre" = "#065A82", "Mujer" = "#F96167"))

# Límites del eje
+ scale_y_continuous(limits = c(0, 0.5))

Temas predefinidos

TemaEstiloRecomendación
theme_minimal()Limpio, fondo blanco, sin bordesNuestro default en el curso
theme_classic()Solo ejes, sin grillaEstilo publicación académica
theme_bw()Fondo blanco con marcoFormal
theme_gray()Default de ggplot (fondo gris)Evitar en reportes ejecutivos

5. Trucos útiles

Voltear las barras con coord_flip()

Cuando los nombres de las categorías son largos (como nombres de departamento), las barras horizontales son más legibles:

ggplot(rotacion_depto, aes(x = reorder(departamento, tasa_rotacion), 
                              y = tasa_rotacion)) +
  geom_col(fill = "#B85042") +
  coord_flip() +                 # Voltea el gráfico
  theme_minimal()

reorder(departamento, tasa_rotacion) ordena las barras de menor a mayor tasa. Así el departamento con más rotación queda arriba (más visible).

Agregar valores sobre las barras con geom_text()

+ geom_text(
    aes(label = scales::percent(tasa_rotacion, accuracy = 0.1)),
    hjust = -0.1,    # Desplaza el texto a la derecha de la barra
    size  = 3.5      # Tamaño del texto
  )

Separar en paneles con facet_wrap()

# Un histograma de edad por cada departamento
ggplot(innovaco, aes(x = edad)) +
  geom_histogram(binwidth = 3, fill = "#065A82", color = "white") +
  facet_wrap(~departamento, ncol = 2) +  # ~ antes de la variable
  theme_minimal()

6. Plantilla copiable

Copia y modifica esta plantilla para cualquier gráfico de barras en el curso:

# --- Plantilla de gráfico de barras ---
ggplot(MIS_DATOS, aes(x = reorder(MI_VARIABLE_X, MI_VARIABLE_Y), 
                        y = MI_VARIABLE_Y)) +
  geom_col(fill = "#0D9488") +
  geom_text(aes(label = round(MI_VARIABLE_Y, 1)), 
            hjust = -0.1, size = 3.5) +
  coord_flip() +
  labs(
    title    = "MI TÍTULO",
    subtitle = "Mi subtítulo con el hallazgo",
    x        = NULL,
    y        = "Mi eje Y",
    caption  = "Fuente: HRIS InnovaCo"
  ) +
  theme_minimal()

7. Errores frecuentes

ErrorCausaSolución
Error: unexpected '+' o gráfico incompleto El + quedó al inicio de la línea siguiente en vez del final de la anterior Siempre deja el + al final de la línea, no al principio de la siguiente
Se abre una ventana de gráfico vacía Escribiste ggplot(datos, aes(...)) sin agregar un geom_*() Agrega al menos un geom: + geom_bar()
object 'rotacion_depto' not found Usas una tabla calculada que no creaste antes Ejecuta primero el código que crea rotacion_depto con summarise()
Usaste |> en vez de + entre capas de ggplot ggplot usa + para agregar capas, no el pipe Cambia |> geom_bar() por + geom_bar()
Colores iguales para todos a pesar de usar fill = genero fill = genero está fuera de aes() Muévelo adentro: aes(fill = genero)