Gráficos de puente en {ggplot2}

5/2/2026

visualización de datos gráficos

Me pidieron reproducir con R un gráfico de puente que habían hecho en Excel, para poder crear decenas de versiones del mismo gráfico a partir de datos distintos y/o actualizados.

Éste era el gráfico original:

El gráfico de puente original

Los gráficos de puente son gráficos donde las barras representan el cambio de un valor original en el tiempo, o bien, la contribución de varias cifras a un valor final, en la forma de barras escalonadas.

Nunca había hecho uno, y tampoco sabía cómo se llamaban como para buscar instrucciones, así que me puse a intentarlo!

Éstos son los datos originales:

datos <- tibble::tribble(
  ~valor,        ~factor, ~region,    
  0.267569260593819,       "produc",       1, 
  0.658155474611586, "merc laboral",       1, 
  0.124032556711811,        "innov",       1, 
  0.346384929903161,       "empres",       1, 
  0.197669772542558,          "gob",       1, 
  0.1331561948028,          "salud",       1, 
  1.18645838429506,         "segur",       1, 
  -0.25626249726395,     "cap ingr",       1, 
  0.0845792632225653,    "igualdad",       1, 
  0.58032570060242,       "ent viv",       1, 
  0.107679485231535,      "med amb",       1, 
  0.0248454494479557,     "cap nat",       1, 
  -0.110064921904146,     "cap hum",       1, 
  0.782311601326303,      "cap fis",       1,
)

Lo primero que hice fue explorar visualmente los datos:

library(ggplot2)

# definir un tema
theme_set(
  theme_classic(base_family = "Rubik", 
                base_size = 10,
                ink = "#2d4a6d")
)

# visualizar
datos |> 
  ggplot() +
  aes(factor, valor) +
  geom_col()

Luego fui pensando qué características tenían que tener los datos para poder visualizarlos como en el gráfico original 🤔

Procesamiento de datos

Lo primero que se necesita es ir sumando los valores consecutivamente, dado que el gráfico va visualizando un puente donde cada barra parte desde el alto de la anterior. Así que usamos la función cumsum() para ir sumando los valores de cada fila con los anteriores, de manera acumulativa:

library(dplyr)

datos <- datos |> 
  mutate(suma = cumsum(valor))

datos
# A tibble: 14 × 4
     valor factor       region  suma
     <dbl> <chr>         <dbl> <dbl>
 1  0.268  produc            1 0.268
 2  0.658  merc laboral      1 0.926
 3  0.124  innov             1 1.05 
 4  0.346  empres            1 1.40 
 5  0.198  gob               1 1.59 
 6  0.133  salud             1 1.73 
 7  1.19   segur             1 2.91 
 8 -0.256  cap ingr          1 2.66 
 9  0.0846 igualdad          1 2.74 
10  0.580  ent viv           1 3.32 
11  0.108  med amb           1 3.43 
12  0.0248 cap nat           1 3.45 
13 -0.110  cap hum           1 3.34 
14  0.782  cap fis           1 4.13 

Con esto obtenemos los valores superiores de cada barra del gráfico.

Después necesitamos saber los valores inferiores de las barras, que en este tipo de gráfico aparecen encima de las barras anteriores. Para saber esto, tomamos la suma acumulada que calculamos antes y le restamos el valor de cada una de las barras; de esta forma obtenemos el valor acumulado menos el valor de la barra presente; es decir, su base:

datos <- datos |> 
  mutate(base = suma - valor)

datos
# A tibble: 14 × 5
     valor factor       region  suma  base
     <dbl> <chr>         <dbl> <dbl> <dbl>
 1  0.268  produc            1 0.268 0    
 2  0.658  merc laboral      1 0.926 0.268
 3  0.124  innov             1 1.05  0.926
 4  0.346  empres            1 1.40  1.05 
 5  0.198  gob               1 1.59  1.40 
 6  0.133  salud             1 1.73  1.59 
 7  1.19   segur             1 2.91  1.73 
 8 -0.256  cap ingr          1 2.66  2.91 
 9  0.0846 igualdad          1 2.74  2.66 
10  0.580  ent viv           1 3.32  2.74 
11  0.108  med amb           1 3.43  3.32 
12  0.0248 cap nat           1 3.45  3.43 
13 -0.110  cap hum           1 3.34  3.45 
14  0.782  cap fis           1 4.13  3.34 

Luego necesitamos algo simple: indicar si el valor de cada barra es positivo o negativo, así que usamos ifelse():

datos <- datos |> 
  mutate(direcc = if_else(valor > 0,
                          "Positivo",
                          "Negativo"))

datos
# A tibble: 14 × 6
     valor factor       region  suma  base direcc  
     <dbl> <chr>         <dbl> <dbl> <dbl> <chr>   
 1  0.268  produc            1 0.268 0     Positivo
 2  0.658  merc laboral      1 0.926 0.268 Positivo
 3  0.124  innov             1 1.05  0.926 Positivo
 4  0.346  empres            1 1.40  1.05  Positivo
 5  0.198  gob               1 1.59  1.40  Positivo
 6  0.133  salud             1 1.73  1.59  Positivo
 7  1.19   segur             1 2.91  1.73  Positivo
 8 -0.256  cap ingr          1 2.66  2.91  Negativo
 9  0.0846 igualdad          1 2.74  2.66  Positivo
