6  Gráficos de comparación

Los gráficos de comparación son una forma de visualizar diferencias entre grupos o categorías, o también para comparar cambios en el tiempo. Siguiendo la Figura 1, los gráficos de comparación que veremos en este libro son:

  1. Gráfico de barras (bar plot): se utiliza para comparar la frecuencia o valor de una variable categórica.

  2. Gráfico de líneas (line plot): se utiliza para comparar cambios en una variable numérica a lo largo del tiempo.

En este capítulo vamos a trabajar con tres datasets:

Para comenzar, vamos a cargar los paquetes necesarios y a leer los datos que utilizaremos en este capítulo.

# Cargar paquetes
library(gapminder)
library(geomtextpath)
library(tidyverse)
# Cargar datos
inventario_tbl <- read_rds("../data/inventario_prep.rds")
gapminder_tbl <- gapminder |> 
    filter(year == 2002)

6.1 Objetivos

Al final de este capítulo serás capaz de:

  • Crear gráficos de barras para comparar la frecuencia de una variable categórica.

  • Crear gráficos de líneas para comparar cambios en una variable numérica a lo largo del tiempo.

6.2 Gráfico de barras

El gráfico de barras es una de las formas más comunes de visualizar la frecuencia de una variable categórica. En este caso, vamos a utilizar el dataset de iris para comparar la frecuencia de las especies de flores. Para ello, tenemos dos funciones que nos permiten crear gráficos de barras:

  • geom_bar(): utiliza stat = "count" para contar la frecuencia de cada categoría. Solamente necesita definir una estética (x o y)

  • geom_col(): utiliza stat = "identity" para representar los valores de la variable. Necesita definir dos estéticas (x e y)

Vamos a generar el mismo gráfico utilizando ambas funciones para ver la diferencia. El gráfico que generaremos será la frecuencia de las especies de flores en el dataset de iris.

Como vemos, solamente indicamos la estética x. Como existen 50 flores de cada especie, simplemente nos da ese número para cada especie.

iris |> 
    ggplot(aes(x = Species)) +
    geom_bar() +
    labs(
        title = "Frecuencia de especies de flores",
         x    = "Especies",
         y    = "Frecuencia"
    )
Fig. 6.1: Gráfico de barras generado con geom_bar() para la frecuencia de las especies de Iris

En este caso, necesitamos calcular primero los valores que queremos mostrar:

## Calcular frecuencias
iris_count <- count(iris, Species)
## Imprimir
iris_count
     Species  n
1     setosa 50
2 versicolor 50
3  virginica 50

Una vez tenemos los valores, vamos a generar el mismo gráfico. Fíjate que tenemos que añadir la estética y para decirle donde tiene que buscar los valores.

iris_count |> 
    ggplot(aes(x = Species, y = n)) +
    geom_col() +
    labs(
        title = "Frecuencia de especies de flores",
         x    = "Especies",
         y    = "Frecuencia"
    )
Fig. 6.2: Gráfico de barras generado con geom_col() para la frecuencia de las especies de Iris

Pues esta es la forma más básica de generar un gráfico de barras. Vamos a ver algo más interesante.

6.2.1 Position

Recuerdas cuando utilizamos geom_jitter(), que dijimos que era lo mismo que geom_point(position = "jitter")? Pues resulta que position es un argumento muy importante en los gráficos de barras para definir cómo se van a posicionar las barras. Esto cobra importancia cuando mapeamos una variable a la estética fill. Vamos a utilizar los datos de gapminder para los años mayores a 1990 y comparar el PIB per cápita de algunos países europeos.

## Filtrar datos
gapminder_tbl <- gapminder |> 
    filter(year > 1990) |>
    filter(country %in% c("Spain", "Germany", "France", "Italy", "Portugal")) |>
    mutate(
        year = as.factor(year)
    ) 
## Imprimir
gapminder_tbl
# A tibble: 20 × 6
   country  continent year  lifeExp      pop gdpPercap
   <fct>    <fct>     <fct>   <dbl>    <int>     <dbl>
 1 France   Europe    1992     77.5 57374179    24704.
 2 France   Europe    1997     78.6 58623428    25890.
 3 France   Europe    2002     79.6 59925035    28926.
 4 France   Europe    2007     80.7 61083916    30470.
 5 Germany  Europe    1992     76.1 80597764    26505.
 6 Germany  Europe    1997     77.3 82011073    27789.
 7 Germany  Europe    2002     78.7 82350671    30036.
 8 Germany  Europe    2007     79.4 82400996    32170.
 9 Italy    Europe    1992     77.4 56840847    22014.
10 Italy    Europe    1997     78.8 57479469    24675.
11 Italy    Europe    2002     80.2 57926999    27968.
12 Italy    Europe    2007     80.5 58147733    28570.
13 Portugal Europe    1992     74.9  9927680    16207.
14 Portugal Europe    1997     76.0 10156415    17641.
15 Portugal Europe    2002     77.3 10433867    19971.
16 Portugal Europe    2007     78.1 10642836    20510.
17 Spain    Europe    1992     77.6 39549438    18603.
18 Spain    Europe    1997     78.8 39855442    20445.
19 Spain    Europe    2002     79.8 40152517    24835.
20 Spain    Europe    2007     80.9 40448191    28821.
gapminder_tbl |> 
    ggplot(aes(x = year, y = gdpPercap, fill = country)) +
    geom_col() +
    labs(
        x    = NULL,
        y    = "PIB per cápita",
        fill = NULL
    ) +
    scale_fill_viridis_d() +
    theme_bw()
Fig. 6.3: Gráfico de barras por defecto. Evolución temporal del PIB per cápita en algunos países europeos

Como ves, por defecto las barras se ponen unas encima de otras. Este comportamiento viene definido por el argumento position, cuyas opciones son:

  • position = position_stack(): valor por defecto. Las barras se acumulan unas encima de otras.

  • position = position_dodge(): las barras se separan unas de otras.

  • position = position_dodge2(): las barras se separan unas de otras con un espacio entre ellas.

  • position = position_fill(): muestra el valor relativo de cada clase. Cada barra suma un total de 1 = 100%.

  • position = position_nudge(): todas las barras empiezan en 0 y se localizan unas detrás de otras.

En la siguiente aplicación puedes experimentar para ver el comportamiento con los diferentes valores de position.

#| standalone: true
#| viewerHeight: 600

## Load packages
library(bslib)
library(dplyr)
library(gapminder)
library(ggplot2)
library(glue)
library(shiny)

## UI
ui <- page_sidebar(
    sidebar = sidebar(
        open = "open",
        width = 200,
        selectInput(
            inputId  = "position_input",
            label    = "Position",
            choices  = c("dodge", "dodge2", "fill", "nudge", "stack"),
            selected = "stack"
        )
    ),
    verbatimTextOutput("code") |> card(),
    plotOutput("plot", width = 500) |> card()
)

## Server
server <- function(input, output, session) {
    
    output$code <- renderPrint({
        glue(
            '
            gapminder_tbl |> 
                ggplot(aes(x = year, y = gdpPercap, fill = country)) +
                geom_col(
                    position = position_{input$position_input}()
                ) +
                labs(x = NULL,y = "PIB per cápita",fill = NULL) +
                scale_fill_viridis_d() +
                theme_bw(base_size = 8)
            '
            )
    })

    ## Plot
    output$plot <- renderPlot({
        gapminder |> 
            filter(year > 1990) |>
            filter(country %in% c("Spain", "Germany", "France", "Italy", "Portugal")) |>
            mutate(year = as.factor(year)) |> 
            ggplot(aes(x = year, y = gdpPercap, fill = country)) +
            geom_col(
                position = input$position_input
            ) +
            labs(
                x    = NULL,
                y    = "PIB per cápita",
                fill = NULL
            ) +
            scale_fill_viridis_d() +
            theme_bw(base_size = 8)
        }, res = 96
    )
}

## Run app
shinyApp(ui = ui, server = server)