10  0.580  ent viv           1 3.32  2.74  Positivo
11  0.108  med amb           1 3.43  3.32  Positivo
12  0.0248 cap nat           1 3.45  3.43  Positivo
13 -0.110  cap hum           1 3.34  3.45  Negativo
14  0.782  cap fis           1 4.13  3.34  Positivo

Ahora falta el último detalle: el gráfico original tiene una barra al final que muestra el total o la suma acumulada de todas las barras, pero empezando desde abajo.

Como nuestro dataframe tiene una fila por cada barra, tendríamos que agregar una fila nueva al final con el valor total, y con la base en cero. Para ésto podemos usar la función add_row(), a la que le podemos entregar los valores de una fila nueva, pero no escribiéndolos a mano, sino basándonos en los datos:

datos <- datos |> 
  add_row(valor = sum(datos$valor),
          suma = sum(datos$valor),
          base = 0, 
          region = 1,
          factor = "total",
          direcc = "Variación")

tail(datos)
# A tibble: 6 × 6
    valor factor  region  suma  base direcc   
    <dbl> <chr>    <dbl> <dbl> <dbl> <chr>    
1  0.580  ent viv      1  3.32  2.74 Positivo 
2  0.108  med amb      1  3.43  3.32 Positivo 
3  0.0248 cap nat      1  3.45  3.43 Positivo 
4 -0.110  cap hum      1  3.34  3.45 Negativo 
5  0.782  cap fis      1  4.13  3.34 Positivo 
6  4.13   total        1  4.13  0    Variación

Finalmente ordenamos los valores del eje horiontal en el orden que vienen en el dataframe, para que no aparezcan por orden alfabético (el orden por defecto que le da {ggplot2} a las variables discretas que no están ordenadas)

datos <- datos |> 
  mutate(factor = forcats::fct_inorder(factor)) 

Visualización

Ahora queda hacer la visualización. Volvemos a probar con las barras:

datos |> 
  ggplot() +
  aes(x = factor,
      y = valor, 
      fill = direcc) +
  geom_col() +
  guides(fill = guide_legend(position = "top"))

Claramente no podemos usar columnas (geom_col()) porque necesitamos indicar el punto de partida de cada columna en el eje vertical.

Si bien se pueden hacer rectángulos con {ggplot2}, preferí usar líneas con la función geom_segment(), que recibe valores para x e y, pero también para el final de x y el final de y, permitiendo longitudes personalizadas. Al final, ¿qué es una columna, sino una línea gruesa? 🧠

datos |> 
  ggplot() +
  aes(x = factor,
      y = valor, 
      color = direcc) +
  geom_segment(
    aes(xend = factor,
        y = base,
        yend = suma)
  ) +
  guides(fill = guide_legend(position = "top"))

¡Estamos cerca! Ahora engrosamos las líneas con el argumento linewidth dentro de geom_segment(), agregamos texto con geom_text() que se posicione según si es positivo o negativo, una escala de colores con scale_color_manual(), y algunos detalles en el tema.

El otro cambio principal es agregar líneas horizontales que conecten cada barra con la otra. Lamentablemente, creo que el largo de estas barras es específico a la cantidad de barras y el ancho del gráfico, entre otros factores, así que habrá que experimentar.

datos |> 
  ggplot() +
  aes(x = factor,
      y = valor, 
      color = direcc) +
  # barras con alto personalizado
  geom_segment(
    aes(xend = factor,
        y = base,
        yend = suma),
    linewidth = 12) +
  # líneas horizontales que conecten las barras
  geom_segment(
    aes(x = as.numeric(factor)-0.4,
        xend = ifelse(factor == "total",
                      as.numeric(factor)+0.4,
                      as.numeric(lead(factor))+0.4),
        y = suma-0.005, yend = suma-0.005),
    alpha = 0.5) +
  # textos encima o debajo de barras
  geom_text(aes(y = suma,
                label = round(valor, 1),
                nudge_y = ifelse(valor > 0, 0.1, -0.1)),
            size = 3,
            show.legend = F) +
  # espaciado vertical
  scale_y_continuous(expand = expansion(c(0, 0.1))) +
  # escala de colores
  scale_color_manual(values = c("Positivo" = "#2d4a6d",
                                "Negativo" = "#b52141",
                                "Variación" = "#668243")) +
  # leyenda
  guides(color = guide_legend(title = NULL, position = "top",
                              override.aes = list(linewidth = 6))) +
  # temas
  theme(axis.text.x = element_text(angle = 45, hjust = 1),
        axis.title = element_blank(),
        legend.key.width = unit(2, "mm"),
        panel.grid.major.y = element_line(linetype = "dashed", linewidth = 0.2, color = "grey80"))

Listo! Quedó idéntico al original, y también más elegante, pero lo más importante: quedó reproducible y listo para empaquetarse en una función que te permita crear cientos de estos mismos gráficos con datos distintos o actualizados.

Fecha de publicación:
February 5, 2026
Extensión:
7 minute read, 1329 words
Tags:
visualización de datos gráficos
Ver también:
Extensiones recomendadas para mejorar tus gráficos de `{ggplot2}`
App: Suicidios en Chile (2017-2024) desde una perspectiva de género
Gráficos ternarios o triangulares de tres variables en `{ggplot2}`