Podemos sacar una serie de conclusiones:

  • nudge es un método peligroso por dos motivos:

    • Puede ser confundido con stack

    • Una barra con valores altos puede ocultar otras barras con valores más bajos, como ocurre en la imagen anterior.

  • stack es un método poco efectivo para comunicar. En el ejemplo anterior, España es sencilla de comparar a lo largo de los años. Pero y el resto? Al no estar en la misma escala, hace más complicado que los usuarios de tu gráfica puedan ver los cambios por ejemplo en Italia o Alemania.

  • De los casos vistos, dodge2 es el mejor. Aún así, es mejorable.

  • fill: es una buena opción cuando tiene sentido comparar los grupos en términos relativos (en este caso, no tiene sentido decir que una país tiene el 20% del PIB. El 20% de qué?).

6.2.2 Buenas prácticas

Vamos a volver al ejemplo de las especies de flores en el dataset de iris Figura 6.1, aunque sea un ejemplo muy sencillo, nos sirve para ver algunas buenas prácticas a la hora de generar gráficos de barras. En lugar de utilizar las 150, vamos a filtrar las 60 aleatorias para que no tengamos el mismo número de flores en cada clase:

## Filtrar datos
set.seed(111)
iris_sample <- iris |> 
    slice_sample(n = 60)

Y ahora vamos a generar el gráfico de barras añadiendo mejoras:

Vamos a generar el primer gráfico:

iris_sample |> 
  ggplot(aes(x = Species)) +
  geom_bar() +
  labs(
      title = "Frecuencia de especies de flores",
       x    = "Especies",
       y    = "Frecuencia"
  )
Fig. 6.4: Gráfico de barras generado con geom_bar() para la frecuencia de las especies de Iris

Vamos a empezar haciendo un par de cambios:

  • Cambiar el color de las barras y el tema.

  • Utilizar barras horizontales: esto suele ayudar mucho a la lectura de este tipo de gráfico, sobre todo cuando tenemos muchas categorías o tienen nombres largos. Para ello podemos o bien utilizar y = Species o coord_flip() (siendo este segundo el más común para invertir los ejes).

iris_sample |> 
  ggplot(aes(x = Species)) +
  geom_bar(fill = "#98B6B1") +
  coord_flip() +
  labs(
       x    = NULL,
       y    = "Frecuencia"
  ) +
  theme_minimal()
Fig. 6.5: Gráfico de barras horizontal para la frecuencia de las especies de Iris

En siguiente lugar, cuando se utilizan gráficos de barras, debemos ordenar las barras siguiendo un orden lógico, que será prácticamente siempre según el tamaño de las barras. Para ello, vamos a utilizar la función fct_infreq() del paquete forcats para ordenar las barras según la frecuencia de las especies.

iris_sample |> 
  ggplot(
    aes(x = fct_infreq(Species))
  ) +
  geom_bar(fill = "#98B6B1") +
  coord_flip() +
  labs(
       x    = NULL,
       y    = "Frecuencia"
  ) +
  theme_minimal()
Fig. 6.6: Gráfico de barras horizontales ordenado de menor a mayor para la frecuencia de las especies de Iris

Aunque esto pueda ser lo que queramos, normalmente querremos ordenar de mayor a menor. Para ello, podemos añadir fct_rev() al final de la función, que invierte los niveles de la variable.

iris_sample |> 
  ggplot(
    aes(
      x = fct_infreq(Species) |> fct_rev()
    )
  ) +
  geom_bar(fill = "#98B6B1") +
  coord_flip() +
  labs(
       x    = NULL,
       y    = "Frecuencia"
  ) +
  theme_minimal()
Fig. 6.7: Gráfico de barras horizontales ordenado de mayor a menor para la frecuencia de las especies de Iris

Para finalizar, vamos a cambiar el tamaño de las barras. De verdad es necesario que las barras sean tan gruesas? Normalmente, cuanto más finas sean las barras, más fácil será compararlas. Para ello, vamos a añadir width = 0.4 a geom_bar().

iris_sample |> 
  ggplot(
    aes(
      x = fct_infreq(Species) |> fct_rev()
    )
  ) +
  geom_bar(fill = "#98B6B1", width = 0.4) +
  coord_flip() +
  labs(
       x    = NULL,
       y    = "Frecuencia"
  ) +
  theme_minimal()
Fig. 6.8: Gráfico de barras horizontales ordenado de mayor a menor y con barras más finas para la frecuencia de las especies de Iris

Este sería el resultado final. Todavía se pueden hacer más mejoras, como utilizar mejores etiquetas del eje Y, y otros ajustes de diseño. Pero eso será cuestión de otro capítulo.

6.2.3 Ejercicio 10

Vamos a practicar lo aprendido con un ejercicio. Utiliza los datos de inventario para generar un gráfico de barras que muestre la frecuencia de árboles por parcela siguiendo las buenas prácticas que acabamos de ver.

Fig. 6.9: Gráfico de barras horizontal ordenado de mayor a menor para la frecuencia de árboles por parcela
inventario_tbl |> 
  ggplot(
    aes(
      x = fct_infreq(id_plots) |> fct_rev()
    )
  ) +
  geom_bar(fill = "#98B6B1", width = 0.6) +
  coord_flip() +
  labs(
      title = "Árboles muestreados por parcela",
      x    = NULL,
      y    = NULL
  ) +
  theme_minimal()
Fig. 6.10: Gráfico de barras horizontal ordenado de mayor a menor para la frecuencia de árboles por parcela

6.3 Gráfico de líneas

El gráfico de líneas es una de las formas más comunes de visualizar cambios en una variable numérica a lo largo del tiempo. En este caso, vamos a utilizar el dataset de gapminder para comparar la evolución de la esperanza de vida de algunos países europeos. Para ello, vamos a utilizar la función geom_line().

gapminder |> 
    filter(country %in% c("Spain", "Germany", "France", "Italy", "Portugal")) |>
    ggplot(aes(x = year, y = lifeExp, color = country)) +
    geom_line(
      linewidth = 1
    ) +
    labs(
        x     = NULL,
        y     = "Esperanza de vida",
        color = NULL
    ) +
    scale_color_viridis_d() +
    theme_minimal()
Fig. 6.11: Gráfico de líneas para la evolución de la esperanza de vida en algunos países europeos

6.3.1 Spaguetti plot

El spaghetti plot es un tipo de gráfico de líneas en el que se representan muchas líneas en un mismo gráfico de forma que no nos permite ver ninguna tendencia en los datos. El siguiente gráfico es un ejemplo de spaghetti plot:

gapminder |> 
    ggplot(aes(x = year, y = lifeExp, color = continent, group = country)) +
    geom_line() +
    labs(
        x     = NULL,
        y     = "Esperanza de vida",
        color = NULL
    ) +
    theme_minimal()
Fig. 6.12: Gráfico de líneas para la evolución de la esperanza de vida en algunos países europeos

Fíjate que añadimos la estética group = country para que cada país tenga su propia línea (en Figura 6.11 no fue necesario porque ya estábamos mapeando la variable a la estética color). Este gráfico es algo que debes evitar. A partir de unas 6-7 líneas, deberías considerar presentar tus datos de otra forma.

Tip

Los gráficos de líneas son una geometría conjunta, lo que quiere decir que cada línea es un solo objeto geométrico. Los gráficos de puntos por lo contrario, son geometrías individuales, ya que cada observación tiene su propia representación. En las geometrías conjuntas dibujamos una geometría por cada grupo, lo que se traduce en una sola línea para cada país en este caso. Otro ejemplo de geometría conjunta es el boxplot:

Un boxplot es básicamente la distribución de una sola variable numérica:

gapminder |> 
    ggplot(aes(y = lifeExp)) +
    geom_boxplot()

Pero si la agrupamos por continente, tendremos un boxplot por cada valor distinto de esa variable:

gapminder |> 
    ggplot(aes(y = lifeExp, group = continent)) +
    geom_boxplot()

No obstante, al utilizar la estética x, se genera un grupo automáticamente por cada valor de la variable y además añade las etiquetas al eje:

gapminder |> 
    ggplot(aes(y = lifeExp, x = continent)) +
    geom_boxplot()

6.3.2 Solución spaghetti plot

La primera solución, es hacer que las líneas sean grises, de forma que no destaquen tanto y hacer visible únicamente la que nos interesa enseñar al público. Por ejemplo, vamos a destacar solamente aquellos países cuyo PIB ha decrecido desde 1952 hasta 2007.

Para ello, seleccionamos las columnas que necesitamos, luego filtramos los dos años que queremos comparar, expandimos la tabla para poder calcular la diferencia y finalmente extraemos los países cuya diferencia sea inferior a 0:

## Filtrar datos
countries_vec <- gapminder |> 
    select(country, year, lifeExp) |> 
    filter(
        year %in% c(1952, 2007)
    ) |> 
    pivot_wider(
        names_from   = year,
        names_prefix = "year_",
        values_from  = lifeExp
    ) |> 
    mutate(
        diff = year_2007 - year_1952
    ) |> 
    filter(
        diff < 0
    ) |> 
    pull(country) |> 
    as.character()
## Ver países
countries_vec
[1] "Swaziland" "Zimbabwe" 

Una vez tenemos estos países, vamos a generar el gráfico destacando solamente estos dos:

gapminder |> 
    ggplot(
        aes(x = year, y = lifeExp, color = continent, group = country)
    ) +
    geom_line(color = "grey80") +
    geom_line(
        aes(x = year, y = lifeExp),
        data  = gapminder |> filter(country %in% countries_vec),
        color = "#BC3908",
        lwd   = 1
    ) +
    labs(
        x     = NULL,
        y     = "Esperanza de vida",
        color = NULL,
        title = "La mayoría de países aumentaron su esperanza de vida entre 1952 y 2007. \nSwaziland y Zimbabwe no"
    ) +
    theme_minimal()
Fig. 6.13: Spaguetti plot destancando los países cuya esperanza de vida ha decrecido

En este caso, fíjate que aunque tengamos un spaguetti plot, estamos resaltando que nuestro mensaje solamente se encuentra en dos líneas, y por lo tanto puede ser un gráfico útil.

Todavía podemos mejorar este gráfico diciendo qué línea representa a cada país. Para ello, podemos utilizar la función geom_textpath() del paquete geomtextpath. Lo único que tenemos que cambiar es el nombre de la geometría y añadir la estética label:

gapminder |> 
    ggplot(
        aes(x = year, y = lifeExp, color = continent, group = country)
    ) +
    geom_line(color = "grey80") +
    geom_textpath(
        aes(x = year, y = lifeExp, label = country),
        data  = gapminder |> filter(country %in% countries_vec),
        color = "#BC3908",
        lwd   = 1
    ) +
    labs(
        x     = NULL,
        y     = "Esperanza de vida",
        color = NULL,
        title = "La mayoría de países aumentaron su esperanza de vida entre 1952 y 2007. \nSwaziland y Zimbabwe no"
    ) +
    theme_minimal()
Fig. 6.14: Spaguetti plot destancando los países cuya esperanza de vida ha decrecido

Y con esto ya tenemos una visualización mucho mejor que la inicial.

6.3.3 Ejercicio 11

Crear un gráfico de líneas donde se vea la evolución del PIB per cápita de los países del mundo (excepto los países de África), destacando solamente aquellos cuyo PIB per cápita se haya reducido entre 1952 y 2007.

Fig. 6.15: Gráfico de líneas para la evolución del PIB per cápita de los países del mundo

En el siguiente bloque de código se muestra como filtrar los países cuyo PIB per cápita ha disminuido entre 1952 y 2007. Utiliza estos datos para generar el gráfico de líneas.

Escribe aquí el código para generar el gráfico:

gapminder |> 
    filter(continent != "Africa") |>
    ggplot(
        aes(x = year, y = gdpPercap, color = country, group = country)
    ) +
    geom_line(color = "grey80") +
    geom_textpath(
        aes(x = year, y = gdpPercap, label = country),
        data  = gapminder |> filter(country %in% countries_vec),
        color = "#BC3908",
        lwd   = 1,
        size  = 3
    ) +
    labs(
        x     = NULL,
        y     = "PIB per cápita",
        color = NULL,
        title = "La mayoría de países aumentaron su PIB per cápita entre 1952 y 2007. \nKuwait, Nicaragua y Haiti no"
    ) +
    theme_minimal()
Fig. 6.16: Gráfico de líneas para la evolución del PIB per cápita de los países del mundo

6.4 Resumen

En este capítulo hemos aprendido a generar gráficos de comparación utilizando geom_bar() y geom_col() para gráficos de barras y geom_line() para gráficos de líneas. Hemos visto cómo utilizar el argumento position para posicionar las barras y cómo evitar el spaghetti plot.